Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Darkdawg's avatar

How to restrict a method to only accept certain Eloquent models (type-safe)?

This is more of a general question, as I'm not yet familiar with the best practices here.

I have a validation rule that currently accepts a generic Model instance, but only some of my models support tree operations (they have ancestorsAndSelf(), $this->path, etc.). I'd like to make this type-safe so the constructor/method can only receive models that support those methods.

A minimal example:

use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Database\Eloquent\Model;

final class NotInSubtree implements ValidationRule
{
    public function __construct(private Model $model) {}

    public function validate(string $attribute, mixed $value, \Closure $fail): void
    {
        $candidate = $this->model->newQuery()->find($value);

        if ($candidate && $candidate->ancestorsAndSelf()->whereKey($this->model->getKey())->exists()) {
            $fail(__('validation.no_subtree_descendant'));
        }
    }
}

PHPStan/Larastan complains about the ancestorsAndSelf(), claiming the method does not exist. Rightfully so, because Model doesn't necessarily have this method.

What's the correct way to solve this?

Here some stuff I have considered:

A) Create an interface with pure PHPDoc

B) Create an interface with methods (methods for properties as well, like getPath())

C) Create an interface with methods and getter/setter properties (PHP 8.4), and a trait that implements them pointing to the same attribute magic that laravel uses

D) Create a new abstract class that extends Model, which includes all methods, and PHPDoc for properties,

E) Use intersection types, like Model&Interface

F) Just accept Model, but assert or validate

I know some of these might not make sense, I'm really just looking for some pro input. Thanks!

0 likes
8 replies
Darkdawg's avatar

Thanks! I'm specifically asking about type safety for the model parameter, not which validation rule to use.

What I'm trying to solve is: how do you constrain a constructor/method so it only accepts models that support certain capabilities (like ancestorsAndSelf() method and a path attribute)?

Glukinho's avatar

You mean you want to have more constrained type here, right?

public function __construct(private Model $model) {}

I would create an interface Treeable with methods ancestorsAndSelf() etc, along with HasTreeStructure trait with implementations of the same methods.

Models with tree structure have implement Treeable and use HasTreeStructure. And your ValidationRule constructor is like this:

public function __construct(private Treeable $model) {}
1 like
Darkdawg's avatar

Alright, cool! And how would you solve properties? PHPDoc in the interface, or getter methods, or the new PHP 8.4 property getters?

For example if your method needed to call $model->path.

Glukinho's avatar

Maybe I wouldn't touch properties at all, fields of regular models are not defined in Laravel and I'm ok with it. Maybe I don't get the question...

Darkdawg's avatar

Yeah, sorry I'm probably explaining it in a difficult way.

I meant if you had to access the db fields of the models within the method. If you tried to call $model->id or $model->name etc. you would get "access to undefined property" phpstan errors, because those belong to the specific child models, not the Treeable that you passed.

So I'm really just asking what you would do to solve those errors.

Glukinho's avatar

Thanks for clarification. I personally would do nothing and just live with those errors, but I'll see other replies with interest.

Please or to participate in this conversation.