'Laravel Polymorphic Relations commentable_type validation

I'm using a REST API to receive the data.
The data model is polymorphic related, similar to the one on the documentation:
https://laravel.com/docs/5.4/eloquent-relationships#polymorphic-relations

posts
    id - integer
    title - string
    body - text

videos
    id - integer
    title - string
    url - string

comments
    id - integer
    body - text
    commentable_id - integer
    commentable_type - string

Let's say, for example, the API is receiving this new comment:

{
    "body": "This a test comment",
    "commentable_type": "posts",
    "commentable_id": "1"
}

How can I validate if the received commentable_type exists and is valid?



Solution 1:[1]

If I correctly understand your question, you are trying to validate that the object of the polymorphic relation exists, for the given commentable_type and commentable_id.

If that is the case, there is no existing validation rule to do so, but you can create one.
Based on the documentation, here is what you could do:

First, add the new rule in the boot method of a service provider (e.g. AppServiceProvider):

Validator::extend('poly_exists', function ($attribute, $value, $parameters, $validator) {
    if (!$objectType = array_get($validator->getData(), $parameters[0], false)) {
        return false;
    }

    return !empty(resolve($objectType)->find($value));
});

And this is how you would use it:

'commentable_id' => 'required|poly_exists:commentable_type

What the rule does is it tries and fetches the commentable type from the input values (based on the parameter passed on to the rule, i.e. commentable_type in our case), and then resolves the object and tries to find a record for the given ID ($value).

Please note that for this to work however, the value of commentable_type must be the fully qualified class name (e.g. App\Models\Post).

Hope this helps!

Solution 2:[2]

Better approach that includes morphs map:

    Validator::extend('poly_exists', function ($attribute, $value, $parameters, $validator) {
        if (! $type = array_get($validator->getData(), $parameters[0], false)) {
            return false;
        }

        if (Relation::getMorphedModel($type)) {
            $type = Relation::getMorphedModel($type);
        }

        if (! class_exists($type)) {
            return false;
        }

        return ! empty(resolve($type)->find($value));
    });

Solution 3:[3]

You can dynamically define a model_exists rule in your Request class. Something like this:

public function rules()
{
    $polymorphExistsRule = '';
    if ($this->has('commentable_type')) {
        $polymorphExistsRule .= '|exists:' . $this->commentable_type . ',id';
    }

    return [
        'commentable_type' => 'required_with:commentable_id',
        'commentable_id'   => 'required_with:commentable_type' . $polymorphExistsRule,
    ];
}

Solution 4:[4]

Edit

I might've misunderstood the first time. If you want to check that the model saved in commentable_type exists you could do something like this:

$type = $comment->commentable_type;
if(class_exists($type)) echo "it exists";

Depending on your needs you could do additional checking for it's inheritance (for example that it extends class Model). Or anything else that fits your needs really.

Edit2

This is what I would do if I were you. I would add property protected $allRelations to your Comment model and manually put all the relationships in. Then make some helper models to check if it's in the array.

Simple example:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    // ..

    protected $allRelations= [
        'posts' => '\App\Post',
        'videos' => '\App\Video',
    ];

    public static function validateRelationNs($ns) {
        return in_array($ns, $this->allRelations);
    }

    public static function validateRelationName($name) {
        return array_key_exists($name, $this->allRelations);
    }

    // ...
}

Old answer:

Laravel expects full namespace name of the model for polymorphic type columns (in your case commentable_type should be \Full\Ns\Post, not posts).

The easiest way to ensure correctness is to always save it through the relationship. For example:

$post = Post::first();
$comment = new Comment($attributes);
$post->comments()->save($comment).

This will automatically set both commentable_id and commentable_type correctly (assuming your relationsare correctly defined).

Additional checking

Other then that you could check through model events. You could validate it before saving to the database.

Solution 5:[5]

My final version work for validate type and id:

Validator::extend('poly_exists', function ($attribute, $value, $parameters, $validator) {
    if (!$objectType = array_get($validator->getData(), $parameters[0], false)) {
        return false;
    }

    if (!class_exists($objectType)) {
        return false;
    }

    return !empty(resolve($objectType)->find($value));
});

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 osteel
Solution 2 Vedmant
Solution 3 Robin Bastiaan
Solution 4
Solution 5 Elton Fonseca