Eloquent MorphMany relationship proxy attribute - laravel

In my application I made the strange observation, that a seemingly equal operation has different results to another.
I have the following tables:
aliases:
$table->engine = "InnoDB";
$table->increments("id");
$table->morphs("aliasable");
$table->string("alias");
$table->string("locale");
categories:
$table->engine = "InnoDB";
$table->increments('id');
The Category model has a relationship to the Alias model:
public function aliases()
{
return $this->morphMany("Alias", "aliasable");
}
When trying to manipulate the data of the alias of a category there is a difference between the two following methods:
$category = Category::find(1);
$alias = $category->aliases()->first();
$alias->alias = "test";
$alias->save();
$category = Category::find(1);
$alias = $category->aliases()->first()->alias = "test";
$category->aliases()->first()->save();
The first one is working, the second one is not saving the change.
I would like to get the second version working as I try to implement a proxy attribute on my Category model in order to change the alias like so:
$category = Category::find(1);
$category->germanAlias = "Heidi";
$category->push();
Do you have any idea why my way is not working as expected?

The key thing to understand here is the way relationships are cached on the model. When you access the relationship method directly, then no caching occurs. You are basically instantiating a new Query based off of that relationships definition.
$query = $category->aliases();
$alias = $query->first();
If you access the relationship as if it were a member/attribute of the model, then it will load and cache the relationship as a collection on the model.
$collection = $category->aliases;
$alias = $collection->first();
Future attempts to access the related model by this method will reference the same cached collection of models. This should work.
$category->aliases->first()->alias = 'test';
$category->aliases->first()->save();
Within /Illuminate/Database/Eloquent/Model __get() is a magic method that redirects request for undefined members to the getAttribute() method. The code comments do a pretty good job of explaining the rest but basically it checks for, loads, caches and reuses the relationships.

$category->aliases()->first()->save();
This line select first alias and saves it. Doesnt actually do anything. Your first method is correct. You could also do
$category->aliases()->first()->update(['alias' => 'test']);
if your alias field is included in the $fillable property of the Alias model
EDIT
Run the code below and compare the object ids;
$alias = $category->aliases()->first()->alias = "test";
var_dump(alias);
dd($category->aliases()->first());

Related

Laravel eloquent for four tables

I'm new to Laravel. I am developing a project. and in this project I have 4 tables related to each other
-Users
-Orders
-OrderParcels
-Situations
When listing the parcels of an order, I want to get the information of that order only once, the user information of that order once again, and list the parcels as a table under it. so far everything ok. but I also want to display the status of the parcels listed in the table as names. I couldn't add the 4th table to the query. do you have a suggestion? I'm putting pictures that explain the structure below.
My current working code is
$orderParcels = Orders::whereId($id)
->with('parcels')
->with('users:id,name')
->first();
and my 'orders' model has method
public function parcels(){
return $this->hasMany(OrderParcels::class);
}
public function users(){
return $this->hasOne(User::class,'id','affixer_id');
}
Note[edit]: I already know how to connect like this
$orderParcels = DB::table('order_parcels as op')
->leftjoin('orders as o','op.orders_id','o.id')
->leftjoin('users as u','o.affixer_id','u.id')
->leftjoin('situations as s','op.status','s.id')
->select('op.*','o.*','u.name','s.situations_name')
->where('op.orders_id',$id)->get();
but this is not working for me, for each parcels record it returns me orders and user info. I want once orders info and once user info.
Laravel provides an elegant way to manage relations between models. In your situation, the first step is to create all relations described in your schema :
1. Model Order
class User extends Model {
public function parcels()
{
return $this->hasMany(OrderParcels::class);
}
public function users()
{
return $this->hasOne(User::class,'id','affixer_id');
}
}
2. Model Parcel
class Parcel extends Model {
public function situations()
{
return $this->hasOne(Situation::class, ...);
}
}
Then, you can retrieve all desired informations simply like this :
// Retrieve all users of an order
$users = $order->users; // You get a Collection of User instances
// Retrieve all parcels of an order
$parcels = $order->parcels; // You get a Collection of User instances
// Retrieve the situation for a parcel
$situations = $parcel->situations // You get Situation instance
How it works ?
When you add a relation on your model, you can retrieve the result of this relation by using the property with the same name of the method. Laravel will automatically provide you those properties ! (e.g: parcels() method in your Order Model will generate $order->parcels property.
To finish, in this situation where you have nested relations (as describe in your schema), you should use with() method of your model to eager load all the nested relation of order model like this :
$orders = Orders::with(['users', 'parcels', 'parcels.situations'])->find($id)
I encourage you to read those stubs of Laravel documentation :
Define model relations
Eager loading
Laravel Collection
Good luck !
Use join to make a perfect relations between tables.
$output = Orders::join('users', 'users.id', '=', 'orders.user_id')
->join('order_parcels', 'order_parcels.id', '=', 'orders.parcel_id')
->join('situations', 'situation.id', '=', 'order_parcels.situation_id')
->select([
'orders.id AS order_id',
'users.id AS user_id',
'order.parcels.id AS parcel_id',
'and so on'
])
->where('some row', '=', 'some row or variable')->get();

Grab relation of saved Eloquent model

After I save a Eloquent model, how am I able to retrieve a relationship with it?
So for example:
$model = new Foo;
$model->save();
dd($model); //return an object of Foo
Let's say I've set an relation to Baz, I should grab the relation like a normal Eloquent Object.
dd($model->with('baz')->get());
But this is returning every Foo record in de database with the relationship.
I just want to be able to get the current Baz model which is related to the saved Foo model.
I could do:
$result = Foo::with('baz')->find($model->id);
dd($result);
But this results in another query, which I want to prevent.
Simply just access it once like this:
$model->baz
And the relationship will be loaded.
Alternatively you can lazy eager load the relation:
$model->load('baz');
The effect is the same, although the first way allows you to actually use the result of the relation directly. If you just want to have the relationship in your array / JSON output I suggest you use the second method, because it's clearer what you're doing.
Depends on how you declare your relationships on your models. Assuming your relationship is declared as One to Many like this:
class Foo extends Eloquent {
public function bazes()
{
return $this->hasMany('Baz');
}
}
Then you can try this:
$results = $model->bazes;
or
$results = Foo::find($id)->bazes
$results is an iterable collection of bazes related directly with foo->id = x
But if you want eager loading with filters, then you can try like this:
$result = Foo::with(array('bazes' => function($query)
{
$query->where('id', $id);
}))->get();
I hope this works for you.
You should eager load on existing models using load().
Try
$model = new Foo;
$model->save();
$model->load('baz');

Database relation with Laravel Eloquent ORM

I'm new to Laravel and I'm stuck. This is what I am struggling with:
$questions = Question::find($id)->quiz(); // this code retrieves data from
// the table using the primary key
// in the table. The is a parameter
// that is passed via get.
This is what I have right now:
$questions = Question::where('quiz_id', '=', $id)->quiz();
This is the error I get:
Call to undefined method Illuminate\Database\Query\Builder::quiz()
What I want to do:
I want to run a query to get data from my database table using the foreign key in the table not the primary key, I also want to be able to use relations with this as seen from what I tried to do above.
Edit: Added the Question Model
<?php
class Question extends Eloquent{
protected $table = 'quiz_questions';
public function quiz()
{
return $this->belongsTo('Quiz');
}
}
Calling the quiz() function from Question::find($id)->quiz() will return a Query Builder instance allowing you to query the parent of the Question, its not going to return any data at that point until you call ->get() or another method that actually executes the query.
If you're wanting to return all the questions belonging to a certain quiz then you can do it like this.
$questions = Question::where('quiz_id', $id)->get();
This will return an Eloquent\Collection of the results for all questions with a quiz_id that is equal to $id.
If you've setup the relations between the Quiz and Questions then you can also do this using the Laravel relations.
$quiz = Quiz::findOrFail($id);
foreach($quiz->questions as $question)
{
// Do stuff with $question
}
Laravel will automagically pull Questions from the database that belongTo the Quiz you've already got from the database, this is known as eager loading http://laravel.com/docs/4.2/eloquent#eager-loading
Wader is correct, just calling where() will not execute your query. You either call get() and get an iterable result or use first() if you only want one result.
$quiz = Question::where('quiz_id', '=', $id)->first()->quiz();

Laravel 4 - Get Array of Attributes from Collection

I have a collection of objects. Let's say the objects are tags:
$tags = Tag::all();
I want to retrieve a certain attribute for each tag, say its name. Of course I can do
foreach ($tags as $tag) {
$tag_names[] = $tag->name;
}
But is there a more laravelish solution to this problem?
Something like $tags->name?
Collections have a lists method similar to the method for tables described by #Gadoma. It returns an array containing the value of a given attribute for each item in the collection.
To retrieve the desired array of names from my collection of tags I can simply use:
$tags->lists('name');
Update: As of laravel 5.2 lists is replaced by pluck.
$tags->pluck('name');
More specifically, the laravel 5.2 upgrade guide states that "[t]he lists method on the Collection, query builder and Eloquent query builder objects has been renamed to pluck. The method signature remains the same."
Yep, you can do it nice and easily. As the Laravel 4 Documentation states, you can do
Retrieving All Rows From A Table
$users = DB::table('users')->get();
foreach ($users as $user)
{
var_dump($user->name);
}
Retrieving A Single Row From A Table
$user = DB::table('users')->where('name', 'John')->first();
var_dump($user->name);
Retrieving A Single Column From A Row
$name = DB::table('users')->where('name', 'John')->pluck('name');
Retrieving A List Of Column Values
$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:
$roles = DB::table('roles')->lists('title', 'name');
Specifying A Select Clause
$users = DB::table('users')->select('name', 'email')->get();
$users = DB::table('users')->distinct()->get();
$users = DB::table('users')->select('name as user_name')->get();
EDIT:
The above examples show how to access data with the help of Laravel's fluent query builder. If you are using models you can access the data with Laravel's Eloquent ORM
Because Eloquent is internaly using the query builder, you can without any problem do the following things:
$tag_names = $tags->lists('tag_name_label', 'tag_name_column')->get();
which could be also done with:
$tag_names = DB::table('tags')->lists('tag_name_label', 'tag_name_column')->get();
Here are a few snippets from my own experimentation on the matter this morning. I only wish (and maybe someone else knows the solution) that the Collection had a $collection->distinct() method, so I could easily generate a list of column values based on an already filtered collection.
Thoughts?
I hope these snippets help clarify some alternative options for generating a list of unique values from a Table, Collection, and Eloquent Model.
Using a Collection (Happy)
/**
* Method A
* Store Collection to reduce queries when building multiple lists
*/
$people = Person::get();
$cities = array_unique( $people->lists('city') );
$states = array_unique( $people->lists('state') );
// etc...
Using an Eloquent Model (Happier)
/**
* Method B
* Utilize the Eloquent model's methods
* One query per list
*/
// This will return an array of unique cities present in the list
$cities = Person::distinct()->lists('city');
$states = Person::distinct()->lists('state');
Using an Eloquent Model PLUS Caching (Happiest)
/**
* Method C
* Utilize the Eloquent model's methods PLUS the built in Caching
* Queries only run once expiry is reached
*/
$expiry = 60; // One Hour
$cities = Person::remember($expiry)->distinct()->lists('city');
$states = Person::remember($expiry)->distinct()->lists('state');
I would love to hear some alternatives to this if you guys have one!
#ErikOnTheWeb
You could use array_column for this (it's a PHP 5.5 function but Laravel has a helper function that replicates the behavior, for the most part).
Something like this should suffice.
$tag_names = array_column($tags->toArray(), 'name');

Eloquent push() and save() difference

I have read laravel 4 docs about eloquent and was quite intrigued by the push() part.
It says,
Sometimes you may wish to save not only a model, but also all of its relationships. To do so, you may use the push method:
Saving A Model And Relationships
$user->push();
See link here
Sorry but it's a bit blurry on my part the difference between save() and push().
I am hoping someone can clear this one out for me. Thank you.
Heres the magic behind the scenes...
/**
* Save the model and all of its relationships.
*
* #return bool
*/
public function push()
{
if ( ! $this->save()) return false;
// To sync all of the relationships to the database, we will simply spin through
// the relationships and save each model via this "push" method, which allows
// us to recurse into all of these nested relations for the model instance.
foreach ($this->relations as $models)
{
foreach (Collection::make($models) as $model)
{
if ( ! $model->push()) return false;
}
}
return true;
}
It just shows that push() will update all the models related to the model in question, so if you change any of the relationships, then call push()
It will update that model, and all its relations
Like so...
$user = User::find(32);
$user->name = "TestUser";
$user->state = "Texas";
$user->location->address = "123 test address"; //This line is a pre-defined relationship
If here you just...
$user->save();
Then the address wont be saved into the address model....
But if you..
$user->push();
Then it will save all the data, and also save the address into the address table/model, because you defined that relationship in the User model.
push() will also update all the updated_at timestamps of all related models of whatever user/model you push()
Hopefully that will clear the things....
Let's say you did this:
$user = User::find(1);
$user->phone = '555-0101';
$user->address->zip_code = '99950';
You just made changes to two different tables, to save them you have to:
$user->save();
$user->address->save();
or
$user->push();
push() can only be used to update an existing model instance along side its relations not to create a new one. Simply say: push() updates and not insert.

Resources