# Action
# Summary
Chevere Action is a PHP library that encapsulates application operations as reusable, self-validating objects. Built on the Parameter (opens new window) library, it implements the Action Design Pattern to enforce strict input/output validation at the class level, reducing runtime errors and improving code reliability with minimal boilerplate.
# Installing
Action is available through Packagist (opens new window) and the repository source is at chevere/action (opens new window).
composer require chevere/action
# Quick Start
# Creating an Action
Create an Action by implementing the ActionInterface either by using ActionTrait or extending the Action class:
# Using ActionTrait
use Chevere\Action\Interfaces\ActionInterface;
use Chevere\Action\Traits\ActionTrait;
class GetUserAction implements ActionInterface
{
use ActionTrait;
public function __invoke(int $userId): array
{
return ['id' => $userId, 'name' => 'John'];
}
}
# Extending Action
use Chevere\Action\Action;
class GetUserAction extends Action
{
public function __invoke(int $userId): array
{
return ['id' => $userId, 'name' => 'John'];
}
}
# Adding Validation
Enhance Actions with input and output validation using attributes from the Parameter library:
use Chevere\Action\Action;
use Chevere\Parameter\Attributes\_arrayp;
use Chevere\Parameter\Attributes\_int;
use Chevere\Parameter\Attributes\_string;
use Chevere\Parameter\Attributes\_return;
class GetUserAction extends Action
{
#[_return(
new _arrayp(
userId: new _int(min: 1),
name: new _string(),
)
)]
public function __invoke(
#[_int(min: 1)]
int $userId
): array {
$this->assertArguments($userId);
$result = ['id' => $userId, 'name' => 'John'];
return $this->assertReturn($result);
}
}
# Invoking an Action
Invoke an Action as you would a function:
$action = new GetUserAction();
$result = $action(1); // or $action->__invoke(1)
# Advanced Usage
# Parameter Validation Methods
Define validation rules using dedicated methods for more control and flexibility. This is especially useful when validation rules cannot be expressed as attribute attributes (literal values only).
# Using acceptParameters()
Define input validation rules centrally:
use Chevere\Action\Action;
use Chevere\Parameter\Interfaces\ParametersInterface;
use function Chevere\Parameter\parameters;
use function Chevere\Parameter\string;
use function Chevere\Parameter\int;
class CreatePostAction extends Action
{
public function __invoke(
string $title,
string $content,
int $authorId
): array {
$this->assertArguments($title, $content, $authorId);
return ['id' => 1];
}
public static function acceptParameters(): ParametersInterface
{
return parameters(
title: string(minLength: 5, maxLength: 200),
content: string(minLength: 10),
authorId: int(min: 1),
);
}
}
# Using acceptReturn()
Define output validation rules:
use Chevere\Action\Action;
use Chevere\Parameter\Interfaces\ParameterInterface;
use function Chevere\Parameter\arrayOf;
use function Chevere\Parameter\int;
class GetUserAction extends Action
{
public function __invoke(int $userId): array
{
$this->assertArguments($userId);
$result = ['id' => $userId, 'name' => 'John'];
return $this->assertReturn($result);
}
public static function acceptReturn(): ParameterInterface
{
return arrayOf(
int(key: 'id', min: 1),
string(key: 'name', minLength: 1),
);
}
}
# Assertion Methods
Control exactly when and how validations occur:
# assertArguments()
Validate input against defined rules. Can be called with explicit arguments or automatically from the caller context:
// Automatic extraction from caller context
public function __invoke($foo, $bar) {
$this->assertArguments();
}
// Explicit arguments
public function __invoke($foo, $bar) {
$this->assertArguments($foo, $bar);
}
// Using get_defined_vars()
public function __invoke($foo, $bar) {
$this->assertArguments(...get_defined_vars());
}
# assertReturn()
Validate the return value against defined rules:
public function __invoke(int $id): array
{
$result = $this->fetchUser($id);
return $this->assertReturn($result);
}
# assert()
Validate runtime rule coherence. Useful for checking internal state:
public function __invoke(): void
{
$this->setupDependencies();
$this->assert();
}
# Advanced Rules
# Static Rules with acceptRulesStatic()
Enforce design rules at class definition time. Called before any assertions:
public static function acceptRulesStatic(): void
{
$reflection = static::reflection();
// Enforce that parameter $password is never used
if ($reflection->parameters()->has('password')) {
throw new LogicException('Password parameter not allowed');
}
}
# Runtime Rules with acceptRulesRuntime()
Enforce rules based on instance state. Called before assertions at invocation time:
private array $config = [];
public function acceptRulesRuntime(): void
{
if (empty($this->config)) {
throw new RuntimeException('Configuration required before invocation');
}
}
# Action Setup Method
Use setUp() to initialize an Action before invocation:
class SendEmailAction extends Action
{
private SmtpConfig $smtpConfig;
public function setUp(SmtpConfig $config): void
{
$this->smtpConfig = $config;
}
public function __invoke(string $email): bool
{
$this->acceptRulesRuntime();
return mail($email, 'Hello', 'World');
}
public function acceptRulesRuntime(): void
{
if (!isset($this->smtpConfig)) {
throw new RuntimeException('SMTP configuration required');
}
}
}
// Usage
$action = new SendEmailAction();
$action->setUp($smtpConfig);
$result = $action('user@example.com');
# ActionName
Store and manage Action instances with their setup arguments using ActionName:
use Chevere\Action\ActionName;
$actionName = new ActionName(
SendEmailAction::class,
$smtpConfig
);
// Later: reconstruct and invoke
$className = (string) $actionName;
$action = new $className();
$action->setUp(...$actionName->arguments());
$action('user@example.com');
Or use a factory method on your Action:
class SendEmailAction extends Action
{
public static function configure(SmtpConfig $config): ActionNameInterface
{
return new ActionName(static::class, $config);
}
}
// Usage
$configured = SendEmailAction::configure($smtpConfig);
$action = new (string)$configured();
$action->setUp(...$configured->arguments());
# Reflection Access
Access Action metadata using reflection():
$action = new MyAction();
// Get parameter definitions
$parameters = $action::reflection()->parameters();
// Get return definition
$return = $action::reflection()->return();
// Check if parameter exists
if ($parameters->has('userId')) {
// ...
}
// Iterate parameters
foreach ($parameters as $name => $parameter) {
echo $name . ': ' . get_class($parameter);
}
The Controller is a specialized Action for handling command-like instructions. Unlike standard Actions that accept any type of parameters, Controllers restrict parameters to scalar types: string, int, and float.
Controllers are ideal for handling user input from APIs, CLI commands, or form submissions where all arguments arrive as scalar values.
# Creating a Controller
Extend the Controller class to create a compliant Controller:
use Chevere\Action\Controller;
use Chevere\Parameter\Interfaces\ParametersInterface;
use function Chevere\Parameter\parameters;
use function Chevere\Parameter\string;
use function Chevere\Parameter\int;
class CreatePostController extends Controller
{
public function __invoke(
string $title,
string $content,
int $authorId
): array {
$this->assertArguments($title, $content, $authorId);
return [
'id' => 1,
'title' => $title,
'author' => $authorId
];
}
public static function acceptParameters(): ParametersInterface
{
return parameters(
title: string(minLength: 5, maxLength: 200),
content: string(minLength: 10),
authorId: int(min: 1),
);
}
}
# Usage Example
$controller = new CreatePostController();
$result = $controller(
title: 'My First Post',
content: 'This is great content.',
authorId: 42
);
// Returns: ['id' => 1, 'title' => 'My First Post', 'author' => 42]
Note: Controllers enforce type validation at the class level. Any parameter with a type other than string|int|float will trigger a validation error during class initialization.