1. Go to this page and download the library: Download lemax10/simple-actions library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
lemax10 / simple-actions example snippets
use LeMaX10\SimpleActions\Action;
class CreateUserAction extends Action
{
protected function handle(string $name, string $email): User
{
return User::create([
'name' => $name,
'email' => $email,
]);
}
}
// Создание и выполнение
$user = CreateUserAction::make()->run('John Doe', '[email protected]');
// Через хелпер
$user = action(CreateUserAction::class, 'John Doe', '[email protected]');
// Условное выполнение
$user = CreateUserAction::make()
->runIf($condition, 'John', '[email protected]');
$user = CreateUserAction::make()
->runUnless($condition, 'John', '[email protected]');
use LeMaX10\SimpleActions\UseCase;
class RegisterUserUseCase extends UseCase
{
protected function handle(array $data): User
{
// Все действия выполняются в транзакции
$user = CreateUserAction::make()->run($data['name'], $data['email']);
return tap($user, function() use($user, $data) {
SendWelcomeEmailAction::make()->run($user);
CreateUserProfileAction::make()->run($user, $data['profile']);
});
}
}
// Использование как обычного Action
$user = RegisterUserUseCase::make()->run($data);
// Или через хелпер
$user = usecase(RegisterUserUseCase::class, $data);
use App\Models\User;
use LeMaX10\SimpleActions\Action;
/**
* @extends Action<User>
*/
class FindUserAction extends Action
{
protected function handle(int $id): User
{
return User::query()->findOrFail($id);
}
}
$user = FindUserAction::make()->run(1); // User|false
$user = action(FindUserAction::class, 1); // User|false
$maybe = FindUserAction::make()->runIf(false, 1); // User|false|null
use App\Models\User;
use LeMaX10\SimpleActions\UseCase;
/**
* @extends UseCase<User>
*/
class RegisterUserUseCase extends UseCase
{
protected function handle(array $data): User
{
return CreateUserAction::make()->run($data['name'], $data['email']);
}
}
$user = RegisterUserUseCase::make()->run($data); // User|false
$user = usecase(RegisterUserUseCase::class, $data); // User|false
CreateUserAction::beforeRun(function (ActionBeforeRun $event) {
if ($user->isBanned()) {
return false; // Остановит выполнение
}
});
use LeMaX10\SimpleActions\Observers\ActionObserver;
class CreateUserActionObserver extends ActionObserver
{
public function beforeRun(ActionBeforeRun $event): void
{
// Логика перед выполнением
}
public function ran(ActionRan $event): void
{
// Логика после успешного выполнения
}
public function failed(ActionFailed $event): void
{
// Логика при ошибке
}
}
// Регистрация observer
CreateUserAction::observe(CreateUserActionObserver::class);
// Кешировать только если условие истинно
$result = GetDataAction::make()
->remember('data-key', 60)
->cacheWhen($user->isPremium())
->run();
// С closure
$result = CalculateAction::make()
->remember('calc-key', 60)
->cacheWhen(fn ($value) => $value > 100)
->run($value);
// Кешировать если НЕ выполнено условие
$result = GetDataAction::make()
->remember('data-key', 60)
->cacheUnless($user->isAdmin())
->run();
$action = GetDataAction::make()->remember('key', 60);
// Проверка наличия в кеше
if ($action->isCached('key')) {
// ...
}
// Получение ключа кеша
$cacheKey = $action->getCacheKey();
// Удаление из кеша
$action->forget('key');
// Первый вызов - выполнит handle()
$user = GetUserAction::make()->memo()->run($userId);
// Повторный вызов с теми же аргументами - вернёт результат из памяти
$user = GetUserAction::make()->memo()->run($userId); // handle() не выполнится
// Другие аргументы - выполнит handle() снова
$otherUser = GetUserAction::make()->memo()->run($otherUserId);
// Сохраняем результат
$data = FetchDataAction::make()->memo()->run($params);
// Получаем мемоизированный результат
$data = FetchDataAction::make()->memo()->run($params);
// Принудительно обновляем результат
$freshData = FetchDataAction::make()
->memo(force: true)
->run($params); // handle() выполнится заново
// Теперь memo() возвращает обновлённый результат
$data = FetchDataAction::make()->memo()->run($params); // Вернёт $freshData
CreateUserAction::ran(function($event) {
Log::info('User created'); // Логируем только при реальном создании
});
// Первый вызов - handle() выполнится, события запустятся
$user1 = CreateUserAction::make()->memo()->run($data);
// Лог: "User created"
// Второй вызов - результат из памяти, события НЕ запустятся
$user2 = CreateUserAction::make()->memo()->run($data);
// Лог: (пусто)
CreateUserAction::ran(function($event) {
Cache::tags(['users'])->flush(); // Нужно каждый раз
});
// События запустятся даже для мемоизированного результата
$user = CreateUserAction::make()
->memo(forceEvents: true)
->run($data);
// Использовать пользовательский ключ вместо хеша аргументов
$result = CalculateAction::make()
->memo(key: 'my-custom-key')
->run($value1, $value2);
// Вернёт тот же результат, даже с другими аргументами!
$result = CalculateAction::make()
->memo(key: 'my-custom-key')
->run($differentValue1, $differentValue2);
$action = GetDataAction::make();
// Проверить, мемоизирован ли результат
if ($action->isMemoized([$userId])) {
// Результат уже в памяти
}
// Забыть конкретный результат
$action->memoForget([$userId]);
// Забыть все результаты данного Action
GetDataAction::memoFlush();
// Очистить мемоизацию всех Actions (редко используется)
Action::memoFlushAll();
// Получить количество мемоизированных результатов
$count = GetDataAction::getMemoizedCount();
class GetUserPermissionsAction extends Action
{
protected function handle(int $userId): array
{
// Тяжёлый запрос к БД
return DB::table('permissions')
->join('user_permissions', ...)
->where('user_id', $userId)
->get()
->toArray();
}
}
class ProcessUsersUseCase extends UseCase
{
protected function handle(array $userIds): array
{
$results = [];
foreach ($userIds as $userId) {
// Если userId повторяется - запрос не выполнится повторно
$permissions = GetUserPermissionsAction::make()
->memo()
->run($userId);
$results[] = ProcessUserAction::make()->run($userId, $permissions);
}
return $results;
}
}
// Controller
class OrderController
{
public function show(int $orderId)
{
// Первый запрос
$order = GetOrderAction::make()->memo()->run($orderId);
// Вызывается UseCase, который тоже запрашивает заказ
$invoice = GenerateInvoiceUseCase::make()->run($orderId);
return view('order.show', compact('order', 'invoice'));
}
}
// UseCase
class GenerateInvoiceUseCase extends UseCase
{
protected function handle(int $orderId): Invoice
{
// Не выполнит запрос повторно - возьмёт из памяти
$order = GetOrderAction::make()->memo()->run($orderId);
return GeneratePdfInvoice::make()->run($order);
}
}
class FetchExchangeRateAction extends Action
{
protected function handle(string $currency): float
{
// Запрос к внешнему API
return Http::get("https://api.example.com/rate/{$currency}")
->json('rate');
}
}
// В любом месте приложения
$rate1 = FetchExchangeRateAction::make()->memo()->run('USD');
$rate2 = FetchExchangeRateAction::make()->memo()->run('USD'); // Не запросит API
$rate3 = FetchExchangeRateAction::make()->memo()->run('EUR'); // Запросит для EUR
// Мемоизация + кеширование = максимальная производительность
$report = GenerateReportAction::make()
->memo() // В памяти на время запроса
->remember('report-key', 3600) // В Redis на час
->run($params);
// Первый запрос: выполнит handle() -> сохранит в Redis и в память
// Второй запрос в том же HTTP запросе: возьмёт из памяти
// Третий запрос в новом HTTP запросе: возьмёт из Redis
class CreateOrderAction extends Action
{
protected bool $singleTransaction = true;
protected function handle(User $user, array $items): Order
{
$order = Order::create(['user_id' => $user->id]);
foreach ($items as $item) {
$order->items()->create($item);
}
return $order;
}
}
// Регистрация observer
CreateOrderAction::observe(OrderActionObserver::class);
// Использование с транзакцией, кешированием и событиями
$order = CreateOrderAction::make()
->withTransaction()
->rememberAuto('order', 3600)
->cacheWhen(fn ($user) => $user->isPremium())
->tags(['orders', 'user-' . $user->id])
->store('redis')
->run($user, $items);
// ✅ Хорошо - атомарные действия
class CreateUserAction extends Action { }
class SendEmailAction extends Action { }
class LogActivityAction extends Action { }
// ✅ Хорошо - UseCase агрегирует действия
class RegisterUserUseCase extends UseCase {
protected function handle($data) {
$user = CreateUserAction::make()->run($data);
SendEmailAction::make()->run($user);
LogActivityAction::make()->run($user);
return $user;
}
}
// ❌ Плохо - слишком общее
class UserAction extends Action { }
// ✅ Хорошо - зависимость от абстрактного класса
abstract class NotificationAction extends Action {
// handle() реализуют дочерние классы
}
class SendEmailAction extends NotificationAction {
protected function handle(User $user, string $message): bool { /* ... */ }
}
class SendSmsAction extends NotificationAction {
protected function handle(User $user, string $message): bool { /* ... */ }
}
class NotifyUserUseCase extends UseCase {
protected function handle(User $user, string $message) {
// Зависимость от абстракции - легко подменить через контейнер
return app(NotificationAction::class)->run($user, $message);
}
}
// ❌ Плохо - невозможность подмены в тестах
class NotifyUserUseCase extends UseCase {
protected function handle(User $user, string $message) {
// Жестко привязан к SendEmailAction, нельзя подменить
return (new SendEmailAction())->run($user, $message);
}
}
class CreateUserAction extends Action
{
protected function handle(
string $name,
string $email,
?string $phone = null
): User {
// ...
}
}
use LeMaX10\SimpleActions\UseCase;
class RegisterUserUseCase extends UseCase
{
// UseCase поддерживает все возможности Action:
// - События (beforeRun, running, ran, failed, afterRun)
// - Транзакции (по умолчанию включены)
// - Кеширование
protected function handle(array $data): User
{
// UseCase координирует выполнение нескольких Actions
$user = CreateUserAction::make()->run($data['name'], $data['email']);
SendWelcomeEmailAction::make()->run($user);
CreateUserProfileAction::make()->run($user, $data['profile']);
NotifyAdminAction::make()->run($user);
return $user;
}
}
// Использование UseCase как обычного Action
$user = RegisterUserUseCase::make()
->remember('user-registration-' . $email, 300) // Можно кешировать
->run($data);
// UseCase поддерживает события
RegisterUserUseCase::ran(function ($event) {
Log::info('User registered', ['user' => $event->result]);
});
// UseCase выполняется в транзакции (по умолчанию)
// Если любое вложенное действие упадет - откатится всё
// Базовый абстрактный класс (вместо интерфейса)
abstract class SendNotificationAction extends Action {
}
// Реальная реализация
class SendEmailNotificationAction extends SendNotificationAction {
protected function handle(User $user, string $message): bool {
Mail::to($user)->send(new Notification($message));
return true;
}
}
// Тестовая реализация
class FakeSendNotificationAction extends SendNotificationAction {
protected function handle(User $user, string $message): bool {
Log::info('Fake notification sent');
return true;
}
}
// Регистрация в ServiceProvider
public function register() {
$this->app->bind(
SendNotificationAction::class,
SendEmailNotificationAction::class
);
}
// UseCase зависит от абстракции, а не конкретного объекта
class RegisterUserUseCase extends UseCase {
protected function handle(array $data): User {
$user = CreateUserAction::make()->run($data);
// Получаем реализацию из контейнера
app(SendNotificationAction::class)->run($user, 'Welcome!');
return $user;
}
}
// В тестах можно подменить
public function test_registration() {
$this->app->bind(
SendNotificationAction::class,
FakeSendNotificationAction::class // Подмена!
);
$user = RegisterUserUseCase::make()->run($data);
$this->assertDatabaseHas('users', ['email' => $data['email']]);
}
// UseCase зависит от конкретного класса
class RegisterUserUseCase extends UseCase {
protected function handle(array $data): User {
$user = CreateUserAction::make()->run($data);
// Использование конкретного класса
SendEmailAction::make()->run($user, 'Welcome!');
return $user;
}
}
// В тестах подменяем конкретный класс на fake
public function test_registration() {
// Подменяем SendEmailAction на FakeEmailAction
$this->app->bind(SendEmailAction::class, FakeEmailAction::class);
$user = RegisterUserUseCase::make()->run($data);
$this->assertDatabaseHas('users', ['email' => $data['email']]);
}
// В ServiceProvider
public function register() {
$this->app->bind('notification.action', function ($app) {
if ($app->environment('testing')) {
return new FakeNotificationAction();
}
return new SendEmailNotificationAction();
});
}
// UseCase использует строковой ключ
class RegisterUserUseCase extends UseCase {
protected function handle(array $data): User {
$user = CreateUserAction::make()->run($data);
app('notification.action')->run($user, 'Welcome!');
return $user;
}
}
class SendEmailAction extends Action {
public function __construct(
protected Mailer $mailer,
protected LoggerInterface $logger
) {
parent::__construct();
}
protected function handle(User $user, string $message): void {
$this->mailer->send($user->email, $message);
$this->logger->info('Email sent', ['user' => $user->id]);
}
}
// Laravel автоматически резолвит зависимости
$result = SendEmailAction::make()->run($user, 'Hello');
// В тестах
class RegisterUserTest extends TestCase {
protected function setUp(): void {
parent::setUp();
// Глобально подменяем тяжелые Actions на фейки
$this->app->bind(SendEmailAction::class, FakeEmailAction::class);
$this->app->bind(NotifySlackAction::class, FakeSlackAction::class);
}
public function test_user_registration() {
// Все UseCase будут использовать фейковые Actions
$user = RegisterUserUseCase::make()->run($data);
$this->assertTrue($user->exists);
}
}
// В ServiceProvider
public function register() {
// В зависимости от окружения используем разные реализации
if ($this->app->environment('testing')) {
$this->app->bind(PaymentActionInterface::class, FakePaymentAction::class);
} elseif ($this->app->environment('local')) {
$this->app->bind(PaymentActionInterface::class, SandboxPaymentAction::class);
} else {
$this->app->bind(PaymentActionInterface::class, StripePaymentAction::class);
}
}