Appearance
Model
Use
Model::query()instead of direct static calls:php// GOOD Member::query()->firstWhere('id', 42); // BAD Member::firstWhere('id', 42);Avoid mass assignment when possible:
php// PREFERRED $member = new Member(); $member->name = $request->input('name'); $member->email = $request->input('email'); // AVOID $member->forceFill([ 'name' => $request->input('name'), 'email' => $request->input('email'), ]); // NEVER DO $member->forceFill($request->all());Don't use
where{Attribute}magic methods. Use thewheremethod to reduce magic.Document all magic using PHPDoc:
php/** * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Modules\Permission\Models\Role> $roles * @method static \Illuminate\Database\Eloquent\Builder|\App\Modules\Member\Models\Member canceled() */Use safe defaults for attributes:
phpfinal class CourseEnrollment extends Model { /** @var array<string, scalar|bool|null> Default values for Eloquent attributes */ protected $attributes = [ 'graded_score' => 0, 'potential_points' => 0, 'completed_time_in_seconds' => 0, ]; }Use custom EloquentBuilder classes for models with 3+ query scopes:
phpclass User extends Model { #[\Override] public function newEloquentBuilder($query): UserEloquentBuilder { return new UserEloquentBuilder($query); } } /** @extends \Illuminate\Database\Eloquent\Builder<\App\Models\User> */ final class UserEloquentBuilder extends Builder { public function confirmed(): self { return $this->whereNotNull('confirmed_at'); } }Use invokable classes for reusable scopes:
php$unverifiedUsers = User::query() ->tap(new UnverifiedScore()) ->get(); final class UnverifiedScore { public function __invoke(Builder $builder): void { $builder->whereNull('email_verified_at'); } }