Зачем этот раздел

В 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 Bindingshow(User $user) — Laravel сам достанет модель из БД по ID
Form Request Injectionstore(StoreRequest $request) — валидация до входа в метод
AuthorizesRequests (trait)$this->authorize('update', $post) — проверка Policy
ValidatesRequests (trait)$this->validate($request, [...]) — валидация прямо в контроллере
Упрощённый код родителя
// Illuminate\Routing\Controller (упрощённо)
abstract class Controller
{
    protected $middleware = [];

    public function middleware($middleware, array $options = [])
    {
        // Регистрирует middleware для этого контроллера
        $this->middleware[] = [
            'middleware' => $middleware,
            'options'    => &$options,
        ];
    }

    public function getMiddleware() { return $this->middleware; }

    public function callAction($method, $parameters)
    {
        return $this->{$method}(...array_values($parameters));
    }
}

// App\Http\Controllers\Controller (базовый — ты можешь редактировать)
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); // ← от trait AuthorizesRequests
        return User::paginate();
    }

    public function store(StoreUserRequest $request) // ← FormRequest injection
    {
        return User::create($request->validated());
    }

    public function show(User $user)   // ← Route Model Binding
    {
        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 BuilderUser::where('age', '>', 18)->get() — цепочки запросов
RelationshasMany(), belongsTo(), morphMany() — все методы отношений
Mass AssignmentЗащита через $fillable / $guarded
Casting$casts = ['is_admin' => 'boolean']
Eventscreating, created, updating, deleted — lifecycle hooks
ScopesscopeActive($q) → User::active()->get()
Accessors/MutatorsgetFullNameAttribute(), setPasswordAttribute()
Timestampscreated_at, updated_at автоматически
Serialization$user->toArray(), $user->toJson(), $hidden, $visible
Упрощённый код родителя
// Illuminate\Database\Eloquent\Model (сильно упрощён)
abstract class Model implements Arrayable, Jsonable, JsonSerializable
{
    use HasAttributes, HasEvents, HasRelationships,
        HasTimestamps, HidesAttributes, GuardsAttributes;

    protected $table;         // имя таблицы (Convention: users)
    protected $primaryKey = 'id';
    protected $fillable = [];  // разрешённые для mass assignment
    protected $guarded = ['*']; // запрещённые (по умолчанию — все)
    protected $hidden = [];    // скрыть при toArray/toJson
    protected $casts = [];     // приведение типов
    public $timestamps = true;

    public static function create(array $attributes) { /* ... */ }
    public function save() { /* INSERT или UPDATE */ }
    public function delete() { /* DELETE */ }
    public static function find($id) { /* SELECT WHERE id = ? */ }
    public static function where(...) { /* Query Builder */ }

    // Relationship methods ты определяешь в СВОЕЙ модели
    public function hasMany($related, ...) { /* HasMany relation */ }
    public function belongsTo($related, ...) { /* BelongsTo relation */ }
}
Твой класс
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); // ← метод из родителя
    }

    // Scope — ты определяешь, Laravel вызывает как User::active()
    public function scopeActive($query)
    {
        return $query->where('is_active', true);
    }
}
Authenticatable: Модель User также наследует Illuminate\Foundation\Auth\User as Authenticatable, который добавляет токены аутентификации, remember token, и реализует контракт AuthenticatableContract.
FormRequest
Illuminate\Http\Request ← Illuminate\Foundation\Http\FormRequest ← App\Http\Requests\LoginRequest
Что даёт родитель бесплатно
ВозможностьОписание
АвтовалидацияВызывается автоматически до входа в метод контроллера
authorize()Проверка прав — если false, вернёт 403
rules()Ты определяешь правила, Laravel их применяет
messages()Кастомные сообщения об ошибках
validated()Возвращает только провалидированные данные (безопасно для create)
prepareForValidation()Hook: преобразовать данные до валидации
passedValidation()Hook: действия после успешной валидации
failedValidation()Hook: кастомный ответ при ошибке
Всё от Request$this->input(), $this->file(), $this->ip(), etc.
Упрощённый код родителя
// Illuminate\Foundation\Http\FormRequest (упрощён)
class FormRequest extends Request
{
    // Laravel вызывает это АВТОМАТИЧЕСКИ через Service Container
    public function validateResolved()
    {
        $this->prepareForValidation();    // ← твой hook

        if (! $this->passesAuthorization()) {
            $this->failedAuthorization();   // → 403
        }

        $validator = $this->getValidatorInstance();
        // Правила берёт из $this->rules()

        if ($validator->fails()) {
            $this->failedValidation($validator); // → redirect/422
        }

        $this->passedValidation();           // ← твой hook
    }

    public function authorize() { return true; }
    public function rules()     { return []; }
    public function messages()  { return []; }
    public function validated() { /* возвращает только проверенные данные */ }
    protected function prepareForValidation() { /* пусто — ты переопределяешь */ }
    protected function passedValidation() { /* пусто */ }
}
Твой класс
class LoginRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true; // любой может попробовать залогиниться
    }

    public function rules(): array
    {
        return [
            'email'    => ['required', 'email'],
            'password' => ['required', 'min:8'],
        ];
    }

    public function messages(): array
    {
        return [
            'email.required' => 'Введите email',
        ];
    }

    protected function prepareForValidation()
    {
        $this->merge([
            'email' => strtolower($this->email),
        ]);
    }
}
Запомни: validated() — возвращает ТОЛЬКО те поля, которые прошли валидацию. Это безопасно передавать в Model::create(). А $request->all() — опасно, может содержать лишние поля.
Middleware
Нет наследования! Middleware в Laravel — это просто класс с методом handle(). Он реализует паттерн «Pipeline» (цепочка обработчиков). Никакого родительского класса не требуется.
Что Laravel ожидает
// Middleware — просто класс с handle()
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())
// After middleware
public function handle($request, Closure $next)
{
    $response = $next($request);  // сначала контроллер
    $response->header('X-Custom', 'value');  // потом модификация
    return $response;
}

// Terminable middleware
public function terminate($request, $response)
{
    // Логирование — после того как клиент УЖЕ получил ответ
    Log::info('Request processed', ['url' => $request->url()]);
}
Job (Queue)
— implements ShouldQueue + App\Jobs\SendWelcomeEmail
Что дают трейты и интерфейс
ЭлементТипЧто даёт
ShouldQueueinterfaceМаркер — Laravel знает, что класс надо отправить в очередь
DispatchabletraitSendWelcomeEmail::dispatch($user) — статический метод отправки
InteractsWithQueuetrait$this->attempts(), $this->release(30), $this->delete()
Queueabletrait->onQueue('emails'), ->delay(60), ->onConnection('redis')
SerializesModelstraitМодели в конструкторе автоматически сериализуются по 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 // ← SerializesModels сохранит только ID
    ) {}

    public function handle(MailService $mail): void // ← DI из контейнера
    {
        $mail->send($this->user, 'welcome');
    }

    public function failed(Throwable $e): void // ← hook при неудаче
    {
        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)); // ← метод из trait Notifiable
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;
    }

    // before() — суперадмин может всё
    public function before(User $user, string $ability): ?bool
    {
        if ($user->isAdmin()) {
            return true;
        }
        return null; // 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    // в Blade
Gate::allows('update', $post);        // программно
Seeder / Factory
Seeder
Illuminate\Database\Seeder ← Database\Seeders\UserSeeder
Что даётОписание
run()Ты переопределяешь — заполняешь таблицу тестовыми данными
call()Вызвать другие сидеры: $this->call(UserSeeder::class)
commandphp artisan db:seed
class UserSeeder extends Seeder
{
    public function run(): void
    {
        User::factory(50)->create(); // ← Factory!
    }
}
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 // state
    {
        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+ способ
// bootstrap/app.php
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
ИнтерфейсПакетЧто обязывает
ShouldQueueContracts\QueueМаркер — класс обрабатывается через очередь (Job, Listener, Notification, Mail)
ShouldBroadcastContracts\BroadcastingEvent передаётся через WebSocket (broadcastOn(), broadcastAs())
AuthenticatableContracts\AuthМодель может использоваться для auth (getAuthIdentifier, getAuthPassword...)
ArrayableContracts\SupportКласс может быть преобразован в array — toArray()
JsonableContracts\SupportКласс может быть преобразован в JSON — toJson()
RenderableContracts\SupportКласс может вернуть HTML — render()
ResponsableContracts\SupportКласс может быть возвращён из контроллера как response — toResponse()
ValidationRuleContracts\ValidationКастомное правило валидации — validate()
CastsAttributesContracts\DatabaseКастомный cast для Eloquent — get(), set()
HtmlableContracts\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
    {
        // Stripe API call
    }

    public function refund(string $transactionId): bool
    {
        // Stripe refund
    }
}

// Привязка в ServiceProvider
$this->app->bind(PaymentGatewayInterface::class, StripeGateway::class);

// В контроллере — зависишь от ИНТЕРФЕЙСА, не реализации
public function pay(PaymentGatewayInterface $gateway) { ... }
Трейты

Трейт — это горизонтальное наследование. Класс может использовать только один родительский класс (extends), но сколько угодно трейтов (use). Laravel активно использует трейты для переиспользования кода.

Разница: Наследование = «я являюсь» (User является Model). Трейт = «я умею» (User умеет SoftDeletes). Интерфейс = «я обязуюсь» (класс обязуется реализовать ShouldQueue).
Ключевые трейты Laravel
ТрейтГде используетсяЧто даёт
HasFactoryModelUser::factory() — создание тестовых данных
SoftDeletesModel«Мягкое удаление»: deleted_at, trashed(), restore(), forceDelete()
NotifiableUser model$user->notify(), $user->notifications
HasApiTokensUser modelSanctum: $user->createToken(), $user->tokens
DispatchableJob, EventMyJob::dispatch() — статический метод создания
InteractsWithQueueJob$this->attempts(), $this->release(), $this->delete()
QueueableJob, Mail, Notification->onQueue(), ->delay(), ->onConnection()
SerializesModelsJob, Mail, EventМодели сериализуются по ID при передаче в очередь
AuthorizesRequestsController$this->authorize() — проверка Policy
ValidatesRequestsController$this->validate() — валидация в контроллере
HasRolesUser (Spatie)$user->assignRole('admin'), $user->hasRole() — пакет Spatie
HasUuidsModelUUID вместо auto-increment ID
HasUlidsModelULID вместо auto-increment ID
SearchableModel (Scout)Полнотекстовый поиск через Algolia/Meilisearch
Как это выглядит в модели
class User extends Authenticatable   // ← наследование (1 родитель)
{
    use HasFactory;          // User::factory() — тестирование
    use Notifiable;          // $user->notify() — уведомления
    use SoftDeletes;         // $user->delete() → deleted_at
    use HasApiTokens;        // Sanctum tokens
    use HasRoles;            // Spatie permissions

    // Теперь User УМЕЕТ всё вышеперечисленное!
}
Свой трейт
trait HasSlug
{
    public static function bootHasSlug(): void // boot{TraitName} — авто-вызов
    {
        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() перенаправляет вызовы к реальному объекту.
Популярные фасады
ФасадРеальный сервисПримеры
RouteIlluminate\Routing\RouterRoute::get(), Route::post(), Route::resource()
DBIlluminate\Database\DatabaseManagerDB::table(), DB::select(), DB::transaction()
CacheIlluminate\Cache\CacheManagerCache::get(), Cache::put(), Cache::remember()
AuthIlluminate\Auth\AuthManagerAuth::user(), Auth::check(), Auth::login()
LogIlluminate\Log\LogManagerLog::info(), Log::error()
MailIlluminate\Mail\MailerMail::to()->send()
StorageIlluminate\Filesystem\FilesystemManagerStorage::put(), Storage::get(), Storage::url()
GateIlluminate\Auth\Access\GateGate::allows(), Gate::define()
EventIlluminate\Events\DispatcherEvent::listen(), Event::dispatch()
QueueIlluminate\Queue\QueueManagerQueue::push(), Queue::size()
SchemaIlluminate\Database\Schema\BuilderSchema::create(), Schema::hasTable()
ValidatorIlluminate\Validation\FactoryValidator::make()
Как создать свой фасад
// 1. Сервис
class SmsService
{
    public function send(string $phone, string $message): bool { /* ... */ }
}

// 2. Фасад
class Sms extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return SmsService::class; // ← ключ в контейнере
    }
}

// 3. Регистрация (ServiceProvider)
$this->app->singleton(SmsService::class);

// 4. Использование
Sms::send('+77001234567', 'Код: 1234');
// ↑ на самом деле: app(SmsService::class)->send(...)
Contracts vs Facades

И Contracts (интерфейсы), и Facades решают одну задачу — абстракция от реализации. Но подход разный:

АспектContracts (interfaces)Facades
Синтаксис__construct(CacheContract $cache)Cache::get('key')
DIЯвный — через конструкторНеявный — через __callStatic
ТестированиеЛегко мокать через DICache::shouldReceive()
IDE supportПолный автокомплитНужен пакет laravel-ide-helper
Когда использоватьВ сервисах, бизнес-логикеВ контроллерах, routes, config — для удобства
Правило: В бизнес-логике (сервисы, репозитории) — используй Contracts (DI через конструктор). В контроллерах, middleware, artisan commands — фасады допустимы для читаемости. Формулировать так: «Я предпочитаю DI через интерфейсы для тестируемости, но фасады удобны в контроллерах».
Шпаргалка: всё в одной таблице
Ты создаёшьНаследуешь / реализуешьЧто переопределяешьКлючевые трейты
Controllerextends ControllerСвои методы-actionsAuthorizesRequests, ValidatesRequests
Modelextends Model$fillable, $casts, relations, scopesHasFactory, SoftDeletes, Notifiable
FormRequestextends FormRequestrules(), authorize(), messages()—
MiddlewareНичегоhandle($request, $next)—
Jobimplements ShouldQueuehandle(), failed()Dispatchable, Queueable, SerializesModels
Commandextends Command$signature, handle()—
Notificationextends Notificationvia(), toMail(), toArray()Queueable
Mailableextends Mailableenvelope(), content(), attachments()Queueable, SerializesModels
EventНичего (DTO)—Dispatchable, SerializesModels
Listenerimplements ShouldQueue (опц.)handle($event)—
PolicyНичегоview, create, update, delete, before—
Seederextends Seederrun()—
Factoryextends Factorydefinition(), states—
Ruleimplements ValidationRulevalidate()—
Resourceextends JsonResourcetoArray()—
ServiceProviderextends ServiceProviderregister(), boot()—
Migrationextends Migrationup(), 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().