MOHAMMAD RASIM
MOHAMMAD RASIM

Reputation: 405

enable type checking when associating a model to a belongsto relation in laravel

Let's say i have three models User Post and Comment and linked like so:

class User extends Model
{
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }
}
class Comment extends Model
{
    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }
}
class Post extends Model
{
    /**
     * Get the comments for the blog post.
     */
    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}

and i wanted to associate a post to comment, i would do:


$comment->post()->associate($post);

but if i mistakenly pass a different model to the associate function:

$comment->post()->associate($user);

then it will still work, it will associate the post that has the same id of the passed user, which is wrong.

basically the associate function doesn't check the type of the model that was passed before associating, is there something that can be enabled so that laravel checks the type of the model before associating?

Upvotes: 0

Views: 141

Answers (2)

Avnee.Angel
Avnee.Angel

Reputation: 429

Laravel associate itself does not perform a check on the type of the model being associated.

You can solve it by extending the relationship method, which can check related model dynamically.

use Illuminate\Database\Eloquent\Relations\BelongsTo;
use InvalidArgumentException;

class TypedBelongsTo extends BelongsTo
{
    public function associate($model)
    {
        $relatedModelClass = $this->getRelated()::class;

        if (! $model instanceof $relatedModelClass) {
            throw new InvalidArgumentException("Expected instance of $relatedModelClass.");
        }

        return parent::associate($model);
    }
}

and for example, your Comment model can have below relation.

public function post(): TypedBelongsTo
{
    return new TypedBelongsTo(Post::query, $this, 'post_id', 'id', 'post');
}

Upvotes: 2

hassan
hassan

Reputation: 8308

The associate method does not check for types nor do a parameter type-hinting, and it's expects a Model instance, string, int or null as shown:

/**
 * Associate the model instance to the given parent.
 *
 * @param  \Illuminate\Database\Eloquent\Model|int|string|null  $model
 * @return \Illuminate\Database\Eloquent\Model
 */
public function associate($model)

Basically, you must take care of this by yourself, as this is not a user input, so it's easy to make sure that you are passing the right parameters.

Otherwise, you will need to do a redundant work -IMO- to handle this.

First, you will override the belongsTo method in your model that returns the new BelongsTo class (in the next step).
Secondly, you will need to create a shadow version of the BelongsTo concern, most likely you will inherit the Illuminate\Database\Eloquent\Relations\BelongsTo and override the associate method to type hint your parameter, give it a proper name, let's say CommentsBelongsTo or something.

The final class would something like:

use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Models\Post;

class CommentsBelongsTo extends BelongsTo
{
    ...
    public function associate(Post $model)
    ...
}

Upvotes: 0

Related Questions