Перейти к основному содержимому

5.07. Laravel и шаблоны проектирования

Разработчику Архитектору

Laravel и шаблоны проектирования

Фреймворк Laravel строится на наборе устоявшихся шаблонов проектирования. Понимание этих шаблонов дает возможность работать с кодом эффективно. Архитектура системы определяет структуру приложений. Каждый компонент выполняет конкретную функцию. Взаимодействие компонентов происходит по строгим правилам.

MVC (Model-View-Controller)

Шаблон MVC разделяет логику приложения на три уровня. Модель отвечает за данные. Представление отображает информацию. Контроллер обрабатывает запросы. Laravel реализует эту схему в основе своей структуры.

Модель

Модель инкапсулирует состояние данных и бизнес-правила. Класс модели описывает сущность предметной области. В Laravel модели наследуются от базового класса Eloquent. Методы модели управляют взаимодействием с базой данных. Логика валидации данных размещается внутри модели. Модель хранит правила доступа к полям.

class User extends Model
{
protected $table = 'users';
protected $fillable = ['name', 'email', 'password'];
protected $hidden = ['password', 'remember_token'];

public function posts()
{
return $this->hasMany(Post::class);
}
}

Код выше описывает сущность пользователя. Свойство fillable определяет поля для массового назначения. Свойство hidden скрывает чувствительные данные при сериализации. Метод posts устанавливает связь с другими сущностями. Модель знает о структуре таблицы. Модель знает о типах данных. Модель знает о связях.

Представление

Представление формирует пользовательский интерфейс. Шаблоны Blade служат файлами представлений в Laravel. Файлы хранятся в директории resources/views. Синтаксис Blade позволяет встраивать PHP-код. Директивы управляют потоком данных. Представление получает данные от контроллера. Представление отображает данные пользователю. Представление не содержит бизнес-логики.

<div class="container">
<h1>{{ $user->name }}</h1>
@if($user->isActive())
<p>Статус: Активен</p>
@else
<p>Статус: Неактивен</p>
@endif
<ul>
@foreach($user->posts as $post)
<li>{{ $post->title }}</li>
@endforeach
</ul>
</div>

Шаблон использует переменные, переданные контроллером. Директива if проверяет условие. Цикл foreach перебирает коллекцию объектов. Синтаксис {{ }} экранирует вывод для защиты от XSS. Файл представления отвечает только за разметку. Файл представления отвечает только за отображение.

Контроллер

Контроллер принимает входящие запросы. Контроллер вызывает методы моделей. Контроллер передает данные в представления. Контроллер определяет ответ сервера. Классы контроллеров находятся в директории app/Http/Controllers. Методы контроллера соответствуют действиям пользователя.

class UserController extends Controller
{
public function show($id)
{
$user = User::findOrFail($id);
return view('user.profile', compact('user'));
}

public function update(Request $request, $id)
{
$user = User::findOrFail($id);
$user->update($request->validated());
return redirect()->route('user.show', $id);
}
}

Метод show извлекает запись из базы. Метод show возвращает представление. Метод update изменяет данные. Метод update проверяет входные данные. Контроллер координирует работу модели и представления. Контроллер остается тонким. Бизнес-логика переносится в сервисы или модели. Контроллер фокусируется на обработке запроса.

Взаимодействие компонентов

Запрос поступает на маршрутизатор. Маршрутизатор определяет контроллер. Контроллер запрашивает данные у модели. Модель обращается к базе данных. Модель возвращает объект контроллеру. Контроллер передает объект в представление. Представление генерирует HTML. Ответ отправляется клиенту. Цепочка вызовов сохраняет разделение ответственности. Изменение одного компонента затрагивает остальные минимально.


ActiveRecord (Eloquent ORM)

Паттерн ActiveRecord объединяет данные и логику работы с ними. Объект модели соответствует строке в таблице. Класс модели соответствует таблице в базе данных. Laravel реализует этот паттерн через компонент Eloquent. ORM скрывает сложности SQL-запросов. Разработчик работает с объектами языка PHP.

Маппинг объектов и таблиц

Eloquent автоматически связывает имена классов и таблиц. Класс User соответствует таблице users. Первичный ключ id предполагается по умолчанию. Временные метки created_at и updated_at поддерживаются автоматически. Настройки изменяются через свойства класса.

class Flight extends Model
{
protected $table = 'my_flights';
protected $primaryKey = 'flight_id';
public $incrementing = false;
public $timestamps = false;
}

Код выше демонстрирует настройку маппинга. Свойство table указывает имя таблицы. Свойство primaryKey задает ключ. Свойство incrementing отключает автоинкремент. Свойство timestamps отключает временные метки. Модель адаптируется под любую структуру базы данных.

Выполнение запросов

Методы модели генерируют SQL-запросы. Статические методы выполняют выборку. Цепочки методов формируют сложные условия. Результаты возвращаются в виде коллекций. Коллекции предоставляют методы для обработки данных.

$flights = Flight::where('active', 1)
->orderBy('price', 'desc')
->limit(10)
->get();

$flight = Flight::find(1);
$flight->status = 'delayed';
$flight->save();

Первый пример выбирает активные рейсы. Сортировка идет по цене. Ограничение устанавливает количество записей. Метод get выполняет запрос. Второй пример находит запись по ключу. Изменение свойства меняет состояние объекта. Метод save сохраняет изменения в базе. ORM отслеживает_dirty атрибуты. Запрос на обновление включает только измененные поля.

Связи между моделями

ActiveRecord упрощает работу со связями. Методы в моделях описывают типы связей. HasOne, HasMany, BelongsTo, BelongsToMany покрывают основные случаи. Доступ к связанным данным происходит через свойства. Запросы к связям оптимизируются через eager loading.

class Post extends Model
{
public function comments()
{
return $this->hasMany(Comment::class);
}
}

$posts = Post::with('comments')->get();
foreach ($posts as $post) {
foreach ($post->comments as $comment) {
echo $comment->text;
}
}

Метод comments определяет связь один-ко-многим. Статический метод with загружает связи заранее. Это предотвращает проблему N+1 запроса. Цикл обращается к загруженным данным. Доступ к свойствам связи не вызывает новых запросов. Структура кода остается читаемой. Логика связей инкапсулирована в моделях.

События и наблюдатели

Модели вызывают события при изменениях состояния. События created, updated, deleted, saved срабатывают автоматически. Наблюдатели слушают события и выполняют действия. Механизм позволяет реализовать побочную логику. Отправка уведомлений происходит через наблюдателей. Логирование изменений происходит через наблюдателей.

class UserObserver
{
public function created(User $user)
{
Log::info('New user registered: ' . $user->email);
}

public function updating(User $user)
{
if ($user->isDirty('email')) {
Notification::send($user, new EmailChanged());
}
}
}

Класс наблюдателя реагирует на события модели. Метод created срабатывает после создания записи. Метод updating срабатывает перед обновлением. Проверка isDirty выявляет измененные поля. Уведомление отправляется при смене почты. События развязывают модель и побочную логику. Модель хранит данные. Наблюдатель обрабатывает реакции.


Front Controller (Фронт-Контроллер)

Паттерн Front Controller обеспечивает единую точку входа. Все запросы проходят через один файл. Laravel использует файл public/index.php. Скрипт инициализирует приложение. Скрипт запускает обработку запроса. Централизация упрощает маршрутизацию и безопасность.

Точка входа

Файл index.php загружает автолоадер Composer. Скрипт создает экземпляр приложения. Скрипт обрабатывает входящий запрос. Ответ отправляется браузеру. Вся логика запуска скрыта внутри фреймворка.

require __DIR__.'/../vendor/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);

Первая строка подключает зависимости. Вторая строка создает контейнер приложения. Третья строка получает ядро HTTP. Объект Request фиксирует данные запроса. Метод handle запускает конвейер обработки. Метод send выводит результат. Метод terminate выполняет завершающие действия. Скрипт остается стабильным. Изменения вносятся внутри компонентов приложения.

Маршрутизация

Маршруты определяют логику обработки URL. Файлы маршрутов находятся в директории routes. Веб-маршруты отделяются от API-маршрутов. Маршрутизатор сопоставляет URL с контроллером. Параметры передаются в методы контроллера.

Route::get('/user/{id}', [UserController::class, 'show']);
Route::post('/user', [UserController::class, 'store']);
Route::middleware(['auth'])->group(function () {
Route::get('/profile', [ProfileController::class, 'edit']);
});

Первая строка описывает GET-запрос. Параметр id передается в контроллер. Вторая строка описывает POST-запрос. Группа маршрутов применяет middleware. Middleware auth проверяет авторизацию. Маршрутизатор направляет запрос нужному обработчику. Структура маршрутов определяет API приложения.

Middleware

Middleware фильтрует запросы до попадания в контроллер. Классы middleware находятся в директории app/Http/Middleware. Метод handle обрабатывает запрос. Метод передает запрос дальше или возвращает ответ. Цепочка middleware формирует конвейер.

class CheckAge
{
public function handle($request, Closure $next)
{
if ($request->age <= 18) {
return redirect('home');
}
return $next($request);
}
}

Класс проверяет возраст пользователя. Условие перенаправляет несовершеннолетних. Функция $next передает запрос следующему звену. Middleware реализует кросс-функциональную логику. Аутентификация реализуется через middleware. Кэширование реализуется через middleware. Сжатие реализуется через middleware. Конвейер сохраняет порядок обработки.

Обработка ошибок

Front Controller перехватывает исключения. Класс Handler управляет отображением ошибок. Ошибки логируются. Пользователь видит безопасную страницу. Режим отладки показывает детали разработчику.

public function render($request, Throwable $e)
{
if ($this->isHttpException($e)) {
return $this->renderHttpException($e);
}
return parent::render($request, $e);
}

Метод render определяет тип ошибки. HTTP-исключения отображаются специально. Остальные ошибки обрабатываются базовым классом. Страницы ошибок настраиваются через шаблоны. Логи сохраняются в файлы или внешние сервисы. Система остается стабильной при сбоях. Пользователь получает понятное сообщение. Разработчик получает стек вызовов.


Singleton (Одиночка)

Паттерн Singleton гарантирует существование единственного экземпляра класса. Приложение получает доступ к этому экземпляру через глобальную точку доступа. Laravel применяет этот шаблон для управления сервисами. Контейнер служб хранит одиночные объекты. Повторный запрос возвращает тот же объект.

Принцип единственного экземпляра

Объект создается один раз за жизненный цикл запроса. Последующие обращения используют созданную копию. Экономия ресурсов происходит за счет отсутствия повторной инициализации. Состояние объекта сохраняется между вызовами. Конфигурация остается неизменной.

class DatabaseManager
{
protected static $instance;

public static function getInstance()
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
}

Класс хранит ссылку в статическом свойстве. Метод getInstance проверяет наличие экземпляра. Создание происходит при первом вызове. Возврат ссылки обеспечивает доступ к тому же объекту. Конструктор остается скрытым. Внешний код вызывает статический метод.

Реализация в контейнере Laravel

Контейнер Illuminate\Container управляет одиночками. Метод singleton регистрирует класс как единственный экземпляр. Контейнер создает объект при первом запросе. Сохранение происходит внутри контейнера. Запрос по имени класса возвращает сохраненный объект.

$this->app->singleton('cache', function ($app) {
return new CacheManager($app);
});

$cache = $this->app->make('cache');
$cacheAgain = $this->app->make('cache');

Замыкание фабрики создает объект CacheManager. Регистрация через singleton гарантирует однократный вызов замыкания. Переменная $cache получает экземпляр. Переменная $cacheAgain получает тот же экземпляр. Идентичность объектов проверяется через оператор сравнения. Контейнер хранит ссылку внутри себя. Освобождение памяти происходит в конце запроса.


Factory (Фабрика)

Паттерн Factory инкапсулирует логику создания объектов. Клиентский код запрашивает объект у фабрики. Фабрика определяет конкретный класс для создания. Laravel использует фабрики для тестирования и создания сущностей. Контейнер также выступает в роли фабрики.

Создание объектов

Фабрика скрывает детали инициализации. Клиент работает с интерфейсом или базовым классом. Конкретная реализация выбирается динамически. Гибкость системы повышается за счет разделения создания и использования.

class LoggerFactory
{
public function create($type)
{
if ($type === 'file') {
return new FileLogger();
}
if ($type === 'database') {
return new DatabaseLogger();
}
return new NullLogger();
}
}

Метод create принимает параметр типа. Условие определяет класс логгера. Возвращается объект реализующего класса. Клиентский код получает готовый экземпляр. Внутренняя структура классов скрыта. Изменение логики создания требует правки только фабрики.

Фабрики моделей для тестирования

Компонент Database Factories генерирует тестовые данные. Классы фабрик описывают состояния моделей. Методы определяют значения атрибутов. Тесты получают наполненную базу данных.

class UserFactory extends Factory
{
public function definition()
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'password' => bcrypt('password'),
];
}
}

User::factory()->count(10)->create();

Класс UserFactory наследуется от базовой фабрики. Метод definition возвращает массив атрибутов. Функция fake генерирует случайные данные. Статический вызов factory создает экземпляр фабрики. Метод count устанавливает количество записей. Метод create сохраняет записи в базу данных. Тестирование происходит на реалистичных данных.


Registry (Регистр)

Паттерн Registry предоставляет глобальную точку доступа к объектам. Реестр хранит ссылки на сервисы. Компоненты запрашивают объекты у реестра. Laravel реализует эту функцию через контейнер зависимостей. Контейнер знает о всех зарегистрированных сервисах.

Централизованное хранение

Реестр собирает объекты в одном месте. Доступ происходит по уникальному ключу. Имя класса служит ключом по умолчанию. Регистрация происходит в провайдерах сервисов. Контейнер хранит определения и экземпляры.

$this->app->bind('PaymentGateway', StripeGateway::class);
$this->app->instance('config', $configArray);

Метод bind связывает интерфейс с реализацией. Запрос интерфейса возвращает экземпляр реализации. Метод instance регистрирует готовый объект. Массив конфигурации доступен по ключу config. Реестр управляет временем жизни объектов. Удаление происходит автоматически при завершении запроса.

Контейнер служб

Контейнер Laravel служит центральным реестром. Компоненты запрашивают зависимости у контейнера. Разрешение зависимостей происходит автоматически. Контейнер строит граф зависимостей. Порядок инициализации определяется автоматически.

class OrderService
{
public function __construct(PaymentGateway $gateway)
{
// Использование шлюза
}
}

$service = $app->make(OrderService::class);

Класс OrderService требует PaymentGateway. Контейнер находит реализацию шлюза в реестре. Создание OrderService происходит после создания шлюза. Метод make запускает процесс разрешения. Объект service получает все зависимости. Реестр обеспечивает связность компонентов.


Facade (Фасад)

Паттерн Facade предоставляет упрощенный интерфейс к сложной подсистеме. Фасад скрывает сложность взаимодействия. Клиент вызывает статические методы. Laravel фасады обращаются к объектам контейнера. Статический синтаксис сочетается с внедрением зависимостей.

Структура фасадов

Класс фасада наследуется от Illuminate\Support\Facades\Facade. Метод getFacadeAccessor возвращает имя сервиса. Статический вызов перенаправляется в контейнер. Реальный объект выполняется внутри метода.

class Route extends Facade
{
protected static function getFacadeAccessor()
{
return 'router';
}
}

Route::get('/home', function () {
// Обработка маршрута
});

Класс Route определяет аксессор router. Вызов Route::get перенаправляется к объекту router. Контейнер разрешает имя router в экземпляр класса. Метод get выполняется на реальном объекте. Синтаксис остается статическим. Тестирование позволяет подменять реализации фасадов.

Внутреннее устройство

Базовый класс Facade перехватывает статические вызовы. Магический метод __callStatic обрабатывает обращение. Контейнер приложения извлекается из глобального контекста. Объект сервиса resolves через аксессор. Метод вызывается на экземпляре.

public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
return $instance->$method(...$args);
}

Метод __callStatic принимает имя метода и аргументы. Функция getFacadeRoot получает объект из контейнера. Вызов метода происходит на экземпляре. Аргументы передаются без изменений. Возвращаемое значение передается вызывающему коду. Прозрачность перенаправления сохраняется полностью.


Presenter (Презентер)

Паттерн Presenter подготавливает данные для отображения. Логика представления выносится из контроллера. Модель остается чистой от логики отображения. View Composers в Laravel реализуют эту функцию. Данные формируются перед рендерингом шаблона.

Разделение логики представления

Презентер форматирует данные для шаблона. Контроллер передает сырые данные презентеру. Презентер возвращает структурированный массив. Шаблон получает готовые к выводу значения.

class UserPresenter
{
protected $user;

public function __construct(User $user)
{
$this->user = $user;
}

public function getFullName()
{
return $this->user->first_name . ' ' . $this->user->last_name;
}
}

Класс UserPresenter принимает модель пользователя. Метод getFullName объединяет поля имени. Контроллер вызывает методы презентера. Шаблон выводит результат метода. Логика конкатенации находится в презентере. Модель хранит только атрибуты базы данных.

Композеры представлений

Композеры выполняют код перед отображением шаблона. Регистрация происходит в провайдерах ViewServiceProvider. Данные передаются во все представления или конкретные шаблоны. Глобальные переменные становятся доступными в Blade.

View::composer('profile', function ($view) {
$view->with('count', User::count());
});

Функция composer привязывает замыкание к шаблону profile. Замыкание получает экземпляр представления. Метод with передает переменную count. Запрос User::count выполняется при рендеринге. Шаблон profile получает переменную count. Логика подсчета изолирована от контроллера.


Dependency Injection (DI)

Внедрение зависимостей передает объекты извне. Класс получает зависимости через конструктор или методы. Создание объектов происходит во внешнем коде. Контейнер Laravel автоматизирует этот процесс. Типизация аргументов определяет зависимости.

Внедрение через конструктор

Конструктор принимает объекты зависимостей. Класс сохраняет ссылки в свойствах. Методы используют переданные объекты. Тестирование упрощается за счет подмены зависимостей.

class NotificationService
{
protected $mailer;

public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}

public function send($user, $message)
{
$this->mailer->send($user->email, $message);
}
}

Класс NotificationService требует Mailer. Конструктор сохраняет экземпляр mailer. Метод send использует сохраненный объект. Внешний код создает Mailer и передает его. Класс не создает Mailer самостоятельно. Слабая связность обеспечивает гибкость.

Автоматическое разрешение контейнером

Контейнер анализирует типы аргументов конструктора. Реализация находится в реестре bindings. Контейнер создает зависимости рекурсивно. Объект класса собирается полностью готовым к работе.

$this->app->bind(Mailer::class, SmtpMailer::class);

$notification = $this->app->make(NotificationService::class);

Регистрация связывает интерфейс Mailer с SmtpMailer. Запрос NotificationService запускает разрешение. Контейнер видит тип Mailer в конструкторе. Создается экземпляр SmtpMailer. Экземпляр передается в NotificationService. Переменная notification получает готовый сервис. Ручное создание зависимостей исключается.


Service Locator

Паттерн Service Locator предоставляет метод поиска сервисов. Код запрашивает сервис по имени или типу. Locator знает о доступных сервисах. Laravel реализует это через хелпер app() и фасад App. Глобальный доступ сочетается с контейнером.

Поиск сервисов по имени

Функция app() принимает имя сервиса. Контейнер возвращает экземпляр сервиса. Вызов возможен в любом месте кода. Статический доступ обеспечивается фасадом.

$logger = app('log');
$logger->info('Message');

$service = App::make('PaymentService');
$service->process();

Вызов app('log') извлекает логгер. Метод info вызывается на экземпляре. Фасад App предоставляет метод make. Имя PaymentService разрешается в объект. Метод process выполняется на сервисе. Доступ происходит без внедрения в конструктор.

Контекст использования

Service Locator подходит для точечного доступа. Глобальные функции ускоряют разработку скриптов. Контейнер управляет временем жизни объектов. Тестирование требует настройки контейнера. Внедрение зависимостей предпочтительнее для классов.

if (app()->environment('production')) {
// Логика для продакшена
}

Метод environment проверяет окружение. Доступ к приложению происходит через хелпер. Условие выполняет код для конкретной среды. Контекст приложения доступен глобально. Архитектура сохраняет целостность состояния.


Repository Pattern (Репозиторий)

Паттерн Repository создает промежуточный слой между бизнес-логикой и источниками данных. Класс репозитория инкапсулирует логику доступа к данным. Контроллер взаимодействует с репозиторием вместо модели напрямую. Архитектура приложения сохраняет гибкость при смене источника данных. Тестирование упрощается за счет подмены реализации репозитория.

Интерфейс и абстракция

Интерфейс определяет контракт для работы с данными. Конкретная реализация следует установленному контракту. Контроллер зависит от интерфейса. Контейнер связывает интерфейс с классом реализации. Замена реализации происходит через конфигурацию контейнера.

interface UserRepositoryInterface
{
public function find($id);
public function create(array $data);
public function update($id, array $data);
public function delete($id);
}

Интерфейс описывает доступные методы работы с пользователем. Метод find извлекает запись по идентификатору. Метод create сохраняет новую запись. Метод update изменяет существующие данные. Метод delete удаляет запись из хранилища. Контроллер использует эти методы без знания внутренней структуры.

Реализация доступа к данным

Класс реализации выполняет запросы к базе данных. Внутри используется Eloquent ORM илиQueryBuilder. Логика выборки остается внутри репозитория. Контроллер получает готовые объекты или массивы.

class EloquentUserRepository implements UserRepositoryInterface
{
protected $model;

public function __construct(User $model)
{
$this->model = $model;
}

public function find($id)
{
return $this->model->findOrFail($id);
}

public function create(array $data)
{
return $this->model->create($data);
}

public function update($id, array $data)
{
$user = $this->find($id);
$user->update($data);
return $user;
}

public function delete($id)
{
return $this->model->destroy($id);
}
}

Класс принимает модель в конструктор. Методы делегируют вызовы модели Eloquent. Логика валидации данных может размещаться здесь. Контроллер остается чистым от запросов к базе. Изменение логики выборки требует правки только репозитория.

Регистрация в контейнере

Контейнер связывает интерфейс с реализацией. Провайдер сервисов выполняет регистрацию связки. Запрос интерфейса возвращает экземпляр реализации.

public function register()
{
$this->app->bind(
UserRepositoryInterface::class,
EloquentUserRepository::class
);
}

Метод bind устанавливает соответствие. Контейнер создает экземпляр EloquentUserRepository при запросе интерфейса. Внедрение зависимости происходит автоматически. Контроллер получает реализацию через типизированный аргумент.

class UserController extends Controller
{
public function show($id, UserRepositoryInterface $repo)
{
$user = $repo->find($id);
return view('user.show', compact('user'));
}
}

Контроллер запрашивает интерфейс UserRepositoryInterface. Контейнер внедряет экземпляр EloquentUserRepository. Метод show использует репозиторий для получения данных. Представление отображает полученный объект.


Strategy Pattern (Стратегия)

Паттерн Strategy позволяет выбирать алгоритм выполнения во время выполнения программы. Семейство алгоритмов инкапсулируется в отдельные классы. Классы реализуют общий интерфейс. Контекст использует объект стратегии для выполнения задачи. Laravel применяет этот шаблон в менеджерах драйверов.

Семейство алгоритмов

Интерфейс стратегии определяет общий метод выполнения. Каждая стратегия реализует метод по-своему. Контекст вызывает метод интерфейса. Конкретная реализация остается скрытой от контекста.

interface PaymentStrategy
{
public function pay($amount);
}

class StripeStrategy implements PaymentStrategy
{
public function pay($amount)
{
// Логика оплаты через Stripe
return true;
}
}

class PayPalStrategy implements PaymentStrategy
{
public function pay($amount)
{
// Логика оплаты через PayPal
return true;
}
}

Интерфейс PaymentStrategy объявляет метод pay. Класс StripeStrategy реализует оплату через сервис Stripe. Класс PayPalStrategy реализует оплату через сервис PayPal. Оба класса следуют одному контракту. Контекст вызывает метод pay без знания конкретной системы.

Менеджер стратегий

Менеджер выбирает подходящую стратегию на основе условий. Конфигурация определяет доступные стратегии. Контейнер создает экземпляр нужного класса.

class PaymentManager
{
protected $strategies = [];

public function addStrategy($name, PaymentStrategy $strategy)
{
$this->strategies[$name] = $strategy;
}

public function execute($name, $amount)
{
if (isset($this->strategies[$name])) {
return $this->strategies[$name]->pay($amount);
}
throw new Exception('Strategy not found');
}
}

Класс PaymentManager хранит коллекцию стратегий. Метод addStrategy регистрирует объект стратегии по имени. Метод execute находит стратегию и запускает оплату. Проверка наличия стратегии предотвращает ошибки. Вызов метода pay происходит на объекте стратегии.

Динамическое переключение

Приложение выбирает стратегию на основе ввода пользователя. Конфигурация может меняться без изменения кода контекста. Расширение системы происходит через добавление новых классов.

$manager = new PaymentManager();
$manager->addStrategy('stripe', new StripeStrategy());
$manager->addStrategy('paypal', new PayPalStrategy());

$method = $request->input('payment_method');
$manager->execute($method, 100.00);

Менеджер регистрирует доступные способы оплаты. Запрос определяет выбранный пользователем метод. Метод execute запускает соответствующую стратегию. Система поддерживает добавление новых провайдеров. Код контекста остается неизменным при расширении.


Observer Pattern (Наблюдатель)

Паттерн Observer реализует механизм подписки на события. Объект наблюдатель реагирует на изменения субъекта. Субъект уведомляет всех подписчиков при изменении состояния. Laravel использует этот шаблон в системе событий и очередей. Связь между компонентами остается слабой.

Субъект и события

Класс события описывает факт произошедшего действия. Данные события передаются наблюдателям. Диспетчер событий управляет списком наблюдателей. Отправка уведомления происходит автоматически.

class OrderShipped
{
public $order;

public function __construct($order)
{
$this->order = $order;
}
}

Класс OrderShipped представляет событие отгрузки заказа. Свойство order хранит ссылку на объект заказа. Конструктор принимает данные для передачи наблюдателям. Экземпляр события передается в диспетчер. Диспетчер находит подписчиков на этот класс.

Наблюдатели и подписчики

Класс наблюдателя содержит логику реакции на событие. Метод handle обрабатывает полученные данные. Регистрация наблюдателя происходит в провайдере EventServiceProvider. Несколько наблюдателей могут слушать одно событие.

class SendShipmentNotification
{
public function handle($event)
{
$event->order->user->notify(new ShipmentStatus($event->order));
}
}

class UpdateInventory
{
public function handle($event)
{
$event->order->items->each(function ($item) {
$item->decrement('quantity');
});
}
}

Класс SendShipmentNotification отправляет уведомление пользователю. Класс UpdateInventory обновляет остатки на складе. Метод handle принимает объект события. Логика разделена между независимыми классами. Изменение одной реакции затрагивает только свой класс.

Регистрация слушателей

Провайдер событий связывает классы событий и наблюдателей. Массив listen определяет карту подписок. Диспетчер загружает конфигурацию при старте.

protected $listen = [
OrderShipped::class => [
SendShipmentNotification::class,
UpdateInventory::class,
],
];

Ключ массива указывает класс события. Значение содержит список классов наблюдателей. Диспетчер создает экземпляры наблюдателей при срабатывании. Порядок выполнения определяется порядком в массиве. Система поддерживает любое количество подписчиков.


Pipeline Pattern (Конвейер)

Паттерн Pipeline передает объект через последовательность обработчиков. Каждый обработчик выполняет действие и передает объект дальше. Обработчики называются middleware или трубами. Laravel применяет этот шаблон для обработки HTTP-запросов. Конвейер обеспечивает модульность обработки данных.

Структура конвейера

Класс конвейера управляет потоком данных. Метод send устанавливает начальный объект. Метод through определяет список обработчиков. Метод then запускает выполнение цепочки.

$pipeline = app(\Illuminate\Pipeline\Pipeline::class);

$response = $pipeline->send($request)
->through([
CheckMaintenance::class,
Authenticate::class,
VerifyCsrfToken::class,
])
->then(function ($request) {
return $this->router->dispatch($request);
});

Объект pipeline создается через контейнер. Метод send передает запрос в конвейер. Метод through устанавливает массив классов middleware. Метод then определяет финальное действие. Запрос проходит через каждый класс последовательно.

Обработчики данных

Класс обработчика получает объект и замыкание для передачи дальше. Метод handle выполняет логику и вызывает замыкание. Возврат результата происходит через цепочку вызовов.

class Authenticate
{
public function handle($request, Closure $next)
{
if (!$request->user()) {
return redirect('login');
}
return $next($request);
}
}

Класс Authenticate проверяет авторизацию пользователя. Условие проверяет наличие аутентифицированной сессии. Перенаправление происходит при отсутствии пользователя. Вызов $next передает запрос следующему обработчику. Возврат результата замыкает цепочку.

Глобальный и групповой конвейер

Конвейер применяется ко всем запросам глобально. Группы маршрутов определяют локальные конвейеры. Middleware распределяются по уровням обработки.

$kernel->middleware([
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
]);

$kernel->middlewareGroups([
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
],
'api' => [
'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
]);

Метод middleware задает глобальный список обработчиков. Метод middlewareGroups определяет группы для типов маршрутов. Группа web включает обработку cookies и сессий. Группа api включает ограничение частоты запросов. Маршруты наследуют настройки соответствующей группы. Конвейер собирает полный список обработчиков для запроса.


Command Bus (Шина команд)

Паттерн Command Bus разделяет инструкцию на выполнение и логику выполнения. Объект команды описывает намерение. Обработчик команды выполняет действие. Laravel реализует эту схему через Jobs и Queues. Асинхронное выполнение задач происходит через очередь.

Объект команды

Класс команды хранит данные для выполнения задачи. Свойства описывают параметры действия. Команда реализует интерфейс Job для очереди.

class ProcessPodcast implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

public $podcast;

public function __construct(Podcast $podcast)
{
$this->podcast = $podcast;
}

public function handle()
{
// Обработка подкаста
}
}

Класс ProcessPodcast содержит модель подкаста. Конструктор сохраняет данные для обработки. Метод handle содержит логику выполнения. Интерфейс ShouldQueue маркирует задачу для очереди. Трейты обеспечивают работу с системой очередей.

Диспетчеризация задач

Диспетчер отправляет команду в очередь или выполняет синхронно. Функция dispatch создает задачу и помещает в очередь. Воркер очереди извлекает задачи и запускает handle.

ProcessPodcast::dispatch($podcast);

Статический метод dispatch создает экземпляр команды. Задача помещается в настроенное хранилище очередей. Воркер получает задачу из хранилища. Метод handle выполняется в отдельном процессе. Основной поток запроса завершается немедленно.

Обработка ошибок и повторные попытки

Система очередей отслеживает статус выполнения. Ошибки логируются и инициируют повторную попытку. Параметры повторения настраиваются в классе команды.

public $tries = 3;
public $timeout = 120;

public function failed(Throwable $exception)
{
// Логика при неудаче
}

Свойство tries устанавливает количество попыток выполнения. Свойство timeout ограничивает время выполнения задачи. Метод failed срабатывает после исчерпания попыток. Логирование ошибки происходит автоматически. Система сохраняет стабильность при сбоях задач.


Decorator Pattern (Декоратор)

Паттерн Decorator добавляет новую функциональность объекту динамически. Обертка сохраняет интерфейс исходного объекта. Клиентский код работает с декорированным объектом как с оригиналом. Laravel применяет этот шаблон для расширения возможностей классов без наследования. Middleware выступает в роли декоратора для запроса.

Структура декоратора

Базовый компонент определяет интерфейс. Конкретный компонент реализует базовую функциональность. Декоратор хранит ссылку на компонент. Методы декоратора делегируют вызовы компоненту. Дополнительная логика выполняется до или после делегирования.

interface LoggerInterface
{
public function log($message);
}

class FileLogger implements LoggerInterface
{
public function log($message)
{
file_put_contents('log.txt', $message . PHP_EOL, FILE_APPEND);
}
}

class CacheLogger implements LoggerInterface
{
protected $logger;

public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}

public function log($message)
{
// Логика кэширования перед записью
$this->logger->log($message);
// Логика после записи
}
}

Интерфейс LoggerInterface задает контракт. Класс FileLogger реализует запись в файл. Класс CacheLogger оборачивает любой реализующий интерфейс объект. Конструктор принимает экземпляр logger. Метод log вызывает метод обернутого объекта. Расширение происходит без изменения кода FileLogger.

Применение в Laravel

Классы Middleware декорируют объект запроса. Каждый middleware добавляет поведение к конвейеру обработки. Аутентификация декорирует запрос данными пользователя. Кэширование декорирует ответ сервера. Цепочка декораторов формирует окончательное поведение.

class CheckForMaintenanceMode
{
public function handle($request, Closure $next)
{
if ($this->app->isDownForMaintenance()) {
return Response::view('errors.503');
}
return $next($request);
}
}

Класс проверяет состояние приложения. Метод handle оборачивает вызов следующего обработчика. Возврат ответа происходит через цепочку. Запрос проходит через все декораторы последовательно. Ответ проходит через декораторы в обратном порядке.


Adapter Pattern (Адаптер)

Паттерн Adapter преобразует интерфейс одного класса в интерфейс другого. Клиент работает с целевым интерфейсом. Адаптер транслирует вызовы к адаптируемому классу. Laravel использует этот шаблон для унификации драйверов. Система кэширования и очередей работает через адаптеры.

Унификация интерфейсов

Клиентский код зависит от абстракции. Конкретные реализации различаются внутренним устройством. Адаптер скрывает различия реализаций. Конфигурация определяет используемый адаптер. Замена провайдера не требует изменения кода клиента.

interface CacheStore
{
public function get($key);
public function put($key, $value, $seconds);
}

class RedisAdapter implements CacheStore
{
protected $redis;

public function __construct(RedisClient $redis)
{
$this->redis = $redis;
}

public function get($key)
{
return $this->redis->get($key);
}

public function put($key, $value, $seconds)
{
$this->redis->setex($key, $seconds, $value);
}
}

Интерфейс CacheStore определяет методы кэша. Класс RedisAdapter реализует интерфейс для Redis. Метод get транслирует вызов к клиенту Redis. Метод put преобразует параметры для команды setex. Клиент вызывает методы интерфейса CacheStore. Внутренняя реализация Redis скрыта.

Менеджеры драйверов

Класс Manager создает адаптеры на основе конфигурации. Метод resolve выбирает конкретную реализацию. Контейнер внедряет зависимости в адаптер. Пул адаптеров хранится в менеджере. Повторный запрос возвращает существующий адаптер.

class CacheManager
{
protected $app;
protected $stores = [];

public function store($name = null)
{
$name = $name ?: $this->getDefaultDriver();
if (!isset($this->stores[$name])) {
$this->stores[$name] = $this->resolve($name);
}
return $this->stores[$name];
}

protected function resolve($name)
{
$config = $this->getConfig($name);
return $this->createAdapter($config['driver'], $config);
}
}

Метод store проверяет наличие адаптера в пуле. Создание происходит при первом запросе. Метод resolve извлекает конфигурацию. Функция createAdapter instantiate нужный класс адаптера. Конфигурация драйвера передается в конструктор. Менеджер обеспечивает единый доступ к разным хранилищам.


Builder Pattern (Строитель)

Паттерн Builder разделяет создание сложного объекта и его представление. Процесс построения позволяет получать разные представления. Класс Builder инкапсулирует шаги создания. Laravel Query Builder реализует этот шаблон для SQL-запросов. Конструктор запросов формирует SQL динамически.

Пошаговое создание

Класс Director управляет процессом построения. Builder реализует шаги создания частей объекта. Продукт собирается последовательно. Клиент запрашивает готовый продукт у Builder. Конфигурация шагов определяет структуру продукта.

class QueryBuilder
{
protected $select = ['*'];
protected $from;
protected $where = [];
protected $bindings = [];

public function select($columns = ['*'])
{
$this->select = $columns;
return $this;
}

public function from($table)
{
$this->from = $table;
return $this;
}

public function where($column, $value)
{
$this->where[] = "$column = ?";
$this->bindings[] = $value;
return $this;
}

public function get()
{
// Генерация SQL и выполнение
$sql = "SELECT " . implode(', ', $this->select) . " FROM " . $this->from;
if ($this->where) {
$sql .= " WHERE " . implode(' AND ', $this->where);
}
return DB::select($sql, $this->bindings);
}
}

Класс QueryBuilder хранит части запроса. Методы select, from, where устанавливают параметры. Возврат $this позволяет chaining вызовов. Метод get компилирует SQL из накопленных данных. Выполнение запроса происходит в конце цепочки. Объект запроса строится поэтапно.

Fluent Interface

Интерфейс обеспечивает читаемость кода. Методы возвращают экземпляр класса. Цепочка вызовов описывает логику построения. Порядок вызовов может варьироваться. Валидация состояния происходит перед выполнением.

$users = DB::table('users')
->select('name', 'email')
->where('active', 1)
->orderBy('name')
->get();

Вызов table инициирует создание builder. Метод select уточняет поля выборки. Метод where добавляет условие фильтрации. Метод orderBy устанавливает сортировку. Метод get завершает построение и выполняет запрос. Синтаксис отражает структуру SQL-запроса.


Chain of Responsibility (Цепочка обязанностей)

Паттерн Chain of Responsibility передает запрос последовательно обработчикам. Каждый обработчик решает выполнить запрос или передать дальше. Цепочка формируется динамически. Laravel Middleware реализует этот шаблон для HTTP-запросов. Конвейер обработки обеспечивает модульность.

Обработчики запроса

Интерфейс Handler определяет метод обработки. Конкретные реализации выполняют свою логику. Ссылка на следующий обработчик хранится внутри. Конец цепочки возвращает окончательный ответ. Разрыв цепочки прекращает дальнейшую обработку.

abstract class Handler
{
protected $next;

public function setNext(Handler $next)
{
$this->next = $next;
return $next;
}

public function handle($request)
{
if ($this->next) {
return $this->next->handle($request);
}
return $request;
}
}

class AuthHandler extends Handler
{
public function handle($request)
{
if (!$request->user()) {
return response('Unauthorized', 401);
}
return parent::handle($request);
}
}

Абстрактный класс хранит ссылку next. Метод setNext связывает обработчики в цепь. Метод handle передает запрос следующему звену. Класс AuthHandler проверяет авторизацию. Возврат ответа прерывает цепочку. Вызов parent::handle продолжает передачу.

Конвейер Laravel

Класс Pipeline управляет передачей запроса. Массив middleware определяет порядок обработчиков. Замыкание carry оборачивает каждый обработчик. Вложенные замыкания формируют стек вызовов. Выполнение идет от первого к последнему.

$pipeline->send($request)
->through($middleware)
->then(function ($request) {
return $this->router->dispatch($request);
});

Метод send устанавливает начальный объект. Метод through загружает массив middleware. Метод then определяет финальное действие. Каждый middleware оборачивает предыдущий. Обратный проход происходит при возврате ответа. Конвейер обеспечивает гибкую настройку обработки.


Template Method Pattern (Шаблонный метод)

Паттерн Template Method определяет скелет алгоритма в базовом классе. Подклассы переопределяют шаги алгоритма. Структура алгоритма остается неизменной. Laravel использует этот шаблон в базовых классах команд и контроллеров. Расширение функциональности происходит через наследование.

Структура алгоритма

Абстрактный класс объявляет шаблонный метод. Метод вызывает примитивные операции в определенном порядке. Подклассы реализуют примитивные операции. Hook-методы позволяют изменять поведение условно. Клиент вызывает шаблонный метод.

abstract class Importer
{
public function import($file)
{
$this->connect();
$data = $this->parse($file);
$this->validate($data);
$this->store($data);
$this->disconnect();
}

protected function connect() { /* Подключение */ }
protected function parse($file) { /* Абстрактный метод */ }
protected function validate($data) { /* Проверка */ }
protected function store($data) { /* Абстрактный метод */ }
protected function disconnect() { /* Отключение */ }
}

Метод import задает последовательность действий. Подключение и отключение реализованы в базовом классе. Методы parse и store объявлены абстрактными. Подклассы обязаны реализовать эти методы. Валидация выполняется стандартно для всех импортеров. Алгоритм импорта остается единым.

Реализация в Laravel

Классы команд наследуют базовый класс Command. Метод handle выступает шаблонным методом. Конструктор инициализирует зависимости. Опции и аргументы определяются в подклассе. Консоль вызывает метод handle автоматически.

class SendEmails extends Command
{
protected $signature = 'emails:send {user}';
protected $description = 'Send marketing emails';

public function handle()
{
$user = User::find($this->argument('user'));
$user->notify(new MarketingEmail());
$this->info('Email sent successfully');
}
}

Свойства signature и description конфигурируют команду. Метод handle содержит логику выполнения. Аргументы извлекаются через вспомогательные методы. Вывод информации происходит через метод info. Базовый класс управляет жизненным циклом команды. Разработчик реализует только бизнес-логику.


State Pattern (Состояние)

Паттерн State позволяет объекту изменять поведение при изменении внутреннего состояния. Объект делегирует поведение текущему состоянию. Переходы между состояниями инкапсулированы. Laravel модели используют события для отслеживания состояния. Статусы заказов реализуются через этот шаблон.

Объекты состояний

Интерфейс State определяет методы поведения. Конкретные состояния реализуют методы по-разному. Контекст хранит ссылку на текущее состояние. Переход инициирует смену объекта состояния. Клиент взаимодействует с контекстом.

interface OrderState
{
public function pay(Order $order);
public function ship(Order $order);
}

class PendingState implements OrderState
{
public function pay(Order $order)
{
$order->setState(new PaidState());
}

public function ship(Order $order)
{
throw new Exception('Cannot ship unpaid order');
}
}

Интерфейс OrderState задает доступные действия. Класс PendingState реализует логику ожидающего заказа. Метод pay переводит заказ в состояние PaidState. Метод ship выбрасывает исключение для неоплаченного заказа. Поведение зависит от текущего объекта состояния. Логика переходов находится внутри состояний.

Управление состоянием модели

Модель хранит текущее состояние в атрибуте. Методы модели делегируют вызовы объекту состояния. События модели уведомляют о переходах. Наблюдатели реагируют на смену состояния. Персистентность состояния обеспечивается базой данных.

class Order extends Model
{
protected $state;

public function setState(OrderState $state)
{
$this->state = $state;
$this->status = get_class($state);
$this->save();
}

public function pay()
{
$this->state->pay($this);
}
}

Класс Order хранит объект состояния. Метод setState обновляет объект и запись в базе. Свойство status сохраняет имя класса состояния. Метод pay делегирует вызов текущему состоянию. Изменение состояния триггерит сохранение модели. Целостность переходов контролируется объектами состояний.


Service Providers (Провайдеры сервисов)

Компонент Service Providers инициализирует сервисы приложения. Провайдеры регистрируют зависимости в контейнере. Метод boot выполняет логику после регистрации всех сервисов. Конфигурация приложения загружается через провайдеры. Порядок загрузки определяет доступность сервисов.

Регистрация зависимостей

Метод register связывает интерфейсы с реализациями. Контейнер получает замыкания создания объектов. Singleton регистрация гарантирует единственный экземпляр. Binding регистрация создает новый экземпляр при запросе. Контекстное связывание уточняет зависимости для классов.

public function register()
{
$this->app->bind(RepositoryInterface::class, EloquentRepository::class);
$this->app->singleton(CacheService::class, function ($app) {
return new CacheService($app['config']['cache']);
});
}

Метод bind устанавливает связь для каждого запроса. Метод singleton создает объект один раз. Замыкание получает доступ к контейнеру через $app. Конфигурация передается в конструктор сервиса. Контейнер разрешает зависимости автоматически. Регистрация происходит до запуска приложения.

Инициализация сервисов

Метод boot выполняется после регистрации всех провайдеров. Сервисы доступны для использования внутри boot. Маршруты регистрируются в этом этапе. Обработчики событий подключаются здесь. Middleware глобального уровня настраиваются в boot.

public function boot()
{
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
$this->loadViewsFrom(__DIR__.'/../resources/views', 'package');
$this->publishes([
__DIR__.'/../config/package.php' => config_path('package.php'),
]);
}

Метод loadRoutesFrom загружает файлы маршрутов. Метод loadViewsFrom регистрирует пространства имен представлений. Метод publishes делает конфигурацию доступной для копирования. Инициализация происходит после создания контейнера. Сервисы полностью готовы к работе. Провайдеры пакетов интегрируются в приложение.


Configuration Patterns (Паттерны конфигурации)

Система конфигурации хранит настройки приложения. Файлы конфигурации возвращают массивы параметров. Доступ к настройкам происходит через хелпер config. Переменные окружения влияют на значения конфигурации. Кэширование конфигурации ускоряет загрузку.

Иерархия настроек

Файлы группируются по доменам приложения. База данных, кэш, почта имеют отдельные файлы. Вложенные ключи обеспечивают структуру доступа. Значения по умолчанию определяются в файлах. Переопределение происходит через переменные окружения.

// config/database.php
return [
'default' => env('DB_CONNECTION', 'mysql'),
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
],
],
];

Массив возвращает структуру настроек базы данных. Ключ default указывает соединение по умолчанию. Функция env извлекает переменные окружения. Второе значение задает fallback при отсутствии переменной. Вложенный массив connections описывает драйверы. Конфигурация адаптируется под среду выполнения.

Доступ и кэширование

Хелпер config извлекает значения по ключу. Точечная нотация определяет путь в массиве. Установка значений возможна во время выполнения. Команда config:cache объединяет файлы в один. Загрузка происходит из кэшированного файла в продакшене.

$value = config('database.default');
config(['database.default' => 'pgsql']);

Вызов config получает значение соединения. Ключ database.default указывает путь в массиве. Установка массивом меняет значение в памяти. Изменения действуют в пределах текущего запроса. Кэширование требует перезапуска при изменении файлов. Производительность загрузки увеличивается в разы.


Caching Strategies (Стратегии кэширования)

Система кэширования хранит данные для быстрого доступа. Драйверы кэша определяют способ хранения. Файлы, база данных, Redis поддерживаются из коробки. Теги позволяют группировать ключи кэша. Инвалидация очищает устаревшие данные.

Драйверы хранения

Интерфейс CacheStore унифицирует доступ к хранилищам. Файловый драйвер сохраняет сериализованные данные в диск. Database драйвер использует таблицу для кэша. Redis и Memcached обеспечивают высокую производительность. Конфигурация выбирает активный драйвер.

Cache::put('key', $value, $seconds);
$value = Cache::get('key', $default);
Cache::forget('key');

Метод put сохраняет значение на указанное время. Метод get извлекает значение или возвращает default. Метод forget удаляет ключ из хранилища. Интерфейс остается единым для всех драйверов. Замена драйвера не требует изменения кода. Скорость доступа зависит от выбранного бэкенда.

Теги и инвалидация

Теги присваиваются ключам при сохранении. Очистка по тегу удаляет группу ключей. Функция поддерживается драйверами Redis и Memcached. Ручная инвалидация требуется при изменении данных. Автоматическая инвалидация реализуется через события моделей.

Cache::tags(['users', 'admins'])->put('key', $value, $seconds);
Cache::tags('users')->flush();

Метод tags присваивает метки ключу. Несколько тегов позволяют гибкую группировку. Метод flush очищает все ключи с тегом users. Остальные ключи остаются нетронутыми. Группировка упрощает управление большими объемами кэша. Инвалидация происходит точечно по группам.