Поддержите проект сделав пожертвование.
Ясность с первого взгляда

Один класс — одна задача

Каждый класс в приложении должен сосредоточиться на выполнении одной конкретной задачи или функции

Один класс — одна задача
0
Основы

Что такое принцип One Class, One Task?

Принцип «Один класс — одна задача» (One Class, One Task) устанавливает требование, согласно которому каждый класс в приложении должен выполнять лишь одну конкретную задачу или функциональность. Этот подход способствует созданию более структурированного и предсказуемого кода, а также облегчает его поддержку.

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

Что это даст?
  • Читаемость кода: Наличие класса, отвечающего за единую задачу, значительно упрощает понимание его назначения и поведения. Это особенно важно в рамках командной разработки или при возвращении к проекту спустя значительное время, так как открыв класс, разработчик может быстро оценить его функциональность.

  • Простота тестирования: Изолирование логики в небольшие классы упрощает процесс тестирования. Каждый класс может быть протестирован независимо, что повышает точность и снижает время, необходимое для написания тестов.

  • Снижение сложности: Логика приложения становится более управляемой, когда она разбивается на мелкие, специализированные части. Такой подход способствует снижению уровня сложности кода и облегчает его восприятие.

  • Легкость изменений: Внесение изменений в код становится проще, когда изменения касаются небольших классов, каждое из которых отвечает за одну задачу. Это предотвращает возникновение неожиданностей при редактировании, так как изменения в одной части приложения не затрагивают другие компоненты.

Тем не менее, важно сохранять баланс между дроблением логики на мелкие классы и чрезмерной детализацией. Не все аспекты логики следует выносить в отдельные классы, необходимо учитывать контекст.

1
Фокус на работе приложения

Использование Actions позволяет сосредоточиться на бизнес-логике приложения, а не на технических деталях.

Классы Action выполняют конкретные задачи и изолируют их от других частей приложения, что упрощает понимание кода и его поддержку. Логика, связанная с выполнением одной задачи, собирается в одном месте, что облегчает её изменение и тестирование.

Пример класса действия:

class GenerateReservationCode
{
    const UNAMBIGUOUS_ALPHABET = 'BCDFGHJLMNPRSTVWXYZ2456789';

    public function __invoke(int $characters = 7): string
    {
        do {
            $code = $this->generateCode($characters);
        } while (Reservation::where('code', $code)->exists());

        return $code;
    }

    protected function generateCode(int $characters): string
    {
        return substr(str_shuffle(str_repeat(static::UNAMBIGUOUS_ALPHABET, $characters)), 0, $characters);
    }
}

Данный класс можно вызвать как функцию:

$generator = new GenerateReservationCode();
$reservationCode = $generator(8); // Генерация кода длиной 8 символов
2
Пакет Laravel Actions

Действие можно удобно запустить как объект, контроллер, фоновую задачу и консольную команду.

В экосистеме Laravel есть прекрасный пакет Laravel Actions который способствует организации кода вокруг действий. Данный пакет позволяет создавать классы действий, которые могут быть вызваны в различных контекстах, таких как контроллеры, события и консольные команды. Это обеспечивает более универсальный и гибкий код.

Пример использования:

class GenerateReservationCode
{
    use AsAction;

    const UNAMBIGUOUS_ALPHABET = 'BCDFGHJLMNPRSTVWXYZ2456789';

    public function handle(int $characters = 7): string
    {
        do {
            $code = $this->generateCode($characters);
        } while(Reservation::where('code', $code)->exists());

        return $code;
    }

    protected function generateCode(int $characters): string
    {
        return substr(str_shuffle(str_repeat(static::UNAMBIGUOUS_ALPHABET, $characters)), 0, $characters);
    }
}

Класс можно вызвать следующим образом:

GenerateReservationCode::run()

Если вам нужно выполнить действие в очереди, то вы так же можете это сделать, например:

GenerateReservationCode::dispatch();

Вы можете узнать больше об удобстве использование действий с пакетом Laravel Actions на его официальном сайте.

3
Рекомендуемые соглашения

Помогут вам оставаться последовательными при организации вашего приложения

Для упрощения поддержки и организации кода целесообразно придерживаться ряда рекомендаций при создании классов действий.

Начните с глагола

Названия классов действий должны представлять собой глаголы, отражающие выполняемую задачу.

Например, если класс предназначен для отправки письма для сброса пароля, его следует назвать SendResetPasswordEmail.

Используйте директорию Actions

Создайте папку app/Actions и сгруппируйте свои действия внутри неё по модулям. Это поможет поддерживать структуру вашего кода организованной и логичной. Например:

app/
├── Actions/
│   ├── Authentication/
│   │   ├── LoginUser.php
│   │   ├── RegisterUser.php
│   │   ├── ResetUserPassword.php
│   │   └── SendResetPasswordEmail.php
│   ├── Leads/
│   │   ├── BulkRemoveLead.php
│   │   ├── CreateNewLead.php
│   │   ├── GetLeadDetails.php
│   │   ├── MarkLeadAsCustomer.php
│   │   ├── MarkLeadAsLost.php
│   │   ├── RemoveLead.php
│   │   ├── SearchLeadsForUser.php
│   │   └── UpdateLeadDetails.php
│   └── Settings/
│       ├── GetUserSettings.php
│       ├── UpdateUserAvatar.php
│       ├── UpdateUserDetails.php
│       ├── UpdateUserPassword.php
│       └── DeleteUserAccount.php
├── Models/
└── ...

Если ваше приложение уже разделено на модули – создайте директорию Actions в каждом из них:

app/
├── Authentication/
│   ├── Actions/
│   ├── Models/
│   └── ...
├── Leads/
│   ├── Actions/
│   ├── Models/
│   └── ...
└── Settings/
    ├── Actions/
    └── ...

Такая организация поможет вам поддерживать порядок в коде и упростит навигацию.

4
Тестирование

Что такое принцип 'Один класс — одна задача'?

Поскольку каждый Action отвечает за одну задачу, его тестирование становится более простым и эффективным. Вы можете изолировать и протестировать каждое действие отдельно, что упрощает написание и выполнение тестов.

class GenerateReservationCodeTest extends TestCase
{
    public function testGeneratedCodeContainsOnlyAllowedCharacters(): void
    {
        $code = GenerateReservationCode::run(8);

        $this->assertMatchesRegularExpression(
            '/^[BCDFGHJLMNPRSTVWXYZ2456789]+$/',
             $code
        );
    }

    public function testGeneratedCodeIsUrlSafe(): void
    {
        $code = GenerateReservationCode::run();

        $this->assertTrue(
            filter_var($code, FILTER_VALIDATE_URL) === false
        );
    }
}

А наличие четко определенных входных и выходных данных позволят легко обнаруживать и исправлять ошибки.