### Comprehensive Action Configuration Example Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md An advanced example showcasing the combination of multiple features including transactions, caching with Redis, conditional caching, and event handling for an Action. ```php 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; } } // Register observer CreateOrderAction::observe(OrderActionObserver::class); // Usage with transaction, caching, and events $order = CreateOrderAction::make() ->withTransaction() ->rememberAuto('order', 3600) ->cacheWhen(fn ($user) => $user->isPremium()) ->tags(['orders', 'user-' . $user->id]) ->store('redis') ->run($user, $items); ``` -------------------------------- ### Global Substitution for Testing Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Explains how to globally substitute multiple heavy Actions with fake implementations in tests by binding them in the `setUp` method of a TestCase. ```php // In tests class RegisterUserTest extends TestCase { protected function setUp(): void { parent::setUp(); // Globally substitute heavy Actions with fakes $this->app->bind(SendEmailAction::class, FakeEmailAction::class); $this->app->bind(NotifySlackAction::class, FakeSlackAction::class); } public function test_user_registration() { // All UseCases will use fake Actions $user = RegisterUserUseCase::make()->run($data); $this->assertTrue($user->exists); } } ``` -------------------------------- ### Install Simple Actions Package Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Use Composer to install the package into your Laravel project. ```bash composer require lemax10/simple-actions ``` -------------------------------- ### Define a Simple Action Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Example of creating a custom action by extending the base Action class and implementing the handle method. ```php use LeMaX10\SimpleActions\Action; class CreateUserAction extends Action { protected function handle(string $name, string $email): User { return User::create([ 'name' => $name, 'email' => $email, ]); } } ``` -------------------------------- ### Retrieve User with Memoization in Controller Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Fetch a user's data in a controller using `action_with` and configure it with memoization to avoid redundant database queries. This is a demonstration example. ```php $user = action_with( GetUserAction::class, static fn(Action $action) => $action->memo(), $userId ); ``` -------------------------------- ### Global Mocking of Actions in Tests Source: https://github.com/lemax10/simple-actions/blob/main/README.md In test setup, globally bind heavy or external-dependent Actions to their fake counterparts. This ensures all UseCases executed during the test will utilize the mocked versions. ```php // В тестах 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); } } ``` -------------------------------- ### Managing Memoization State Source: https://github.com/lemax10/simple-actions/blob/main/README.md Control and inspect the memoization status of actions. Use these methods to check if a result is cached, clear specific or all cached results, and get the count of memoized items. ```php $action = GetDataAction::make(); // Проверить, мемоизирован ли результат if ($action->isMemoized([$userId])) { // Результат уже в памяти } // Забыть конкретный результат $action->memoForget([$userId]); // Забыть все результаты данного Action GetDataAction::memoFlush(); // Очистить мемоизацию всех Actions (редко используется) Action::memoFlushAll(); // Получить количество мемоизированных результатов $count = GetDataAction::getMemoizedCount(); ``` -------------------------------- ### Execute a UseCase Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Demonstrates how to instantiate and run a UseCase, similar to a regular action, using make and run, or via a helper function. ```php // Usage like a regular Action $user = RegisterUserUseCase::make()->run($data); // Or via helper $user = usecase(RegisterUserUseCase::class, $data); ``` -------------------------------- ### Execute a Simple Action Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Demonstrates how to instantiate and run a custom action using the make and run methods, or via a helper function. ```php // Create and execute $user = CreateUserAction::make()->run('John Doe', 'john@example.com'); // Via helper $user = action(CreateUserAction::class, 'John Doe', 'john@example.com'); ``` -------------------------------- ### Quick UseCase Execution with `usecase()` Helper Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Demonstrates the `usecase()` helper function for quick execution of UseCases, offering a shorthand compared to the standard instantiation and run method. ```php // Quick execution of a UseCase $user = usecase(RegisterUserUseCase::class, $data); // Equivalent to: $user = RegisterUserUseCase::make()->run($data); ``` -------------------------------- ### Quick Action Execution with `action()` Helper Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Provides a concise way to execute an Action using the `action()` helper function, which is equivalent to instantiating and running the Action directly. ```php // Quick execution of an Action $result = action(CalculateAction::class, $data); // Equivalent to: $result = CalculateAction::make()->run($data); ``` -------------------------------- ### Atomic Actions and UseCase Aggregation Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Demonstrates the creation of atomic actions and how a UseCase can aggregate these actions to form a complete scenario. Avoid overly general actions. ```php // ✅ Good - atomic actions class CreateUserAction extends Action { } class SendEmailAction extends Action { } class LogActivityAction extends Action { } // ✅ Good - UseCase aggregates actions class RegisterUserUseCase extends UseCase { protected function handle($data) { $user = CreateUserAction::make()->run($data); SendEmailAction::make()->run($user); LogActivityAction::make()->run($user); return $user; } } // ❌ Bad - too general class UserAction extends Action { } ``` -------------------------------- ### Action with Type Hinting Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Shows how to use type hinting for action parameters and return types to improve code clarity and robustness. ```php class CreateUserAction extends Action { protected function handle( string $name, string $email, ?string $phone = null ): User { // ... } } ``` -------------------------------- ### Action Configuration with `action_with()` Helper Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Introduces the `action_with()` helper for configuring Actions before execution using a callback, enabling options like memoization, caching, and transactions. ```php // With memoization $user = action_with( GetUserAction::class, fn(Action $action) => $action->memo(), $userId ); // With caching $report = action_with( GenerateReportAction::class, fn(Action $action) => $action->rememberAuto('reports', 3600), $from, $to ); // Combination of options $result = action_with( ProcessOrderAction::class, fn(Action $action) => $action->memo()->withTransaction(), $orderId, $items ); // With cache tags $data = action_with( GetUserDataAction::class, fn(Action $action) => $action->remember('user-'.$id, 3600)->tags(['users']), $userId ); ``` -------------------------------- ### Dependency Inversion with Abstract Classes Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Illustrates using abstract classes for actions to enable dependency inversion. This allows for easy substitution of implementations, especially in testing. ```php // ✅ Good - dependency on an abstract class abstract class NotificationAction extends Action { // handle() is implemented by child classes } 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) { // Dependency on abstraction - easy to substitute via container return app(NotificationAction::class)->run($user, $message); } } // ❌ Bad - impossible to substitute in tests class NotifyUserUseCase extends UseCase { protected function handle(User $user, string $message) { // Tightly coupled to SendEmailAction, cannot be substituted return (new SendEmailAction())->run($user, $message); } } ``` -------------------------------- ### Configure UseCase with Combined Options Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Combine multiple configuration options like memoization and transactions using `usecase_with` for a UseCase. This allows for flexible execution strategies. ```php $result = usecase_with( GenerateFinanceReportUseCase::class, fn(UseCase $usecase) => $usecase->memo()->withTransaction(), $orderId, $items ); ``` -------------------------------- ### Generate Action and UseCase Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Artisan commands to quickly create boilerplate files for new actions and use cases. ```bash php artisan make:action User/CreateUser php artisan make:usecase User/RegisterUser ``` -------------------------------- ### Registration by String Keys in ServiceProvider Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Shows how to register an Action using a string key in a ServiceProvider, allowing conditional resolution based on the application environment. ```php // In ServiceProvider public function register() { $this->app->bind('notification.action', function ($app) { if ($app->environment('testing')) { return new FakeNotificationAction(); } return new SendEmailNotificationAction(); }); } // UseCase uses the string key class RegisterUserUseCase extends UseCase { protected function handle(array $data): User { $user = CreateUserAction::make()->run($data); app('notification.action')->run($user, 'Welcome!'); return $user; } } ``` -------------------------------- ### Conditional Substitution Based on Environment Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Demonstrates how to conditionally bind Action implementations in a ServiceProvider based on the current application environment (e.g., testing, local, production). ```php // In ServiceProvider public function register() { // Use different implementations based on environment 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); } } ``` -------------------------------- ### UseCase for Coordinating Actions Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Demonstrates a UseCase coordinating multiple actions, supporting features like events, transactions, and caching. UseCases can be used like regular Actions. ```php use LeMaX10\SimpleActions\UseCase; class RegisterUserUseCase extends UseCase { // UseCase supports all Action features: // - Events (beforeRun, running, ran, failed, afterRun) // - Transactions (enabled by default) // - Caching protected function handle(array $data): User { // UseCase coordinates the execution of several 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; } } // Using UseCase like a regular Action $user = RegisterUserUseCase::make() ->remember('user-registration-' . $email, 300) // Can be cached ->run($data); // UseCase supports events RegisterUserUseCase::ran(function ($event) { Log::info('User registered', ['user' => $event->result]); }); // UseCase runs in a transaction (by default) // If any nested action fails, everything is rolled back ``` -------------------------------- ### Generate Action with Full Path Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Artisan commands to create action and use case files with a specified full path and suffix. ```bash php artisan make:action Actions/User/CreateUserAction php artisan make:usecase UseCases/User/RegisterUserUseCase ``` -------------------------------- ### Dependency Inversion with Abstract Classes for Notifications Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Shows how to implement Dependency Inversion using abstract classes for interchangeable notification actions. This includes service container registration and test substitution. ```php // Base abstract class (instead of an interface) abstract class SendNotificationAction extends Action { } // Real implementation class SendEmailNotificationAction extends SendNotificationAction { protected function handle(User $user, string $message): bool { Mail::to($user)->send(new Notification($message)); return true; } } // Test implementation class FakeSendNotificationAction extends SendNotificationAction { protected function handle(User $user, string $message): bool { Log::info('Fake notification sent'); return true; } } // Registration in ServiceProvider public function register() { $this->app->bind( SendNotificationAction::class, SendEmailNotificationAction::class ); } // UseCase depends on abstraction, not a concrete object class RegisterUserUseCase extends UseCase { protected function handle(array $data): User { $user = CreateUserAction::make()->run($data); // Get implementation from container app(SendNotificationAction::class)->run($user, 'Welcome!'); return $user; } } // In tests, you can substitute public function test_registration() { $this->app->bind( SendNotificationAction::class, FakeSendNotificationAction::class // Substitution! ); $user = RegisterUserUseCase::make()->run($data); $this->assertDatabaseHas('users', ['email' => $data['email']]); } ``` -------------------------------- ### Custom Idempotency Repository and Store Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Configure custom idempotency repositories and storage drivers (e.g., 'cache', 'redis') for idempotency management. ```php $result = SomeAction::make() ->idempotentRepository('cache') ->idempotentStore('redis') ->idempotent('my:key', 300) ->run($payload); ``` -------------------------------- ### Combining Memoization with Persistent Caching Source: https://github.com/lemax10/simple-actions/blob/main/README.md Demonstrates how to combine in-memory memoization with a persistent cache (like Redis) for maximum performance. This approach caches results both in memory for the current request and in a persistent store for future requests. ```php // Мемоизация + кеширование = максимальная производительность $report = GenerateReportAction::make() ->memo() // В памяти на время запроса ->remember('report-key', 3600) // В Redis на час ->run($params); // Первый запрос: выполнит handle() -> сохранит в Redis и в память // Второй запрос в том же HTTP запросе: возьмёт из памяти // Третий запрос в новом HTTP запросе: возьмёт из Redis ``` -------------------------------- ### Dependency Injection via Constructor Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Illustrates dependency injection for Actions using constructor parameters, allowing automatic resolution of dependencies like Mailer and Logger by Laravel. ```php 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 automatically resolves dependencies $result = SendEmailAction::make()->run($user, 'Hello'); ``` -------------------------------- ### Conditional Action Execution Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Shows how to conditionally execute an action using runIf or runUnless methods based on a boolean condition. ```php // Conditional execution $user = CreateUserAction::make() ->runIf($condition, 'John', 'john@example.com'); $user = CreateUserAction::make() ->runUnless($condition, 'John', 'john@example.com'); ``` -------------------------------- ### Substitution of a Concrete Class in Tests Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Demonstrates how to substitute a concrete Action class with a fake implementation during testing by binding it in the application container. ```php class RegisterUserUseCase extends UseCase { protected function handle(array $data): User { $user = CreateUserAction::make()->run($data); // Using a concrete class SendEmailAction::make()->run($user, 'Welcome!'); return $user; } } // In tests, substitute the concrete class with a fake public function test_registration() { // Substitute SendEmailAction with FakeEmailAction $this->app->bind(SendEmailAction::class, FakeEmailAction::class); $user = RegisterUserUseCase::make()->run($data); $this->assertDatabaseHas('users', ['email' => $data['email']]); } ``` -------------------------------- ### Configure UseCase with Memoization Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Use `usecase_with` to configure a UseCase with memoization before execution. This helps in caching results based on arguments. ```php $user = usecase_with( RegisterUserUseCase::class, fn(UseCase $usecase) => $usecase->memo(), $userId ); ``` -------------------------------- ### String Keyed Bindings for Conditional Mocking Source: https://github.com/lemax10/simple-actions/blob/main/README.md Register actions using string keys in the service container, allowing conditional resolution based on the application environment. This is less preferred due to reduced type safety but offers flexibility. ```php // В 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; } } ``` -------------------------------- ### Execute UseCase in Controller Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Register a new user by executing a UseCase with validated request data within a controller method. Returns a JSON response with the created user. ```php $user = usecase(RegisterUserUseCase::class, $request->validated()); return response()->json(['user' => $user]); ``` -------------------------------- ### Idempotent Action with Auto Key Generation Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Utilize auto-generated keys for idempotency, similar to memoization, to simplify key management. ```php // Auto-generation of a key similar to rememberAuto/memo $order = CreateOrderAction::make() ->idempotentAuto('order:create', 300) ->run($payload); ``` -------------------------------- ### Implementing Action Observers for Event Logic Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Group event logic by creating an observer class that extends `ActionObserver`. Register the observer using the `observe()` method on the Action class. ```php use LeMaX10\SimpleActions\Observers\ActionObserver; class CreateUserActionObserver extends ActionObserver { public function beforeRun(ActionBeforeRun $event): void { // Logic before execution } public function ran(ActionRan $event): void { // Logic after successful execution } public function failed(ActionFailed $event): void { // Logic on error } } // Register observer CreateUserAction::observe(CreateUserActionObserver::class); ``` -------------------------------- ### Abstract Class Substitution for Mocking Source: https://github.com/lemax10/simple-actions/blob/main/README.md Use an abstract base class to define actions and bind concrete implementations to their abstract counterparts in the service container. This allows for easy swapping of implementations, especially in tests. ```php // Базовый абстрактный класс (вместо интерфейса) 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']]); } ``` -------------------------------- ### Configure UseCase with Caching Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Use `usecase_with` to configure a UseCase with caching, specifying a cache key and expiration time. The `rememberAuto` method is used here. ```php $report = usecase_with( GenerateFinanceReportUseCase::class, fn(UseCase $usecase) => $usecase->rememberAuto('reports', 3600), $from, $to ); ``` -------------------------------- ### Configure UseCase with Cache Tags Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Configure a UseCase with caching that includes specific tags for cache invalidation. The `remember` method with `tags` is used for this purpose. ```php $data = usecase_with( GenerateFinanceReportFromUserUseCase::class, fn(UseCase $usecase) => $usecase->remember('user-'.$userModel->getKey(), 3600)->tags(['reports']), $userModel ); ``` -------------------------------- ### Using Memoization Across Application Layers Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Illustrates how `memo()` ensures that an Action's result is retrieved from memory on subsequent calls within the same request, even when invoked from different parts of the application like a Controller and a UseCase. ```php // Controller class OrderController { public function show(int $orderId) { // First request $order = GetOrderAction::make()->memo()->run($orderId); // Calls a UseCase that also requests the order $invoice = GenerateInvoiceUseCase::make()->run($orderId); return view('order.show', compact('order', 'invoice')); } } // UseCase class GenerateInvoiceUseCase extends UseCase { protected function handle(int $orderId): Invoice { // Won't execute the query again - will take from memory $order = GetOrderAction::make()->memo()->run($orderId); return GeneratePdfInvoice::make()->run($order); } } ``` -------------------------------- ### Inter-Layer Communication with Memoization Source: https://github.com/lemax10/simple-actions/blob/main/README.md Illustrates how memoization facilitates efficient data retrieval when an action is called from different application layers (Controller and UseCase). The second call to GetOrderAction retrieves the data from memory, avoiding a redundant database query. ```php // 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); } } ``` -------------------------------- ### Define a UseCase Aggregating Actions Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Create a UseCase by extending the UseCase class, which aggregates multiple actions into a single scenario, executed within a transaction. ```php use LeMaX10\SimpleActions\UseCase; class RegisterUserUseCase extends UseCase { protected function handle(array $data): User { // All actions are executed within a transaction $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']); }); } } ``` -------------------------------- ### Concrete Class Substitution for Mocking Source: https://github.com/lemax10/simple-actions/blob/main/README.md When a UseCase depends on a specific concrete class, you can bind a fake implementation of that class in your tests. This approach is simpler if the dependency is not intended to be highly polymorphic. ```php // 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']]); } ``` -------------------------------- ### Generate MD5 Hash from Arguments Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Use `generate_args_hash` to create an MD5 hash from an array of arguments. This is useful for generating unique keys for memoization and caching. ```php $hash = generate_args_hash([$userId, $type, ['option' => 'value']]); // Result: "5d41402abc4b2a76b9719d911017c592" ``` ```php $cacheKey = "custom-key:" . generate_args_hash($params); Cache::remember($cacheKey, 3600, fn() => heavyCalculation($params)); ``` -------------------------------- ### Select cache driver Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Specify a particular cache driver, such as Redis, for storing action results. This is useful for performance-sensitive operations. ```php $result = HeavyCalculationAction::make() ->store('redis') ->remember('calculation-key', 3600) ->run($data); ``` -------------------------------- ### Registering Lifecycle Event Listeners for Actions Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Attach listeners to an Action's lifecycle events such as `beforeRun`, `ran`, and `failed` to execute custom logic at specific points. Use `Log` facade for logging event details. ```php // Simple registration CreateUserAction::beforeRun(function (ActionBeforeRun $event) { Log::info('Creating user', $event->arguments); }); CreateUserAction::ran(function (ActionRan $event) { Log::info('User created', ['user' => $event->result]); }); CreateUserAction::failed(function (ActionFailed $event) { Log::error('Failed to create user', [ 'exception' => $event->exception ]); }); ``` -------------------------------- ### Strict Typing with PHPDoc Generics for Use Cases Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Similar to Actions, Use Cases can leverage PHPDoc generics to define their return types, enhancing type safety with `run()` and `usecase()`. ```php use App\Models\User; use LeMaX10\SimpleActions\UseCase; /** * @extends UseCase */ 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 ``` -------------------------------- ### Avoiding N+1 Problem with Memoization in UseCase Source: https://github.com/lemax10/simple-actions/blob/main/README.md Demonstrates how to prevent redundant database queries within a loop by memoizing an action. This ensures that an expensive database query is executed only once per unique user ID, even if called multiple times. ```php 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; } } ``` -------------------------------- ### Custom memoization key Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Define a custom key for memoization instead of relying on the default argument hashing. This allows overriding the memoized result even with different input arguments. ```php // Use a custom key instead of argument hash $result = CalculateAction::make() ->memo(key: 'my-custom-key') ->run($value1, $value2); // Will return the same result, even with different arguments! $result = CalculateAction::make() ->memo(key: 'my-custom-key') ->run($differentValue1, $differentValue2); ``` -------------------------------- ### Idempotent Action Execution with Key Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Implement idempotency for actions to prevent repeated parallel execution by providing a unique key and expiration time. ```php $order = CreateOrderAction::make() ->idempotent("order:create:{$requestId}", 300) ->run($payload); ``` -------------------------------- ### Basic Caching for Action Results Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Cache the result of an Action for a specified duration (in seconds) using `remember()` or cache it permanently using `rememberForever()`. Provide a unique cache key. ```php // Cache for 60 seconds $result = CalculateAction::make() ->remember('calc-key', 60) ->run($data); // Permanent caching $result = CalculateAction::make() ->rememberForever('calc-key') ->run($data); ``` -------------------------------- ### Attaching Local Events to Specific Action Instances Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Attach event hooks like `before`, `after`, `then`, `onFail`, and `runningLocal` to a single Action instance. These hooks only affect the specific instance and do not persist. ```php CreateUserAction::make() ->before(fn () => Log::info('single before')) ->after(fn () => Log::info('single after')) ->run($data); // Next call — without these hooks CreateUserAction::make()->run($data); ``` -------------------------------- ### Caching External API Requests with Memoization Source: https://github.com/lemax10/simple-actions/blob/main/README.md Shows how to cache responses from external API calls using memoization. Subsequent calls within the same request lifecycle with the same arguments will return the cached result, preventing repeated API hits. ```php 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 ``` -------------------------------- ### Execute Action in Controller Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Dispatch an action, such as sending an email, from a controller method. The `action` helper is used to execute the specified action with provided arguments. ```php action(SendEmailAction::class, $user, 'Welcome!'); ``` -------------------------------- ### Conditional Local Events for Actions Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Use `*When` and `*Unless` helper methods to conditionally run local callbacks based on a boolean or a closure. `Unless` inverts the condition of `When`. ```php GetUserAction::make() ->beforeWhen(fn ($id) => $id > 0, fn () => Log::debug('positive id')) ->afterUnless(false, fn () => Log::debug('always after')) ->run(10); ``` -------------------------------- ### Basic in-memory memoization Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Memoize action results in memory for the current request to avoid re-execution. Subsequent calls with identical arguments will return the stored result without running the action's handle method. ```php // First call - executes handle() $user = GetUserAction::make()->memo()->run($userId); // Repeated call with the same arguments - returns the result from memory $user = GetUserAction::make()->memo()->run($userId); // handle() is not executed // Different arguments - executes handle() again $otherUser = GetUserAction::make()->memo()->run($otherUserId); ``` -------------------------------- ### Auto-Generating Cache Keys for Actions Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Automatically generate a cache key based on a provided prefix and the Action's arguments by using the `rememberAuto()` method. ```php // Key is generated automatically based on arguments $result = CalculateAction::make() ->rememberAuto('prefix', 60) ->run($value); ``` -------------------------------- ### Conditional Dependency Binding Based on Environment Source: https://github.com/lemax10/simple-actions/blob/main/README.md Bind different implementations of an interface or abstract class based on the current application environment. This allows for distinct behavior in testing, local development, and production. ```php // В 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); } } ``` -------------------------------- ### Memoization management Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Manage memoized results for specific actions or globally. Includes checking if a result is memoized, forgetting specific results, flushing all memoized results for an action, or clearing all memoization across all actions. ```php $action = GetDataAction::make(); // Check if the result is memoized if ($action->isMemoized([$userId])) { // Result is already in memory } // Forget a specific result $action->memoForget([$userId]); // Forget all results for this Action GetDataAction::memoFlush(); // Clear memoization for all Actions (rarely used) Action::memoFlushAll(); // Get the number of memoized results $count = GetDataAction::getMemoizedCount(); ``` -------------------------------- ### Cache data with tags Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Cache user data and associate it with specific tags for easier invalidation. Use 'remember' to set the cache duration. Clear cache entries by tags. ```php $result = GetUserDataAction::make() ->tags(['users', 'user-' . $userId]) ->remember('user-data-' . $userId, 60) ->run($userId); // Clear by tags Cache::tags(['users'])->flush(); ``` -------------------------------- ### Stopping Action Execution with Events Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Execution of an Action can be halted during the `beforeRun` or `running` events by returning `false` from the event listener. ```php CreateUserAction::beforeRun(function (ActionBeforeRun $event) { if ($user->isBanned()) { return false; // Will stop execution } }); ``` -------------------------------- ### Cache management methods Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Manage cached action results by checking existence, retrieving the cache key, or explicitly removing entries from the cache. ```php $action = GetDataAction::make()->remember('key', 60); // Check if exists in cache if ($action->isCached('key')) { // ... } // Get the cache key $cacheKey = $action->getCacheKey(); // Remove from cache $action->forget('key'); ``` -------------------------------- ### Idempotent Action with Dynamic Key Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Generate the idempotency key dynamically from action arguments, such as an array payload, to ensure uniqueness. ```php // Key generation from arguments $order = CreateOrderAction::make() ->idempotent(fn (array $payload) => 'order:' . $payload['external_id']) ->run($payload); ``` -------------------------------- ### Dynamic Transaction Management for Actions Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Control transaction behavior dynamically for a specific Action instance. Use `withTransaction()` to enable it and `withoutTransaction()` to disable it, overriding the `$singleTransaction` property. ```php // Enable transaction CreateUserAction::make() ->withTransaction() ->run('John', 'john@example.com'); // Disable transaction (overrides $singleTransaction) CreateUserAction::make() ->withoutTransaction() ->run('John', 'john@example.com'); ``` -------------------------------- ### Strict Typing with PHPDoc Generics for Actions Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Use PHPDoc generics to specify the return type for Actions, ensuring strict typing when using `run()` and helper functions like `action()`. ```php use App\Models\User; use LeMaX10\SimpleActions\Action; /** * @extends Action */ 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 ``` -------------------------------- ### Trigger events for memoized results Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Optionally trigger action events even when results are served from memory using `forceEvents: true`. This is useful for side effects in events like cache clearing or logging, but incurs performance overhead. ```php CreateUserAction::ran(function($event) { Log::info('User created'); // Log only on actual creation }); // First call - handle() executes, events fire $user1 = CreateUserAction::make()->memo()->run($data); // Log: "User created" // Second call - result from memory, events DO NOT fire $user2 = CreateUserAction::make()->memo()->run($data); // Log: (empty) // Events will fire even for a memoized result $user = CreateUserAction::make() ->memo(forceEvents: true) ->run($data); ``` -------------------------------- ### Constructor Injection with Dependencies Source: https://github.com/lemax10/simple-actions/blob/main/README.md Inject dependencies like Mailer and Logger directly into the Action's constructor. Laravel's service container automatically resolves these dependencies when the Action is instantiated. ```php 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'); ``` -------------------------------- ### Conditional caching Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Cache action results only when a specific condition is met or not met. Supports direct boolean conditions or closures for dynamic evaluation. ```php // Cache only if condition is true $result = GetDataAction::make() ->remember('data-key', 60) ->cacheWhen($user->isPremium()) ->run(); // With closure $result = CalculateAction::make() ->remember('calc-key', 60) ->cacheWhen(fn ($value) => $value > 100) ->run($value); // Cache if condition is NOT met $result = GetDataAction::make() ->remember('data-key', 60) ->cacheUnless($user->isAdmin()) ->run(); ``` -------------------------------- ### Cache Heavy Operations with Actions Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Cache the results of a heavy operation defined within an Action. The `rememberAuto` and `tags` methods are chained to configure caching with a key and tags. ```php $report = GenerateReportAction::make() ->rememberAuto('reports', 3600) ->tags(['reports']) ->run($from, $to); ``` -------------------------------- ### Force refresh memoized results Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Force an action to re-execute its handle method and update the memoized result, even if a valid result is already stored in memory for the current request. ```php // Save the result $data = FetchDataAction::make()->memo()->run($params); // Get the memoized result $data = FetchDataAction::make()->memo()->run($params); // Force refresh the result $freshData = FetchDataAction::make() ->memo(force: true) ->run($params); // handle() will execute again // Now memo() returns the updated result $data = FetchDataAction::make()->memo()->run($params); // Will return $freshData ``` -------------------------------- ### Automatic Transactions for Actions Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Configure an Action to always execute within a database transaction by setting the `$singleTransaction` property to `true`. ```php class CreateUserAction extends Action { // Always execute in a transaction protected bool $singleTransaction = true; protected function handle(string $name, string $email): User { return User::create(['name' => $name, 'email' => $email]); } } ``` -------------------------------- ### Forcing Event Dispatch for Memoized Actions Source: https://github.com/lemax10/simple-actions/blob/main/README.md By default, events are not dispatched for memoized results to optimize performance. This snippet shows how to use `forceEvents: true` to ensure events are triggered even for repeated calls to a memoized action, though it incurs a performance cost. ```php // С `memo(forceEvents: true)`: +50-100μs (запуск событий) ``` -------------------------------- ### Disabling Events for Action Execution Source: https://github.com/lemax10/simple-actions/blob/main/README.en.md Temporarily disable all lifecycle events for an Action's execution by wrapping the `run()` call within `withoutEvents()`. ```php CreateUserAction::withoutEvents(function () { CreateUserAction::make()->run('John', 'john@example.com'); }); ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.