From 0ddd50060848011a2cb5ab7c8de5825be549a954 Mon Sep 17 00:00:00 2001 From: Lenny ROUANET Date: Wed, 17 Dec 2025 20:45:42 +0100 Subject: [PATCH 01/13] =?UTF-8?q?=E2=9C=A8=20Readonly=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- component/Abstract/Value/Boolean.php | 4 ++-- component/Abstract/Value/Decimal.php | 4 ++-- component/Abstract/Value/Integer.php | 4 ++-- component/Abstract/Value/Varchar.php | 4 ++-- component/Color/Hexadecimal.php | 4 +--- component/Company/Fr/CodeActivite.php | 2 +- component/Company/Fr/Siren.php | 2 +- component/Company/Fr/Siret.php | 2 +- component/Company/Name.php | 2 +- component/Geography/Fr/CodeCommune.php | 2 +- component/Geography/Fr/CodePostal.php | 2 +- component/Geography/Fr/NumeroDepartement.php | 2 +- component/Geography/GpsCoordinates.php | 6 +++--- component/Id/Uuid.php | 2 +- component/Key/Ssl.php | 6 +++--- component/Money/Price.php | 8 ++++---- component/Number/Grade.php | 6 +++--- component/Number/Rate.php | 2 +- component/Person/Birthday.php | 2 +- component/Person/Firstname.php | 2 +- component/Person/Lastname.php | 2 +- component/Time/Date.php | 8 ++++---- component/Time/DateInterval.php | 8 ++++---- component/Time/DateTime.php | 4 +--- component/Time/DateTimeInterval.php | 8 ++++---- component/Time/Duration.php | 6 +++--- component/Token/ApiKey.php | 2 +- component/Token/Jwt.php | 2 +- component/Web/DomainName.php | 2 +- component/Web/Email.php | 2 +- component/Web/EmailAddress.php | 2 +- component/Web/EmailAddressAndName.php | 6 +++--- component/Web/EmailAttachment.php | 2 +- component/Web/Url.php | 15 ++++++++++++--- component/Web/UserName.php | 2 +- test/Abstract/Fixture/Value/Boolean.php | 2 +- test/Abstract/Fixture/Value/Decimal.php | 2 +- test/Abstract/Fixture/Value/Integer.php | 2 +- test/Abstract/Fixture/Value/Varchar.php | 2 +- 39 files changed, 76 insertions(+), 71 deletions(-) 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/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..83a7a89 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}$/'; diff --git a/component/Company/Fr/Siret.php b/component/Company/Fr/Siret.php index 7d39479..874ad15 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'; 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..6ef91c0 100644 --- a/component/Key/Ssl.php +++ b/component/Key/Ssl.php @@ -6,11 +6,11 @@ use Phant\Error\NotCompliant; -final class Ssl +readonly final 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..62d7af8 100644 --- a/component/Money/Price.php +++ b/component/Money/Price.php @@ -4,12 +4,12 @@ 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 ) { } 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..2b85ced 100644 --- a/component/Time/DateInterval.php +++ b/component/Time/DateInterval.php @@ -8,13 +8,13 @@ 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); 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..2aca503 100644 --- a/component/Time/DateTimeInterval.php +++ b/component/Time/DateTimeInterval.php @@ -8,13 +8,13 @@ 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); diff --git a/component/Time/Duration.php b/component/Time/Duration.php index 794500f..c6c0eee 100644 --- a/component/Time/Duration.php +++ b/component/Time/Duration.php @@ -4,7 +4,7 @@ namespace Phant\DataStructure\Time; -class Duration +readonly class Duration { // Duration in secondes public const MINUTE = 60; @@ -26,10 +26,10 @@ class Duration public const YEAR_LABEL = 'year'; public const YEAR_LABEL_PLURAL = 'years'; - public readonly string $label; + public string $label; public function __construct( - public readonly int $value + public int $value ) { $this->label = $this->buildLabel(); } 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..f4dc418 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 + readonly public 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/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/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,}$/'; } From 9ea804b2b3e68f9631a32fd6cc215fadab99c068 Mon Sep 17 00:00:00 2001 From: Lenny ROUANET Date: Wed, 17 Dec 2025 20:47:55 +0100 Subject: [PATCH 02/13] CS --- component/Key/Ssl.php | 2 +- component/Web/Url.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/component/Key/Ssl.php b/component/Key/Ssl.php index 6ef91c0..118b702 100644 --- a/component/Key/Ssl.php +++ b/component/Key/Ssl.php @@ -6,7 +6,7 @@ use Phant\Error\NotCompliant; -readonly final class Ssl +final readonly class Ssl { public function __construct( public string $private, diff --git a/component/Web/Url.php b/component/Web/Url.php index f4dc418..dae6afa 100644 --- a/component/Web/Url.php +++ b/component/Web/Url.php @@ -20,7 +20,7 @@ class Url protected ?string $fragment; public function __construct( - readonly public string $value + public readonly string $value ) { if (defined(get_class($this) . '::PATTERN') && static::PATTERN && !preg_match(static::PATTERN, $value)) { throw new NotCompliant('Value : ' . $value); From 61bc770663ce6f93967a4ad0d8fc8666c8b02bef Mon Sep 17 00:00:00 2001 From: Lenny ROUANET Date: Wed, 17 Dec 2025 20:51:05 +0100 Subject: [PATCH 03/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20readonly=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- component/Web/UserAgent/Browser.php | 6 +++--- component/Web/UserAgent/OperatingSystem.php | 6 +++--- component/Web/UserAgent/OperatingSystemFamily.php | 1 - component/Web/UserAgent/Version.php | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) 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 { } From 6c2f76273187afac3d2b6bd49df1aa121c21129a Mon Sep 17 00:00:00 2001 From: Lenny ROUANET Date: Wed, 17 Dec 2025 20:52:02 +0100 Subject: [PATCH 04/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20version=20PHP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1652a81..2d18a91 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "phant/error": "1.*", "ramsey/uuid": "4.*", "firebase/php-jwt": "6.*" From 96cea1bc98797e85f5a4dac3e466e938270593f1 Mon Sep 17 00:00:00 2001 From: Lenny ROUANET Date: Wed, 17 Dec 2025 20:57:06 +0100 Subject: [PATCH 05/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Update=20JWT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 2 +- test/Token/JwtTest.php | 49 +++++++++++++++++++++++++++--------------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/composer.json b/composer.json index 2d18a91..632e2ee 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "php": ">=8.2", "phant/error": "1.*", "ramsey/uuid": "4.*", - "firebase/php-jwt": "6.*" + "firebase/php-jwt": "7.*" }, "require-dev": { "friendsofphp/php-cs-fixer": "3.*", 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 = << Date: Wed, 17 Dec 2025 20:58:08 +0100 Subject: [PATCH 06/13] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20php=20stan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 632e2ee..b9a2f94 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "3.*", - "phpstan/phpstan": "1.*", + "phpstan/phpstan": "2.*", "phpunit/phpunit": "12.*" }, "scripts": { From 102138913a130fdbeb7f4c249d1f6f8bc91e2ad6 Mon Sep 17 00:00:00 2001 From: Lenny ROUANET Date: Wed, 17 Dec 2025 21:09:23 +0100 Subject: [PATCH 07/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Translate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- component/Company/Fr/Siren.php | 6 +++--- component/Company/Fr/Siret.php | 6 +++--- component/Money/Price.php | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/component/Company/Fr/Siren.php b/component/Company/Fr/Siren.php index 83a7a89..147d980 100755 --- a/component/Company/Fr/Siren.php +++ b/component/Company/Fr/Siren.php @@ -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 874ad15..251189d 100755 --- a/component/Company/Fr/Siret.php +++ b/component/Company/Fr/Siret.php @@ -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/Money/Price.php b/component/Money/Price.php index 62d7af8..e4aa454 100644 --- a/component/Money/Price.php +++ b/component/Money/Price.php @@ -14,7 +14,7 @@ public function __construct( } 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; From 2d092dbb8fb38b0e2f4570add9f2ce695dda6b2d Mon Sep 17 00:00:00 2001 From: Lenny ROUANET Date: Wed, 17 Dec 2025 21:21:48 +0100 Subject: [PATCH 08/13] =?UTF-8?q?=E2=9C=A8=20EUID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- component/Company/Euid.php | 79 +++++++++++++++++++++ test/Company/EuidTest.php | 137 +++++++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100755 component/Company/Euid.php create mode 100644 test/Company/EuidTest.php 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/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); + } +} From b466ae7a68bd51dd04510ecd91c90dd8a9163868 Mon Sep 17 00:00:00 2001 From: Lenny ROUANET Date: Wed, 17 Dec 2025 21:26:36 +0100 Subject: [PATCH 09/13] =?UTF-8?q?=E2=9C=85=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpunit.xml | 25 ++++++++++++------------ test/Key/SslTest.php | 46 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 17 deletions(-) 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/Key/SslTest.php b/test/Key/SslTest.php index a0d5376..30e901b 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(); + } } } From aabca04fc0c74e421aefc90ad0e732d23f52b1fb Mon Sep 17 00:00:00 2001 From: Lenny ROUANET Date: Wed, 17 Dec 2025 21:34:29 +0100 Subject: [PATCH 10/13] =?UTF-8?q?=F0=9F=92=AC=20Exception=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- component/Time/DateInterval.php | 4 ++-- component/Time/DateTimeInterval.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/component/Time/DateInterval.php b/component/Time/DateInterval.php index 2b85ced..36e8773 100644 --- a/component/Time/DateInterval.php +++ b/component/Time/DateInterval.php @@ -17,10 +17,10 @@ public function __construct( 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/DateTimeInterval.php b/component/Time/DateTimeInterval.php index 2aca503..b90f08f 100644 --- a/component/Time/DateTimeInterval.php +++ b/component/Time/DateTimeInterval.php @@ -17,10 +17,10 @@ public function __construct( 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; From f55860bbf5d03fb8e88dc26d53650dbe41388594 Mon Sep 17 00:00:00 2001 From: Lenny ROUANET Date: Wed, 17 Dec 2025 21:42:22 +0100 Subject: [PATCH 11/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20CS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Key/SslTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/Key/SslTest.php b/test/Key/SslTest.php index 30e901b..fd2eae9 100644 --- a/test/Key/SslTest.php +++ b/test/Key/SslTest.php @@ -104,13 +104,13 @@ public function testEncryptInvalid(): void $this->expectException(NotCompliant::class); // Set error handler to suppress expected openssl warnings - set_error_handler(function($severity, $message, $filename, $lineno) { + 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 { @@ -123,13 +123,13 @@ public function testEncryptInvalidBis(): void $this->expectException(NotCompliant::class); // Set error handler to suppress expected openssl warnings - set_error_handler(function($severity, $message, $filename, $lineno) { + 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 { @@ -151,13 +151,13 @@ public function testDecryptInvalid(): void $this->expectException(NotCompliant::class); // Set error handler to suppress expected openssl warnings - set_error_handler(function($severity, $message, $filename, $lineno) { + 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') From 8ec4d729676077dd8381c33598daad5422cd2ac4 Mon Sep 17 00:00:00 2001 From: Lenny ROUANET Date: Wed, 17 Dec 2025 21:45:57 +0100 Subject: [PATCH 12/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Duration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- component/Time/Duration.php | 35 ++++++++++-------------------- component/Time/Unit.php | 39 +++++++++++++++++++++++++++++++++ test/Time/UnitTest.php | 43 +++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 24 deletions(-) create mode 100644 component/Time/Unit.php create mode 100644 test/Time/UnitTest.php diff --git a/component/Time/Duration.php b/component/Time/Duration.php index c6c0eee..e502fb1 100644 --- a/component/Time/Duration.php +++ b/component/Time/Duration.php @@ -7,24 +7,11 @@ 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 const MINUTE = 60; + public const HOUR = 3600; + public const DAY = 86400; + public const MONTH = 2628000; + public const YEAR = 31536000; public string $label; @@ -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/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()); + } +} From 2c735b07ed5b52db326f4bb322fea56d89957bd5 Mon Sep 17 00:00:00 2001 From: Lenny ROUANET Date: Thu, 18 Dec 2025 08:57:42 +0100 Subject: [PATCH 13/13] Update component/Time/Duration.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- component/Time/Duration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component/Time/Duration.php b/component/Time/Duration.php index e502fb1..e83329d 100644 --- a/component/Time/Duration.php +++ b/component/Time/Duration.php @@ -6,7 +6,7 @@ readonly class Duration { - // Duration in secondes + // Duration in seconds public const MINUTE = 60; public const HOUR = 3600; public const DAY = 86400;