dzentota / respected-typedvalue
A PHP 7.4 typed values (value objects) helper library.
dev-master
2025-07-06 19:22 UTC
Requires
- php: ^7.4 || ^8.0
- dzentota/typedvalue: dev-master
- respect/validation: ^2.0
Requires (Dev)
- phpunit/phpunit: ^6.2 || ^9.3
This package is auto-updated.
Last update: 2025-07-06 19:22:21 UTC
README
A PHP library for creating typed values (Value Objects) with integrated validation via Respect\Validation. Built according to the principles of the Application Security Manifesto to ensure secure data processing.
Features
- 🔒 Secure Validation - Integration with Respect\Validation for reliable data verification
- 🎯 Typed Values - Create Value Objects with automatic validation
- 📋 Detailed Reporting - Get comprehensive error information from validation
- 🔄 Flexible Architecture - Support for simple and composite data types
- 🛡️ Fail-fast Principle - Immediate failure on invalid data
Requirements
- PHP 7.4 or higher
- dzentota/typedvalue - base typed values library
- respect/validation - validation library
Installation
composer require dzentota/respected-typedvalue
Quick Start
Simple Example
<?php use dzentota\TypedValue\RespectedTypedValue; use dzentota\TypedValue\Typed; use Respect\Validation\Validator; class Email implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create()->notEmpty()->email(); } } // Create object with validation $email = Email::fromNative('user@example.com'); echo $email->toNative(); // user@example.com // Attempt to create with invalid data try { $invalidEmail = Email::fromNative('invalid-email'); } catch (ValidationException $e) { echo $e->getMessage(); // "Email" cannot be created from "invalid-email" }
Validation Without Exceptions
// Validation check $result = Email::validate('user@example.com'); if ($result->success()) { echo 'Email is valid'; } else { foreach ($result->getErrors() as $error) { echo $error->getMessage(); } } // Safe object creation $email = null; $result = null; if (Email::tryParse('user@example.com', $email, $result)) { echo 'Email created: ' . $email->toNative(); } else { echo 'Validation errors:'; foreach ($result->getErrors() as $error) { echo '- ' . $error->getMessage(); } }
Usage Examples
Basic Data Types
<?php use dzentota\TypedValue\RespectedTypedValue; use dzentota\TypedValue\Typed; use Respect\Validation\Validator; // Email address class Email implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create()->notEmpty()->email(); } } // Phone number class PhoneNumber implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create()->notEmpty()->phone(); } } // URL class WebsiteUrl implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create()->notEmpty()->url(); } } // Password with security requirements class SecurePassword implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create() ->notEmpty() ->length(8, 128) ->regex('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/') ->setName('Password'); } }
Numeric Types
// Age class Age implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create() ->notEmpty() ->intType() ->between(0, 150); } } // Price class Price implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create() ->notEmpty() ->floatType() ->positive(); } } // Discount percentage class DiscountPercent implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create() ->notEmpty() ->intType() ->between(0, 100); } }
Composite Types
use dzentota\TypedValue\CompositeValue; // User address class UserAddress implements Typed { use CompositeValue; private Street $street; private City $city; private PostalCode $postalCode; private Country $country; } class Street implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create() ->notEmpty() ->stringType() ->length(1, 200); } } class City implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create() ->notEmpty() ->stringType() ->length(1, 100) ->regex('/^[a-zA-Z\s\-]+$/'); } } class PostalCode implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create() ->notEmpty() ->stringType() ->regex('/^\d{5}(-\d{4})?$/'); // US ZIP code format } } class Country implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create() ->notEmpty() ->stringType() ->countryCode(); } } // Usage $address = UserAddress::fromNative([ 'street' => '123 Main Street', 'city' => 'New York', 'postalCode' => '10001', 'country' => 'US' ]);
Custom Validators
// Tax ID Number class TaxId implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create() ->notEmpty() ->stringType() ->regex('/^\d{2}-\d{7}$/') ->callback([self::class, 'validateTaxId']); } public static function validateTaxId(string $taxId): bool { // Custom validation logic for tax ID return true; // Simplified validation } } // Bank Account Number class BankAccount implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create() ->notEmpty() ->stringType() ->regex('/^\d{10,12}$/') ->callback([self::class, 'validateBankAccount']); } public static function validateBankAccount(string $account): bool { // Bank account validation algorithm return true; // Simplified validation } }
Error Handling
Getting Detailed Error Information
class ComplexValidator implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create() ->notEmpty() ->stringType() ->length(5, 50) ->alnum() ->setName('Username'); } } // Validation with detailed errors $result = ComplexValidator::validate('ab!'); if ($result->fails()) { foreach ($result->getErrors() as $error) { echo "Field: " . ($error->getField() ?? 'unknown') . "\n"; echo "Error: " . $error->getMessage() . "\n"; echo "---\n"; } }
Form Integration
class ContactForm { private Email $email; private PhoneNumber $phone; private string $message; public function validate(array $data): ValidationResult { $result = new ValidationResult(); // Email validation $emailResult = Email::validate($data['email'] ?? ''); if ($emailResult->fails()) { foreach ($emailResult->getErrors() as $error) { $result->addError($error->getMessage(), 'email'); } } // Phone validation $phoneResult = PhoneNumber::validate($data['phone'] ?? ''); if ($phoneResult->fails()) { foreach ($phoneResult->getErrors() as $error) { $result->addError($error->getMessage(), 'phone'); } } // Message validation if (empty($data['message'])) { $result->addError('Message cannot be empty', 'message'); } return $result; } }
Best Practices
1. Single Responsibility Principle
// ✅ Correct - each class handles one data type class Email implements Typed { /* ... */ } class PhoneNumber implements Typed { /* ... */ } // ❌ Incorrect - one class for different data types class ContactInfo implements Typed { /* ... */ }
2. Descriptive Names
// ✅ Correct class UserAge implements Typed { /* ... */ } class ProductPrice implements Typed { /* ... */ } // ❌ Incorrect class IntValue implements Typed { /* ... */ } class StringValue implements Typed { /* ... */ }
3. Composition Over Inheritance
// ✅ Correct - using composition class User implements Typed { use CompositeValue; private UserId $id; private Email $email; private UserAge $age; } // ❌ Incorrect - multiple inheritance class User extends Email { /* ... */ }
4. Data Security
// ✅ Correct - strict validation class CreditCardNumber implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create() ->notEmpty() ->stringType() ->regex('/^\d{13,19}$/') ->creditCard(); } } // ❌ Incorrect - weak validation class CreditCardNumber implements Typed { use RespectedTypedValue; public static function getValidator(): Validator { return Validator::create()->notEmpty(); } }
Security Principles Compliance
This library implements the following principles from the Application Security Manifesto:
- Rule #2: The Parser's Prerogative (Least Computational Power Principle) - Data is parsed into typed objects with minimal computational overhead
- Rule #3: Forget-me-not – Preserving Data Validity - Data validity is preserved through the type system
- Rule #5: The Principle of Pruning (Least Privilege) - Only expose necessary methods and minimize attack surface
Performance
The library is optimized for the following scenarios:
- Object Creation: ~0.1ms for simple types
- Validation: ~0.05ms for basic rules
- Composite Types: ~0.5ms for objects with 5-10 fields
- Memory: ~1KB per object on average
Testing
# Run tests composer test # Run tests with coverage composer test-coverage
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Create a Pull Request
License
MIT License. See the LICENSE file for details.
Author
Alex Tatulchenkov - AppSec Leader | Enterprise Web Defender
- GitHub: @dzentota
- Email: webtota@gmail.com
Related Projects
- TypedValue - Base typed values library
- AppSecManifesto - Application Security Manifesto
- Respect/Validation - Powerful validation library for PHP