How to select specific columns with eloquent "with" - php

Is there a way to select only specific columns when using "with" with eloquent?
My problem is that i am getting a very long list of accounts and i also need to get their websites using the relation "with", but the "websites" object is big and it makes the request very slow.
I tried using:
$accounts = $accounts->with('websites')->select(['account_id','url']);
And also:
$accounts = $accounts->with('websites', function($query) {
$query->select(['account_id','url']);
});
And they both didn't work.
I didn't see an answer for this in their documentation, so if there is a way i would really appreciate if someone would share it with me. Also if there is an alternative for "with" that can give the same result it will be great as well. Thanks.

$accounts = $accounts->with([
'websites' => function($query) {
$query->select(['account_id','url']);
}
]);

Try to define the columns on your relationship:
class Account
{
public function websites()
{
return $this->hasMany('App\Website')->select(['account_id','url']);
}
}
Please note that the class and the relation is only for demonstrating purposes, try to add this ->select(['account_id','url']) on your relation.

Related

How to set alias table name in Laravel table model

I have this query builder in Laravel:
$obj_table = MyTable::get();
Now, how to make my MyTable to have alias name?
The reason I need this, I need to use with() and Joining with my another tables.
Anyone got the same problems with me?
Thanks!
From laravel documentaion
You may also alias the relationship count result, allowing multiple counts on the same relationship:
$posts = Post::withCount([
'comments',
'comments AS pending_comments' => function ($query) {
$query->where('approved', false);
}
])->get();
For more information have a look at
https://laravel.com/docs/5.4/eloquent-relationships
Cheers,
Hope this helps

Check if belongsToMany relation exists - Laravel

Two of my tables (clients and products) have a ManyToMany relation using Laravel's blongToMany and a pivot table.
Now I want to check if a certain client has a certain product.
I could create a model to check in the pivot table but since Laravel does not require this model for the belongsToMany method I was wondering if there is another way to check if a certain relationship exists without having a model for the pivot table.
I think the official way to do this is to do:
$client = Client::find(1);
$exists = $client->products->contains($product_id);
It's somewhat wasteful in that it'll do the SELECT query, get all results into a Collection and then finally do a foreach over the Collection to find a model with the ID you pass in. However, it doesn't require modelling the pivot table.
If you don't like the wastefulness of that, you could do it yourself in SQL/Query Builder, which also wouldn't require modelling the table (nor would it require getting the Client model if you don't already have it for other purposes:
$exists = DB::table('client_product')
->whereClientId($client_id)
->whereProductId($product_id)
->count() > 0;
The question is quite old but this may help others looking for a solution:
$client = Client::find(1);
$exists = $client->products()->where('products.id', $productId)->exists();
No "wastefulness" as in #alexrussell's solution and the query is more efficient, too.
Alex's solution is working one, but it will load a Client model and all related Product models from DB into memory and only after that, it will check if the relationship exists.
A better Eloquent way to do that is to use whereHas() method.
1. You don't need to load client model, you can just use his ID.
2. You also don't need to load all products related to that client into memory, like Alex does.
3. One SQL query to DB.
$doesClientHaveProduct = Product::where('id', $productId)
->whereHas('clients', function($q) use($clientId) {
$q->where('id', $clientId);
})
->count();
Update: I did not take into account the usefulness of checking multiple relations, if that is the case then #deczo has a way better answer to this question. Running only one query to check for all relations is the desired solution.
/**
* Determine if a Client has a specific Product
* #param $clientId
* #param $productId
* #return bool
*/
public function clientHasProduct($clientId, $productId)
{
return ! is_null(
DB::table('client_product')
->where('client_id', $clientId)
->where('product_id', $productId)
->first()
);
}
You could put this in you User/Client model or you could have it in a ClientRepository and use that wherever you need it.
if ($this->clientRepository->clientHasProduct($clientId, $productId)
{
return 'Awesome';
}
But if you already have defined the belongsToMany relationship on a Client Eloquent model, you could do this, inside your Client model, instead:
return ! is_null(
$this->products()
->where('product_id', $productId)
->first()
);
#nielsiano's methods will work, but they will query DB for every user/product pair, which is a waste in my opinion.
If you don't want to load all the related models' data, then this is what I would do for a single user:
// User model
protected $productIds = null;
public function getProductsIdsAttribute()
{
if (is_null($this->productsIds) $this->loadProductsIds();
return $this->productsIds;
}
public function loadProductsIds()
{
$this->productsIds = DB::table($this->products()->getTable())
->where($this->products()->getForeignKey(), $this->getKey())
->lists($this->products()->getOtherKey());
return $this;
}
public function hasProduct($id)
{
return in_array($id, $this->productsIds);
}
Then you can simply do this:
$user = User::first();
$user->hasProduct($someId); // true / false
// or
Auth::user()->hasProduct($someId);
Only 1 query is executed, then you work with the array.
The easiest way would be using contains like #alexrussell suggested.
I think this is a matter of preference, so unless your app is quite big and requires a lot of optimization, you can choose what you find easier to work with.
Hello all) My solution for this problem: i created a own class, extended from Eloquent, and extend all my models from it. In this class i written this simple function:
function have($relation_name, $id) {
return (bool) $this->$relation_name()->where('id','=',$id)->count();
}
For make a check existing relation you must write something like:
if ($user->have('subscribes', 15)) {
// do some things
}
This way generates only a SELECT count(...) query without receiving real data from tables.
To check the existence of a relationship between 2 models, all we need is a single query against the pivot table without any joins.
You can achieve it using the built-in newPivotStatementForId method:
$exists = $client->products()->newPivotStatementForId($product->id)->exists();
use trait:
trait hasPivotTrait
{
public function hasPivot($relation, $model)
{
return (bool) $this->{$relation}()->wherePivot($model->getForeignKey(), $model->{$model->getKeyName()})->count();
}
}
.
if ($user->hasPivot('tags', $tag)){
// do some things...
}
This has time but maybe I can help someone
if($client->products()->find($product->id)){
exists!!
}
It should be noted that you must have the product and customer model, I hope it helps,

Laravel eloquent query with several layers of model relationships

I'm having some trouble doing eloquent database queries in Laravel. I have searched around a lot, but I don't know exactly what to search for to get my answer.
I'm making a web page in Laravel for kindergardens, and I need to get all children connected to a kindergarden in a single collection/array. The problem is that children are only connected to the kindergarden through their group(s). So the query needs to be able to get all children that are part of any group connected to the kindergarden.
The model relationships are like this:
A kindergarden has many groups, and groups belong to a kindergarden.
A group has many children, and children have many groups.
The best I've managed to do so far is this:
public function getChildren(){
$kid = Session::get('kid');
$k = Kindergarden::find($kid);
$groups = $k->group;
$children;
foreach($groups as $g){
$children = $children->merge($g->children);
}
$children = $children->toArray();
return Response::json($children);
}
But this makes duplicates when children are in several groups in the same kindergarden. It also seems like an unnecessary complicated way to do it.
For a while I also tried to get the hasManyThrough-relationship to work, but it doesn't seem to work when there'a a many-to-many relationship and a pivot table involved.
I tried with this:
class Kindergarden extends Eloquent {
public function children()
{
return $this->hasManyThrough('Children', 'Group', 'kid', 'gid');
}
}
and then tried to call
Kindergarden::find(1)->children;
I'm sure there is a really simple way to do this, but I'm totally new to laravel and not really that great at sql, so I haven't been able to find anything to help me figure this out.
Edit:
Managed to find a way to do it using Fluent:
$children = DB::table('children')
->join('children_group', 'children.chiid', '=', 'children_group.chiid')
->join('group', 'group.gid', '=', 'children_group.gid')
->where('group.kid', '=', $kid)
->groupby('children.chiid')
->get();
Still want to be able to do it using Eloquent, though.
In your Kindergarden class:
public function groups()
{
$this->hasMany('Groups', 'group_id', 'id');
}
In your Group class:
public function children()
{
$this->hasMany('Children', 'child_id', 'id')
}
Then try:
$children = Kindergarden::with('groups.children')->get();
This way you could go through each group in Kindergarden, and display each child that is apart of each group.
Also just a suggestion: In your database tables, I would name your related id's (such as a group ID in your Kindergarden table) group_id, instead of gid. Makes it much more readable and you know exactly what it's related to. Same with your child id, name it 'child_id' instead of 'chiid'.
EDIT: Since I don't know your table structure, take the functions above with a grain of salt. I hope what I suggested gets you on the right path! Let me know if it helps

Laravel 4 - Eloquent way to attach a where clause to a relationship when building a collection

This may be a dupe but I've been trawling for some time looking for a proper answer to this and haven't found one yet.
So essentially all I want to do is join two tables and attach a where condition to the entire collection based on a field from the joined table.
So lets say I have two tables:
users:
-id
-name
-email
-password
-etc
user_addresses:
-address_line1
-address_line2
-town
-city
-etc
For the sake of argument (realising this may not be the best example) - lets assume a user can have multiple address entries. Now, laravel/eloquent gives us a nice way of wrapping up conditions on a collection in the form of scopes, so we'll use one of them to define the filter.
So, if I want to get all the users with an address in smallville, I may create a scope and relationships as follows:
Users.php (model)
class users extends Eloquent{
public function addresses(){
return $this->belongsToMany('Address');
}
public function scopeSmallvilleResidents($query){
return $query->join('user_addresses', function($join) {
$join->on('user.id', '=', 'user_addresses.user_id');
})->where('user_addresses.town', '=', 'Smallville');
}
}
This works but its a bit ugly and it messes up my eloquent objects, since I no longer have a nice dynamic attribute containing users addresses, everything is just crammed into the user object.
I have tried various other things to get this to work, for example using a closure on the relationship looked promising:
//this just filters at the point of attaching the relationship so will display all users but only pull in the address where it matches
User::with(array('Addresses' => function($query){
$query->where('town', '=', 'Smallville');
}));
//This doesnt work at all
User::with('Addresses')->where('user_addresses.town', '=', 'Smallville');
So is there an 'Eloquent' way of applying where clauses to relationships in a way that filters the main collection and keeps my eloquent objects in tact? Or have I like so many others been spoiled by the elegant syntax of Eloquent to the point where I'm asking too much?
Note: I am aware that you can usually get round this by defining relationships in the other direction (e.g. accessing the address table first) but this is not always ideal and not what i am asking.
Thanks in advance for any help.
At this point, there is no means by which you can filter primary model based on a constraint in the related models.
That means, you can't get only Users who have user_address.town = 'Smallwille' in one swipe.
Personally I hope that this will get implemented soon because I can see a lot of people asking for it (including myself here).
The current workaround is messy, but it works:
$products = array();
$categories = Category::where('type', 'fruit')->get();
foreach($categories as $category)
{
$products = array_merge($products, $category->products);
}
return $products;
As stated in the question there is a way to filter the adresses first and then use eager loading to load the related users object. As so:
$addressFilter = Addresses::with('Users')->where('town', $keyword)->first();
$users= $addressFilter->users;
of course bind with belongsTo in the model.
///* And in case anyone reading wants to also use pre-filtered Users data you can pass a closure to the 'with'
$usersFilter = Addresses::with(array('Users' => function($query) use ($keyword){
$query->where('somefield', $keyword);
}))->where('town', $keyword)->first();
$myUsers = $usersFilter->users;

Laravel Eloquent: How to get only certain columns from joined tables

I have got 2 joined tables in Eloquent namely themes and users.
theme model:
public function user() {
return $this->belongs_to('User');
}
user model:
public function themes() {
return $this->has_many('Theme');
}
My Eloquent api call looks as below:
return Response::eloquent(Theme::with('user')->get());
Which returns all columns from theme (that's fine), and all columns from user (not fine). I only need the 'username' column from the user model, how can I limit the query to that?
Change your model to specify what columns you want selected:
public function user() {
return $this->belongs_to('User')->select(array('id', 'username'));
}
And don't forget to include the column you're joining on.
For Laravel >= 5.2
Use the ->pluck() method
$roles = DB::table('roles')->pluck('title');
If you would like to retrieve an array containing the values of a single column, you may use the pluck method
For Laravel <= 5.1
Use the ->lists() method
$roles = DB::table('roles')->lists('title');
This method will return an array of role titles. You may also specify a custom key column for the returned array:
You can supply an array of fields in the get parameter like so:
return Response::eloquent(Theme::with('user')->get(array('user.username'));
UPDATE (for Laravel 5.2)
From the docs, you can do this:
$response = DB::table('themes')
->select('themes.*', 'users.username')
->join('users', 'users.id', '=', 'themes.user_id')
->get();
I know, you ask for Eloquent but you can do it with Fluent Query Builder
$data = DB::table('themes')
->join('users', 'users.id', '=', 'themes.user_id')
->get(array('themes.*', 'users.username'));
This is how i do it
$posts = Post::with(['category' => function($query){
$query->select('id', 'name');
}])->get();
First answer by user2317976 did not work for me, i am using laravel 5.1
Using with pagination
$data = DB::table('themes')
->join('users', 'users.id', '=', 'themes.user_id')
->select('themes.*', 'users.username')
->paginate(6);
Another option is to make use of the $hidden property on the model to hide the columns you don't want to display. You can define this property on the fly or set defaults on your model.
public static $hidden = array('password');
Now the users password will be hidden when you return the JSON response.
You can also set it on the fly in a similar manner.
User::$hidden = array('password');
user2317976 has introduced a great static way of selecting related tables' columns.
Here is a dynamic trick I've found so you can get whatever you want when using the model:
return Response::eloquent(Theme::with(array('user' => function ($q) {
$q->addSelect(array('id','username'))
}))->get();
I just found this trick also works well with load() too. This is very convenient.
$queriedTheme->load(array('user'=>function($q){$q->addSelect(..)});
Make sure you also include target table's key otherwise it won't be able to find it.
This Way:
Post::with(array('user'=>function($query){
$query->select('id','username');
}))->get();
I know that this is an old question, but if you are building an API, as the author of the question does, use output transformers to perform such tasks.
Transofrmer is a layer between your actual database query result and a controller. It allows to easily control and modify what is going to be output to a user or an API consumer.
I recommend Fractal as a solid foundation of your output transformation layer. You can read the documentation here.
In Laravel 4 you can hide certain fields from being returned by adding the following in your model.
protected $hidden = array('password','secret_field');
http://laravel.com/docs/eloquent#converting-to-arrays-or-json
On Laravel 5.5, the cleanest way to do this is:
Theme::with('user:userid,name,address')->get()
You add a colon and the fields you wish to select separated by a comma and without a space between them.
Using Model:
Model::where('column','value')->get(['column1','column2','column3',...]);
Using Query Builder:
DB::table('table_name')->where('column','value')->get(['column1','column2','column3',...]);
If I good understood this what is returned is fine except you want to see only one column. If so this below should be much simpler:
return Response::eloquent(Theme::with('user')->get(['username']));
#You can get selected columns from two or three different tables
$users= DB::Table('profiles')->select('users.name','users.status','users.avatar','users.phone','profiles.user_id','profiles.full_name','profiles.email','profiles.experience','profiles.gender','profiles.profession','profiles.dob',)->join('users','profiles.user_id','=','users.id')
->paginate(10);
Check out, http://laravel.com/docs/database/eloquent#to-array
You should be able to define which columns you do not want displayed in your api.

Categories