# 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.