larastan/larastan · error · InvalidArgumentException

is not a Model.

Error message

 is not a Model.

What it means

determineBuilderName() in BuilderHelper resolves the Eloquent builder class used for a model, and it only accepts class names whose ClassReflection is exactly Illuminate\Database\Eloquent\Model (checked via ->is(Model::class)). Any other class name passed in makes Larastan throw an InvalidArgumentException ('X is not a Model.'). This is an internal invariant: callers such as determineBuilderClass must only pass model class names.

Solutions

  1. Check the calling code/PHPDoc and pass the model class name (a subclass of Illuminate\Database\Eloquent\Model) instead of the builder or other class.
  2. If you hit it on your own code, inspect @template TModel annotations on the custom builder/relation so generics resolve to the model, not the builder.
  3. If it reproduces on plain Laravel models, file a Larastan issue with a minimal reproducer — it indicates a wrong class reached determineBuilderClass.

Example fix

// before (custom builder annotated wrong)
/** @template TModel of \App\Builders\PostBuilder */
class Post extends Model {}
// after
class Post extends Model {}
/** @extends Builder<Post> */
class PostBuilder extends Builder {}
Defensive patterns

Strategy: type-guard

Validate before calling

// before relying on builder resolution
if (! is_subclass_of($modelClassName, \Illuminate\Database\Eloquent\Model::class) && $modelClassName !== \Illuminate\Database\Eloquent\Model::class) {
    throw new \InvalidArgumentException("$modelClassName must be an Eloquent Model.");
}

Type guard

function isEloquentModel(string $class): bool
{
    return class_exists($class) && (is_subclass_of($class, \Illuminate\Database\Eloquent\Model::class) || $class === \Illuminate\Database\Eloquent\Model::class);
}

Prevention

When it happens

Trigger: A code path calls determineBuilderName()/determineBuilderClass() with a class name that is not (a subclass resolution of) Eloquent\Model — e.g. a Builder or query class name was passed where a model class was expected.

Common situations: Custom relation or builder code paths feeding a non-model type into model-based builder resolution; misconfigured generic template parameters where TModel was substituted with a builder or plain class instead of a model; internal extension bugs after Laravel/PPHPStan upgrades.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of larastan/larastan@79091ad281 (2026-09-15). Data as JSON: /api/errors/490b4c4e758e6fd2. Report an issue: GitHub.

Appendix: source

Thrown at src/Methods/BuilderHelper.php:294

    public function getBuilderType(string $builderClassName, Type $modelType): ObjectType
    {
        if (! $this->reflectionProvider->getClass($builderClassName)->isGeneric()) {
            return new ObjectType($builderClassName);
        }

        return new GenericObjectType($builderClassName, [$modelType]);
    }

    /**
     * @throws MissingMethodFromReflectionException
     * @throws InvalidArgumentException
     */
    public function determineBuilderName(string $modelClassName): string
    {
        $modelReflection = $this->reflectionProvider->getClass($modelClassName);

        if (! $modelReflection->is(Model::class)) {
            throw new InvalidArgumentException($modelClassName . ' is not a Model.');
        }

        $method = $modelReflection->getNativeMethod('newEloquentBuilder');

        if ($method->getDeclaringClass()->getName() === Model::class) {
            $attrs = $modelReflection->getNativeReflection()->getAttributes('Illuminate\Database\Eloquent\Attributes\UseEloquentBuilder'); //@phpstan-ignore argument.type (Attribute class might not exist)

            if ($attrs !== []) {
                $expr =  $attrs[0]->getArgumentsExpressions()[0];

                if ($expr instanceof ClassConstFetch && $expr->class instanceof Name) {
                    return $expr->class->toString();
                }
            }
        }

        $returnType = $method->getVariants()[0]->getReturnType();

View on GitHub (pinned to 79091ad281)