Context :
I make an Json api with laravel 5.1 to fetch all of categories and sub categories
and the relationship between categories table and subcategories table is ManyToMany
Code :
Categories model :
public function subcategory() {
return $this->belongsToMany(Subcategory::class);
}
Subcategory model :
public function category() {
return $this->belongsToMany(Categories::class);
}
Api Controller :
$response = Categories::with('subcategory')->first();
return $response
Problem :
I want loop $response to get all data not only the first one
The answer is
$response = Categories::with('subcategory')->take(-1)->get();
You are calling the first method which retrieve only the first model from Categories. In order to get all the records, you should use the get method like so:
$response = Categories::with('subcategory')->get();
Now the $response object will be a Collection of Eloquent models.
The get method is usually the last method of your queries, that acts like: run the query now please Eloquent
Take a look of the documentation about Eloquent Collection
Related
How do write this eloquent query in Laravel so that it eager loads with() the relationship model in this example between a User model and Profile model? I was trying to avoid 2 separate queries.
I feel I am close, but somethings not quite right.
$author = User::where('id', $id)->with('profile')->get();
The collection is returning the user details correctly. But it's showing the profile relationship as null.
#relations: array:1 [▼
"profile" => null
]
I believe I have things setup correctly with a User model and a Profile needed relationships.
User.php
public function profile()
{
return $this->hasOne('App\AuthorProfile', 'user_id');
}
AuthorProfile.php
public function user()
{
return $this->belongsTo('App\User');
}
Assuming for AuthorProfile model table you have record with id of user it should be fine.
However you wrote:
I was trying to avoid 2 separate queries.
Well, it's not true, if you have single record, eager loading won't help you at all. In this case 2 queries will be execute - no matter if you use eager loading or you won't.
Eager loading would help if you had multiple users and for each of them you wanted to load profile, but if you have single record it won't change anything.
Additionally instead of:
$author = User::where('id', $id)->with('profile')->get();
you should rather use:
$author = User::with('profile')->find($id);
because you expect here single user.
$users = User::with('profile')->find($id);
Your model should be like this.The User_id on the profile table and id on the user table
public function profile()
{
return $this->hasOne('App\AuthorProfile', 'user_id','id');
}
Model users has onetomany relation with category
public function getCategory()
{
return $this->hasMany(Category::className(), ['user_id' => 'user_id']);
}
I am not being able to access the fields of category using this relation.
public function actionGetResults()
{
$results = users::find()->where(['user_id' =>8])
->with('category')
->all();
echo "<pre>"; print_r($results);
}
$results here shows an array with fields of category. i.e category_name, category_id etc. but if i do this:
echo "<pre>"; print_r($results->category_name);
it fails.
First of all, since relation type is has many, category name is misleading and it's better to use categories.
Second, you are accessing related field wrong. $results is an array of User models and categories property of each model contains an array of related Category models to that user.
Use nested foreach loop:
foreach ($results as $user) {
foreach ($user->categories as $category) {
var_dump($category->name);
}
}
Or to get the name of the first user's first category:
$results[0]->categories[0]->name
Note that the second approach is only for demonstration purposes and can fail with Undefined index error if user or / and category does not exist.
Read Working with Relational Data for more info.
I am dealing with the following situation: I have two models, an Employee with id and name fields and a Telephone with id, employee_id and flag fields. There is also an one-to-many relationship between these two models, that is an employee may have many telephones and a telephone may belong to a single employee.
class Employee extends Model
{
public function telephones()
{
return $this->hasMany(Telephone::class);
}
}
class Telephone extends Model
{
public function employee()
{
return $this->belongsTo(Employee::class);
}
}
The Employee model references a table employees that exists in database schema named mydb1, while the Telephone model is related to a telephones table that exists in a different database schema named mydb2.
What I want is to fetch only the employees with at least one telephone of a specific flag eager loaded, using Eloquent and (if possible) not the query builder
What I tried so far without success is:
1) use the whereHas method in the Controller
$employees = Employee::whereHas('telephones', function ($query) {
$query->where('flag', 1); //Fetch only the employees with telephones of flag=1
})->with([
'telephones' => function ($query) { //Eager load only the telephones of flag=1
$query->where('flag', 1);
}
])->get();
What I try to do here is first to retrieve only the employees that have telephones with flag=1 and second to eager load only these telephones, but I get the following query exception because of the different db connections used:
Base table or view not found: Table mydb1.telephones doesn't exist (this is true, telephones exists in mydb2)
2) Eager load with constrains in the Controller
$employees = Employee::with([
'telephones' => function ($query) {
$query->where('flag', 1);
},
])->get();
This method eager loads the telephones with flag=1, but it returns all the employee instances, which is not what I really want. I would like to have a collection of only the employee models that have telephones with flag = 1, excluding the models with telephones = []
Taking into account this post, this post and #Giedrius Kiršys answer below, I finally came up with a solution that fits my needs, using the following steps:
create a method that returns a Relation object in the Model
eager load this new relationship in the Controller
filtered out the telephones of flag != 1 using a query scope in the Model
In Employee model
/**
* This is the new relationship
*
*/
public function flaggedTelephones()
{
return $this->telephones()
->where('flag', 1); //this will return a relation object
}
/**
* This is the query scope that filters the flagged telephones
*
* This is the raw query performed:
* select * from mydb1.employees where exists (
* select * from mydb2.telephones
* where telephones.employee_id = employee.id
* and flag = 1);
*
*/
public function scopeHasFlaggedTelephones($query, $id)
{
return $query->whereExists(function ($query) use ($id) {
$query->select(DB::raw('*'))
->from('mydb2.telephones')
->where('telephones.flag', $flag)
->whereRaw('telephones.employee_id = employees.id');
});
}
In the Controller
Now I may use this elegant syntax a’la Eloquent
$employees = Employee::with('flaggedTelephones')->hasFlaggedTelephones()->get();
which reads like "Fetch all the employees with flagged telephones eager loaded, and then take only the employees that have at least one flagged telephone"
EDIT:
After dealing with the Laravel framework for a while (current version used 5.2.39), I figured, that in fact, whereHas() clauses do work in case of the relationship model exists in a different database using the from() method, as it is depicted below:
$employees = Employee::whereHas('telephones', function($query){
$query->from('mydb2.telephones')->where('flag', 1);
})->get();
#Rob Contreras credits for stating the use of the from() method, however it looks like the method requires to take both the database and the table as an argument.
Not sure if this will work but you can use the from method to specify your database connection within the closure:
$employees = Employee::whereHas('telephones', function($query){
$query->from('mydb2')->where('flag', 1);
})->get();
Hope this helps
Dirty solution:
Use whereExists and scope for better readability.
In Your Employee model put:
public function scopeFlags($query, $flag)
{
$query->whereExists(function ($q) use ($flag) {
$q->select(\DB::raw(1))
->from('mydb2.telephones')
->where('telephones.flag', $flag)
->whereRaw('telephones.employee_id = employees.id');
});
}
Then modify your query like so:
$employees = Employee::flags(1)->get();
How can I add where condition to my Articles model so that slug(From category model) is equal to $slug?
And this is a function that Gii generated:
public function getCategory()
{
return $this->hasOne(Categories::className(), ['id' => 'category_id']);
}
Here's my code:
public function specificItems($slug)
{
$query = Articles::find()->with('category');
$countQuery = clone $query;
$pages = new Pagination(['totalCount' => $countQuery->count(),'pageSize' => 12]);
$articles = $query->offset($pages->offset)
->limit($pages->limit)
->all();
return ['articles' => $articles,'pages' => $pages];
}
Your SQL query should contain columns from both article and category table. For that you need to use joinWith().
$result = Articles::find()
->joinWith('category')
->andWhere(['category.slug' => $slug])
->all();
Where 'category' is then name of your category table.
However, in your code you deviate from certain best practices. I would recommend the following:
Have both table name and model class in singular (Article and article). A relation can be in plural, like getCategories if an article has multiple categories.
Avoid functions that return result sets. Better return ActiveQuery class. If you have a query object, all you need to get the actual models is ->all(). However, you can further manipulate this object, add more conditions, change result format (->asArray()) and other useful stuff. Returning array of results does not allow that.
Consider extending ActiveQuery class into ArticleQuery and implementing conditions there. You'll then be able to do things like Article::find()->joinWith('category')->byCategorySlug('foo')->all().
How I can return object with all relations(ans sub objects relations?).
Now I use EJsonBehavior but it returns only first level relations, not sub related objects.
My source code:
$order = Order::model()->findByPk($_GET['id']);
echo $order->toJSON();
Yii::app()->end();
The eager loading approach retrieves the related AR instances together with the main AR instance(s). This is accomplished by using the with() method together with one of the find or findAll methods in AR. For example,
$posts=Post::model()->with('author')->findAll();
The above code will return an array of Post instances. Unlike the lazy approach, the author property in each Post instance is already populated with the related User instance before we access the property. Instead of executing a join query for each post, the eager loading approach brings back all posts together with their authors in a single join query!
We can specify multiple relationship names in the with() method and the eager loading approach will bring them back all in one shot. For example, the following code will bring back posts together with their authors and categories:
$posts=Post::model()->with('author','categories')->findAll();
We can also do nested eager loading. Instead of a list of relationship names, we pass in a hierarchical representation of relationship names to the with() method, like the following,
$posts=Post::model()->with(
'author.profile',
'author.posts',
'categories')->findAll();
The above example will bring back all posts together with their author and categories. It will also bring back each author's profile and posts.
Eager loading may also be executed by specifying the CDbCriteria::with property, like the following:
$criteria=new CDbCriteria;
$criteria->with=array(
'author.profile',
'author.posts',
'categories',
);
$posts=Post::model()->findAll($criteria);
or
$posts=Post::model()->findAll(array(
'with'=>array(
'author.profile',
'author.posts',
'categories',
)
);
I found the solution for that. you can use $row->attributes to create data
$magazines = Magazines::model()->with('articles')->findAll();
$arr = array();
$i = 0;
foreach($magazines as $mag)
{
$arr[$i] = $mag->attributes;
$arr[$i]['articles']=array();
$j=0;
foreach($mag->articles as $article){
$arr[$i]['articles'][$j]=$article->attributes;
$j++;
}
$i++;
}
print CJSON::encode(array(
'code' => 1001,
'magazines' => $arr,
));
This is the best piece of code I found after a long time search to meet this requirement.
This will work like Charm.
protected function renderJson($o) {
//header('Content-type: application/json');
// if it's an array, call getAttributesDeep for each record
if (is_array($o)) {
$data = array();
foreach ($o as $record) {
array_push($data, $this->getAttributes($record));
}
echo CJSON::encode($data);
} else {
// otherwise just do it on the passed-in object
echo CJSON::encode($this->getAttributes($o));
}
// this just prevents any other Yii code from being output
foreach (Yii::app()->log->routes as $route) {
if ($route instanceof CWebLogRoute) {
$route->enabled = false; // disable any weblogroutes
}
}
Yii::app()->end();
}
protected function getAttributes($o) {
// get the attributes and relations
$data = $o->attributes;
$relations = $o->relations();
foreach (array_keys($relations) as $r) {
// for each relation, if it has the data and it isn't nul/
if ($o->hasRelated($r) && $o->getRelated($r) != null) {
// add this to the attributes structure, recursively calling
// this function to get any of the child's relations
$data[$r] = $this->getAttributes($o->getRelated($r));
}
}
return $data;
}