Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
276
backups/docs-backup-20250731125004/validation/phone-number.md
Normal file
276
backups/docs-backup-20250731125004/validation/phone-number.md
Normal file
@@ -0,0 +1,276 @@
|
||||
# PhoneNumber Value Object & Validation
|
||||
|
||||
Complete phone number handling system with Value Object pattern, validation rule, and database integration.
|
||||
|
||||
## ✅ Features Implemented
|
||||
|
||||
### PhoneNumber Value Object
|
||||
- **Type-safe Phone Numbers**: Immutable readonly class with comprehensive validation
|
||||
- **Multiple Input Formats**: Supports international, national, and formatted inputs
|
||||
- **Smart Normalization**: Automatic cleaning and formatting of phone number strings
|
||||
- **Country Code Detection**: Automatic extraction of country codes from international numbers
|
||||
- **Mobile Detection**: Heuristic detection of mobile vs. landline numbers
|
||||
- **Multiple Output Formats**: E.164, International, and National formatting
|
||||
|
||||
### Phone Validation Rule
|
||||
- **Attribute-based Validation**: `#[Phone]` attribute for automatic validation
|
||||
- **Framework Integration**: Works with existing validation system
|
||||
- **Flexible Validation**: Allows empty values (use with `#[Required]` if needed)
|
||||
- **Custom Messages**: Customizable error messages
|
||||
- **Type Safety**: Validates input types and formats
|
||||
|
||||
### Database Integration
|
||||
- **TypeCaster Support**: Automatic conversion between database strings and PhoneNumber objects
|
||||
- **Null Handling**: Proper handling of null and empty values
|
||||
- **Round-trip Conversion**: Preserves formatting through database operations
|
||||
- **Type Safety**: Ensures only valid PhoneNumber instances are stored
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Value Object Usage
|
||||
```php
|
||||
use App\Domain\Common\ValueObject\PhoneNumber;
|
||||
|
||||
// Create from various formats
|
||||
$phone1 = PhoneNumber::from('+49 123 456789');
|
||||
$phone2 = PhoneNumber::parse('+49-123-456-789'); // Normalizes formatting
|
||||
$phone3 = PhoneNumber::from('0123 456789'); // National format
|
||||
|
||||
// Validation
|
||||
$isValid = PhoneNumber::isValid('+49 123 456789'); // true
|
||||
$isValid = PhoneNumber::isValid('abc'); // false
|
||||
|
||||
// Information extraction
|
||||
$countryCode = $phone1->getCountryCode(); // '49'
|
||||
$national = $phone1->getNationalNumber(); // ' 123 456789'
|
||||
$isMobile = $phone1->isMobile(); // false (German landline)
|
||||
|
||||
// Formatting
|
||||
$e164 = $phone1->toE164(); // '+49123456789'
|
||||
$international = $phone1->toInternational(); // '+49 123 456 789'
|
||||
$national = $phone1->toNational(); // '0123 456 789'
|
||||
|
||||
// Comparison
|
||||
$phone4 = PhoneNumber::from('+49-123-456-789');
|
||||
$isEqual = $phone1->equals($phone4); // true (same number)
|
||||
$sameCountry = $phone1->isSameCountry($phone4); // true
|
||||
```
|
||||
|
||||
### Validation Rule Usage
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Phone;
|
||||
use App\Framework\Validation\Rules\Required;
|
||||
|
||||
class CustomerRequest
|
||||
{
|
||||
#[Required]
|
||||
#[Phone('Please enter a valid phone number.')]
|
||||
public ?string $phone = null;
|
||||
|
||||
#[Phone] // Optional phone field
|
||||
public ?string $mobile = null;
|
||||
}
|
||||
|
||||
// Usage in validation
|
||||
$validator = new Validator();
|
||||
$request = new CustomerRequest();
|
||||
$request->phone = '+49 123 456789'; // Valid
|
||||
$request->mobile = 'invalid'; // Invalid
|
||||
|
||||
$errors = $validator->validate($request);
|
||||
// Returns validation errors for invalid phone numbers
|
||||
```
|
||||
|
||||
### Database Integration
|
||||
```php
|
||||
use App\Framework\Database\Attributes\Column;
|
||||
use App\Domain\Common\ValueObject\PhoneNumber;
|
||||
|
||||
class Customer
|
||||
{
|
||||
#[Column(type: PhoneNumber::class)]
|
||||
public ?PhoneNumber $phone = null;
|
||||
}
|
||||
|
||||
// Entity usage
|
||||
$customer = new Customer();
|
||||
$customer->phone = PhoneNumber::from('+49 123 456789');
|
||||
|
||||
// EntityManager automatically handles conversion
|
||||
$entityManager->save($customer); // Stores as string: "+49 123 456789"
|
||||
$loaded = $entityManager->find(Customer::class, $id);
|
||||
// $loaded->phone is automatically converted back to PhoneNumber object
|
||||
```
|
||||
|
||||
## Supported Phone Number Formats
|
||||
|
||||
### International Formats
|
||||
```php
|
||||
PhoneNumber::from('+49 123 456789'); // German
|
||||
PhoneNumber::from('+1 (555) 123-4567'); // US with formatting
|
||||
PhoneNumber::from('+33 1 23 45 67 89'); // French
|
||||
PhoneNumber::from('+44 20 7946 0958'); // UK London
|
||||
```
|
||||
|
||||
### National Formats
|
||||
```php
|
||||
PhoneNumber::from('0123 456789'); // German national
|
||||
PhoneNumber::from('(555) 123-4567'); // US national
|
||||
PhoneNumber::from('030 12345678'); // German Berlin
|
||||
```
|
||||
|
||||
### Flexible Input Processing
|
||||
```php
|
||||
// All of these create equivalent PhoneNumber objects:
|
||||
$formats = [
|
||||
'+49 123 456789',
|
||||
'+49-123-456-789',
|
||||
'+49.123.456.789',
|
||||
'+49 (123) 456-789',
|
||||
'+49 123 456 789', // Multiple spaces
|
||||
];
|
||||
|
||||
foreach ($formats as $format) {
|
||||
$phone = PhoneNumber::parse($format);
|
||||
echo $phone->toE164(); // Always: '+49123456789'
|
||||
}
|
||||
```
|
||||
|
||||
## Country-Specific Features
|
||||
|
||||
### German Phone Numbers
|
||||
```php
|
||||
$landline = PhoneNumber::from('+49 30 12345678'); // Berlin landline
|
||||
$mobile = PhoneNumber::from('+49 151 12345678'); // Mobile (15x prefix)
|
||||
|
||||
echo $landline->isMobile(); // false
|
||||
echo $mobile->isMobile(); // true
|
||||
|
||||
echo $landline->toNational(); // '030 123 456 78'
|
||||
echo $mobile->toNational(); // '0151 123 456 78'
|
||||
```
|
||||
|
||||
### US Phone Numbers
|
||||
```php
|
||||
$usPhone = PhoneNumber::from('+1 555 123 4567');
|
||||
|
||||
echo $usPhone->getCountryCode(); // '1'
|
||||
echo $usPhone->isMobile(); // true (heuristic: 10 digits)
|
||||
echo $usPhone->toInternational(); // '+1 (555) 123-4567'
|
||||
echo $usPhone->toNational(); // '(555) 123-4567'
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Validation Errors
|
||||
```php
|
||||
try {
|
||||
$phone = PhoneNumber::from('');
|
||||
} catch (InvalidArgumentException $e) {
|
||||
echo $e->getMessage(); // "Phone number cannot be empty."
|
||||
}
|
||||
|
||||
try {
|
||||
$phone = PhoneNumber::from('123');
|
||||
} catch (InvalidArgumentException $e) {
|
||||
echo $e->getMessage(); // "Phone number too short: 123"
|
||||
}
|
||||
|
||||
try {
|
||||
$phone = PhoneNumber::from('not-a-phone');
|
||||
} catch (InvalidArgumentException $e) {
|
||||
echo $e->getMessage(); // "Phone number must contain digits: not-a-phone"
|
||||
}
|
||||
```
|
||||
|
||||
### Safe Validation
|
||||
```php
|
||||
// Non-throwing validation
|
||||
$isValid = PhoneNumber::isValid('+49 123 456789'); // true
|
||||
$isValid = PhoneNumber::isValid('invalid'); // false
|
||||
|
||||
// Use in validation logic
|
||||
if (PhoneNumber::isValid($userInput)) {
|
||||
$phone = PhoneNumber::from($userInput);
|
||||
// Process valid phone number
|
||||
} else {
|
||||
// Handle invalid input
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Coverage
|
||||
|
||||
### Comprehensive Test Suite
|
||||
- **19 test cases** covering all PhoneNumber functionality
|
||||
- **10 test cases** for TypeCaster database integration
|
||||
- **8 test cases** for Phone validation rule
|
||||
- **Edge cases**: Empty values, format variations, error conditions
|
||||
- **Real-world scenarios**: International numbers, mobile detection, formatting
|
||||
|
||||
### Test Examples
|
||||
```php
|
||||
// All tests pass with comprehensive assertions
|
||||
describe('PhoneNumber Value Object', function () {
|
||||
it('handles various input formats', function () {
|
||||
$formats = [
|
||||
'+49 123 456789',
|
||||
'+49-123-456-789',
|
||||
'+49.123.456.789',
|
||||
'0123 456789',
|
||||
];
|
||||
|
||||
foreach ($formats as $format) {
|
||||
$phone = PhoneNumber::parse($format);
|
||||
expect($phone)->toBeInstanceOf(PhoneNumber::class);
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Integration with Existing Systems
|
||||
|
||||
### Migration from String Phone Fields
|
||||
```php
|
||||
// Before: Simple string validation
|
||||
class CustomerRequest {
|
||||
public ?string $phone = null;
|
||||
}
|
||||
|
||||
// After: Type-safe PhoneNumber with comprehensive validation
|
||||
class CustomerRequest {
|
||||
#[Phone]
|
||||
public ?string $phone = null; // Still string for form input
|
||||
}
|
||||
|
||||
// Entity level: Convert to Value Object
|
||||
class Customer {
|
||||
#[Column(type: PhoneNumber::class)]
|
||||
public ?PhoneNumber $phone = null; // Type-safe storage
|
||||
}
|
||||
```
|
||||
|
||||
### Backward Compatibility
|
||||
```php
|
||||
// Existing code continues to work
|
||||
$phoneString = '+49 123 456789';
|
||||
|
||||
// New type-safe approach
|
||||
$phoneObject = PhoneNumber::from($phoneString);
|
||||
$backToString = (string) $phoneObject; // '+49 123 456789'
|
||||
```
|
||||
|
||||
## Performance & Best Practices
|
||||
|
||||
### Efficient Usage
|
||||
- **Normalization**: Parse method handles formatting variations efficiently
|
||||
- **Validation**: Use `isValid()` for non-throwing validation in user input
|
||||
- **Comparison**: Use `equals()` method for accurate phone number comparison
|
||||
- **Formatting**: Choose appropriate format method for display context
|
||||
|
||||
### Framework Integration
|
||||
- **Validation**: Combine with `#[Required]` for mandatory fields
|
||||
- **Database**: TypeCaster handles conversion automatically
|
||||
- **Forms**: Accept string input, validate with `#[Phone]`, convert to object in entity
|
||||
- **APIs**: Format output with appropriate method (`toE164()` for APIs, `toInternational()` for display)
|
||||
|
||||
The PhoneNumber implementation provides a complete solution for phone number handling in the framework, ensuring type safety, validation, and proper formatting throughout the application.
|
||||
Reference in New Issue
Block a user