solophp / request-guard
Robust request validation & authorization layer for HTTP inputs with type-safe guards
v1.2.0
2025-02-27 16:31 UTC
Requires
- php: ^8.1
- psr/http-message: ^2.0
- solophp/validator: ^2.0
README
Robust request validation & authorization layer for HTTP inputs with type-safe guards
✨ Features
- Type-safe request validation
- Field mapping from custom input names/nested structures
- Built-in authorization checks
- Multi-stage processing pipeline (
preprocess
→validate
→postprocess
) - Custom error messages
- PSR-7 compatible
- Smart request data merging (POST body > GET params)
- Immutable field definitions
- Default values access via unified API
🔗 Dependencies
- PSR-7 HTTP Message Interface (
psr/http-message
^2.0) - Solo Validator (
solophp/validator
^2.0)
📥 Installation
composer require solophp/request-guard
🚀 Quick Start
Define a Request Guard
namespace App\Requests; use Solo\RequestGuard; use Solo\RequestGuard\Field; class CreateArticleRequest extends RequestGuard { protected function fields(): array { return [ Field::for('author_email') ->mapFrom('meta.author.email') ->validate('required|email'), Field::for('title') ->validate('required|string|max:100') ->preprocess('trim'), Field::for('status') ->default('draft') ->validate('string|in:draft,published') ->postprocess(fn($v) => strtoupper($v)) ]; } protected function authorize(): bool { return $this->user()->can('create', Article::class); } }
Handle in Controller
namespace App\Controllers; use App\Requests\CreateArticleRequest; use Solo\RequestGuard\Exceptions\{ValidationException, AuthorizationException}; class ArticleController { public function store(ServerRequestInterface $request) { try { $data = (new CreateArticleRequest(new Validator()))->handle($request); Article::create($data); return response()->json(['success' => true], 201); } catch (ValidationException $e) { return response()->json(['errors' => $e->getErrors()], 422); } catch (AuthorizationException $e) { return response()->json(['message' => $e->getMessage()], 403); } } }
Working with Default Values
$guard = new CreateArticleRequest(new Validator()); // Get all defined default values $defaults = $guard->defaultValues(); // Returns: ['status' => 'draft'] // Use defaults with form initialization return view('articles.create', [ 'defaults' => $guard->defaultValues() ]);
⚙️ Field Configuration
Method | Required? | Description |
---|---|---|
Field::for(string) |
Yes | Starts field definition |
mapFrom(string) |
No | Map input from custom name/nested path |
default(mixed) |
No | Fallback value if field is missing |
validate(string) |
No | Validation rules (e.g., `required |
preprocess(callable) |
No | Transform raw input before validation |
postprocess(callable) |
No | Modify value after validation |
Processing Pipeline
- Map Input - Resolve value using
mapFrom
path - Preprocess - Clean/transform raw input
- Validate - Check against rules
- Postprocess - Final value adjustments
Example
Field::for('tags') ->mapFrom('raw_csv') ->preprocess(fn($v) => explode(',', $v)) ->validate('array|max:5') ->postprocess(fn($v) => array_unique($v));
🔄 Request Data Handling
- Nested Structures: Use dot notation (
mapFrom('contacts.user_name')
) - GET: Query parameters only
- POST/PUT/PATCH: Merged body + query (body priority)
- Files: Via
$request->getUploadedFiles()
- Defaults: Access via
$guard->defaultValues()
⚡ Error Handling
ValidationException (HTTP 422)
catch (ValidationException $e) { return ['errors' => $e->getErrors()]; // Format: ['field' => ['Error 1']] }
AuthorizationException (HTTP 403)
catch (AuthorizationException $e) { return ['message' => $e->getMessage()]; // "Unauthorized request" }
🚦 Custom Messages
protected function messages(): array { return [ 'author_email.required' => 'Author email required', 'status.in' => 'Invalid status: :value' ]; }
🛠️ Testing
public function test_nested_mapping() { $data = $request->handle( $this->createRequest('POST', '/', [ 'meta' => ['author' => ['email' => 'test@example.com']] ]) ); $this->assertEquals('test@example.com', $data['author_email']); }
⚙️ Requirements
- PHP 8.2+
📄 License
MIT - See LICENSE for details.