dzentota / domain-primitives
A collection of reusable domain primitives for PHP applications
Requires
- php: ^8.1
- dzentota/respected-typedvalue: dev-master
Requires (Dev)
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2025-07-09 21:13:47 UTC
README
A comprehensive collection of reusable domain primitives for PHP applications, built on the robust dzentota/typedvalue library. This package provides secure, validated, and immutable value objects that follow the "Parse, Don't Validate" principle from the AppSec Manifesto.
Features
- ๐ฏ Type-safe value objects with automatic validation
- ๐ Immutable by design - values cannot be changed after creation
- โ Comprehensive validation with detailed error reporting
- ๐ TryParse pattern - safe parsing without exceptions
- ๐ Domain-specific validation - tailored rules for each primitive type
- ๐ Rich domain models - extensive utility methods for each primitive
- ๐งช Production ready - battle-tested validation logic
Installation
composer require dzentota/domain-primitives
Requirements
- PHP 8.1 or higher
- dzentota/typedvalue ^1.0
Available Domain Primitives
Network
- Port - Network port validation (1-65535) with type detection
- ServiceUrl - HTTP/HTTPS URL validation with parsing utilities
Common
- Email - Email address validation with normalization and obfuscation
Configuration
- FeatureFlag - Boolean flag parsing with multiple format support
Database
- DatabaseDsn - Database connection string validation and parsing
Identity
- UuidId - UUID validation with version detection and generation
Quick Start
Basic Usage
use DomainPrimitives\Network\Port; use DomainPrimitives\Common\Email; use DomainPrimitives\Configuration\FeatureFlag; // Create from valid input $port = Port::fromNative(3000); echo $port->toInt(); // 3000 echo $port->isWellKnown() ? 'Well-known' : 'Not well-known'; // Not well-known // Safe parsing without exceptions if (Email::tryParse('user@example.com', $email, $validationResult)) { echo "Valid email: " . $email->getDomain(); // example.com } else { echo "Invalid: " . $validationResult->getFirstError()->getMessage(); } // Feature flag parsing $flag = FeatureFlag::fromNative('enabled'); echo $flag->isEnabled() ? 'Feature is on' : 'Feature is off'; // Feature is on
Domain Primitives Reference
Network\Port
Validates network ports (1-65535) with additional port type detection:
use DomainPrimitives\Network\Port; $port = Port::fromNative(443); echo $port->toInt(); // 443 echo $port->isWellKnown(); // false (443 > 1023) echo $port->isRegistered(); // true (1024-49151) echo $port->isDynamic(); // false (49152-65535) echo $port->isSecure(); // true (HTTPS port)
Network\ServiceUrl
HTTP/HTTPS URL validation with rich parsing capabilities:
use DomainPrimitives\Network\ServiceUrl; $url = ServiceUrl::fromNative('https://api.example.com:8080/v1/users'); echo $url->getScheme(); // https echo $url->getHost(); // api.example.com echo $url->getPort(); // 8080 echo $url->getPath(); // /v1/users echo $url->isSecure(); // true echo $url->getEffectivePort(); // 8080 // Immutable transformations $newUrl = $url->withPath('/v2/users'); $queryUrl = $url->withQuery('limit=10&offset=0');
Common\Email
Email validation with normalization and utility features:
use DomainPrimitives\Common\Email; $email = Email::fromNative('John.Doe+newsletter@gmail.com'); echo $email->getLocalPart(); // John.Doe+newsletter echo $email->getDomain(); // gmail.com echo $email->isPersonal(); // true (gmail.com is personal) echo $email->isBusiness(); // false // Gmail normalization (removes dots, plus addressing) $normalized = $email->normalize(); echo $normalized->toString(); // johndoe@gmail.com // Privacy protection echo $email->obfuscate(); // J*******************m@gmail.com
Configuration\FeatureFlag
Smart boolean parsing supporting multiple formats:
use DomainPrimitives\Configuration\FeatureFlag; // Supports: true/false, 1/0, yes/no, on/off, enabled/disabled $flag1 = FeatureFlag::fromNative('enabled'); $flag2 = FeatureFlag::fromNative('1'); $flag3 = FeatureFlag::fromNative(true); echo $flag1->isEnabled(); // true echo $flag1->toBool(); // true echo $flag2->isDisabled(); // false
Database\DatabaseDsn
Database connection string validation and parsing:
use DomainPrimitives\Database\DatabaseDsn; $dsn = DatabaseDsn::fromNative('mysql:host=localhost;dbname=myapp;port=3306'); echo $dsn->getDriver(); // mysql echo $dsn->getHost(); // localhost echo $dsn->getPort(); // 3306 echo $dsn->getDatabaseName(); // myapp echo $dsn->getDefaultPort(); // 3306 echo $dsn->isFileDatabase(); // false // Special case handling $sqlite = DatabaseDsn::fromNative('sqlite::memory:'); echo $sqlite->isInMemory(); // true
Identity\UuidId
UUID validation with version detection and utilities:
use DomainPrimitives\Identity\UuidId; // Generate new UUID $uuid = UuidId::generate(); echo $uuid->toNative(); // e.g., 550e8400-e29b-41d4-a716-446655440000 // Parse existing UUID $existing = UuidId::fromNative('550e8400-e29b-41d4-a716-446655440000'); echo $existing->getVersion(); // 4 (random) echo $existing->isNil(); // false // Format transformations echo $existing->toShort(); // 550e8400e29b41d4a716446655440000 $binary = $existing->toBinary(); // Binary representation // UUID v1 timestamp extraction (if applicable) $timestamp = $existing->getTimestamp(); // DateTimeImmutable or null
Error Handling
All domain primitives provide comprehensive error handling:
use DomainPrimitives\Network\Port; // Exception-based (for known-good input) try { $port = Port::fromNative(99999); // Invalid port } catch (ValidationException $e) { echo $e->getMessage(); print_r($e->getValidationErrors()); } // TryParse pattern (for user input) if (Port::tryParse($userInput, $port, $validationResult)) { echo "Valid port: " . $port->toInt(); } else { foreach ($validationResult->getErrors() as $error) { echo "Error: " . $error->getMessage() . "\n"; } }
Best Practices
1. Use TryParse for User Input
Always use tryParse()
when dealing with untrusted input:
// Good: Safe parsing if (Email::tryParse($_POST['email'], $email, $validationResult)) { // Process valid email } else { // Handle validation errors gracefully } // Avoid: Direct fromNative with user input try { $email = Email::fromNative($_POST['email']); // May throw } catch (ValidationException $e) { // Exception handling is less elegant }
2. Leverage Immutability
Domain primitives are immutable - transformations create new instances:
$originalUrl = ServiceUrl::fromNative('https://api.example.com/v1'); $newUrl = $originalUrl->withPath('/v2'); // Creates new instance // $originalUrl is unchanged
3. Use Rich Domain Methods
Take advantage of the rich domain-specific methods:
$email = Email::fromNative('user@gmail.com'); // Rich domain logic if ($email->isPersonal()) { $businessEmail = $email->normalize(); // Apply Gmail rules $safe = $email->obfuscate(); // For logging }
4. Validate Early and Consistently
Always validate data at the boundaries of your application:
// Validate configuration at startup $dsn = DatabaseDsn::fromNative($config['database_url']); $port = Port::fromNative($config['server_port']); // Use domain-specific validation if ($email->isPersonal()) { // Handle personal email logic }
Architecture
This library is built on the dzentota/typedvalue foundation and follows these principles:
- Parse, Don't Validate - Values are parsed into valid objects immediately
- Immutability - All values are read-only after creation
- Type Safety - Rich type system prevents many classes of bugs
- Security First - Secure validation with detailed error reporting
- Domain-Rich - Each primitive includes relevant domain methods
Contributing
Contributions are welcome! Please ensure:
- All tests pass:
composer test
- Code follows PSR-12 standards
- New primitives include comprehensive tests
- Documentation is updated
License
MIT License. See LICENSE file for details.
Related Libraries
- dzentota/typedvalue - Core typed value library
- dzentota/config-loader - Configuration management using these primitives