Custom PHPDoc types¶
All custom types that are specific to this extension are listed here. Types that are defined by PHPStan can be found on their website.
view-string¶
The view-string type is a subset of the string type. Any string that passes the view()->exists($string) test
is also a valid view-string.
Example:
/**
* @phpstan-param view-string $view
* @param string $view
* @return \Illuminate\View\View
*/
public function renderView(string $view): View
{
return view($view);
}
renderView, this extension will try to check whether
the given string is a valid blade view.
If the string is not an existing blade view, the following error will be displayed by this extension.
The view, html, text, and markdown constructor arguments of
Illuminate\Mail\Mailables\Content are also view-string|null, including named
arguments. htmlString is rendered HTML and is not checked as a view name.
When working with packages, all vendor-prefixed paths like acme::example may fail. As packages don't contain a Laravel app, the default skeleton from orchestra/testbench is used. This instance doesn't know about the package so views are not registered. Create a testbench.yaml file to register your service provider to solve this issue.
model-property¶
model-property extends the built-in string type and acts like a string in the type level. But during the analysis if this extension finds that an argument of the method or a function has a model-property<ModelName>, it'll try to check that the given argument value is actually a property of the model.
All of the Laravel core methods have this type thanks to the stubs. So whenever you use a Eloquent builder, relation or a model method that expects a column, it'll be checked by this extension if the column actually exists. But you can also typehint any argument with model-property in your code.
The type is only active when modelPropertyType is enabled. With it off, model-property<Model> behaves as a plain string and nothing is checked. There is no rule behind it: once the type is active, the mismatches are reported by PHPStan's ordinary argument checks, so they carry core identifiers such as argument.type. See checking column names for a worked example.
builder-of¶
The builder-of<Model> type resolves to the Eloquent builder for that model. It
uses a custom newEloquentBuilder() implementation, #[UseEloquentBuilder], or
the model's static $builder property, in that order. Otherwise it is
Illuminate\Database\Eloquent\Builder<Model>.
A union of models becomes a union of their builders. Generic arguments, static,
$this, and intersections on the model are kept. Model query methods,
Collection::toQuery(), and relation query builders use it so custom builders
are retained.
use App\User;
use App\Post;
use Illuminate\Database\Eloquent\Builder;
/**
* @phpstan-return builder-of<User>
*/
function getActiveUsers(): Builder
{
return User::query()->where('active', true);
}
/**
* @phpstan-param builder-of<Post> $postQuery
*/
function publishPosts(Builder $postQuery): void
{
$postQuery->where('foo', 'bar')->get()->each(fn ($post) => $post->publish());
}
It also works with generic templates:
use Illuminate\Database\Eloquent\Builder;
/**
* @template TModel of \Illuminate\Database\Eloquent\Model
*/
class ModelRepository
{
/**
* @phpstan-param class-string<TModel> $modelClass
* @phpstan-return builder-of<TModel>
*/
public function getQuery(string $modelClass): Builder
{
return $modelClass::query();
}
}
An optional second argument selects a relationship on the first model:
/** @param builder-of<User, 'accounts'> $query */
function filterAccounts(Builder $query): void
{
$query->where('active', true);
}
If User::accounts() relates to Account, this resolves to that model's
builder, including a custom builder. Dotted paths such as
builder-of<User, 'posts.comments'> resolve to the final related model.
Unions of models or relation names become unions of builders. Paths that
cannot resolve are discarded; if none resolve, the type falls back to
builder-of<User>. If a path cannot be followed because a relation lost its
related model type, it instead falls back to Builder<Model>.
relation-of¶
The relation-of<Model, 'name'> type resolves to the relationship object
returned by the model's relationship method, including its concrete relation
class and generic model types. Dotted paths resolve to the final relationship
object, rather than its query builder.
/** @param relation-of<\App\User, 'posts.comments'> $comments */
function filterComments(\Illuminate\Database\Eloquent\Relations\Relation $comments): void
{
$comments->where('approved', true);
}
Unions of models or relationship names resolve to unions of relationship
objects. Unresolvable names are discarded when another name resolves; when
none resolve, the type falls back to Relation<Model, Model> with the original
model as the declaring model. Generic model and relationship name templates
resolve once their types are known.
eager-load-of¶
The eager-load-of<Model, TRelations> type is the with() argument with each
closure typed by its key: the closure under 'posts' receives
relation-of<Model, 'posts'>. The builder's with() / withOnly() and the
Eloquent collection's load() / loadMissing() bind TRelations to their
argument with a template, whose bound checks the argument. A wrapper does the
same:
/**
* @template TRelations of array<array-key, array<mixed>|\Closure|string>|string
* @param eager-load-of<\App\User, TRelations> $relations
*/
function loadUsers(array|string $relations): void
{
\App\User::query()->with($relations)->get();
}
loadUsers(['posts.comments' => fn ($query) => $query->latest()]);
// $query: MorphMany<App\Comment, App\Post>
Other values, and arrays whose keys are not all literals, are checked against the bound.
collection-of¶
The collection-of<Model> type resolves to the Eloquent collection used by
that model. It uses a custom newCollection() implementation,
#[CollectedBy], or the model's static $collectionClass property, in that
order. Laravel 13's inherited #[CollectedBy] behavior is also respected.
Otherwise it is
Illuminate\Database\Eloquent\Collection<int|string, Model>.
Model unions become unions of their collections, and generic templates are
resolved when their model type becomes known. Like array<TKey, TValue>, an
optional key type can be given first:
/** @param collection-of<string, \App\User> $users */
function usersByEmail(\Illuminate\Database\Eloquent\Collection $users): void
{
}
factory-of¶
The factory-of<Model> type resolves to the concrete factory selected for the
model. It follows the same precedence as Model::factory(): a model's custom
newFactory() method, static $factory property, #[UseFactory], then
Laravel's factory naming convention. Model unions become unions of their
factories.