├── .editorconfig ├── .github └── workflows │ └── main.yaml ├── .gitignore ├── LICENSE ├── README.md ├── bin ├── cbf ├── cs └── stan ├── composer.json ├── docs └── img │ ├── ide-love-2.png │ └── ide-love.png ├── src ├── Address.php ├── Arrays.php ├── Base64String.php ├── BoolArray.php ├── BoolType.php ├── CompanyRegistrationNumber.php ├── Comparable │ ├── ArrayComparableTrait.php │ ├── ComparableInterface.php │ └── StringComparableTrait.php ├── ContentType.php ├── CountryCode.php ├── CurrencyCode.php ├── DateTimeFormat.php ├── DateTimeFormatter.php ├── DateTimeRange.php ├── DateTimes.php ├── DateTimesImmutable.php ├── Dates.php ├── DatesImmutable.php ├── Domain.php ├── DomainName.php ├── Duration.php ├── Emailaddress.php ├── Enum.php ├── ExtractableTraits │ ├── ArrayExtractableTrait.php │ ├── EnumExtractableTrait.php │ ├── ExtractableTrait.php │ ├── FloatExtractableTrait.php │ ├── IntExtractableTrait.php │ └── StringExtractableTrait.php ├── ExtractableTypeInterface.php ├── FieldOfApplication.php ├── FloatArray.php ├── FloatType.php ├── Guid.php ├── Helpers │ ├── ArrayHelpers.php │ ├── CountryCodeToPhoneCodeTable.php │ ├── ExtractableHelpers.php │ ├── InvisibleSpaceCharacterCodes.php │ ├── StringHelpers.php │ ├── UniqueToStringArray.php │ └── ValidationHelpers.php ├── Hex32.php ├── HexColor.php ├── HostName.php ├── HttpMethod.php ├── Iban.php ├── IntArray.php ├── IntType.php ├── InvalidArgumentException.php ├── InvalidEmailaddressException.php ├── InvalidTypeException.php ├── IpAddress.php ├── JsonString.php ├── KeyValuePair.php ├── LawfulBasisForProcessing.php ├── LoginCredentials.php ├── NonEmptyString.php ├── Part.php ├── PhoneNumber.php ├── Port.php ├── Price.php ├── PrimitiveTypes.php ├── Quantity.php ├── ReLUValue.php ├── Relation.php ├── ScalarLeavesArray.php ├── SigmoidValue.php ├── StringArray.php ├── StringType.php ├── SwiftBic.php ├── TimeUnit.php ├── ToArrayInterface.php ├── ToStringInterface.php ├── ToStringTrait.php ├── UniqueArrayFeatures.php ├── UniqueIntArray.php ├── UniqueStringArray.php ├── UnsignedFloat.php ├── UnsignedInt.php ├── UrlType.php ├── VatId.php └── ZipCode.php ├── tests ├── .coveralls.yml ├── AddressTest.phpt ├── ArrayHelpersTest.phpt ├── ArraysTest.phpt ├── Base64StringTest.phpt ├── BoolArrayTest.phpt ├── CompanyRegistrationNumberTest.phpt ├── ContentTypeTest.phpt ├── CountryCodeTest.phpt ├── CurrencyCodeTest.phpt ├── DateTimeFormatterTest.phpt ├── DateTimeRangeTest.phpt ├── DateTimesImmutableTest.phpt ├── DateTimesTest.phpt ├── DatesImmutableTest.phpt ├── DatesTest.phpt ├── DomainNameTest.phpt ├── DomainTest.phpt ├── DurationTest.phpt ├── EmailaddressTest.phpt ├── EnumTest.phpt ├── ExtractableTraitTest.phpt ├── GuidTest.phpt ├── Hex32Test.phpt ├── HexColorTest.phpt ├── IbanTest.phpt ├── IntArrayUniqueTest.phpt ├── IntTypeTest.phpt ├── IpAddressTest.phpt ├── JsonStringTest.phpt ├── KeyValuePairTest.phpt ├── LoginCredentialsTest.phpt ├── NonEmptyStringTest.phpt ├── PartTest.phpt ├── PhoneNumberTest.phpt ├── PortTest.phpt ├── PriceTest.phpt ├── PrimitiveTypesTest.phpt ├── QuantityTest.phpt ├── ReLUValueTest.phpt ├── ScalarLeavesArrayTest.phpt ├── SigmoidValueTest.phpt ├── StringHelpersTest.phpt ├── SwiftBicTest.phpt ├── UniqueIntArrayTest.phpt ├── UniqueStringArrayTest.phpt ├── UniqueToStringArrayTest.phpt ├── UnsignedFloatTest.phpt ├── UnsignedIntTest.phpt ├── UrlTypeTest.phpt ├── VatIdTest.phpt ├── ZipCodeTest.phpt └── bootstrap.php └── tools ├── cs └── ruleset.xml └── phpstan └── phpstan.neon /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.{php|phpt}] 10 | indent_style = tab 11 | indent_size = 1 12 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: 'build' 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - 'master' 7 | push: 8 | branches: 9 | - 'master' 10 | 11 | env: 12 | php-version: '8.3' 13 | php-extensions: 'json' 14 | composer-install: 'composer update --no-interaction --no-progress --no-suggest --prefer-dist --prefer-stable' 15 | 16 | jobs: 17 | coding_style: 18 | name: 'Coding style' 19 | runs-on: 'ubuntu-latest' 20 | 21 | steps: 22 | - name: 'Checkout' 23 | uses: 'actions/checkout@v4' 24 | 25 | - name: 'Install PHP' 26 | uses: 'shivammathur/setup-php@v2' 27 | with: 28 | php-version: '${{ env.php-version }}' 29 | extensions: '${{ env.php-extensions }}' 30 | 31 | - name: 'Install dependencies' 32 | run: '${{ env.composer-install }}' 33 | 34 | - name: 'Coding Standard' 35 | run: 'bin/cs' 36 | 37 | php_stan: 38 | name: 'PHP Stan' 39 | runs-on: 'ubuntu-latest' 40 | 41 | steps: 42 | - name: 'Checkout' 43 | uses: 'actions/checkout@v4' 44 | 45 | - name: 'Install PHP' 46 | uses: 'shivammathur/setup-php@v2' 47 | with: 48 | php-version: '${{ env.php-version }}' 49 | extensions: '${{ env.php-extensions }}' 50 | 51 | - name: 'Install dependencies' 52 | run: '${{ env.composer-install }}' 53 | 54 | - name: 'PHPStan' 55 | run: 'bin/stan' 56 | 57 | tests: 58 | name: 'Tests' 59 | runs-on: '${{ matrix.operating-system }}' 60 | 61 | strategy: 62 | matrix: 63 | php-version: [ '8.1', '8.2', '8.3', '8.4' ] 64 | operating-system: [ 'ubuntu-latest' ] 65 | composer-args: [ '' ] 66 | include: 67 | - php-version: '8.1' 68 | operating-system: 'ubuntu-latest' 69 | composer-args: '--prefer-lowest' 70 | 71 | steps: 72 | - name: 'Checkout' 73 | uses: 'actions/checkout@v4' 74 | 75 | - name: 'Install PHP' 76 | uses: 'shivammathur/setup-php@v2' 77 | with: 78 | php-version: '${{ matrix.php-version }}' 79 | extensions: '${{ env.php-extensions }}' 80 | 81 | - name: 'Install dependencies' 82 | run: '${{ env.composer-install }} ${{ matrix.composer-args }}' 83 | 84 | - name: 'Tests' 85 | run: 'vendor/bin/tester tests -C' 86 | 87 | - name: 'Email test' 88 | run: 'php tests/EmailaddressTest.phpt' 89 | 90 | tests_code_coverage: 91 | name: 'Tests with code coverage' 92 | runs-on: 'ubuntu-latest' 93 | 94 | if: "github.event_name == 'push'" 95 | 96 | steps: 97 | - name: 'Checkout' 98 | uses: 'actions/checkout@v4' 99 | 100 | - name: 'Install PHP' 101 | uses: 'shivammathur/setup-php@v2' 102 | with: 103 | php-version: '8.3' 104 | coverage: 'pcov' 105 | extensions: '${{ env.php-extensions }}' 106 | 107 | - name: 'Install dependencies' 108 | run: '${{ env.composer-install }}' 109 | 110 | - name: 'Tests' 111 | run: 'vendor/bin/tester -s -p phpdbg --colors 1 -C --coverage ./coverage.xml --coverage-src ./src tests' 112 | 113 | - name: 'Coveralls.io' 114 | env: 115 | CI_NAME: github 116 | CI: true 117 | COVERALLS_REPO_TOKEN: '${{ secrets.GITHUB_TOKEN }}' 118 | run: | 119 | wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.7.0/php-coveralls.phar 120 | php php-coveralls.phar --verbose --config tests/.coveralls.yml 121 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /composer.lock 2 | /vendor 3 | /.idea 4 | **/.DS_Store 5 | /tests/output/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | This work is published under the MIT license (see full statement below). 5 | If you want to deal (as described below) only with a part of this work, 6 | you have to include this license in the part itself. 7 | 8 | The MIT License (MIT) 9 | --------------------- 10 | 11 | Copyright (c) 2018 Martin Strouhal 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in 21 | all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | THE SOFTWARE. 30 | -------------------------------------------------------------------------------- /bin/cbf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | $data 27 | */ 28 | private function __construct( 29 | array $data 30 | ) { 31 | $this->streetAndNumber = StringType::extract($data, 'street_and_number'); 32 | $this->town = StringType::extract($data, 'town'); 33 | $this->zipCode = ZipCode::extract($data, 'zip_code'); 34 | $this->country = CountryCode::extract($data, 'country'); 35 | } 36 | 37 | /** 38 | * @return array 39 | */ 40 | public function toArray(): array 41 | { 42 | return [ 43 | 'street_and_number' => $this->streetAndNumber, 44 | 'town' => $this->town, 45 | 'zip_code' => $this->zipCode->getValue(), 46 | 'country' => $this->country->getValue(), 47 | ]; 48 | } 49 | 50 | public function getStreetAndNumber(): string 51 | { 52 | return $this->streetAndNumber; 53 | } 54 | 55 | public function getTown(): string 56 | { 57 | return $this->town; 58 | } 59 | 60 | public function getZipCode(): ZipCode 61 | { 62 | return $this->zipCode; 63 | } 64 | 65 | public function getCountry(): CountryCode 66 | { 67 | return $this->country; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/Base64String.php: -------------------------------------------------------------------------------- 1 | isValid($value)) { 23 | throw new InvalidTypeException('Invalid Base64 string'); 24 | } 25 | } 26 | 27 | public static function encode( 28 | string $value 29 | ): self 30 | { 31 | return new static( 32 | \base64_encode($value) 33 | ); 34 | } 35 | 36 | public function getValue(): string 37 | { 38 | return $this->value; 39 | } 40 | 41 | public function getDecodedValue(): string 42 | { 43 | $value = \base64_decode($this->value, true); 44 | 45 | if ($value === false) { 46 | throw new InvalidTypeException('Unable to decode Base64'); 47 | } 48 | 49 | return $value; 50 | } 51 | 52 | private function isValid( 53 | string $value 54 | ): bool 55 | { 56 | return \base64_decode($value, true) !== false; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/BoolArray.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final public static function from( 14 | mixed $value 15 | ): array { 16 | $array = Arrays::from($value); 17 | 18 | $return = []; 19 | 20 | foreach ($array as $index => $item) { 21 | $return[$index] = BoolType::from($item); 22 | } 23 | 24 | return $return; 25 | } 26 | 27 | /** 28 | * @return array|null 29 | */ 30 | final public static function fromOrNull( 31 | mixed $value, 32 | bool $nullIfInvalid = false 33 | ): ?array { 34 | $array = Arrays::fromOrNull($value, $nullIfInvalid); 35 | 36 | if ($array === null) { 37 | return null; 38 | } 39 | 40 | $return = []; 41 | 42 | try { 43 | foreach ($array as $index => $item) { 44 | $return[$index] = BoolType::from($item); 45 | } 46 | } catch (InvalidTypeException $e) { 47 | if ($nullIfInvalid) { 48 | return null; 49 | } 50 | 51 | throw $e; 52 | } 53 | 54 | return $return; 55 | } 56 | 57 | /** 58 | * @param array|\ArrayAccess $data 59 | * @return array 60 | * @throws \SmartEmailing\Types\InvalidTypeException 61 | */ 62 | final public static function extract( 63 | array|\ArrayAccess $data, 64 | string $key 65 | ): array { 66 | $value = Arrays::extract($data, $key); 67 | 68 | try { 69 | return self::from($value); 70 | } catch (InvalidTypeException $e) { 71 | throw $e->wrap($key); 72 | } 73 | } 74 | 75 | /** 76 | * @param array|\ArrayAccess $data 77 | * @return array|null 78 | * @throws \SmartEmailing\Types\InvalidTypeException 79 | */ 80 | final public static function extractOrNull( 81 | array|\ArrayAccess $data, 82 | string $key, 83 | bool $nullIfInvalid = false 84 | ): ?array { 85 | $value = Arrays::extractOrNull($data, $key, $nullIfInvalid); 86 | 87 | if ($value === null) { 88 | return null; 89 | } 90 | 91 | try { 92 | return self::fromOrNull($value, $nullIfInvalid); 93 | } catch (InvalidTypeException $exception) { 94 | throw $exception->wrap($key); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/BoolType.php: -------------------------------------------------------------------------------- 1 | |\ArrayAccess $data 56 | * @throws \SmartEmailing\Types\InvalidTypeException 57 | */ 58 | final public static function extract( 59 | array|\ArrayAccess $data, 60 | string $key 61 | ): bool { 62 | $value = ExtractableHelpers::extractValue($data, $key); 63 | 64 | try { 65 | return self::from($value); 66 | } catch (InvalidTypeException $e) { 67 | throw $e->wrap($key); 68 | } 69 | } 70 | 71 | /** 72 | * @param array|\ArrayAccess $data 73 | * @throws \SmartEmailing\Types\InvalidTypeException 74 | */ 75 | final public static function extractOrNull( 76 | array|\ArrayAccess $data, 77 | string $key, 78 | bool $nullIfInvalid = false 79 | ): ?bool { 80 | $value = ExtractableHelpers::extractValueOrNull($data, $key); 81 | 82 | if ($value === null) { 83 | return null; 84 | } 85 | 86 | try { 87 | return self::fromOrNull($value, $nullIfInvalid); 88 | } catch (InvalidTypeException $exception) { 89 | throw $exception->wrap($key); 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/Comparable/ArrayComparableTrait.php: -------------------------------------------------------------------------------- 1 | toArray() === $that->toArray(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/Comparable/ComparableInterface.php: -------------------------------------------------------------------------------- 1 | value = StringType::extract($match, '0'); 39 | $this->type = StringType::extract($match, '1'); 40 | $this->subType = StringType::extract($match, '2'); 41 | } 42 | 43 | public function getValue(): string 44 | { 45 | return $this->value; 46 | } 47 | 48 | public function getType(): string 49 | { 50 | return $this->type; 51 | } 52 | 53 | public function getSubType(): string 54 | { 55 | return $this->subType; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/DateTimeFormat.php: -------------------------------------------------------------------------------- 1 | format($format); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/DateTimeRange.php: -------------------------------------------------------------------------------- 1 | $data 25 | */ 26 | private function __construct( 27 | array $data 28 | ) { 29 | $this->from = DateTimesImmutable::extract($data, 'from'); 30 | $this->to = DateTimesImmutable::extract($data, 'to'); 31 | 32 | $compared = \strcmp( 33 | DateTimeFormatter::format($this->from), 34 | DateTimeFormatter::format($this->to) 35 | ); 36 | 37 | if ($compared > 0) { 38 | throw new InvalidTypeException(self::class . ' cannot have negative duration'); 39 | } 40 | 41 | $interval = $this->to->diff($this->from); 42 | 43 | $this->durationInSeconds 44 | = (int) $interval->days * 86_400 45 | + $interval->h * 3_600 46 | + $interval->i * 60 47 | + $interval->s; 48 | } 49 | 50 | public function getFrom(): \DateTimeImmutable 51 | { 52 | return $this->from; 53 | } 54 | 55 | public function getTo(): \DateTimeImmutable 56 | { 57 | return $this->to; 58 | } 59 | 60 | public function getDurationInSeconds(): int 61 | { 62 | return $this->durationInSeconds; 63 | } 64 | 65 | public function contains( 66 | \DateTimeInterface $dateTime 67 | ): bool { 68 | $timestamp = $dateTime->getTimestamp(); 69 | 70 | return $timestamp >= $this->getFrom()->getTimestamp() && 71 | $timestamp <= $this->getTo()->getTimestamp(); 72 | } 73 | 74 | /** 75 | * @return array 76 | */ 77 | public function toArray(): array 78 | { 79 | return [ 80 | 'from' => DateTimeFormatter::format($this->from), 81 | 'to' => DateTimeFormatter::format($this->to), 82 | ]; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/DateTimes.php: -------------------------------------------------------------------------------- 1 | |\ArrayAccess $data 43 | * @throws \SmartEmailing\Types\InvalidTypeException 44 | */ 45 | final public static function extract( 46 | array|\ArrayAccess $data, 47 | string $key 48 | ): \DateTime { 49 | $value = ExtractableHelpers::extractValue($data, $key); 50 | 51 | try { 52 | return self::from($value); 53 | } catch (InvalidTypeException $exception) { 54 | throw $exception->wrap($key); 55 | } 56 | } 57 | 58 | /** 59 | * @param array|\ArrayAccess $data 60 | */ 61 | final public static function extractOrNull( 62 | array|\ArrayAccess $data, 63 | string $key, 64 | bool $nullIfInvalid = false 65 | ): ?\DateTime { 66 | $value = ExtractableHelpers::extractValueOrNull($data, $key); 67 | 68 | if ($value === null) { 69 | return null; 70 | } 71 | 72 | try { 73 | return self::fromOrNull($value, $nullIfInvalid); 74 | } catch (InvalidTypeException $exception) { 75 | throw $exception->wrap($key); 76 | } 77 | } 78 | 79 | /** 80 | * @param array $data 81 | * @throws \SmartEmailing\Types\InvalidTypeException 82 | * @deprecated Use Dates::extract 83 | */ 84 | final public static function extractDate( 85 | array &$data, 86 | string $key 87 | ): \DateTime { 88 | return Dates::extract($data, $key); 89 | } 90 | 91 | /** 92 | * @param array $data 93 | * @deprecated Use Dates::extractDateOrNull 94 | */ 95 | final public static function extractDateOrNull( 96 | array &$data, 97 | string $key 98 | ): ?\DateTime { 99 | return Dates::extractOrNull($data, $key); 100 | } 101 | 102 | public static function fromOrNull( 103 | mixed $value, 104 | bool $nullIfInvalid = false 105 | ): ?\DateTime { 106 | if ($value === null) { 107 | return null; 108 | } 109 | 110 | try { 111 | return self::from($value); 112 | } catch (InvalidTypeException $e) { 113 | if ($nullIfInvalid) { 114 | return null; 115 | } 116 | 117 | throw $e; 118 | } 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/DateTimesImmutable.php: -------------------------------------------------------------------------------- 1 | |\ArrayAccess $data 20 | * @throws \SmartEmailing\Types\InvalidTypeException 21 | */ 22 | final public static function extract( 23 | array|\ArrayAccess $data, 24 | string $key 25 | ): \DateTimeImmutable { 26 | $dateTime = DateTimes::extract( 27 | $data, 28 | $key 29 | ); 30 | 31 | return self::immutate($dateTime); 32 | } 33 | 34 | /** 35 | * @param array|\ArrayAccess $data 36 | */ 37 | final public static function extractOrNull( 38 | array|\ArrayAccess $data, 39 | string $key, 40 | bool $nullIfInvalid = false 41 | ): ?\DateTimeImmutable { 42 | $dateTime = DateTimes::extractOrNull( 43 | $data, 44 | $key, 45 | $nullIfInvalid 46 | ); 47 | 48 | if ($dateTime === null) { 49 | return null; 50 | } 51 | 52 | return self::immutate($dateTime); 53 | } 54 | 55 | /** 56 | * @param array $data 57 | * @throws \SmartEmailing\Types\InvalidTypeException 58 | * @deprecated Use DatesImmutable::extract 59 | */ 60 | final public static function extractDate( 61 | array &$data, 62 | string $key 63 | ): \DateTimeImmutable { 64 | return DatesImmutable::extract($data, $key); 65 | } 66 | 67 | /** 68 | * @param array $data 69 | * @deprecated Use DatesImmutable::extractDateOrNull 70 | */ 71 | final public static function extractDateOrNull( 72 | array &$data, 73 | string $key 74 | ): ?\DateTimeImmutable { 75 | return DatesImmutable::extractOrNull($data, $key); 76 | } 77 | 78 | public static function fromOrNull( 79 | mixed $value, 80 | bool $nullIfInvalid = false 81 | ): ?\DateTimeImmutable { 82 | $dateTime = DateTimes::fromOrNull($value, $nullIfInvalid); 83 | 84 | if ($dateTime === null) { 85 | return null; 86 | } 87 | 88 | return self::immutate($dateTime); 89 | } 90 | 91 | private static function immutate( 92 | \DateTime $dateTime 93 | ): \DateTimeImmutable { 94 | return \DateTimeImmutable::createFromMutable( 95 | $dateTime 96 | ); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/Dates.php: -------------------------------------------------------------------------------- 1 | format('Y-m-d'); 17 | } 18 | 19 | if (\is_string($value) && \preg_match('#^\d\d\d\d-\d\d-\d\d\z#', $value) === 1) { 20 | $date = \DateTime::createFromFormat(DateTimeFormat::DATETIME, $value . ' 00:00:00'); 21 | 22 | if ($date instanceof \DateTime && DateTimeFormatter::format($date, DateTimeFormat::DATE) === $value) { 23 | return $date; 24 | } 25 | } 26 | 27 | throw InvalidTypeException::typeError(DateTimeFormat::DATE . ' format', $value); 28 | } 29 | 30 | /** 31 | * @param array|\ArrayAccess $data 32 | * @throws \SmartEmailing\Types\InvalidTypeException 33 | */ 34 | final public static function extract( 35 | array|\ArrayAccess $data, 36 | string $key 37 | ): \DateTime { 38 | $value = ExtractableHelpers::extractValue($data, $key); 39 | 40 | try { 41 | return self::from($value); 42 | } catch (InvalidTypeException $exception) { 43 | throw $exception->wrap($key); 44 | } 45 | } 46 | 47 | /** 48 | * @param array|\ArrayAccess $data 49 | */ 50 | final public static function extractOrNull( 51 | array|\ArrayAccess $data, 52 | string $key, 53 | bool $nullIfInvalid = false 54 | ): ?\DateTime { 55 | $value = ExtractableHelpers::extractValueOrNull($data, $key); 56 | 57 | if ($value === null) { 58 | return null; 59 | } 60 | 61 | try { 62 | return self::fromOrNull($value, $nullIfInvalid); 63 | } catch (InvalidTypeException $exception) { 64 | throw $exception->wrap($key); 65 | } 66 | } 67 | 68 | public static function fromOrNull( 69 | mixed $value, 70 | bool $nullIfInvalid = false 71 | ): ?\DateTime { 72 | if ($value === null) { 73 | return null; 74 | } 75 | 76 | try { 77 | return self::from($value); 78 | } catch (InvalidTypeException $e) { 79 | if ($nullIfInvalid) { 80 | return null; 81 | } 82 | 83 | throw $e; 84 | } 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/DatesImmutable.php: -------------------------------------------------------------------------------- 1 | |\ArrayAccess $data 20 | * @throws \SmartEmailing\Types\InvalidTypeException 21 | */ 22 | final public static function extract( 23 | array|\ArrayAccess $data, 24 | string $key 25 | ): \DateTimeImmutable { 26 | $dateTime = Dates::extract( 27 | $data, 28 | $key 29 | ); 30 | 31 | return self::immutate($dateTime); 32 | } 33 | 34 | /** 35 | * @param array|\ArrayAccess $data 36 | */ 37 | final public static function extractOrNull( 38 | array|\ArrayAccess $data, 39 | string $key, 40 | bool $nullIfInvalid = false 41 | ): ?\DateTimeImmutable { 42 | $dateTime = Dates::extractOrNull( 43 | $data, 44 | $key, 45 | $nullIfInvalid 46 | ); 47 | 48 | if ($dateTime === null) { 49 | return null; 50 | } 51 | 52 | return self::immutate($dateTime); 53 | } 54 | 55 | public static function fromOrNull( 56 | mixed $value, 57 | bool $nullIfInvalid = false 58 | ): ?\DateTimeImmutable { 59 | $dateTime = Dates::fromOrNull($value, $nullIfInvalid); 60 | 61 | if ($dateTime === null) { 62 | return null; 63 | } 64 | 65 | return self::immutate($dateTime); 66 | } 67 | 68 | private static function immutate( 69 | \DateTime $dateTime 70 | ): \DateTimeImmutable { 71 | return \DateTimeImmutable::createFromMutable( 72 | $dateTime 73 | ); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/Domain.php: -------------------------------------------------------------------------------- 1 | isValid($value)) { 32 | throw new InvalidTypeException('Invalid domain: ' . $value); 33 | } 34 | 35 | $this->value = $value; 36 | } 37 | 38 | public function getValue(): string 39 | { 40 | return $this->value; 41 | } 42 | 43 | public function getSecondLevelDomain(): Domain 44 | { 45 | $parts = \explode('.', $this->value); 46 | $numberOfKeptParts = 2; 47 | 48 | if (\count($parts) > 2 && \end($parts) === 'uk') { 49 | $numberOfKeptParts = 3; 50 | } 51 | 52 | $secondLevelParts = \array_slice($parts, -$numberOfKeptParts, $numberOfKeptParts); 53 | 54 | return self::from(\implode('.', $secondLevelParts)); 55 | } 56 | 57 | private function isValid( 58 | string $value 59 | ): bool { 60 | return \preg_match('/^([a-z\\d](-*[a-z\\d])*)(\\.([a-z\\d](-*[a-z\\d])*))*$/i', $value) === 1 //valid chars check 61 | && \preg_match('/^.{1,253}$/', $value) === 1// overall length check 62 | && \preg_match('/^[^\\.]{1,63}(\\.[^\\.]{1,63})*$/', $value) === 1; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/DomainName.php: -------------------------------------------------------------------------------- 1 | isValid($value)) { 29 | throw new InvalidTypeException('Invalid domain: ' . $value); 30 | } 31 | 32 | $this->value = $value; 33 | } 34 | 35 | public function getValue(): string 36 | { 37 | return $this->value; 38 | } 39 | 40 | public function getSecondLevelDomain(): DomainName 41 | { 42 | $parts = \explode('.', $this->value); 43 | $numberOfKeptParts = 2; 44 | 45 | if (\count($parts) > 2 && \end($parts) === 'uk') { 46 | $numberOfKeptParts = 3; 47 | } 48 | 49 | $secondLevelParts = \array_slice($parts, -$numberOfKeptParts, $numberOfKeptParts); 50 | 51 | return self::from(\implode('.', $secondLevelParts)); 52 | } 53 | 54 | private function isValid( 55 | string $value 56 | ): bool { 57 | return \preg_match('/^([_a-z\\d](-*[_a-z\\d])*)(\\.([_a-z\\d](-*[_a-z\\d])*))*$/i', $value) === 1 //valid chars check 58 | && \preg_match('/^.{1,253}$/', $value) === 1// overall length check 59 | && \preg_match('/^[^\\.]{1,63}(\\.[^\\.]{1,63})*$/', $value) === 1; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/Duration.php: -------------------------------------------------------------------------------- 1 | $data 26 | */ 27 | private function __construct( 28 | array $data 29 | ) 30 | { 31 | $this->value = IntType::extract($data, 'value'); 32 | $this->unit = TimeUnit::extract($data, 'unit'); 33 | 34 | $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); 35 | $end = $now->modify('+' . $this->getDateTimeModify()); 36 | $diff = $end->getTimestamp() - $now->getTimestamp(); 37 | $this->lengthInSeconds = \abs($diff); 38 | } 39 | 40 | public static function fromDateTimeModify( 41 | string $dateTimeModify 42 | ): self 43 | { 44 | $matches = Strings::match($dateTimeModify, '/^(-?|\+?)(\d+)\s+(.+)/'); 45 | 46 | if ($matches === null) { 47 | throw new InvalidTypeException('Duration: ' . $dateTimeModify . ' is not in valid duration format.'); 48 | } 49 | 50 | $value = IntType::extract($matches, '2'); 51 | $unit = TimeUnit::extract($matches, '3'); 52 | 53 | if ($matches[1] === '-') { 54 | $value *= -1; 55 | } 56 | 57 | return new static( 58 | [ 59 | 'value' => $value, 60 | 'unit' => $unit->getValue(), 61 | ] 62 | ); 63 | } 64 | 65 | public function getValue(): int 66 | { 67 | return $this->value; 68 | } 69 | 70 | public function getUnit(): TimeUnit 71 | { 72 | return $this->unit; 73 | } 74 | 75 | public function getDateTimeModify(): string 76 | { 77 | return $this->value . ' ' . $this->unit->getValue(); 78 | } 79 | 80 | /** 81 | * @return array 82 | */ 83 | public function toArray(): array 84 | { 85 | return [ 86 | 'value' => $this->value, 87 | 'unit' => $this->unit->getValue(), 88 | ]; 89 | } 90 | 91 | public function getLengthInSeconds(): int 92 | { 93 | return $this->lengthInSeconds; 94 | } 95 | 96 | public static function from( 97 | mixed $data 98 | ): static 99 | { 100 | if ($data instanceof self) { 101 | return $data; 102 | } 103 | 104 | $string = StringType::fromOrNull($data, true); 105 | 106 | if (\is_string($string)) { 107 | return self::fromDateTimeModify($string); 108 | } 109 | 110 | $array = Arrays::fromOrNull($data, true); 111 | 112 | if (\is_array($array)) { 113 | return new self($data); 114 | } 115 | 116 | throw InvalidTypeException::typesError(['string', 'array'], $data); 117 | } 118 | 119 | public function __toString(): string 120 | { 121 | return $this->getDateTimeModify(); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/Emailaddress.php: -------------------------------------------------------------------------------- 1 | initialize($value); 34 | } catch (\Throwable $e) { 35 | $ok = false; 36 | } 37 | 38 | if (!$ok) { 39 | throw new InvalidEmailaddressException('Invalid emailaddress: ' . $value); 40 | } 41 | } 42 | 43 | public function getLocalPart(): string 44 | { 45 | return $this->localPart; 46 | } 47 | 48 | public function getValue(): string 49 | { 50 | return $this->value; 51 | } 52 | 53 | /** 54 | * @deprecated Use getHostName() instead 55 | */ 56 | public function getDomain(): Domain 57 | { 58 | return Domain::from($this->hostName->getValue()); 59 | } 60 | 61 | public function getHostName(): HostName 62 | { 63 | return $this->hostName; 64 | } 65 | 66 | public static function preprocessEmailaddress( 67 | string $emailaddress 68 | ): string 69 | { 70 | $sanitized = Strings::lower( 71 | Strings::toAscii( 72 | Strings::trim( 73 | $emailaddress 74 | ) 75 | ) 76 | ); 77 | 78 | return \strtr( 79 | $sanitized, 80 | [ 81 | '>' => '', 82 | '<' => '', 83 | ] 84 | ); 85 | } 86 | 87 | private function initialize( 88 | string $emailaddress 89 | ): bool 90 | { 91 | $emailaddress = self::preprocessEmailaddress($emailaddress); 92 | 93 | if ( 94 | !Strings::contains($emailaddress, '@') 95 | || Strings::contains($emailaddress, '"') 96 | || Strings::contains($emailaddress, ' ') 97 | || !Validators::isEmail($emailaddress) 98 | ) { 99 | return false; 100 | } 101 | 102 | $validator = new EmailValidator(); 103 | 104 | $isValid = $validator->isValid( 105 | $emailaddress, 106 | new RFCValidation() 107 | ); 108 | 109 | if (!$isValid) { 110 | return false; 111 | } 112 | 113 | $exploded = \explode('@', $emailaddress); 114 | 115 | [$this->localPart, $hostName] = $exploded; 116 | 117 | $this->hostName = HostName::from($hostName); 118 | 119 | $this->value = $emailaddress; 120 | 121 | return true; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/ExtractableTraits/ArrayExtractableTrait.php: -------------------------------------------------------------------------------- 1 | $data 29 | */ 30 | abstract public function __construct( 31 | array $data 32 | ); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/ExtractableTraits/EnumExtractableTrait.php: -------------------------------------------------------------------------------- 1 | |\ArrayAccess $data 21 | * @throws \SmartEmailing\Types\InvalidTypeException 22 | */ 23 | public static function extract( 24 | array|\ArrayAccess $data, 25 | string $key 26 | ): static 27 | { 28 | $value = ExtractableHelpers::extractValue($data, $key); 29 | 30 | if ($value instanceof self) { 31 | return $value; 32 | } 33 | 34 | try { 35 | return self::from($value); 36 | } catch (InvalidTypeException $e) { 37 | throw $e->wrap($key); 38 | } 39 | } 40 | 41 | /** 42 | * @throws \SmartEmailing\Types\InvalidTypeException 43 | */ 44 | public static function fromOrNull( 45 | mixed $value, 46 | bool $getNullIfInvalid = false 47 | ): ?static 48 | { 49 | if ($value === null) { 50 | return null; 51 | } 52 | 53 | try { 54 | return self::from($value); 55 | } catch (InvalidTypeException $e) { 56 | if ($getNullIfInvalid) { 57 | return null; 58 | } 59 | 60 | throw $e; 61 | } 62 | } 63 | 64 | /** 65 | * @param array|\ArrayAccess $data 66 | * @throws \SmartEmailing\Types\InvalidTypeException 67 | */ 68 | public static function extractOrNull( 69 | array|\ArrayAccess $data, 70 | string $key, 71 | bool $nullIfInvalid = false 72 | ): ?static 73 | { 74 | $value = ExtractableHelpers::extractValueOrNull($data, $key); 75 | 76 | if ($value === null) { 77 | return null; 78 | } 79 | 80 | if (!$nullIfInvalid && $value === '') { 81 | return null; 82 | } 83 | 84 | if ($value instanceof self) { 85 | return $value; 86 | } 87 | 88 | try { 89 | return self::fromOrNull($value, $nullIfInvalid); 90 | } catch (InvalidTypeException $exception) { 91 | throw $exception->wrap($key); 92 | } 93 | } 94 | 95 | /** 96 | * @param array $data 97 | * @return array 98 | * @throws \SmartEmailing\Types\InvalidTypeException 99 | */ 100 | public static function extractArrayOf( 101 | array $data, 102 | string $key 103 | ): array 104 | { 105 | $typedArray = Arrays::extract($data, $key); 106 | 107 | try { 108 | return self::getArrayOf($typedArray); 109 | } catch (InvalidTypeException $e) { 110 | throw $e->wrap($key); 111 | } 112 | } 113 | 114 | /** 115 | * @param array $data 116 | * @return array 117 | * @throws \SmartEmailing\Types\InvalidTypeException 118 | */ 119 | public static function extractArrayOfOrEmpty( 120 | array $data, 121 | string $key 122 | ): array 123 | { 124 | if (!isset($data[$key])) { 125 | return []; 126 | } 127 | 128 | return self::extractArrayOf($data, $key); 129 | } 130 | 131 | /** 132 | * @param array $array 133 | * @return array 134 | */ 135 | public static function getArrayOf( 136 | array $array 137 | ): array 138 | { 139 | $return = []; 140 | 141 | if (ValidationHelpers::isTypedObjectArray($array, static::class)) { 142 | return $array; 143 | } 144 | 145 | foreach ($array as $item) { 146 | $return[] = $item instanceof self 147 | ? $item 148 | : self::from($item); 149 | } 150 | 151 | return $return; 152 | } 153 | 154 | /** 155 | * @param array $array 156 | * @return array 157 | */ 158 | public static function getArrayOfSkipInvalid( 159 | array $array 160 | ): array 161 | { 162 | $return = []; 163 | 164 | if (ValidationHelpers::isTypedObjectArray($array, static::class)) { 165 | return $array; 166 | } 167 | 168 | foreach ($array as $item) { 169 | try { 170 | $return[] = self::from($item); 171 | } catch (InvalidTypeException $e) { 172 | // exclude from result 173 | } 174 | } 175 | 176 | return $return; 177 | } 178 | 179 | /** 180 | * @param array $data 181 | * @return array 182 | */ 183 | public static function extractArrayOfSkipInvalid( 184 | array $data, 185 | string $key 186 | ): array 187 | { 188 | $typedArray = Arrays::extract($data, $key); 189 | 190 | return self::getArrayOfSkipInvalid($typedArray); 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /src/ExtractableTraits/FloatExtractableTrait.php: -------------------------------------------------------------------------------- 1 | |\ArrayAccess $data 21 | */ 22 | public static function extract( 23 | array|\ArrayAccess $data, 24 | string $key 25 | ): mixed; 26 | 27 | /** 28 | * @param array|\ArrayAccess $data 29 | */ 30 | public static function extractOrNull( 31 | array|\ArrayAccess $data, 32 | string $key, 33 | bool $nullIfInvalid 34 | ): mixed; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/FieldOfApplication.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class FieldOfApplication extends Enum 13 | { 14 | 15 | use EnumExtractableTrait; 16 | 17 | public const CARS = 'cars'; 18 | 19 | public const SAFETY = 'safety'; 20 | 21 | public const TRAVEL = 'travel'; 22 | 23 | public const TRANSPORT = 'transport'; 24 | 25 | public const ESHOP = 'eshop'; 26 | 27 | public const PHARMACY = 'pharmacy'; 28 | 29 | public const FINANCIAL = 'financial'; 30 | 31 | public const PHOTOGRAPHY = 'photography'; 32 | 33 | public const HOTELS = 'hotels'; 34 | 35 | public const COSMETICS = 'cosmetics'; 36 | 37 | public const PERSONAL_DEVELOPMENT = 'personal-development'; 38 | 39 | public const MARGETING = 'margeting'; 40 | 41 | public const NON_PROFIT = 'non-profit'; 42 | 43 | public const OTHER = 'other'; 44 | 45 | public const FOOD = 'food'; 46 | 47 | public const LAW = 'law'; 48 | 49 | public const REAL_ESTATES = 'real-estates'; 50 | 51 | public const CRAFT = 'craft'; 52 | 53 | public const GARDEN = 'garden'; 54 | 55 | public const IT = 'it'; 56 | 57 | public const ARCHITECTURE = 'architecture'; 58 | 59 | public const ENGINEERING = 'engineering'; 60 | 61 | public const WHOLESALE = 'wholesale'; 62 | 63 | public const PUBLIC_ADMINISTRATION = 'public-administration'; 64 | 65 | public const PRODUCTION = 'production'; 66 | 67 | public const EDUCATION = 'education'; 68 | 69 | public const SLIM = 'slim'; 70 | 71 | public const HEALTHCARE = 'healthcare'; 72 | 73 | public const AGRICULTURE = 'agriculture'; 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/FloatArray.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final public static function from( 14 | mixed $value 15 | ): array { 16 | $array = Arrays::from($value); 17 | 18 | $return = []; 19 | 20 | foreach ($array as $index => $item) { 21 | $return[$index] = FloatType::from($item); 22 | } 23 | 24 | return $return; 25 | } 26 | 27 | /** 28 | * @return array|null 29 | */ 30 | final public static function fromOrNull( 31 | mixed $value, 32 | bool $nullIfInvalid = false 33 | ): ?array { 34 | $array = Arrays::fromOrNull($value, $nullIfInvalid); 35 | 36 | if ($array === null) { 37 | return null; 38 | } 39 | 40 | $return = []; 41 | 42 | try { 43 | foreach ($array as $index => $item) { 44 | $return[$index] = FloatType::from($item); 45 | } 46 | } catch (InvalidTypeException $e) { 47 | if ($nullIfInvalid) { 48 | return null; 49 | } 50 | 51 | throw $e; 52 | } 53 | 54 | return $return; 55 | } 56 | 57 | /** 58 | * @param array|\ArrayAccess $data 59 | * @return array 60 | * @throws \SmartEmailing\Types\InvalidTypeException 61 | */ 62 | final public static function extract( 63 | array|\ArrayAccess $data, 64 | string $key 65 | ): array { 66 | $value = Arrays::extract($data, $key); 67 | 68 | try { 69 | return self::from($value); 70 | } catch (InvalidTypeException $e) { 71 | throw $e->wrap($key); 72 | } 73 | } 74 | 75 | /** 76 | * @param array|\ArrayAccess $data 77 | * @return array|null 78 | * @throws \SmartEmailing\Types\InvalidTypeException 79 | */ 80 | final public static function extractOrNull( 81 | array|\ArrayAccess $data, 82 | string $key, 83 | bool $nullIfInvalid = false 84 | ): ?array { 85 | $value = Arrays::extractOrNull($data, $key, $nullIfInvalid); 86 | 87 | if ($value === null) { 88 | return null; 89 | } 90 | 91 | try { 92 | return self::fromOrNull($value, $nullIfInvalid); 93 | } catch (InvalidTypeException $exception) { 94 | throw $exception->wrap($key); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/FloatType.php: -------------------------------------------------------------------------------- 1 | '.', 20 | ] 21 | ); 22 | } 23 | 24 | if (\is_numeric($value)) { 25 | return (float) $value; 26 | } 27 | 28 | throw InvalidTypeException::typeError('float', $value); 29 | } 30 | 31 | final public static function fromOrNull( 32 | mixed $value, 33 | bool $nullIfInvalid = false 34 | ): ?float { 35 | if ($value === null) { 36 | return null; 37 | } 38 | 39 | try { 40 | return self::from($value); 41 | } catch (InvalidTypeException $e) { 42 | if ($nullIfInvalid) { 43 | return null; 44 | } 45 | 46 | throw $e; 47 | } 48 | } 49 | 50 | /** 51 | * @param array|\ArrayAccess $data 52 | */ 53 | final public static function extract( 54 | array|\ArrayAccess $data, 55 | string $key 56 | ): float { 57 | $value = ExtractableHelpers::extractValue($data, $key); 58 | 59 | try { 60 | return self::from($value); 61 | } catch (InvalidTypeException $e) { 62 | throw $e->wrap($key); 63 | } 64 | } 65 | 66 | /** 67 | * @param array|\ArrayAccess $data 68 | * @throws \SmartEmailing\Types\InvalidTypeException 69 | */ 70 | final public static function extractOrNull( 71 | array|\ArrayAccess $data, 72 | string $key, 73 | bool $nullIfInvalid = false 74 | ): ?float { 75 | $value = ExtractableHelpers::extractValueOrNull($data, $key); 76 | 77 | if ($value === null) { 78 | return null; 79 | } 80 | 81 | try { 82 | return self::fromOrNull($value, $nullIfInvalid); 83 | } catch (InvalidTypeException $exception) { 84 | throw $exception->wrap($key); 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/Guid.php: -------------------------------------------------------------------------------- 1 | value = Strings::lower($value); 29 | } 30 | 31 | public static function fromHex32( 32 | Hex32 $hex32 33 | ): Guid { 34 | $parts = \str_split( 35 | $hex32->getValue(), 36 | 4 37 | ); 38 | 39 | return self::from( 40 | \sprintf( 41 | '%s%s-%s-%s-%s-%s%s%s', 42 | ...$parts // phpcs:disable SlevomatCodingStandard.PHP.OptimizedFunctionsWithoutUnpacking.UnpackingUsed 43 | ) 44 | ); 45 | } 46 | 47 | public function getValue(): string 48 | { 49 | return $this->value; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/Helpers/ArrayHelpers.php: -------------------------------------------------------------------------------- 1 | $arrayableCollection 15 | * @return array 16 | */ 17 | final public static function collectionItemsToArray( 18 | array $arrayableCollection 19 | ): array { 20 | $toArrayCallback = static fn (ToArrayInterface $toArray): array => $toArray->toArray(); 21 | 22 | return \array_map( 23 | $toArrayCallback, 24 | $arrayableCollection 25 | ); 26 | } 27 | 28 | /** 29 | * @param array<\SmartEmailing\Types\ToStringInterface> $stringableCollection 30 | * @return array 31 | */ 32 | final public static function stringExtractableCollectionToArray( 33 | array $stringableCollection 34 | ): array { 35 | $toArrayCallback = static fn (ToStringInterface $toString): string => (string) $toString; 36 | 37 | return \array_map( 38 | $toArrayCallback, 39 | $stringableCollection 40 | ); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Helpers/ExtractableHelpers.php: -------------------------------------------------------------------------------- 1 | $data 45 | */ 46 | private static function extractValueFromArray( 47 | array $data, 48 | string $key 49 | ): mixed 50 | { 51 | if (!\array_key_exists($key, $data)) { 52 | throw InvalidTypeException::missingKey($key); 53 | } 54 | 55 | return $data[$key]; 56 | } 57 | 58 | /** 59 | * @param \ArrayAccess $data 60 | */ 61 | private static function extractValueFromArrayAccess( 62 | ArrayAccess $data, 63 | string $key 64 | ): mixed 65 | { 66 | if (!$data->offsetExists($key)) { 67 | throw InvalidTypeException::missingKey($key); 68 | } 69 | 70 | return $data->offsetGet($key); 71 | } 72 | 73 | /** 74 | * @param array $data 75 | */ 76 | private static function extractValueFromArrayOrNull( 77 | array $data, 78 | string $key 79 | ): mixed 80 | { 81 | return $data[$key] ?? null; 82 | } 83 | 84 | /** 85 | * @param \ArrayAccess $data 86 | */ 87 | private static function extractValueFromArrayAccessOrNull( 88 | ArrayAccess $data, 89 | string $key 90 | ): mixed 91 | { 92 | if (!$data->offsetExists($key)) { 93 | return null; 94 | } 95 | 96 | return $data->offsetGet($key); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/Helpers/InvisibleSpaceCharacterCodes.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | private static array $codes = [ 14 | 0x9, 15 | 0xA, 16 | 0xB, 17 | 0xC, 18 | 0xD, 19 | 0x20, 20 | 0x85, 21 | 0xA0, 22 | 0x1680, 23 | 0x2000, 24 | 0x2001, 25 | 0x2002, 26 | 0x2003, 27 | 0x2004, 28 | 0x2005, 29 | 0x2006, 30 | 0x2007, 31 | 0x2008, 32 | 0x2009, 33 | 0x200A, 34 | 0x3000, 35 | 0xAD, 36 | 0xF0, 37 | 0xC2AD, 38 | 0xCA, 39 | 0xC2, 40 | ]; 41 | 42 | /** 43 | * @return array 44 | */ 45 | final public static function getCodes(): array 46 | { 47 | return self::$codes; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/Helpers/StringHelpers.php: -------------------------------------------------------------------------------- 1 | "\n", 96 | "\r" => "\n", 97 | ] 98 | ); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/Helpers/UniqueToStringArray.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | final class UniqueToStringArray implements \Countable, \IteratorAggregate 21 | { 22 | 23 | use UniqueArrayFeatures; 24 | use ArrayExtractableTrait; 25 | 26 | /** 27 | * @var array<\SmartEmailing\Types\ToStringInterface> 28 | */ 29 | private array $objects; 30 | 31 | private ?string $type = null; 32 | 33 | /** 34 | * @param array<\SmartEmailing\Types\ToStringInterface|mixed> $data 35 | * @throws \SmartEmailing\Types\InvalidTypeException 36 | */ 37 | private function __construct( 38 | array $data = [] 39 | ) 40 | { 41 | $this->objects = []; 42 | 43 | foreach ($data as $value) { 44 | if (!$value instanceof ToStringInterface) { 45 | throw InvalidTypeException::typeError( 46 | 'all members of array must implement ' . ToStringInterface::class, 47 | $value 48 | ); 49 | } 50 | 51 | $this->add($value); 52 | } 53 | } 54 | 55 | /** 56 | * @param array<\SmartEmailing\Types\Helpers\UniqueToStringArray> $uniqueToStringArrays 57 | * @return \SmartEmailing\Types\Helpers\UniqueToStringArray 58 | */ 59 | public static function intersect( 60 | array $uniqueToStringArrays 61 | ): self { 62 | if (\count($uniqueToStringArrays) === 1) { 63 | return \reset($uniqueToStringArrays); 64 | } 65 | 66 | $plainIntArrays = []; 67 | 68 | foreach ($uniqueToStringArrays as $uniqueToStringArray) { 69 | $plainIntArrays[] = $uniqueToStringArray->objects; 70 | } 71 | 72 | $result = \array_intersect_key( 73 | ...$plainIntArrays 74 | ); 75 | 76 | $output = new UniqueToStringArray([]); 77 | $output->objects = $result; 78 | 79 | return $output; 80 | } 81 | 82 | /** 83 | * @param array<\SmartEmailing\Types\Helpers\UniqueToStringArray> $uniqueIntArrays 84 | * @return \SmartEmailing\Types\Helpers\UniqueToStringArray 85 | */ 86 | public static function union( 87 | array $uniqueIntArrays 88 | ): self { 89 | $result = []; 90 | 91 | foreach ($uniqueIntArrays as $uniqueIntArray) { 92 | foreach ($uniqueIntArray->objects as $key => $object) { 93 | $result[$key] = $object; 94 | } 95 | } 96 | 97 | $output = new self([]); 98 | $output->objects = $result; 99 | 100 | return $output; 101 | } 102 | 103 | /** 104 | * @return \Traversable<\SmartEmailing\Types\ToStringInterface> 105 | */ 106 | public function getIterator(): \Traversable 107 | { 108 | return new \RecursiveArrayIterator($this->getValues()); 109 | } 110 | 111 | public function count(): int 112 | { 113 | return \count($this->objects); 114 | } 115 | 116 | /** 117 | * @return array<\SmartEmailing\Types\ToStringInterface> 118 | */ 119 | public function getValues(): array 120 | { 121 | return \array_values($this->objects); 122 | } 123 | 124 | /** 125 | * @return array<\SmartEmailing\Types\ToStringInterface> 126 | */ 127 | public function toArray(): array 128 | { 129 | return $this->getValues(); 130 | } 131 | 132 | public function add( 133 | ToStringInterface $valueObject 134 | ): bool { 135 | $type = $valueObject::class; 136 | 137 | if ($this->type === null) { 138 | $this->type = $type; 139 | } 140 | 141 | if ($this->type !== $type) { 142 | throw InvalidTypeException::typeError( 143 | 'all members of array must be of type ' . $this->type, 144 | $valueObject 145 | ); 146 | } 147 | 148 | $key = $valueObject->__toString(); 149 | 150 | if (!isset($this->objects[$key])) { 151 | $this->objects[$key] = $valueObject; 152 | 153 | return true; 154 | } 155 | 156 | return false; 157 | } 158 | 159 | public function remove( 160 | ToStringInterface $valueObject 161 | ): void { 162 | $key = $valueObject->__toString(); 163 | unset($this->objects[$key]); 164 | } 165 | 166 | public function isEmpty(): bool 167 | { 168 | return $this->objects === []; 169 | } 170 | 171 | public function contains( 172 | ToStringInterface $valueObject 173 | ): bool { 174 | $key = $valueObject->__toString(); 175 | 176 | return isset($this->objects[$key]); 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /src/Helpers/ValidationHelpers.php: -------------------------------------------------------------------------------- 1 | $array 12 | */ 13 | final public static function isTypedObjectArray( 14 | array $array, 15 | string $typeName 16 | ): bool { 17 | foreach ($array as $item) { 18 | if (!($item instanceof $typeName)) { 19 | return false; 20 | } 21 | } 22 | 23 | return true; 24 | } 25 | 26 | /** 27 | * Validates multidimensional array to have scalar or NULL leaves 28 | * 29 | * @param array $array 30 | */ 31 | final public static function isScalarLeavesArray( 32 | array $array 33 | ): bool { 34 | foreach ($array as $item) { 35 | if (\is_array($item)) { 36 | $isScalar = self::isScalarLeavesArray($item); 37 | 38 | if (!$isScalar) { 39 | return false; 40 | } 41 | } elseif ($item !== null && !\is_scalar($item)) { 42 | return false; 43 | } 44 | } 45 | 46 | return true; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/Hex32.php: -------------------------------------------------------------------------------- 1 | isValid($value)) { 25 | throw new InvalidTypeException('Invalid hex string: ' . $value); 26 | } 27 | 28 | $this->value = Strings::lower($value); 29 | } 30 | 31 | public static function fromGuid( 32 | Guid $guid 33 | ): Hex32 { 34 | return self::from( 35 | Strings::replace($guid->getValue(), '/-/', '') 36 | ); 37 | } 38 | 39 | public function getValue(): string 40 | { 41 | return $this->value; 42 | } 43 | 44 | private function isValid( 45 | string $value 46 | ): bool 47 | { 48 | if (Strings::length($value) !== 32) { 49 | return false; 50 | } 51 | 52 | return \ctype_xdigit($value); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/HexColor.php: -------------------------------------------------------------------------------- 1 | preProcess($value); 26 | 27 | if (!$this->isValid($value)) { 28 | throw new InvalidTypeException('Invalid hex color string: ' . $value); 29 | } 30 | 31 | $this->value = $value; 32 | } 33 | 34 | public function getValue(): string 35 | { 36 | return $this->value; 37 | } 38 | 39 | private function isValid( 40 | string $value 41 | ): bool 42 | { 43 | return (bool) \preg_match('#^\#([A-F0-9]{3}){1,2}\z#', $value); 44 | } 45 | 46 | private function preProcess( 47 | string $value 48 | ): string 49 | { 50 | return Strings::upper($value); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/HostName.php: -------------------------------------------------------------------------------- 1 | isValid($value)) { 29 | throw new InvalidTypeException('Invalid hostname: ' . $value); 30 | } 31 | 32 | $this->value = $value; 33 | } 34 | 35 | public function getValue(): string 36 | { 37 | return $this->value; 38 | } 39 | 40 | public function getSecondLevelDomain(): HostName 41 | { 42 | $parts = \explode('.', $this->value); 43 | $numberOfKeptParts = 2; 44 | 45 | if (\count($parts) > 2 && \end($parts) === 'uk') { 46 | $numberOfKeptParts = 3; 47 | } 48 | 49 | $secondLevelParts = \array_slice($parts, -$numberOfKeptParts, $numberOfKeptParts); 50 | 51 | return self::from(\implode('.', $secondLevelParts)); 52 | } 53 | 54 | private function isValid( 55 | string $value 56 | ): bool { 57 | return \preg_match('/^([a-z\\d](-*[a-z\\d])*)(\\.([a-z\\d](-*[a-z\\d])*))*$/i', $value) === 1 //valid chars check 58 | && \preg_match('/^.{1,253}$/', $value) === 1// overall length check 59 | && \preg_match('/^[^\\.]{1,63}(\\.[^\\.]{1,63})*$/', $value) === 1; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/HttpMethod.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class HttpMethod extends Enum implements ToStringInterface 13 | { 14 | 15 | use EnumExtractableTrait; 16 | use ToStringTrait; 17 | 18 | public const GET = 'GET'; 19 | 20 | public const HEAD = 'HEAD'; 21 | 22 | public const POST = 'POST'; 23 | 24 | public const PUT = 'PUT'; 25 | 26 | public const DELETE = 'DELETE'; 27 | 28 | public const CONNECT = 'CONNECT'; 29 | 30 | public const OPTIONS = 'OPTIONS'; 31 | 32 | public const TRACE = 'TRACE'; 33 | 34 | public const PATCH = 'PATCH'; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/Iban.php: -------------------------------------------------------------------------------- 1 | iban = new \Iban\Validation\Iban($value); 32 | $validator = new \Iban\Validation\Validator(); 33 | 34 | if (!$validator->validate($this->iban)) { 35 | throw new InvalidTypeException('Invalid Iban: ' . $value); 36 | } 37 | } 38 | 39 | public function getValue(): string 40 | { 41 | return $this->value; 42 | } 43 | 44 | public function getCountry(): CountryCode 45 | { 46 | return CountryCode::from($this->iban->countryCode()); 47 | } 48 | 49 | public function getFormatted( 50 | string $type = self::FORMAT_ELECTRONIC 51 | ): string 52 | { 53 | return $this->iban->format($type); 54 | } 55 | 56 | public function getChecksum(): int 57 | { 58 | return (int) $this->iban->checksum(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/IntArray.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final public static function from( 14 | mixed $value 15 | ): array { 16 | $array = Arrays::from($value); 17 | 18 | $return = []; 19 | 20 | foreach ($array as $index => $item) { 21 | $return[$index] = IntType::from($item); 22 | } 23 | 24 | return $return; 25 | } 26 | 27 | /** 28 | * @return array|null 29 | */ 30 | final public static function fromOrNull( 31 | mixed $value, 32 | bool $nullIfInvalid = false 33 | ): ?array { 34 | $array = Arrays::fromOrNull($value, $nullIfInvalid); 35 | 36 | if ($array === null) { 37 | return null; 38 | } 39 | 40 | $return = []; 41 | 42 | try { 43 | foreach ($array as $index => $item) { 44 | $return[$index] = IntType::from($item); 45 | } 46 | } catch (InvalidTypeException $e) { 47 | if ($nullIfInvalid) { 48 | return null; 49 | } 50 | 51 | throw $e; 52 | } 53 | 54 | return $return; 55 | } 56 | 57 | /** 58 | * @param array|\ArrayAccess $data 59 | * @return array 60 | * @throws \SmartEmailing\Types\InvalidTypeException 61 | */ 62 | final public static function extract( 63 | array|\ArrayAccess $data, 64 | string $key 65 | ): array { 66 | $value = Arrays::extract($data, $key); 67 | 68 | try { 69 | return self::from($value); 70 | } catch (InvalidTypeException $e) { 71 | throw $e->wrap($key); 72 | } 73 | } 74 | 75 | /** 76 | * @param array|\ArrayAccess $data 77 | * @return array|null 78 | * @throws \SmartEmailing\Types\InvalidTypeException 79 | */ 80 | final public static function extractOrNull( 81 | array|\ArrayAccess $data, 82 | string $key, 83 | bool $nullIfInvalid = false 84 | ): ?array { 85 | $value = Arrays::extractOrNull($data, $key, $nullIfInvalid); 86 | 87 | if ($value === null) { 88 | return null; 89 | } 90 | 91 | try { 92 | return self::fromOrNull($value, $nullIfInvalid); 93 | } catch (InvalidTypeException $exception) { 94 | throw $exception->wrap($key); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/IntType.php: -------------------------------------------------------------------------------- 1 | |\ArrayAccess $data 48 | * @throws \SmartEmailing\Types\InvalidTypeException 49 | */ 50 | final public static function extract( 51 | array|\ArrayAccess $data, 52 | string $key 53 | ): int { 54 | $value = ExtractableHelpers::extractValue($data, $key); 55 | 56 | try { 57 | return self::from($value); 58 | } catch (InvalidTypeException $e) { 59 | throw $e->wrap($key); 60 | } 61 | } 62 | 63 | /** 64 | * @param array|\ArrayAccess $data 65 | * @throws \SmartEmailing\Types\InvalidTypeException 66 | */ 67 | final public static function extractOrNull( 68 | array|\ArrayAccess $data, 69 | string $key, 70 | bool $nullIfInvalid = false 71 | ): ?int { 72 | $value = ExtractableHelpers::extractValueOrNull($data, $key); 73 | 74 | if ($value === null) { 75 | return null; 76 | } 77 | 78 | try { 79 | return self::fromOrNull($value, $nullIfInvalid); 80 | } catch (InvalidTypeException $exception) { 81 | throw $exception->wrap($key); 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | $expected 39 | */ 40 | public static function typesError( 41 | array $expected, 42 | mixed $value 43 | ): self { 44 | $type = self::getType($value); 45 | $description = self::getDescription($value); 46 | 47 | return new static( 48 | 'Expected types ' 49 | . '[' . \implode(', ', $expected) . ']' 50 | . ', got ' 51 | . $type 52 | . $description 53 | ); 54 | } 55 | 56 | public static function missingKey( 57 | string $key 58 | ): self { 59 | return new static('Missing key: ' . $key); 60 | } 61 | 62 | public static function cannotBeEmptyError( 63 | string $key 64 | ): self { 65 | return new static('Array at key ' . $key . ' must not be empty.'); 66 | } 67 | 68 | public function wrap( 69 | string $key 70 | ): self { 71 | $message = 'Problem at key ' 72 | . $key 73 | . ': ' 74 | . $this->getMessage(); 75 | 76 | return new static($message, $this->code, $this); 77 | } 78 | 79 | private static function getType( 80 | mixed $value 81 | ): string 82 | { 83 | $type = \gettype($value); 84 | 85 | if (\in_array($type, ['double', 'real'], true)) { 86 | $type = 'float'; 87 | } 88 | 89 | return $type; 90 | } 91 | 92 | private static function getDescription( 93 | mixed $value 94 | ): string 95 | { 96 | $description = ''; 97 | 98 | if (\is_scalar($value)) { 99 | $stringValue = (string) $value; 100 | $stringValue = StringHelpers::sanitize($stringValue); 101 | $description = ' (' . $stringValue . ')'; 102 | } elseif (\is_object($value)) { 103 | $description = ' (' . $value::class . ')'; 104 | } 105 | 106 | return $description; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/IpAddress.php: -------------------------------------------------------------------------------- 1 | initialize($value); 27 | } 28 | 29 | public function getValue(): string 30 | { 31 | return $this->value; 32 | } 33 | 34 | public function getVersion(): int 35 | { 36 | return $this->version; 37 | } 38 | 39 | private function isValidIpV4( 40 | string $value 41 | ): bool { 42 | return (bool) \preg_match( 43 | '~^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$~', 44 | $value 45 | ); 46 | } 47 | 48 | private function isValidIpV6( 49 | string $value 50 | ): bool { 51 | return (bool) \preg_match( 52 | '~^(((?=(?>.*?(::))(?!.+\3)))\3?|([\dA-F]{1,4}(\3|:(?!$)|$)|\2))(?4){5}((?4){2}|((2[0-4]|1\d|[1-9])?\d|25[0-5])(\.(?7)){3})\z~i', 53 | $value 54 | ); 55 | } 56 | 57 | private function initialize( 58 | string $value 59 | ): bool { 60 | $value = Strings::trim($value); 61 | $value = Strings::lower($value); 62 | $value = \strtr( 63 | $value, 64 | [ 65 | '[' => '', 66 | ']' => '', 67 | ] 68 | ); 69 | 70 | if ($this->isValidIpV4($value)) { 71 | $this->value = $value; 72 | $this->version = 4; 73 | 74 | return true; 75 | } 76 | 77 | if ($this->isValidIpV6($value)) { 78 | $this->value = $value; 79 | $this->version = 6; 80 | 81 | return true; 82 | } 83 | 84 | throw new InvalidTypeException('Invalid IP address: ' . $value); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/JsonString.php: -------------------------------------------------------------------------------- 1 | isValid($value)) { 25 | throw new InvalidTypeException('Invalid JSON string'); 26 | } 27 | } 28 | 29 | public static function encode( 30 | mixed $value, 31 | bool $oneLine = false 32 | ): self 33 | { 34 | try { 35 | return new static( 36 | Json::encode( 37 | $value, 38 | $oneLine ? 0 : Json::PRETTY 39 | ) 40 | ); 41 | } catch (JsonException $e) { 42 | throw new InvalidTypeException($e->getMessage()); 43 | } 44 | } 45 | 46 | public function getValue(): string 47 | { 48 | return $this->value; 49 | } 50 | 51 | public function getDecodedValue(): mixed 52 | { 53 | return Json::decode($this->value, \JSON_OBJECT_AS_ARRAY); 54 | } 55 | 56 | /** 57 | * @throws \SmartEmailing\Types\InvalidTypeException 58 | */ 59 | public static function from( 60 | mixed $data 61 | ): static 62 | { 63 | if ($data instanceof self) { 64 | return $data; 65 | } 66 | 67 | $string = StringType::fromOrNull($data, true); 68 | 69 | if (\is_string($string)) { 70 | return new static($string); 71 | } 72 | 73 | $array = Arrays::fromOrNull($data, true); 74 | 75 | if (\is_array($array)) { 76 | return self::encode($data); 77 | } 78 | 79 | throw InvalidTypeException::typesError(['string', 'array'], $data); 80 | } 81 | 82 | private function isValid( 83 | string $value 84 | ): bool 85 | { 86 | try { 87 | Json::decode($value); 88 | 89 | return true; 90 | } catch (JsonException $e) { 91 | return false; 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/KeyValuePair.php: -------------------------------------------------------------------------------- 1 | $data 23 | */ 24 | private function __construct( 25 | array $data 26 | ) { 27 | $this->key = StringType::extract($data, 'key'); 28 | $this->value = StringType::extract($data, 'value'); 29 | } 30 | 31 | public function getKey(): string 32 | { 33 | return $this->key; 34 | } 35 | 36 | public function getValue(): string 37 | { 38 | return $this->value; 39 | } 40 | 41 | /** 42 | * @return array 43 | */ 44 | public function toArray(): array 45 | { 46 | return [ 47 | 'key' => $this->key, 48 | 'value' => $this->value, 49 | ]; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/LawfulBasisForProcessing.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final class LawfulBasisForProcessing extends Enum 17 | { 18 | 19 | use EnumExtractableTrait; 20 | 21 | /** 22 | * The individual has given clear consent for you 23 | * to process their personal data for a specific purpose. 24 | */ 25 | public const CONSENT = 'consent'; 26 | 27 | /** 28 | * The processing is necessary for a contract you have 29 | * with the individual, or because they have asked you 30 | * to take specific steps before entering into a contract. 31 | */ 32 | public const CONTRACT = 'contract'; 33 | 34 | /** 35 | * The processing is necessary for you to comply 36 | * with the law (not including contractual obligations). 37 | */ 38 | public const LEGAL_OBLIGATION = 'legal-obligation'; 39 | 40 | /** 41 | * The processing is necessary for your legitimate interests 42 | * or the legitimate interests of a third party unless 43 | * there is a good reason to protect the individual’s personal 44 | * data which overrides those legitimate interests. 45 | * (This cannot apply if you are a public authority processing 46 | * data to perform your official tasks.) 47 | */ 48 | public const LEGITIMATE_INTEREST = 'legitimate-interest'; 49 | 50 | /** 51 | * The processing is necessary to protect someone’s life. 52 | */ 53 | public const VITAL_INTEREST = 'vital-interest'; 54 | 55 | /** 56 | * The processing is necessary for you to perform a task 57 | * in the public interest or for your official functions, 58 | * and the task or function has a clear basis in law. 59 | */ 60 | public const PUBLIC_TASK = 'public-task'; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/LoginCredentials.php: -------------------------------------------------------------------------------- 1 | $data 23 | */ 24 | private function __construct( 25 | array $data 26 | ) { 27 | $this->login = StringType::extract($data, 'login'); 28 | $this->password = StringType::extract($data, 'password'); 29 | } 30 | 31 | public function getLogin(): string 32 | { 33 | return $this->login; 34 | } 35 | 36 | public function getPassword(): string 37 | { 38 | return $this->password; 39 | } 40 | 41 | /** 42 | * @return array 43 | */ 44 | public function toArray(): array 45 | { 46 | return [ 47 | 'login' => $this->login, 48 | 'password' => $this->password, 49 | ]; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/NonEmptyString.php: -------------------------------------------------------------------------------- 1 | value = $value; 35 | } 36 | 37 | /** 38 | * @return non-empty-string 39 | */ 40 | public function getValue(): string 41 | { 42 | return $this->value; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/Part.php: -------------------------------------------------------------------------------- 1 | 1) { 22 | throw new InvalidTypeException('Invalid part: ' . $value); 23 | } 24 | } 25 | 26 | public static function fromRatio( 27 | float $value, 28 | float $whole 29 | ): self { 30 | if ($value > $whole) { 31 | throw new InvalidTypeException( 32 | 'Value cannot be higher than whole: but ' 33 | . $value 34 | . ' / ' 35 | . $whole 36 | . ' given.' 37 | ); 38 | } 39 | 40 | if ($whole === 0.0) { 41 | return new static(0.0); 42 | } 43 | 44 | return new static($value / $whole); 45 | } 46 | 47 | public function getValue(): float 48 | { 49 | return $this->value; 50 | } 51 | 52 | public function getPercent(): float 53 | { 54 | return $this->getValue() * 100; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/PhoneNumber.php: -------------------------------------------------------------------------------- 1 | initialize($value); 27 | 28 | if ($preprocessed === null) { 29 | throw new InvalidTypeException('Invalid phone number: ' . $value); 30 | } 31 | 32 | $this->value = $preprocessed; 33 | } 34 | 35 | /** 36 | * @deprecated use PhoneNumber::guessCountry() 37 | */ 38 | public function getCountry(): ?CountryCode 39 | { 40 | return $this->guessCountry(); 41 | } 42 | 43 | public function guessCountry(): ?CountryCode 44 | { 45 | $input = CountryCodeToPhoneCodeTable::$countryCodesToPhoneCodes; 46 | 47 | \uasort( 48 | $input, 49 | static fn (int $a, int $b): int => Strings::length((string) $b) <=> Strings::length((string) $a) 50 | ); 51 | 52 | $justNumbers = Strings::replace( 53 | $this->value, 54 | [ 55 | '~[^\d]~' => '', 56 | ] 57 | ); 58 | 59 | foreach ($input as $countryCode => $dial) { 60 | if (Strings::startsWith($justNumbers, (string) $dial)) { 61 | return CountryCode::from($countryCode); 62 | } 63 | } 64 | 65 | return null; 66 | } 67 | 68 | public function getValue(): string 69 | { 70 | return $this->value; 71 | } 72 | 73 | public static function preprocess( 74 | string $value 75 | ): string 76 | { 77 | $value = Strings::replace( 78 | $value, 79 | [ 80 | '~\(0\)~' => '', 81 | ] 82 | ); 83 | 84 | $value = Strings::replace( 85 | $value, 86 | [ 87 | '~[\s\(\)\-]~' => '', 88 | ] 89 | ); 90 | 91 | if (\str_starts_with($value, '00')) { 92 | $value = '+' . Strings::substring($value, 2); 93 | } 94 | 95 | return $value; 96 | } 97 | 98 | private function initialize( 99 | string $value 100 | ): ?string 101 | { 102 | $value = self::preprocess($value); 103 | 104 | $match = \preg_match( 105 | '~^\+?[\d]{5,19}$~', 106 | $value, 107 | $m 108 | ); 109 | 110 | if ($match !== 1) { 111 | return null; 112 | } 113 | 114 | return $value; 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/Port.php: -------------------------------------------------------------------------------- 1 | 65_535) { 22 | throw new InvalidTypeException('Invalid Port number: ' . $value); 23 | } 24 | } 25 | 26 | public function getValue(): int 27 | { 28 | return $this->value; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/Price.php: -------------------------------------------------------------------------------- 1 | $data 25 | */ 26 | private function __construct( 27 | array $data 28 | ) 29 | { 30 | $this->withoutVat = FloatType::extract($data, 'without_vat'); 31 | $this->withVat = FloatType::extract($data, 'with_vat'); 32 | $this->currency = CurrencyCode::extract($data, 'currency'); 33 | } 34 | 35 | public function getWithoutVat(): float 36 | { 37 | return $this->withoutVat; 38 | } 39 | 40 | public function getWithVat(): float 41 | { 42 | return $this->withVat; 43 | } 44 | 45 | public function getCurrency(): CurrencyCode 46 | { 47 | return $this->currency; 48 | } 49 | 50 | public function calculateVatRatePercent(): float 51 | { 52 | return \round(($this->withVat / $this->withoutVat - 1) * 100, 3); 53 | } 54 | 55 | /** 56 | * @return array 57 | */ 58 | public function toArray(): array 59 | { 60 | return [ 61 | 'without_vat' => $this->withoutVat, 62 | 'with_vat' => $this->withVat, 63 | 'currency' => $this->currency->getValue(), 64 | ]; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/Quantity.php: -------------------------------------------------------------------------------- 1 | \PHP_INT_MAX) { 22 | throw new InvalidTypeException('Invalid quantity: ' . $value); 23 | } 24 | } 25 | 26 | public function getValue(): int 27 | { 28 | return $this->value; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/ReLUValue.php: -------------------------------------------------------------------------------- 1 | value; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/Relation.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class Relation extends Enum 13 | { 14 | 15 | use EnumExtractableTrait; 16 | 17 | public const AND = 'AND'; 18 | 19 | public const OR = 'OR'; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/ScalarLeavesArray.php: -------------------------------------------------------------------------------- 1 | $data 20 | */ 21 | public function __construct( 22 | private array $data 23 | ) { 24 | if (!ValidationHelpers::isScalarLeavesArray($data)) { 25 | throw new InvalidTypeException('Array must have all it\'s leaves scalar or null'); 26 | } 27 | } 28 | 29 | /** 30 | * @param array $data 31 | */ 32 | public static function extractOrEmpty( 33 | array $data, 34 | string $key 35 | ): self { 36 | $self = self::extractOrNull( 37 | $data, 38 | $key 39 | ); 40 | 41 | if ($self !== null) { 42 | return $self; 43 | } 44 | 45 | return new self([]); 46 | } 47 | 48 | /** 49 | * @return array 50 | */ 51 | public function toArray(): array 52 | { 53 | return $this->data; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/SigmoidValue.php: -------------------------------------------------------------------------------- 1 | 1) { 22 | throw new InvalidTypeException('Invalid sigmoid value: ' . $value); 23 | } 24 | } 25 | 26 | public function getValue(): float 27 | { 28 | return $this->value; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/StringArray.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final public static function from( 14 | mixed $value 15 | ): array { 16 | $array = Arrays::from($value); 17 | 18 | $return = []; 19 | 20 | foreach ($array as $index => $item) { 21 | $return[$index] = StringType::from($item); 22 | } 23 | 24 | return $return; 25 | } 26 | 27 | /** 28 | * @return array|null 29 | */ 30 | final public static function fromOrNull( 31 | mixed $value, 32 | bool $nullIfInvalid = false 33 | ): ?array { 34 | $array = Arrays::fromOrNull($value, $nullIfInvalid); 35 | 36 | if ($array === null) { 37 | return null; 38 | } 39 | 40 | $return = []; 41 | 42 | try { 43 | foreach ($array as $index => $item) { 44 | $return[$index] = StringType::from($item); 45 | } 46 | } catch (InvalidTypeException $e) { 47 | if ($nullIfInvalid) { 48 | return null; 49 | } 50 | 51 | throw $e; 52 | } 53 | 54 | return $return; 55 | } 56 | 57 | /** 58 | * @param array|\ArrayAccess $data 59 | * @return array 60 | * @throws \SmartEmailing\Types\InvalidTypeException 61 | */ 62 | final public static function extract( 63 | array|\ArrayAccess $data, 64 | string $key 65 | ): array { 66 | $value = Arrays::extract($data, $key); 67 | 68 | try { 69 | return self::from($value); 70 | } catch (InvalidTypeException $e) { 71 | throw $e->wrap($key); 72 | } 73 | } 74 | 75 | /** 76 | * @param array|\ArrayAccess $data 77 | * @return array|null 78 | * @throws \SmartEmailing\Types\InvalidTypeException 79 | */ 80 | final public static function extractOrNull( 81 | array|\ArrayAccess $data, 82 | string $key, 83 | bool $nullIfInvalid = false 84 | ): ?array { 85 | $value = Arrays::extractOrNull($data, $key, $nullIfInvalid); 86 | 87 | if ($value === null) { 88 | return null; 89 | } 90 | 91 | try { 92 | return self::fromOrNull($value, $nullIfInvalid); 93 | } catch (InvalidTypeException $exception) { 94 | throw $exception->wrap($key); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/StringType.php: -------------------------------------------------------------------------------- 1 | |\ArrayAccess $data 43 | * @throws \SmartEmailing\Types\InvalidTypeException 44 | */ 45 | final public static function extract( 46 | array|\ArrayAccess $data, 47 | string $key 48 | ): string { 49 | $value = ExtractableHelpers::extractValue($data, $key); 50 | 51 | try { 52 | return self::from($value); 53 | } catch (InvalidTypeException $e) { 54 | throw $e->wrap($key); 55 | } 56 | } 57 | 58 | /** 59 | * @param \ArrayAccess|array $data 60 | * @throws \SmartEmailing\Types\InvalidTypeException 61 | */ 62 | final public static function extractOrNull( 63 | array|\ArrayAccess $data, 64 | string $key, 65 | bool $nullIfInvalid = false 66 | ): ?string { 67 | $value = ExtractableHelpers::extractValueOrNull($data, $key); 68 | 69 | if ($value === null || $value === '') { 70 | return null; 71 | } 72 | 73 | try { 74 | return self::fromOrNull($value, $nullIfInvalid); 75 | } catch (InvalidTypeException $exception) { 76 | throw $exception->wrap($key); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/SwiftBic.php: -------------------------------------------------------------------------------- 1 | isValid($this->value)) { 24 | throw new InvalidTypeException('Invalid Swift/Bic: ' . $value); 25 | } 26 | } 27 | 28 | public function getValue(): string 29 | { 30 | return $this->value; 31 | } 32 | 33 | private function isValid( 34 | string $value 35 | ): bool 36 | { 37 | return (bool) Strings::match( 38 | $value, 39 | '/^([a-zA-Z]){4}([a-zA-Z]){2}([0-9a-zA-Z]){2}([0-9a-zA-Z]{3})?$/' 40 | ); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/TimeUnit.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class TimeUnit extends Enum implements ToStringInterface 13 | { 14 | 15 | use EnumExtractableTrait; 16 | use ToStringTrait; 17 | 18 | public const SECONDS = 'seconds'; 19 | 20 | public const MINUTES = 'minutes'; 21 | 22 | public const HOURS = 'hours'; 23 | 24 | public const DAYS = 'days'; 25 | 26 | public const WEEKS = 'weeks'; 27 | 28 | public const MONTHS = 'months'; 29 | 30 | public const YEARS = 'years'; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/ToArrayInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | public function toArray(): array; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/ToStringInterface.php: -------------------------------------------------------------------------------- 1 | getValue(); // @phpstan-ignore-line 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/UniqueArrayFeatures.php: -------------------------------------------------------------------------------- 1 | $data 17 | */ 18 | public static function extractOrEmpty( 19 | array $data, 20 | string $key 21 | ): self { 22 | $self = self::extractOrNull( 23 | $data, 24 | $key 25 | ); 26 | 27 | if ($self !== null) { 28 | return $self; 29 | } 30 | 31 | return self::empty(); 32 | } 33 | 34 | /** 35 | * @return array 36 | */ 37 | public function split( 38 | int $chunkSize 39 | ): array { 40 | if ($chunkSize < 1) { 41 | throw new \Exception('Parameter $chunkSize must be greater than 0.'); 42 | } 43 | 44 | $return = []; 45 | $chunks = \array_chunk( 46 | $this->getValues(), 47 | $chunkSize 48 | ); 49 | 50 | foreach ($chunks as $chunk) { 51 | $return[] = self::from($chunk); 52 | } 53 | 54 | return $return; 55 | } 56 | 57 | public function merge( 58 | self $toBeMerged 59 | ): self { 60 | $dolly = clone $this; 61 | 62 | foreach ($toBeMerged->getValues() as $value) { 63 | $dolly->add($value); 64 | } 65 | 66 | return $dolly; 67 | } 68 | 69 | public function deduct( 70 | self $toBeDeducted 71 | ): self { 72 | $dolly = clone $this; 73 | 74 | foreach ($toBeDeducted->getValues() as $value) { 75 | $dolly->remove($value); 76 | } 77 | 78 | return $dolly; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/UniqueIntArray.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class UniqueIntArray implements \Countable, \IteratorAggregate, ToArrayInterface, ComparableInterface 15 | { 16 | 17 | use ArrayExtractableTrait; 18 | use UniqueArrayFeatures; 19 | use ArrayComparableTrait; 20 | 21 | /** 22 | * @var array 23 | */ 24 | private array $valuesPresenceMap; 25 | 26 | /** 27 | * @param array $data 28 | */ 29 | private function __construct( 30 | array $data = [] 31 | ) 32 | { 33 | $this->valuesPresenceMap = []; 34 | 35 | foreach ($data as $value) { 36 | try { 37 | $this->add(IntType::from($value)); 38 | } catch (InvalidTypeException $e) { 39 | throw InvalidTypeException::typeError('all members of array to be int', $value); 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * @param array $data 46 | */ 47 | public static function extractNotEmpty( 48 | array $data, 49 | string $key 50 | ): self { 51 | $self = self::extract( 52 | $data, 53 | $key 54 | ); 55 | 56 | if ($self->isEmpty()) { 57 | throw InvalidTypeException::cannotBeEmptyError($key); 58 | } 59 | 60 | return $self; 61 | } 62 | 63 | /** 64 | * @param array<\SmartEmailing\Types\UniqueIntArray> $uniqueIntArrays 65 | */ 66 | public static function intersect( 67 | array $uniqueIntArrays 68 | ): UniqueIntArray { 69 | if (\count($uniqueIntArrays) === 1) { 70 | return \reset($uniqueIntArrays); 71 | } 72 | 73 | $plainIntArrays = []; 74 | 75 | foreach ($uniqueIntArrays as $uniqueIntArray) { 76 | $plainIntArrays[] = $uniqueIntArray->valuesPresenceMap; 77 | } 78 | 79 | $result = \array_intersect_key( 80 | ...$plainIntArrays 81 | ); 82 | 83 | $output = new UniqueIntArray([]); 84 | $output->valuesPresenceMap = $result; 85 | 86 | return $output; 87 | } 88 | 89 | /** 90 | * @param array<\SmartEmailing\Types\UniqueIntArray> $uniqueIntArrays 91 | */ 92 | public static function union( 93 | array $uniqueIntArrays 94 | ): UniqueIntArray { 95 | $result = []; 96 | 97 | foreach ($uniqueIntArrays as $uniqueIntArray) { 98 | foreach ($uniqueIntArray->valuesPresenceMap as $key => $true) { 99 | $result[$key] = $true; 100 | } 101 | } 102 | 103 | $output = new UniqueIntArray([]); 104 | $output->valuesPresenceMap = $result; 105 | 106 | return $output; 107 | } 108 | 109 | /** 110 | * @return \Traversable 111 | */ 112 | public function getIterator(): \Traversable 113 | { 114 | return new \RecursiveArrayIterator($this->getValues()); 115 | } 116 | 117 | public function count(): int 118 | { 119 | return \count($this->valuesPresenceMap); 120 | } 121 | 122 | /** 123 | * @return array 124 | */ 125 | public function getValues(): array 126 | { 127 | return \array_keys($this->valuesPresenceMap); 128 | } 129 | 130 | /** 131 | * @return array 132 | */ 133 | public function toArray(): array 134 | { 135 | return $this->getValues(); 136 | } 137 | 138 | public function add( 139 | int $id 140 | ): bool { 141 | if (!isset($this->valuesPresenceMap[$id])) { 142 | $this->valuesPresenceMap[$id] = true; 143 | 144 | return true; 145 | } 146 | 147 | return false; 148 | } 149 | 150 | public function remove( 151 | int $id 152 | ): void 153 | { 154 | unset($this->valuesPresenceMap[$id]); 155 | } 156 | 157 | public function contains( 158 | int $id 159 | ): bool 160 | { 161 | return isset($this->valuesPresenceMap[$id]); 162 | } 163 | 164 | public function isEmpty(): bool 165 | { 166 | return $this->valuesPresenceMap === []; 167 | } 168 | 169 | public function orderASC(): void 170 | { 171 | \ksort($this->valuesPresenceMap); 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/UniqueStringArray.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class UniqueStringArray implements \Countable, \IteratorAggregate, ToArrayInterface, ComparableInterface 15 | { 16 | 17 | use ArrayExtractableTrait; 18 | use UniqueArrayFeatures; 19 | use ArrayComparableTrait; 20 | 21 | /** 22 | * @var array 23 | */ 24 | private array $valuesPresenceMap; 25 | 26 | /** 27 | * @param array $data 28 | */ 29 | private function __construct( 30 | array $data = [] 31 | ) 32 | { 33 | $this->valuesPresenceMap = []; 34 | 35 | foreach ($data as $value) { 36 | try { 37 | $this->add(StringType::from($value)); 38 | } catch (InvalidTypeException $e) { 39 | throw InvalidTypeException::typeError('all members of array to be string', $value); 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * @return \Traversable 46 | */ 47 | public function getIterator(): \Traversable 48 | { 49 | return new \RecursiveArrayIterator($this->getValues()); 50 | } 51 | 52 | public function count(): int 53 | { 54 | return \count($this->valuesPresenceMap); 55 | } 56 | 57 | /** 58 | * @return array 59 | */ 60 | public function getValues(): array 61 | { 62 | // numeric keys are implicitly converted to int. We must be sure to return strings. 63 | return \array_map( 64 | '\strval', 65 | \array_keys($this->valuesPresenceMap) 66 | ); 67 | } 68 | 69 | /** 70 | * @return array 71 | */ 72 | public function toArray(): array 73 | { 74 | return $this->getValues(); 75 | } 76 | 77 | /** 78 | * @deprecated This method does nothing because array is already unique 79 | */ 80 | public function removeDuplicities(): void 81 | { 82 | $this->valuesPresenceMap = \array_unique($this->valuesPresenceMap); 83 | } 84 | 85 | public function add( 86 | string $id 87 | ): bool { 88 | if (!isset($this->valuesPresenceMap[$id])) { 89 | $this->valuesPresenceMap[$id] = true; 90 | 91 | return true; 92 | } 93 | 94 | return false; 95 | } 96 | 97 | public function remove( 98 | string $id 99 | ): void 100 | { 101 | unset($this->valuesPresenceMap[$id]); 102 | } 103 | 104 | public function isEmpty(): bool 105 | { 106 | return $this->valuesPresenceMap === []; 107 | } 108 | 109 | public function contains( 110 | string $value 111 | ): bool { 112 | return isset($this->valuesPresenceMap[$value]); 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/UnsignedFloat.php: -------------------------------------------------------------------------------- 1 | \PHP_INT_MAX) { 22 | throw new InvalidTypeException('Invalid unsigned float: ' . $value); 23 | } 24 | } 25 | 26 | public function getValue(): float 27 | { 28 | return $this->value; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/UnsignedInt.php: -------------------------------------------------------------------------------- 1 | \PHP_INT_MAX) { 22 | throw new InvalidTypeException('Invalid unsigned integer: ' . $value); 23 | } 24 | } 25 | 26 | public function getValue(): int 27 | { 28 | return $this->value; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/UrlType.php: -------------------------------------------------------------------------------- 1 | '%20', 33 | ] 34 | ); 35 | 36 | // urlencode non-ascii chars 37 | $value = (string) \preg_replace_callback( 38 | '/[^\x20-\x7f]/', 39 | static fn ($match): string => \urlencode($match[0]), 40 | $value 41 | ); 42 | 43 | // Nette\Utils 2.4 has a bug, URLs without slash in empty path are treated as invalid 44 | $value = $this->addSlashToPathOrFail($value) ?? $value; 45 | 46 | if (!Validators::isUrl($value)) { 47 | throw new InvalidTypeException('Invalid URL or missing protocol: ' . $value); 48 | } 49 | 50 | try { 51 | $this->url = new Url($value); 52 | } catch (InvalidArgumentException $e) { 53 | throw new InvalidTypeException('Invalid URL or missing protocol: ' . $value); 54 | } 55 | } 56 | 57 | public function getAuthority(): string 58 | { 59 | return $this->url->getAuthority(); 60 | } 61 | 62 | public function getHost(): string 63 | { 64 | return $this->url->getHost(); 65 | } 66 | 67 | public function getQueryString(): string 68 | { 69 | return $this->url->getQuery(); 70 | } 71 | 72 | public function getPath(): string 73 | { 74 | return $this->url->getPath(); 75 | } 76 | 77 | public function getAbsoluteUrl(): string 78 | { 79 | return $this->getValue(); 80 | } 81 | 82 | /** 83 | * @deprecated use getQueryParameter instead 84 | */ 85 | public function getParameter( 86 | string $name 87 | ): ?string 88 | { 89 | return $this->url->getQueryParameter($name) ?? null; 90 | } 91 | 92 | public function getBaseUrl(): string 93 | { 94 | return $this->url->getBaseUrl(); 95 | } 96 | 97 | /** 98 | * @deprecated use getValue or (string) type cast 99 | */ 100 | public function toString(): string 101 | { 102 | return (string) $this->url; 103 | } 104 | 105 | public function getScheme(): string 106 | { 107 | return $this->url->getScheme(); 108 | } 109 | 110 | /** 111 | * @param array $names 112 | */ 113 | public function hasParameters( 114 | array $names 115 | ): bool 116 | { 117 | $parameters = \array_keys($this->url->getQueryParameters()); 118 | 119 | foreach ($names as $name) { 120 | if (!\in_array($name, $parameters, true)) { 121 | return false; 122 | } 123 | } 124 | 125 | return true; 126 | } 127 | 128 | /** 129 | * @return array 130 | */ 131 | public function getParameters(): array 132 | { 133 | return $this->url->getQueryParameters(); 134 | } 135 | 136 | public function withQueryParameter( 137 | string $name, 138 | mixed $value 139 | ): self 140 | { 141 | $dolly = clone $this; 142 | $dolly->url->setQueryParameter( 143 | $name, 144 | $value 145 | ); 146 | 147 | return $dolly; 148 | } 149 | 150 | /** 151 | * @deprecated use withHostName 152 | */ 153 | public function withHost( 154 | Domain $host 155 | ): self 156 | { 157 | $dolly = clone $this; 158 | $dolly->url->setHost( 159 | $host->getValue() 160 | ); 161 | 162 | return $dolly; 163 | } 164 | 165 | public function withHostName( 166 | HostName $host 167 | ): self 168 | { 169 | $dolly = clone $this; 170 | $dolly->url->setHost( 171 | $host->getValue() 172 | ); 173 | 174 | return $dolly; 175 | } 176 | 177 | public function withScheme( 178 | string $scheme 179 | ): self 180 | { 181 | $dolly = clone $this; 182 | $dolly->url->setScheme( 183 | $scheme 184 | ); 185 | 186 | return $dolly; 187 | } 188 | 189 | public function withPath( 190 | string $path 191 | ): self 192 | { 193 | $dolly = clone $this; 194 | $dolly->url->setPath( 195 | $path 196 | ); 197 | 198 | return $dolly; 199 | } 200 | 201 | public function getQueryParameter( 202 | string $name, 203 | mixed $default = null 204 | ): mixed 205 | { 206 | return $this->url->getQueryParameter($name) ?? $default; 207 | } 208 | 209 | public function getValue(): string 210 | { 211 | return $this->url->getAbsoluteUrl(); 212 | } 213 | 214 | private function addSlashToPathOrFail( 215 | string $value 216 | ): ?string 217 | { 218 | $urlParts = \parse_url($value); 219 | 220 | if ($urlParts === false) { 221 | return null; 222 | } 223 | 224 | return $this->buildUrl($urlParts); 225 | } 226 | 227 | /** 228 | * @param array $urlParts 229 | */ 230 | private function buildUrl( 231 | array $urlParts 232 | ): string 233 | { 234 | $scheme = isset($urlParts['scheme']) 235 | ? $urlParts['scheme'] . '://' 236 | : ''; 237 | $host = $urlParts['host'] ?? ''; 238 | $port = isset($urlParts['port']) 239 | ? ':' . $urlParts['port'] 240 | : ''; 241 | $user = $urlParts['user'] ?? ''; 242 | $pass = isset($urlParts['pass']) 243 | ? ':' . $urlParts['pass'] 244 | : ''; 245 | $pass = $user !== '' || $pass !== '' 246 | ? $pass . '@' 247 | : ''; 248 | $path = $urlParts['path'] ?? '/'; 249 | $query = isset($urlParts['query']) 250 | ? '?' . $urlParts['query'] 251 | : ''; 252 | $fragment = isset($urlParts['fragment']) 253 | ? '#' . $urlParts['fragment'] 254 | : ''; 255 | 256 | return $scheme . $user . $pass . $host . $port . $path . $query . $fragment; 257 | } 258 | 259 | public function __clone() 260 | { 261 | $this->url = clone $this->url; 262 | } 263 | 264 | } 265 | -------------------------------------------------------------------------------- /src/ZipCode.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | private static array $patternsByCountry = [ 27 | CountryCode::CZ => '#^\d{3}?\d{2}\z#', 28 | CountryCode::SK => '#^\d{3}?\d{2}\z#', 29 | CountryCode::AT => '#^\d{4}\z#', 30 | CountryCode::BE => '#^\d{4}\z#', 31 | CountryCode::FR => '#^\d{2}?\d{3}\z#', 32 | CountryCode::HU => '#^\d{4}\z#', 33 | CountryCode::GB => '#^GIR?0AA|(?:(?:AB|AL|B|BA|BB|BD|BH|BL|BN|BR|BS|BT|BX|CA|CB|CF|CH|CM|CO|CR|CT|CV|CW|DA|DD|DE|DG|DH|DL|DN|DT|DY|E|EC|EH|EN|EX|FK|FY|G|GL|GY|GU|HA|HD|HG|HP|HR|HS|HU|HX|IG|IM|IP|IV|JE|KA|KT|KW|KY|L|LA|LD|LE|LL|LN|LS|LU|M|ME|MK|ML|N|NE|NG|NN|NP|NR|NW|OL|OX|PA|PE|PH|PL|PO|PR|RG|RH|RM|S|SA|SE|SG|SK|SL|SM|SN|SO|SP|SR|SS|ST|SW|SY|TA|TD|TF|TN|TQ|TR|TS|TW|UB|W|WA|WC|WD|WF|WN|WR|WS|WV|YO|ZE)(?:\d[\dA-Z]??\d[ABD-HJLN-UW-Z]{2}))|BFPO?\d{1,4}\z#', 34 | CountryCode::DE => '#^\d{5}\z#', 35 | CountryCode::US => '#^(\d{5})(?:[\-](\d{4}))?\z#', 36 | CountryCode::PL => '#^\d{2}-\d{3}\z#', 37 | CountryCode::IT => '#^\d{5}\z#', 38 | CountryCode::SE => '#^\d{3}?\d{2}\z#', 39 | CountryCode::SI => '#^\d{4}\z#', 40 | CountryCode::MH => '#^(969[67]\d)(?:[\-](\d{4}))?\z#', 41 | CountryCode::NL => '#^\d{4}?[A-Z]{2}\z#', 42 | CountryCode::CY => '#^\d{4}\z#', 43 | CountryCode::IE => '#^[\dA-Z]{3}?[\dA-Z]{4}\z#', 44 | CountryCode::DK => '#^\d{4}\z#', 45 | CountryCode::FI => '#^\d{5}\z#', 46 | CountryCode::LU => '#^\d{4}\z#', 47 | CountryCode::MT => '#^[A-Z]{3}?\d{2,4}\z#', 48 | ]; 49 | 50 | private function __construct( 51 | string $value 52 | ) { 53 | $value = StringHelpers::removeWhitespace($value); 54 | $value = Strings::upper($value); 55 | 56 | if (!$this->isValid($value)) { 57 | throw new InvalidTypeException('Invalid ZIP code: ' . $value); 58 | } 59 | 60 | $this->value = $value; 61 | } 62 | 63 | public function getValue(): string 64 | { 65 | return $this->value; 66 | } 67 | 68 | private function isValid( 69 | string $value 70 | ): bool { 71 | foreach (self::$patternsByCountry as $pattern) { 72 | if (Strings::match($value, $pattern) !== null) { 73 | return true; 74 | } 75 | } 76 | 77 | return false; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /tests/.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: github-actions 2 | coverage_clover: coverage.xml 3 | json_path: coverage.json 4 | -------------------------------------------------------------------------------- /tests/AddressTest.phpt: -------------------------------------------------------------------------------- 1 | 'Testovací 123', 19 | 'town' => 'Želeč', 20 | 'zip_code' => '391 74', 21 | 'country' => 'CZ', 22 | ]; 23 | 24 | Assert::throws( 25 | static function (): void { 26 | Address::from([]); 27 | }, 28 | InvalidTypeException::class 29 | ); 30 | 31 | $address = Address::from($data); 32 | 33 | Assert::true($address->equals(Address::from($data))); 34 | Assert::false($address->equals(Address::from([ 35 | 'street_and_number' => 'Netestovací 11', 36 | 'town' => 'Želeč', 37 | 'zip_code' => '391 74', 38 | 'country' => 'CZ', 39 | ]))); 40 | 41 | $address = Address::from($data); 42 | 43 | Assert::equal($data['country'], $address->getCountry()->getValue()); 44 | Assert::equal($data['street_and_number'], $address->getStreetAndNumber()); 45 | Assert::equal('39174', $address->getZipCode()->getValue()); 46 | Assert::equal($data['town'], $address->getTown()); 47 | } 48 | 49 | } 50 | 51 | (new AddressTest())->run(); 52 | -------------------------------------------------------------------------------- /tests/ArrayHelpersTest.phpt: -------------------------------------------------------------------------------- 1 | run(); 35 | -------------------------------------------------------------------------------- /tests/Base64StringTest.phpt: -------------------------------------------------------------------------------- 1 | Base64String::from($base64)); 41 | } 42 | 43 | $b = Base64String::encode('hello'); 44 | Assert::equal('hello', $b->getDecodedValue()); 45 | 46 | Assert::true($b->equals(Base64String::encode('hello'))); 47 | Assert::false($b->equals(Base64String::encode('1234'))); 48 | } 49 | 50 | } 51 | 52 | (new Base64StringTest())->run(); 53 | -------------------------------------------------------------------------------- /tests/BoolArrayTest.phpt: -------------------------------------------------------------------------------- 1 | [1, 0, 'true', 'false']], 'a') 54 | ); 55 | } 56 | 57 | public function textExtractOrNull(): void 58 | { 59 | Assert::null(BoolArray::extractOrNull([], 'a')); 60 | 61 | Assert::same( 62 | [true, false, true, false], 63 | BoolArray::extractOrNull(['a' => [1, 0, 'true', 'false']], 'a') 64 | ); 65 | } 66 | 67 | } 68 | 69 | (new BoolArrayTest())->run(); 70 | -------------------------------------------------------------------------------- /tests/CompanyRegistrationNumberTest.phpt: -------------------------------------------------------------------------------- 1 | CompanyRegistrationNumber::from($validValue)); 64 | } 65 | 66 | $number = CompanyRegistrationNumber::from('73270091'); 67 | Assert::equal('73270091', $number->getValue()); 68 | } 69 | 70 | } 71 | 72 | (new CompanyRegistrationNumberTest())->run(); 73 | -------------------------------------------------------------------------------- /tests/ContentTypeTest.phpt: -------------------------------------------------------------------------------- 1 | getValue()); 30 | } 31 | 32 | $invalidValues = [ 33 | 'application/schema_json', 34 | 'audio/3pm', 35 | 'csv', 36 | 'text-plain', 37 | 'text/', 38 | ]; 39 | 40 | foreach ($invalidValues as $value) { 41 | Assert::throws( 42 | static function () use ($value): void { 43 | ContentType::from($value); 44 | }, 45 | InvalidTypeException::class 46 | ); 47 | } 48 | } 49 | 50 | } 51 | 52 | (new ContentTypeTest())->run(); 53 | -------------------------------------------------------------------------------- /tests/CountryCodeTest.phpt: -------------------------------------------------------------------------------- 1 | getValue()); 19 | 20 | $countryGB = CountryCode::extractOrNull(['currency_code' => 'GB'], 'currency_code'); 21 | Assert::type(CountryCode::class, $countryGB); 22 | Assert::equal('GB', $countryGB->getValue()); 23 | 24 | $countryPL = CountryCode::extract(['currency_code' => 'PL'], 'currency_code'); 25 | Assert::equal('PL', $countryPL->getValue()); 26 | Assert::equal('PL', (string) $countryPL); 27 | 28 | Assert::true($countryPL->equalsValue(CountryCode::PL)); 29 | Assert::false($countryPL->equals($countryGB)); 30 | 31 | Assert::noError(static fn () => CountryCode::from('CZ')); 32 | } 33 | 34 | } 35 | 36 | (new CountryCodeTest())->run(); 37 | -------------------------------------------------------------------------------- /tests/CurrencyCodeTest.phpt: -------------------------------------------------------------------------------- 1 | getValue()); 21 | 22 | $currencyCzk = CurrencyCode::extractOrNull(['currency_code' => 'CZK'], 'currency_code'); 23 | Assert::type(CurrencyCode::class, $currencyCzk); 24 | Assert::equal('CZK', $currencyCzk->getValue()); 25 | 26 | $currencyPln = CurrencyCode::extract(['currency_code' => 'PLN'], 'currency_code'); 27 | Assert::equal('PLN', $currencyPln->getValue()); 28 | Assert::equal('PLN', (string) $currencyPln); 29 | 30 | Assert::true($currencyPln->equalsValue(CurrencyCode::PLN)); 31 | Assert::false($currencyPln->equals($currencyCzk)); 32 | 33 | $enums = CurrencyCode::getAvailableEnums(); 34 | 35 | $enum = \reset($enums); 36 | Assert::type(CurrencyCode::class, $enum); 37 | Assert::equal('CZK', $enum->getValue()); 38 | 39 | $values = CurrencyCode::getAvailableValues(); 40 | 41 | $value = \reset($values); 42 | Assert::equal('CZK', $value); 43 | 44 | Assert::noError(static fn () => CurrencyCode::from('CZK')); 45 | 46 | Assert::exception(static function (): void { 47 | CurrencyCode::from('test'); 48 | }, InvalidTypeException::class); 49 | } 50 | 51 | } 52 | 53 | (new CurrencyCodeTest())->run(); 54 | -------------------------------------------------------------------------------- /tests/DateTimeFormatterTest.phpt: -------------------------------------------------------------------------------- 1 | run(); 29 | -------------------------------------------------------------------------------- /tests/DateTimeRangeTest.phpt: -------------------------------------------------------------------------------- 1 | '2000-01-01 00:00:00', 21 | 'from' => '2100-01-01 00:00:00', 22 | ]; 23 | DateTimeRange::from($data); 24 | }, 25 | InvalidTypeException::class 26 | ); 27 | 28 | $data = [ 29 | 'from' => '2100-01-01 00:00:00', 30 | 'to' => '2100-01-02 00:00:00', 31 | ]; 32 | 33 | $dateTimeRange = DateTimeRange::from($data); 34 | 35 | Assert::equal($data, $dateTimeRange->toArray()); 36 | 37 | Assert::equal( 38 | 3_600 * 24, 39 | $dateTimeRange->getDurationInSeconds() 40 | ); 41 | 42 | Assert::equal( 43 | '2100-01-01 00:00:00', 44 | DateTimeFormatter::format($dateTimeRange->getFrom()) 45 | ); 46 | 47 | Assert::equal( 48 | '2100-01-02 00:00:00', 49 | DateTimeFormatter::format($dateTimeRange->getTo()) 50 | ); 51 | 52 | Assert::true($dateTimeRange->contains(new \DateTimeImmutable('2100-01-01 12:00:00'))); 53 | Assert::false($dateTimeRange->contains(new \DateTimeImmutable('2100-02-01 12:00:00'))); 54 | } 55 | 56 | } 57 | 58 | (new DateTimeRangeTest())->run(); 59 | -------------------------------------------------------------------------------- /tests/DateTimesImmutableTest.phpt: -------------------------------------------------------------------------------- 1 | DateTimesImmutable::from('2000-01-01 00:00:00')); 18 | 19 | Assert::throws( 20 | static function (): void { 21 | DateTimesImmutable::from('2000-01-50 00:99:00.22'); 22 | }, 23 | InvalidTypeException::class 24 | ); 25 | 26 | Assert::throws( 27 | static function (): void { 28 | DateTimesImmutable::from('2000-01-50 00:99:00'); 29 | }, 30 | InvalidTypeException::class 31 | ); 32 | } 33 | 34 | public function testExtract(): void 35 | { 36 | $data = [ 37 | 'b' => '2000-01-01 00:00:00', 38 | ]; 39 | 40 | Assert::noError(static fn () => DateTimesImmutable::extract($data, 'b')); 41 | } 42 | 43 | public function testExtractDate(): void 44 | { 45 | $data = [ 46 | 'b' => '2000-01-01', 47 | ]; 48 | Assert::noError(static fn () => DateTimesImmutable::extractDate($data, 'b')); 49 | } 50 | 51 | public function testExtractDateOrNull(): void 52 | { 53 | $data = [ 54 | 'b' => '2000-01-01', 55 | ]; 56 | 57 | $d = DateTimesImmutable::extractDateOrNull($data, 'not-a-key'); 58 | Assert::null($d); 59 | 60 | $d = DateTimesImmutable::extractDateOrNull($data, 'b'); 61 | Assert::type(\DateTimeImmutable::class, $d); 62 | } 63 | 64 | public function testExtractOrNull(): void 65 | { 66 | $data = [ 67 | 'b' => '2000-01-01 00:00:00', 68 | ]; 69 | 70 | $d = DateTimesImmutable::extractOrNull($data, 'not-a-key'); 71 | Assert::null($d); 72 | 73 | $d = DateTimesImmutable::extractOrNull($data, 'b'); 74 | Assert::type(\DateTimeImmutable::class, $d); 75 | } 76 | 77 | public function testFromOrNull(): void 78 | { 79 | $date = DateTimesImmutable::fromOrNull('2000-01-01 00:00:00'); 80 | Assert::type(\DateTimeImmutable::class, $date); 81 | 82 | $date = DateTimesImmutable::fromOrNull(null); 83 | Assert::null($date); 84 | 85 | Assert::exception(static function (): void { 86 | DateTimesImmutable::fromOrNull('2000-01-01'); 87 | }, InvalidTypeException::class); 88 | 89 | $date = DateTimesImmutable::fromOrNull('2000-01-01', true); 90 | Assert::null($date); 91 | } 92 | 93 | } 94 | 95 | (new DateTimesImmutableTest())->run(); 96 | -------------------------------------------------------------------------------- /tests/DateTimesTest.phpt: -------------------------------------------------------------------------------- 1 | '1979-02-23 00:00:00.000000', 51 | 'timezone' => 'Europe/Prague', 52 | 'timezone_type' => 3, 53 | ] 54 | ); 55 | }, 56 | InvalidTypeException::class 57 | ); 58 | 59 | $d = DateTimes::from('2000-01-01 00:00:00.123456'); 60 | Assert::equal('2000-01-01 00:00:00', DateTimeFormatter::format($d)); 61 | 62 | $d = DateTimes::from('2020-01-01 08:35:21.000000'); 63 | Assert::equal('2020-01-01 08:35:21', DateTimeFormatter::format($d)); 64 | } 65 | 66 | public function testExtract(): void 67 | { 68 | $d = DateTimes::from('2010-01-01 00:00:00'); 69 | $data = [ 70 | 'a' => 'xx', 71 | 'b' => '2000-01-01 00:00:00', 72 | 'c' => $d, 73 | ]; 74 | 75 | Assert::throws( 76 | static function () use ($data): void { 77 | DateTimes::extract($data, 'a'); 78 | }, 79 | InvalidTypeException::class 80 | ); 81 | 82 | Assert::throws( 83 | static function () use ($data): void { 84 | DateTimes::extract($data, 'not-key'); 85 | }, 86 | InvalidTypeException::class 87 | ); 88 | 89 | Assert::noError(static fn () => DateTimes::extract($data, 'b')); 90 | 91 | Assert::noError(static fn () => DateTimes::extract($data, 'c')); 92 | } 93 | 94 | public function testExtractDate(): void 95 | { 96 | $d = DateTimes::from('2010-01-01 10:00:00'); 97 | $data = [ 98 | 'a' => 'xx', 99 | 'b' => '2000-01-01', 100 | 'c' => $d, 101 | ]; 102 | 103 | Assert::throws( 104 | static function () use ($data): void { 105 | DateTimes::extractDate($data, 'a'); 106 | }, 107 | InvalidTypeException::class 108 | ); 109 | 110 | Assert::throws( 111 | static function () use ($data): void { 112 | DateTimes::extractDate($data, 'not-key'); 113 | }, 114 | InvalidTypeException::class 115 | ); 116 | 117 | $d = DateTimes::extractDate($data, 'b'); 118 | Assert::equal('2000-01-01 00:00:00', DateTimeFormatter::format($d)); 119 | 120 | $d = DateTimes::extractDate($data, 'c'); 121 | Assert::equal('2010-01-01 00:00:00', DateTimeFormatter::format($d)); 122 | } 123 | 124 | public function testExtractDateOrNull(): void 125 | { 126 | $data = [ 127 | 'b' => '2000-01-01', 128 | ]; 129 | 130 | $d = DateTimes::extractDateOrNull($data, 'not-a-key'); 131 | Assert::null($d); 132 | 133 | $d = DateTimes::extractDateOrNull($data, 'b'); 134 | Assert::type(\DateTime::class, $d); 135 | } 136 | 137 | public function testExtractOrNull(): void 138 | { 139 | $d = DateTimes::from('2010-01-01 10:00:00'); 140 | $data = [ 141 | 'a' => 'xx', 142 | 'b' => '2000-01-01 00:00:00', 143 | 'c' => $d, 144 | ]; 145 | 146 | $d = DateTimes::extractOrNull($data, 'not-a-key'); 147 | Assert::null($d); 148 | 149 | Assert::throws( 150 | static function () use ($data): void { 151 | DateTimes::extractOrNull($data, 'a'); 152 | }, 153 | InvalidTypeException::class 154 | ); 155 | 156 | $d = DateTimes::extractOrNull($data, 'a', true); 157 | Assert::null($d); 158 | 159 | $d = DateTimes::extractOrNull($data, 'b'); 160 | Assert::type(\DateTime::class, $d); 161 | 162 | $d = DateTimes::extractOrNull($data, 'b', true); 163 | Assert::type(\DateTime::class, $d); 164 | } 165 | 166 | } 167 | 168 | (new DateTimesTest())->run(); 169 | -------------------------------------------------------------------------------- /tests/DatesImmutableTest.phpt: -------------------------------------------------------------------------------- 1 | DatesImmutable::from('2000-01-01')); 18 | 19 | Assert::noError(static fn () => DatesImmutable::from(new \DateTime('2000-01-01'))); 20 | 21 | $d = DatesImmutable::from(new \DateTimeImmutable('2000-01-01')); 22 | Assert::equal('2000-01-01 00:00:00', DateTimeFormatter::format($d)); 23 | 24 | Assert::throws( 25 | static function (): void { 26 | DatesImmutable::from('aaa'); 27 | }, 28 | InvalidTypeException::class 29 | ); 30 | 31 | Assert::throws( 32 | static function (): void { 33 | DatesImmutable::from('2000-01-52'); 34 | }, 35 | InvalidTypeException::class 36 | ); 37 | 38 | Assert::throws( 39 | static function (): void { 40 | DatesImmutable::from('2000-13-01'); 41 | }, 42 | InvalidTypeException::class 43 | ); 44 | } 45 | 46 | public function testExtract(): void 47 | { 48 | $d = DatesImmutable::from('2010-01-01'); 49 | $data = [ 50 | 'a' => 'xx', 51 | 'b' => '2000-01-01', 52 | 'c' => $d, 53 | ]; 54 | 55 | Assert::throws( 56 | static function () use ($data): void { 57 | DatesImmutable::extract($data, 'a'); 58 | }, 59 | InvalidTypeException::class 60 | ); 61 | 62 | Assert::throws( 63 | static function () use ($data): void { 64 | DatesImmutable::extract($data, 'not-key'); 65 | }, 66 | InvalidTypeException::class 67 | ); 68 | 69 | Assert::noError(static fn () => DatesImmutable::extract($data, 'b')); 70 | 71 | Assert::noError(static fn () => DatesImmutable::extract($data, 'c')); 72 | } 73 | 74 | public function testExtractOrNull(): void 75 | { 76 | $d = DatesImmutable::from('2010-01-01'); 77 | $data = [ 78 | 'a' => 'xx', 79 | 'b' => '2000-01-01', 80 | 'c' => $d, 81 | ]; 82 | 83 | $d = DatesImmutable::extractOrNull($data, 'not-a-key'); 84 | Assert::null($d); 85 | 86 | Assert::throws( 87 | static function () use ($data): void { 88 | DatesImmutable::extractOrNull($data, 'a'); 89 | }, 90 | InvalidTypeException::class 91 | ); 92 | 93 | $d = DatesImmutable::extractOrNull($data, 'a', true); 94 | Assert::null($d); 95 | 96 | $d = DatesImmutable::extractOrNull($data, 'b'); 97 | Assert::type(\DateTimeImmutable::class, $d); 98 | 99 | $d = DatesImmutable::extractOrNull($data, 'b', true); 100 | Assert::type(\DateTimeImmutable::class, $d); 101 | } 102 | 103 | public function testFromOrNull(): void 104 | { 105 | $date = DatesImmutable::fromOrNull('2000-01-01'); 106 | Assert::type(\DateTimeImmutable::class, $date); 107 | 108 | $date = DatesImmutable::fromOrNull(null); 109 | Assert::null($date); 110 | 111 | Assert::exception(static function (): void { 112 | DatesImmutable::fromOrNull('2000-01-01 00:00:00'); 113 | }, InvalidTypeException::class); 114 | 115 | $date = DatesImmutable::fromOrNull('2000-01-01 00:00:00', true); 116 | Assert::null($date); 117 | } 118 | 119 | } 120 | 121 | (new DatesImmutableTest())->run(); 122 | -------------------------------------------------------------------------------- /tests/DatesTest.phpt: -------------------------------------------------------------------------------- 1 | Dates::from('2000-01-01')); 18 | 19 | Assert::noError(static fn () => Dates::from(new \DateTime('2000-01-01'))); 20 | 21 | $d = Dates::from(new \DateTimeImmutable('2000-01-01')); 22 | Assert::equal('2000-01-01 00:00:00', DateTimeFormatter::format($d)); 23 | 24 | Assert::throws( 25 | static function (): void { 26 | Dates::from('aaa'); 27 | }, 28 | InvalidTypeException::class 29 | ); 30 | 31 | Assert::throws( 32 | static function (): void { 33 | Dates::from('2000-01-52'); 34 | }, 35 | InvalidTypeException::class 36 | ); 37 | 38 | Assert::throws( 39 | static function (): void { 40 | Dates::from('2000-13-01'); 41 | }, 42 | InvalidTypeException::class 43 | ); 44 | 45 | Assert::throws( 46 | static function (): void { 47 | Dates::from( 48 | [ 49 | 'date' => '1979-02-23 00:00:00.000000', 50 | 'timezone' => 'Europe/Prague', 51 | 'timezone_type' => 3, 52 | ] 53 | ); 54 | }, 55 | InvalidTypeException::class 56 | ); 57 | } 58 | 59 | public function testExtract(): void 60 | { 61 | $d = Dates::from('2010-01-01'); 62 | $data = [ 63 | 'a' => 'xx', 64 | 'b' => '2000-01-01', 65 | 'c' => $d, 66 | ]; 67 | 68 | Assert::throws( 69 | static function () use ($data): void { 70 | Dates::extract($data, 'a'); 71 | }, 72 | InvalidTypeException::class 73 | ); 74 | 75 | Assert::throws( 76 | static function () use ($data): void { 77 | Dates::extract($data, 'not-key'); 78 | }, 79 | InvalidTypeException::class 80 | ); 81 | 82 | Assert::noError(static fn () => Dates::extract($data, 'b')); 83 | 84 | Assert::noError(static fn () => Dates::extract($data, 'c')); 85 | } 86 | 87 | public function testExtractOrNull(): void 88 | { 89 | $d = Dates::from('2010-01-01'); 90 | $data = [ 91 | 'a' => 'xx', 92 | 'b' => '2000-01-01', 93 | 'c' => $d, 94 | ]; 95 | 96 | $d = Dates::extractOrNull($data, 'not-a-key'); 97 | Assert::null($d); 98 | 99 | Assert::throws( 100 | static function () use ($data): void { 101 | Dates::extractOrNull($data, 'a'); 102 | }, 103 | InvalidTypeException::class 104 | ); 105 | 106 | $d = Dates::extractOrNull($data, 'a', true); 107 | Assert::null($d); 108 | 109 | $d = Dates::extractOrNull($data, 'b'); 110 | Assert::type(\DateTime::class, $d); 111 | 112 | $d = Dates::extractOrNull($data, 'b', true); 113 | Assert::type(\DateTime::class, $d); 114 | } 115 | 116 | public function testFromOrNull(): void 117 | { 118 | $date = Dates::fromOrNull('2000-01-01'); 119 | Assert::type(\DateTime::class, $date); 120 | 121 | $date = Dates::fromOrNull(null); 122 | Assert::null($date); 123 | 124 | Assert::exception(static function (): void { 125 | DatesImmutable::fromOrNull('2000-01-01 00:00:00'); 126 | }, InvalidTypeException::class); 127 | 128 | $date = Dates::fromOrNull('2000-01-01 00:00:00', true); 129 | Assert::null($date); 130 | } 131 | 132 | } 133 | 134 | (new DatesTest())->run(); 135 | -------------------------------------------------------------------------------- /tests/DomainNameTest.phpt: -------------------------------------------------------------------------------- 1 | DomainName::from($validValue)); 44 | } 45 | } 46 | 47 | } 48 | 49 | (new DomainNameTest())->run(); 50 | -------------------------------------------------------------------------------- /tests/DomainTest.phpt: -------------------------------------------------------------------------------- 1 | Domain::from($validValue)); 43 | } 44 | 45 | $domain = Domain::from('test.seznam.cz'); 46 | Assert::equal('seznam.cz', $domain->getSecondLevelDomain()->getValue()); 47 | 48 | $domain = Domain::from('test.seznam.co.uk'); 49 | Assert::equal('seznam.co.uk', $domain->getSecondLevelDomain()->getValue()); 50 | } 51 | 52 | } 53 | 54 | (new DomainTest())->run(); 55 | -------------------------------------------------------------------------------- /tests/DurationTest.phpt: -------------------------------------------------------------------------------- 1 | 0, 29 | 'unit' => 'week', 30 | ]); 31 | }, 32 | InvalidTypeException::class 33 | ); 34 | 35 | Assert::throws( 36 | static function (): void { 37 | Duration::from('xxx'); 38 | }, 39 | InvalidTypeException::class 40 | ); 41 | 42 | Assert::throws( 43 | static function (): void { 44 | Duration::extract(['duration' => 1], 'duration'); 45 | }, 46 | InvalidTypeException::class, 47 | 'Problem at key duration: Duration: 1 is not in valid duration format.' 48 | ); 49 | 50 | Assert::throws( 51 | static function (): void { 52 | Duration::extract(['duration' => []], 'duration'); 53 | }, 54 | InvalidTypeException::class, 55 | 'Problem at key duration: Missing key: value' 56 | ); 57 | 58 | Assert::throws( 59 | static function (): void { 60 | Duration::extract(['duration' => new stdClass()], 'duration'); 61 | }, 62 | InvalidTypeException::class, 63 | 'Problem at key duration: Expected types [string, array], got object (stdClass)' 64 | ); 65 | } 66 | 67 | public function testCreate(): void 68 | { 69 | Duration::from([ 70 | 'value' => 1, 71 | 'unit' => TimeUnit::HOURS, 72 | ]); 73 | 74 | Duration::extract([ 75 | 'duration' => [ 76 | 'value' => -10, 77 | 'unit' => TimeUnit::DAYS, 78 | ], 79 | ], 'duration'); 80 | 81 | Duration::from('1 weeks'); 82 | 83 | Duration::fromDateTimeModify('-10 months'); 84 | 85 | Duration::extract(['duration' => '1 days'], 'duration'); 86 | } 87 | 88 | public function testGetUnit(): void 89 | { 90 | Assert::noError(static fn () => Duration::from([ 91 | 'value' => 1, 92 | 'unit' => TimeUnit::YEARS, 93 | ])); 94 | } 95 | 96 | public function testGetValue(): void 97 | { 98 | $duration = Duration::from([ 99 | 'value' => 1, 100 | 'unit' => TimeUnit::DAYS, 101 | ]); 102 | 103 | Assert::equal(1, $duration->getValue()); 104 | } 105 | 106 | public function testLengthInSeconds(): void 107 | { 108 | $duration = Duration::from([ 109 | 'value' => 3, 110 | 'unit' => TimeUnit::HOURS, 111 | ]); 112 | Assert::equal(10_800, $duration->getLengthInSeconds()); 113 | 114 | $duration = Duration::from([ 115 | 'value' => 10, 116 | 'unit' => TimeUnit::MINUTES, 117 | ]); 118 | Assert::equal(600, $duration->getLengthInSeconds()); 119 | } 120 | 121 | public function testGetDateTimeModify(): void 122 | { 123 | foreach ($this->getTestDateTimeModifyData() as $data) { 124 | [$duration, $expectedDateTimeModify] = $data; 125 | 126 | Assert::equal($expectedDateTimeModify, $duration->getDateTimeModify()); 127 | } 128 | } 129 | 130 | public function testToArray(): void 131 | { 132 | foreach ($this->getTestToArrayData() as $data) { 133 | [$duration, $expectedArray] = $data; 134 | 135 | Assert::equal($expectedArray, $duration->toArray()); 136 | } 137 | } 138 | 139 | public function testSerialize(): void 140 | { 141 | $duration = Duration::fromDateTimeModify('1 weeks'); 142 | 143 | Assert::noError(static fn () => Duration::from($duration->toArray())); 144 | Assert::noError(static fn () => Duration::extract(['duration' => $duration->toArray()], 'duration')); 145 | Assert::noError(static fn () => Duration::from((string) $duration)); 146 | } 147 | 148 | /** 149 | * @return array 153 | */ 154 | private function getTestDateTimeModifyData(): array 155 | { 156 | return [ 157 | [Duration::fromDateTimeModify('-5 hours'), '-5 hours'], 158 | [Duration::fromDateTimeModify('-1 days'), '-1 days'], 159 | [Duration::fromDateTimeModify('-55 weeks'), '-55 weeks'], 160 | [Duration::fromDateTimeModify('-12 months'), '-12 months'], 161 | [Duration::fromDateTimeModify('-1 years'), '-1 years'], 162 | 163 | [Duration::fromDateTimeModify('5 hours'), '5 hours'], 164 | [Duration::fromDateTimeModify('1 days'), '1 days'], 165 | [Duration::fromDateTimeModify('55 weeks'), '55 weeks'], 166 | [Duration::fromDateTimeModify('12 months'), '12 months'], 167 | [Duration::fromDateTimeModify('1 years'), '1 years'], 168 | 169 | [Duration::fromDateTimeModify('+5 hours'), '5 hours'], 170 | [Duration::fromDateTimeModify('+1 days'), '1 days'], 171 | [Duration::fromDateTimeModify('+55 weeks'), '55 weeks'], 172 | [Duration::fromDateTimeModify('+12 months'), '12 months'], 173 | [Duration::fromDateTimeModify('+1 years'), '1 years'], 174 | ]; 175 | } 176 | 177 | /** 178 | * @return array 185 | */ 186 | private function getTestToArrayData(): array 187 | { 188 | return [ 189 | [Duration::fromDateTimeModify('-1 years'), ['value' => -1, 'unit' => TimeUnit::YEARS]], 190 | [Duration::fromDateTimeModify('2 months'), ['value' => 2, 'unit' => TimeUnit::MONTHS]], 191 | [Duration::fromDateTimeModify('+8 weeks'), ['value' => 8, 'unit' => TimeUnit::WEEKS]], 192 | ]; 193 | } 194 | 195 | } 196 | 197 | (new DurationTest())->run(); 198 | -------------------------------------------------------------------------------- /tests/EmailaddressTest.phpt: -------------------------------------------------------------------------------- 1 | $invalidValue], 'email'); 47 | }, 48 | InvalidEmailaddressException::class, 49 | 'Problem at key email: Invalid emailaddress: ' . $invalidValue 50 | ); 51 | } 52 | 53 | $validValues = [ 54 | 'íýžčíýžčýíčíýžč@seznam.cz', 55 | '608024038@post.cz', 56 | '-xyz-@seznam.cz', 57 | 'martin@smartemailing.cz', 58 | 'test-@seznam.cz', 59 | 'realdruid@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com', 60 | ]; 61 | 62 | foreach ($validValues as $validValue) { 63 | Assert::noError(static fn () => Emailaddress::from($validValue)); 64 | } 65 | 66 | $e = Emailaddress::from('martin@smartemailing.cz'); 67 | Assert::equal('martin', $e->getLocalPart()); 68 | Assert::equal('smartemailing.cz', $e->getDomain()->getValue()); 69 | Assert::equal('smartemailing.cz', $e->getHostName()->getValue()); 70 | } 71 | 72 | } 73 | 74 | (new EmailaddressTest())->run(); 75 | -------------------------------------------------------------------------------- /tests/EnumTest.phpt: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final class SimpleEnum extends Enum 17 | { 18 | 19 | use EnumExtractableTrait; 20 | 21 | public const TEST = 'test'; 22 | 23 | } 24 | 25 | final class EnumTest extends TestCase 26 | { 27 | 28 | public function testInvalidTypeException(): void 29 | { 30 | Assert::exception( 31 | static function (): void { 32 | SimpleEnum::get(new \stdClass()); 33 | }, 34 | InvalidTypeException::class, 35 | 'stdClass [object] is not a valid value for SmartEmailing\Types\SimpleEnum, accepted values: test' 36 | ); 37 | 38 | Assert::exception( 39 | static function (): void { 40 | SimpleEnum::extract(['test' => new \stdClass()], 'test'); 41 | }, 42 | InvalidTypeException::class, 43 | 'Problem at key test: stdClass [object] is not a valid value for SmartEmailing\Types\SimpleEnum, accepted values: test' 44 | ); 45 | 46 | Assert::exception( 47 | static function (): void { 48 | SimpleEnum::extract(['test' => [new \stdClass()]], 'test'); 49 | }, 50 | InvalidTypeException::class, 51 | 'Problem at key test: [{}] [array] is not a valid value for SmartEmailing\Types\SimpleEnum, accepted values: test' 52 | ); 53 | 54 | Assert::exception( 55 | static function (): void { 56 | SimpleEnum::extract(['test' => static function (): void {}], 'test'); 57 | }, 58 | InvalidTypeException::class, 59 | 'Problem at key test: Closure [object] is not a valid value for SmartEmailing\Types\SimpleEnum, accepted values: test' 60 | ); 61 | } 62 | 63 | } 64 | 65 | (new EnumTest())->run(); 66 | -------------------------------------------------------------------------------- /tests/ExtractableTraitTest.phpt: -------------------------------------------------------------------------------- 1 | 'martin@smartemailing.cz', 20 | 'y' => $emailaddress1, 21 | 'z' => 'qq', 22 | ]; 23 | 24 | Assert::noError(static fn () => Emailaddress::extract($data, 'x')); 25 | 26 | $emailaddress3 = Emailaddress::extract($data, 'y'); 27 | Assert::same($emailaddress1, $emailaddress3); 28 | 29 | Assert::throws( 30 | static function () use ($data): void { 31 | Emailaddress::extract($data, 'z'); 32 | }, 33 | InvalidTypeException::class 34 | ); 35 | } 36 | 37 | public function testExtractArrayOf(): void 38 | { 39 | $data = [ 40 | 'a' => [ 41 | 'martin+2@smartemailing.cz', 42 | 'martin+3@smartemailing.cz', 43 | ], 44 | 'b' => [ 45 | Emailaddress::from('martin+4@smartemailing.cz'), 46 | Emailaddress::from('martin+5@smartemailing.cz'), 47 | 'martin+6@smartemailing.cz', 48 | ], 49 | 'c' => 'error here!', 50 | 'd' => [ 51 | 'not-email', 52 | ], 53 | 'e' => [ 54 | Emailaddress::from('martin+10@smartemailing.cz'), 55 | Emailaddress::from('martin+11@smartemailing.cz'), 56 | ], 57 | ]; 58 | 59 | $arr = Emailaddress::extractArrayOf($data, 'a'); 60 | Assert::equal(2, \count($arr)); 61 | 62 | $arr = Emailaddress::extractArrayOf($data, 'b'); 63 | Assert::equal(3, \count($arr)); 64 | 65 | Assert::throws( 66 | static function () use ($data): void { 67 | Emailaddress::extractArrayOf($data, 'c'); 68 | }, 69 | InvalidTypeException::class 70 | ); 71 | 72 | Assert::throws( 73 | static function () use ($data): void { 74 | Emailaddress::extractArrayOf($data, 'd'); 75 | }, 76 | InvalidTypeException::class 77 | ); 78 | 79 | $arr = Emailaddress::extractArrayOfOrEmpty($data, 'a'); 80 | Assert::equal(2, \count($arr)); 81 | 82 | $arr = Emailaddress::extractArrayOfOrEmpty($data, 'not-existing-key'); 83 | Assert::equal([], $arr); 84 | 85 | $arr = Emailaddress::extractArrayOf($data, 'e'); 86 | Assert::equal(2, \count($arr)); 87 | } 88 | 89 | public function testExtractArrayOfSkipInvalid(): void 90 | { 91 | $data = [ 92 | 'a' => [ 93 | 'martin+1@smartemailing.cz', 94 | 'martin+2@smartemailing.cz', 95 | '1', 96 | true, 97 | null, 98 | new \stdClass(), 99 | ], 100 | ]; 101 | 102 | $arr = Emailaddress::extractArrayOfSkipInvalid($data, 'a'); 103 | Assert::equal(2, \count($arr)); 104 | } 105 | 106 | public function testFromOrNull(): void 107 | { 108 | $e = Emailaddress::fromOrNull(null); 109 | Assert::null($e); 110 | 111 | $e = Emailaddress::fromOrNull('martin+6@smartemailing.cz'); 112 | Assert::type(Emailaddress::class, $e); 113 | 114 | Assert::throws( 115 | static function (): void { 116 | Emailaddress::fromOrNull('qqqq'); 117 | }, 118 | InvalidTypeException::class 119 | ); 120 | 121 | $e = Emailaddress::fromOrNull('qqqq', true); 122 | Assert::null($e); 123 | } 124 | 125 | public function testExtractOrNull(): void 126 | { 127 | $data = [ 128 | 'a' => '', 129 | 'b' => 'qqq', 130 | 'c' => 'martin+7@smartemailing.cz', 131 | ]; 132 | 133 | $e = Emailaddress::extractOrNull($data, 'not-key'); 134 | Assert::null($e); 135 | 136 | $e = Emailaddress::extractOrNull($data, 'a'); 137 | Assert::null($e); 138 | 139 | Assert::throws( 140 | static function () use ($data): void { 141 | Emailaddress::extractOrNull($data, 'b'); 142 | }, 143 | InvalidTypeException::class 144 | ); 145 | 146 | $e = Emailaddress::extractOrNull($data, 'b', true); 147 | Assert::null($e); 148 | 149 | $e = Emailaddress::extractOrNull($data, 'c'); 150 | Assert::type(Emailaddress::class, $e); 151 | } 152 | 153 | public function testIdentityExtraction(): void 154 | { 155 | $e = Emailaddress::from('martin@smartemailng.cz'); 156 | $e2 = Emailaddress::from($e); 157 | Assert::same($e, $e2); 158 | 159 | $p = Port::from(80); 160 | $p2 = Port::from($p); 161 | Assert::same($p, $p2); 162 | 163 | $s = SigmoidValue::from(0.3); 164 | $s2 = SigmoidValue::from($s); 165 | Assert::same($s, $s2); 166 | 167 | $a = Address::from( 168 | [ 169 | 'street_and_number' => 'Netroufalky 5', 170 | 'town' => 'Brno', 171 | 'zip_code' => 12_345, 172 | 'country' => 'CZ', 173 | ] 174 | ); 175 | 176 | $a2 = Address::from($a); 177 | Assert::same($a, $a2); 178 | } 179 | 180 | } 181 | 182 | (new ExtractableTraitTest())->run(); 183 | -------------------------------------------------------------------------------- /tests/GuidTest.phpt: -------------------------------------------------------------------------------- 1 | getValue() 45 | ); 46 | } 47 | } 48 | 49 | public function test2(): void 50 | { 51 | $hex32 = Hex32::from('d7c8539e089e11e8b1612edbc134be21'); 52 | 53 | $guid = Guid::fromHex32($hex32); 54 | Assert::equal('d7c8539e-089e-11e8-b161-2edbc134be21', $guid->getValue()); 55 | } 56 | 57 | } 58 | 59 | (new GuidTest())->run(); 60 | -------------------------------------------------------------------------------- /tests/Hex32Test.phpt: -------------------------------------------------------------------------------- 1 | getValue()); 38 | } 39 | 40 | public function test2(): void 41 | { 42 | $guid = Guid::from('d7c8539e-089e-11e8-b161-2edbc134be21'); 43 | 44 | $hex32 = Hex32::fromGuid($guid); 45 | Assert::equal('d7c8539e089e11e8b1612edbc134be21', $hex32->getValue()); 46 | } 47 | 48 | } 49 | 50 | (new Hex32Test())->run(); 51 | -------------------------------------------------------------------------------- /tests/HexColorTest.phpt: -------------------------------------------------------------------------------- 1 | getValue()); 40 | } 41 | 42 | $hexColor = HexColor::from('#fff'); 43 | Assert::equal('#FFF', $hexColor->getValue()); 44 | } 45 | 46 | } 47 | 48 | (new HexColorTest())->run(); 49 | -------------------------------------------------------------------------------- /tests/IbanTest.phpt: -------------------------------------------------------------------------------- 1 | getValidIbanValues() as $validIbanValue) { 18 | $iban = Iban::from($validIbanValue); 19 | Assert::notEqual(0, $iban->getChecksum()); 20 | } 21 | 22 | $iban = Iban::from('CZ8508000000002677686023'); 23 | 24 | Assert::equal('CZ', $iban->getCountry()->getValue()); 25 | Assert::equal('CZ8508000000002677686023', $iban->getValue()); 26 | Assert::equal(85, $iban->getChecksum()); 27 | Assert::equal('CZ8508000000002677686023', $iban->getFormatted()); 28 | Assert::equal('CZ85 0800 0000 0026 7768 6023', $iban->getFormatted(Iban::FORMAT_PRINT)); 29 | } 30 | 31 | public function testInvalidIban(): void 32 | { 33 | foreach ($this->getInvalidIbanValues() as $invalidIbanValue) { 34 | Assert::exception( 35 | static function () use ($invalidIbanValue): void { 36 | Iban::from($invalidIbanValue); 37 | }, 38 | InvalidTypeException::class 39 | ); 40 | } 41 | } 42 | 43 | /** 44 | * @return array 45 | */ 46 | private function getValidIbanValues(): array 47 | { 48 | return [ 49 | 'CZ8508000000002677686023', 50 | ]; 51 | } 52 | 53 | /** 54 | * @return array 55 | */ 56 | private function getInvalidIbanValues(): array 57 | { 58 | return [ 59 | '8508000000002677686023', 60 | 'CZ85080000000026776860', 61 | ]; 62 | } 63 | 64 | } 65 | 66 | (new IbanTest())->run(); 67 | -------------------------------------------------------------------------------- /tests/IntArrayUniqueTest.phpt: -------------------------------------------------------------------------------- 1 | UniqueIntArray::from($validValue)); 66 | } 67 | 68 | $append = UniqueIntArray::from([1]); 69 | Assert::equal([1], $append->getValues()); 70 | 71 | $append->add(2); 72 | Assert::equal([1, 2], $append->getValues()); 73 | 74 | $append->remove(12_345); 75 | Assert::equal([1, 2], $append->getValues()); 76 | 77 | $append->remove(1); 78 | Assert::equal([2], $append->getValues()); 79 | 80 | $empty = UniqueIntArray::extractOrEmpty( 81 | [], 82 | 'not_existing' 83 | ); 84 | 85 | Assert::count(0, $empty); 86 | 87 | $data = [ 88 | 'data' => $empty, 89 | ]; 90 | 91 | Assert::noError(static fn () => UniqueIntArray::extract($data, 'data')); 92 | 93 | Assert::throws( 94 | static function (): void { 95 | $data = [ 96 | 'arr' => [], 97 | ]; 98 | UniqueIntArray::extractNotEmpty($data, 'arr'); 99 | }, 100 | InvalidTypeException::class 101 | ); 102 | 103 | $data = [ 104 | 'arr' => [ 105 | 1, 106 | 2, 107 | ], 108 | ]; 109 | $notEmpty = UniqueIntArray::extractNotEmpty($data, 'arr'); 110 | 111 | Assert::equal([1, 2], $notEmpty->getValues()); 112 | 113 | Assert::true($notEmpty->contains(1)); 114 | Assert::false($notEmpty->contains(44)); 115 | 116 | $data = [ 117 | 1, 118 | 2, 119 | 3, 120 | 4, 121 | 5, 122 | ]; 123 | 124 | $intArray = UniqueIntArray::from($data); 125 | 126 | $chunks = $intArray->split(2); 127 | Assert::count(3, $chunks); 128 | 129 | Assert::count(2, $chunks[0]->toArray()); 130 | Assert::count(2, $chunks[1]->toArray()); 131 | Assert::count(1, $chunks[2]->toArray()); 132 | } 133 | 134 | } 135 | 136 | (new IntArrayUniqueTest())->run(); 137 | -------------------------------------------------------------------------------- /tests/IntTypeTest.phpt: -------------------------------------------------------------------------------- 1 | IntType::from($value)); 56 | } 57 | } 58 | 59 | } 60 | 61 | (new IntTypeTest())->run(); 62 | -------------------------------------------------------------------------------- /tests/IpAddressTest.phpt: -------------------------------------------------------------------------------- 1 | IpAddress::from($validValue)); 43 | } 44 | 45 | Assert::equal(4, IpAddress::from('192.168.0.1')->getVersion()); 46 | Assert::equal(6, IpAddress::from('[2001:0db8:0a0b:12f0:0000:0000:0000:0001]')->getVersion()); 47 | } 48 | 49 | } 50 | 51 | (new IpAddressTest())->run(); 52 | -------------------------------------------------------------------------------- /tests/JsonStringTest.phpt: -------------------------------------------------------------------------------- 1 | getValue()); 23 | Assert::equal([], $json->getDecodedValue()); 24 | 25 | $json = JsonString::from(123); 26 | Assert::equal('123', $json->getValue()); 27 | Assert::equal(123, $json->getDecodedValue()); 28 | 29 | $json = JsonString::from(['hello', '123', 456]); 30 | Assert::equal('[ 31 | "hello", 32 | "123", 33 | 456 34 | ]', $json->getValue()); 35 | Assert::equal(['hello', '123', 456], $json->getDecodedValue()); 36 | 37 | $json = JsonString::from(['test' => new \stdClass()]); 38 | Assert::equal('{ 39 | "test": {} 40 | }', $json->getValue()); 41 | Assert::equal(['test' => []], $json->getDecodedValue()); 42 | } 43 | 44 | public function testInvalid(): void 45 | { 46 | Assert::throws( 47 | static function (): void { 48 | JsonString::from(new \stdClass()); 49 | }, 50 | InvalidTypeException::class, 51 | 'Expected types [string, array], got object (stdClass)' 52 | ); 53 | 54 | Assert::throws( 55 | static function (): void { 56 | JsonString::from('{"test": "test"'); 57 | }, 58 | InvalidTypeException::class, 59 | 'Invalid JSON string' 60 | ); 61 | 62 | Assert::throws( 63 | static function (): void { 64 | JsonString::from('='); 65 | }, 66 | InvalidTypeException::class, 67 | 'Invalid JSON string' 68 | ); 69 | 70 | Assert::throws( 71 | static function (): void { 72 | JsonString::encode(\NAN); 73 | }, 74 | InvalidTypeException::class, 75 | 'Inf and NaN cannot be JSON encoded' 76 | ); 77 | } 78 | 79 | } 80 | 81 | (new JsonStringTest())->run(); 82 | -------------------------------------------------------------------------------- /tests/KeyValuePairTest.phpt: -------------------------------------------------------------------------------- 1 | 'X-Header-Name', 19 | 'value' => 'cool value', 20 | ]; 21 | 22 | $keyValuePair = KeyValuePair::from($data); 23 | 24 | Assert::equal('X-Header-Name', $keyValuePair->getKey()); 25 | Assert::equal('cool value', $keyValuePair->getValue()); 26 | 27 | Assert::equal($data, $keyValuePair->toArray()); 28 | } 29 | 30 | } 31 | 32 | (new KeyValuePairTest())->run(); 33 | -------------------------------------------------------------------------------- /tests/LoginCredentialsTest.phpt: -------------------------------------------------------------------------------- 1 | 'admin', 19 | 'password' => '1234', 20 | ]; 21 | 22 | $loginCredentials = LoginCredentials::from($data); 23 | 24 | Assert::equal('admin', $loginCredentials->getLogin()); 25 | Assert::equal('1234', $loginCredentials->getPassword()); 26 | 27 | Assert::equal($data, $loginCredentials->toArray()); 28 | } 29 | 30 | } 31 | 32 | (new LoginCredentialsTest())->run(); 33 | -------------------------------------------------------------------------------- /tests/NonEmptyStringTest.phpt: -------------------------------------------------------------------------------- 1 | getValue()); 20 | Assert::same('test', (string) $string); 21 | 22 | $string = NonEmptyString::from(' test '); 23 | 24 | Assert::same('test', (string) $string); 25 | 26 | $string = NonEmptyString::from(" test \n"); 27 | 28 | Assert::same('test', (string) $string); 29 | } 30 | 31 | public function testInvalid(): void 32 | { 33 | Assert::throws( 34 | static function (): void { 35 | NonEmptyString::from(' '); 36 | }, 37 | InvalidTypeException::class, 38 | 'Value must be non empty string.' 39 | ); 40 | 41 | Assert::throws( 42 | static function (): void { 43 | NonEmptyString::extract(['data' => " \n"], 'data'); 44 | }, 45 | InvalidTypeException::class, 46 | 'Problem at key data: Value must be non empty string.' 47 | ); 48 | } 49 | 50 | } 51 | 52 | (new NonEmptyStringTest())->run(); 53 | -------------------------------------------------------------------------------- /tests/PartTest.phpt: -------------------------------------------------------------------------------- 1 | getValue()); 36 | 37 | $part = Part::from(0.0); 38 | Assert::equal(0.0, $part->getValue()); 39 | 40 | $part = Part::from(1); 41 | Assert::equal(1.0, $part->getValue()); 42 | 43 | $part = Part::from(1.0); 44 | Assert::equal(1.0, $part->getValue()); 45 | 46 | $part = Part::from(0.000_3); 47 | Assert::equal(0.000_3, $part->getValue()); 48 | 49 | $part = Part::fromRatio(0.0, 0.0); 50 | Assert::equal(0.0, $part->getValue()); 51 | 52 | $part = Part::fromRatio(1.0, 2.0); 53 | Assert::equal(0.5, $part->getValue()); 54 | 55 | Assert::throws( 56 | static function (): void { 57 | Part::fromRatio(2.0, 1.0); 58 | }, 59 | InvalidTypeException::class, 60 | 'Value cannot be higher than whole: but 2 / 1 given.' 61 | ); 62 | 63 | $part = Part::from(0.123_4); 64 | Assert::equal(12.34, $part->getPercent()); 65 | } 66 | 67 | } 68 | 69 | (new PartTest())->run(); 70 | -------------------------------------------------------------------------------- /tests/PhoneNumberTest.phpt: -------------------------------------------------------------------------------- 1 | CountryCode::HR, 42 | '+420(608)111111' => CountryCode::CZ, 43 | '00421 254111111' => CountryCode::SK, 44 | '00421 905 111 111' => CountryCode::SK, 45 | '+420 950 111 111' => CountryCode::CZ, 46 | '+420720182158' => CountryCode::CZ, 47 | '+391234567891234' => CountryCode::IT, 48 | '+905322002020' => CountryCode::TR, 49 | '+972546589568' => CountryCode::IL, 50 | '+35796562049' => CountryCode::CY, 51 | ]; 52 | 53 | foreach ($validValues as $number => $country) { 54 | $phone = PhoneNumber::from($number); 55 | Assert::type(CountryCode::class, $phone->guessCountry()); 56 | Assert::equal( 57 | $country, 58 | $phone->guessCountry() 59 | ->getValue() 60 | ); 61 | echo 'Phone number ' 62 | . $phone->getValue() 63 | . ' belongs to ' 64 | . $phone->guessCountry() 65 | ->getValue() 66 | . \PHP_EOL; 67 | } 68 | } 69 | 70 | } 71 | (new PhoneNumberTest())->run(); 72 | -------------------------------------------------------------------------------- /tests/PortTest.phpt: -------------------------------------------------------------------------------- 1 | Port::from(0)); 33 | Assert::noError(static fn () => Port::from(65_535)); 34 | Assert::noError(static fn () => Port::from(20)); 35 | 36 | Assert::equal(20, Port::from(20)->getValue()); 37 | Assert::equal('20', (string) Port::from(20)); 38 | } 39 | 40 | } 41 | 42 | (new PortTest())->run(); 43 | -------------------------------------------------------------------------------- /tests/PriceTest.phpt: -------------------------------------------------------------------------------- 1 | 432.1, 19 | 'without_vat' => '123.45', 20 | 'currency' => CurrencyCode::CZK, 21 | ]; 22 | 23 | $price = Price::from($data); 24 | 25 | Assert::equal(CurrencyCode::CZK, $price->getCurrency()->getValue()); 26 | 27 | Assert::throws( 28 | static function (): void { 29 | Price::from([]); 30 | }, 31 | InvalidTypeException::class 32 | ); 33 | } 34 | 35 | public function test2(): void 36 | { 37 | $data = [ 38 | 'with_vat' => 242, 39 | 'without_vat' => 200, 40 | 'currency' => CurrencyCode::CZK, 41 | ]; 42 | 43 | $price = Price::from($data); 44 | 45 | $vatRate = $price->calculateVatRatePercent(); 46 | 47 | Assert::equal(21.0, $vatRate); 48 | } 49 | 50 | } 51 | 52 | (new PriceTest())->run(); 53 | -------------------------------------------------------------------------------- /tests/QuantityTest.phpt: -------------------------------------------------------------------------------- 1 | Quantity::from(1)); 33 | Assert::noError(static fn () => Quantity::from(1_000_000)); 34 | 35 | Assert::equal(20, Quantity::from(20)->getValue()); 36 | Assert::equal('20', (string) Quantity::from(20)); 37 | } 38 | 39 | } 40 | 41 | (new QuantityTest())->run(); 42 | -------------------------------------------------------------------------------- /tests/ReLUValueTest.phpt: -------------------------------------------------------------------------------- 1 | getValue()); 33 | 34 | $reLuValue = ReLUValue::from(0.0); 35 | Assert::equal(0.0, $reLuValue->getValue()); 36 | 37 | $reLuValue = ReLUValue::from(1); 38 | Assert::equal(1.0, $reLuValue->getValue()); 39 | 40 | $reLuValue = ReLUValue::from(1.0); 41 | Assert::equal(1.0, $reLuValue->getValue()); 42 | } 43 | 44 | } 45 | 46 | (new ReLUValueTest())->run(); 47 | -------------------------------------------------------------------------------- /tests/ScalarLeavesArrayTest.phpt: -------------------------------------------------------------------------------- 1 | toArray()); 20 | 21 | $input = [ 22 | [ 23 | 'a', 24 | ], 25 | [ 26 | 1, 27 | ], 28 | [ 29 | 'b', 30 | [ 31 | true, 32 | [ 33 | null, 34 | ], 35 | [], 36 | ], 37 | ], 38 | ]; 39 | 40 | $scalarArray = ScalarLeavesArray::from($input); 41 | Assert::equal($input, $scalarArray->toArray()); 42 | 43 | Assert::throws( 44 | static function (): void { 45 | ScalarLeavesArray::from([new \stdClass()]); 46 | }, 47 | InvalidTypeException::class 48 | ); 49 | 50 | Assert::throws( 51 | static function (): void { 52 | ScalarLeavesArray::from([\tmpfile()]); 53 | }, 54 | InvalidTypeException::class 55 | ); 56 | 57 | $data = [ 58 | 'key' => $input, 59 | ]; 60 | 61 | $scalarArray = ScalarLeavesArray::extractOrEmpty($data, 'key'); 62 | Assert::equal($input, $scalarArray->toArray()); 63 | 64 | $scalarArray = ScalarLeavesArray::extractOrEmpty($data, 'not_existing'); 65 | Assert::equal([], $scalarArray->toArray()); 66 | } 67 | 68 | } 69 | 70 | (new ScalarLeavesArrayTest())->run(); 71 | -------------------------------------------------------------------------------- /tests/SigmoidValueTest.phpt: -------------------------------------------------------------------------------- 1 | getValue()); 35 | 36 | $sigmoidValue = SigmoidValue::from(0.0); 37 | Assert::equal(0.0, $sigmoidValue->getValue()); 38 | 39 | $sigmoidValue = SigmoidValue::from(1); 40 | Assert::equal(1.0, $sigmoidValue->getValue()); 41 | 42 | $sigmoidValue = SigmoidValue::from(1.0); 43 | Assert::equal(1.0, $sigmoidValue->getValue()); 44 | 45 | $sigmoidValue = SigmoidValue::from(-0.000_3); 46 | Assert::equal(-0.000_3, $sigmoidValue->getValue()); 47 | 48 | $sigmoidValue = SigmoidValue::from(-1.0); 49 | Assert::equal(-1.0, $sigmoidValue->getValue()); 50 | } 51 | 52 | } 53 | 54 | (new SigmoidValueTest())->run(); 55 | -------------------------------------------------------------------------------- /tests/StringHelpersTest.phpt: -------------------------------------------------------------------------------- 1 | ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^ _`abcdefghijklmnopqrstuvwxyz{|}~ěščřžýáíéůúťď ĚŠČŘŽÝÁÍÉŮÚŤĎ'; 23 | 24 | $result = StringHelpers::normalizeWhitespace($string); 25 | 26 | Assert::equal($string, $result); 27 | } 28 | 29 | public function testSanitizeOrNull(): void 30 | { 31 | $string = "čš \n\r\n a "; 32 | 33 | Assert::equal("čš \n\n a", StringHelpers::sanitizeOrNull($string)); 34 | Assert::equal(null, StringHelpers::sanitizeOrNull(null)); 35 | } 36 | 37 | public function testSanitizeUtf8Mb4OrNull(): void 38 | { 39 | $string = "čš \n\r\n a 😊 "; 40 | 41 | Assert::equal("čš \n\n a 😊", StringHelpers::sanitizeUtf8Mb4OrNull($string)); 42 | Assert::equal(null, StringHelpers::sanitizeUtf8Mb4OrNull(null)); 43 | } 44 | 45 | public function testRemoveWhitespace(): void 46 | { 47 | // contains soft hyphens 48 | $string = '+420 607 988 394' . \PHP_EOL; 49 | 50 | $noWhitespace = StringHelpers::removeWhitespace($string); 51 | 52 | Assert::equal( 53 | '+420607988394', 54 | $noWhitespace 55 | ); 56 | } 57 | 58 | } 59 | 60 | (new StringHelpersTest())->run(); 61 | -------------------------------------------------------------------------------- /tests/SwiftBicTest.phpt: -------------------------------------------------------------------------------- 1 | getValidIbanValues() as $validIbanValue) { 18 | Assert::noError(static fn () => SwiftBic::from($validIbanValue)); 19 | } 20 | } 21 | 22 | public function testInvalidIban(): void 23 | { 24 | foreach ($this->getInvalidIbanValues() as $invalidIbanValue) { 25 | Assert::exception(static function () use ($invalidIbanValue): void { 26 | SwiftBic::from($invalidIbanValue); 27 | }, InvalidTypeException::class); 28 | } 29 | } 30 | 31 | /** 32 | * @return array 33 | */ 34 | private function getValidIbanValues(): array 35 | { 36 | return [ 37 | 'GIBACZPX', 38 | 'UNCRSKBXXXX', 39 | 'UNCRSKBX', 40 | ]; 41 | } 42 | 43 | /** 44 | * @return array 45 | */ 46 | private function getInvalidIbanValues(): array 47 | { 48 | return [ 49 | 'GIBACZP', 50 | 'IBACZPX', 51 | 'UNCRSKBXX', 52 | ]; 53 | } 54 | 55 | } 56 | 57 | (new SwiftBicTest())->run(); 58 | -------------------------------------------------------------------------------- /tests/UniqueIntArrayTest.phpt: -------------------------------------------------------------------------------- 1 | UniqueIntArray::from($validValue)); 54 | } 55 | 56 | $append = UniqueIntArray::from([1]); 57 | Assert::equal([1], $append->getValues()); 58 | 59 | $append->add(2); 60 | Assert::equal([1, 2], $append->getValues()); 61 | 62 | $append->remove(12_345); 63 | Assert::equal([1, 2], $append->getValues()); 64 | 65 | $append->remove(1); 66 | Assert::equal([2], $append->getValues()); 67 | 68 | $empty = UniqueIntArray::extractOrEmpty( 69 | [], 70 | 'not_existing' 71 | ); 72 | 73 | Assert::count(0, $empty); 74 | 75 | $data = [ 76 | 'data' => $empty, 77 | ]; 78 | Assert::noError(static fn () => UniqueIntArray::extract($data, 'data')); 79 | 80 | $containsTest = UniqueIntArray::from( 81 | [ 82 | 123, 83 | ] 84 | ); 85 | Assert::true($containsTest->contains(123)); 86 | Assert::false($containsTest->contains(321)); 87 | 88 | Assert::true($containsTest->add(321)); 89 | Assert::false($containsTest->add(321)); 90 | Assert::true($containsTest->contains(321)); 91 | 92 | $containsTest->remove(123); 93 | Assert::false($containsTest->contains(123)); 94 | 95 | $deductable = UniqueIntArray::from( 96 | [ 97 | 1, 98 | 2, 99 | ] 100 | ); 101 | 102 | $toBeDeducted = UniqueIntArray::from( 103 | [ 104 | 2, 105 | 3, 106 | ] 107 | ); 108 | 109 | $result = $deductable->deduct($toBeDeducted); 110 | 111 | Assert::equal( 112 | [ 113 | 1, 114 | ], 115 | $result->toArray() 116 | ); 117 | 118 | $arr = UniqueIntArray::from( 119 | [ 120 | 2, 121 | 1, 122 | ] 123 | ); 124 | $arr->orderASC(); 125 | Assert::equal( 126 | [ 127 | 1, 128 | 2, 129 | ], 130 | $arr->toArray() 131 | ); 132 | } 133 | 134 | public function testIntersect(): void 135 | { 136 | $arr1 = UniqueIntArray::from( 137 | [ 138 | 0, 139 | ] 140 | ); 141 | 142 | UniqueIntArray::intersect( 143 | [ 144 | $arr1, 145 | ] 146 | ); 147 | 148 | /// 149 | 150 | $arr1 = UniqueIntArray::from( 151 | [ 152 | 0, 153 | 1, 154 | 2, 155 | 3, 156 | ] 157 | ); 158 | 159 | $arr2 = UniqueIntArray::from( 160 | [ 161 | 2, 162 | 3, 163 | 4, 164 | 5, 165 | ] 166 | ); 167 | 168 | $result = UniqueIntArray::intersect( 169 | [ 170 | $arr1, 171 | $arr2, 172 | ] 173 | ); 174 | 175 | Assert::equal( 176 | [ 177 | 2, 178 | 3, 179 | ], 180 | $result->toArray() 181 | ); 182 | 183 | /// 184 | 185 | $result = UniqueIntArray::intersect( 186 | [ 187 | UniqueIntArray::from([]), 188 | UniqueIntArray::from([]), 189 | ] 190 | ); 191 | 192 | Assert::equal( 193 | [], 194 | $result->toArray() 195 | ); 196 | 197 | /// 198 | $arr1 = UniqueIntArray::from( 199 | [ 200 | 0, 201 | ] 202 | ); 203 | 204 | $arr2 = UniqueIntArray::from( 205 | [ 206 | 1, 207 | ] 208 | ); 209 | 210 | $result = UniqueIntArray::intersect( 211 | [ 212 | $arr1, 213 | $arr2, 214 | ] 215 | ); 216 | 217 | Assert::equal( 218 | [], 219 | $result->toArray() 220 | ); 221 | } 222 | 223 | public function testUnion(): void 224 | { 225 | $arr1 = UniqueIntArray::from( 226 | [ 227 | 0, 228 | 1, 229 | 2, 230 | 3, 231 | ] 232 | ); 233 | 234 | $arr2 = UniqueIntArray::from( 235 | [ 236 | 2, 237 | 3, 238 | 4, 239 | 5, 240 | ] 241 | ); 242 | 243 | $result = UniqueIntArray::union( 244 | [ 245 | $arr1, 246 | $arr2, 247 | ] 248 | ); 249 | 250 | Assert::equal( 251 | [ 252 | 0, 253 | 1, 254 | 2, 255 | 3, 256 | 4, 257 | 5, 258 | ], 259 | $result->toArray() 260 | ); 261 | 262 | /// 263 | 264 | $result = UniqueIntArray::union( 265 | [ 266 | UniqueIntArray::from([]), 267 | UniqueIntArray::from([]), 268 | ] 269 | ); 270 | 271 | Assert::equal( 272 | [], 273 | $result->toArray() 274 | ); 275 | 276 | /// 277 | $arr1 = UniqueIntArray::from( 278 | [ 279 | 333, 280 | ] 281 | ); 282 | 283 | $arr2 = UniqueIntArray::from( 284 | [ 285 | 1, 286 | ] 287 | ); 288 | 289 | $result = UniqueIntArray::union( 290 | [ 291 | $arr1, 292 | $arr2, 293 | ] 294 | ); 295 | 296 | Assert::equal( 297 | [ 298 | 333, 299 | 1, 300 | ], 301 | $result->toArray() 302 | ); 303 | } 304 | 305 | } 306 | 307 | (new UniqueIntArrayTest())->run(); 308 | -------------------------------------------------------------------------------- /tests/UniqueStringArrayTest.phpt: -------------------------------------------------------------------------------- 1 | UniqueStringArray::from($validValue)); 55 | } 56 | 57 | $append = UniqueStringArray::from([1]); 58 | Assert::equal(['1'], $append->getValues()); 59 | 60 | $append->add('2'); 61 | Assert::equal(['1', '2'], $append->getValues()); 62 | 63 | $append->remove('12345'); 64 | Assert::equal(['1', '2'], $append->getValues()); 65 | 66 | $append->remove('1'); 67 | Assert::equal(['2'], $append->getValues()); 68 | 69 | $empty = UniqueStringArray::extractOrEmpty( 70 | [], 71 | 'not_existing' 72 | ); 73 | 74 | Assert::true($empty->isEmpty()); 75 | Assert::count(0, $empty); 76 | 77 | $data = [ 78 | 'data' => $empty, 79 | ]; 80 | 81 | Assert::noError(static fn () => UniqueStringArray::extract($data, 'data')); 82 | 83 | $containsTest = UniqueStringArray::from( 84 | [ 85 | 'xxx', 86 | ] 87 | ); 88 | Assert::true($containsTest->contains('xxx')); 89 | Assert::false($containsTest->contains('yyy')); 90 | 91 | Assert::true($containsTest->add('yyy')); 92 | Assert::false($containsTest->add('yyy')); 93 | Assert::true($containsTest->contains('yyy')); 94 | 95 | $containsTest->remove('xxx'); 96 | Assert::false($containsTest->contains('xxx')); 97 | 98 | $deductable = UniqueStringArray::from( 99 | [ 100 | 'a', 101 | 'b', 102 | ] 103 | ); 104 | 105 | $toBeDeducted = UniqueStringArray::from( 106 | [ 107 | 'b', 108 | 'c', 109 | ] 110 | ); 111 | 112 | $result = $deductable->deduct($toBeDeducted); 113 | 114 | Assert::equal( 115 | [ 116 | 'a', 117 | ], 118 | $result->toArray() 119 | ); 120 | } 121 | 122 | } 123 | 124 | (new UniqueStringArrayTest())->run(); 125 | -------------------------------------------------------------------------------- /tests/UnsignedFloatTest.phpt: -------------------------------------------------------------------------------- 1 | UnsignedFloat::from(1)); 34 | Assert::noError(static fn () => UnsignedFloat::from(1_000_000.0)); 35 | 36 | Assert::equal(20.0, UnsignedFloat::from(20)->getValue()); 37 | Assert::equal('20', (string) UnsignedFloat::from(20)); 38 | 39 | Assert::equal(0.125, UnsignedFloat::from(0.125)->getValue()); 40 | Assert::equal('0.125', (string) UnsignedFloat::from(0.125)); 41 | } 42 | 43 | } 44 | 45 | (new UnsignedFloatTest())->run(); 46 | -------------------------------------------------------------------------------- /tests/UnsignedIntTest.phpt: -------------------------------------------------------------------------------- 1 | UnsignedInt::from(1)); 36 | Assert::noError(static fn () => UnsignedInt::from(1_000_000)); 37 | 38 | Assert::equal(20, UnsignedInt::from(20)->getValue()); 39 | Assert::equal('20', (string) UnsignedInt::from(20)); 40 | } 41 | 42 | } 43 | 44 | (new UnsignedIntTest())->run(); 45 | -------------------------------------------------------------------------------- /tests/UrlTypeTest.phpt: -------------------------------------------------------------------------------- 1 | getPath()); 31 | } 32 | 33 | public function test1(): void 34 | { 35 | $validValues = [ 36 | 'https://www.vyzva21dni.cz/sq-podekovani-2018/?utm_source=seznam&utm_medium=cpc&utm_campaign=Sklik-[sch] - Cviky%2C Cviceni&utm_content=Stehna&email=xxx@seeznam.cz', 37 | ]; 38 | 39 | foreach ($validValues as $validValue) { 40 | Assert::noError(static fn () => UrlType::from($validValue)); 41 | } 42 | } 43 | 44 | public function testMethods(): void 45 | { 46 | $url = UrlType::from('https://www.seznam.cz/'); 47 | Assert::equal('https://www.seznam.cz/', $url->getValue()); 48 | Assert::equal('https://www.seznam.cz/', $url->getAbsoluteUrl()); 49 | Assert::equal('https://www.seznam.cz/', $url->getBaseUrl()); 50 | Assert::equal('www.seznam.cz', $url->getAuthority()); 51 | Assert::equal('www.seznam.cz', $url->getHost()); 52 | Assert::equal('https', $url->getScheme()); 53 | Assert::equal('/', $url->getPath()); 54 | } 55 | 56 | public function testQueryParameters(): void 57 | { 58 | $url = UrlType::from('https://www.seznam.cz'); 59 | 60 | Assert::equal( 61 | [], 62 | $url->getParameters() 63 | ); 64 | 65 | $parameterX = $url->getQueryParameter( 66 | 'x' 67 | ); 68 | 69 | Assert::null($parameterX); 70 | 71 | $url = $url->withQueryParameter( 72 | 'x', 73 | 'y' 74 | ); 75 | 76 | $hasParameters = $url->hasParameters( 77 | [ 78 | 'x', 79 | ] 80 | ); 81 | 82 | Assert::true($hasParameters); 83 | 84 | $hasParameters = $url->hasParameters( 85 | [ 86 | 'x', 87 | 'z', 88 | ] 89 | ); 90 | 91 | Assert::false($hasParameters); 92 | 93 | $parameters = $url->getParameters(); 94 | Assert::equal( 95 | [ 96 | 'x' => 'y', 97 | ], 98 | $parameters 99 | ); 100 | 101 | Assert::equal('x=y', $url->getQueryString()); 102 | 103 | Assert::equal('y', $url->getParameter('x')); 104 | 105 | Assert::equal('https://www.seznam.cz/?x=y', $url->getValue()); 106 | Assert::equal('https://www.seznam.cz/?x=y', $url->toString()); 107 | 108 | Assert::throws( 109 | static function (): void { 110 | UrlType::from('ddddd'); 111 | }, 112 | InvalidTypeException::class 113 | ); 114 | 115 | $url = $url->withHostName( 116 | HostName::from('test.cz') 117 | ); 118 | 119 | Assert::equal('https://test.cz/?x=y', $url->getValue()); 120 | 121 | $url = $url->withHost( 122 | Domain::from('atlas.cz') 123 | ); 124 | 125 | Assert::equal('https://atlas.cz/?x=y', $url->getValue()); 126 | 127 | $url = $url->withScheme( 128 | 'ftp' 129 | ); 130 | 131 | Assert::equal('ftp://atlas.cz/?x=y', $url->getValue()); 132 | 133 | $url = UrlType::from('https://www.seznam.cz'); 134 | 135 | $url = $url->withPath('test'); 136 | 137 | Assert::equal('https://www.seznam.cz/test', $url->getValue()); 138 | } 139 | 140 | public function testNonAsciiChars(): void 141 | { 142 | $value = 'https://marketadanisova.cz/wp-content/uploads/2018/09/Snímek-obrazovky-2018-09-18-v-21.21.41.png'; 143 | 144 | $url = UrlType::from($value); 145 | 146 | Assert::equal( 147 | 'https://marketadanisova.cz/wp-content/uploads/2018/09/Sni%CC%81mek-obrazovky-2018-09-18-v-21.21.41.png', 148 | $url->getValue() 149 | ); 150 | } 151 | 152 | } 153 | 154 | (new UrlTypeTest())->run(); 155 | -------------------------------------------------------------------------------- /tests/ZipCodeTest.phpt: -------------------------------------------------------------------------------- 1 | getValue()); 40 | } 41 | 42 | Assert::equal('WC2N5DU', ZipCode::from('WC2N 5DU')->getValue()); 43 | 44 | Assert::equal('W22LW', ZipCode::from('w2 2lw')->getValue()); 45 | } 46 | 47 | } 48 | 49 | (new ZipCodeTest())->run(); 50 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |