eloquent use ->with() with only retrieving one column? - laravel

e.g.
i want to retrieve all users with the relation roles, but only the field of role name.
something like this:
User::with('user_role', 'user_role.name')
does something like this exist? i have looked around and don't seem to find something related. The performance might be better if you can filter down the returned columns

Yes, you can use something like this:
$user = User::with('user_role:foreign_key,name')->find(1);
In this case, the foreign_key should be the name of the foreign key that is used to build the relation and it's required here and then you may pass other field names to select them by separating with comma.
This is not documented so be careful, it could be removed in the newer versions. It exists there and below is the code sample, taken from Laravel-5.3 (Illuminate\Database\Eloquent\Builder), it works tho (This is how I've used it: User::with('messages:recipient_id,body')->get()):
/**
* Parse a list of relations into individuals.
*
* #param array $relations
* #return array
*/
protected function parseWithRelations(array $relations)
{
$results = [];
foreach ($relations as $name => $constraints) {
// If the "relation" value is actually a numeric key, we can assume that no
// constraints have been specified for the eager load and we'll just put
// an empty Closure with the loader so that we can treat all the same.
if (is_numeric($name)) {
if (Str::contains($constraints, ':')) {
list($constraints, $columns) = explode(':', $constraints);
$f = function ($q) use ($columns) {
$q->select(explode(',', $columns));
};
} else {
$f = function () {
//
};
}
list($name, $constraints) = [$constraints, $f];
}
// We need to separate out any nested includes. Which allows the developers
// to load deep relationships using "dots" without stating each level of
// the relationship with its own key in the array of eager load names.
$results = $this->parseNestedWith($name, $results);
$results[$name] = $constraints;
}
return $results;
}

You can add constraints to eager loaded relations by supplying a with array with closure as the value with the relation as the key.
$user = User::with(['user_role' => function($query) {
return $query->select('name');
}]);

Related

How to compare two objects and get different columns in Laravel

I have two objects of the same record which I am getting from the database. One is before the update, and the other is after the update. I want to know the column values which are changed during this update query.
$before_update = DeliveryRun::find($id);
$before_update->name = $request->input('name');
$before_update->save();
$after_update = DeliveryRun::find($id);
compare($before_update, $after_update)
I would define a method on your DeliveryRun model which can be used to compare objects of the same type.
Lets say we want to be able to do something like $deliveryRun->compareTo($otherDeliveryRun). That seems like a nice fluid syntax and reads well in my opinion.
What we want to do is get the attributes and their values for the DeliveryRun we're calling compareTo on and then compare them against the attributes and values for the DeliveryRun we provide as an arguement to the compareTo method.
class DeliveryRun extends Model
{
public function compareTo(DeliveryRun $other)
{
$attributes = collect($this->getAttributes())
->map(function ($attribute, $key) use ($other) {
if ($attribute != $other->$key) {
return $key = $attribute;
}
})->reject(function ($attribute, $key) {
return !$attribute || in_array($key, ['id', 'created_at', 'updated_at']);
});
return $attributes;
}
}
The above gets the attributes for the current ($this) DeliveryRun, converts the array returned from getAttributes() to a collection so we can use the map() function and then loops over each attribute on the DeliveryRun model comparing the key and value of each against the $other DeliveryRun model provided.
The reject() call is used to remove attributes which are the same and some attribute keys which you might not be interested in leaving you just the attributes that have changed.
Update
I am saving object in other variable before update $before_update = $delivery_run; but after update $before_update variable I also gets updated
If I am understanding you correctly, you're still comparing the same object to itself. Try something like the following.
$before = clone $delivery_run; // use clone to force a copy
$delivery_run->name = 'something';
$delivery_run->save();
$difference = $before->compareTo($delivery_run);
I would consider using getChanges() as suggested by #Clément Baconnier if all you're doing is looking to get the changes of an object straight after the object has been saved/updated.

Eager load for collections on the fly

I have this, user has many to many property via pivot property_users.
I am making somehow reusable classes in my webapp.
These are the models with their eager loading functions:
//User model
public function properties()
{
return $this->belongsToMany(Property::class, 'property_users', 'user_id', 'property_id');
}
//Property model
public function property_users()
{
return $this->hasMany(PropertyUser::class, 'property_id', 'id');
}
//PropertyUser model
public function user()
{
return $this->belongsTo(User::class);
}
//GetProperties class
public function handle()
{
return auth()->user()->properties()->get();
}
//somewhere in a feature
$properties = $this->run(GetProperties::class);
//this returns valid properties under the logged in user
I now need to get the chat_username in property_users that belongs to this user
I manage to make it work if I loop through the properties and then doing it on the fly.
$properties = $properties->map(function($property) {
$propertyUsers = $property->property_users()->get();
$chatUsername = null;
foreach($propertyUsers as $propertyUser) {
if($propertyUser->property_id == $property->id) {
$chatUsername = $propertyUser->chat_username;
}
}
return [
'name' => $property->name,
'id' => $property->id,
'chat_username' => $chatUsername
];
});
But I am trying to reduce query on loop to reduce hits especially when they are on multiple properties in the database.
The other way is that I can add the property_users in the eager loading under GetProperties class by updating it to:
$query = Property::query();
$query->with(['property_users']);
$query->whereHas('property_users', function($qry) {
$qry->where('user_id', Auth::user()->id);
});
$properties = $query->get();
return $properties;
But I do not want to rely on adding more eager loading to the original GetProperties class as the GetProperties will get fat and I do not really need those data (let's say adding property_invoices, property_schedules, etc but not really needing it in some area).
Rather, I want to do the eager loading on the fly but with a twist! This is how I would imagine it:
Collect all the ids from the properties, do the fetch using wherein and apply all the users to the properties in a single query. This way it will be even more beautiful.
Maybe something like this: (using the original GetProperties class)
$properties = $this->run(GetProperties::class);
//this does not work. The error is: Method Illuminate\Database\Eloquent\Collection::property_users does not exist.
$property->property_users = $properties->property_users()->get();
Would be great if someone can show me how to do it.
What about eager loading only the fields you actually need?
$query->with('property_users:id,user_id');
The model will not get fat, and you will not need to do separate queries in the loop.
It is documented in the official documentation: https://laravel.com/docs/5.8/eloquent-relationships#eager-loading , see Eager Loading Specific Columns
Edit: if you want to perform the query after the GetProperties class, you will need to collect all the ids and perform a second query. I honestly don't like this second approach, because it is far slower, less performant and I consider it less elegant then adding a single line in the GetProperties class, but it is gonna work:
$properties = $this->run(GetProperties::class);
$ids = $properties->pluck('id'); // Get all the ids as an array
$propertyUsers = propertyUsers::whereIn('property_id', $ids)->get(); // Get the propertyUsers model
foreach($properties as $property) {
$property->property_users = $propertyUsers->where('property_id', $property->id); // Not even sure you can do that, proerty_users may not been writable
}
Alright, after reading here and there.
I found this article: https://laravel-news.com/eloquent-eager-loading
The solution is really nice. Simply:
$properties = $this->run(GetProperties::class);
//lazy load
$properties->load('property_users');
$properties = $properties->map(function($property) {
$user = $property->property_users->first(function($user) {
if($user->user_id == Auth::user()->id) {
return $user;
}
})->only('chat_username');
return [
'name' => $property->name,
'id' => $property->id,
'chat_username' => $user['chat_username']
];
});
After checking query logs:
//auth query done by middleware
[2019-05-21 07:59:11] local.INFO: select * from `users` where `auth_token` = ? or `temporary_auth_token` = ? limit 1 ["token_removed_for_security_purpose","token_removed_for_security_purpose"]
//These are the queries made:
[2019-05-21 07:59:11] local.INFO: select `properties`.*, `property_users`.`user_id` as `pivot_user_id`, `property_users`.`property_id` as `pivot_property_id` from `properties` inner join `property_users` on `properties`.`id` = `property_users`.`property_id` where `property_users`.`user_id` = ? [8]
[2019-05-21 07:59:11] local.INFO: select * from `property_users` where `property_users`.`property_id` in (2, 4)
This way, I can keep my GetProperties as small as possible, then just lazy load it whereever I need it.

Laravel firstOrCreate without Eloquent

Eloquent has a firstOrCreate method which gets a model based on a condition, or creates it if it doesn't exist.
Is there any equivalent method in Laravel's query builder (i.e. NOT in Eloquent)? For example:
$row = DB::table('users')->where('user_id', 5)->firstOrCreate('name' => 'Peter', 'last_name' => 'Pan');
That would try to get a row from users with 'user_id'==5. If it doesn't exist, it would insert a row with that id number, plus the other mentioned fields.
EDIT: I'm not trying to apply my question with users. I used users as an example to make as clear as possible what I'm looking for.
updateOrInsert function with empty values give me the result like firstOrCreate
Nope, Laravel firstOrCreate is function, that says next:
public function firstOrCreate(array $attributes, array $values = [])
{
if (! is_null($instance = $this->where($attributes)->first())) {
return $instance;
}
return tap($this->newModelInstance($attributes + $values), function ($instance) {
$instance->save();
});
}
But you can add it with query micro:
DB::query()->macro('firstOrCreate', function (array $attributes, array $values = [])
{
if ($record = $this->first()) {
// return model instance
}
// create model instance
});
So than you will be able to call it same way you do with Eloquent.
$record= DB::table('records')->where('alias', $alias)->firstOrFail();
Yeah of course! Just use normal SQL and ->selectRaw( your conditions ) and look for if there is a entry where your specifications are.
https://laravel.com/docs/5.7/queries#raw-expressions

Best way to do this listing laravel with relationship

What is the best way to do this listing?
I would not want to do it that way "ugly".
/**
* Get user indicateds
* #return array|null
*/
static public function indicateds()
{
$users = ModelUser::all();
foreach( $users as $user ) {
if( $user->financial->status_payment ) {
$newArray[] = $user;
}
}
return (isset($newArray) ? $newArray : null);
}
Thanks
You can use the collection's filter method:
return ModelUser::with('financial')
->get()
->filter(function($user) {
return $user->financial->status_payment;
});
I'm supposing you have defined the financial relation and you should eager load it as I did to improve the performance.
One of the benefits to relationships is that you can use them to modify your queries, as well. So, instead of getting all users into a Collection, and then filtering that Collection, you can use the relationship to modify the query so that you only get the desired records in the first place. This will reduce the number of records returned from the database, as well as the number of model instances that get created. This will save you time and memory.
$users = ModelUser::with('financial')
->whereHas('financial', function($q) {
// $q is the query for the financial relationship;
return $q->where('status_payment', true);
}
->get();
The with() is not required, but if you'll be accessing the financial relationship on the returned users, it is a good idea to eager load it.
The whereHas() is where the magic happens. It modifies the query so that it will only return users that have a related financial record that matches the conditions added by the closure used in the second parameter.
You can read more about it in the documentation here.

Laravel ORM should return Relation

Is it possible to return an value for an hasOne relation directly with an Model?
For example:
$List = Element::orderBy('title')->get();
The Element has a "hasOne" Relation to an column:
public function type()
{
return $this->hasOne('App\Type', 'id', 'type_id');
}
How can i now return automatically the "type" for the Model?
At the Moment i am looping through all Elements, and build my own "Array" of Objects, including the Key of "type" in this example. But ill prefer to do this only in my Model.
Ill know how to add a "normal" property, but can it be someone from an relation?
public function getTypeAttribute($value)
{
return // how ?
}
protected $appends = array('type');
Is this possible?
Edit:
A workaround could be to use DB:: to return the correct value - but ill dont thing thats a good workaround: like:
public function getTypeAttribute($value)
{
// make a query with $this->type_id and return the value of the type_name
}
protected $appends = array('type');
you need to eager load your relations when getting the Element:
$list = Element::with('type')->orderBy('title')->get();
then access the type using
foreach ($list as $item) {
echo $item->type->type_name;
}
where type_name would be the name of a column in the types table
Make a query scope and in that scope, join your attributes from other tables.
http://laravel.com/docs/5.1/eloquent#query-scopes
http://laravel.com/docs/5.1/queries#joins

Resources