Зачем этот раздел
В Laravel ты почти никогда не пишешь с нуля. Ты наследуешь готовые родительские классы, подключаешь трейты, реализуешь интерфейсы — и получаешь кучу функционала бесплатно. Но закономерный вопрос: «Что именно даёт тебе FormRequest?» или «Зачем Model наследует HasFactory?».
Паттерн Laravel: Ты создаёшь свой класс → наследуешь родителя → переопределяешь нужные методы → Laravel вызывает твой класс автоматически в нужный момент (через Service Container, Router, Queue Worker и т.д.)
Пример:
Illuminate\Foundation\Http\FormRequest
←
App\Http\Requests\LoginRequest
Родитель FormRequest уже умеет: валидировать, авторизовывать, возвращать ошибки, редиректить. Ты только задаёшь rules() и authorize().
Помимо классов в Laravel есть ещё 3 типа «строительных блоков»:
• Интерфейсы (Contracts) — «контракт» что класс обязан уметь
• Трейты — переиспользуемые методы (горизонтальное наследование)
• Фасады — статический доступ к сервисам из контейнера
Controller
Illuminate\Routing\Controller
←
App\Http\Controllers\Controller
←
App\Http\Controllers\UserController
Что даёт родитель бесплатно
| Возможность | Описание |
| middleware() | Назначать middleware прямо в конструкторе: $this->middleware('auth') |
| Dependency Injection | Параметры метода автоматически резолвятся из Service Container |
| Route Model Binding | show(User $user) — Laravel сам достанет модель из БД по ID |
| Form Request Injection | store(StoreRequest $request) — валидация до входа в метод |
| AuthorizesRequests (trait) | $this->authorize('update', $post) — проверка Policy |
| ValidatesRequests (trait) | $this->validate($request, [...]) — валидация прямо в контроллере |
Упрощённый код родителя
abstract class Controller
{
protected $middleware = [];
public function middleware($middleware, array $options = [])
{
$this->middleware[] = [
'middleware' => $middleware,
'options' => &$options,
];
}
public function getMiddleware() { return $this->middleware; }
public function callAction($method, $parameters)
{
return $this->{$method}(...array_values($parameters));
}
}
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
}
Твой класс — что ты пишешь
class UserController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('admin')->only('destroy');
}
public function index()
{
$this->authorize('viewAny', User::class);
return User::paginate();
}
public function store(StoreUserRequest $request)
{
return User::create($request->validated());
}
public function show(User $user)
{
return $user;
}
}
Частый вопрос: «Можно ли контроллер без наследования?» — Да, в Laravel 11+ Controller может быть просто invokable class с __invoke(). Но тогда нет $this->middleware() и $this->authorize().
Model (Eloquent)
Illuminate\Database\Eloquent\Model
←
App\Models\User
Что даёт родитель бесплатно
| Возможность | Как используется |
| CRUD операции | User::create(), $user->save(), $user->delete(), User::find(1) |
| Query Builder | User::where('age', '>', 18)->get() — цепочки запросов |
| Relations | hasMany(), belongsTo(), morphMany() — все методы отношений |
| Mass Assignment | Защита через $fillable / $guarded |
| Casting | $casts = ['is_admin' => 'boolean'] |
| Events | creating, created, updating, deleted — lifecycle hooks |
| Scopes | scopeActive($q) → User::active()->get() |
| Accessors/Mutators | getFullNameAttribute(), setPasswordAttribute() |
| Timestamps | created_at, updated_at автоматически |
| Serialization | $user->toArray(), $user->toJson(), $hidden, $visible |
Упрощённый код родителя
abstract class Model implements Arrayable, Jsonable, JsonSerializable
{
use HasAttributes, HasEvents, HasRelationships,
HasTimestamps, HidesAttributes, GuardsAttributes;
protected $table;
protected $primaryKey = 'id';
protected $fillable = [];
protected $guarded = ['*'];
protected $hidden = [];
protected $casts = [];
public $timestamps = true;
public static function create(array $attributes) { }
public function save() { }
public function delete() { }
public static function find($id) { }
public static function where(...) { }
public function hasMany($related, ...) { }
public function belongsTo($related, ...) { }
}
Твой класс
class User extends Model
{
use HasFactory, Notifiable, SoftDeletes;
protected $fillable = ['name', 'email', 'password'];
protected $hidden = ['password', 'remember_token'];
protected $casts = ['email_verified_at' => 'datetime'];
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
public function scopeActive($query)
{
return $query->where('is_active', true);
}
}
Authenticatable: Модель User также наследует Illuminate\Foundation\Auth\User as Authenticatable, который добавляет токены аутентификации, remember token, и реализует контракт AuthenticatableContract.
Middleware
Нет наследования! Middleware в Laravel — это просто класс с методом handle(). Он реализует паттерн «Pipeline» (цепочка обработчиков). Никакого родительского класса не требуется.
Что Laravel ожидает
class CheckAge
{
public function handle(Request $request, Closure $next): Response
{
if ($request->age < 18) {
return redirect('home');
}
return $next($request);
}
}
Виды middleware
| Вид | Когда выполняется | Пример |
| Before | До контроллера | Проверка auth, CORS |
| After | После контроллера | Добавить заголовки к response |
| Terminable | После отправки ответа клиенту | Логирование, аналитика (метод terminate()) |
public function handle($request, Closure $next)
{
$response = $next($request);
$response->header('X-Custom', 'value');
return $response;
}
public function terminate($request, $response)
{
Log::info('Request processed', ['url' => $request->url()]);
}
Job (Queue)
—
implements
ShouldQueue
+
App\Jobs\SendWelcomeEmail
Что дают трейты и интерфейс
| Элемент | Тип | Что даёт |
| ShouldQueue | interface | Маркер — Laravel знает, что класс надо отправить в очередь |
| Dispatchable | trait | SendWelcomeEmail::dispatch($user) — статический метод отправки |
| InteractsWithQueue | trait | $this->attempts(), $this->release(30), $this->delete() |
| Queueable | trait | ->onQueue('emails'), ->delay(60), ->onConnection('redis') |
| SerializesModels | trait | Модели в конструкторе автоматически сериализуются по ID, а не целиком |
Твой класс
class SendWelcomeEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $backoff = [10, 60];
public $timeout = 120;
public function __construct(
public readonly User $user
) {}
public function handle(MailService $mail): void
{
$mail->send($this->user, 'welcome');
}
public function failed(Throwable $e): void
{
Log::error('Welcome email failed', ['user' => $this->user->id]);
}
}
SendWelcomeEmail::dispatch($user)->onQueue('emails');
Command (Artisan)
Symfony\Component\Console\Command\Command
←
Illuminate\Console\Command
←
App\Console\Commands\CleanOldLogs
Что даёт родитель
| Возможность | Описание |
| $signature | Определяет имя команды и аргументы: 'logs:clean {--days=30}' |
| $description | Описание для php artisan list |
| I/O методы | $this->info(), $this->error(), $this->table(), $this->ask() |
| Progress bar | $this->withProgressBar($items, fn) |
| Arguments & Options | $this->argument('name'), $this->option('days') |
| Scheduling | Можно привязать к Schedule в console.php |
| call() | Вызывать другие artisan-команды программно |
Твой класс
class CleanOldLogs extends Command
{
protected $signature = 'logs:clean {--days=30 : Сколько дней хранить}';
protected $description = 'Удаляет старые лог-файлы';
public function handle(): int
{
$days = $this->option('days');
$this->info("Cleaning logs older than {$days} days...");
$files = Storage::files('logs');
$this->withProgressBar($files, function($file) use ($days) {
});
$this->newLine();
$this->info('Done!');
return Command::SUCCESS;
}
}
Notification
Illuminate\Notifications\Notification
←
App\Notifications\OrderShipped
Что даёт родитель
| Возможность | Описание |
| via() | Каналы доставки: ['mail', 'database', 'slack'] |
| toMail() | Формирование email через MailMessage builder |
| toArray() | Формат для хранения в таблице notifications |
| toDatabase() | То же, но для database-канала специально |
| ShouldQueue | Добавь интерфейс — и уведомления уйдут в очередь |
Твой класс
class OrderShipped extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(public readonly Order $order) {}
public function via($notifiable): array
{
return ['mail', 'database'];
}
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->subject('Ваш заказ отправлен!')
->line("Заказ #{$this->order->id} в пути.")
->action('Отследить', url("/orders/{$this->order->id}"));
}
public function toArray($notifiable): array
{
return [
'order_id' => $this->order->id,
'message' => 'Заказ отправлен',
];
}
}
$user->notify(new OrderShipped($order));
Mailable
Illuminate\Mail\Mailable
←
App\Mail\WelcomeMail
Что даёт родитель
| Метод | Описание |
| envelope() | Тема, от кого, кому, reply-to |
| content() | Blade-шаблон и данные для него |
| attachments() | Прикрепить файлы |
| Chainable API | ->to(), ->cc(), ->bcc(), ->subject() |
| ShouldQueue | Отправка через очередь |
Твой класс
class WelcomeMail extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
public function __construct(public readonly User $user) {}
public function envelope(): Envelope
{
return new Envelope(subject: 'Добро пожаловать!');
}
public function content(): Content
{
return new Content(view: 'emails.welcome');
}
public function attachments(): array
{
return [
Attachment::fromPath('/docs/guide.pdf'),
];
}
}
Mail::to($user)->send(new WelcomeMail($user));
Event + Listener
Event — это простой DTO-класс (никакого обязательного наследования!). Listener — класс с методом handle(). Если добавить ShouldQueue к Listener, он обрабатывается асинхронно.
Event
class OrderPlaced
{
use Dispatchable, SerializesModels;
public function __construct(
public readonly Order $order
) {}
}
OrderPlaced::dispatch($order);
event(new OrderPlaced($order));
Listener
class SendOrderConfirmation implements ShouldQueue
{
public function handle(OrderPlaced $event): void
{
$event->order->user->notify(new OrderConfirmation($event->order));
}
public function shouldQueue(OrderPlaced $event): bool
{
return $event->order->total > 0;
}
}
Регистрация: В EventServiceProvider → $listen массив, или авто-обнаружение через Event::listen() в boot(), или атрибут #[ListensTo(OrderPlaced::class)] (Laravel 11+).
Policy
Нет обязательного наследования. Policy — просто класс с методами-действиями (view, create, update, delete). Laravel привязывает его к модели автоматически по Convention.
Твой класс
class PostPolicy
{
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
public function before(User $user, string $ability): ?bool
{
if ($user->isAdmin()) {
return true;
}
return null;
}
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id
&& $post->published_at === null;
}
}
$this->authorize('update', $post);
@can('update', $post) ... @endcan
Gate::allows('update', $post);
Seeder / Factory
Seeder
Illuminate\Database\Seeder
←
Database\Seeders\UserSeeder
| Что даёт | Описание |
| run() | Ты переопределяешь — заполняешь таблицу тестовыми данными |
| call() | Вызвать другие сидеры: $this->call(UserSeeder::class) |
| command | php artisan db:seed |
class UserSeeder extends Seeder
{
public function run(): void
{
User::factory(50)->create();
}
}
Factory
Illuminate\Database\Eloquent\Factories\Factory
←
Database\Factories\UserFactory
| Что даёт | Описание |
| definition() | Ты определяешь шаблон данных с Faker |
| States | Вариации: ->admin(), ->unverified() |
| Связь с моделью | Через trait HasFactory в модели: User::factory() |
class UserFactory extends Factory
{
protected $model = User::class;
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'password' => bcrypt('password'),
];
}
public function admin(): static
{
return $this->state(['role' => 'admin']);
}
}
User::factory()->admin()->count(3)->create();
Rule (Custom Validation)
implements
→
Illuminate\Contracts\Validation\ValidationRule
←
App\Rules\Uppercase
Laravel 10+: Кастомные правила реализуют интерфейс ValidationRule (метод validate()). Раньше был класс Rule с passes() + message().
class Uppercase implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (strtoupper($value) !== $value) {
$fail("Поле :attribute должно быть в верхнем регистре.");
}
}
}
'name' => ['required', new Uppercase]
Resource / Collection
Illuminate\Http\Resources\Json\JsonResource
←
App\Http\Resources\UserResource
Что даёт родитель
| Возможность | Описание |
| toArray() | Ты определяешь структуру JSON-ответа |
| $this->resource | Доступ к оригинальной модели |
| when() / whenLoaded() | Условные поля: $this->when($condition, $value) |
| collection() | Автоматическая обёртка массива моделей |
| with() | Мета-данные: ['meta' => ['version' => '1.0']] |
| Pagination | Автоматически добавляет links и meta при пагинации |
Твой класс
class UserResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => PostResource::collection(
$this->whenLoaded('posts')
),
'is_admin' => $this->when(
$request->user()?->isAdmin(), $this->role === 'admin'
),
];
}
}
return UserResource::collection(User::paginate());
Exception Handler
Laravel 11+: Exception Handler больше не отдельный класс. Исключения настраиваются в bootstrap/app.php через ->withExceptions(). В Laravel 10 и ранее — наследовался Illuminate\Foundation\Exceptions\Handler.
Laravel 11+ способ
return Application::configure(...)
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (NotFoundHttpException $e) {
return response()->json(['error' => 'Not found'], 404);
});
$exceptions->reportable(function (PaymentException $e) {
Sentry::captureException($e);
});
})
->create();
Laravel 10 способ (наследование)
class Handler extends ExceptionHandler
{
protected $dontReport = [
AuthorizationException::class,
];
public function register(): void
{
$this->renderable(function (NotFoundHttpException $e) {
return response()->json(['error' => 'Not found'], 404);
});
}
}
ServiceProvider
Illuminate\Support\ServiceProvider
←
App\Providers\AppServiceProvider
Что даёт родитель
| Метод | Описание |
| register() | Привязки в контейнер: $this->app->bind(), $this->app->singleton() |
| boot() | Выполняется ПОСЛЕ регистрации всех провайдеров — здесь роуты, views, observers |
| $this->app | Доступ к Service Container |
| mergeConfigFrom() | Слияние конфига пакета с приложением |
| loadRoutesFrom() | Загрузить файл маршрутов |
| loadViewsFrom() | Регистрация views из пакета |
| loadMigrationsFrom() | Миграции из пакета |
Твой класс
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(
PaymentGatewayInterface::class,
StripePaymentGateway::class
);
}
public function boot(): void
{
User::observe(UserObserver::class);
Blade::directive('money', function ($amount) {
return "<?php echo number_format($amount, 2) . ' ₸'; ?>";
});
}
}
register() vs boot(): В register() — только привязки контейнера. Не обращайся к другим сервисам (они могут быть ещё не зарегистрированы!). В boot() — всё остальное, все провайдеры уже загружены.
Migration
Illuminate\Database\Migrations\Migration
←
CreateUsersTable
Что даёт родитель
| Метод/Свойство | Описание |
| up() | Ты определяешь — создание/изменение таблицы |
| down() | Ты определяешь — откат миграции |
| $connection | На какое подключение применить |
| Schema facade | Не от родителя, но используется — Schema::create() |
return new class extends Migration
{
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->text('body');
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};
Laravel 11+: Миграции — анонимные классы (return new class extends Migration). Это избегает коллизий имён.
Интерфейсы (Contracts)
Интерфейс — это контракт. Он говорит: «Любой класс, который меня реализует, обязан иметь вот эти методы». Laravel активно использует интерфейсы через пакет illuminate/contracts.
Зачем? Dependency Inversion (SOLID). Контроллер зависит от интерфейса PaymentGateway, а не от конкретного StripeGateway. В ServiceProvider ты привязываешь нужную реализацию.
Ключевые интерфейсы Laravel
| Интерфейс | Пакет | Что обязывает |
ShouldQueue | Contracts\Queue | Маркер — класс обрабатывается через очередь (Job, Listener, Notification, Mail) |
ShouldBroadcast | Contracts\Broadcasting | Event передаётся через WebSocket (broadcastOn(), broadcastAs()) |
Authenticatable | Contracts\Auth | Модель может использоваться для auth (getAuthIdentifier, getAuthPassword...) |
Arrayable | Contracts\Support | Класс может быть преобразован в array — toArray() |
Jsonable | Contracts\Support | Класс может быть преобразован в JSON — toJson() |
Renderable | Contracts\Support | Класс может вернуть HTML — render() |
Responsable | Contracts\Support | Класс может быть возвращён из контроллера как response — toResponse() |
ValidationRule | Contracts\Validation | Кастомное правило валидации — validate() |
CastsAttributes | Contracts\Database | Кастомный cast для Eloquent — get(), set() |
Htmlable | Contracts\Support | Класс возвращает безопасный HTML — toHtml() |
Пример своего интерфейса
interface PaymentGatewayInterface
{
public function charge(float $amount, string $token): PaymentResult;
public function refund(string $transactionId): bool;
}
class StripeGateway implements PaymentGatewayInterface
{
public function charge(float $amount, string $token): PaymentResult
{
}
public function refund(string $transactionId): bool
{
}
}
$this->app->bind(PaymentGatewayInterface::class, StripeGateway::class);
public function pay(PaymentGatewayInterface $gateway) { ... }
Трейты
Трейт — это горизонтальное наследование. Класс может использовать только один родительский класс (extends), но сколько угодно трейтов (use). Laravel активно использует трейты для переиспользования кода.
Разница: Наследование = «я являюсь» (User является Model). Трейт = «я умею» (User умеет SoftDeletes). Интерфейс = «я обязуюсь» (класс обязуется реализовать ShouldQueue).
Ключевые трейты Laravel
| Трейт | Где используется | Что даёт |
HasFactory | Model | User::factory() — создание тестовых данных |
SoftDeletes | Model | «Мягкое удаление»: deleted_at, trashed(), restore(), forceDelete() |
Notifiable | User model | $user->notify(), $user->notifications |
HasApiTokens | User model | Sanctum: $user->createToken(), $user->tokens |
Dispatchable | Job, Event | MyJob::dispatch() — статический метод создания |
InteractsWithQueue | Job | $this->attempts(), $this->release(), $this->delete() |
Queueable | Job, Mail, Notification | ->onQueue(), ->delay(), ->onConnection() |
SerializesModels | Job, Mail, Event | Модели сериализуются по ID при передаче в очередь |
AuthorizesRequests | Controller | $this->authorize() — проверка Policy |
ValidatesRequests | Controller | $this->validate() — валидация в контроллере |
HasRoles | User (Spatie) | $user->assignRole('admin'), $user->hasRole() — пакет Spatie |
HasUuids | Model | UUID вместо auto-increment ID |
HasUlids | Model | ULID вместо auto-increment ID |
Searchable | Model (Scout) | Полнотекстовый поиск через Algolia/Meilisearch |
Как это выглядит в модели
class User extends Authenticatable
{
use HasFactory;
use Notifiable;
use SoftDeletes;
use HasApiTokens;
use HasRoles;
}
Свой трейт
trait HasSlug
{
public static function bootHasSlug(): void
{
static::creating(function ($model) {
$model->slug = Str::slug($model->title);
});
}
public function getRouteKeyName(): string
{
return 'slug';
}
}
class Post extends Model { use HasSlug; }
Фасады (Facades)
Фасад — это статическая обёртка над сервисом из контейнера. Когда ты пишешь Cache::get('key'), на самом деле вызывается app('cache')->get('key'). Фасад — это синтаксический сахар.
Как работает: Фасад — класс, наследующий Illuminate\Support\Facades\Facade. Он определяет метод getFacadeAccessor(), который возвращает ключ сервиса в контейнере. Магический метод __callStatic() перенаправляет вызовы к реальному объекту.
Популярные фасады
| Фасад | Реальный сервис | Примеры |
Route | Illuminate\Routing\Router | Route::get(), Route::post(), Route::resource() |
DB | Illuminate\Database\DatabaseManager | DB::table(), DB::select(), DB::transaction() |
Cache | Illuminate\Cache\CacheManager | Cache::get(), Cache::put(), Cache::remember() |
Auth | Illuminate\Auth\AuthManager | Auth::user(), Auth::check(), Auth::login() |
Log | Illuminate\Log\LogManager | Log::info(), Log::error() |
Mail | Illuminate\Mail\Mailer | Mail::to()->send() |
Storage | Illuminate\Filesystem\FilesystemManager | Storage::put(), Storage::get(), Storage::url() |
Gate | Illuminate\Auth\Access\Gate | Gate::allows(), Gate::define() |
Event | Illuminate\Events\Dispatcher | Event::listen(), Event::dispatch() |
Queue | Illuminate\Queue\QueueManager | Queue::push(), Queue::size() |
Schema | Illuminate\Database\Schema\Builder | Schema::create(), Schema::hasTable() |
Validator | Illuminate\Validation\Factory | Validator::make() |
Как создать свой фасад
class SmsService
{
public function send(string $phone, string $message): bool { }
}
class Sms extends Facade
{
protected static function getFacadeAccessor(): string
{
return SmsService::class;
}
}
$this->app->singleton(SmsService::class);
Sms::send('+77001234567', 'Код: 1234');
Contracts vs Facades
И Contracts (интерфейсы), и Facades решают одну задачу — абстракция от реализации. Но подход разный:
| Аспект | Contracts (interfaces) | Facades |
| Синтаксис | __construct(CacheContract $cache) | Cache::get('key') |
| DI | Явный — через конструктор | Неявный — через __callStatic |
| Тестирование | Легко мокать через DI | Cache::shouldReceive() |
| IDE support | Полный автокомплит | Нужен пакет laravel-ide-helper |
| Когда использовать | В сервисах, бизнес-логике | В контроллерах, routes, config — для удобства |
Правило: В бизнес-логике (сервисы, репозитории) — используй Contracts (DI через конструктор). В контроллерах, middleware, artisan commands — фасады допустимы для читаемости. Формулировать так: «Я предпочитаю DI через интерфейсы для тестируемости, но фасады удобны в контроллерах».
Шпаргалка: всё в одной таблице
| Ты создаёшь | Наследуешь / реализуешь | Что переопределяешь | Ключевые трейты |
| Controller | extends Controller | Свои методы-actions | AuthorizesRequests, ValidatesRequests |
| Model | extends Model | $fillable, $casts, relations, scopes | HasFactory, SoftDeletes, Notifiable |
| FormRequest | extends FormRequest | rules(), authorize(), messages() | — |
| Middleware | Ничего | handle($request, $next) | — |
| Job | implements ShouldQueue | handle(), failed() | Dispatchable, Queueable, SerializesModels |
| Command | extends Command | $signature, handle() | — |
| Notification | extends Notification | via(), toMail(), toArray() | Queueable |
| Mailable | extends Mailable | envelope(), content(), attachments() | Queueable, SerializesModels |
| Event | Ничего (DTO) | — | Dispatchable, SerializesModels |
| Listener | implements ShouldQueue (опц.) | handle($event) | — |
| Policy | Ничего | view, create, update, delete, before | — |
| Seeder | extends Seeder | run() | — |
| Factory | extends Factory | definition(), states | — |
| Rule | implements ValidationRule | validate() | — |
| Resource | extends JsonResource | toArray() | — |
| ServiceProvider | extends ServiceProvider | register(), boot() | — |
| Migration | extends Migration | up(), down() | — |
Проверь себя
Что такое FormRequest и зачем он нужен?
FormRequest — это класс, наследующий Illuminate\Foundation\Http\FormRequest (который сам наследует Request). Он инкапсулирует логику валидации и авторизации, убирая её из контроллера. Laravel автоматически вызывает валидацию через Service Container ДО входа в метод контроллера. Ты переопределяешь rules() (правила валидации) и authorize() (проверка прав). Дополнительно можно переопределить messages(), prepareForValidation(), passedValidation().
Чем отличается интерфейс от трейта?
Интерфейс (interface) — это контракт: он объявляет методы, но НЕ содержит реализации. Класс, реализующий интерфейс, обязан написать все методы. Трейт — это набор готовых методов, который класс «подключает» через use. Трейт содержит реализацию, но не может быть инстанцирован сам по себе. Интерфейс отвечает на вопрос «что ты обязуешься делать?», трейт — «какие готовые способности ты получаешь?».
Что делает trait SoftDeletes?
Добавляет «мягкое удаление». Вместо реального DELETE из базы, $model->delete() заполняет столбец deleted_at текущей датой. Все запросы через Eloquent автоматически исключают «удалённые» записи (WHERE deleted_at IS NULL). Для доступа к удалённым: withTrashed(), onlyTrashed(). Для реального удаления: forceDelete(). Для восстановления: restore().
Что такое фасад и как он работает внутри?
Фасад — это статическая обёртка над сервисом из Service Container. Внутри каждый фасад наследует Illuminate\Support\Facades\Facade и определяет метод getFacadeAccessor(), возвращающий ключ сервиса. Когда ты вызываешь Cache::get('key'), магический метод __callStatic() перехватывает вызов, достаёт объект из контейнера по ключу, и вызывает метод get() уже на реальном объекте. Это синтаксический сахар для app('cache')->get('key').
Зачем ShouldQueue — это интерфейс, а не класс?
Потому что ShouldQueue — это маркерный интерфейс (marker interface). Он не содержит методов, которые нужно реализовать. Он просто «маркирует» класс: «этот класс должен обрабатываться через очередь». Laravel проверяет $job instanceof ShouldQueue и решает — выполнить синхронно или отправить в очередь. Если бы это был класс, Job/Notification/Listener не смогли бы его наследовать (в PHP нет множественного наследования классов), а интерфейсов можно реализовать сколько угодно.
Что даёт trait HasFactory и зачем его подключать к модели?
HasFactory добавляет модели статический метод factory(), который создаёт экземпляр соответствующей Factory-класса. По Convention: для App\Models\User ищется Database\Factories\UserFactory. Без этого трейта ты бы писал UserFactory::new()->create() вместо User::factory()->create(). Это нужно для тестов (генерация тестовых данных) и сидеров.
Можно ли создать контроллер без extends Controller?
Да! Laravel не требует наследования для контроллера. Можно создать простой class с методами — роутер вызовет их через контейнер. Или invokable controller с методом __invoke(). Но без наследования ты теряешь: $this->middleware(), $this->authorize(), $this->validate() — эти методы приходят из базового Controller и его трейтов.
Чем Contracts отличаются от Facades в Laravel?
Оба предоставляют доступ к сервисам контейнера, но по-разному. Contracts — это интерфейсы из пакета illuminate/contracts, их инжектят через конструктор (DI). Facades — статические обёртки, вызываются как Cache::get(). Contracts лучше для тестируемости и явного DI. Facades удобнее синтаксически. В бизнес-логике предпочтительны Contracts, в контроллерах допустимы Facades.
Зачем trait SerializesModels в Job?
Когда Job отправляется в очередь, все его свойства сериализуются в JSON/строку для хранения (Redis, database). Без SerializesModels, Eloquent модель сериализовалась бы целиком (все атрибуты, relations). С этим трейтом — сохраняется только ID модели и имя класса. Когда worker берёт Job из очереди, трейт по ID достаёт свежую модель из БД. Это: 1) экономит место в очереди, 2) гарантирует актуальные данные, 3) избегает проблем с сериализацией сложных объектов.
Что такое register() vs boot() в ServiceProvider?
register() вызывается первым — здесь только привязки контейнера (bind, singleton). Нельзя использовать другие сервисы, потому что они могут быть ещё не зарегистрированы. boot() вызывается ПОСЛЕ того, как ВСЕ провайдеры отработали register(). Здесь можно делать всё: регистрировать observers, маршруты, Blade directives, event listeners. Порядок: все register() → все boot().