# Features All features that are specific to Laravel applications are listed here. ## Laravel 9 Attributes In order for [Laravel 9 Attributes](https://laravel.com/docs/9.x/eloquent-mutators#accessors-and-mutators) to be recognized as model properties, they must be `protected` methods annotated with the `Attribute` Generic Types. The first generic type is the getter return type, and the second is the setter argument type. #### Examples ```php */ protected function scopes(): Attribute { return Attribute::make( get: fn (?string $value) => is_null($value) ? [] : explode(' ', $value), set: function(array $value) { $set = array_unique($value); sort($set); return ['scopes' => implode(' ', $set)]; } ); } ``` ```php */ protected function isTrue(): Attribute { return Attribute::make( get: fn (?string $value): bool => $value === null, ); } ``` ## Model serialization Given a `posts` table with an integer `id`, string `title`, and nullable `published_at`: ```php class Post extends Model { protected $casts = ['published_at' => 'datetime']; } $post = new Post(); // Both infer array{id?: int, title?: string, published_at?: string|null, ...} $post->toArray(); $post->attributesToArray(); ``` ## Custom Model Builders Custom builders offer a better static analysis experience than using model scopes, and they help slim down the model class. Here's an example of how to create a custom builder class: ```php */ class UserBuilder extends Builder { /** @return $this */ public function active(): static { $this->where('active', true); return $this; } } class User extends Model { /** @use HasBuilder */ use HasBuilder; protected static string $builder = UserBuilder::class; } // Usage $users = User::query() ->active() ->get(); ``` > [!NOTE] > The `HasBuilder` trait was introduced in Laravel 11, if you are using an older version of Laravel you can use the following: > > ```php > class User extends Model > { > public static function query(): UserBuilder > { > return parent::query(); > } > > /** @param \Illuminate\Database\Query\Builder $query */ > public function newEloquentBuilder($query): UserBuilder > { > return new UserBuilder($query); > } > } > ``` ## Model Factories Because the `Factory` class is generic, you need to specify the template type in your model factories. And while Laravel has magic to automatically associate a factory with a model, you'll have a much better static analysis experience if you specify the factory class in the model. So for example, here's how the classes can look: ```php */ class UserFactory extends Factory { protected $model = User::class; } class User extends Model { /** @use HasFactory */ use HasFactory; protected static string $factory = UserFactory::class; } ``` > [!NOTE] > The `HasFactory` generics was introduced in Laravel 11, if you are using an older version of Laravel you can use the following: > > ```php > class User extends Model > { > /** > * @param (callable(array, static|null): array)|array|int|null $count > * @param (callable(array, static|null): array)|array $state > */ > public static function factory($count = null, $state = []): UserFactory > { > return parent::factory(); > } > > protected static function newFactory(): UserFactory > { > return UserFactory::new(); > } > } > ``` ## Custom Model Collections Custom collections can be created to extend the functionality of the default collection class. So for example, here's how the classes can look: ```php */ final class UserCollection extends Collection { } class User extends Model { /** @use HasCollection */ use HasCollection; protected static string $collectionClass = UserCollection::class; } ``` Or if the collection is used for multiple models then you need to create a generic collection class and then specify the template type in the model. ```php */ class GeneralCollection extends Collection { } class User extends Model { /** @use HasCollection> */ use HasCollection; protected static string $collectionClass = GeneralCollection::class; } ``` > [!NOTE] > The `HasCollection` trait was introduced in Laravel 11, if you are using an older version of Laravel you can use the `newCollection` method to override the collection class: > > ```php > class User extends Model > { > /** > * Create a new Eloquent Collection instance. > * > * @param array $models > * @return GeneralCollection > */ > public function newCollection(array $models = []): GeneralCollection > { > return new GeneralCollection($models); > } > } > ``` `getCollection()` on length-aware, simple, and cursor paginators resolves to the model's custom collection. Non-model items resolve to a Support collection. `setCollection()` updates the paginator's inferred key and value types. This inference follows the item type: manually supplying a Support collection of models still infers the model's Eloquent collection. It does not track changes to the collection class separately from the item type. ## Model Properties Larastan will automatically scan your application's migrations in order to infer the database schema and therefore it is able to infer the existence of magic properties on Eloquent model classes. Various parameters can be set to [configure this behavior](custom-config-parameters.md#databasemigrationspath). ## Model Relationships In order for Larastan to recognize Model relationships you are required to document the generic types of the relation class: ```php /** @return BelongsTo */ public function user(): BelongsTo { return $this->belongsTo(User::class); } /** @return HasMany */ public function posts(): HasMany { return $this->hasMany(Post::class); } ``` Relationship query callbacks infer the related model's builder, including custom builders: ```php User::whereHas('posts.comments', function (Builder $query) { // $query: Builder }); Post::whereRelation('user', function (Builder $query) { // $query: UserBuilder (from the custom builder example above) }); Comment::whereHasMorph('commentable', [Post::class, User::class], function (Builder $query, $type) { // $query: Builder|UserBuilder // $type: string }); User::withWhereHas('posts', function (Builder|Relation $query) { // $query: Builder|HasMany }); User::query()->with('posts', function (Relation $query) { // $query: HasMany }); ``` > [!NOTE] > Callbacks inside `with([...])` and similar relationship arrays do not receive these inferred types. ## Bootstrap Error Reporting (since 3.9.0) Larastan boots your Laravel application during analysis. If that bootstrap fails, Larastan can print a beautifully styled error report with a clear title, useful tips to resolve the issue and a stack trace. Depending on if the error is coming from the framework itself or from user code, it provides different tips and messages. The output respects `--ansi` and `--no-ansi` flags. ![Screenshot of a failed PHPStan analysis showcasing the custom styled error.](/docs/framework-bootstrap-error.png)