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!