Chevere Action is a PHP library that encapsulates application operations as reusable, self-validating objects. Built on the Parameter 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.
Action is available through Packagist and the repository source is at chevere/action.
composer require chevere/actionCreate an Action by implementing the ActionInterface either by using ActionTrait or extending the Action class:
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'];
}
}use Chevere\Action\Action;
class GetUserAction extends Action
{
public function __invoke(int $userId): array
{
return ['id' => $userId, 'name' => 'John'];
}
}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);
}
}Invoke an Action as you would a function:
$action = new GetUserAction();
$result = $action(1); // or $action->__invoke(1)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).
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),
);
}
}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),
);
}
}Control exactly when and how validations occur:
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());
}Validate the return value against defined rules:
public function __invoke(int $id): array
{
$result = $this->fetchUser($id);
return $this->assertReturn($result);
}Validate runtime rule coherence. Useful for checking internal state:
public function __invoke(): void
{
$this->setupDependencies();
$this->assert();
}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');
}
}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');
}
}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');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());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.
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),
);
}
}$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.
Documentation is available at chevere.org/packages/action.
Copyright Rodolfo Berrios A.
Chevere is licensed under the Apache License, Version 2.0. See LICENSE for the full license text.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.