Ой, ничего не найдено!

К сожалению, по вашему запросу пока ничего нет (но это только пока!), зато вы можете подписаться на нашу замечательную email-рассылку, чтобы не пропустить самое интересное в будущем.

  • 94

Laravel Design Patterns, которые вам нужно знать

  • 2 минуты на чтение

Представьте, что вы разрабатываете приложение на Laravel. Сначала всё идёт гладко — контроллеры получают данные из моделей, представления отображают результаты, и жизнь прекрасна. Но по мере роста проекта код начинает разрастаться, логика переплетается, и изменения становятся всё сложнее. Дизайн-паттерны приходят на помощь, чтобы организовать код, сделать его чище и облегчить поддержку. В этой статье мы поговорим о самых распространённых паттернах проектирования в Laravel, таких как Repository, Singleton, Factory, Observer, Facade и Strategy. Мы разберём, как они работают, в чём их польза и покажем пошаговые примеры кода для каждого паттерна. Стиль изложения будет простой и понятный, без лишнего формализма, чтобы материал был доступен и интересен. Поехали!

Паттерн Репозиторий (Repository)

Паттерн Репозиторий выступает прослойкой между бизнес-логикой приложения и источником данных. Проще говоря, репозиторий — это класс, через который ваш код получает доступ к данным, не зная деталей их хранения. Это своего рода абстракция уровня данных, позволяющая работать с объектами так, будто они в памяти, а не в базе данных (Паттерн «Репозиторий» в Laravel | Уроки Laravel). Бизнес-логика обращается к репозиторию, чтобы получить нужные данные, и ей всё равно, откуда эти данные — из MySQL, MongoDB или внешнего API. Такой подход облегчает поддержку: если изменится источник данных, вам достаточно изменить код репозитория, не трогая остальную часть приложения.

Зачем использовать Репозиторий? Во-первых, для разделения обязанностей: контроллеры и сервисы занимаются логикой, а репозиторий — общением с базой данных. Это делает код чище и тестируемее. Во-вторых, репозиторий способствует принципу инверсии зависимостей — вы пишете код против абстракций (интерфейсов), а не конкретных классов, что упрощает смену реализации хранилища данных (например, переход с Eloquent на другой ORM) (How to Use the Repository Pattern in a Laravel Application | Twilio) (How to Use the Repository Pattern in a Laravel Application | Twilio).

Реализация паттерна Репозиторий

Рассмотрим простой пример: допустим, у нас есть модель User и нам нужен репозиторий для пользователей. Мы создадим интерфейс репозитория, реализацию под Eloquent и научим Laravel подменять класс модели на наш репозиторий в нужных местах.

Шаг 1. Создаём интерфейс репозитория. Интерфейс определяет методы, которые мы ожидаем от репозитория. Например, метод для получения всех пользователей, поиска по ID и создания нового пользователя:

<?php

namespace App\Repositories;

interface UserRepositoryInterface 
{
    public function all(): iterable;
    public function find(int $id);
    public function create(array $data);
    // ... другие необходимые методы
}

Здесь мы описали контракт нашего хранилища пользователей, не вдаваясь в детали реализации.

Получите 6 месяцев бесплатного хостинга!
Воспользуйтесь нашим промокодом FREE6MONTH и начните свой проект без лишних затрат.

Шаг 2. Реализуем интерфейс с помощью Eloquent. Создадим класс UserRepository, который реализует UserRepositoryInterface и внутри использует модель User для выполнения операций. Например:

<?php

namespace App\Repositories;

use App\Models\User;

class UserRepository implements UserRepositoryInterface
{
    public function all(): iterable
    {
        return User::all();
    }

    public function find(int $id)
    {
        return User::find($id);
    }

    public function create(array $data)
    {
        return User::create($data);
    }
}

В этом классе мы вынесли всю работу с моделью User – получаем всех пользователей, ищем по ID и создаём новую запись. Контроллеры будут пользоваться репозиторием, не обращаясь напрямую к модели.

Шаг 3. Привязываем интерфейс к реализации в контейнере. Чтобы Laravel понял, какой класс использовать для интерфейса, зарегистрируем связывание в сервис-провайдере. Обычно это можно сделать в app/Providers/AppServiceProvider.php в методе register():

use App\Repositories\UserRepository;
use App\Repositories\UserRepositoryInterface;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        // При обращении к интерфейсу вернуть экземпляр UserRepository
        $this->app->bind(UserRepositoryInterface::class, UserRepository::class);
    }

    // ...
}

Метод bind сообщает сервис-контейнеру Laravel: "если кто-то запросит UserRepositoryInterface, предоставь ему UserRepository". Также можно использовать $this->app->singleton вместо bind, если вы хотите, чтобы создавался единственный экземпляр репозитория (например, для кэша данных), но чаще для репозиториев достаточно bind, чтобы при каждом запросе давать новый экземпляр.

Шаг 4. Используем репозиторий в коде. Теперь мы можем в контроллере получить репозиторий через внедрение зависимостей (Dependency Injection). Например, в контроллере пользователей:

use App\Repositories\UserRepositoryInterface;

class UserController extends Controller
{
    protected $users;

    // Laravel сам передаст нужную реализацию репозитория в конструктор
    public function __construct(UserRepositoryInterface $users)
    {
        $this->users = $users;
    }

    public function index()
    {
        // Получаем всех пользователей через репозиторий
        $allUsers = $this->users->all();
        return view('users.index', ['users' => $allUsers]);
    }

    public function show($id)
    {
        $user = $this->users->find($id);
        return view('users.show', ['user' => $user]);
    }

    public function store(Request $request)
    {
        $newUser = $this->users->create($request->all());
        // ... например, редирект или возврат ответа
    }
}

Обратите внимание: мы совершенно не вызываем User::find или User::create напрямую в контроллере. Вместо этого контроллер "знает" только об интерфейсе UserRepositoryInterface. Laravel сам передаст объект UserRepository в конструктор контроллера, согласно привязке, которую мы описали в сервис-провайдере. В результате контроллер не зависит от Eloquent или конкретной реализации хранения данных — мы можем заменить UserRepository на другую реализацию (например, хранящую пользователей в кэше или вообще получающую их по API) без изменения кода контроллера. Код стал более гибким и тестируемым: можно подставить фейковый репозиторий при тестировании, избежав реальных запросов к базе.

Получите 6 месяцев бесплатного хостинга!
Воспользуйтесь нашим промокодом FREE6MONTH и начните свой проект без лишних затрат.

Паттерн Одиночка (Singleton)

Паттерн Singleton (Одиночка) — это способ сделать так, чтобы у класса была только одна единственная экземпляр (объект) во всем приложении. Обычно этот паттерн используют для классов, которые управляют каким-то общим ресурсом или хранят глобальные настройки — например, класс для работы с конфигурацией, логированием или подключением к внешнему API. В Laravel паттерн одиночка часто встречается при работе с сервис-контейнером, когда мы регистрируем singleton сервисы: это гарантирует, что Laravel будет пересоздавать объект при каждом запросе, а будет использовать один и тот же экземпляр.

Зачем нужен Singleton? Представьте, что у вас есть класс для подключения к внешнему сервису или к файлу. Если при каждом использовании создавать новый объект, можно тратить лишние ресурсы или получать несогласованные данные. Singleton же обеспечивает единое "истинное состояние": все части программы используют один экземпляр, данные в нём всегда актуальны и синхронизированы. К тому же singleton позволяет централизованно управлять доступом к ресурсу и предотвращает создание лишних объектов (например, чтобы не открыть одно и то же соединение несколько раз).

Реализация паттерна Singleton

В классическом виде реализация Singleton в PHP сводится к созданию класса с приватным конструктором и статическим методом доступа к экземпляру. Давайте сделаем простой пример: класс конфигурации AppConfig, который загружает настройки и предоставляет их остальной части приложения как единый объект.

Шаг 1. Создаём класс Singleton. У класса будет приватный конструктор (чтобы нельзя было сделать new снаружи) и статический метод getInstance(), который либо создаёт новый экземпляр, либо возвращает уже созданный:

<?php

namespace App\Services;

class AppConfig
{
    private static ?AppConfig $instance = null;
    protected array $settings;

    // Приватный конструктор: вызывается только внутри самого класса
    private function __construct()
    {
        // Допустим, загружаем настройки из файла или env
        $this->settings = [
            'mode' => 'production',
            'items_per_page' => 10,
            // ... другие настройки
        ];
    }

    // Статический метод для получения экземпляра (Singleton access)
    public static function getInstance(): AppConfig
    {
        if (self::$instance === null) {
            self::$instance = new AppConfig();
        }
        return self::$instance;
    }

    // Пример метода для доступа к настройкам
    public function get(string $key)
    {
        return $this->settings[$key] ?? null;
    }
}

Здесь AppConfig::getInstance() либо создаёт объект (при первом вызове), либо возвращает сохранённый в self::$instance. В итоге где бы в коде мы ни вызвали AppConfig::getInstance(), мы всегда получим один и тот же объект с одним и тем же набором настроек.

Шаг 2. Используем Singleton. Теперь можно в любом месте кода получить настройки:

$config = App\Services\AppConfig::getInstance();
$mode = $config->get('mode');
Получите 6 месяцев бесплатного хостинга!
Воспользуйтесь нашим промокодом FREE6MONTH и начните свой проект без лишних затрат.

Переменная $mode будет возвращать одно и то же значение, и если вдруг в процессе работы приложения в $config->settings что-то изменится, все, кто получил $config через getInstance(), будут это видеть (потому что разделяют один объект).

В Laravel многие вещи можно зарегистрировать как Singleton в контейнере, не реализуя логику одиночки вручную. Например, если у нас есть свой сервис (класс), мы можем внутри AppServiceProvider сделать так:

$this->app->singleton(App\Services\AppConfig::class, function ($app) {
    return AppConfig::getInstance();
});

Однако часто даже не приходится явно писать собственные одиночки — Laravel сам предоставляет singleton-объекты (например, App контейнер, объект запроса Request и т.д. существуют в одном экземпляре на запрос). Но понимать сам паттерн полезно: вы будете знать, как сделать класс с единственным экземпляром и зачем это может понадобиться. Главное — использовать Singleton разумно, так как злоупотребление глобальными состояниями может затруднить тестирование и сопровождение (не зря Singleton иногда шутливо называют антипаттерном).

Паттерн Фабрика (Factory)

Паттерн Factory (Фабрика) — это порождающий шаблон проектирования, цель которого упростить создание объектов. Вместо того чтобы в коде разбросать вызовы конструкторов разных классов, вы выносите логику создания в особое место — фабрику. Фабрика решает, какой именно класс нуждается в создании, и возвращает готовый объект. Это особенно полезно, когда процесс создания сложный или зависит от условий.

В Laravel термин "Factory" также используется для фабрик моделей — удобного механизма для генерации тестовых данных (например, UserFactory для создания пользователей). Но в контексте дизайн-паттернов мы говорим не только о тестовых данных, а вообще о принципе отделения логики создания объекта от его использования (Laravel Design Pattern: Simplifying Web Development | by techiydude | Stackademic). Это делает код более гибким: вы можете легко менять классы, которые создаются, не переписывая место их вызова. Кроме того, фабрика часто используется совместно с Dependency Injection — фабрика может решать, какой конкретно класс передать, исходя из конфигурации или окружения.

Реализация паттерна Factory

Давайте представим ситуацию: у нас есть приложение, которое может отправлять уведомления пользователям разными способами — по электронной почте или по SMS. У нас будут два класса уведомлений: EmailNotification и SMSNotification, оба реализуют общий интерфейс NotificationInterface с методом send($message). Будем использовать фабрику, чтобы создавалось нужное уведомление в зависимости от типа, который мы укажем.

Эксклюзивно для читателей: полгода бесплатного хостинга!
Заберите свой промокод FREE6MONTH и воспользуйтесь всеми преимуществами премиум-хостинга бесплатно.

Шаг 1. Определяем интерфейс уведомления и несколько классов, его реализующих:

<?php

namespace App\Notifications;

interface NotificationInterface
{
    public function send(string $message): void;
}

// Класс для email-уведомлений
class EmailNotification implements NotificationInterface
{
    public function send(string $message): void
    {
        // Здесь бы произошла реальная отправка email
        echo "Sending EMAIL with message: {$message}\n";
    }
}

// Класс для SMS-уведомлений
class SMSNotification implements NotificationInterface
{
    public function send(string $message): void
    {
        // Здесь логика отправки SMS
        echo "Sending SMS with message: {$message}\n";
    }
}

У нас есть два типа уведомлений, и мы могли бы создавать их так: new EmailNotification() или new SMSNotification(). Но тогда выбор класса (email или sms) был бы размазан по коду. Вместо этого напишем фабрику.

Шаг 2. Создаём фабрику уведомлений. Фабрика может быть реализована как отдельный класс с методами, либо как статический метод. Для простоты сделаем статический метод:

<?php

namespace App\Notifications;

class NotificationFactory
{
    public static function create(string $type): NotificationInterface
    {
        switch ($type) {
            case 'email':
                return new EmailNotification();
            case 'sms':
                return new SMSNotification();
            default:
                throw new \Exception("Unsupported notification type: $type");
        }
    }
}

Метод create принимает параметр $type (например, 'email' или 'sms') и на его основе возвращает экземпляр соответствующего класса, который реализует NotificationInterface. Если тип не распознан, выбрасывается исключение (в боевом коде можно сделать обработку ошибок более изящной, но для примера сойдёт).

Шаг 3. Используем фабрику для отправки уведомлений. Теперь в коде, где нужно отправить уведомление, нам не нужно знать детали классов:

use App\Notifications\NotificationFactory;

// Где-то в контроллере или сервисе:
$type = 'email'; // допустим, выбираем тип уведомления (например, на основе настроек пользователя)
$notification = NotificationFactory::create($type);
$notification->send("Ваш заказ отправлен!");  // вызов метода send у нужного класса

Если позже мы решим добавить новый способ уведомлений, например пуш-уведомление (PushNotification), мы просто создадим новый класс, реализуем NotificationInterface и добавим новый case в фабрику. Места, где фабрика используется, менять не придётся — они по-прежнему вызывают NotificationFactory::create($type).

Начните с нами: 6 месяцев бесплатного хостинга!
Используйте промокод FREE6MONTH и раскройте потенциал своего сайта без финансовых вложений.

Преимущество фабрики очевидно: централизация создания объектов. Удобно контролировать процесс создания, например, можно внутри фабрики применять паттерн Singleton (если хотим переиспользовать один объект) или возвращать разные классы в зависимости от окружения (например, в тестовом окружении возвращать заглушки). Код, вызывающий фабрику, при этом остаётся простым и не меняется.

Паттерн Наблюдатель (Observer)

Паттерн Observer (Наблюдатель) — это шаблон, позволяющий одним объектам подписываться на события в другом объекте и получать уведомления, когда эти события происходят. Говоря проще, есть издатель (subject), который рассылает сообщения о своих изменениях, и есть подписчики (observers), которые эти сообщения получают и реагируют. Этот паттерн помогает организовать реакцию на события без жёсткой связи между классами: издатель не знает о конкретных подписчиках, он просто оповещает "всех, кто подписан".

В Laravel паттерн наблюдатель используется, например, для отслеживания событий моделей (Model Events). Вы можете определить Observer-класс для модели, чтобы выполнять код при создании, обновлении, удалении записей и т.д. Это позволяет вынести побочные эффекты (например, отправку письма при регистрации пользователя, запись логов при обновлении модели) из контроллеров или моделей в отдельные классы.

Реализация паттерна Observer

Рассмотрим пример: у нас есть модель User. Мы хотим автоматически отправлять приветственное письмо новому пользователю после регистрации. Вместо того чтобы вставлять код отправки письма прямо в контроллер или модель, мы создадим наблюдателя, который будет "слушать" событие создания пользователя.

Шаг 1. Создаём класс Observer. В Laravel достаточно создать класс, например UserObserver, с методами-обработчиками событий модели. Для события "создание" Eloquent вызывает метод created (после успешного сохранения нового объекта). Вот пример наблюдателя:

<?php

namespace App\Observers;

use App\Models\User;
use Mail; // условный фасад для отправки почты

class UserObserver
{
    // Метод вызывается после создания нового пользователя
    public function created(User $user)
    {
        // Отправляем приветственное письмо новому пользователю
        Mail::to($user->email)->send(new \App\Mail\WelcomeMail($user));
    }

    // Можно добавить и другие события, например:
    // public function updated(User $user) { ... }
    // public function deleted(User $user) { ... }
}

У UserObserver мы реализовали метод created(), который Laravel вызовет каждый раз, когда новый User будет сохранён в базу данных. Внутри метода мы выполняем нужное действие — здесь отправляем письмо (предположим, у нас есть Mailable WelcomeMail для этого).

Начните с нами: 6 месяцев бесплатного хостинга!
Используйте промокод FREE6MONTH и раскройте потенциал своего сайта без финансовых вложений.

Шаг 2. Регистрируем Observer для модели. Чтобы Laravel знал, что наш UserObserver должен следить за событиями модели User, его нужно зарегистрировать. Обычно это делается в AppServiceProvider (или можно создать отдельный EventServiceProvider). Добавим в метод boot:

use App\Models\User;
use App\Observers\UserObserver;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        User::observe(UserObserver::class);
    }
}

Вызывая User::observe(...), мы привязываем наш Observer-класс к модели. Теперь при возникновении событий в модели User (создание, обновление, удаление и пр.) Laravel будет вызывать соответствующие методы нашего UserObserver.

Шаг 3. Проверяем в действии. Теперь, когда где-то в приложении создаётся пользователь, например:

$user = User::create([
    'name' => 'Иван',
    'email' => 'ivan@example.com',
    'password' => bcrypt('secret'),
]);

После выполнения User::create(...) Laravel автоматически вызовет UserObserver::created($user). Наш наблюдатель отправит письмо Ивану, а контроллеру или коду, создавшему пользователя, не нужно об этом заботиться. Таким образом, бизнес-логика (создание пользователя) отделена от побочного эффекта (отправка письма). Мы легко можем добавить новые действия на это же событие (например, записать информацию в лог) — достаточно расширить UserObserver, не трогая код создания пользователя.

Laravel также предоставляет систему Events & Listeners, которая концептуально близка к паттерну Observer. Вы можете объявлять события (event(new UserRegistered($user))) и слушателей этих событий. Внутренне Eloquent-модели сами используют эту систему для реализации Observer (при вызове User::observe Laravel регистрирует скрытые слушатели на события модели). Но удобство Observer-классов в том, что они группируют логику, связанную с конкретной моделью, в одном месте.

Паттерн Фасад (Facade)

Паттерн Facade (Фасад) — это структурный шаблон проектирования, который предоставляет простой (упрощённый) интерфейс к сложной системе или группе классов. Фасад как бы скрывает сложность за кулисами, предлагая клиентскому коду удобные методы для выполнения подкапотных процессов (php - What is Facades used in Laravel? - Stack Overflow). В контексте Laravel фасады знакомы всем — это те самые классы вроде Auth, DB, Cache, Mail и многие другие, которые мы вызываем статически, хотя на самом деле за ними скрываются объекты из сервис-контейнера.

Почему фасады удобны? Они позволяют писать выразительный код без необходимости вручную создавать объекты или тянуть их из контейнера при каждом использовании. Например, вместо $cache = app('cache'); $cache->get('key'); мы пишем Cache::get('key'). Laravel под капотом находит объект нужного сервиса и вызывает метод. При этом фасады остаются более тестируемыми и гибкими, чем прямые статические вызовы, потому что в основе у них всё равно лежат обычные объекты (их можно подменить при тестировании). Фасады в Laravel — это не совсем классический шаблон Facade, но близки к нему: они дают единую точку доступа к функциональности и изолируют детали реализации.

Бесплатный хостинг на 6 месяцев для новых пользователей!
Примените промокод FREE6MONTH и получите высокоскоростной хостинг без оплаты.

(How Laravel Facades Work and How to Use Them Elsewhere — SitePoint) (How Laravel Facades Work and How to Use Them Elsewhere — SitePoint)Диаграмма ниже иллюстрирует суть паттерна Facade: клиент (Client) обращается к упрощённому интерфейсу (Facade class), а фасад внутри себя вызывает необходимые методы сложных подсистем (Complicated class 1, 2 и т.д.), скрывая их от пользователя. В Laravel роль "сложных классов" выполняют сервисы контейнера, а фасад — это оболочка, позволяющая к ним легко обратиться.

Теперь рассмотрим создание собственного фасада в Laravel в пару шагов.

Реализация паттерна Facade (на примере собственного фасада)

Предположим, у нас есть некий сервис (класс) для получения прогноза погоды, и мы хотим обращаться к нему через красивый фасад Weather. Покажем упрощённо, как создать такой фасад.

Шаг 1. Создаём сервис-класс. Этот класс выполняет основную работу. Например, получит данные о погоде (для простоты вернём заглушку):

<?php

namespace App\Services;

class WeatherService
{
    public function getForecast(string $city): string
    {
        // В реальности здесь, возможно, запрос к внешнему API погоды
        // Пока вернём фиктивные данные:
        return "Прогноз погоды для {$city}: солнечно, +20°C.";
    }
}

Шаг 2. Регистрируем сервис в контейнере Laravel. Это нужно, чтобы Laravel знал, как создать наш сервис. В AppServiceProvider добавим:

use App\Services\WeatherService;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        // Регистрируем WeatherService как singleton в контейнере
        $this->app->singleton('weather', function($app) {
            return new WeatherService();
        });
    }

    // ...
}

Здесь мы связываем строковый ключ 'weather' с экземпляром WeatherService. Теперь в любом месте приложения мы можем получить наш сервис через app('weather') или через резолвинг зависимости.

Эксклюзивно для читателей: полгода бесплатного хостинга!
Заберите свой промокод FREE6MONTH и воспользуйтесь всеми преимуществами премиум-хостинга бесплатно.

Шаг 3. Создаём класс Facade. Laravel фасады — это классы, наследующие Illuminate\Support\Facades\Facade. В нашей фасад-классе нужно определить метод getFacadeAccessor() который возвращает ключ в контейнере, связанный с базовым объектом. Создадим Weather.php в app/Facades:

<?php

namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class Weather extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return 'weather'; // тот самый ключ, под которым сервис в контейнере
    }
}

Этот минималистичный класс не содержит логики — он просто указывает Facade-наследнику, какой сервис вызывать из контейнера. Laravel, когда увидит вызов Weather::someMethod(), заглянет в контейнер по ключу 'weather' и вызовет этот метод на объекте WeatherService.

Шаг 4. Подключаем фасад для удобного использования. Чтобы фасад класс Weather стал доступен как Weather::method(), мы можем добавить его в псевдонимы (aliases). В Laravel < 9 это делалось в config/app.php в секции aliases. В Laravel 9+ можно использовать автодискавер пакетов или просто использовать полное имя класса фасада. Для простоты предположим, что мы добавили алиас:

'aliases' => [
    // ... другие фасады ...
    'Weather' => App\Facades\Weather::class,
],

Теперь мы готовы использовать наш фасад.

Шаг 5. Используем фасад. Где-нибудь в контроллере можем написать:

$forecast = \Weather::getForecast('Таллин');
// $forecast теперь содержит "Прогноз погоды для Таллин: солнечно, +20°C."

При вызове Weather::getForecast(...) Laravel найдет в контейнере зарегистрированный ранее weather (наш WeatherService) и вызовет метод getForecast у его экземпляра, вернув результат.

Бесплатный хостинг на 6 месяцев для новых пользователей!
Примените промокод FREE6MONTH и получите высокоскоростной хостинг без оплаты.

Итак, мы получили статически вызываемый фасад для нашего сервиса. Мы спрятали за фасадом детали: контроллеру не нужно знать, как именно WeatherService получает данные, или что он называется 'weather' в контейнере. Он просто вызывает понятный метод фасада.

Преимущества фасадов: лаконичность кода и удобство. Вместо передачи объектов в каждый класс или использование хелперов app(), мы получаем красивые однострочники. Главное — не злоупотреблять фасадами там, где прямая DI (Dependency Injection) лучше. Но для типичных случаев (кеш, очередь, логирование и т.п.) Laravel фасады значительно экономят время.

Паттерн Стратегия (Strategy)

Паттерн Strategy (Стратегия) — это поведенческий паттерн, который позволяет определить семейство алгоритмов (или вариантов поведения), и выбрать один из них во время выполнения программы. Объект, который использует стратегию, не знает деталей реализации алгоритма — он просто вызывает метод через общий интерфейс. Звучит абстрактно, но на практике стратегия помогает подставлять разную логику, не меняя окружающий код.

В Laravel (и вообще в PHP) стратегия часто используется, когда нужно по-разному обрабатывать данные в зависимости от настроек. Например, в самом Laravel есть разные драйверы кэширования (файловый, мемкеш, базу данных и т.д.) — они все реализуют единый интерфейс кэша, и в runtime выбирается нужная реализация (по сути, стратегия кэширования) (Laravel Design Pattern: Simplifying Web Development | by techiydude | Stackademic). Вы, как разработчик, можете тоже применять этот паттерн: скажем, переключаться между разными способами оплаты, разными алгоритмами расчёта или форматирования данных.

Реализация паттерна Strategy

Возьмём пример с оплатой заказов. Допустим, наше приложение поддерживает несколько способов оплаты: кредитной картой и PayPal. Мы хотим иметь возможность легко добавить новые способы в будущем, и не запутать код условностями вроде if ($method == 'card') { ... } else if .... Реализуем стратегию:

Шаг 1. Определяем интерфейс стратегии оплаты.

<?php

namespace App\Payment;

interface PaymentStrategy
{
    public function pay(float $amount): bool;
}
Начните с нами: 6 месяцев бесплатного хостинга!
Используйте промокод FREE6MONTH и раскройте потенциал своего сайта без финансовых вложений.

Интерфейс PaymentStrategy содержит метод pay для выполнения оплаты на указанную сумму. Он возвращает bool (например, успех или провал платежа).

Шаг 2. Реализуем несколько стратегий оплаты.

<?php

namespace App\Payment;

class CreditCardPayment implements PaymentStrategy
{
    public function pay(float $amount): bool
    {
        // Логика оплаты кредитной картой
        echo "Processing credit card payment of \${$amount}\n";
        // ... (в реальности здесь взаимодействие с API платежного шлюза)
        return true;
    }
}

class PayPalPayment implements PaymentStrategy
{
    public function pay(float $amount): bool
    {
        // Логика оплаты через PayPal
        echo "Processing PayPal payment of \${$amount}\n";
        // ... (взаимодействие с PayPal API)
        return true;
    }
}

У нас есть два класса, каждый реализует PaymentStrategy по-своему.

Шаг 3. Создаём контекст, использующий стратегию. Можно, конечно, вызывать стратегии напрямую, но обычно выделяют класс-контекст, который хранит в себе ссылку на текущую стратегию и использует её. Назовём его PaymentProcessor:

<?php

namespace App\Payment;

class PaymentProcessor
{
    private PaymentStrategy $strategy;

    public function __construct(PaymentStrategy $strategy)
    {
        // Внедряем выбранную стратегию через конструктор
        $this->strategy = $strategy;
    }

    public function setStrategy(PaymentStrategy $strategy): void
    {
        // Дополнительно метод для смены стратегии во время исполнения, если нужно
        $this->strategy = $strategy;
    }

    public function process(float $amount): bool
    {
        // Перенаправляем вызов к стратегии
        return $this->strategy->pay($amount);
    }
}

PaymentProcessor не знает деталей, как именно производится оплата — он просто вызывает $this->strategy->pay(). Стратегия может быть любая, лишь бы соответствовала интерфейсу PaymentStrategy.

Шаг 4. Используем стратегию оплаты. Теперь клиентский код может выбрать нужную стратегию и выполнить оплату:

use App\Payment\PaymentProcessor;
use App\Payment\CreditCardPayment;
use App\Payment\PayPalPayment;

// Допустим, выбор способа оплаты сделан на основе настроек пользователя или параметра:
$method = 'paypal'; // или 'card'

if ($method === 'card') {
    $strategy = new CreditCardPayment();
} else {
    $strategy = new PayPalPayment();
}

$processor = new PaymentProcessor($strategy);
$processor->process(99.00);  // вывод: "Processing PayPal payment of $99"
Начните с нами: 6 месяцев бесплатного хостинга!
Используйте промокод FREE6MONTH и раскройте потенциал своего сайта без финансовых вложений.

Здесь мы в зависимости от значения $method выбираем стратегию (в реальности этот выбор мог бы происходить автоматически, например, фабрикой). Затем создаём PaymentProcessor, передавая ему выбранную стратегию, и вызываем метод process. Если позже понадобится добавить, например, оплату криптовалютой, мы просто реализуем PaymentStrategy для нового способа и можем начать его передавать в PaymentProcessor — остальной код не нужно переписывать.

Где применяется стратегия? Помимо приведённого примера, практически везде, где есть сменные алгоритмы: разные способы авторизации пользователей (пароль, соцсети, OTP), разные форматы экспорта данных (CSV, PDF, XML) — всё это можно реализовать через Strategy. Паттерн помогает поддерживать открытость к расширению: добавление нового поведения не затрагивает старый код, вы просто добавляете новую стратегию.

Заключение

Мы рассмотрели несколько ключевых шаблонов проектирования на примере Laravel: Repository, Singleton, Factory, Observer, Facade и Strategy. Эти паттерны не придуманы специально для Laravel — это общие принципы архитектуры, но Laravel предоставляет удобную почву для их применения. Используя их, вы сможете писать код "как профессионал": более чистый, гибкий и сопровождаемый.

Важно помнить, что паттерны — это инструменты, а не строгие правила. Не надо применять паттерн лишь ради галочки. Всегда оценивайте, принесёт ли он упрощение и пользу в вашем конкретном случае. Например, нет смысла создавать репозиторий для каждой модели в маленьком проекте, но в большом монолите это может значительно улучшить структуру. Singleton удобен для общих сервисов, но избегайте превращать всё в синглтоны. Фасады облегчают жизнь, но тоже имеют свои компромиссы.

Изучайте код Laravel и популярных пакетов — вы обнаружите там множество реализованных паттернов. Со временем вы начнёте "узнавать" их в диком природе и интуитивно понимать, какой шаблон применить для той или иной задачи. А эта осознанность отличает джуна от мидла, мидла от сеньора. Так что прокачивайтесь и пишите отличный код! 😎

Хостинг, на который можно положиться!
Siteko.net

Устали от медленного хостинга или дорогих тарифов? Тогда вам к нам! Siteko.net — это быстрый и простой хостинг для тех, кто ценит удобство и стабильность.

  • Без падений и нервов — наш uptime почти всегда 100%.
  • Гибкие тарифы — только нужные функции, ничего лишнего.
  • Скорость— сайты грузятся, как пуля!
  • Удобно — разобраться сможет даже новичок, всё под рукой.
  • Поддержка всегда рядом 24/7 поможем решить любой вопрос.

Заходите на Siteko.net и попробуйте нас бесплатно первый месяц! Мы делаем всё, чтобы ваш сайт работал без проблем.

Siteko.net — просто, быстро и надёжно!