PHP/Laravel - Assign new model to a fetched one - php

Let's say I make a call to my User and return the following:
$user = User::with('permissions')->find(1);
I expect to get a user with $user->permissions being the permission of the user.
Next, I create and assign some new permissions to this user by:
// Say Input::all() contains an array of new permissions I want to add
// I am using Underscore PHP here
$newPermissions = Arrays::each(Input::all(), function($permission) {
$new = new Permission($permission);
$user->associate($new);
return $new;
});
Now I want to update the $user and return them back:
// This does NOT work (it returns the original $user->permissions)
return $user->permissions = $newPermissions;
// But this DOES work
unset($user->permissions);
return $user->permissions = $newPermissions;
Is this a PHP thing or Laravel thing? And what can I do? (btw, even if I say $user->permissions ='anything, text, string, or object doesnt work!' nothing happens).

It looks like permissions is a relationship, not a simple value that you can assign values to. I'd recommend reading the documentation on relationships, eager loading, and on inserting related models.
That last one, in particular, is what you're trying to do. Instead of assigning a value to $user->permissions, you need to update the relationship with either the save, associate, or attach methods, depending on the type of relationship.

Related

Laravel: Is always read the newly written data after calling save() method of ORM?

Code:
$item1 = Item::find(1);
$item1->foo = 1;
$item1->save();
$another_item1 = Item::find(1);
dd($another_item1->foo);//Is this value always 1?
My question:
Is always read the newly written data after calling save() method of ORM? In my example, Is $another_item1->foo always 1?
If the answer to question 1 is not, how could I ensure I read the newly written data from the database?
Is always read the newly written data after calling save() method of ORM?
No, there is no SELECT ran after a INSERT or UPDATE statement in this case.
In my example, Is $another_item1->foo always 1?
Based on your own comment, Yes.
If the answer to question 1 is not, how could I ensure I read the newly written data from the database?
$model->save();
// Reload the current model instance with fresh attributes from the database.
$model->refresh();
// OR
// Reload a fresh model instance from the database.
$fresh = $model->fresh();
I think you may be confused about the find() function. find() is used to fetch one or many models by its / their primary key(s). The return value will either be a single model, a collection or null if the record is not found.
If you are looking to lookup multiple rows you need to run Item::get();
Uses
$Item = Item::find(1); // returns model or null
$Items = Item::find(array(1, 2, 3)); // returns selected Items in collection
$Items = Item::get(); // Returns all in collection
https://laravel.com/docs/5.5/eloquent

Use collection->get() instead of collection->pluck()

I am using laravel-permission for managing roles and displaying content. Per the docs you can retrieve a users roles by using $roles = $user->roles()->pluck('name'). My problem is that the data returned is ["admin"] rather than just admin. I was reviewing the collections methods and it looked like get('name') would return what I was looking for. When I try to use the following command Auth::user()->roles()->get('name') I get
1/1
ErrorException in BelongsToMany.php line 360:
Argument 1 passed to Illuminate\Database\Eloquent\Relations\BelongsToMany::getSelectColumns() must be of the type array, string given
It seems to me like the get() method is expecting an array however, I'm trying to reference an item in the array. The raw output of Auth::user()->roles()->get() is [{"id":1,"name":"admin","created_at":"2016-03-10 06:24:47","updated_at":"2016-03-10 06:24:47","pivot":{"user_id":1,"role_id":1}}]
I have found a workaround for pulling the correct content, but it is using regex for removing the unwanted characters that are included in the pluck() method.
preg_replace('/\W/i','', Auth::user()->roles()->pluck('name'))
It seems like I'm missing something or approaching using the get() method incorrectly. Any advice is appreciated.
I think pluck() will return the value of the given column for each model in the collection, which would explain the array. In your case it looks like the user has only one role, so you get an array with only one item. If the user had multiple roles, you would likely get an array with multiple items in it.
On the other hand, the get() method is used to execute a query against the database after a query is built. The results of the query are what is returned. To return a collection of models with only a single value you will need to pass an array with just the one column you want, but that will just select models, which does not appear to be what you ultimately need.
You can try this instead: $roles = $user->roles()->first()->name
The call to first() will grab the first model in the collection returned by roles(), and then you can grab the name of the role from that model.
I typically throw some error checking around this:
$role = $user->roles()->first();
if (is_null($role)) {
//Handle what happens if no role comes back
}
$role_name = $role->name;
That's because an user can have many roles, so roles is a collection.
If you are 100% sure that a user will have only one role, you can easily do
Auth::user()->roles()->first()->name
That will get the first item of that collection (the role) and then its name.

Laravel nested relationship not working

I have a user model which stores basic user information such as username, password etc.
There are also 3 types of user, Student, Staff and Parent. Each type also has a seperate model. For example, there is a Student model which belongs to a User model.
I also have a relationships table, which stores relationships between students and parents. This relationship is stored in the User model.
If I do something like:
App\Student::first()->user->relations;
It happily returns a collection of related parents.
In my Students model, I have a method called hasParent() which accepts a given user ID, and checks to ensure the student has a parent with that id. In that method, I have the following:
public function hasParent($parent)
{
return $this->user->relations->where('id', $parent)->count() === 1;
}
However, this returns an error Cannot call 'where' on a non-object. If I debug further, $this->user->relations returns an empty array.
The problem is, like above, if I call the methods separately, I get the results I want.
So to clarify, if I run:
App\Student::first()->user->relations;
This returns a collection of users just fine.
In my Student model however, if I call:
$this->user
Then I get the correct student
If I call
$this->user->relations
I get an empty array. Which doesn't make sense! Can anyone shed any light on this, or what I might be doing wrong? If you need any further info, please let me know.
You need to call where on the relation like below.
public function hasParent($parent)
{
return $this->user->relations()->where('id', $parent)->count() === 1;
}
See the parenthesis after the relations. If you call the relation without the parenthesis Laravel returns you a collection. To get the builder you need to call the relation with the parenthesis.
I'd suggest - to avoid creating a huge query overhead (which you'll do by calling where and count on the Query builder, not the collection) - to do what you're doing already, except using Illuminate Collections filter-method:
public function hasParent($parent)
{
return $this->user->relations->filter(function($relation) use ($parent){return $entity->id === $parent;})->count() === 1;
}

Laravel: Create or update related model?

Please be gentle with me - I'm a Laravel noob.
So currently, I loop through a load of users deciding whether I need to update a related model (UserLocation).
I've got as far as creating a UserLocation if it needs creating, and after a bit of fumbling, I've come up with the following;
$coords = $json->features[0]->geometry->coordinates;
$location = new UserLocation(['lat'=>$coords[1],'lng'=>$coords[0]]);
$user->location()->save($location);
My issue is that one the second time around, the Location may want updating and a row will already exist for that user.
Is this handled automatically, or do I need to do something different?
The code reads like it's creating a new row, so wouldn't handle the case of needing to update it?
Update - solution:
Thanks to Matthew, I've come up with the following solution;
$location = UserLocation::firstOrNew(['user_id'=>$user->id]);
$location->user_id = $user->id;
$location->lat = $coords[1];
$location->lng = $coords[0];
$location->save();
You should reference the Laravel API Docs. I don't think they mention these methods in the "regular docs" though so I understand why you may have not seen it.
You can use the models firstOrNew or firstOrCreate methods.
firstOrNew: Get the first record matching the attributes or instantiate
it.
firstOrCreate: Get the first record matching the attributes or create it.
For Example:
$model = SomeModel::firstOrNew(['model_id' => 4]);
In the above example, if a model with a model_id of 4 isn't found then it creates a new instance of SomeModel. Which you can then manipulate and later ->save(). If it is found, it is returned.
You can also use firstOrCreate, which instead of creating a new Model instance would insert the new model into the table immediately.
So in your instance:
$location = UserLocation::firstOrNew(['lat'=>$coords[1],'lng'=>$coords[0]]);
$location will either contain the existing model from the DB or a new instance with the attributes lat and lng set to $coords[1] and $coords[0] respectively, which you can then save or set more attribute values if needed.
Another example:
$location = UserLocation::firstOrCreate(['lat'=>$coords[1],'lng'=>$coords[0]]);
$location will either contain the existing model from the DB or a new model with the attributes set again, except this time the model will have already been written to the table if not found.

Create or Update - Laravel 4.1

As there is firstOrCreate()
in Laravel Eloquent, I was wondering if there was a function that could either create a record or if it exists, update the current one?
Or would I have to write my own one?
I wasn't able to find anything in the documentation, but it's not the first time, I've found stuff about Eloquent elsewhere than the docs.
You nearly named it. :)
$instance = Model::updateOrCreate(['id' => $id], $newAttributes);
If $id is null then a new instance will be created and saved, else it will be updated.
You need to find model before updating it, right? You cannot just call Model::firstOrUpdate($newAttributes) simply because there is no model in database with such (new) attributes.
I. e. you must know some model's unique attribute, for example, an id. After this, you can fetch it and call update method with new attributes: Model::firstOrNew(['id' => $id])->update($newAttributes). $id here can be null, in this case new model will be instantiated (but not saved).
As you can see, this code is pretty short, but of course, you might put it into method if you wish.
More straight forward and DRY would it be to add the following method to your BaseModel:
public function write($input, $key = 'id')
{
// Instantiate new OR existing object
if (! empty($input[$key]))
$resource = $this->findOrFail($input[$key]);
else
$resource = $this; // Use a clone to prevent overwriting the same object in case of recursion
// Fill object with user input using Mass Assignment
$resource->fill($input);
// Save data to db
if (! $resource->save())
App::abort(500, 'Could not save resource');
return $resource;
}

Categories