Can't get Laravel associate to work - php

I'm not quite sure if I understand the associate method in Laravel. I understand the idea, but I can't seem to get it to work.
With this (distilled) code:
class User
{
public function customer()
{
return $this->hasOne('Customer');
}
}
class Customer
{
public function user()
{
return $this->belongsTo('User');
}
}
$user = new User($data);
$customer = new Customer($customerData);
$user->customer()->associate($customer);
I get a Call to undefined method Illuminate\Database\Query\Builder::associate() when I try to run this.
From what I can read, I do it exactly as is stated in the docs.
What am I doing wrong?

I have to admit that when I first started using Laravel the relationships where the part that I had to consistently refer back to the docs for and even then in some cases I didn't quite get it right.
To help you along, associate() is used to update a belongsTo() relationship. Looking at your code, the returned class from $user->customer() is a hasOne relationship class and will not have the associate method on it.
If you were to do it the other way round.
$user = new User($data);
$customer = new Customer($customerData);
$customer->user()->associate($user);
$customer->save();
It would work as $customer->user() is a belongsTo relationship.
To do this the other way round you would first save the user model and then save the customer model to it like:
$user = new User($data);
$user->save();
$customer = new Customer($customerData);
$user->customer()->save($customer);
Edit: It may not be necessary to save the user model first but I've just always done that, not sure why.

As I understand it, ->associate() can onyl be called on a BelongsTo relationship. So, in your example, you could do $customer->user()->associate($user). However, in order to 'associate' a Has* relationship you use ->save(), so your code should be $user->customer()->save($customer)

just add ->save() to the end.
$user->customer()->associate($customer)->save();

Related

How to get instance of nested relationship in Laravel?

I have User model,
has many morph Notification. The Notification model also belongs to one NotificationType. I want to find data from NotificationType through the User instance. Here is the relationship code:
// User.php
public function notifications()
{
return $this->morphMany(Notification::class, 'notifiable');
}
// Notification.php
public function notification_type()
{
return $this->belongsTo(NotificationType::class, 'notification_type_id');
}
This is the code I have tried:
$notificationType = $user->notifications()->notification_type()->where('name', $this->data->type)->first();
But, it returns
staging.ERROR: Call to undefined method
Illuminate\Database\Eloquent\Relations\MorphMany::notification_type()
{"userId":"996cdaea-c6f3-444a-a430-036e1966ab91","exception":"[object]
(BadMethodCallException(code: 0): Call to undefined method
Illuminate\Database\Eloquent\Relations\MorphMany::notification_type()`
Is it possible to get the notification_type directly from the $user?
Thanks in advance!
Actually, I just found the solution. Since the $user->notifications() is the instance of Illuminate\Database\Eloquent\Relations\MorphMany, there is a function to get the instance of the Notification model, that is getRelated(). I found it here.
Since I got the Notification model class, I finally can retrieve the notification_type() relation method.
Here is my fixed code:
$notificationType = $user->notifications()->getRelated()->notification_type()->getRelated()->where('name', $this->data['type'])->first();
I am open to any suggestions to improve or fix the code above. Thank you.

Find child model hasMany relation in laravel eloquent

Here is model structure of my Laravel 5.3 project,
User.php (Model)
it has one invitation method that returns the invitation of a user.
public function invitations()
{
return $this->hasMany( 'App\Invitation', 'invitee_id', 'id' );
}
Invitation.php (Model)
This model has another method that would return the inviter detail of an invitation.
public function inviter()
{
return $this->hasOne( 'App\User', 'id', 'invited_by' );
}
If i want to retrieve all invitations of current user it works,
\Auth::user()->invitations;
But if i try to get the information about the inviter it won't work! (Question: How to do it?)
\Auth::user()->invitations->inviter;
Though i can query the inviter from a invitation eloquent object like this,
\App\Invitation::first()->inviter;
But this is not working when i try to access it from the user model -> invitation -> inviter!
Also can i use eager loading here?
\Auth::user()->invitations->inviter;
Looking at this, it appears that you're attempting to retrieve the inviter property from a collection of invitations. The reason Ken's suggestion to use \App\Invitation::first()->inviter; worked is because you are retrieving the inviter of only one invitation (in this instance, the first). To resolve this, loop through your invites before attempting to retrieve the properties for each one:
$invitations = \Auth::user()->invitations;
foreach ($invitations as $invitation) {
$inviter = $invitation->inviter;
}
There is also an each() method specific to Laravel Collections that will allow you to loop through your object.

Laravel's Model observer class is not working when updating using query builder

I am explaining my issue with an example.
My model class is User and observer class is UserObserver.
I have added some code in the updated method of UserObserver that will run everytime the User model update function is used. For example updated method in theUserObserver(below) should get called whenever an update happen in User record.
class UserObserver{
function updated($userModel)
{
//Send mail code
}
}
The code in the UserObserver works when User data update like shown below:
User::find(2)->update(['name'=>'Update Name']);
However, the code in the UserObserver won't run when User data is updated in the following way:
User::where('id', 2)->update(['name'=>'Update Name']);
When I debug I can understand that User::find(2) return User model object and User::where('id', 2) will return a Builder object. So, how can I make use of our observer class method regardless of whether it is updated using User Model object or Builder object?
The issue is, I do have an existing application, some of the models are updating like User::where('id', 2)->update(['name'=>'Update Name']);. It is a difficult task to modify update statement to User::find(2)->update(['name'=>'Update Name']);.
Try as below:
User::where('id', 2)->first()->update(['name'=>'Update Name']);
The one problem with this code is that if you don't have the user with the id = 2 in your database then you will get null and you will get and exception may be as calling update on undefined entity.
So you can avoid this as below:
$user = User::where('id', 2)->first();
if(!empty($user)) {
$user->getModel()->update(['name'=>'Update Name']);
}
Dear Observer not depend on Model object or Builder Object.
It works on both statements...
User::find(2)
and
User::where('id', 2)
I am facing the same problem yesterday, but i install last Monday revisionable package and when i explore that package, its work on my code "User::where('id', 2)".
Actually i am placing my code in updating() method, but when i place the same code in saving() method its work for me...
User model class should have a fillable variable.
class User extends Model
{
protected $fillable = ['name'];
}
Try to add the first() method after where clause.
User::where('id', 2)->first()->update(['name'=>'Update Name']);
Or twist where clause to get User model object.
$user = User::where('id', 2)->first();
if(!$user) {
$user->update(['name'=>'Update Name']);
}

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 save nested model

I am using Laravel 4 and got stuck with a problem.
I have 3 models: User, Project, Task.
Relationships:
User belongsToMany('Project')
Project belongsToMany('User')
Project belongsToMany('Task')
Task belongsTo('Project')
I want to store a task but with no luck. The following code maybe tells in more detail what I want to accomplish:
Auth::user()->projects($projectId)->tasks()->save($task);
With that code I get:
Call to undefined method Illuminate\Database\Query\Builder::tasks()
Save your task first.
Then you can try...
Auth::user()->projects()->find($projectID)->tasks()->associate($task);
You may also need to modify your Project/Task relationship. The inverse of belongsToMany is always belongsToMany. I am thinking you will probably need hasMany and belongsTo instead.
Edit:
Sorry I think I lead you in the wrong direction, I finally got this working.
$task = new Task;
$task->name = 'Some super ultra task';
Auth::user()->projects()->where('projects.id', $projectID)->first()->tasks()->save($task);
Additionally, I think I made it harder than it needed to be.
$project = Project::find($projectID);
$project->tasks()->save($task);
That should work exactly the same. Since we know the ID of the project we are looking for, we shouldn't really need to go through the user model first.
To check that a user owns a project before saving, you can add this function to your Project model.
public function ownedByUser($user_id)
{
if(User::find($user_id)->projects()->count()) {
return true;
} else {
return false;
}
}
Then to you use it, here is what I did...
$project = Project::find($projectID);
if($project->ownedByUser(Auth::user()->id)) {
$project->tasks()->save($task);
} else {
echo 'This isn\'t your projec to edit, fool!';
}

Categories