aegisora/guardian
最新稳定版本:v1.0.0
Composer 安装命令:
composer require aegisora/guardian
包简介
Lightweight validation rule execution orchestrator for the Aegisora ecosystem
关键字:
README 文档
README
Aegisora Guardian is a lightweight and extensible validation orchestrator for the Aegisora ecosystem.
The package provides a fluent and predictable way to execute validation rules against arbitrary values using the standardized architecture defined in aegisora/rule-contract.
Guardian focuses on:
- centralized validation execution
- structured validation results
- custom exception handling
- sequential rule pipelines
- clean and framework-agnostic architecture
The library is intentionally minimalistic while still being powerful enough to build complex domain validation layers.
✨ Features
Fluent Validation Pipeline
Guardian allows validation rules to be chained together in a readable and expressive way.
$guardian ->that($value) ->must(FirstRule::create()) ->must(SecondRule::create()) ->validate();
This makes complex validation scenarios easy to maintain and extend.
Rule-Based Validation
Validation logic is completely separated into independent rule objects implementing:
Aegisora\RuleContract\RuleInterface
This promotes:
- single responsibility
- reusable validation logic
- domain-oriented architecture
- testability
Structured Validation Flow
Guardian follows a strict validation flow:
Value → Context → Rule → Result → Exception
Each rule receives a Context object and returns a standardized Result object.
No raw booleans are used internally.
Custom Exception Handling
Every rule can define its own exception.
This enables precise domain-level error handling:
->must( EmailRule::create(), new InvalidEmailException() )
Lightweight Architecture
The package has:
- no framework dependencies
- minimal memory overhead
- simple object graph
- predictable behavior
Guardian can be used in:
- Laravel
- Symfony
- Slim
- custom PHP applications
- microservices
- domain-driven projects
Safe Rule Execution
Internal rule execution is protected from unexpected contract-level failures.
If a rule throws RuleException, Guardian converts it into:
GuardianExecutingRuleException
This keeps the validation layer consistent and isolated.
📦 Installation
Install the package via Composer:
composer require aegisora/guardian
🔗 Related Packages
Guardian is part of the broader Aegisora ecosystem.
Several ready-to-use validation rules are already implemented as separate packages and can be found in the official Aegisora repositories:
https://github.com/orgs/Aegisora/repositories
Rule repositories follow a consistent naming convention:
*-rule
Examples:
- https://github.com/Aegisora/is-callable-rule
- https://github.com/Aegisora/is-array-rule
- https://github.com/Aegisora/emptiness-rule
- https://github.com/Aegisora/scalar-equality-rule
- etc
These packages provide reusable validation logic fully compatible with Guardian and aegisora/rule-contract.
The ecosystem is designed to allow combining independent validation rules into flexible and composable validation pipelines.
📚 Requirements
Required Package
Guardian relies on:
aegisora/rule-contract
which provides:
RuleInterfaceContextResultRuleException
🚀 Core Concept
Guardian acts as a validation execution engine.
Instead of manually invoking validation logic throughout the application, Guardian centralizes the process and provides a consistent execution model.
Validation flow:
- A value is passed into Guardian
- Validation rules are attached
- Guardian creates a
Context - Rules are executed sequentially
- Every rule returns a
Result - Validation failures trigger exceptions
This architecture makes validation:
- predictable
- composable
- extensible
- easy to debug
- easy to test
🏗️ Basic Usage
Simple Validation
The simplest way to validate a value is using check().
use Aegisora\Guardian\Guardian; $guardian = new Guardian(); $guardian->check( $value, FooRule::create() );
If validation passes, execution continues normally.
If validation fails, Guardian throws:
GuardianValidationException
Validation with Custom Exception
You may provide a custom exception for validation failures.
use Aegisora\Guardian\Guardian; use App\Exceptions\InvalidValueException; $guardian = new Guardian(); $guardian->check( $value, FooRule::create(), new InvalidValueException() );
If validation fails, the provided exception will be thrown instead of the default Guardian exception.
This is especially useful in:
- domain-driven design
- business validation layers
- API validation
- application services
🔗 Fluent Validation API
Guardian supports fluent rule chaining through GuardianExecutor.
Example:
use Aegisora\Guardian\Guardian; $guardian = new Guardian(); $guardian ->that($value) ->must(FirstRule::create()) ->must(SecondRule::create()) ->validate();
Execution behavior:
- rules execute sequentially
- validation stops on first failure
- exceptions are thrown immediately
- successful rules continue pipeline execution
Multiple Rules with Different Exceptions
Each rule may define its own exception.
$guardian ->that($value) ->must( EmailRule::create(), new InvalidEmailException() ) ->must( PasswordRule::create(), new WeakPasswordException() ) ->validate();
This enables extremely precise validation semantics.
🧩 Real-World Example: User Registration Validation
Guardian can be used in an application service to validate incoming data before executing business logic.
In this example, a user registration command is validated by three independent rules:
- email must have a valid format
- password must be strong enough
- user must be at least 18 years old
Guardian does not contain validation logic itself.
It receives a value, passes it into rules through Context, executes rules one by one, and stops on the first failed rule.
Registration Command
The command contains the data required to register a user.
<?php namespace App\Registration; final class RegisterUserCommand { private string $email; private string $password; private int $age; public function __construct( string $email, string $password, int $age ) { $this->email = $email; $this->password = $password; $this->age = $age; } public function getEmail(): string { return $this->email; } public function getPassword(): string { return $this->password; } public function getAge(): int { return $this->age; } }
Email Rule
This rule checks that the command contains a valid email address.
<?php namespace App\Registration\Rules; use Aegisora\RuleContract\Models\Context; use Aegisora\RuleContract\Models\Result; use Aegisora\RuleContract\Rule; use App\Registration\RegisterUserCommand; final class ValidEmailRule extends Rule { protected function executeValidate(Context $context): Result { $command = $context->getValue(); if (!$command instanceof RegisterUserCommand) { return Result::invalid('invalid_registration_command'); } if (filter_var($command->getEmail(), FILTER_VALIDATE_EMAIL) === false) { return Result::invalid('invalid_email'); } return Result::valid(); } }
Password Rule
This rule checks that the password is at least 12 characters long and contains both an uppercase letter and a number.
<?php namespace App\Registration\Rules; use Aegisora\RuleContract\Models\Context; use Aegisora\RuleContract\Models\Result; use Aegisora\RuleContract\Rule; use App\Registration\RegisterUserCommand; final class StrongPasswordRule extends Rule { protected function executeValidate(Context $context): Result { $command = $context->getValue(); if (!$command instanceof RegisterUserCommand) { return Result::invalid('invalid_registration_command'); } $password = $command->getPassword(); if (strlen($password) < 12) { return Result::invalid('weak_password'); } if (preg_match('/[A-Z]/', $password) !== 1) { return Result::invalid('weak_password'); } if (preg_match('/[0-9]/', $password) !== 1) { return Result::invalid('weak_password'); } return Result::valid(); } }
Age Rule
This rule checks that the user is old enough to register.
<?php namespace App\Registration\Rules; use Aegisora\RuleContract\Models\Context; use Aegisora\RuleContract\Models\Result; use Aegisora\RuleContract\Rule; use App\Registration\RegisterUserCommand; final class AdultUserRule extends Rule { private const MINIMUM_AGE = 18; protected function executeValidate(Context $context): Result { $command = $context->getValue(); if (!$command instanceof RegisterUserCommand) { return Result::invalid('invalid_registration_command'); } if ($command->getAge() < self::MINIMUM_AGE) { return Result::invalid('user_is_too_young'); } return Result::valid(); } }
Application Service
The service describes the validation pipeline. Each rule may have its own domain exception.
<?php namespace App\Registration; use Aegisora\Guardian\Guardian; use App\Registration\Exceptions\InvalidEmailException; use App\Registration\Exceptions\UserIsTooYoungException; use App\Registration\Exceptions\WeakPasswordException; use App\Registration\Rules\AdultUserRule; use App\Registration\Rules\StrongPasswordRule; use App\Registration\Rules\ValidEmailRule; final class RegisterUserService { private Guardian $guardian; private UserRepository $users; public function __construct( Guardian $guardian, UserRepository $users ) { $this->guardian = $guardian; $this->users = $users; } public function register(RegisterUserCommand $command): User { $this->guardian ->that($command) ->must( new ValidEmailRule(), new InvalidEmailException() ) ->must( new StrongPasswordRule(), new WeakPasswordException() ) ->must( new AdultUserRule(), new UserIsTooYoungException() ) ->validate(); return $this->users->create( $command->getEmail(), password_hash($command->getPassword(), PASSWORD_DEFAULT), $command->getAge() ); } }
Usage
<?php use Aegisora\Guardian\Exceptions\GuardianExecutingRuleException; use App\Registration\Exceptions\InvalidEmailException; use App\Registration\Exceptions\UserIsTooYoungException; use App\Registration\Exceptions\WeakPasswordException; use App\Registration\RegisterUserCommand; try { $user = $registerUserService->register( new RegisterUserCommand( 'john@example.com', 'SecurePassword123', 25 ) ); return [ 'status' => 201, 'user_id' => $user->getId(), ]; } catch (InvalidEmailException $exception) { return [ 'status' => 422, 'error' => 'Invalid email address.', ]; } catch (WeakPasswordException $exception) { return [ 'status' => 422, 'error' => 'Password is too weak.', ]; } catch (UserIsTooYoungException $exception) { return [ 'status' => 422, 'error' => 'User must be at least 18 years old.', ]; } catch (GuardianExecutingRuleException $exception) { return [ 'status' => 500, 'error' => 'Validation rule execution failed.', ]; }
If all rules pass, the user is created.
If one rule fails, Guardian immediately stops execution and throws the exception attached to that rule.
For example, if ValidEmailRule fails, InvalidEmailException is thrown. StrongPasswordRule and AdultUserRule will not be executed.
If a rule fails internally and throws RuleException, Guardian converts it into GuardianExecutingRuleException, keeping rule execution errors separate from business validation errors.
🏛️ Architecture
Guardian contains several small and focused components.
Guardian
Main entry point for validation operations.
Responsibilities:
- starts validation process
- creates validation pipeline
- provides simple API
- delegates execution to
GuardianExecutor
Methods:
check($value, RuleInterface $rule, Throwable $exception = null): void
that($value): GuardianExecutor
GuardianExecutor
Core validation execution engine.
Responsibilities:
- stores validation rules
- creates validation context
- executes rules sequentially
- analyzes validation results
- throws exceptions
Methods:
must(RuleInterface $rule, Throwable $exception = null): self
validate(): void
GuardRule
Immutable transport object containing:
- validation rule
- optional exception
Purpose:
- encapsulates validation configuration
- keeps executor implementation clean
GuardValue
Simple wrapper around arbitrary validated value.
Purpose:
- encapsulates raw input value
- centralizes value transport
⚙️ Validation Lifecycle
Complete internal validation lifecycle:
Guardian::check()
↓
GuardianExecutor
↓
Context::create($value)
↓
Rule::validate(Context)
↓
Result
↓
Validation Analysis
↓
Exception or Success
🚨 Exceptions
Guardian defines dedicated exceptions for predictable error handling.
GuardianValidationException
Thrown when validation fails and no custom exception is provided.
Example:
throw new GuardianValidationException( $failedRuleCode );
The exception contains the failed rule code returned by Result.
GuardianExecutingRuleException
Thrown when internal rule execution fails due to contract-level exceptions.
Example scenario:
RuleException → GuardianExecutingRuleException
This protects application code from low-level rule implementation failures.
🧠 Validation Philosophy
Guardian follows several important principles.
Explicit Validation
Validation should be visible and explicit.
Bad:
if (!$value) { }
Good:
$guardian->check($value, SomeRule::create());
Domain-Oriented Exceptions
Validation errors should communicate domain meaning.
Example:
new InvalidUserEmailException()
instead of generic runtime errors.
Structured Result Objects
Rules should return standardized objects instead of booleans.
This enables:
- extensibility
- debugging
- metadata support
- consistent validation behavior
Separation of Concerns
Guardian separates:
- validation execution
- validation rules
- result analysis
- exception handling
This keeps code maintainable and scalable.
⚖️ License
This package is open-source and licensed under the MIT License. See the LICENSE for details.
🌱 Contributing
Contributions are welcome and greatly appreciated!. See the CONTRIBUTING for details.
🌟 Support
If you find this project useful, please consider giving it a star on GitHub!
It helps the project grow and motivates further development of the Aegisora ecosystem.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 1
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-06-11