26 вопросов
public/index.php - точка входа, загружает Composer autoloadService Providers загружаются при создании Application - они регистрируют сервисы, маршруты, middleware, event listeners.
Service Container (IoC Container) - ядро Laravel. Управляет зависимостями и их внедрением:
// Регистрация
app()->bind(PaymentGateway::class, StripeGateway::class);
app()->singleton(Logger::class, fn() => new FileLogger('/logs'));
// Auto-resolution (без явной регистрации)
class UserController {
public function __construct(
private UserService $service, // Laravel сам создаст
) {}
}
// Контекстная привязка
app()->when(PhotoController::class)
->needs(FileSystem::class)
->give(S3FileSystem::class);Auto-resolution использует Reflection для анализа конструктора и автоматического создания зависимостей.
Service Provider - центральное место для регистрации сервисов, привязок, событий, middleware:
class PaymentServiceProvider extends ServiceProvider {
public function register(): void {
// Привязки в контейнер
$this->app->singleton(PaymentGateway::class, StripeGateway::class);
}
public function boot(): void {
// После регистрации всех провайдеров
// Маршруты, события, команды
$this->loadRoutesFrom(__DIR__.'/../routes/payment.php');
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
}Провайдеры регистрируются в config/app.php. register() - только привязки. boot() - любая логика (все сервисы уже доступны).
Facade - статический интерфейс к сервисам из контейнера:
// Фасад
Cache::get('key');
// Эквивалент
app('cache')->get('key');
// Под капотом:
class Cache extends Facade {
protected static function getFacadeAccessor(): string {
return 'cache'; // ключ в контейнере
}
}
// __callStatic() перехватывает вызов и делегирует реальному объектуФасады удобны для быстрой разработки, но скрывают зависимости. Для тестируемости предпочитайте DI через конструктор. Real-time facades: use Facades\App\Services\PaymentService;
class AuthMiddleware {
public function handle(Request $request, Closure $next): Response {
if (!auth()->check()) {
return redirect('/login');
}
return $next($request); // передать следующему
}
// After middleware
public function terminate(Request $request, Response $response): void {
// После отправки ответа клиенту
Log::info('Request completed', ['url' => $request->url()]);
}
}Типы: глобальные (каждый запрос), route middleware (для конкретных маршрутов), middleware groups (web, api). Регистрация в bootstrap/app.php (Laravel 11+).
Eloquent - ORM в Laravel, реализующий паттерн Active Record:
class User extends Model {
protected $fillable = ['name', 'email'];
protected $casts = ['email_verified_at' => 'datetime'];
public function posts(): HasMany {
return $this->hasMany(Post::class);
}
}
// CRUD
$user = User::create(['name' => 'John', 'email' => 'j@test.com']);
$user = User::find(1);
$user = User::where('email', 'j@test.com')->first();
$user->update(['name' => 'Jane']);
$user->delete();
// Query Builder
User::where('age', '>', 18)->orderBy('name')->paginate(15);Active Record (Eloquent) - модель = запись в таблице. Модель содержит и данные, и логику работы с БД:
$user = User::find(1); // модель знает как загрузить себя
$user->name = 'Jane';
$user->save(); // и как сохранитьData Mapper (Doctrine) - модель не знает о БД. Отдельный маппер (EntityManager) управляет персистенцией:
$user = $entityManager->find(User::class, 1);
$user->setName('Jane');
$entityManager->flush(); // маппер сохраняет измененияActive Record проще, Data Mapper - чище (модель не зависит от инфраструктуры). Для сложного домена Data Mapper предпочтительнее.
class User extends Model {
public function phone(): HasOne { return $this->hasOne(Phone::class); }
public function posts(): HasMany { return $this->hasMany(Post::class); }
public function roles(): BelongsToMany { return $this->belongsToMany(Role::class); }
}
class Post extends Model {
public function user(): BelongsTo { return $this->belongsTo(User::class); }
public function tags(): MorphToMany { return $this->morphToMany(Tag::class, 'taggable'); }
}
class Country extends Model {
public function posts(): HasManyThrough {
return $this->hasManyThrough(Post::class, User::class);
}
}Типы: hasOne, hasMany, belongsTo, belongsToMany (M:N через pivot), hasManyThrough, morphOne/morphMany/morphToMany (полиморфные).
// N+1 проблема
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // запрос на каждый пост!
}
// Eager loading - решение
$posts = Post::with('author')->get(); // 2 запроса
$posts = Post::with(['author', 'tags'])->get(); // 3 запроса
$posts = Post::with('author.profile')->get(); // вложенные
// Lazy eager loading (после загрузки)
$posts->load('comments');
// Подсчет без загрузки
$posts = Post::withCount('comments')->get();
echo $post->comments_count;
// Предотвращение N+1 (Laravel 11+)
Model::preventLazyLoading(); // Exception при lazy loadclass User extends Model {
// Local scope - вызывается явно
public function scopeActive(Builder $query): Builder {
return $query->where('status', 'active');
}
public function scopeOlderThan(Builder $query, int $age): Builder {
return $query->where('age', '>', $age);
}
}
// Использование
User::active()->olderThan(18)->get();
// Global scope - применяется ко всем запросам
class ActiveScope implements Scope {
public function apply(Builder $builder, Model $model): void {
$builder->where('is_active', true);
}
}
// User::withoutGlobalScope(ActiveScope::class)->get();// Laravel 9+ (Attribute casting)
class User extends Model {
protected function firstName(): Attribute {
return Attribute::make(
get: fn(string $value) => ucfirst($value), // accessor
set: fn(string $value) => strtolower($value), // mutator
);
}
// Виртуальный атрибут
protected function fullName(): Attribute {
return Attribute::make(
get: fn() => "{$this->first_name} {$this->last_name}",
);
}
// Casts (встроенные)
protected $casts = [
'options' => 'array',
'birthday' => 'date',
'is_admin' => 'boolean',
'status' => StatusEnum::class,
];
}use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model {
use SoftDeletes; // добавляет deleted_at
}
$post->delete(); // устанавливает deleted_at (не удаляет из БД)
$post->forceDelete(); // реальное удаление
$post->restore(); // восстановление
Post::all(); // без удаленных
Post::withTrashed()->get(); // все, включая удаленные
Post::onlyTrashed()->get(); // только удаленныеМиграция: $table->softDeletes(); добавляет nullable deleted_at timestamp.
// Создание: php artisan make:migration create_users_table
class CreateUsersTable extends Migration {
public function up(): void {
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->timestamps();
$table->softDeletes();
$table->index(['name', 'email']);
});
}
public function down(): void {
Schema::dropIfExists('users');
}
}
// php artisan migrate
// php artisan migrate:rollback
// php artisan migrate:fresh --seedМиграции - version control для базы данных. Каждая миграция выполняется один раз (трекинг в таблице migrations).
// php artisan make:request CreateUserRequest
class CreateUserRequest extends FormRequest {
public function authorize(): bool {
return $this->user()->can('create', User::class);
}
public function rules(): array {
return [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'age' => 'required|integer|min:18',
];
}
public function messages(): array {
return ['email.unique' => 'Этот email уже занят'];
}
}
// Контроллер
public function store(CreateUserRequest $request): JsonResponse {
$validated = $request->validated(); // уже валидировано
$user = User::create($validated);
return response()->json($user, 201);
}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')),
'links' => ['self' => route('users.show', $this)],
];
}
}
// Использование
return new UserResource($user);
return UserResource::collection(User::paginate(15));Resources - трансформационный слой между моделями и JSON-ответами API. Контролирует, какие данные и в каком формате отдаются клиенту.
// Gate - простое правило авторизации
Gate::define('update-post', fn(User $user, Post $post) => $user->id === $post->user_id);
// Policy - набор правил для модели
class PostPolicy {
public function update(User $user, Post $post): bool {
return $user->id === $post->user_id;
}
public function delete(User $user, Post $post): bool {
return $user->isAdmin() || $user->id === $post->user_id;
}
}
// Использование
$this->authorize('update', $post);
if ($user->can('delete', $post)) { /* ... */ }
@can('update', $post) ... @endcan// Job
class SendEmailJob implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public User $user) {}
public function handle(Mailer $mailer): void {
$mailer->send($this->user->email, new WelcomeEmail());
}
public function failed(Throwable $e): void {
Log::error('Email failed', ['user' => $this->user->id]);
}
}
// Отправка в очередь
SendEmailJob::dispatch($user);
SendEmailJob::dispatch($user)->onQueue('emails')->delay(now()->addMinutes(5));Драйверы: database, Redis, SQS, Beanstalkd. Воркер: php artisan queue:work --queue=emails
// Запуск
SendEmailJob::dispatch($user); // async
SendEmailJob::dispatchSync($user); // sync (для тестов)
SendEmailJob::dispatch($user)->afterCommit(); // после commit транзакции
// Обработка ошибок
class ProcessPayment implements ShouldQueue {
public int $tries = 3; // макс. попыток
public int $backoff = 60; // секунд между попытками
public int $timeout = 120; // таймаут выполнения
public int $maxExceptions = 2; // макс. исключений
public function handle(): void { /* ... */ }
public function failed(Throwable $e): void {
// Вызывается после исчерпания попыток
Notification::send($admin, new JobFailed($e));
}
public function retryUntil(): DateTime {
return now()->addHours(1);
}
}// Event
class UserRegistered {
public function __construct(public User $user) {}
}
// Listener
class SendWelcomeEmail {
public function handle(UserRegistered $event): void {
Mail::to($event->user)->send(new WelcomeMail());
}
}
// Регистрация (EventServiceProvider или атрибуты)
Event::listen(UserRegistered::class, SendWelcomeEmail::class);
// Dispatch
event(new UserRegistered($user));
UserRegistered::dispatch($user);Events обеспечивают слабую связанность - код, создающий событие, не знает о слушателях.
class UserObserver {
public function creating(User $user): void {
$user->uuid = Str::uuid();
}
public function created(User $user): void {
Mail::to($user)->send(new WelcomeMail());
}
public function updating(User $user): void { /* ... */ }
public function deleting(User $user): void { /* ... */ }
}
// Регистрация
User::observe(UserObserver::class);
// Или через атрибут #[ObservedBy(UserObserver::class)]Observer реагирует на Eloquent события модели: creating, created, updating, updated, deleting, deleted, restoring, restored.
// routes/console.php (Laravel 11+)
Schedule::command('reports:generate')->dailyAt('06:00');
Schedule::job(new CleanupJob)->hourly();
Schedule::call(fn() => DB::table('temp')->delete())->daily();
// Настройки
Schedule::command('emails:send')
->everyFiveMinutes()
->withoutOverlapping() // не запускать если предыдущий не завершился
->onOneServer() // только на одном сервере
->runInBackground()
->emailOutputTo('admin@test.com');
// Cron на сервере (один раз):
// * * * * * cd /app && php artisan schedule:run >> /dev/null 2>&1Sanctum - простая аутентификация: API tokens + SPA cookie auth. Для простых приложений.
Passport - полная реализация OAuth2 сервера. Для приложений, предоставляющих API третьим сторонам.
// Sanctum: API token
$token = $user->createToken('api-token')->plainTextToken;
// Authorization: Bearer {token}
// Sanctum: SPA auth (cookie-based)
// Автоматически через middlewareВыбор: Sanctum для 90% проектов (SPA, mobile, simple API). Passport - когда нужен OAuth2 (авторизация для third-party apps).
use Illuminate\Pipeline\Pipeline;
$result = app(Pipeline::class)
->send($order)
->through([
ValidateOrder::class,
CalculateTotal::class,
ApplyDiscount::class,
ProcessPayment::class,
])
->thenReturn();
// Каждый шаг
class CalculateTotal {
public function handle(Order $order, Closure $next): mixed {
$order->total = $order->items->sum('price');
return $next($order);
}
}Pipeline = Chain of Responsibility. Laravel middleware работают именно так. Полезно для обработки заказов, ETL, конвейеров трансформации.
class OrderShipped extends Notification {
public function via(User $notifiable): array {
return ['mail', 'database', 'slack'];
}
public function toMail(User $notifiable): MailMessage {
return (new MailMessage)
->subject('Order Shipped')
->line('Your order has been shipped.');
}
public function toDatabase(User $notifiable): array {
return ['order_id' => $this->order->id, 'status' => 'shipped'];
}
}
$user->notify(new OrderShipped($order));
Notification::send($users, new OrderShipped($order));Каналы: mail, database, broadcast, Slack, SMS (Vonage), push и custom.
// Базовые операции
Cache::put('key', 'value', now()->addHours(1));
$value = Cache::get('key', 'default');
Cache::forget('key');
// Remember - кеш с lazy-загрузкой
$users = Cache::remember('users', 3600, fn() => User::all());
// Tags
Cache::tags(['users', 'admins'])->put('key', $data, 600);
Cache::tags('users')->flush();
// Atomic lock
Cache::lock('processing', 10)->block(5, function () {
// эксклюзивный доступ на 10 секунд
});Драйверы: file, database, Redis, Memcached, DynamoDB, array (для тестов).
Контроллер должен быть "тонким" - только маршрутизация, валидация, ответ:
// 1. Service - бизнес-логика
class OrderService {
public function create(CreateOrderDTO $dto): Order {
// валидация, расчеты, сохранение
}
}
// 2. Action - одно действие (Single Action Class)
class CreateOrderAction {
public function execute(CreateOrderDTO $dto): Order { /* ... */ }
}
// 3. Repository - работа с БД
class OrderRepository {
public function findActive(): Collection {
return Order::where('status', 'active')->get();
}
}
// Контроллер
class OrderController {
public function store(CreateOrderRequest $request, CreateOrderAction $action) {
$order = $action->execute(CreateOrderDTO::fromRequest($request));
return new OrderResource($order);
}
}