diff --git a/component/Abstract/Value/Boolean.php b/component/Abstract/Value/Boolean.php index b887865..de2d236 100644 --- a/component/Abstract/Value/Boolean.php +++ b/component/Abstract/Value/Boolean.php @@ -4,10 +4,10 @@ namespace Phant\DataStructure\Abstract\Value; -abstract class Boolean +abstract readonly class Boolean { public function __construct( - public readonly bool $value + public bool $value ) { } } diff --git a/component/Abstract/Value/Decimal.php b/component/Abstract/Value/Decimal.php index b114eb1..5b4586d 100644 --- a/component/Abstract/Value/Decimal.php +++ b/component/Abstract/Value/Decimal.php @@ -4,10 +4,10 @@ namespace Phant\DataStructure\Abstract\Value; -abstract class Decimal +abstract readonly class Decimal { public function __construct( - public readonly float $value + public float $value ) { } } diff --git a/component/Abstract/Value/Integer.php b/component/Abstract/Value/Integer.php index 00484f2..527740a 100644 --- a/component/Abstract/Value/Integer.php +++ b/component/Abstract/Value/Integer.php @@ -4,10 +4,10 @@ namespace Phant\DataStructure\Abstract\Value; -abstract class Integer +abstract readonly class Integer { public function __construct( - public readonly int $value + public int $value ) { } } diff --git a/component/Abstract/Value/Varchar.php b/component/Abstract/Value/Varchar.php index d94d37c..a045b8d 100644 --- a/component/Abstract/Value/Varchar.php +++ b/component/Abstract/Value/Varchar.php @@ -6,12 +6,12 @@ use Phant\Error\NotCompliant; -abstract class Varchar +abstract readonly class Varchar { public const PATTERN = null; public function __construct( - public readonly string $value + public string $value ) { if (defined(get_class($this) . '::PATTERN') && static::PATTERN && !preg_match(static::PATTERN, $value)) { throw new NotCompliant('Value : ' . $value); diff --git a/component/Color/Hexadecimal.php b/component/Color/Hexadecimal.php index cba442e..688932c 100644 --- a/component/Color/Hexadecimal.php +++ b/component/Color/Hexadecimal.php @@ -4,9 +4,7 @@ namespace Phant\DataStructure\Color; -use Phant\Error\NotCompliant; - -class Hexadecimal extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class Hexadecimal extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/^#[0-9a-fA-F]{3}$|#[0-9a-fA-F]{6}$|#[0-9a-fA-F]{8}$/'; } diff --git a/component/Company/Euid.php b/component/Company/Euid.php new file mode 100755 index 0000000..5b42652 --- /dev/null +++ b/component/Company/Euid.php @@ -0,0 +1,79 @@ +extractParts(); + + if (!$parts->countryCode || !in_array($parts->countryCode, self::COUNTRY_CODES)) { + throw new NotCompliant('Not compliant country code'); + } + + if ($check) { + match ($parts->countryCode) { + 'FR' => new Siren($parts->companyCode), + default => null, + }; + } + } + + public function getFormatted( + bool $nonBreakingSpace = true + ): string { + $parts = $this->extractParts(); + + $euid = implode(' ', [ + $parts->countryCode, + $parts->registryCode, + $parts->separator, + self::formatCompanyCode($parts->companyCode), + ]); + + if ($nonBreakingSpace) { + $euid = str_replace(' ', "\xC2\xA0", $euid); // non breaking space + } + + return $euid; + } + + public function extractParts( + ): object { + preg_match('/^(\D{2})(\w+)(\.)(\w+)$/', $this->value, $matches); + + return (object) [ + 'countryCode' => $matches[1] ?? null, + 'registryCode' => $matches[2] ?? null, + 'separator' => $matches[3] ?? null, + 'companyCode' => $matches[4] ?? null, + ]; + } + + private static function formatCompanyCode( + string $companyCode + ): string { + return match (strlen($companyCode)) { + 5 => preg_replace('/^(\d{2})(\d{3})$/', '$1 $2', $companyCode), + 6 => preg_replace('/^(\d{3})(\d{3})$/', '$1 $2', $companyCode), + 7 => preg_replace('/^(\d{3})(\d{4})$/', '$1 $2', $companyCode), + 8 => preg_replace('/^(\d{2})(\d{3})(\d{3})$/', '$1 $2 $3', $companyCode), + 9 => preg_replace('/^(\d{3})(\d{3})(\d{3})$/', '$1 $2 $3', $companyCode), + default => $companyCode, + }; + } +} diff --git a/component/Company/Fr/CodeActivite.php b/component/Company/Fr/CodeActivite.php index f954059..f417879 100755 --- a/component/Company/Fr/CodeActivite.php +++ b/component/Company/Fr/CodeActivite.php @@ -4,7 +4,7 @@ namespace Phant\DataStructure\Company\Fr; -class CodeActivite extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class CodeActivite extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/^\d{2}\.?\d{1,2}?\w{1}?$/'; diff --git a/component/Company/Fr/Siren.php b/component/Company/Fr/Siren.php index c0744b8..147d980 100755 --- a/component/Company/Fr/Siren.php +++ b/component/Company/Fr/Siren.php @@ -6,7 +6,7 @@ use Phant\Error\NotCompliant; -class Siren extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class Siren extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/^\d{9}$/'; @@ -39,12 +39,12 @@ public static function luhnCheck( } public function getFormatted( - bool $espaceInsecable = true + bool $nonBreakingSpace = true ): string { $siren = $this->value; $siren = preg_replace('/^(\d{3})(\d{3})(\d{3})$/', '$1 $2 $3', $siren); - if ($espaceInsecable) { - $siren = str_replace(' ', "\xC2\xA0", $siren); // Espace insécable + if ($nonBreakingSpace) { + $siren = str_replace(' ', "\xC2\xA0", $siren); // non breaking space } return $siren; diff --git a/component/Company/Fr/Siret.php b/component/Company/Fr/Siret.php index 7d39479..251189d 100755 --- a/component/Company/Fr/Siret.php +++ b/component/Company/Fr/Siret.php @@ -7,7 +7,7 @@ use Phant\DataStructure\Company\Fr\Siren; use Phant\Error\NotCompliant; -class Siret extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class Siret extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/^\d{14}$/'; public const SIREN_LA_POSTE = '356000000'; @@ -31,12 +31,12 @@ public function getSiren( } public function getFormatted( - bool $espaceInsecable = true + bool $nonBreakingSpace = true ): string { $siret = $this->value; $siret = preg_replace('/^(\d{3})(\d{3})(\d{3})(\d{5})$/', '$1 $2 $3 $4', $siret); - if ($espaceInsecable) { - $siret = str_replace(' ', "\xC2\xA0", $siret); // Espace insécable + if ($nonBreakingSpace) { + $siret = str_replace(' ', "\xC2\xA0", $siret); // non breaking space } return $siret; diff --git a/component/Company/Name.php b/component/Company/Name.php index 803f479..70ebbdf 100644 --- a/component/Company/Name.php +++ b/component/Company/Name.php @@ -4,7 +4,7 @@ namespace Phant\DataStructure\Company; -class Name extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class Name extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/^.{1,}$/'; diff --git a/component/Geography/Fr/CodeCommune.php b/component/Geography/Fr/CodeCommune.php index 46ebd28..8a7bd50 100755 --- a/component/Geography/Fr/CodeCommune.php +++ b/component/Geography/Fr/CodeCommune.php @@ -4,7 +4,7 @@ namespace Phant\DataStructure\Geography\Fr; -class CodeCommune extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class CodeCommune extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/^[0-9][0-9AB][0-9]{3}$/'; diff --git a/component/Geography/Fr/CodePostal.php b/component/Geography/Fr/CodePostal.php index f0983d1..1be0998 100755 --- a/component/Geography/Fr/CodePostal.php +++ b/component/Geography/Fr/CodePostal.php @@ -4,7 +4,7 @@ namespace Phant\DataStructure\Geography\Fr; -class CodePostal extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class CodePostal extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/^\d{5}$/'; diff --git a/component/Geography/Fr/NumeroDepartement.php b/component/Geography/Fr/NumeroDepartement.php index 97f49a1..a9dd982 100755 --- a/component/Geography/Fr/NumeroDepartement.php +++ b/component/Geography/Fr/NumeroDepartement.php @@ -4,7 +4,7 @@ namespace Phant\DataStructure\Geography\Fr; -class NumeroDepartement extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class NumeroDepartement extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/^(9[7-8][0-9])|([0-9]{2})|(2[AB])$/'; diff --git a/component/Geography/GpsCoordinates.php b/component/Geography/GpsCoordinates.php index da2fb14..8ce2408 100644 --- a/component/Geography/GpsCoordinates.php +++ b/component/Geography/GpsCoordinates.php @@ -6,12 +6,12 @@ use Phant\Error\NotCompliant; -class GpsCoordinates +readonly class GpsCoordinates { // Format : WGS84 (https://en.wikipedia.org/wiki/World_Geodetic_System) final public function __construct( - public readonly float $latitude, - public readonly float $longitude + public float $latitude, + public float $longitude ) { if ($latitude > 90 || $latitude < -90 || $longitude > 180 || $longitude < -180) { throw new NotCompliant('GPS coordinates: ' . $latitude . ';' . $longitude); diff --git a/component/Id/Uuid.php b/component/Id/Uuid.php index f0a2fa6..7f8944a 100644 --- a/component/Id/Uuid.php +++ b/component/Id/Uuid.php @@ -8,7 +8,7 @@ use Ramsey\Uuid\Uuid as UuidBuilder; use Ramsey\Uuid\Exception\InvalidUuidStringException; -class Uuid extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class Uuid extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}/'; diff --git a/component/Key/Ssl.php b/component/Key/Ssl.php index 17f7bbf..118b702 100644 --- a/component/Key/Ssl.php +++ b/component/Key/Ssl.php @@ -6,11 +6,11 @@ use Phant\Error\NotCompliant; -final class Ssl +final readonly class Ssl { public function __construct( - public readonly string $private, - public readonly string $public + public string $private, + public string $public ) { } diff --git a/component/Money/Price.php b/component/Money/Price.php index 8c6597b..e4aa454 100644 --- a/component/Money/Price.php +++ b/component/Money/Price.php @@ -4,17 +4,17 @@ namespace Phant\DataStructure\Money; -class Price +readonly class Price { public function __construct( - public readonly float $amount, - public readonly ?Currency $currency, - public readonly ?string $unit + public float $amount, + public ?Currency $currency, + public ?string $unit ) { } public function getFormatted( - bool $espaceInsecable = true + bool $nonBreakingSpace = true ): string { $price = number_format($this->amount, 2, ',', ' '); @@ -26,8 +26,8 @@ public function getFormatted( $price .= '/' . $this->unit; } - if ($espaceInsecable) { - $price = str_replace(' ', "\xC2\xA0", $price); // Espace insécable + if ($nonBreakingSpace) { + $price = str_replace(' ', "\xC2\xA0", $price); // non breaking space } return $price; diff --git a/component/Number/Grade.php b/component/Number/Grade.php index 5fddfc2..91ed931 100644 --- a/component/Number/Grade.php +++ b/component/Number/Grade.php @@ -6,11 +6,11 @@ use Phant\Error\NotCompliant; -class Grade +readonly class Grade { public function __construct( - public readonly int $position, - public readonly int $scale + public int $position, + public int $scale ) { if ($position < 0) { throw new NotCompliant('Note : ' . $position); diff --git a/component/Number/Rate.php b/component/Number/Rate.php index c44bdc5..2080ddc 100644 --- a/component/Number/Rate.php +++ b/component/Number/Rate.php @@ -4,7 +4,7 @@ namespace Phant\DataStructure\Number; -class Rate extends \Phant\DataStructure\Abstract\Value\Decimal +readonly class Rate extends \Phant\DataStructure\Abstract\Value\Decimal { public function __toString( ) { diff --git a/component/Person/Birthday.php b/component/Person/Birthday.php index 62780ea..b1fba70 100644 --- a/component/Person/Birthday.php +++ b/component/Person/Birthday.php @@ -4,6 +4,6 @@ namespace Phant\DataStructure\Person; -class Birthday extends \Phant\DataStructure\Time\Date +readonly class Birthday extends \Phant\DataStructure\Time\Date { } diff --git a/component/Person/Firstname.php b/component/Person/Firstname.php index 007702d..b056585 100644 --- a/component/Person/Firstname.php +++ b/component/Person/Firstname.php @@ -6,7 +6,7 @@ use Phant\Error\NotCompliant; -class Firstname extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class Firstname extends \Phant\DataStructure\Abstract\Value\Varchar { public function __construct( string $firstname diff --git a/component/Person/Lastname.php b/component/Person/Lastname.php index 8e018af..a2bc5b6 100644 --- a/component/Person/Lastname.php +++ b/component/Person/Lastname.php @@ -6,7 +6,7 @@ use Phant\Error\NotCompliant; -class Lastname extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class Lastname extends \Phant\DataStructure\Abstract\Value\Varchar { public function __construct( string $lastname diff --git a/component/Time/Date.php b/component/Time/Date.php index 92ea771..c79b681 100644 --- a/component/Time/Date.php +++ b/component/Time/Date.php @@ -6,11 +6,11 @@ use Phant\Error\NotCompliant; -class Date +readonly class Date { - public readonly int $time; - public readonly string $date; - public readonly string $format; + public int $time; + public string $date; + public string $format; public function __construct( int|string $date, diff --git a/component/Time/DateInterval.php b/component/Time/DateInterval.php index 5efbb27..36e8773 100644 --- a/component/Time/DateInterval.php +++ b/component/Time/DateInterval.php @@ -8,19 +8,19 @@ use Phant\DataStructure\Time\Duration; use Phant\Error\NotCompliant; -class DateInterval +readonly class DateInterval { - public readonly ?Duration $duration; + public ?Duration $duration; public function __construct( - public readonly ?Date $from, - public readonly ?Date $to + public ?Date $from, + public ?Date $to ) { if (!$from && !$to) { - throw new NotCompliant('Date intervals: from ' . $from . ' to' . $to); + throw new NotCompliant('A date interval must contain at least one date'); } if ($from && $to && $from->time > $to->time) { - throw new NotCompliant('From can be after To : ' . $from . '/' . $to); + throw new NotCompliant('Invalid date interval: from date (' . $from . ') cannot be after to date (' . $to . ')'); } $this->duration = ($this->from && $this->to) ? new Duration(($this->to->time + Duration::DAY - 1) - $this->from->time) : null; diff --git a/component/Time/DateTime.php b/component/Time/DateTime.php index 33af05d..2491607 100644 --- a/component/Time/DateTime.php +++ b/component/Time/DateTime.php @@ -4,9 +4,7 @@ namespace Phant\DataStructure\Time; -use Phant\Error\NotCompliant; - -class DateTime extends \Phant\DataStructure\Time\Date +readonly class DateTime extends \Phant\DataStructure\Time\Date { public function __construct( int|string $date, diff --git a/component/Time/DateTimeInterval.php b/component/Time/DateTimeInterval.php index 83a3a78..b90f08f 100644 --- a/component/Time/DateTimeInterval.php +++ b/component/Time/DateTimeInterval.php @@ -8,19 +8,19 @@ use Phant\DataStructure\Time\Duration; use Phant\Error\NotCompliant; -class DateTimeInterval +readonly class DateTimeInterval { - public readonly ?Duration $duration; + public ?Duration $duration; public function __construct( - public readonly ?DateTime $from, - public readonly ?DateTime $to + public ?DateTime $from, + public ?DateTime $to ) { if (!$from && !$to) { - throw new NotCompliant('Date time intervals: from ' . $from . ' to' . $to); + throw new NotCompliant('A datetime interval must contain at least one date'); } if ($from && $to && $from->time > $to->time) { - throw new NotCompliant('From can be after To : ' . $from . '/' . $to); + throw new NotCompliant('Invalid datetime interval: from datetime (' . $from . ') cannot be after to datetime (' . $to . ')'); } $this->duration = ($this->from && $this->to) ? new Duration($this->to->time - $this->from->time) : null; diff --git a/component/Time/Duration.php b/component/Time/Duration.php index 794500f..e83329d 100644 --- a/component/Time/Duration.php +++ b/component/Time/Duration.php @@ -4,32 +4,19 @@ namespace Phant\DataStructure\Time; -class Duration +readonly class Duration { - // Duration in secondes - public const MINUTE = 60; - public const HOUR = 3600; - public const DAY = 86400; - public const MONTH = 2628000; - public const YEAR = 31536000; - - public const SECOND_LABEL = 's'; - public const SECOND_LABEL_PLURAL = 's'; - public const MINUTE_LABEL = 'min'; - public const MINUTE_LABEL_PLURAL = 'min'; - public const HOUR_LABEL = 'h'; - public const HOUR_LABEL_PLURAL = 'h'; - public const DAY_LABEL = 'day'; - public const DAY_LABEL_PLURAL = 'days'; - public const MONTH_LABEL = 'month'; - public const MONTH_LABEL_PLURAL = 'months'; - public const YEAR_LABEL = 'year'; - public const YEAR_LABEL_PLURAL = 'years'; - - public readonly string $label; + // Duration in seconds + public const MINUTE = 60; + public const HOUR = 3600; + public const DAY = 86400; + public const MONTH = 2628000; + public const YEAR = 31536000; + + public string $label; public function __construct( - public readonly int $value + public int $value ) { $this->label = $this->buildLabel(); } @@ -48,7 +35,7 @@ protected function buildLabel( if ($remainingTime >= self::YEAR) { $years = intval($remainingTime / self::YEAR); if ($years) { - $labels[] = $years . ' ' . ($years > 1 ? self::YEAR_LABEL_PLURAL : self::YEAR_LABEL); + $labels[] = $years . ' ' . ($years > 1 ? Unit::Year->getLabelPlural() : Unit::Year->getLabel()); $remainingTime = $remainingTime % self::YEAR; } } @@ -56,7 +43,7 @@ protected function buildLabel( if ($remainingTime >= self::MONTH) { $months = intval($remainingTime / self::MONTH); if ($months) { - $labels[] = $months . ' ' . ($months > 1 ? self::MONTH_LABEL_PLURAL : self::MONTH_LABEL); + $labels[] = $months . ' ' . ($months > 1 ? Unit::Month->getLabelPlural() : Unit::Month->getLabel()); $remainingTime = $remainingTime % self::MONTH; } } @@ -64,7 +51,7 @@ protected function buildLabel( if ($remainingTime >= self::DAY) { $days = intval($remainingTime / self::DAY); if ($days) { - $labels[] = $days . ' ' . ($days > 1 ? self::DAY_LABEL_PLURAL : self::DAY_LABEL); + $labels[] = $days . ' ' . ($days > 1 ? Unit::Day->getLabelPlural() : Unit::Day->getLabel()); $remainingTime = $remainingTime % self::DAY; } } @@ -72,7 +59,7 @@ protected function buildLabel( if ($remainingTime >= self::HOUR) { $hours = intval($remainingTime / self::HOUR); if ($hours) { - $labels[] = $hours . ' ' . ($hours > 1 ? self::HOUR_LABEL_PLURAL : self::HOUR_LABEL); + $labels[] = $hours . ' ' . ($hours > 1 ? Unit::Hour->getLabelPlural() : Unit::Hour->getLabel()); $remainingTime = $remainingTime % self::HOUR; } } @@ -80,7 +67,7 @@ protected function buildLabel( if ($remainingTime >= self::MINUTE) { $minutes = intval($remainingTime / self::MINUTE); if ($minutes) { - $labels[] = $minutes . ' ' . ($minutes > 1 ? self::MINUTE_LABEL_PLURAL : self::MINUTE_LABEL); + $labels[] = $minutes . ' ' . ($minutes > 1 ? Unit::Minute->getLabelPlural() : Unit::Minute->getLabel()); $remainingTime = $remainingTime % self::MINUTE; } } @@ -88,7 +75,7 @@ protected function buildLabel( if ($remainingTime > 0) { $secondes = intval($remainingTime); if ($secondes) { - $labels[] = $secondes . ' ' . ($secondes > 1 ? self::SECOND_LABEL_PLURAL : self::SECOND_LABEL); + $labels[] = $secondes . ' ' . ($secondes > 1 ? Unit::Second->getLabelPlural() : Unit::Second->getLabel()); } } diff --git a/component/Time/Unit.php b/component/Time/Unit.php new file mode 100644 index 0000000..7186d24 --- /dev/null +++ b/component/Time/Unit.php @@ -0,0 +1,39 @@ + 's', + self::Minute => 'min', + self::Hour => 'h', + self::Day => 'day', + self::Month => 'month', + self::Year => 'year', + }; + } + + public function getLabelPlural( + ): string { + return match ($this) { + self::Second => 's', + self::Minute => 'min', + self::Hour => 'h', + self::Day => 'days', + self::Month => 'months', + self::Year => 'years', + }; + } +} diff --git a/component/Token/ApiKey.php b/component/Token/ApiKey.php index 0ac00fd..e2079d2 100644 --- a/component/Token/ApiKey.php +++ b/component/Token/ApiKey.php @@ -4,7 +4,7 @@ namespace Phant\DataStructure\Token; -class ApiKey extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class ApiKey extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/^[0-9a-zA-Z]{8}\.[0-9a-zA-Z]{64}$/'; diff --git a/component/Token/Jwt.php b/component/Token/Jwt.php index 9bf3f0f..ebce335 100644 --- a/component/Token/Jwt.php +++ b/component/Token/Jwt.php @@ -10,7 +10,7 @@ use Firebase\JWT\ExpiredException; use Firebase\JWT\SignatureInvalidException; -class Jwt extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class Jwt extends \Phant\DataStructure\Abstract\Value\Varchar { public const PAYLOAD_CREATION_TIME = 'iat'; public const PAYLOAD_LIFE_TIME = 'exp'; diff --git a/component/Web/DomainName.php b/component/Web/DomainName.php index 60dda6e..c57b0dd 100644 --- a/component/Web/DomainName.php +++ b/component/Web/DomainName.php @@ -4,7 +4,7 @@ namespace Phant\DataStructure\Web; -class DomainName extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class DomainName extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9\-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})|localhost$/'; diff --git a/component/Web/Email.php b/component/Web/Email.php index 5553bc6..50d62db 100644 --- a/component/Web/Email.php +++ b/component/Web/Email.php @@ -7,7 +7,7 @@ use Phant\DataStructure\Web\EmailAddressAndName; use Phant\DataStructure\Web\EmailAttachmentList; -class Email +readonly class Email { final public function __construct( public string $subject, diff --git a/component/Web/EmailAddress.php b/component/Web/EmailAddress.php index 83fd623..eac373b 100644 --- a/component/Web/EmailAddress.php +++ b/component/Web/EmailAddress.php @@ -7,7 +7,7 @@ use Phant\DataStructure\Web\DomainName; use Phant\DataStructure\Web\UserName; -class EmailAddress extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class EmailAddress extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/'; diff --git a/component/Web/EmailAddressAndName.php b/component/Web/EmailAddressAndName.php index a421165..9f39df4 100644 --- a/component/Web/EmailAddressAndName.php +++ b/component/Web/EmailAddressAndName.php @@ -4,11 +4,11 @@ namespace Phant\DataStructure\Web; -class EmailAddressAndName +readonly class EmailAddressAndName { public function __construct( - public readonly EmailAddress $emailAddress, - public readonly ?string $name + public EmailAddress $emailAddress, + public ?string $name ) { } diff --git a/component/Web/EmailAttachment.php b/component/Web/EmailAttachment.php index 01342f8..acfc15a 100644 --- a/component/Web/EmailAttachment.php +++ b/component/Web/EmailAttachment.php @@ -4,7 +4,7 @@ namespace Phant\DataStructure\Web; -class EmailAttachment +readonly class EmailAttachment { final public function __construct( public string $name, diff --git a/component/Web/Url.php b/component/Web/Url.php index 4426527..dae6afa 100644 --- a/component/Web/Url.php +++ b/component/Web/Url.php @@ -4,7 +4,9 @@ namespace Phant\DataStructure\Web; -class Url extends \Phant\DataStructure\Abstract\Value\Varchar +use Phant\Error\NotCompliant; + +class Url { public const PATTERN = '%\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))%s'; @@ -18,13 +20,20 @@ class Url extends \Phant\DataStructure\Abstract\Value\Varchar protected ?string $fragment; public function __construct( - string $url + public readonly string $value ) { - parent::__construct($url); + if (defined(get_class($this) . '::PATTERN') && static::PATTERN && !preg_match(static::PATTERN, $value)) { + throw new NotCompliant('Value : ' . $value); + } $this->decompose(); } + public function __toString( + ): string { + return $this->value; + } + public function getScheme( ): ?string { return $this->scheme; diff --git a/component/Web/UserAgent/Browser.php b/component/Web/UserAgent/Browser.php index 0d929a8..de49fb2 100644 --- a/component/Web/UserAgent/Browser.php +++ b/component/Web/UserAgent/Browser.php @@ -4,11 +4,11 @@ namespace Phant\DataStructure\Web\UserAgent; -class Browser +readonly class Browser { public function __construct( - public readonly BrowserFamily $family, - public readonly Version $version + public BrowserFamily $family, + public Version $version ) { } } diff --git a/component/Web/UserAgent/OperatingSystem.php b/component/Web/UserAgent/OperatingSystem.php index 9b96749..3118d0a 100644 --- a/component/Web/UserAgent/OperatingSystem.php +++ b/component/Web/UserAgent/OperatingSystem.php @@ -4,11 +4,11 @@ namespace Phant\DataStructure\Web\UserAgent; -class OperatingSystem +readonly class OperatingSystem { public function __construct( - public readonly OperatingSystemFamily $family, - public readonly Version $version + public OperatingSystemFamily $family, + public Version $version ) { } } diff --git a/component/Web/UserAgent/OperatingSystemFamily.php b/component/Web/UserAgent/OperatingSystemFamily.php index bc77d4a..1dde805 100644 --- a/component/Web/UserAgent/OperatingSystemFamily.php +++ b/component/Web/UserAgent/OperatingSystemFamily.php @@ -12,6 +12,5 @@ enum OperatingSystemFamily: string case Android = 'Android'; case iOS = 'iOS'; case iPadOS = 'iPadOS'; - case Other = 'Other'; } diff --git a/component/Web/UserAgent/Version.php b/component/Web/UserAgent/Version.php index 39af951..4f65c53 100644 --- a/component/Web/UserAgent/Version.php +++ b/component/Web/UserAgent/Version.php @@ -6,6 +6,6 @@ use Phant\DataStructure\Abstract\Value\Varchar; -class Version extends Varchar +readonly class Version extends Varchar { } diff --git a/component/Web/UserName.php b/component/Web/UserName.php index 5056328..6c08337 100644 --- a/component/Web/UserName.php +++ b/component/Web/UserName.php @@ -4,7 +4,7 @@ namespace Phant\DataStructure\Web; -class UserName extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class UserName extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))$/i'; diff --git a/composer.json b/composer.json index 1652a81..b9a2f94 100644 --- a/composer.json +++ b/composer.json @@ -10,14 +10,14 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "phant/error": "1.*", "ramsey/uuid": "4.*", - "firebase/php-jwt": "6.*" + "firebase/php-jwt": "7.*" }, "require-dev": { "friendsofphp/php-cs-fixer": "3.*", - "phpstan/phpstan": "1.*", + "phpstan/phpstan": "2.*", "phpunit/phpunit": "12.*" }, "scripts": { diff --git a/phpunit.xml b/phpunit.xml index 23e2dfb..6fd795e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,12 +1,13 @@ - - - - test - - - - - component - - - \ No newline at end of file + + + + + test + + + + + component + + + diff --git a/test/Abstract/Fixture/Value/Boolean.php b/test/Abstract/Fixture/Value/Boolean.php index 3d06cac..c159d33 100644 --- a/test/Abstract/Fixture/Value/Boolean.php +++ b/test/Abstract/Fixture/Value/Boolean.php @@ -4,6 +4,6 @@ namespace Test\Abstract\Fixture\Value; -class Boolean extends \Phant\DataStructure\Abstract\Value\Boolean +readonly class Boolean extends \Phant\DataStructure\Abstract\Value\Boolean { } diff --git a/test/Abstract/Fixture/Value/Decimal.php b/test/Abstract/Fixture/Value/Decimal.php index 52ee0f3..9e13e5a 100644 --- a/test/Abstract/Fixture/Value/Decimal.php +++ b/test/Abstract/Fixture/Value/Decimal.php @@ -4,6 +4,6 @@ namespace Test\Abstract\Fixture\Value; -class Decimal extends \Phant\DataStructure\Abstract\Value\Decimal +readonly class Decimal extends \Phant\DataStructure\Abstract\Value\Decimal { } diff --git a/test/Abstract/Fixture/Value/Integer.php b/test/Abstract/Fixture/Value/Integer.php index 2dcc301..3089c15 100644 --- a/test/Abstract/Fixture/Value/Integer.php +++ b/test/Abstract/Fixture/Value/Integer.php @@ -4,6 +4,6 @@ namespace Test\Abstract\Fixture\Value; -class Integer extends \Phant\DataStructure\Abstract\Value\Integer +readonly class Integer extends \Phant\DataStructure\Abstract\Value\Integer { } diff --git a/test/Abstract/Fixture/Value/Varchar.php b/test/Abstract/Fixture/Value/Varchar.php index a3fb3a2..0ab30cd 100644 --- a/test/Abstract/Fixture/Value/Varchar.php +++ b/test/Abstract/Fixture/Value/Varchar.php @@ -4,7 +4,7 @@ namespace Test\Abstract\Fixture\Value; -class Varchar extends \Phant\DataStructure\Abstract\Value\Varchar +readonly class Varchar extends \Phant\DataStructure\Abstract\Value\Varchar { public const PATTERN = '/^[a-zA-Z !]{2,}$/'; } diff --git a/test/Company/EuidTest.php b/test/Company/EuidTest.php new file mode 100644 index 0000000..623bf2d --- /dev/null +++ b/test/Company/EuidTest.php @@ -0,0 +1,137 @@ +assertEquals('FR001.512747395', (string)$euid); + + $this->assertIsString($euid->value); + $this->assertEquals('FR001.512747395', $euid->value); + + $this->assertIsString($euid->getFormatted()); + $this->assertEquals('FR 001 . 512 747 395', $euid->getFormatted(false)); + } + + public function testFormattedWithoutNonBreakingSpace(): void + { + $euid = new Euid('FR001.512747395'); + + $formatted = $euid->getFormatted(false); + $this->assertEquals('FR 001 . 512 747 395', $formatted); + $this->assertStringNotContainsString("\xC2\xA0", $formatted); + } + + public function testFormattedWithNonBreakingSpace(): void + { + $euid = new Euid('FR001.512747395'); + + $formatted = $euid->getFormatted(true); + $this->assertEquals("FR\xC2\xA0001\xC2\xA0.\xC2\xA0512\xC2\xA0747\xC2\xA0395", $formatted); + } + + public function testExtractParts(): void + { + $euid = new Euid('FR001.512747395'); + $parts = $euid->extractParts(); + + $this->assertEquals('FR', $parts->countryCode); + $this->assertEquals('001', $parts->registryCode); + $this->assertEquals('.', $parts->separator); + $this->assertEquals('512747395', $parts->companyCode); + } + + public function testConstructorWithNonComplianceCheck(): void + { + // Test avec un SIREN valide français + $euid = new Euid('FR001.512747395', true); + $this->assertEquals('FR001.512747395', (string)$euid); + + // Test sans vérification de compliance + $euid = new Euid('FR001.123456789', false); + $this->assertEquals('FR001.123456789', (string)$euid); + } + + public function testInvalidCountryCode(): void + { + $this->expectException(NotCompliant::class); + $this->expectExceptionMessage('Not compliant country code'); + + new Euid('XX001.512747395'); + } + + public function testInvalidFrenchSiren(): void + { + $this->expectException(NotCompliant::class); + + // SIREN invalide avec vérification activée + new Euid('FR001.123456789', true); + } + + public function testValidEuropeanCountryCodes(): void + { + $validCountryCodes = ['AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK']; + + foreach ($validCountryCodes as $countryCode) { + $euid = new Euid("{$countryCode}001.123456789", false); + $this->assertEquals("{$countryCode}001.123456789", (string)$euid); + } + } + + public function testCompanyCodeFormatting(): void + { + // Test avec différentes longueurs de codes entreprise + $testCases = [ + ['FR001.12345', '12 345'], // 5 digits + ['FR001.123456', '123 456'], // 6 digits + ['FR001.1234567', '123 4567'], // 7 digits + ['FR001.12345678', '12 345 678'], // 8 digits + ['FR001.123456789', '123 456 789'], // 9 digits + ]; + + foreach ($testCases as [$input, $expectedFormatting]) { + $euid = new Euid($input, false); + $formatted = $euid->getFormatted(false); + $this->assertStringContainsString($expectedFormatting, $formatted); + } + } + + public function testPatternConformity(): void + { + $euid = new Euid('FR001.512747395'); + + $this->assertMatchesRegularExpression(Euid::PATTERN, $euid->value); + } + + public function testStringRepresentation(): void + { + $euid = new Euid('DE002.987654321', false); + + // Test du cast en string + $this->assertEquals('DE002.987654321', (string)$euid); + + // Test de la propriété value + $this->assertEquals('DE002.987654321', $euid->value); + } + + public function testWithDifferentRegistryCodes(): void + { + $euid1 = new Euid('FR001.512747395'); + $euid2 = new Euid('FR999.512747395'); + + $parts1 = $euid1->extractParts(); + $parts2 = $euid2->extractParts(); + + $this->assertEquals('001', $parts1->registryCode); + $this->assertEquals('999', $parts2->registryCode); + } +} diff --git a/test/Key/SslTest.php b/test/Key/SslTest.php index a0d5376..fd2eae9 100644 --- a/test/Key/SslTest.php +++ b/test/Key/SslTest.php @@ -103,14 +103,38 @@ public function testEncryptInvalid(): void { $this->expectException(NotCompliant::class); - $result = $this->fixtureInvalid->encrypt('Foo bar'); + // Set error handler to suppress expected openssl warnings + set_error_handler(function ($severity, $message, $filename, $lineno) { + if (strpos($message, 'openssl_private_encrypt') !== false) { + return true; // Suppress this warning + } + return false; // Let other errors/warnings through + }); + + try { + $result = $this->fixtureInvalid->encrypt('Foo bar'); + } finally { + restore_error_handler(); + } } public function testEncryptInvalidBis(): void { $this->expectException(NotCompliant::class); - $result = $this->fixtureInvalid->encrypt(''); + // Set error handler to suppress expected openssl warnings + set_error_handler(function ($severity, $message, $filename, $lineno) { + if (strpos($message, 'openssl_private_encrypt') !== false) { + return true; // Suppress this warning + } + return false; // Let other errors/warnings through + }); + + try { + $result = $this->fixtureInvalid->encrypt(''); + } finally { + restore_error_handler(); + } } public function testDecrypt(): void @@ -126,8 +150,20 @@ public function testDecryptInvalid(): void { $this->expectException(NotCompliant::class); - $result = $this->fixtureInvalid->decrypt( - $this->fixture->encrypt('Foo bar') - ); + // Set error handler to suppress expected openssl warnings + set_error_handler(function ($severity, $message, $filename, $lineno) { + if (strpos($message, 'openssl_public_decrypt') !== false) { + return true; // Suppress this warning + } + return false; // Let other errors/warnings through + }); + + try { + $result = $this->fixtureInvalid->decrypt( + $this->fixture->encrypt('Foo bar') + ); + } finally { + restore_error_handler(); + } } } diff --git a/test/Time/UnitTest.php b/test/Time/UnitTest.php new file mode 100644 index 0000000..5034886 --- /dev/null +++ b/test/Time/UnitTest.php @@ -0,0 +1,43 @@ +assertCount(6, $cases); + $this->assertContains(Unit::Second, $cases); + $this->assertContains(Unit::Minute, $cases); + $this->assertContains(Unit::Hour, $cases); + $this->assertContains(Unit::Day, $cases); + $this->assertContains(Unit::Month, $cases); + $this->assertContains(Unit::Year, $cases); + } + + public function testGetLabelSingular(): void + { + $this->assertEquals('s', Unit::Second->getLabel()); + $this->assertEquals('min', Unit::Minute->getLabel()); + $this->assertEquals('h', Unit::Hour->getLabel()); + $this->assertEquals('day', Unit::Day->getLabel()); + $this->assertEquals('month', Unit::Month->getLabel()); + $this->assertEquals('year', Unit::Year->getLabel()); + } + + public function testGetLabelPlural(): void + { + $this->assertEquals('s', Unit::Second->getLabelPlural()); + $this->assertEquals('min', Unit::Minute->getLabelPlural()); + $this->assertEquals('h', Unit::Hour->getLabelPlural()); + $this->assertEquals('days', Unit::Day->getLabelPlural()); + $this->assertEquals('months', Unit::Month->getLabelPlural()); + $this->assertEquals('years', Unit::Year->getLabelPlural()); + } +} diff --git a/test/Token/JwtTest.php b/test/Token/JwtTest.php index 9091f52..305374e 100644 --- a/test/Token/JwtTest.php +++ b/test/Token/JwtTest.php @@ -11,28 +11,43 @@ final class JwtTest extends \PHPUnit\Framework\TestCase { public const PRIVATE_KEY = <<