diff --git a/.circleci/README.md b/.circleci/README.md new file mode 100644 index 0000000..6f3c233 --- /dev/null +++ b/.circleci/README.md @@ -0,0 +1,66 @@ +# CircleCI Continuous Integration + +PHP-Datatypes uses CircleCI for code testing and coverage. + +The project currently supports version `7.4`, `8.0` & `8.1` of PHP. + +## Installation + +In order to validate CircleCI configuration, you'll need to install locally `circleci` cli utility: + +```bash +sudo snap install circleci +``` + +After installation of `circleci` utility, you can assess Coding Standards and Testing in multiple supported PHP versions. + +In order to test CircleCI configiuration file, you can run `circleci config validate`. + +## Quick links + +- [CodeDov](https://app.codecov.io/gh/HRADigital/php-datatypes) +- [Codacy](https://app.codacy.com/gh/HRADigital/php-datatypes/dashboard) +- [CircleCI](https://app.circleci.com/pipelines/github/HRADigital/php-datatypes?filter=all) + +## Coding Standards + +Run `circleci local execute --job php-cs` from the root of the project in order to test Coding Standards. + +Coding standards' job will also give you information about the environment and project it's running on: + +### PHP Version + +```bash +PHP 7.4.26 (cli) (built: Nov 23 2021 21:06:06) ( NTS ) +Copyright (c) The PHP Group +Zend Engine v3.4.0, Copyright (c) Zend Technologies +``` + +### Project's Statistics + +```bash +------------------------------------------------------------------------------- +Language files blank comment code +------------------------------------------------------------------------------- +PHP 108 1129 4016 4139 +Markdown 27 171 0 431 +YAML 1 6 0 83 +JSON 1 0 0 47 +------------------------------------------------------------------------------- +SUM: 137 1306 4016 4700 +------------------------------------------------------------------------------- +``` + +In order to access Code Coverage reports, please go to [CodeDov](https://app.codecov.io/gh/HRADigital/php-datatypes). + +In order to access Code Quality reports, please go to [Codacy](https://app.codacy.com/gh/HRADigital/php-datatypes/dashboard). + +## Testing + +Run `circleci local execute --job php-74` to test `v7.4`, or replace `74` with `80` or `81`, for versions `v8.0` & `v8.1`: + +- `circleci local execute --job php-74` +- `circleci local execute --job php-80` +- `circleci local execute --job php-81` + +In order to access CI reports, please got to [CircleCI](https://app.circleci.com/pipelines/github/HRADigital/php-datatypes?filter=all). diff --git a/.circleci/config.yml b/.circleci/config.yml index 5e68e91..a356492 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,41 +1,107 @@ version: 2.1 + orbs: codecov: codecov/codecov@1.0.4 + jobs: - build: + php-cs: docker: - - image: circleci/php:7.2 + - image: cimg/php:7.4 working_directory: ~/php-datatypes steps: - # GIT - checkout - # SYSTEM - run: - name: Install System Packages + name: Update System command: sudo apt-get update - run: name: Install CLOC command: sudo apt-get -y install cloc - # COMPOSER - run: - name: Install Composer Dependencies - command: composer install - # STATISTICS + name: PHP Version + command: php -v - run: name: Project's Statistics command: git ls-files | xargs cloc - # TESTS - run: - name: Run Tests - command: phpdbg -qrr vendor/bin/phpunit --coverage-clover ~/build/coverage-report + name: Composer Install + command: composer install + - run: + name: Check Coding Standards for PHPv7.4 + command: | + vendor/bin/phpcs --standard=PSR2 --exclude=Squiz.WhiteSpace.ControlStructureSpacing src + vendor/bin/phpcs --standard=PSR2 --exclude=Squiz.WhiteSpace.ControlStructureSpacing tests + + php-74: + docker: + - image: cimg/php:7.4 + working_directory: ~/php-datatypes + steps: + - checkout + - run: + name: Composer Update + command: composer update + - run: + name: Run Tests for PHPv7.4 + command: phpdbg -qrr vendor/bin/phpunit + + php-80: + docker: + - image: cimg/php:8.0 + working_directory: ~/php-datatypes + steps: + - checkout - run: - name: Check coding standards in /src - command: vendor/bin/phpcs --standard=PSR2 --exclude=Squiz.WhiteSpace.ControlStructureSpacing src + name: Composer Update + command: composer update - run: - name: Check coding standards in /tests - command: vendor/bin/phpcs --standard=PSR2 --exclude=Squiz.WhiteSpace.ControlStructureSpacing tests + name: Run Tests for PHPv8.0 + command: phpdbg -qrr vendor/bin/phpunit + + php-81: + docker: + - image: cimg/php:8.1 + working_directory: ~/php-datatypes + steps: + - checkout + - run: + name: Composer Update + command: composer update + - run: + name: Run Tests for PHPv8.1 + command: phpdbg -qrr vendor/bin/phpunit + + php-coverage: + docker: + - image: cimg/php:7.4 + working_directory: ~/php-datatypes + steps: + - checkout + - run: + name: Composer Install + command: composer install + - run: + name: Run Tests for PHPv7.4 + command: phpdbg -qrr vendor/bin/phpunit --coverage-clover ~/build/coverage-report - store_artifacts: - path: ~/build/coverage-report + path: ~/build/coverage-report - codecov/upload: file: ~/build/coverage-report - + +workflows: + ci-flow: + jobs: + - php-cs + - php-74: + requires: + - php-cs + - php-80: + requires: + - php-cs + - php-81: + requires: + - php-cs + - php-coverage: + requires: + - php-74 + - php-80 + - php-81 diff --git a/.gitignore b/.gitignore index 739db1a..4b1abee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ +ci/ vendor/ +notyetdeveloped/ .buildpath .project composer.lock phpunit.xml +.phpunit.result.cache diff --git a/README.md b/README.md index 29c1c5c..bacc907 100755 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # PHP Datatypes ## Master branch build status + [![Build](https://img.shields.io/circleci/build/github/HRADigital/php-datatypes.svg)](https://github.com/HRADigital/php-datatypes) [![Coverage](https://img.shields.io/codecov/c/github/HRADigital/php-datatypes.svg)](https://github.com/HRADigital/php-datatypes) -[![Quality](https://api.codacy.com/project/badge/Grade/3be6c231eea84329878a59a66af49e2f)](https://github.com/HRADigital/php-datatypes) +[![Quality](https://app.codacy.com/project/badge/Grade/de03155208c64196899848458c2ced8a)](https://www.codacy.com/gh/HRADigital/php-datatypes/dashboard?utm_source=github.com&utm_medium=referral&utm_content=HRADigital/php-datatypes&utm_campaign=Badge_Grade) [![Downloads](https://img.shields.io/github/downloads/HRADigital/php-datatypes/total.svg)](https://github.com/HRADigital/php-datatypes) [![Licence](https://img.shields.io/github/license/HRADigital/php-datatypes.svg)](https://github.com/HRADigital/php-datatypes) [![Version](https://img.shields.io/github/release/HRADigital/php-datatypes.svg)](https://github.com/HRADigital/php-datatypes) @@ -11,31 +12,50 @@ ## About -**PHP Datatypes** is a project is based and inspired on many other projects around, and is mainly meant to bring support -for **Scalar objects** and other common **Complex datatypes** into PHP, while native support isn't around. +**PHP Datatypes** is meant to provide an easy way to create your Value Objects/Entities/Aggregates, in a fast and platform agnostic way, +that promotes: + +- Code reusability +- Data normalization +- Type hint enforcement +- Full data serializing +- No 3rd party dependency apart from PHP. Clean/Self reliant project. + +An Aggregate/Entity/ValueObject that extends [AbstractValueObject](/HRADigital/php-datatypes/blob/master/src/ValueObjects/AbstractValueObject.php) +will be built using predefined/tested [Traits](/HRADigital/php-datatypes/tree/master/src/Traits/Entities) for each of the class attributes, +leaving your class definition cleaned/free for your business logic implementation. + +This will also allow you to reuse/load your objects with data that can come from a Database, Webservice, Event payload, etc... + +Getters/Accessors for class attributes will return ValueObjects instead of primitive types, as much as possible. All these datatypes will +also be included in the package, as it doesn't have any dependencies apart from, PHP itself. + +To learn how to use this package, please go to [AbstractValueObject](/HRADigital/php-datatypes/blob/master/src/ValueObjects/) documentation. -Some of the projects that inspired this one, are mainly [Nikita Popov's Scalar Objects](https://github.com/nikic/scalar_objects), -but also [Martin Helmich's Scalar Classes](https://github.com/martin-helmich/php-scalarclasses/) and -[Michael Hall's Datatypes](https://github.com/themichaelhall/datatypes/). +### Inspiration -### Scalar objects +Some of the projects that inspired this one, are mainly [Nikita Popov's Scalar Objects](/nikic/scalar_objects), +but also [Martin Helmich's Scalar Classes](/martin-helmich/php-scalarclasses/) and +[Michael Hall's Datatypes](/themichaelhall/datatypes/). -**PHP Datatypes** will initially wrap common functionality to PHP's native datatypes, such as `string`, `integer`, `float` -and `boolean`. +Due to the "_No 3rd party dependency_" rule, this package will use some simplified versions of more popular datatypes. Some examples are: -### Complex datatypes +- [synfony/string](/symfony/string), for String related manipulations. +- [nesbot/carbon](/briannesbitt/Carbon), for DateTime manipulations. +- ... -There will also be wrapping classes around **Complex Datatypes** such as `Datetime`, `Email`, `Color`, `UrlAddress`, ..., -and both _Linear_ and _Associative_ **Collections** such as `Queues`, `Stacks` and `Sets`/`Stores`. +## Requirements & Installation -## Installation +- PHP >= 7.4||8.* -In order to install this package, just add it to your **composer**, by executing `composer require hradigital/php-datatypes`. +```bash +composer require hradigital/php-datatypes +``` ## Usage For more information about how to to use these Datatypes, please see the project's **usage notes** and some implementation examples -in [here](https://github.com/HRADigital/php-datatypes/tree/master/src/). +in [here](src/). ## Contributing diff --git a/composer.json b/composer.json index 208714b..5eaa4e8 100755 --- a/composer.json +++ b/composer.json @@ -22,20 +22,25 @@ "mutable objects" ], "require": { - "php": "^7.2" + "php": "^7.4||^8.0" }, "require-dev": { - "phpunit/phpunit": "^7.5", + "phpunit/phpunit": "^9.0", "squizlabs/php_codesniffer": "^3.0@dev" }, "autoload": { "psr-4": { - "Hradigital\\Datatypes\\": "src/" + "HraDigital\\Datatypes\\": "src/" } }, "autoload-dev": { "psr-4": { - "Hradigital\\Tests\\Datatypes\\": "tests/" + "HraDigital\\Tests\\Datatypes\\": "tests/" } + }, + "scripts": { + "test-code" : "./vendor/bin/phpunit --coverage-clover ci/coverage-report.xml --coverage-html ci/coverage-report --log-junit ci/tests-results.xml", + "test-cs" : "./vendor/bin/phpcs -p --colors --no-cache --report=full --standard=PSR2 --exclude=Squiz.WhiteSpace.ControlStructureSpacing src/", + "test-all" : "composer run test-code && composer run test-cs" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c8fb2d6..28b7aab 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,24 +1,27 @@ - + + - - - tests/Unit - - + + + src + + - - - src - - + + + tests/Unit + + diff --git a/src/Attributes/General/HasActiveTrait.php b/src/Attributes/General/HasActiveTrait.php new file mode 100755 index 0000000..d62f898 --- /dev/null +++ b/src/Attributes/General/HasActiveTrait.php @@ -0,0 +1,39 @@ +active = $active; + } + + /** + * Returns TRUE if the record is marked as ACTIVE in the system. + * + * @return bool + */ + public function isActive(): bool + { + return $this->active; + } +} diff --git a/src/Attributes/General/HasAliasTrait.php b/src/Attributes/General/HasAliasTrait.php new file mode 100755 index 0000000..863aa1a --- /dev/null +++ b/src/Attributes/General/HasAliasTrait.php @@ -0,0 +1,54 @@ +trim()->toLower()->replace(' ', '_'); + + // Validates if alias is filled. + if ($aliasValue->getLength() === 0) { + throw NonEmptyStringException::withName('$alias'); + } + + // We'll set the alias value on the attribute, but use the sanitizeAlias() method + // to sanitize its value. + $this->alias = $aliasValue; + } + + /** + * Returns the Entity's alias. + * + * @return Str + */ + public function getAlias(): Str + { + return $this->alias; + } +} diff --git a/src/Attributes/General/HasCreatedAtTrait.php b/src/Attributes/General/HasCreatedAtTrait.php new file mode 100644 index 0000000..c0ad394 --- /dev/null +++ b/src/Attributes/General/HasCreatedAtTrait.php @@ -0,0 +1,41 @@ +created_at = Datetime::fromString($timestamp); + } + + /** + * Returns a Datetime representation from the instant the record was last updated. + * + * @return Datetime + */ + public function getCreatedAt(): Datetime + { + return $this->created_at; + } +} diff --git a/src/Attributes/General/HasDeletedAtTrait.php b/src/Attributes/General/HasDeletedAtTrait.php new file mode 100644 index 0000000..ca10bd0 --- /dev/null +++ b/src/Attributes/General/HasDeletedAtTrait.php @@ -0,0 +1,53 @@ +deleted_at = ($timestamp ? Datetime::fromString($timestamp) : null); + } + + /** + * Returns a Datetime representation from the instant the record was marked as deleted. + * + * @return Datetime|null + */ + public function getDeletedAt(): ?Datetime + { + return $this->deleted_at; + } + + /** + * Returns TRUE if the record is marked as deleted in the system. + * + * @return bool + */ + public function isDeleted(): bool + { + return ($this->deleted_at !== null); + } +} diff --git a/src/Attributes/General/HasEmailTrait.php b/src/Attributes/General/HasEmailTrait.php new file mode 100644 index 0000000..594a5f9 --- /dev/null +++ b/src/Attributes/General/HasEmailTrait.php @@ -0,0 +1,41 @@ +email = EmailAddress::create($email); + } + + /** + * Returns an EmailAddress representation for the record's E-mail Address. + * + * @return EmailAddress|null + */ + public function getEmail(): ?EmailAddress + { + return $this->email; + } +} diff --git a/src/Attributes/General/HasFeatureTrait.php b/src/Attributes/General/HasFeatureTrait.php new file mode 100755 index 0000000..c95b23c --- /dev/null +++ b/src/Attributes/General/HasFeatureTrait.php @@ -0,0 +1,39 @@ +is_featured = $featured; + } + + /** + * Returns TRUE if the record is marked as FEATURED in the system. + * + * @return bool + */ + public function isFeatured(): bool + { + return $this->is_featured; + } +} diff --git a/src/Attributes/General/HasHitsTrait.php b/src/Attributes/General/HasHitsTrait.php new file mode 100755 index 0000000..0985782 --- /dev/null +++ b/src/Attributes/General/HasHitsTrait.php @@ -0,0 +1,49 @@ +hits = $hits; + } + + /** + * Returns the number of Hits. + * + * @return int + */ + public function getHits(): int + { + return $this->hits; + } +} diff --git a/src/Attributes/General/HasNameTrait.php b/src/Attributes/General/HasNameTrait.php new file mode 100755 index 0000000..007c69f --- /dev/null +++ b/src/Attributes/General/HasNameTrait.php @@ -0,0 +1,50 @@ +trim(); + if ($nameValue->getLength() === 0) { + throw NonEmptyStringException::withName('$name'); + } + + $this->name = $nameValue; + } + + /** + * Returns the Instance's name. + * + * @return Str + */ + public function getName(): Str + { + return $this->name; + } +} diff --git a/src/Attributes/General/HasOrderingTrait.php b/src/Attributes/General/HasOrderingTrait.php new file mode 100755 index 0000000..e758548 --- /dev/null +++ b/src/Attributes/General/HasOrderingTrait.php @@ -0,0 +1,49 @@ +ordering = $order; + } + + /** + * The ordering of this record, in a parent container's context. + * + * @return int + */ + public function getOrdering(): int + { + return $this->ordering; + } +} diff --git a/src/Attributes/General/HasPasswordTrait.php b/src/Attributes/General/HasPasswordTrait.php new file mode 100755 index 0000000..0fca223 --- /dev/null +++ b/src/Attributes/General/HasPasswordTrait.php @@ -0,0 +1,41 @@ +password = ($password ? Str::create($password) : null); + } + + /** + * Returns record's Password value. + * + * @return Str|null + */ + public function getPassword(): ?Str + { + return $this->password; + } +} diff --git a/src/Attributes/General/HasPositiveIntegerIDTrait.php b/src/Attributes/General/HasPositiveIntegerIDTrait.php new file mode 100644 index 0000000..1bb5904 --- /dev/null +++ b/src/Attributes/General/HasPositiveIntegerIDTrait.php @@ -0,0 +1,57 @@ +id = $id; + } + + /** + * Returns the Positive Integer ID + * + * @return int + */ + public function getId(): int + { + return $this->id; + } + + /** + * If record is a new record. Not returned from DB. + * + * Validates if the VO/Entity has an ID set. + * + * @return bool + */ + public function isNew(): bool + { + return ($this->id === null); + } +} diff --git a/src/Attributes/General/HasPublishedTimestampsTrait.php b/src/Attributes/General/HasPublishedTimestampsTrait.php new file mode 100755 index 0000000..403c4a3 --- /dev/null +++ b/src/Attributes/General/HasPublishedTimestampsTrait.php @@ -0,0 +1,20 @@ +is_published = $published; + } + + /** + * Returns TRUE if the record is marked as PUBLISHED for the frontend. + * + * @return bool + */ + public function isPublished(): bool + { + return $this->is_published; + } +} diff --git a/src/Attributes/General/HasSeoFieldsTrait.php b/src/Attributes/General/HasSeoFieldsTrait.php new file mode 100755 index 0000000..6f39799 --- /dev/null +++ b/src/Attributes/General/HasSeoFieldsTrait.php @@ -0,0 +1,148 @@ +. + * Setting the value. + * + * @param string|null $title - New value to be set on Attribute. + * + * @throws InvalidStringLengthException - Supplied Seo Title must be a non empty string. + * + * @link https://seopressor.com/blog/google-title-meta-descriptions-length + * @return void + */ + protected function castSeoTitle(?string $title = null): void + { + // Setting the max length for Seo description. + $titleValue = ($title ? Str::create($title)->trim() : null); + + if ($titleValue !== null && $titleValue->getLength() > 70) { + throw InvalidStringLengthException::withNameAndLength("Seo title", 70); + } + + $this->seo_title = $this->seoSanitize($titleValue); + } + + /** + * Checking the character limitations of the . + * Setting the value. + * + * @param string|null $description - New value to be set on Attribute. + * + * @throws InvalidStringLengthException - Supplied Seo Description must be a non empty string. + * + * @link https://seopressor.com/blog/google-title-meta-descriptions-length + * @return void + */ + protected function castSeoDescription(?string $description = null): void + { + // Setting the max length for Seo description. + $descriptionValue = ($description ? Str::create($description) : null); + + if ($descriptionValue !== null && $descriptionValue->trim()->getLength() > 160) { + throw InvalidStringLengthException::withNameAndLength("Seo description", 160); + } + + $this->seo_description = $this->seoSanitize($descriptionValue); + } + + /** + * Checking the character limitations of the . + * Setting the value. + * + * @param string|null $keywords - New value to be set on Attribute. + * + * @throws InvalidStringLengthException - Supplied Seo Keywords must be a non empty string. + * + * @link https://www.quora.com/What-is-the-minimum-length-of-a-meta-keyword-in-on-page-SEO + * @return void + */ + protected function castSeoKeywords(string $keywords = null): void + { + // Setting the max length for Seo keywords. + $keywordsValue = ($keywords ? Str::create($keywords) : null); + + if ($keywordsValue !== null && $keywordsValue->trim()->getLength() > 255) { + throw InvalidStringLengthException::withNameAndLength("Seo keywords", 255); + } + + $this->seo_keywords = $this->seoSanitize($keywordsValue); + } + + /** + * Returns the Entity's seo title. + * + * @return Str|null + */ + public function getSeoTitle(): ?Str + { + return $this->seo_title; + } + + /** + * Returns the Entity's seo description. + * + * @return Str|null + */ + public function getSeoDescription(): ?Str + { + return $this->seo_description; + } + + /** + * Returns the Entity's seo keywords. + * + * @return Str|null + */ + public function getSeoKeywords(): ?Str + { + return $this->seo_keywords; + } + + /** + * Sanitizes the individual Seo field in the Entity. + * + * @param Str|null $seoField - New Hit's value. + * @return Str|null + */ + private function seoSanitize(?Str $seoField = null): ?Str + { + // Checking if the value is an empty string or a string made of spaces. + if ($seoField !== null && $seoField->trim()->getLength() === 0) { + $seoField = null; + } + + // If there is a valid string trimming the spaces form the beginning and the end. + if ($seoField !== null) { + $seoField = $seoField->trim(); + } + + return $seoField; + } +} diff --git a/src/Attributes/General/HasSurnameTrait.php b/src/Attributes/General/HasSurnameTrait.php new file mode 100755 index 0000000..f0fb65c --- /dev/null +++ b/src/Attributes/General/HasSurnameTrait.php @@ -0,0 +1,51 @@ +trim(); + + if ($surnameValue->getLength() === 0) { + throw NonEmptyStringException::withName('$surname'); + } + + $this->surname = $surnameValue; + } + + /** + * Returns the Instance's Surname. + * + * @return Str + */ + public function getSurname(): Str + { + return $this->surname; + } +} diff --git a/src/Attributes/General/HasTimestampsTrait.php b/src/Attributes/General/HasTimestampsTrait.php new file mode 100755 index 0000000..43ecf2b --- /dev/null +++ b/src/Attributes/General/HasTimestampsTrait.php @@ -0,0 +1,20 @@ +trim(); + if ($titleValue->getLength() === 0) { + throw NonEmptyStringException::withName('$title'); + } + + $this->title = $titleValue; + } + + /** + * Returns the Instance's Title. + * + * @return Str + */ + public function getTitle(): Str + { + return $this->title; + } +} diff --git a/src/Attributes/General/HasUpdatableUpdatedAtTrait.php b/src/Attributes/General/HasUpdatableUpdatedAtTrait.php new file mode 100644 index 0000000..b1d56f2 --- /dev/null +++ b/src/Attributes/General/HasUpdatableUpdatedAtTrait.php @@ -0,0 +1,31 @@ +updated_at = Datetime::now(); + } +} diff --git a/src/Attributes/General/HasUpdatedAtTrait.php b/src/Attributes/General/HasUpdatedAtTrait.php new file mode 100644 index 0000000..b101a6f --- /dev/null +++ b/src/Attributes/General/HasUpdatedAtTrait.php @@ -0,0 +1,41 @@ +updated_at = ($timestamp ? Datetime::fromString($timestamp) : null); + } + + /** + * Returns a Datetime representation from the instant the record was last updated. + * + * @return Datetime|null + */ + public function getUpdatedAt(): ?Datetime + { + return $this->updated_at; + } +} diff --git a/src/Attributes/General/HasUuidTrait.php b/src/Attributes/General/HasUuidTrait.php new file mode 100644 index 0000000..34155c7 --- /dev/null +++ b/src/Attributes/General/HasUuidTrait.php @@ -0,0 +1,41 @@ +uuid = Str::create($uuid); + } + + /** + * Returns the Universal Unique Identifier. + * + * @return Str + */ + public function getUuid(): Str + { + return $this->uuid; + } +} diff --git a/src/Attributes/General/README.md b/src/Attributes/General/README.md new file mode 100644 index 0000000..3e3fe64 --- /dev/null +++ b/src/Attributes/General/README.md @@ -0,0 +1 @@ +# Entity related General Pourpose Traits diff --git a/src/Attributes/Location/HasAddressTrait.php b/src/Attributes/Location/HasAddressTrait.php new file mode 100644 index 0000000..41c3aab --- /dev/null +++ b/src/Attributes/Location/HasAddressTrait.php @@ -0,0 +1,41 @@ +address = Str::create($address)->trim(); + } + + /** + * Returns the Entity's Address. + * + * @return Str + */ + public function getAddress(): Str + { + return $this->address; + } +} diff --git a/src/Attributes/Location/HasCityTrait.php b/src/Attributes/Location/HasCityTrait.php new file mode 100644 index 0000000..6419bb2 --- /dev/null +++ b/src/Attributes/Location/HasCityTrait.php @@ -0,0 +1,50 @@ +trim() : null; + + if ($cityValue !== null && $cityValue->getLength() === 0) { + throw NonEmptyStringException::withName('$city'); + } + + $this->city = $cityValue; + } + + /** + * Returns the Entity's City. + * + * @return Str|null + */ + public function getCity(): ?Str + { + return $this->city; + } +} diff --git a/src/Attributes/Location/HasCountryCodeTrait.php b/src/Attributes/Location/HasCountryCodeTrait.php new file mode 100644 index 0000000..0d213e5 --- /dev/null +++ b/src/Attributes/Location/HasCountryCodeTrait.php @@ -0,0 +1,41 @@ +country_code = Str::create($countryCode); + } + + /** + * Returns the Entity's Country Code. + * + * @return Str + */ + public function getCountryCode(): Str + { + return $this->country_code; + } +} diff --git a/src/Attributes/Location/HasCountryTrait.php b/src/Attributes/Location/HasCountryTrait.php new file mode 100644 index 0000000..002129b --- /dev/null +++ b/src/Attributes/Location/HasCountryTrait.php @@ -0,0 +1,41 @@ +country = Str::create($country)->trim(); + } + + /** + * Returns the Entity's Country. + * + * @return Str + */ + public function getCountry(): Str + { + return $this->country; + } +} diff --git a/src/Attributes/Location/HasDistrictTrait.php b/src/Attributes/Location/HasDistrictTrait.php new file mode 100644 index 0000000..132925c --- /dev/null +++ b/src/Attributes/Location/HasDistrictTrait.php @@ -0,0 +1,50 @@ +trim() : null; + + if ($districtValue !== null && $districtValue->getLength() === 0) { + throw NonEmptyStringException::withName('$district'); + } + + $this->district = $districtValue; + } + + /** + * Returns the Entity's District. + * + * @return Str|null + */ + public function getDistrict(): ?Str + { + return $this->district; + } +} diff --git a/src/Attributes/Location/HasLatitudeTrait.php b/src/Attributes/Location/HasLatitudeTrait.php new file mode 100644 index 0000000..b0e8d60 --- /dev/null +++ b/src/Attributes/Location/HasLatitudeTrait.php @@ -0,0 +1,39 @@ +latitude = $latitude; + } + + /** + * Returns the Entity's Latitude. + * + * @return float + */ + public function getLatitude(): float + { + return $this->latitude; + } +} diff --git a/src/Attributes/Location/HasLongitudeTrait.php b/src/Attributes/Location/HasLongitudeTrait.php new file mode 100644 index 0000000..99ba66f --- /dev/null +++ b/src/Attributes/Location/HasLongitudeTrait.php @@ -0,0 +1,39 @@ +longitude = $longitude; + } + + /** + * Returns the Entity's Longitude. + * + * @return float + */ + public function getLongitude(): float + { + return $this->longitude; + } +} diff --git a/src/Attributes/Location/HasParishTrait.php b/src/Attributes/Location/HasParishTrait.php new file mode 100644 index 0000000..24a0ab4 --- /dev/null +++ b/src/Attributes/Location/HasParishTrait.php @@ -0,0 +1,50 @@ +trim() : null; + + if ($parishValue !== null && $parishValue->getLength() === 0) { + throw NonEmptyStringException::withName('$parish'); + } + + $this->parish = $parishValue; + } + + /** + * Returns the Entity's Parish. + * + * @return Str|null + */ + public function getParish(): ?Str + { + return $this->parish; + } +} diff --git a/src/Attributes/Location/HasPostalCodeTrait.php b/src/Attributes/Location/HasPostalCodeTrait.php new file mode 100644 index 0000000..b133b0a --- /dev/null +++ b/src/Attributes/Location/HasPostalCodeTrait.php @@ -0,0 +1,41 @@ +postal_code = Str::create($postalCode)->trim(); + } + + /** + * Returns the Entity's Postal Code. + * + * @return Str + */ + public function getPostalCode(): Str + { + return $this->postal_code; + } +} diff --git a/src/Attributes/Location/HasStreetAdditionalTrait.php b/src/Attributes/Location/HasStreetAdditionalTrait.php new file mode 100644 index 0000000..7ae2782 --- /dev/null +++ b/src/Attributes/Location/HasStreetAdditionalTrait.php @@ -0,0 +1,41 @@ +street_additional = Str::create($street)->trim(); + } + + /** + * Returns the Entity's Street Additional. + * + * @return Str + */ + public function getStreetAdditional(): Str + { + return $this->street_additional; + } +} diff --git a/src/Attributes/Location/HasStreetNumberTrait.php b/src/Attributes/Location/HasStreetNumberTrait.php new file mode 100644 index 0000000..a766a12 --- /dev/null +++ b/src/Attributes/Location/HasStreetNumberTrait.php @@ -0,0 +1,41 @@ +street_no = Str::create($number)->trim(); + } + + /** + * Returns the Entity's Street Number. + * + * @return Str + */ + public function getStreetNumber(): Str + { + return $this->street_no; + } +} diff --git a/src/Attributes/Location/HasStreetTrait.php b/src/Attributes/Location/HasStreetTrait.php new file mode 100644 index 0000000..943e673 --- /dev/null +++ b/src/Attributes/Location/HasStreetTrait.php @@ -0,0 +1,41 @@ +street = Str::create($street)->trim(); + } + + /** + * Returns the Entity's Street. + * + * @return Str + */ + public function getStreet(): Str + { + return $this->street; + } +} diff --git a/src/Attributes/Location/README.md b/src/Attributes/Location/README.md new file mode 100644 index 0000000..673ecf8 --- /dev/null +++ b/src/Attributes/Location/README.md @@ -0,0 +1 @@ +# Entity related Location Traits diff --git a/src/Attributes/Personal/HasCountryOfBirthTrait.php b/src/Attributes/Personal/HasCountryOfBirthTrait.php new file mode 100644 index 0000000..8071b24 --- /dev/null +++ b/src/Attributes/Personal/HasCountryOfBirthTrait.php @@ -0,0 +1,48 @@ +trim(); + + if ($countryValue->getLength() === 0) { + throw NonEmptyStringException::withName('$country_of_birth'); + } + + $this->country_of_birth = $countryValue; + } + + /** + * Returns the Entity's Country of Birth. + * + * @return Str + */ + public function getCountryOfBirth(): Str + { + return $this->country_of_birth; + } +} diff --git a/src/Attributes/Personal/HasDateOfBirthTrait.php b/src/Attributes/Personal/HasDateOfBirthTrait.php new file mode 100644 index 0000000..1d29e5f --- /dev/null +++ b/src/Attributes/Personal/HasDateOfBirthTrait.php @@ -0,0 +1,41 @@ +dob = Datetime::fromString($dob); + } + + /** + * Returns a Datetime representation for the Entity's Date of Birth. + * + * @return Datetime|null + */ + public function getDateOfBirth(): ?Datetime + { + return $this->dob; + } +} diff --git a/src/Attributes/Personal/HasGenderTrait.php b/src/Attributes/Personal/HasGenderTrait.php new file mode 100644 index 0000000..2fe3e17 --- /dev/null +++ b/src/Attributes/Personal/HasGenderTrait.php @@ -0,0 +1,53 @@ +toLower()->toUpperFirst(); + + if (!( + $genderValue->equals('Male') || + $genderValue->equals('Female') || + $genderValue->equals('Other') + )) { + throw new UnexpectedEntityValueException('$gender'); + } + + $this->gender = $genderValue; + } + + /** + * Returns the Entity's Gender. + * + * @return Str + */ + public function getGender(): Str + { + return $this->gender; + } +} diff --git a/src/Attributes/Personal/HasNationalityTrait.php b/src/Attributes/Personal/HasNationalityTrait.php new file mode 100644 index 0000000..58aefe0 --- /dev/null +++ b/src/Attributes/Personal/HasNationalityTrait.php @@ -0,0 +1,48 @@ +trim() : null; + + if ($nationalityValue !== null && $nationalityValue->getLength() === 0) { + throw NonEmptyStringException::withName('$nationality'); + } + + $this->nationality = $nationalityValue; + } + + /** + * Returns the Entity's Nationality. + * + * @return Str|null + */ + public function getNationality(): ?Str + { + return $this->nationality; + } +} diff --git a/src/Attributes/Personal/HasPhotoTrait.php b/src/Attributes/Personal/HasPhotoTrait.php new file mode 100755 index 0000000..136f5ce --- /dev/null +++ b/src/Attributes/Personal/HasPhotoTrait.php @@ -0,0 +1,61 @@ +trim(); + + if ($photoValue->getLength() === 0) { + throw NonEmptyStringException::withName('$photo'); + } + + $this->photo = $photoValue; + } + + /** + * Returns the Instance's Profile's Photo. + * + * @return Str|null + */ + public function getPhoto(): ?Str + { + return $this->photo; + } + + /** + * If record has Profile's Photo. + * + * @return bool + */ + public function hasPhoto(): bool + { + return ($this->photo !== null); + } +} diff --git a/src/Attributes/Personal/README.md b/src/Attributes/Personal/README.md new file mode 100644 index 0000000..7945b58 --- /dev/null +++ b/src/Attributes/Personal/README.md @@ -0,0 +1 @@ +# Entity related Personal Traits diff --git a/src/Attributes/Professional/HasIndustryTrait.php b/src/Attributes/Professional/HasIndustryTrait.php new file mode 100755 index 0000000..d031cf3 --- /dev/null +++ b/src/Attributes/Professional/HasIndustryTrait.php @@ -0,0 +1,51 @@ +trim() : null; + + if ($industryValue !== null && $industryValue->getLength() === 0) { + throw NonEmptyStringException::withName('$occupation'); + } + + $this->industry = $industryValue; + } + + /** + * Returns the Instance's Profile's Industry. + * + * @return Str|null + */ + public function getIndustry(): ?Str + { + return $this->industry; + } +} diff --git a/src/Attributes/Professional/HasOccupationTrait.php b/src/Attributes/Professional/HasOccupationTrait.php new file mode 100755 index 0000000..36f364c --- /dev/null +++ b/src/Attributes/Professional/HasOccupationTrait.php @@ -0,0 +1,61 @@ +trim() : null; + + if ($occupationValue !== null && $occupationValue->getLength() === 0) { + throw NonEmptyStringException::withName('$occupation'); + } + + $this->occupation = $occupationValue; + } + + /** + * Returns the Instance's Profile's Occupation. + * + * @return Str|null + */ + public function getOccupation(): ?Str + { + return $this->occupation; + } + + /** + * If record has Profile's Occupation. + * + * @return bool + */ + public function hasOccupation(): bool + { + return ($this->occupation !== null); + } +} diff --git a/src/Attributes/Professional/README.md b/src/Attributes/Professional/README.md new file mode 100644 index 0000000..f40d577 --- /dev/null +++ b/src/Attributes/Professional/README.md @@ -0,0 +1 @@ +# Entity related Timezone Traits diff --git a/src/Attributes/README.md b/src/Attributes/README.md new file mode 100755 index 0000000..88ed938 --- /dev/null +++ b/src/Attributes/README.md @@ -0,0 +1,35 @@ +# Attribute's related Traits + +In this namespace, you'll several Traits for individual fields, that will help you build your Aggregates/Entities/Value Objects. + +Each individual Trait, contains minimum functionality for a specific field. + +When adding each of these Traits to your object, you'll automatically add minimum state handling for that specific field. + +## Attribute Loading + +On the following example, you can see an object with an e-mail address being created: + +```php +use HraDigital\Datatypes\Attributes\General\HasEmailTrait; +use HraDigital\Datatypes\ValueObjects\AbstractValueObject; + +class MyObject extends AbstractValueObject +{ + use HasEmailTrait; +} +``` + +## Attribute Accessing + +You can load and access the object's data in the following manner: + +```php +$object = new MyObject([ + 'email' => 'my.email.address@domain.tld', +]); + +echo $object->getEmail(); // my.email.address@domain.tld +echo $object->getEmail()->getUsername()->toUpper(); // MY.EMAIL.ADDRESS + +``` diff --git a/src/Attributes/SocialMedia/HasFacebookProfileTrait.php b/src/Attributes/SocialMedia/HasFacebookProfileTrait.php new file mode 100755 index 0000000..dd17fb6 --- /dev/null +++ b/src/Attributes/SocialMedia/HasFacebookProfileTrait.php @@ -0,0 +1,51 @@ +facebook = $facebook ? Str::create($facebook)->trim() : null; + } + + /** + * Retrieves record's Social Media account's URL. + * + * @return Str|null + */ + public function getFacebookUrl(): ?Str + { + return $this->facebook; + } + + /** + * Is Facebook's Social Media account URL is set. + * + * @return bool + */ + public function hasFacebookProfileUrl(): bool + { + return ($this->facebook !== null); + } +} diff --git a/src/Attributes/SocialMedia/HasInstagramProfileTrait.php b/src/Attributes/SocialMedia/HasInstagramProfileTrait.php new file mode 100755 index 0000000..11379c5 --- /dev/null +++ b/src/Attributes/SocialMedia/HasInstagramProfileTrait.php @@ -0,0 +1,51 @@ +instagram = $instagram ? Str::create($instagram)->trim() : null; + } + + /** + * Retrieves record's Social Media account's URL. + * + * @return Str|null + */ + public function getInstagramUrl(): ?Str + { + return $this->instagram; + } + + /** + * Is Instagram's Social Media account URL is set. + * + * @return bool + */ + public function hasInstagramProfileUrl(): bool + { + return ($this->instagram !== null); + } +} diff --git a/src/Attributes/SocialMedia/HasLinkedinProfileTrait.php b/src/Attributes/SocialMedia/HasLinkedinProfileTrait.php new file mode 100755 index 0000000..49666aa --- /dev/null +++ b/src/Attributes/SocialMedia/HasLinkedinProfileTrait.php @@ -0,0 +1,51 @@ +linkedin = $linkedin ? Str::create($linkedin)->trim() : null; + } + + /** + * Retrieves record's Social Media account's URL. + * + * @return Str|null + */ + public function getLinkedinUrl(): ?Str + { + return $this->linkedin; + } + + /** + * Is Linkedin's Social Media account URL is set. + * + * @return bool + */ + public function hasLinkedinProfileUrl(): bool + { + return ($this->linkedin !== null); + } +} diff --git a/src/Attributes/SocialMedia/HasTwitterProfileTrait.php b/src/Attributes/SocialMedia/HasTwitterProfileTrait.php new file mode 100755 index 0000000..bde4923 --- /dev/null +++ b/src/Attributes/SocialMedia/HasTwitterProfileTrait.php @@ -0,0 +1,51 @@ +twitter = $twitter ? Str::create($twitter)->trim() : null; + } + + /** + * Retrieves record's Social Media account's URL. + * + * @return Str|null + */ + public function getTwitterUrl(): ?Str + { + return $this->twitter; + } + + /** + * Is Twitter's Social Media account URL is set. + * + * @return bool + */ + public function hasTwitterProfileUrl(): bool + { + return ($this->twitter !== null); + } +} diff --git a/src/Attributes/SocialMedia/README.md b/src/Attributes/SocialMedia/README.md new file mode 100644 index 0000000..64d26e6 --- /dev/null +++ b/src/Attributes/SocialMedia/README.md @@ -0,0 +1 @@ +# Entity related Social Media Traits diff --git a/src/Attributes/Timezone/HasTimestampTrait.php b/src/Attributes/Timezone/HasTimestampTrait.php new file mode 100755 index 0000000..2d3645f --- /dev/null +++ b/src/Attributes/Timezone/HasTimestampTrait.php @@ -0,0 +1,39 @@ +timestamp = $timestamp; + } + + /** + * Returns the Instance's Unix timestamp. + * + * @return int + */ + public function getTimestamp(): int + { + return $this->timestamp; + } +} diff --git a/src/Attributes/Timezone/README.md b/src/Attributes/Timezone/README.md new file mode 100644 index 0000000..f40d577 --- /dev/null +++ b/src/Attributes/Timezone/README.md @@ -0,0 +1 @@ +# Entity related Timezone Traits diff --git a/src/Collections/Associative/README.md b/src/Collections/Associative/README.md new file mode 100644 index 0000000..154ed20 --- /dev/null +++ b/src/Collections/Associative/README.md @@ -0,0 +1,24 @@ +# Associative Collections + +## Store / Set + +In a **Set**, the order of data items does not matter (or is undefined) but duplicate data items are not permitted. +Examples of operations on **Sets** are the addition and removal of data items and searching for a data item in the +**Set**. + +Some languages support **Sets** directly. In others, **Sets** can be implemented by a hash table with dummy values; +only the keys are used in representing the **Set**. + +## Multisets / Bag + +In a **Multiset** (or _**Bag**_), like in a **Set**, the order of data items does not matter, but in this case +duplicate data items are permitted. Examples of operations on **Multisets** are the addition and removal of data items +and determining how many duplicates of a particular data item are present in the **Multiset**. + +**Multisets** can be transformed into **Lists** by the action of sorting. + +## Associative arrays / Map / Dictionary / Lookup Table / Hash + +In an **Associative Array** (or **Map**, **Dictionary**, **Lookup table**), like in a **Dictionary**, a _lookup_ on +a key (like a word) provides a value (like a definition). The value might be a reference to a compound data structure. +A _Hash Table_ is usually an efficient implementation, and thus this data type is often known as a "_hash_". diff --git a/src/Collections/Associative/Store.php b/src/Collections/Associative/Store.php new file mode 100644 index 0000000..505faf1 --- /dev/null +++ b/src/Collections/Associative/Store.php @@ -0,0 +1,253 @@ +context = $context; + } + + /** + * Returns number of items in the Store. + * + * @return int + */ + public function count(): int + { + return \count($this->store); + } + + /** + * Retrieves all existing Values from the Store. + * + * @return array + */ + public function getValues(): array + { + return $this->store; + } + + /** + * Retrieves a value from the Store. + * + * If the value is not found, the supplied $default value will be returned. + * + * @param string $name - Name of the Value to be retrieved. + * @param string|NULL $default - Default returned value, of not found. + * + * @throws \InvalidArgumentException - If supplied name is a not a non empty string. + * @return string|NULL + */ + public function get(string $name, ?string $default = null): ?string + { + // Sanitize provided $name/key. + $name = $this->sanitizeName($name); + + // Validate if the provided key is available in the "store". + if ($this->has($name)) { + return $this->store[$this->name($name)]; + } + + return $default; + } + + /** + * Returns TRUE of the value exists in the store. + * FALSE otherwise. + * + * @param string $name - Name of the Value to be searched. + * + * @throws \InvalidArgumentException - If supplied name is a not a non empty string. + * @return bool + */ + public function has(string $name): bool + { + // Sanitize provided $name/key. + $name = $this->sanitizeName($name); + + return \array_key_exists($this->name($name), $this->store); + } + + /** + * Sets a value in the Store. + * + * @param string $name - Name of the Value to be set. + * @param string $value - Value to be set. + * + * @throws NonEmptyStringException - If supplied name is a not a non empty string. + * @return void + */ + public function set(string $name, string $value): void + { + // Sanitize provided $name/key. + $name = $this->sanitizeName($name); + + // Sets the value in the store. + $this->store[$this->name($name)] = $value; + } + + /** + * Adds a non existing value in the Store. + * + * @param string $name - Name of the Value to be added. + * @param string $value - Value to be added. + * + * @throws NonEmptyStringException - If supplied name is a not a non empty string. + * @return bool + */ + public function add(string $name, string $value): bool + { + // Sanitize provided $name/key. + $name = $this->sanitizeName($name); + + // Validate if the provided key is available in the "store". + if ($this->has($name)) { + return false; + } + + // Sets the value in the store. + $this->set($name, $value); + + return true; + } + + /** + * Edits an existing value in the Store. + * + * @param string $name - Name of the Value to be edited. + * @param string $value - Value to be edited. + * + * @throws NonEmptyStringException - If supplied name is a not a non empty string. + * @return bool + */ + public function edit(string $name, string $value): bool + { + // Sanitize provided $name/key. + $name = $this->sanitizeName($name); + + // Validate if the provided key is available in the "store". + if (! $this->has($name)) { + return false; + } + + // Sets the value in the store. + $this->set($name, $value); + + return true; + } + + /** + * Removes a given value from the Store. + * + * @param string $name - Name of the value to be removed from the store. + * + * @throws NonEmptyStringException - If supplied name is a not a non empty string. + * @return bool + */ + public function delete(string $name): bool + { + // Sanitize provided $name/key. + $name = $this->sanitizeName($name); + + // Validate if the provided key is available in the "store". + if (! $this->has($name)) { + return false; + } + + // Removes value from Store. + unset($this->store[$this->name($name)]); + + return true; + } + + /** + * Returns sanitized $name/key. + * + * @param string $name - Name to be sanitized. + * + * @throws NonEmptyStringException - If supplied name is a not a non empty string. + * @return string + */ + protected function sanitizeName(string $name): string + { + // Validates provided parameters. + if (\strlen(\trim($name)) === 0) { + throw NonEmptyStringException::withName('$name'); + } + + // Returns sanitized $name/key. + return \strtolower(\trim($name)); + } + + /** + * Returns full name, with namespace included if necessary. + * + * @param string $name - Sanitized name. + * + * @return string + */ + protected function name(string $name): string + { + // Returns sanitized $name/key. + if ($this->context === null) { + return $name; + } + + return ($this->context . '.' . $name); + } + + /** {@inheritDoc} */ + public function jsonSerialize(): array + { + return $this->getValues(); + } +} diff --git a/src/Collections/Linear/AbstractListArray.php b/src/Collections/Linear/AbstractListArray.php new file mode 100644 index 0000000..0041881 --- /dev/null +++ b/src/Collections/Linear/AbstractListArray.php @@ -0,0 +1,138 @@ +list); + } + + /** + * Returns the array List of values. + * + * @return array|Str[] + */ + public function toArray(): array + { + return $this->list; + } + + /** + * Clears the List of values. + * + * @return void + */ + public function clear(): void + { + $this->list = []; + } + + /** + * Returns TRUE if the List is empty. + * + * @return bool + */ + public function isEmpty(): bool + { + return ($this->count() === 0); + } + + /** + * Returns the total capacity allowed by the List. + * + * If the capacity is set to -1, it means the List has no capacity limit. + * + * @return int|NULL + */ + public function getCapacity(): ?int + { + return $this->capacity; + } + + /** + * If List has a Maximum Capacity set. + * + * @return boolean + */ + public function hasMaxCapacity(): bool + { + return ($this->capacity !== null); + } + + /** + * Allocates a capacity limit to the List. + * + * @param int|NULL $capacity - Capacity limit to set in the List. If NULL, no capacity will be set. + * + * @throws PositiveIntegerException - If provided capacity is not a positive integer. + * @throws ParameterOutOfRangeException - The the supplied capacity is less than the current List's value count. + * @return void + */ + public function allocateCapacity(?int $capacity): void + { + // Validates provided parameter. + if ($capacity !== null && $capacity < 1) { + throw PositiveIntegerException::withName('$capacity'); + } + if ($capacity !== null && $capacity < $this->count()) { + throw ParameterOutOfRangeException::withName('$capacity'); + } + + // Sets the capacity in the List. + $this->capacity = $capacity; + } + + /** @inheritDoc */ + public function jsonSerialize(): array + { + return $this->toArray(); + } +} diff --git a/src/Collections/Linear/EntityCollection.php b/src/Collections/Linear/EntityCollection.php new file mode 100644 index 0000000..796be33 --- /dev/null +++ b/src/Collections/Linear/EntityCollection.php @@ -0,0 +1,276 @@ +collection); + } + + /** + * Returns TRUE if an Entity with the supplied ID exists in the collection. + * + * @param int $id - Entity's ID to search for. + * + * @throws PositiveIntegerException - If the supplied ID is not a positive integer. + * @return bool + */ + public function has(int $id): bool + { + // Validates supplied ID. + if ($id <= 0) { + throw PositiveIntegerException::withName('$id'); + } + + // Returns the existence of the Entity in the Collection. + return \array_key_exists($id, $this->collection); + } + + /** + * Retrieves the Collection's Entity with the supplied ID. + * + * @param int $id - Entity's ID to search for. + * + * @throws PositiveIntegerException - If the supplied ID is not a positive integer. + * @throws ParameterOutOfRangeException - If the supplied ID was not present in the Collection. + * @return AbstractValueObject + */ + public function get(int $id): AbstractValueObject + { + // Validates supplied ID. + if ($id <= 0) { + throw PositiveIntegerException::withName('$id'); + } + if (!\array_key_exists($id, $this->collection)) { + throw ParameterOutOfRangeException::withName('$id'); + } + + // Returns the Entity. + return $this->collection[$id]; + } + + /** + * Counts the number of elements the Collection contains. + * + * @return int + */ + public function count(): int + { + return \count($this->collection); + } + + /** + * Clear the Entity Collection from the object. + * + * This method supports chaining. + * + * @return self + */ + public function clear(): self + { + // Clears Collection's array. + $this->collection = []; + $this->rewind(); + + // Returns instance. + return $this; + } + + /** + * Returns the list of Entity's IDs loaded in the Collection + * + * @return array + */ + public function ids(): array + { + return \array_keys($this->collection); + } + + /** + * Adds a new Value Object to the Collection. + * + * This method supports chaining. + * + * @param AbstractValueObject $valueObject - Entity to add to the collection. + * + * @throws DuplicatedEntryException - If you're trying to add a repeated Entity to the Collection. + * @return self + */ + public function add(AbstractValueObject $object): self + { + $id = $object->{'getId'}(); + + if (\array_key_exists($id, $this->collection)) { + throw DuplicatedEntryException::withId($id); + } + + $this->collection[$id] = $object; + + return $this; + } + + /** + * Removes an Entity, identified by the supplied ID, from the Collection. + * + * Returns TRUE on success, FALSE otherwise. + * + * @param int $id - Entity's ID to search for. + * + * @throws PositiveIntegerException - If the supplied ID is not a positive integer. + * @throws ParameterOutOfRangeException - If the Entity with the supplied ID doesn't exist in the Collection. + * @return bool + */ + public function remove(int $id): bool + { + // Validates supplied ID. + if ($id <= 0) { + throw PositiveIntegerException::withName('$id'); + } + if (!\array_key_exists($id, $this->collection)) { + throw ParameterOutOfRangeException::withName('$id'); + } + + // Removes element and rewinds + unset($this->collection[$id]); + $this->previous(); + + // Returns success. + return true; + } + + /** + * Rewinds the cursor in the Collection, and returns the first Element for the + * Collection. + * + * {@inheritDoc} + * @see \Iterator::rewind() + */ + #[\ReturnTypeWillChange] + public function rewind(): ?AbstractValueObject + { + // Rewinds the cursor and returns the first Element. + if (($element = \reset($this->collection)) !== false) { + return $element; + } + + return null; + } + + /** + * Retrieves the current Element for the Collection + * + * {@inheritDoc} + * @see \Iterator::current() + */ + public function current(): ?AbstractValueObject + { + // If retrieving the current element fails, we'll still try to rewind the + // pointer and return the first element. + if (($current = \current($this->collection)) === false) { + return $this->rewind(); + } + + return $current; + } + + /** + * Retrieves the current Collection's Element key. + * + * {@inheritDoc} + * @see \Iterator::key() + */ + public function key(): ?int + { + // First, we'll collect the key. + $key = \key($this->collection); + + if ($key === null || $key === false) { + return null; + } + + return (int) $key; + } + + /** + * Gets next cursor element in the Collection. + * + * {@inheritDoc} + * @see \Iterator::next() + */ + public function next(): void + { + \next($this->collection); + } + + /** + * Gets the previous cursor element in the Collection. + * + * Although this method is not actually part of the \Iterator interface, this was added + * as an add on for consistency with the next() method. + * + * @return void + */ + public function previous(): void + { + \prev($this->collection); + } + + /** + * Validates if the cursor in the Collection points to a valid element. + * + * {@inheritDoc} + * @see \Iterator::valid() + */ + public function valid(): bool + { + return ($this->key() !== null); + } + + /** + * Returns an array containing all its item's serialized data. + * + * Allows the Collection to be serialized directly by the json_encode() function. + * + * {@inheritDoc} + * @link http://www.php.net/manual/en/jsonserializable.jsonserialize.php + * @see \JsonSerializable::jsonSerialize() + */ + public function jsonSerialize(): array + { + $array = []; + + /** @var AbstractValueObject $valueObject - Type casts record */ + foreach ($this->collection as $valueObject) { + $array[] = $valueObject->jsonSerialize(); + } + + return $array; + } +} diff --git a/src/Collections/Linear/Queue.php b/src/Collections/Linear/Queue.php new file mode 100644 index 0000000..b43ca59 --- /dev/null +++ b/src/Collections/Linear/Queue.php @@ -0,0 +1,113 @@ +push($element); + } + } + + /** + * Returns a copy of the current Queue object. + * + * @return Queue + */ + public function clone(): Queue + { + return new Queue($this->toArray()); + } + + /** + * Returns the next element in the Queue, without removing it. + * + * If the Queue has no elements, it will return NULL. + * + * @return Str|NULL + */ + public function peek(): ?Str + { + // Validates the Queue has elements. + if ($this->count() === 0) { + return null; + } + + return $this->list[0]; + } + + /** + * Returns the next element in the Queue, while removing it. + * + * If the Queue has no elements, it will return NULL. + * + * @return Str|NULL + */ + public function pop(): ?Str + { + return \array_shift($this->list); + } + + /** + * Adds a new element to the Queue. + * + * @param string $element - Element to be added to the Queue. + * + * @throws NonEmptyStringException - If supplied element is not a non empty string. + * @throws ParameterOutOfRangeException - If adding element will exceed list's capacity. + * @return void + */ + public function push(string $element): void + { + if (\strlen(\trim($element)) === 0) { + throw NonEmptyStringException::withName('$element'); + } + + if ($this->hasMaxCapacity() && $this->getCapacity() === $this->count()) { + throw new ParameterOutOfRangeException(); + } + + $this->list[] = Str::create($element); + } +} diff --git a/src/Collections/Linear/README.md b/src/Collections/Linear/README.md new file mode 100644 index 0000000..bc9ee0e --- /dev/null +++ b/src/Collections/Linear/README.md @@ -0,0 +1,31 @@ +# Linear Collections + +## Lists + +In a **List**, the order of data items is significant. Duplicate data items are permitted. Examples of +operations on lists are searching for a data item in the list and determining its location (if it is present), +removing a data item from the list, adding a data item to the list at a specific location, etc. If the +principal operations on the list are to be the addition of data items at one end and the removal of data items +at the other, it will generally be called a queue or **FIFO**. If the principal operations are the addition and +removal of data items at just one end, it will be called a stack or **LIFO**. In both cases, data items are maintained +within the collection in the same order (unless they are removed and re-inserted somewhere else) and so these +are special cases of the list Collection. Other specialized operations on lists include sorting, where, again, +the order of data items is of great importance. + +## Stacks + +A **Stack** is a **LIFO** data structure with two principal operations: +- **push**, which adds an element to the "top" of the Collection. +- **pop**, which removes the top element. + +## Queues + +A **Queue** is a **LIFO** data structure with two principal operations: +- **push**, which adds an element to the "top" of the Collection. +- **pop**, which removes the first element in the Collection. + +## Priority queues + +In a **Priority Queue**, the tracks of the minimum or maximum data item in the collection are kept, according to +some ordering criterion, and the order of the other data items does not matter. One may think of a **Priority Queue** +as a list that always keeps the minimum or maximum at the head, while the remaining elements are kept in a _bag_. diff --git a/src/Collections/Linear/Stack.php b/src/Collections/Linear/Stack.php new file mode 100644 index 0000000..13b8c21 --- /dev/null +++ b/src/Collections/Linear/Stack.php @@ -0,0 +1,115 @@ +push($element); + } + } + + /** + * Returns a cloned copy of the current Stack object. + * + * @return Stack + */ + public function clone(): Stack + { + return new Stack( + $this->toArray() + ); + } + + /** + * Returns the next element in the Stack, without removing it. + * + * If the Stack has no elements, it will return NULL. + * + * @return Str|NULL + */ + public function peek(): ?Str + { + // Validates the Stack has elements. + if ($this->count() === 0) { + return null; + } + + return $this->list[$this->count() - 1]; + } + + /** + * Returns the next element in the Stack, while removing it. + * + * If the Stack has no elements, it will return NULL. + * + * @return Str|NULL + */ + public function pop(): ?Str + { + return \array_pop($this->list); + } + + /** + * Adds a new element to the Stack. + * + * @param string $element - Element to be added to the Stack. + * + * @throws NonEmptyStringException - If supplied element is not a non empty string. + * @throws ParameterOutOfRangeException - If adding element will exceed list's capacity. + * @return void + */ + public function push(string $element): void + { + if (\strlen(\trim($element)) === 0) { + throw NonEmptyStringException::withName('$element'); + } + + if ($this->hasMaxCapacity() && $this->getCapacity() === $this->count()) { + throw new ParameterOutOfRangeException(); + } + + $this->list[] = Str::create($element); + } +} diff --git a/src/Collections/README.md b/src/Collections/README.md new file mode 100644 index 0000000..d9969fd --- /dev/null +++ b/src/Collections/README.md @@ -0,0 +1,73 @@ +# Collection (abstract data type) + +In computer science, a **Collection** or container is a grouping of some variable number of data items +(possibly zero) that have some shared significance to the problem being solved and need to be operated +upon together in some controlled fashion. Generally, the data items will be of the same type or, in +languages supporting inheritance, derived from some common ancestor type. A **Collection** is a concept +applicable to abstract data types, and does not prescribe a specific implementation as a concrete data +structure, though often there is a conventional choice. + +Examples of collections include lists, sets, multisets, trees and graphs. + +Fixed-size arrays (or tables) are usually not considered a collection because they hold a fixed number +of data items, although they commonly play a role in the implementation of collections. Variable-size +arrays are generally considered collections. + +More information about this ([here](https://en.wikipedia.org/wiki/Collection_(abstract_data_type))) + +## Linear Collections + +### Lists + +In a **List**, the order of data items is significant. Duplicate data items are permitted. Examples of +operations on lists are searching for a data item in the list and determining its location (if it is present), +removing a data item from the list, adding a data item to the list at a specific location, etc. If the +principal operations on the list are to be the addition of data items at one end and the removal of data items +at the other, it will generally be called a queue or **FIFO**. If the principal operations are the addition and +removal of data items at just one end, it will be called a stack or **LIFO**. In both cases, data items are maintained +within the collection in the same order (unless they are removed and re-inserted somewhere else) and so these +are special cases of the list Collection. Other specialized operations on lists include sorting, where, again, +the order of data items is of great importance. + +### Stacks + +A **Stack** is a **LIFO** data structure with two principal operations: +- **push**, which adds an element to the "top" of the Collection. +- **pop**, which removes the top element. + +### Queues + +A **Queue** is a **LIFO** data structure with two principal operations: +- **push**, which adds an element to the "top" of the Collection. +- **pop**, which removes the first element in the Collection. + +### Priority queues + +In a **Priority Queue**, the tracks of the minimum or maximum data item in the collection are kept, according to +some ordering criterion, and the order of the other data items does not matter. One may think of a **Priority Queue** +as a list that always keeps the minimum or maximum at the head, while the remaining elements are kept in a _bag_. + +## Associative Collections + +### Store / Set + +In a **Set**, the order of data items does not matter (or is undefined) but duplicate data items are not permitted. +Examples of operations on **Sets** are the addition and removal of data items and searching for a data item in the +**Set**. + +Some languages support **Sets** directly. In others, **Sets** can be implemented by a hash table with dummy values; +only the keys are used in representing the **Set**. + +### Multisets / Bag + +In a **Multiset** (or _**Bag**_), like in a **Set**, the order of data items does not matter, but in this case +duplicate data items are permitted. Examples of operations on **Multisets** are the addition and removal of data items +and determining how many duplicates of a particular data item are present in the **Multiset**. + +**Multisets** can be transformed into **Lists** by the action of sorting. + +### Associative arrays / Map / Dictionary / Lookup Table / Hash + +In an **Associative Array** (or **Map**, **Dictionary**, **Lookup table**), like in a **Dictionary**, a _lookup_ on +a key (like a word) provides a value (like a definition). The value might be a reference to a compound data structure. +A _Hash Table_ is usually an efficient implementation, and thus this data type is often known as a "_hash_". diff --git a/src/Datetime/DateInterval.php b/src/Datetime/DateInterval.php new file mode 100644 index 0000000..e473307 --- /dev/null +++ b/src/Datetime/DateInterval.php @@ -0,0 +1,188 @@ +invert + ); + } + + public static function fromDuration(Str $duration, bool $inverted = false): DateInterval + { + return new DateInterval((string) $duration, $inverted); + } + + public static function fromYears(int $years): DateInterval + { + return new DateInterval( + \sprintf("P%dY", \abs($years)), + ($years < 0) + ); + } + + public static function fromMonths(int $months): DateInterval + { + return new DateInterval( + \sprintf("P%dM", \abs($months)), + ($months < 0) + ); + } + + public static function fromDays(int $days): DateInterval + { + return new DateInterval( + \sprintf("P%dD", \abs($days)), + ($days < 0) + ); + } + + public static function fromHours(int $hours): DateInterval + { + return new DateInterval( + \sprintf("PT%dH", \abs($hours)), + ($hours < 0) + ); + } + + public static function fromMinutes(int $minutes): DateInterval + { + return new DateInterval( + \sprintf("PT%dM", \abs($minutes)), + ($minutes < 0) + ); + } + + public static function fromSeconds(int $seconds): DateInterval + { + return new DateInterval( + \sprintf("PT%dS", \abs($seconds)), + ($seconds < 0) + ); + } + + public function __construct(string $duration, bool $inverted = false) + { + parent::__construct($duration); + + $this->invert = (int) $inverted; + } + + public function __toString(): string + { + return (string) $this->toDatetimeString(); + } + + protected function invertValue(int $value, int $invert): int + { + return $invert ? (0 - $value) : $value; + } + + public function getYears(): int + { + return $this->invertValue($this->y, $this->invert); + } + + public function getMonths(): int + { + return $this->invertValue($this->m, $this->invert); + } + + public function getDays(): int + { + return $this->invertValue($this->d, $this->invert); + } + + public function getHours(): int + { + return $this->invertValue($this->h, $this->invert); + } + + public function getMinutes(): int + { + return $this->invertValue($this->i, $this->invert); + } + + public function getSeconds(): int + { + return $this->invertValue($this->s, $this->invert); + } + + public function getMicroSeconds(): float + { + return $this->invert ? (0 - $this->f) : $this->f; + } + + protected function toFormatInternal(string $format): Str + { + return Str::create( + $this->format($format) + ); + } + + /** + * Returns Str instance with value in format "Y-m-d H:i:s". + * + * @return Str + */ + public function toDatetimeString(): Str + { + return $this->toFormatInternal('%r%Yy %Mm %Dd %H:%I:%S'); + } + + public function toFormat(Str $format): Str + { + return $this->toFormatInternal((string) $format); + } + + protected static function toDurationInternal(ParentDateInterval $interval): string + { + return \sprintf( + 'P%dY%dM%dDT%dH%dM%dS', + $interval->y, + $interval->m, + $interval->d, + $interval->h, + $interval->i, + $interval->s + ); + } + + public function toDuration(): Str + { + return Str::create( + self::toDurationInternal($this) + ); + } + + public function isNegative(): bool + { + return (bool) $this->invert; + } +} diff --git a/src/Datetime/DateTimeInterface.php b/src/Datetime/DateTimeInterface.php new file mode 100644 index 0000000..90a08ea --- /dev/null +++ b/src/Datetime/DateTimeInterface.php @@ -0,0 +1,30 @@ +toDatetimeString(); + } + + /** @inheritDoc */ + public function jsonSerialize(): string + { + return (string) $this; + } + + protected function toFormatInternal(string $format): Str + { + return Str::create( + $this->format($format) + ); + } + + /** + * Returns Str instance with value in format "Y-m-d\TH:i:sP". + * + * @return Str + */ + public function toATOM(): Str + { + return $this->toFormatInternal(\Datetime::ATOM); + } + + /** + * Returns Str instance with value in format "l, d-M-Y H:i:s T". + * + * @return Str + */ + public function toCookie(): Str + { + return $this->toFormatInternal(\Datetime::COOKIE); + } + + /** + * Returns Str instance with value in format "Y-m-d\TH:i:sO". + * + * @return Str + */ + public function toISO8601(): Str + { + return $this->toFormatInternal(\Datetime::ISO8601); + } + + /** + * Returns Str instance with value in format "D, d M y H:i:s O". + * + * @return Str + */ + public function toRFC822(): Str + { + return $this->toFormatInternal(\Datetime::RFC822); + } + + /** + * Returns Str instance with value in format "l, d-M-y H:i:s T". + * + * @return Str + */ + public function toRFC850(): Str + { + return $this->toFormatInternal(\Datetime::RFC850); + } + + /** + * Returns Str instance with value in format "D, d M y H:i:s O". + * + * @return Str + */ + public function toRFC1036(): Str + { + return $this->toFormatInternal(\Datetime::RFC1036); + } + + /** + * Returns Str instance with value in format "D, d M Y H:i:s O". + * + * @return Str + */ + public function toRFC1123(): Str + { + return $this->toFormatInternal(\Datetime::RFC1123); + } + + /** + * Returns Str instance with value in format "D, d M Y H:i:s \G\M\T". + * + * @return Str + */ + public function toRFC7231(): Str + { + return $this->toFormatInternal(\Datetime::RFC7231); + } + + /** + * Returns Str instance with value in format "D, d M Y H:i:s O". + * + * @return Str + */ + public function toRFC2822(): Str + { + return $this->toFormatInternal(\Datetime::RFC2822); + } + + /** + * Returns Str instance with value in format "Y-m-d\TH:i:sP". + * + * @return Str + */ + public function toRFC3339(): Str + { + return $this->toFormatInternal(\Datetime::RFC3339); + } + + /** + * Returns Str instance with value in format "Y-m-d\TH:i:s.vP". + * + * @return Str + */ + public function toRFC3339Extended(): Str + { + return $this->toFormatInternal(\Datetime::RFC3339_EXTENDED); + } + + /** + * Returns Str instance with value in format "D, d M Y H:i:s O". + * + * @return Str + */ + public function toRSS(): Str + { + return $this->toFormatInternal(\Datetime::RSS); + } + + /** + * Returns Str instance with value in format "Y-m-d\TH:i:sP". + * + * @return Str + */ + public function toW3C(): Str + { + return $this->toFormatInternal(\Datetime::W3C); + } + + /** + * Returns Str instance with value in format "Y-m-d H:i:s". + * + * @return Str + */ + public function toDatetimeString(): Str + { + return $this->toFormatInternal('Y-m-d H:i:s'); + } + + /** + * Returns Str instance with value in format "H:i:s". + * + * @return Str + */ + public function toTimeString(): Str + { + return $this->toFormatInternal('H:i:s'); + } + + /** + * Returns Str instance with value in specified format. + * + * @param Str $format - Str instance containing desired Datetime format. + * @return Str + */ + public function toFormat(Str $format): Str + { + return $this->toFormatInternal((string) $format); + } + + /** + * Get Year part of the instance. + * + * @return int + */ + public function getYear(): int + { + return (int) ((string) $this->toFormatInternal('Y')); + } + + /** + * Get Month part of the instance. + * + * @return int + */ + public function getMonth(): int + { + return (int) ((string) $this->toFormatInternal('m')); + } + + /** + * Get Day part of the instance. + * + * @return int + */ + public function getDay(): int + { + return (int) ((string) $this->toFormatInternal('d')); + } + + /** + * Get Hour part of the instance. + * + * @return int + */ + public function getHour(): int + { + return (int) ((string) $this->toFormatInternal('H')); + } + + /** + * Get Minute part of the instance. + * + * @return int + */ + public function getMinute(): int + { + return (int) ((string) $this->toFormatInternal('i')); + } + + /** + * Get Second part of the instance. + * + * @return int + */ + public function getSecond(): int + { + return (int) ((string) $this->toFormatInternal('s')); + } + + /** + * Internal method for DateInterval addition and subtraction. + * + * @param string $duration - Duration string + * @param int $value + * @return self + */ + protected function addDateIntervalValue(string $duration, int $value): self + { + $isNegative = ($value < 0); + $value = (int) \abs($value); + + $dateInterval = new \DateInterval( + \sprintf($duration, $value) + ); + + if ($isNegative) { + $dateInterval->invert = 1; + } + + return new self( + $this->add($dateInterval)->format(self::START_FORMAT) + ); + } + + /** + * Add the supplied number of Seconds to the Datetime instance. + * + * Supports chaining. + * + * @param int $seconds - Number of Seconds to add to instance. Supports negative numbers for subtraction. + * @return self + */ + public function addSeconds(int $seconds): self + { + return $this->addDateIntervalValue("PT%dS", $seconds); + } + + /** + * Add the supplied number of Minutes to the Datetime instance. + * + * Supports chaining. + * + * @param int $minutes - Number of Minutes to add to instance. Supports negative numbers for subtraction. + * @return self + */ + public function addMinutes(int $minutes): self + { + return $this->addDateIntervalValue("PT%dM", $minutes); + } + + /** + * Add the supplied number of Hours to the Datetime instance. + * + * Supports chaining. + * + * @param int $hours - Number of Hours to add to instance. Supports negative numbers for subtraction. + * @return self + */ + public function addHours(int $hours): self + { + return $this->addDateIntervalValue("PT%dH", $hours); + } + + /** + * Add the supplied number of Days to the Datetime instance. + * + * Supports chaining. + * + * @param int $days - Number of Days to add to instance. Supports negative numbers for subtraction. + * @return self + */ + public function addDays(int $days): self + { + return $this->addDateIntervalValue("P%dD", $days); + } + + /** + * Add the supplied number of Months to the Datetime instance. + * + * Supports chaining. + * + * @param int $months - Number of Months to add to instance. Supports negative numbers for subtraction. + * @return self + */ + public function addMonths(int $months): self + { + return $this->addDateIntervalValue("P%dM", $months); + } + + /** + * Add the supplied number of Years to the Datetime instance. + * + * Supports chaining. + * + * @param int $years - Number of Years to add to instance. Supports negative numbers for subtraction. + * @return self + */ + public function addYears(int $years): self + { + return $this->addDateIntervalValue("P%dY", $years); + } +} diff --git a/src/Exceptions/AbstractBaseException.php b/src/Exceptions/AbstractBaseException.php new file mode 100755 index 0000000..5859ab8 --- /dev/null +++ b/src/Exceptions/AbstractBaseException.php @@ -0,0 +1,54 @@ +message; + } + $code = $this->code; + + // Calls parent construct() with default code. + parent::__construct($message, $code, $inner); + } +} diff --git a/src/Exceptions/Collections/DuplicatedEntryException.php b/src/Exceptions/Collections/DuplicatedEntryException.php new file mode 100644 index 0000000..e0b1aba --- /dev/null +++ b/src/Exceptions/Collections/DuplicatedEntryException.php @@ -0,0 +1,27 @@ +json(['error' => $e->getMessage()], $e->getCode()); +} +``` + +All Domain Exception's also extend `AbstractBaseException` class, and therefore, the application layer can easily identify a Domain raised exception from another one. + +```php +try { + // ... raised a domain exception +} catch (AbstractBaseException $e) { + // ... have some behavior. +} catch (\Throwable $e) { + // ... have some OTHER behavior. +} +``` + +By creating and using Domain Exceptions, the application not only pinpoints what type of error occured, but also reduces the number of exceptions raised, while abstracting from any 3rd party dependency. + +If in a given context, a User record is required but not found, an `UserNotFoundException` can be raised, independently of the persistence layer's type (_DB, REST API, ..._). +This Exception, will already have a predefined eror message and code, that can be used in the application layer, either for an API response, or just for logging. diff --git a/src/Exceptions/RequestedRangeNotSatisfiableException.php b/src/Exceptions/RequestedRangeNotSatisfiableException.php new file mode 100644 index 0000000..d421b86 --- /dev/null +++ b/src/Exceptions/RequestedRangeNotSatisfiableException.php @@ -0,0 +1,40 @@ +trim(); -$string3 = $string2->toLower(); - -if ($string1 === $string2 || $string1 === $string3 || $string2 === $string3) { - echo "This will never print, as all are different instances."; -} - -echo "{$string1} still has the value ' This Is A String '."; -echo "{$string2} has the value 'This Is A String'."; -echo "{$string3} has the value 'this is a string'."; -``` - -```php -// Example of a Mutable string. -$string1 = MutableString::fromString(" This Is A String "); -$string2 = $string1->trim(); -$string3 = $string2->toLower(); - -if ($string1 === $string2 && $string1 === $string3 && $string2 === $string3) { - echo "This will print, as all are the same instance."; -} - -echo "All variables have the same 'this is a string' value."; -``` - -### Method chaning - -You can also do the previous processing, by channing the methods, independently of what type of datatype it is. - -```php -// Example of chaning methods. -$string1 = ImmutableString::fromString(" This Is A String "); -$string2 = MutableString::fromString(" This Is A String "); - -// Both will print out 'this is a string'. -echo $string1->trim()->toLower(); -echo $string2->trim()->toLower(); -``` - -## Domain Driven Design - -These datatypes are great if your implementing **Domain Driven Design** methodology to your project, as you can return -`Immutable` datatypes as **Value Objects** in your _Entities_. +- [Attributes](Attributes/) +- [Collections](Collections/) +- [Datetime](Datetime/) +- [Exceptions](Exceptions/) +- [Scalar](Scalar/) +- [Traits](Traits/) +- [ValueObjects](ValueObjects/) +- [Web](Web/) diff --git a/src/Scalar/AbstractReadBoolean.php b/src/Scalar/AbstractReadBoolean.php deleted file mode 100644 index 65bdbdd..0000000 --- a/src/Scalar/AbstractReadBoolean.php +++ /dev/null @@ -1,71 +0,0 @@ - - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -abstract class AbstractReadBoolean -{ - /** - * @var boolean $value - Internal boolean value for the instance. - */ - protected $value = true; - - /** - * Initializes a new instance of a Boolean. - * - * @param bool $value - Initial boolean value. - * - * @since 1.0.0 - * @return void - */ - protected function __construct(bool $value) - { - $this->value = $value; - } - - /** - * Magic method that will print out the native string representation of the instance. - * - * @since 1.0.0 - * @return string - */ - public function __toString(): string - { - return ($this->value ? 'true' : 'false'); - } - - /** - * Compares two Boolean instances, and return TRUE if supplied instance is equal. - * - * @param AbstractReadBoolean $compare - Boolean instance to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function equals(AbstractReadBoolean $compare): bool - { - return $compare->equalsNative($this->value); - } - - /** - * Compares instance's value, with the supplied native value, and returns TRUE if equal. - * - * @param bool $value - Boolean native value to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function equalsNative(bool $value): bool - { - return ($value === $this->value); - } -} diff --git a/src/Scalar/AbstractReadFloat.php b/src/Scalar/AbstractReadFloat.php deleted file mode 100644 index cc31e18..0000000 --- a/src/Scalar/AbstractReadFloat.php +++ /dev/null @@ -1,173 +0,0 @@ - - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -abstract class AbstractReadFloat -{ - /** - * Returns the maximum float allowed in the system. - * - * @since 1.0.0 - * @return float - */ - public static function max(): float - { - return PHP_FLOAT_MAX; - } - - /** - * Returns the minimum float allowed in the system. - * - * @since 1.0.0 - * @return float - */ - public static function min(): float - { - return PHP_FLOAT_MIN; - } - - /** - * @var float $value - Internal float value for the instance. - */ - protected $value = 0.0; - - /** - * Initializes a new instance of a Float. - * - * @param float $value - Initial float value. - * - * @since 1.0.0 - * @return void - */ - protected function __construct(float $value) - { - $this->value = $value; - } - - /** - * Magic method that will print out the native string representation of the instance. - * - * @since 1.0.0 - * @return string - */ - public function __toString(): string - { - return $this->value; - } - - /** - * Returns the native value of the instance. - * - * @since 1.0.0 - * @return float - */ - public function value(): float - { - return $this->value; - } - - /** - * Compares two Float instances, and return TRUE if supplied instance is bigger. - * - * @param AbstractReadFloat $compare - Float instance to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function isBigger(AbstractReadFloat $compare): bool - { - return ( - $compare->isSmallerNative($this->value) || - $compare->equalsNative($this->value) - ); - } - - /** - * Compares instance's value, with the supplied native value, and returns TRUE if instance is bigger. - * - * @param float $value - Float native value to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function isBiggerNative(float $value): bool - { - return ($this->value > $value); - } - - /** - * Compares two Float instances, and return TRUE if supplied instance is smaller. - * - * @param AbstractReadFloat $compare - Float instance to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function isSmaller(AbstractReadFloat $compare): bool - { - return ( - $compare->isBiggerNative($this->value) || - $compare->equalsNative($this->value) - ); - } - - /** - * Compares instance's value, with the supplied native value, and returns TRUE if instance is smaller. - * - * @param float $value - Float native value to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function isSmallerNative(float $value): bool - { - return ($this->value < $value); - } - - /** - * Compares two Float instances, and return TRUE if supplied instance is equal. - * - * @param AbstractReadFloat $compare - Float instance to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function equals(AbstractReadFloat $compare): bool - { - return $compare->equalsNative($this->value); - } - - /** - * Compares instance's value, with the supplied native value, and returns TRUE if equal. - * - * @param float $value - Float native value to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function equalsNative(float $value): bool - { - return ($this->value === $value); - } - - /** - * Returns TRUE if instance contains a negative value. - * - * @since 1.0.0 - * @return bool - */ - public function isNegative(): bool - { - return ($this->value < 0.0); - } -} diff --git a/src/Scalar/AbstractReadInteger.php b/src/Scalar/AbstractReadInteger.php deleted file mode 100644 index c312fd5..0000000 --- a/src/Scalar/AbstractReadInteger.php +++ /dev/null @@ -1,167 +0,0 @@ - - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -abstract class AbstractReadInteger -{ - /** - * Returns the maximum integer allowed in the system. - * - * @since 1.0.0 - * @return int - */ - public static function max(): int - { - return PHP_INT_MAX; - } - - /** - * Returns the minimum integer allowed in the system. - * - * @since 1.0.0 - * @return int - */ - public static function min(): int - { - return PHP_INT_MIN; - } - - /** - * @var integer $value - Internal integer value for the instance. - */ - protected $value = 0; - - /** - * Initializes a new instance of an Integer. - * - * @param integer $value - Initial integer value. - * - * @since 1.0.0 - * @return void - */ - protected function __construct(int $value) - { - $this->value = $value; - } - - /** - * Magic method that will print out the native string representation of the instance. - * - * @since 1.0.0 - * @return string - */ - public function __toString(): string - { - return $this->value; - } - - /** - * Returns the native value of the instance. - * - * @since 1.0.0 - * @return int - */ - public function value(): int - { - return $this->value; - } - - /** - * Compares two Integer instances, and return TRUE if supplied instance is bigger. - * - * @param AbstractReadInteger $compare - Integer instance to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function isBigger(AbstractReadInteger $compare): bool - { - return $this->isBiggerNative($compare->value()); - } - - /** - * Compares instance's value, with the supplied native value, and returns TRUE if instance is bigger. - * - * @param int $value - Integer native value to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function isBiggerNative(int $value): bool - { - return ($this->value > $value); - } - - /** - * Compares two Integer instances, and return TRUE if supplied instance is smaller. - * - * @param AbstractReadInteger $compare - Integer instance to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function isSmaller(AbstractReadInteger $compare): bool - { - return $this->isSmallerNative($compare->value()); - } - - /** - * Compares instance's value, with the supplied native value, and returns TRUE if instance is smaller. - * - * @param int $value - Integer native value to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function isSmallerNative(int $value): bool - { - return ($this->value < $value); - } - - /** - * Returns TRUE if instance contains a negative value. - * - * @since 1.0.0 - * @return bool - */ - public function isNegative(): bool - { - return ($this->value < 0); - } - - /** - * Compares two Integer instances, and return TRUE if supplied instance is equal. - * - * @param AbstractReadInteger $compare - Integer instance to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function equals(AbstractReadInteger $compare): bool - { - return $this->equalsNative($compare->value()); - } - - /** - * Compares instance's value, with the supplied native value, and returns TRUE if equal. - * - * @param int $value - Integer native value to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function equalsNative(int $value): bool - { - return ($value === $this->value); - } -} diff --git a/src/Scalar/AbstractReadString.php b/src/Scalar/AbstractReadString.php deleted file mode 100644 index b3a21a2..0000000 --- a/src/Scalar/AbstractReadString.php +++ /dev/null @@ -1,260 +0,0 @@ -ReadOnlyString, MutableString datatype, - * as well as for ImmutableString. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -abstract class AbstractReadString -{ - /** - * @var string $value - Internal string value for the instance. - */ - protected $value = ''; - - /** - * Initializes a new instance of a String. - * - * @param string $value - Initial string value. - * - * @since 1.0.0 - * @return void - */ - protected function __construct(string $value) - { - $this->value = $value; - } - - /** - * Use this method to copy a child instance to a ReadonlyString instance, or just to clone a - * ReadonlyString. - * - * @since 1.0.0 - * @return ReadonlyString - */ - public function toReadonly(): ReadonlyString - { - return new ReadonlyString( - $this->value - ); - } - - /** - * Magic method that will print out the native string representation of the instance. - * - * @since 1.0.0 - * @return string - */ - public function __toString(): string - { - return $this->value; - } - - /** - * Compares the values of 2 separate instances. - * - * Returns TRUE if the 2 instance's values match. FALSE otherwise. - * - * @param ReadOnlyString $string - Another String instance to compare to. - * - * @since 1.0.0 - * @return bool - */ - public function equals(ReadOnlyString $string): bool - { - return ($this->__toString() === $string->__toString()); - } - - /** - * Returns the instance's value character length. - * - * @since 1.0.0 - * @return int - */ - public function length(): int - { - return \strlen($this->value); - } - - /** - * Searches and returns the index in the instance, of the $search string. - * - * If a $start is specified, search will start this number of characters counted from - * the beginning of the string. If $start is negative, the search will start this number - * of characters counted from the end of the string. - * - * If the $search is not found inthe instance's value, NULL is returned. - * - * @param string $search - String to search for in the instance. - * @param int $start - Search offset start. Defaults to NULL. - * - * @throws \InvalidArgumentException - If $search value is an empty string. - * @throws \OutOfRangeException - If the $start is either too small, or too long. - * - * @since 1.0.0 - * @return int|NULL - */ - public function indexOf(string $search, int $start = null): ?int - { - // Validates supplied parameters. - if (\strlen($search) === 0) { - throw new \InvalidArgumentException("Supplied search must be a non empty string."); - } - if ($start) { - $this->validateStartAndLength($start, null); - } - - // Collects the string position. - $index = \strpos($this->value, $search, $start); - - // If false is returned, no index was found, and therefore NULL is returned. - if ($index === false) { - return null; - } - - return $index; - } - - /** - * Checks if the instance contains the supplied $search value. - * - * Returns TRUE if found. FALSE otherwise. - * - * @param string $search - Non empty string to search for in the instance. - * - * @throws \InvalidArgumentException - If supplied $search is empty. - * - * @since 1.0.0 - * @return bool - */ - public function contains(string $search): bool - { - // Validates supplied parameter. - if (\strlen($search) === 0) { - throw new \InvalidArgumentException("Supplied search must be a non empty string."); - } - - return ($this->indexOf($search) !== null); - } - - /** - * Checks if the instance's value starts with the supplied string. - * - * @param string $search - Non empty string to search for in the instance. - * - * @throws \InvalidArgumentException - If supplied $search is empty. - * - * @since 1.0.0 - * @return bool - */ - public function startsWith(string $search): bool - { - // Validates supplied parameter. - if (\strlen($search) === 0) { - throw new \InvalidArgumentException("Supplied search must be a non empty string."); - } - - return ($this->indexOf($search) === 0); - } - - /** - * Checks if the instance's value ends with the supplied string. - * - * @param string $search - Non empty string to search for in the instance. - * - * @throws \InvalidArgumentException - If supplied $search is empty. - * - * @since 1.0.0 - * @return bool - */ - public function endsWith(string $search): bool - { - // Validates supplied parameter. - if (\strlen($search) === 0) { - throw new \InvalidArgumentException("Supplied search must be a non empty string."); - } - - return (\substr($this->value, (0 - \strlen($search))) === $search); - } - - /** - * Counts the number of substring occurrences in the instance's value. - * - * @param string $search - Non empty string to search for in the instance. - * @param int $start - The sub-string's offset/start. - * @param int|NULL $length - Length value. Can be NULL, in which case, it won't be validated. - * - * @throws \InvalidArgumentException - If supplied $search is empty. - * @throws \OutOfRangeException - If the $start and/or $length is either too small, or too long. - * - * @since 1.0.0 - * @return int - */ - public function count(string $search, int $start = 0, int $length = null): int - { - // Validates supplied $search parameter. - if (\strlen($search) === 0) { - throw new \InvalidArgumentException("Supplied search must be a non empty string."); - } - - // Validates supplied $start and $length. - $this->validateStartAndLength($start, $length); - - // Checks if $length was defined. - if ($length) { - return \substr_count($this->value, $search, $start, $length); - } - - return \substr_count($this->value, $search, $start); - } - - /** - * Validates a character $length, based on the instance value's length, and supplied $start. - * - * @param int $start - The sub-string's offset/start. - * @param int|NULL $length - Length value. Can be NULL, in which case, it won't be validated. - * - * @throws \OutOfRangeException - If the $start and/or $length is either too small, or too long. - * - * @since 1.0.0 - * @return void - */ - protected function validateStartAndLength(int $start, ?int $length): void - { - // Calculates the absolute values for validations. - $absStart = (int) \abs($start); - $absLength = (int) \abs($length); - - // Validates the starting value. - if ($absStart > $this->length()) { - throw new \OutOfRangeException( - "The Start's absolute value must be less than the total number of characters in the string." - ); - } - - // If supplied $length is NULL, no further validations are required. - if ($length === null) { - return; - } - - // Checks if the supplied $length, doesn't exceed the available number of characters. - if (($start >= 0 && ($this->length() - $start < $absLength)) || - ($start < 0 && $absLength > $absStart)) { - - throw new \OutOfRangeException( - "The Length's absolute value cannot be higher than the number of available characters." - ); - } - } -} diff --git a/src/Scalar/AbstractWriteFloat.php b/src/Scalar/AbstractWriteFloat.php deleted file mode 100644 index 92714fd..0000000 --- a/src/Scalar/AbstractWriteFloat.php +++ /dev/null @@ -1,68 +0,0 @@ - - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -abstract class AbstractWriteFloat extends AbstractReadFloat -{ - /** - * Adds an external value to the instance's internal value. - * - * @param float $value - Value to add to the instance. - * - * @since 1.0.0 - * @return float - */ - protected function doAdd(float $value): float - { - return ($this->value + $value); - } - - /** - * Subtracts an external value from the instance's internal value. - * - * @param float $value - Value to subtract from the instance. - * - * @since 1.0.0 - * @return float - */ - protected function doSubtract(float $value): float - { - return ($this->value - $value); - } - - /** - * Multiply an external value to the instance's internal value. - * - * @param float $value - Value to multiply to the instance. - * - * @since 1.0.0 - * @return float - */ - protected function doMultiply(float $value): float - { - return ($this->value * $value); - } - - /** - * Divides the instance's internal value by an external value. - * - * @param float $value - Value used to divide the instance. - * - * @since 1.0.0 - * @return float - */ - protected function doDivide(float $value): float - { - return ($this->value / $value); - } -} diff --git a/src/Scalar/AbstractWriteInteger.php b/src/Scalar/AbstractWriteInteger.php deleted file mode 100644 index 9d3fca6..0000000 --- a/src/Scalar/AbstractWriteInteger.php +++ /dev/null @@ -1,68 +0,0 @@ - - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -abstract class AbstractWriteInteger extends AbstractReadInteger -{ - /** - * Adds an external value to the instance's internal value. - * - * @param int $value - Value to add to the instance. - * - * @since 1.0.0 - * @return int - */ - protected function doAdd(int $value): int - { - return ($this->value + $value); - } - - /** - * Subtracts an external value from the instance's internal value. - * - * @param int $value - Value to subtract from the instance. - * - * @since 1.0.0 - * @return int - */ - protected function doSubtract(int $value): int - { - return ($this->value - $value); - } - - /** - * Multiply an external value to the instance's internal value. - * - * @param int $value - Value to multiply to the instance. - * - * @since 1.0.0 - * @return int - */ - protected function doMultiply(int $value): int - { - return ($this->value * $value); - } - - /** - * Divides the instance's internal value by an external value. - * - * @param int $value - Value used to divide the instance. - * - * @since 1.0.0 - * @return int - */ - protected function doDivide(int $value): int - { - return ((int) ($this->value / $value)); - } -} diff --git a/src/Scalar/AbstractWriteString.php b/src/Scalar/AbstractWriteString.php deleted file mode 100644 index 4110e3e..0000000 --- a/src/Scalar/AbstractWriteString.php +++ /dev/null @@ -1,358 +0,0 @@ -ImmutableString and - * MutableString objects, as both perform the same operations, but return different outcomes. - * - * This class will perform every writing operation, for internal use only. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -abstract class AbstractWriteString extends AbstractReadString -{ - /** - * Trims instance's value, and returns a new instance of the object. - * - * @since 1.0.0 - * @return string - */ - protected function doTrim(): string - { - return \trim($this->value); - } - - /** - * Left trims instance's value, and returns a new instance of the object. - * - * @since 1.0.0 - * @return string - */ - protected function doTrimLeft(): string - { - return \ltrim($this->value); - } - - /** - * Right trims instance's value, and returns a new instance of the object. - * - * @since 1.0.0 - * @return string - */ - protected function doTrimRight(): string - { - return \rtrim($this->value); - } - - /** - * Converts the instance's value to Upper Case, and returns a new instance of the object. - * - * @since 1.0.0 - * @return string - */ - protected function doToUpper(): string - { - return \mb_strtoupper($this->value); - } - - /** - * Converts the instance's value first character to Upper Case, and returns a new instance - * of the object. - * - * @since 1.0.0 - * @return string - */ - protected function doToUpperFirst(): string - { - return \ucfirst($this->value); - } - - /** - * Converts the instance's value first character of each word to Upper Case, and returns a new instance - * of the object. - * - * @param string $delimiters - The optional delimiters contains the word separator characters. - * - * @since 1.0.0 - * @return string - */ - protected function doToUpperWords(string $delimiters = " \t\r\n\f\v"): string - { - return \ucwords($this->value, $delimiters); - } - - /** - * Converts the instance's value to Lower Case, and returns a new instance of the object. - * - * @since 1.0.0 - * @return string - */ - protected function doToLower(): string - { - return \mb_strtolower($this->value); - } - - /** - * Converts the instance's value first character to Lower Case, and returns a new instance - * of the object - * - * @since 1.0.0 - * @return string - */ - protected function doToLowerFirst(): string - { - return \lcfirst($this->value); - } - - /** - * This method returns a new instance padded on the left to the specified padding length minus - * the length of the instance's value. - * - * Eg:. if the padding length is 12, and the instance's value is only 10 characters long, the value will - * only be padded by the value of 2. - * - * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise - * it is padded with characters from $padString up to the limit. - * - * @param int $length - Length of the padded value. - * @param string $padString - The pad_string may be truncated if the required number of padding characters - * can't be evenly divided by the $padString's length. - * - * @throws \InvalidArgumentException - If any of the parameters is invalid. - * - * @since 1.0.0 - * @return string - */ - protected function doPadLeft(int $length, string $padString = " "): string - { - // Validates supplied parameters. - if ($length < 1) { - throw new \InvalidArgumentException("Supplied Length must be a positive integer."); - } - if (\strlen($padString) === 0) { - throw new \InvalidArgumentException("Supplied padding must be a non empty string."); - } - - return \str_pad($this->value, $length, $padString, STR_PAD_LEFT); - } - - /** - * This method returns a new instance padded on the left, exactly to the specified padding length. - * - * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise - * it is padded with characters from $padString up to the limit. - * - * @param int $length - Length of the padded value. - * @param string $padString - The pad_string may be truncated if the required number of padding characters - * can't be evenly divided by the $padString's length. - * - * @throws \InvalidArgumentException - If any of the parameters is invalid. - * - * @since 1.0.0 - * @return string - */ - protected function doPadLeftExtra(int $length, string $padString = " "): string - { - // Validates supplied parameters. - if ($length < 1) { - throw new \InvalidArgumentException("Supplied Length must be a positive integer."); - } - if (\strlen($padString) === 0) { - throw new \InvalidArgumentException("Supplied padding must be a non empty string."); - } - - return \str_pad($this->value, ($this->length() + $length), $padString, STR_PAD_LEFT); - } - - /** - * This method returns a new instance padded on the right to the specified padding length minus - * the length of the instance's value. - * - * Eg:. if the padding length is 12, and the instance's value is only 10 characters long, the value will - * only be padded by the value of 2. - * - * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise - * it is padded with characters from $padString up to the limit. - * - * @param int $length - Length of the padded value. - * @param string $padString - The pad_string may be truncated if the required number of padding characters - * can't be evenly divided by the $padString's length. - * - * @throws \InvalidArgumentException - If any of the parameters is invalid. - * - * @since 1.0.0 - * @return string - */ - protected function doPadRight(int $length, string $padString = " "): string - { - // Validates supplied parameters. - if ($length < 1) { - throw new \InvalidArgumentException("Supplied Length must be a positive integer."); - } - if (\strlen($padString) === 0) { - throw new \InvalidArgumentException("Supplied padding must be a non empty string."); - } - - return \str_pad($this->value, $length, $padString, STR_PAD_RIGHT); - } - - /** - * This method returns a new instance padded on the right, exactly to the specified padding length. - * - * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise - * it is padded with characters from $padString up to the limit. - * - * @param int $length - Length of the padded value. - * @param string $padString - The pad_string may be truncated if the required number of padding characters - * can't be evenly divided by the $padString's length. - * - * @throws \InvalidArgumentException - If any of the parameters is invalid. - * - * @since 1.0.0 - * @return string - */ - protected function doPadRightExtra(int $length, string $padString = " "): string - { - // Validates supplied parameters. - if ($length < 1) { - throw new \InvalidArgumentException("Supplied Length must be a positive integer."); - } - if (\strlen($padString) === 0) { - throw new \InvalidArgumentException("Supplied padding must be a non empty string."); - } - - return \str_pad($this->value, ($this->length() + $length), $padString, STR_PAD_RIGHT); - } - - /** - * This method returns a new instance with a portion of the original instance's value, specified by the - * $start and $length parameters. - * - * $start parameter: - *
    - *
  • If $start is non-negative, the returned an instance will start at the $start'th position in - * string, counting from zero. For instance, in the string 'abcdef', the character at position 0 is 'a', the - * character at position 2 is 'c', and so forth.
  • - *
  • If $start is negative, the returned string will start at the $start'th character from the end - * of string.
  • - *
  • If the absolute value of $start is higher than the instance's length, an - * exception is thrown.
  • - *
- * - * $length parameter: - *
    - *
  • If $length is given and is positive, the string returned will contain at most length characters - * beginning from $start (depending on the length of string).
  • - *
  • If $length is given and is negative, then that many characters will be omitted from the end of string - * (after the start position has been calculated when a start is negative).
  • - *
  • If $length exceeds the remaining number of characters, after the $start calculation, an - * Exception will be raised.
  • - *
- * - * @param int $start - Start of the sub-string. Can be negative. - * @param int $length - Length of the sub-string. Can be negative. - * - * @throws \OutOfRangeException - If the $start and/or $length is either too small, or too long. - * - * @since 1.0.0 - * @return string - */ - protected function doSubString(int $start, int $length = null): string - { - // Validates supplied $start and $length. - $this->validateStartAndLength($start, $length); - - // Processes the substring. - if ($length !== null) { - $value = \substr($this->value, $start, $length); - } else { - $value = \substr($this->value, $start); - } - - return ($value ?? ''); - } - - /** - * This method returns a new instance with a portion of the original instance's value, starting at the beginning - * of the value, with the number of characters specified in the $length parameter. - * - * Same rules as ImmutableString::subString() are applied. - * - * @param int $length - Length of the sub-string. Must be positive. - * - * @throws \InvalidArgumentException - If supplied Length is not a positive integer. - * - * @since 1.0.0 - * @return string - */ - protected function doSubLeft(int $length): string - { - // Validates parameter. - if ($length < 1) { - throw new \InvalidArgumentException("Supplied length must be a positive integer."); - } - - return $this->doSubString(0, $length); - } - - /** - * This method returns a new instance with a portion of the original instance's value, couting from the end - * of the value, with the number of characters specified in the $length parameter. - * - * Same rules as ImmutableString::subString() are applied. - * - * @param int $length - Length of the sub-string. Must be positive. - * - * @throws \InvalidArgumentException - If supplied Length is not a positive integer. - * - * @since 1.0.0 - * @return string - */ - protected function doSubRight(int $length): string - { - // Validates parameter. - if ($length < 1) { - throw new \InvalidArgumentException("Supplied length must be a positive integer."); - } - - return $this->doSubString(0 - $length); - } - - /** - * This method returns a new instance with the reversed value of the original instance. - * - * @since 1.0.0 - * @return string - */ - protected function doReverse(): string - { - return \strrev($this->value); - } - - /** - * This method replaces a string's occurance by another, and returns a new instance with the new value. - * - * @param string $search - The string to search for. - * @param string $replace - The search's replacement. - * - * @throws \InvalidArgumentException - If $search is empty, or count is a not a positive integer. - * - * @since 1.0.0 - * @return string - */ - protected function doReplace(string $search, string $replace): string - { - // Validates supplied parameters. - if (\strlen($search) === 0) { - throw new \InvalidArgumentException("Supplied search must be a non empty string."); - } - - return \str_replace($search, $replace, $this->value); - } -} diff --git a/src/Scalar/ImmutableFloat.php b/src/Scalar/ImmutableFloat.php deleted file mode 100644 index 440a16a..0000000 --- a/src/Scalar/ImmutableFloat.php +++ /dev/null @@ -1,164 +0,0 @@ -Value Object, in a DDD project. - * - * Method chaning is not supported by any mutators. A new instance will be returned instead. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ImmutableFloat extends AbstractWriteFloat -{ - /** - * Creates a new instance of ImmutableFloat based on a string value. - * - * @param string $number - Number as a string value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return ImmutableFloat - */ - public static function fromString(string $number): ImmutableFloat - { - // Valiates supplied parameter. - if (\strlen(\trim($number)) === 0) { - throw new \InvalidArgumentException("Supplied string must be a non empty string."); - } - - return new ImmutableFloat( - \floatval($number) - ); - } - - /** - * Creates a new instance of ImmutableFloat based on a float value. - * - * @param float $number - Number as a float value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return ImmutableFloat - */ - public static function fromFloat(float $number): ImmutableFloat - { - return new ImmutableFloat($number); - } - - /** - * Returns instance to a Mutable Float instance. - * - * @since 1.0.0 - * @return MutableFloat - */ - public function toMutable(): MutableFloat - { - return MutableFloat::fromFloat($this->value); - } - - /** - * Adds an external value to the instance's internal value. - * - * @param AbstractReadFloat $value - Value to add to the instance. - * - * @since 1.0.0 - * @return ImmutableFloat - */ - public function add(AbstractReadFloat $value): ImmutableFloat - { - return new ImmutableFloat( - parent::doAdd($value->value()) - ); - } - - /** - * Subtracts an external value from the instance's internal value. - * - * @param AbstractReadFloat $value - Value to subtract from the instance. - * - * @since 1.0.0 - * @return ImmutableFloat - */ - public function subtract(AbstractReadFloat $value): ImmutableFloat - { - return new ImmutableFloat( - parent::doSubtract($value->value()) - ); - } - - /** - * Multiply an external value to the instance's internal value. - * - * @param AbstractReadFloat $value - Value to multiply with the instance. - * - * @since 1.0.0 - * @return ImmutableFloat - */ - public function multiply(AbstractReadFloat $value): ImmutableFloat - { - return new ImmutableFloat( - parent::doMultiply($value->value()) - ); - } - - /** - * Divides the instance's internal value by an external value. - * - * @param AbstractReadFloat $value - Value to divide the instance with. - * - * @since 1.0.0 - * @return ImmutableFloat - */ - public function divide(AbstractReadFloat $value): ImmutableFloat - { - return new ImmutableFloat( - parent::doDivide($value->value()) - ); - } - - /** - * Formats the Float as a formatted string. - * - * @param \NumberFormatter $formatter - Format that should be used in the returned string. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function format(\NumberFormatter $formatter): ImmutableString - { - return ImmutableString::fromString( - $formatter->format($this->value, \NumberFormatter::TYPE_DOUBLE) - ); - } - - /** - * Converts float's instance to an equivalent string instance. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function toString(): ImmutableString - { - return ImmutableString::fromString($this->__toString()); - } - - /** - * Converts float's instance to an equivalent integer instance. - * - * @since 1.0.0 - * @return ImmutableInteger - */ - public function toInteger(): ImmutableInteger - { - return ImmutableInteger::fromString($this->__toString()); - } -} diff --git a/src/Scalar/ImmutableInteger.php b/src/Scalar/ImmutableInteger.php deleted file mode 100644 index a871cc2..0000000 --- a/src/Scalar/ImmutableInteger.php +++ /dev/null @@ -1,164 +0,0 @@ -Value Object, in a DDD project. - * - * Method chaning is not supported by any mutators. A new instance will be returned instead. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ImmutableInteger extends AbstractWriteInteger -{ - /** - * Creates a new instance of ImmutableInteger based on a string value. - * - * @param string $number - Number as a string value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return ImmutableInteger - */ - public static function fromString(string $number): ImmutableInteger - { - // Valiates supplied parameter. - if (\strlen(\trim($number)) === 0) { - throw new \InvalidArgumentException("Supplied string must be a non empty string."); - } - - return new ImmutableInteger( - ((int) $number) - ); - } - - /** - * Creates a new instance of ImmutableInteger based on an integer value. - * - * @param int $number - Number as an integer value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return ImmutableInteger - */ - public static function fromInteger(int $number): ImmutableInteger - { - return new ImmutableInteger($number); - } - - /** - * Converts instance to a Mutable Integer instance. - * - * @since 1.0.0 - * @return MutableInteger - */ - public function toMutable(): MutableInteger - { - return new MutableInteger($this->value); - } - - /** - * Adds an external value to the instance's internal value. - * - * @param AbstractReadInteger $value - Value to add to the instance. - * - * @since 1.0.0 - * @return ImmutableInteger - */ - public function add(AbstractReadInteger $value): ImmutableInteger - { - return new ImmutableInteger( - parent::doAdd($value->value()) - ); - } - - /** - * Subtracts an external value from the instance's internal value. - * - * @param AbstractReadInteger $value - Value to subtract from the instance. - * - * @since 1.0.0 - * @return ImmutableInteger - */ - public function subtract(AbstractReadInteger $value): ImmutableInteger - { - return new ImmutableInteger( - parent::doSubtract($value->value()) - ); - } - - /** - * Multiply an external value to the instance's internal value. - * - * @param AbstractReadInteger $value - Value to multiply with the instance. - * - * @since 1.0.0 - * @return ImmutableInteger - */ - public function multiply(AbstractReadInteger $value): ImmutableInteger - { - return new ImmutableInteger( - parent::doMultiply($value->value()) - ); - } - - /** - * Divides the instance's internal value by an external value. - * - * @param AbstractReadInteger $value - Value to divide with the instance. - * - * @since 1.0.0 - * @return ImmutableInteger - */ - public function divide(AbstractReadInteger $value): ImmutableInteger - { - return new ImmutableInteger( - parent::doDivide($value->value()) - ); - } - - /** - * Formats the Integer as a formatted string. - * - * @param \NumberFormatter $formatter - Format that should be used in the returned string. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function format(\NumberFormatter $formatter): ImmutableString - { - return ImmutableString::fromString( - $formatter->format($this->value, \NumberFormatter::TYPE_INT64) - ); - } - - /** - * Converts integer's instance to an equivalent string instance. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function toString(): ImmutableString - { - return ImmutableString::fromString($this->__toString()); - } - - /** - * Converts integer's instance to an equivalent float instance. - * - * @since 1.0.0 - * @return ImmutableFloat - */ - public function toFloat(): ImmutableFloat - { - return ImmutableFloat::fromString($this->__toString()); - } -} diff --git a/src/Scalar/ImmutableString.php b/src/Scalar/ImmutableString.php deleted file mode 100644 index 145f956..0000000 --- a/src/Scalar/ImmutableString.php +++ /dev/null @@ -1,357 +0,0 @@ -Value Object, in a DDD project. - * - * Method chaning is not supported by any mutators. A new instance will be returned instead. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ImmutableString extends AbstractWriteString -{ - /** - * Creates a new instance of ImmutableString based on a string value. - * - * @param string $value - Instance's initial value. - * - * @since 1.0.0 - * @return ImmutableString - */ - public static function fromString(string $value): ImmutableString - { - return new ImmutableString($value); - } - - /** - * Clones this ImmutableString instance, into a MutableString one. - * - * @since 1.0.0 - * @return MutableString - */ - public function toMutable(): MutableString - { - return MutableString::fromString( - $this->value - ); - } - - /** - * Trims instance's value, and returns a new instance of the object. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function trim(): ImmutableString - { - return new ImmutableString( - parent::doTrim() - ); - } - - /** - * Left trims instance's value, and returns a new instance of the object. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function trimLeft(): ImmutableString - { - return new ImmutableString( - parent::doTrimLeft() - ); - } - - /** - * Right trims instance's value, and returns a new instance of the object. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function trimRight(): ImmutableString - { - return new ImmutableString( - parent::doTrimRight() - ); - } - - /** - * Converts the instance's value to Upper Case, and returns a new instance of the object. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function toUpper(): ImmutableString - { - return new ImmutableString( - parent::doToUpper() - ); - } - - /** - * Converts the instance's value first character to Upper Case, and returns a new instance - * of the object. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function toUpperFirst(): ImmutableString - { - return new ImmutableString( - parent::doToUpperFirst() - ); - } - - /** - * Converts the instance's value first character of each word to Upper Case, and returns a new instance - * of the object. - * - * @param string $delimiters - The optional delimiters contains the word separator characters. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function toUpperWords(string $delimiters = " \t\r\n\f\v"): ImmutableString - { - return new ImmutableString( - parent::doToUpperWords($delimiters) - ); - } - - /** - * Converts the instance's value to Lower Case, and returns a new instance of the object. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function toLower(): ImmutableString - { - return new ImmutableString( - parent::doToLower() - ); - } - - /** - * Converts the instance's value first character to Lower Case, and returns a new instance - * of the object - * - * @since 1.0.0 - * @return ImmutableString - */ - public function toLowerFirst(): ImmutableString - { - return new ImmutableString( - parent::doToLowerFirst() - ); - } - - /** - * This method returns a new instance padded on the left to the specified padding length minus - * the length of the instance's value. - * - * Eg:. if the padding length is 12, and the instance's value is only 10 characters long, the value will - * only be padded by the value of 2. - * - * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise - * it is padded with characters from $padString up to the limit. - * - * @throws \InvalidArgumentException - If any of the parameters is invalid. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function padLeft(int $length, string $padString = " "): ImmutableString - { - return new ImmutableString( - parent::doPadLeft($length, $padString) - ); - } - - /** - * This method returns a new instance padded on the left, exactly to the specified padding length. - * - * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise - * it is padded with characters from $padString up to the limit. - * - * @param int $length - Length of the padded value. - * @param string $padString - The pad_string may be truncated if the required number of padding characters - * can't be evenly divided by the $padString's length. - * - * @throws \InvalidArgumentException - If any of the parameters is invalid. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function padLeftExtra(int $length, string $padString = " "): ImmutableString - { - return new ImmutableString( - parent::doPadLeftExtra($length, $padString) - ); - } - - /** - * This method returns a new instance padded on the right to the specified padding length minus - * the length of the instance's value. - * - * Eg:. if the padding length is 12, and the instance's value is only 10 characters long, the value will - * only be padded by the value of 2. - * - * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise - * it is padded with characters from $padString up to the limit. - * - * @param int $length - Length of the padded value. - * @param string $padString - The pad_string may be truncated if the required number of padding characters - * can't be evenly divided by the $padString's length. - * - * @throws \InvalidArgumentException - If any of the parameters is invalid. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function padRight(int $length, string $padString = " "): ImmutableString - { - return new ImmutableString( - parent::doPadRight($length, $padString) - ); - } - - /** - * This method returns a new instance padded on the right, exactly to the specified padding length. - * - * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise - * it is padded with characters from $padString up to the limit. - * - * @param int $length - Length of the padded value. - * @param string $padString - The pad_string may be truncated if the required number of padding characters - * can't be evenly divided by the $padString's length. - * - * @throws \InvalidArgumentException - If any of the parameters is invalid. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function padRightExtra(int $length, string $padString = " "): ImmutableString - { - return new ImmutableString( - parent::doPadRightExtra($length, $padString) - ); - } - - /** - * This method returns a new instance with a portion of the original instance's value, specified by the - * $start and $length parameters. - * - * $start parameter: - *
    - *
  • If $start is non-negative, the returned an instance will start at the $start'th position in - * string, counting from zero. For instance, in the string 'abcdef', the character at position 0 is 'a', the - * character at position 2 is 'c', and so forth.
  • - *
  • If $start is negative, the returned string will start at the $start'th character from the end - * of string.
  • - *
  • If the absolute value of $start is higher than the instance's length, an - * exception is thrown.
  • - *
- * - * $length parameter: - *
    - *
  • If $length is given and is positive, the string returned will contain at most length characters - * beginning from $start (depending on the length of string).
  • - *
  • If $length is given and is negative, then that many characters will be omitted from the end of string - * (after the start position has been calculated when a start is negative).
  • - *
  • If $length exceeds the remaining number of characters, after the $start calculation, an - * Exception will be raised.
  • - *
- * - * @param int $start - Start of the sub-string. Can be negative. - * @param int $length - Length of the sub-string. Can be negative. - * - * @throws \OutOfRangeException - If the $start and/or $length is either too small, or too long. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function subString(int $start, int $length = null): ImmutableString - { - return new ImmutableString( - parent::doSubString($start, $length) - ); - } - - /** - * This method returns a new instance with a portion of the original instance's value, starting at the beginning - * of the value, with the number of characters specified in the $length parameter. - * - * Same rules as ImmutableString::subString() are applied. - * - * @param int $length - Length of the sub-string. Must be positive. - * - * @throws \InvalidArgumentException - If supplied Length is not a positive integer. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function subLeft(int $length): ImmutableString - { - return new ImmutableString( - parent::doSubLeft($length) - ); - } - - /** - * This method returns a new instance with a portion of the original instance's value, couting from the end - * of the value, with the number of characters specified in the $length parameter. - * - * Same rules as ImmutableString::subString() are applied. - * - * @param int $length - Length of the sub-string. Must be positive. - * - * @throws \InvalidArgumentException - If supplied Length is not a positive integer. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function subRight(int $length): ImmutableString - { - return new ImmutableString( - parent::doSubRight($length) - ); - } - - /** - * This method returns a new instance with the reversed value of the original instance. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function reverse(): ImmutableString - { - return new ImmutableString( - parent::doReverse() - ); - } - - /** - * This method replaces a string's occurance by another, and returns a new instance with the new value. - * - * @param string $search - The string to search for. - * @param string $replace - The search's replacement. - * - * @throws \InvalidArgumentException - If $search is empty, or count is a not a positive integer. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function replace(string $search, string $replace): ImmutableString - { - return new ImmutableString( - parent::doReplace($search, $replace) - ); - } -} diff --git a/src/Scalar/MutableFloat.php b/src/Scalar/MutableFloat.php deleted file mode 100644 index 3f2b258..0000000 --- a/src/Scalar/MutableFloat.php +++ /dev/null @@ -1,166 +0,0 @@ - - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class MutableFloat extends AbstractWriteFloat -{ - /** - * Creates a new instance of MutableFloat based on a string value. - * - * @param string $number - Number as a string value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return MutableFloat - */ - public static function fromString(string $number): MutableFloat - { - // Valiates supplied parameter. - if (\strlen(\trim($number)) === 0) { - throw new \InvalidArgumentException("Supplied string must be a non empty string."); - } - - return new MutableFloat( - \floatval($number) - ); - } - - /** - * Creates a new instance of MutableFloat based on a float value. - * - * @param float $number - Number as a float value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return MutableInteger - */ - public static function fromFloat(float $number): MutableFloat - { - return new MutableFloat($number); - } - - /** - * Returns instance to an Immutable Float instance. - * - * @since 1.0.0 - * @return ImmutableFloat - */ - public function toImmutable(): ImmutableFloat - { - return ImmutableFloat::fromFloat($this->value); - } - - /** - * Adds an external value to the instance's internal value. - * - * @param AbstractReadFloat $value - Value to add to the instance. - * - * @since 1.0.0 - * @return MutableFloat - */ - public function add(AbstractReadFloat $value): MutableFloat - { - $this->value = parent::doAdd($value->value()); - - return $this; - } - - /** - * Subtracts an external value from the instance's internal value. - * - * @param AbstractReadFloat $value - Value to add to the instance. - * - * @since 1.0.0 - * @return MutableFloat - */ - public function subtract(AbstractReadFloat $value): MutableFloat - { - $this->value = parent::doSubtract($value->value()); - - return $this; - } - - /** - * Multiply an external value to the instance's internal value. - * - * @param AbstractReadFloat $value - Value to add to the instance. - * - * @since 1.0.0 - * @return MutableFloat - */ - public function multiply(AbstractReadFloat $value): MutableFloat - { - $this->value = parent::doMultiply($value->value()); - - return $this; - } - - /** - * Divides the instance's internal value by an external value. - * - * @param AbstractReadFloat $value - Value to add to the instance. - * - * @since 1.0.0 - * @return MutableFloat - */ - public function divide(AbstractReadFloat $value): MutableFloat - { - $this->value = parent::doDivide($value->value()); - - return $this; - } - - /** - * Formats the Float as a formatted string. - * - * @param \NumberFormatter $formatter - Format that should be used in the returned string. - * - * @since 1.0.0 - * @return MutableString - */ - public function format(\NumberFormatter $formatter): MutableString - { - return MutableString::fromString( - $formatter->format($this->value, \NumberFormatter::TYPE_DOUBLE) - ); - } - - /** - * Converts Float's instance to an equivalent string instance. - * - * @since 1.0.0 - * @return MutableString - */ - public function toString(): MutableString - { - return MutableString::fromString($this->__toString()); - } - - /** - * Converts Float's instance to an equivalent integer instance. - * - * @since 1.0.0 - * @return MutableInteger - */ - public function toInteger(): MutableInteger - { - return MutableInteger::fromString($this->__toString()); - } -} diff --git a/src/Scalar/MutableInteger.php b/src/Scalar/MutableInteger.php deleted file mode 100644 index fdce060..0000000 --- a/src/Scalar/MutableInteger.php +++ /dev/null @@ -1,166 +0,0 @@ - - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class MutableInteger extends AbstractWriteInteger -{ - /** - * Creates a new instance of MutableInteger based on a string value. - * - * @param string $number - Number as a string value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return MutableInteger - */ - public static function fromString(string $number): MutableInteger - { - // Valiates supplied parameter. - if (\strlen(\trim($number)) === 0) { - throw new \InvalidArgumentException("Supplied string must be a non empty string."); - } - - return new MutableInteger( - ((int) $number) - ); - } - - /** - * Creates a new instance of MutableInteger based on an integer value. - * - * @param int $number - Number as an integer value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return MutableInteger - */ - public static function fromInteger(int $number): MutableInteger - { - return new MutableInteger($number); - } - - /** - * Adds an external value to the instance's internal value. - * - * @param AbstractReadInteger $value - Value to add to the instance. - * - * @since 1.0.0 - * @return MutableInteger - */ - public function add(AbstractReadInteger $value): MutableInteger - { - $this->value = parent::doAdd($value->value()); - - return $this; - } - - /** - * Subtracts an external value from the instance's internal value. - * - * @param AbstractReadInteger $value - Value to add to the instance. - * - * @since 1.0.0 - * @return MutableInteger - */ - public function subtract(AbstractReadInteger $value): MutableInteger - { - $this->value = parent::doSubtract($value->value()); - - return $this; - } - - /** - * Multiply an external value to the instance's internal value. - * - * @param AbstractReadInteger $value - Value to add to the instance. - * - * @since 1.0.0 - * @return MutableInteger - */ - public function multiply(AbstractReadInteger $value): MutableInteger - { - $this->value = parent::doMultiply($value->value()); - - return $this; - } - - /** - * Divides the instance's internal value by an external value. - * - * @param AbstractReadInteger $value - Value to add to the instance. - * - * @since 1.0.0 - * @return MutableInteger - */ - public function divide(AbstractReadInteger $value): MutableInteger - { - $this->value = parent::doDivide($value->value()); - - return $this; - } - - /** - * Formats the Integer as a formatted string. - * - * @param \NumberFormatter $formatter - Format that should be used in the returned string. - * - * @since 1.0.0 - * @return MutableString - */ - public function format(\NumberFormatter $formatter): MutableString - { - return MutableString::fromString( - $formatter->format($this->value, \NumberFormatter::TYPE_INT64) - ); - } - - /** - * Converts integer's instance to an equivalent string instance. - * - * @since 1.0.0 - * @return MutableString - */ - public function toString(): MutableString - { - return MutableString::fromString($this->__toString()); - } - - /** - * Converts the integer's instance to an Immutable Integer. - * - * @since 1.0.0 - * @return ImmutableInteger - */ - public function toImmutable(): ImmutableInteger - { - return ImmutableInteger::fromInteger($this->value); - } - - /** - * Converts integer's instance to an equivalent float instance. - * - * @since 1.0.0 - * @return MutableFloat - */ - public function toFloat(): MutableFloat - { - return MutableFloat::fromString($this->__toString()); - } -} diff --git a/src/Scalar/MutableString.php b/src/Scalar/MutableString.php deleted file mode 100644 index 225e88c..0000000 --- a/src/Scalar/MutableString.php +++ /dev/null @@ -1,376 +0,0 @@ - - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class MutableString extends AbstractWriteString -{ - /** - * Creates a new instance of MutableString based on a string value. - * - * @param string $value - Instance's initial value. - * - * @since 1.0.0 - * @return MutableString - */ - public static function fromString(string $value): MutableString - { - return new MutableString($value); - } - - /** - * Clones this MutableString instance, into a ImmutableString one. - * - * @since 1.0.0 - * @return ImmutableString - */ - public function toImmutable(): ImmutableString - { - return ImmutableString::fromString( - $this->value - ); - } - - /** - * Trims the instance's value. - * - * This method supports chaning. - * - * @since 1.0.0 - * @return MutableString - */ - public function trim(): MutableString - { - $this->value = parent::doTrim(); - - return $this; - } - - /** - * Left trims the instance's value. - * - * This method supports chaning. - * - * @since 1.0.0 - * @return MutableString - */ - public function trimLeft(): MutableString - { - $this->value = parent::doTrimLeft(); - - return $this; - } - - /** - * Right trims the instance's value. - * - * This method supports chaning. - * - * @since 1.0.0 - * @return MutableString - */ - public function trimRight(): MutableString - { - $this->value = parent::doTrimRight(); - - return $this; - } - - /** - * Converts the instance's value to Upper Case. - * - * This method supports chaning. - * - * @since 1.0.0 - * @return MutableString - */ - public function toUpper(): MutableString - { - $this->value = parent::doToUpper(); - - return $this; - } - - /** - * Make a string's first character uppercase. - * - * @return MutableString - * - * @since 1.0.0 - * @return MutableString - */ - public function toUpperFirst(): MutableString - { - $this->value = parent::doToUpperFirst(); - - return $this; - } - - /** - * Makes the string's first character of each word to Upper Case. - * - * @param string $delimiters - The optional delimiters contains the word separator characters. - * - * @since 1.0.0 - * @return MutableString - */ - public function toUpperWords(string $delimiters = " \t\r\n\f\v"): MutableString - { - $this->value = parent::doToUpperWords($delimiters); - - return $this; - } - - /** - * Converts the instance's value to Lower Case. - * - * This method supports chaning. - * - * @since 1.0.0 - * @return MutableString - */ - public function toLower(): MutableString - { - $this->value = parent::doToLower(); - - return $this; - } - - /** - * Make a string's first character lowercase. - * - * @since 1.0.0 - * @return MutableString - */ - public function toLowerFirst(): MutableString - { - $this->value = parent::doToLowerFirst(); - - return $this; - } - - /** - * This method returns a new instance padded on the left to the specified padding length minus - * the length of the instance's value. - * - * Eg:. if the padding length is 12, and the instance's value is only 10 characters long, the value will - * only be padded by the value of 2. - * - * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise - * it is padded with characters from $padString up to the limit. - * - * This method supports chaning. - * - * @param int $length - Length of the padded value. - * @param string $padString - The pad_string may be truncated if the required number of padding characters - * can't be evenly divided by the $padString's length. - * - * @throws \InvalidArgumentException - If any of the parameters is invalid. - * - * @since 1.0.0 - * @return MutableString - */ - public function padLeft(int $length, string $padString = " "): MutableString - { - $this->value = parent::doPadLeft($length, $padString); - - return $this; - } - - /** - * This method returns a new instance padded on the left, exactly to the specified padding length. - * - * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise - * it is padded with characters from $padString up to the limit. - * - * @param int $length - Length of the padded value. - * @param string $padString - The pad_string may be truncated if the required number of padding characters - * can't be evenly divided by the $padString's length. - * - * @throws \InvalidArgumentException - If any of the parameters is invalid. - * - * @since 1.0.0 - * @return MutableString - */ - public function padLeftExtra(int $length, string $padString = " "): MutableString - { - $this->value = parent::doPadLeftExtra($length, $padString); - - return $this; - } - - /** - * This method returns a new instance padded on the right to the specified padding length minus - * the length of the instance's value. - * - * Eg:. if the padding length is 12, and the instance's value is only 10 characters long, the value will - * only be padded by the value of 2. - * - * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise - * it is padded with characters from $padString up to the limit. - * - * This method supports chaning. - * - * @param int $length - Length of the padded value. - * @param string $padString - The pad_string may be truncated if the required number of padding characters - * can't be evenly divided by the $padString's length. - * - * @throws \InvalidArgumentException - If any of the parameters is invalid. - * - * @since 1.0.0 - * @return MutableString - */ - public function padRight(int $length, string $padString = " "): MutableString - { - $this->value = parent::doPadRight($length, $padString); - - return $this; - } - - /** - * This method returns a new instance padded on the right, exactly to the specified padding length. - * - * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise - * it is padded with characters from $padString up to the limit. - * - * @param int $length - Length of the padded value. - * @param string $padString - The pad_string may be truncated if the required number of padding characters - * can't be evenly divided by the $padString's length. - * - * @throws \InvalidArgumentException - If any of the parameters is invalid. - * - * @since 1.0.0 - * @return MutableString - */ - public function padRightExtra(int $length, string $padString = " "): MutableString - { - $this->value = parent::doPadRightExtra($length, $padString); - - return $this; - } - - /** - * This method mutates the instance's value with a portion of the original value, specified by the - * $start and $length parameters. - * - * $start parameter: - *
    - *
  • If $start is non-negative, the returned an instance will start at the $start'th position in - * string, counting from zero. For instance, in the string 'abcdef', the character at position 0 is 'a', the - * character at position 2 is 'c', and so forth.
  • - *
  • If $start is negative, the returned string will start at the $start'th character from the end - * of string.
  • - *
  • If the absolute value of $start is higher than the instance's length, an - * exception is thrown.
  • - *
- * - * $length parameter: - *
    - *
  • If $length is given and is positive, the string returned will contain at most length characters - * beginning from $start (depending on the length of string).
  • - *
  • If $length is given and is negative, then that many characters will be omitted from the end of string - * (after the start position has been calculated when a start is negative).
  • - *
  • If $length exceeds the remaining number of characters, after the $start calculation, an - * Exception will be raised.
  • - *
- * - * @param int $start - Start of the sub-string. Can be negative. - * @param int $length - Length of the sub-string. Can be negative. - * - * @throws \OutOfRangeException - If the $start and/or $length is either too small, or too long. - * - * @since 1.0.0 - * @return MutableString - */ - public function subString(int $start, int $length = null): MutableString - { - $this->value = parent::doSubString($start, $length); - - return $this; - } - - /** - * This method mutates the instance's value with a portion of the original instance's value, starting at the - * beginning of the value, with the number of characters specified in the $length parameter. - * - * Same rules as MutableString::subString() are applied. - * - * @param int $length - Length of the sub-string. Must be positive. - * - * @throws \InvalidArgumentException - If supplied Length is not a positive integer. - * - * @since 1.0.0 - * @return MutableString - */ - public function subLeft(int $length): MutableString - { - $this->value = parent::doSubLeft($length); - - return $this; - } - - /** - * This method mutates the instance's value with a portion of the original instance's value, couting from the - * end of the value, with the number of characters specified in the $length parameter. - * - * Same rules as MutableString::subString() are applied. - * - * @param int $length - Length of the sub-string. Must be positive. - * - * @throws \InvalidArgumentException - If supplied Length is not a positive integer. - * - * @since 1.0.0 - * @return MutableString - */ - public function subRight(int $length): MutableString - { - $this->value = parent::doSubRight($length); - - return $this; - } - - /** - * This method reverses value of the instance. - * - * @since 1.0.0 - * @return MutableString - */ - public function reverse(): MutableString - { - $this->value = parent::doReverse(); - - return $this; - } - - /** - * This method replaces a string's occurance by another. - * - * @param string $search - The string to search for. - * @param string $replace - The search's replacement. - * - * @throws \InvalidArgumentException - If $search is empty. - * - * @since 1.0.0 - * @return MutableString - */ - public function replace(string $search, string $replace): MutableString - { - $this->value = parent::doReplace($search, $replace); - - return $this; - } -} diff --git a/src/Scalar/ReadonlyBoolean.php b/src/Scalar/ReadonlyBoolean.php deleted file mode 100644 index 489cdd3..0000000 --- a/src/Scalar/ReadonlyBoolean.php +++ /dev/null @@ -1,91 +0,0 @@ - - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ReadonlyBoolean extends AbstractReadBoolean -{ - /** - * Creates a new instance of ReadonlyBoolean based on a string value. - * - * @param string $string - Boolean as a string value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return ReadonlyBoolean - */ - public static function fromString(string $string): ReadonlyBoolean - { - // Valiates supplied parameter. - if (\strlen(\trim($string)) === 0) { - throw new \InvalidArgumentException("Supplied string must be a non empty string."); - } - - return new ReadonlyBoolean( - \in_array(\strtolower($string), ['1', 'true', 'yes']) - ); - } - - /** - * Creates a new instance of ReadonlyBoolean based on a string value. - * - * @param int $number - Number as an integer value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return ReadonlyBoolean - */ - public static function fromInteger(int $number): ReadonlyBoolean - { - return new ReadonlyBoolean($number > 0); - } - - /** - * Creates a new instance of ReadonlyBoolean based on a string value. - * - * @param float $number - Number as a float value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return ReadonlyBoolean - */ - public static function fromFloat(float $number): ReadonlyBoolean - { - return new ReadonlyBoolean($number > 0); - } - - /** - * Converts boolean's instance to an equivalent string instance. - * - * @since 1.0.0 - * @return ReadonlyString - */ - public function toString(): ReadonlyString - { - return ReadonlyString::fromString($this->value ? 'True' : 'False'); - } - - /** - * Converts boolean's instance to an equivalent integer's instance. - * - * @since 1.0.0 - * @return ReadonlyInteger - */ - public function toInteger(): ReadonlyInteger - { - return ReadonlyInteger::fromInteger($this->value ? 1 : 0); - } -} diff --git a/src/Scalar/ReadonlyFloat.php b/src/Scalar/ReadonlyFloat.php deleted file mode 100644 index f2acb0b..0000000 --- a/src/Scalar/ReadonlyFloat.php +++ /dev/null @@ -1,125 +0,0 @@ -MutableFloat datatype, as well as for - * ImmutableFloat. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ReadonlyFloat extends AbstractReadFloat -{ - /** - * Creates a new instance of ReadonlyFloat based on a string value. - * - * @param string $number - Number as a string value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return ReadonlyFloat - */ - public static function fromString(string $number): ReadonlyFloat - { - // Valiates supplied parameter. - if (\strlen(\trim($number)) === 0) { - throw new \InvalidArgumentException("Supplied string must be a non empty string."); - } - - return new ReadonlyFloat( - \floatval($number) - ); - } - - /** - * Creates a new instance of ReadonlyFloat based on a string value. - * - * @param int $number - Number as an integer value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return ReadonlyFloat - */ - public static function fromInteger(int $number): ReadonlyFloat - { - return new ReadonlyFloat( - \floatval($number) - ); - } - - /** - * Creates a new instance of ReadonlyFloat based on a string value. - * - * @param float $number - Number as a float value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return ReadonlyFloat - */ - public static function fromFloat(float $number): ReadonlyFloat - { - return new ReadonlyFloat($number); - } - - /** - * Formats the Float number as a formatted string. - * - * @param \NumberFormatter $formatter - Format that should be used in the returned string. - * - * @since 1.0.0 - * @return ReadonlyString - */ - public function format(\NumberFormatter $formatter): ReadonlyString - { - return ReadonlyString::fromString( - $formatter->format($this->value, \NumberFormatter::TYPE_DOUBLE) - ); - } - - /** - * Converts float's instance to an equivalent string instance. - * - * @since 1.0.0 - * @return ReadonlyString - */ - public function toString(): ReadonlyString - { - return ReadonlyString::fromString($this->__toString()); - } - - /** - * Converts float's instance to an equivalent integer's instance. - * - * @since 1.0.0 - * @return ReadonlyInteger - */ - public function toInteger(): ReadonlyInteger - { - return ReadonlyInteger::fromString($this->__toString()); - } - - /** - * Converts float's instance to an equivalent boolean instance. - * - * A ReadonlyBoolean will be returned, as Booleans don't require Immutable and Mutable - * distinctions. - * - * @since 1.0.0 - * @return ReadonlyBoolean - */ - public function toBoolean(): ReadonlyBoolean - { - return ReadonlyBoolean::fromFloat($this->value); - } -} diff --git a/src/Scalar/ReadonlyInteger.php b/src/Scalar/ReadonlyInteger.php deleted file mode 100644 index c9c769a..0000000 --- a/src/Scalar/ReadonlyInteger.php +++ /dev/null @@ -1,108 +0,0 @@ -MutableInteger datatype, as well as for - * ImmutableInteger. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ReadonlyInteger extends AbstractReadInteger -{ - /** - * Creates a new instance of ReadonlyInteger based on a string value. - * - * @param string $number - Number as a string value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return ReadonlyInteger - */ - public static function fromString(string $number): ReadonlyInteger - { - // Valiates supplied parameter. - if (\strlen(\trim($number)) === 0) { - throw new \InvalidArgumentException("Supplied string must be a non empty string."); - } - - return new ReadonlyInteger( - ((int) $number) - ); - } - - /** - * Creates a new instance of ReadonlyInteger based on a string value. - * - * @param int $number - Number as an integer value. - * - * @throws \InvalidArgumentException - If supplied argument is empty. - * - * @since 1.0.0 - * @return ReadonlyInteger - */ - public static function fromInteger(int $number): ReadonlyInteger - { - return new ReadonlyInteger($number); - } - - /** - * Formats the Integer as a formatted string. - * - * @param \NumberFormatter $formatter - Format that should be used in the returned string. - * - * @since 1.0.0 - * @return ReadonlyString - */ - public function format(\NumberFormatter $formatter): ReadonlyString - { - return ReadonlyString::fromString( - $formatter->format($this->value, \NumberFormatter::TYPE_INT64) - ); - } - - /** - * Converts integer's instance to an equivalent string instance. - * - * @since 1.0.0 - * @return ReadonlyString - */ - public function toString(): ReadonlyString - { - return ReadonlyString::fromString($this->__toString()); - } - - /** - * Converts integer's instance to an equivalent float's instance. - * - * @since 1.0.0 - * @return ReadonlyFloat - */ - public function toFloat(): ReadonlyFloat - { - return ReadonlyFloat::fromInteger($this->value); - } - - /** - * Converts integer's instance to an equivalent boolean instance. - * - * A ReadonlyBoolean will be returned, as Booleans don't require Immutable and Mutable - * distinctions. - * - * @since 1.0.0 - * @return ReadonlyBoolean - */ - public function toBoolean(): ReadonlyBoolean - { - return ReadonlyBoolean::fromInteger($this->value); - } -} diff --git a/src/Scalar/ReadonlyString.php b/src/Scalar/ReadonlyString.php deleted file mode 100644 index b061f45..0000000 --- a/src/Scalar/ReadonlyString.php +++ /dev/null @@ -1,33 +0,0 @@ -MutableString datatype, as well as for - * ImmutableString. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ReadonlyString extends AbstractReadString -{ - /** - * Creates a new instance of ReadonlyString based on a string value. - * - * @param string $value - Instance's initial value. - * - * @since 1.0.0 - * @return ReadonlyString - */ - public static function fromString(string $value): ReadonlyString - { - return new ReadonlyString($value); - } -} diff --git a/src/Scalar/Str.php b/src/Scalar/Str.php new file mode 100644 index 0000000..0ad891f --- /dev/null +++ b/src/Scalar/Str.php @@ -0,0 +1,618 @@ +value = $value; + } + + /** + * Magic method that will print out the native string representation of the instance. + * + * @return string + */ + public function __toString(): string + { + return $this->value; + } + + /** + * Compares the values of 2 separate instances. + * + * Returns TRUE if the 2 instance's values match. FALSE otherwise. + * + * @param Str $string - Another Str instance to compare to. + * @return bool + */ + public function match(Str $string): bool + { + return $this->equals((string) $string); + } + + /** + * Compares the instance's value, with the supplied native string. + * + * Returns TRUE if the 2 values match. FALSE otherwise. + * + * @param string $string - Another string to compare with. + * @return bool + */ + public function equals(string $string): bool + { + return ($this->value === $string); + } + + /** + * Returns the Instance's character length. + * + * @return int + */ + public function getLength(): int + { + return \strlen($this->value); + } + + /** + * Counts the number of words in the String. + * + * @return int + */ + public function getWordCount(): int + { + return \str_word_count($this->value); + } + + /** + * Checks if the instance's value contains the supplied $search string. + * + * Returns TRUE if found. FALSE otherwise. + * + * @param string $search - Non empty string to search for in the instance. + * + * @throws NonEmptyStringException - If supplied $search is empty. + * @return bool + */ + public function contains(string $search): bool + { + // Validates supplied parameter. + if (\strlen($search) === 0) { + throw NonEmptyStringException::withName('$search'); + } + + return ($this->indexOf($search) !== null); + } + + /** + * Searches and returns the character index in the instance, of the $search string. + * + * If a $start is specified, search will start this number of characters counted from + * the beginning of the string. If $start is negative, the search will start this number + * of characters counted from the end of the string. + * + * If the $search is not found in the instance's value, NULL is returned. + * + * @param string $search - String to search for in the instance. + * @param int $start - Search offset start. Defaults to ZERO. + * + * @throws NonEmptyStringException - If $search value is an empty string. + * @throws ParameterOutOfRangeException - If the $start is either too small, or too long. + * @return int|null + */ + public function indexOf(string $search, int $start = 0): ?int + { + // Validates supplied parameters. + if (\strlen($search) === 0) { + throw NonEmptyStringException::withName('$search'); + } + if ($start) { + $this->validateStartAndLength($start, null); + } + + // Collects the string position. + $index = \strpos($this->value, $search, $start); + + // If false is returned, no index was found, and therefore NULL is returned. + if ($index === false) { + return null; + } + + return $index; + } + + /** + * Checks if the instance's value starts with the supplied string. + * + * @param string $search - Non empty string to search for in the instance. + * + * @throws NonEmptyStringException - If supplied $search is empty. + * @return bool + */ + public function startsWith(string $search): bool + { + // Validates supplied parameter. + if (\strlen($search) === 0) { + throw NonEmptyStringException::withName('$search'); + } + + return ($this->indexOf($search) === 0); + } + + /** + * Checks if the instance's value ends with the supplied string. + * + * @param string $search - Non empty string to search for in the instance. + * + * @throws NonEmptyStringException - If supplied $search is empty. + * @return bool + */ + public function endsWith(string $search): bool + { + // Validates supplied parameter. + if (\strlen($search) === 0) { + throw NonEmptyStringException::withName('$search'); + } + + return ( + ((string) $this->subString((0 - \strlen($search)))) === $search + ); + } + + /** + * Counts the number of substring occurrences in the instance's value. + * + * @param string $search - Non empty string to search for in the instance. + * @param int $start - The sub-string's offset/start. + * @param int|NULL $length - Length value. Can be NULL, in which case, it won't be validated. + * + * @throws NonEmptyStringException - If supplied $search is empty. + * @throws ParameterOutOfRangeException - If the $start and/or $length is either too small, or too long. + * @return int + */ + public function count(string $search, int $start = 0, ?int $length = null): int + { + // Validates supplied $search parameter. + if (\strlen($search) === 0) { + throw NonEmptyStringException::withName('$search'); + } + + // Validates supplied $start and $length. + $this->validateStartAndLength($start, $length); + + // Checks if $length was defined. + if ($length) { + return \substr_count($this->value, $search, $start, $length); + } + + return \substr_count($this->value, $search, $start); + } + + /** + * Validates a character $length, based on the instance value's length, and supplied $start. + * + * @param int $start - The sub-string's offset/start. + * @param int|null $length - Length value. Can be NULL, in which case, it won't be validated. + * + * @throws ParameterOutOfRangeException - If the $start and/or $length is either too small, or too long. + * @return void + */ + private function validateStartAndLength(int $start, ?int $length): void + { + // Calculates the absolute values for validations. + $absStart = (int) \abs($start); + $absLength = $length ? (int) \abs($length) : null; + + // Validates the starting value. + if ($absStart > $this->getLength()) { + throw ParameterOutOfRangeException::withName('$start'); + } + + // If supplied $length is NULL, no further validations are required. + if ($length === null) { + return; + } + + // Checks if the supplied $length, doesn't exceed the available number of characters. + $startTooBig = ($start >= 0 && ($this->getLength() - $start < $absLength)); + $startTooSmall = ($start < 0 && $absLength > $absStart); + + if ($startTooBig || $startTooSmall) { + throw ParameterOutOfRangeException::withName('$length'); + } + } + + /** + * Trims instance's value on both ends. + * + * @return self + */ + public function trim(): self + { + return new self( + \trim($this->value) + ); + } + + /** + * Trims instance's value only on the left. + * + * @return self + */ + public function trimLeft(): self + { + return new self( + \ltrim($this->value) + ); + } + + /** + * Trims instance's value only on the right. + * + * @return self + */ + public function trimRight(): self + { + return new self( + \rtrim($this->value) + ); + } + + /** + * Converts the instance's value to Uppercase. + * + * @return self + */ + public function toUpper(): self + { + return new self( + \mb_strtoupper($this->value) + ); + } + + /** + * Converts the instance's value first character to Uppercase. + * + * @return self + */ + public function toUpperFirst(): self + { + return new self( + \ucfirst($this->value) + ); + } + + /** + * Converts the instance's value first character of each word to Upper Case. + * + * @param string $delimiters - The optional delimiters contains the word separator characters. + * @return self + */ + public function toUpperWords(string $delimiters = " \t\r\n\f\v"): self + { + return new self( + \ucwords($this->value, $delimiters) + ); + } + + /** + * Converts the instance's value to Lowercase. + * + * @return self + */ + public function toLower(): self + { + return new self( + \mb_strtolower($this->value) + ); + } + + /** + * Converts the instance's value first character to Lowercase. + * + * @return self + */ + public function toLowerFirst(): self + { + return new self( + \lcfirst($this->value) + ); + } + + /** + * This method returns a new instance padded on the LEFT to the specified padding length minus + * the length of the instance's value. + * + * Eg:. if the padding length is 12, and the instance's value is only 10 characters long, the value will + * only be padded by the value of 2. + * + * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise + * it is padded with characters from $padString up to the limit. + * + * @param int $length - Length of the padded value. + * @param string $padding - The pad_string may be truncated if the required number of padding characters + * can't be evenly divided by the $padString's length. + * + * @throws NonEmptyStringException - If supplied $padding is empty. + * @throws ParameterOutOfRangeException - If the $length is either too small, or too long. + * @return self + */ + public function padLeft(int $length, string $padding = " "): self + { + // Validates supplied parameters. + if ($length < 1) { + throw ParameterOutOfRangeException::withName('$length'); + } + if (\strlen($padding) === 0) { + throw NonEmptyStringException::withName('$padding'); + } + + return new self( + \str_pad($this->value, $length, $padding, STR_PAD_LEFT) + ); + } + + /** + * This method returns a new instance padded on the LEFT, exactly to the specified padding length. + * + * If the optional argument $padding is not supplied, the input is padded with spaces, otherwise + * it is padded with characters from $padding up to the limit. + * + * @param int $length - Length of the padded value. + * @param string $padding - The pad_string may be truncated if the required number of padding characters + * can't be evenly divided by the $padding's length. + * + * @throws NonEmptyStringException - If supplied $padding is empty. + * @throws ParameterOutOfRangeException - If the $length is either too small, or too long. + * @return self + */ + public function padLeftExtra(int $length, string $padding = " "): self + { + return $this->padLeft(($this->getLength() + $length), $padding); + } + + /** + * This method returns a new instance padded on the RIGHT to the specified padding length minus + * the length of the instance's value. + * + * Eg:. if the padding length is 12, and the instance's value is only 10 characters long, the value will + * only be padded by the value of 2. + * + * If the optional argument $padString is not supplied, the input is padded with spaces, otherwise + * it is padded with characters from $padding up to the limit. + * + * @param int $length - Length of the padded value. + * @param string $padding - The pad_string may be truncated if the required number of padding characters + * can't be evenly divided by the $padding's length. + * + * @throws NonEmptyStringException - If supplied $padding is empty. + * @throws ParameterOutOfRangeException - If the $length is either too small, or too long. + * @return self + */ + public function padRight(int $length, string $padding = " "): self + { + // Validates supplied parameters. + if ($length < 1) { + throw ParameterOutOfRangeException::withName('$length'); + } + if (\strlen($padding) === 0) { + throw NonEmptyStringException::withName('$padding'); + } + + return new self( + \str_pad($this->value, $length, $padding, STR_PAD_RIGHT) + ); + } + + /** + * This method returns a new instance padded on the RIGHT, exactly to the specified padding length. + * + * If the optional argument $padding is not supplied, the input is padded with spaces, otherwise + * it is padded with characters from $padding up to the limit. + * + * @param int $length - Length of the padded value. + * @param string $padding - The pad_string may be truncated if the required number of padding characters + * can't be evenly divided by the $padding's length. + * + * @throws NonEmptyStringException - If supplied $padding is empty. + * @throws ParameterOutOfRangeException - If the $length is either too small, or too long. + * @return self + */ + public function padRightExtra(int $length, string $padding = " "): self + { + return $this->padRight(($this->getLength() + $length), $padding); + } + + /** + * This method returns a new instance with a portion of the original instance's value, specified by the + * $start and $length parameters. + * + * $start parameter: + * - If $start is non-negative, the returned an instance will start at the $start'th position in + * string, counting from zero. For instance, in the string 'abcdef', the character at position 0 is 'a', the + * character at position 2 is 'c', and so forth. + * - If $start is negative, the returned string will start at the $start'th character from the end + * of string. + * - If the absolute value of $start is higher than the instance's length, an + * exception is thrown. + * + * + * $length parameter: + * - If $length is given and is positive, the string returned will contain at most length characters + * beginning from $start (depending on the length of string). + * - If $length is given and is negative, then that many characters will be omitted from the end of string + * (after the start position has been calculated when a start is negative). + * - If $length exceeds the remaining number of characters, after the $start calculation, an + * Exception will be raised. + * + * @param int $start - Start of the sub-string. Can be negative. + * @param int $length - Length of the sub-string. Can be negative. + * + * @throws ParameterOutOfRangeException - If the $start and/or $length is either too small, or too long. + * @return self + */ + public function subString(int $start, int $length = null): self + { + // Validates supplied $start and $length. + $this->validateStartAndLength($start, $length); + + if ($length === null) { + return new self(\substr($this->value, $start) ?? ''); + } + + return new self(\substr($this->value, $start, $length) ?? ''); + } + + /** + * This method returns a new instance with a portion of the original instance's value, starting at the beginning + * of the value, with the number of characters specified in the $length parameter. + * + * @param int $length - Length of the sub-string. Must be positive. + * + * @throws ParameterOutOfRangeException - If supplied Length is not a positive integer. + * @return self + */ + public function subLeft(int $length): self + { + // Validates parameter. + if ($length < 1) { + throw ParameterOutOfRangeException::withName('$length'); + } + + return $this->subString(0, $length); + } + + /** + * This method returns a new instance with a portion of the original instance's value, couting from the end + * of the value, with the number of characters specified in the $length parameter. + * + * @param int $length - Length of the sub-string. Must be positive. + * + * @throws ParameterOutOfRangeException - If supplied Length is not a positive integer. + * @return self + */ + public function subRight(int $length): self + { + // Validates parameter. + if ($length < 1) { + throw ParameterOutOfRangeException::withName('$length'); + } + + return $this->subString(0 - $length); + } + + /** + * This method returns the reversed value of the instance. + * + * @return self + */ + public function reverse(): self + { + return new self( + \strrev($this->value) + ); + } + + /** + * This method replaces a string's occurance by another, and returns a new instance with the new value. + * + * @param string $search - The string to search for. + * @param string $replace - The search's replacement. + * + * @throws NonEmptyStringException - If either $search or $replace are empty. + * @return self + */ + public function replace(string $search, string $replace): self + { + // Validates supplied parameters. + if (\strlen($search) === 0) { + throw NonEmptyStringException::withName('$search'); + } + if (\strlen($replace) === 0) { + throw NonEmptyStringException::withName('$replace'); + } + + return new self( + \str_replace($search, $replace, $this->value) + ); + } + + /** + * Returns an array of strings, each of which is a substring of string formed + * by splitting it on boundaries formed by the string separator. + * + * If limit is set and positive, the returned array will contain a maximum of limit + * elements with the last element containing the rest of string. + * + * If the limit parameter is negative, all components except the last -limit are returned. + * + * If the limit parameter is zero, then this is treated as 1. + * + * @param string $separator - The boundary string. + * @param int|null $limit - The limit of returned segments. + * + * @throws NonEmptyStringException - If $separator is an empty string. + * @return array|Str[] + */ + public function explode(string $separator, ?int $limit = null): array + { + // Validates supplied parameters. + if (\strlen($separator) === 0) { + throw NonEmptyStringException::withName('$separator'); + } + + if ($limit === null) { + $segments = \explode($separator, $this->value); + } else { + $segments = \explode($separator, $this->value, $limit); + } + + $exploded = []; + foreach ($segments as $segment) { + $exploded[] = Str::create($segment); + } + + return $exploded; + } +} diff --git a/src/Traits/Datetime/Datetime/HasStaticFactoryMethodsTrait.php b/src/Traits/Datetime/Datetime/HasStaticFactoryMethodsTrait.php new file mode 100644 index 0000000..0cbb9bb --- /dev/null +++ b/src/Traits/Datetime/Datetime/HasStaticFactoryMethodsTrait.php @@ -0,0 +1,69 @@ +setTimestamp($timestamp); + + return new Datetime( + $dt->format(\Datetime::W3C) + ); + } + + public static function fromUnits( + int $years, + int $months, + int $days, + int $hours = 0, + int $minutes = 0, + int $seconds = 0, + ?DateTimeZone $timezone = null + ): Datetime { + return new Datetime( + \sprintf( + "%d-%d-%dT%d:%d:%d%s", + $years, + $months, + $days, + $hours, + $minutes, + $seconds, + ((string) $timezone ?? '+00:00') + ) + ); + } +} diff --git a/src/Traits/Datetime/README.md b/src/Traits/Datetime/README.md new file mode 100755 index 0000000..72e2465 --- /dev/null +++ b/src/Traits/Datetime/README.md @@ -0,0 +1 @@ +# Datetime object's configuration Traits diff --git a/src/Traits/Entities/CanMassAssignStateTrait.php b/src/Traits/Entities/CanMassAssignStateTrait.php new file mode 100644 index 0000000..6eb648a --- /dev/null +++ b/src/Traits/Entities/CanMassAssignStateTrait.php @@ -0,0 +1,56 @@ +translateToMappedFields($fields); + $this->processRules($mapped); + + $fields = []; + foreach ($mapped as $field => $value) { + if ($this->{$field} instanceof AbstractValueObject && \method_exists($this->{$field}, 'setAttributes')) { + $this->{$field}->setAttributes($value); + } elseif (\is_object($this->{$field}) && \method_exists($this->{$field}, '__toString')) { + $fields[$field] = (string) $value; + } else { + $fields[$field] = $value; + } + } + + $this->castAttributes($fields); + + if (\method_exists($this, 'triggerOnUpdate')) { + $this->{'triggerOnUpdate'}(); + } + } +} diff --git a/src/Traits/Entities/CanProcessEntityStateTrait.php b/src/Traits/Entities/CanProcessEntityStateTrait.php new file mode 100644 index 0000000..0de1761 --- /dev/null +++ b/src/Traits/Entities/CanProcessEntityStateTrait.php @@ -0,0 +1,128 @@ +initialState = $this->toArray(); + } + + /** + * Validates if there are any Dirty class attributes. + * + * Returns TRUE if at least one attribute has changed, since the class + * was initially loaded. + * + * @return bool + */ + final public function isDirty(): bool + { + return (\count($this->getDirty()) > 0); + } + + /** + * Returns a list of attributes that have changed value. + * + * If the Entity is not marked as dirty, this will return an empty array. + * + * @param bool $withTimestamps - In case dirty attributes include timestamps. Defaults to FALSE. + * @return array + */ + final public function getDirty(bool $withTimestamps = false): array + { + // Loops through all the class' attributes, and validates if any has changed. + $dirty = []; + foreach ($this->initialState as $field => $value) { + + $isNewOrRequired = ( + (\method_exists($this, 'isNew') && $this->isNew()) || + \array_search($field, $this->required) + ); + + if ($this->{$field} instanceof AbstractValueObject) { + + if ($isNewOrRequired || (\method_exists($this->{$field}, 'isDirty') && $this->{$field}->isDirty())) { + $dirty[$field] = $this->{$field}->getDirty($withTimestamps); + } + + continue; + } + + if (\is_object($this->{$field}) && \method_exists($this->{$field}, '__toString')) { + $hasChanged = ((string) $value) !== ((string) $this->{$field}); + } else { + $hasChanged = $value !== $this->{$field}; + } + + if ($hasChanged || $isNewOrRequired) { + if (\is_object($this->{$field}) && \method_exists($this->{$field}, '__toString')) { + $dirty[$field] = (string) $this->{$field}; + } else { + $dirty[$field] = $this->{$field}; + } + } + } + + /// Removes timestamps in case they exist, and they were not requested. + if (!$withTimestamps) { + unset($dirty['created_at'], $dirty['updated_at'], $dirty['deleted_at']); + } + + // Returns all dirty attributes. + return $dirty; + } + + /** + * Returns a list of attributes, with their original values. + * + * When the Entity's attributes change, it becomes Dirty. This method will return the + * list of attributes, with their original values prior to that change. + * + * @return array + */ + final public function getOriginal(): array + { + return $this->initialState; + } + + /** + * Resets the instance's state, cleaning up "Dirty" attributes, and reset tracking. + * + * @return void + */ + final public function resetState(): void + { + $fields = \array_keys($this->initialState); + + foreach ($fields as $field) { + if ($this->{$field} instanceof AbstractValueObject && \method_exists($this->{$field}, 'resetState')) { + $this->{$field}->resetState(); + } + } + + $this->initialState = $this->toArray(); + } +} diff --git a/src/Traits/Entities/CanProcessOnUpdateEventsTrait.php b/src/Traits/Entities/CanProcessOnUpdateEventsTrait.php new file mode 100644 index 0000000..db99dcf --- /dev/null +++ b/src/Traits/Entities/CanProcessOnUpdateEventsTrait.php @@ -0,0 +1,54 @@ + \strlen(self::$ONUPDATEPREFIX)) { + $this->onUpdateEvents[] = $method; + } + } + } + + /** + * Triggers all onUpdate pre-declared events. + * + * @return void + */ + protected function triggerOnUpdate(): void + { + foreach ($this->onUpdateEvents as $onUpdate) { + $this->$onUpdate(); + } + } +} diff --git a/src/Traits/Entities/README.md b/src/Traits/Entities/README.md new file mode 100755 index 0000000..b83a7f1 --- /dev/null +++ b/src/Traits/Entities/README.md @@ -0,0 +1 @@ +# Entity's configuration Traits diff --git a/src/Traits/README.md b/src/Traits/README.md new file mode 100755 index 0000000..13a3449 --- /dev/null +++ b/src/Traits/README.md @@ -0,0 +1,3 @@ +# Object building related Traits + +In this namespace, you'll find Traits that will help configure your objects. diff --git a/src/Traits/ValueObjects/CanProcessOnLoadEventsTrait.php b/src/Traits/ValueObjects/CanProcessOnLoadEventsTrait.php new file mode 100644 index 0000000..56c0b51 --- /dev/null +++ b/src/Traits/ValueObjects/CanProcessOnLoadEventsTrait.php @@ -0,0 +1,39 @@ + \strlen(self::$ONLOADPREFIX)) { + $this->$method(); + } + } + } +} diff --git a/src/Traits/ValueObjects/CanSerializeAllToJsonTrait.php b/src/Traits/ValueObjects/CanSerializeAllToJsonTrait.php new file mode 100644 index 0000000..e0dd708 --- /dev/null +++ b/src/Traits/ValueObjects/CanSerializeAllToJsonTrait.php @@ -0,0 +1,45 @@ + $value) { + // If the Attribute's value is json serializable itself, + // serialize and add it to the returning array. + // Otherwize, return its holding value. + if ($value instanceof \JsonSerializable) { + $json[$name] = $value->jsonSerialize(); + } elseif (\is_object($value) && \method_exists($value, '__toString')) { + $json[$name] = (string) $value; + } else { + $json[$name] = $value; + } + } + + return (object) $json; + } +} diff --git a/src/Traits/ValueObjects/HasConversionToPrimitiveValuesTrait.php b/src/Traits/ValueObjects/HasConversionToPrimitiveValuesTrait.php new file mode 100644 index 0000000..71a567a --- /dev/null +++ b/src/Traits/ValueObjects/HasConversionToPrimitiveValuesTrait.php @@ -0,0 +1,46 @@ + $value) { + // Returns the string representation of the object, or tries to convert array or object to JSON. + // If is not an Object not an Array, returns the actual value of the Field. + if ($value instanceof AbstractValueObject || $value instanceof EntityCollection) { + $converted[$field] = $objectAsJson ? $value->jsonSerialize() : $value->toArray(); + } elseif (\is_object($value) && \method_exists($value, '__toString')) { + $converted[$field] = (string) $value; + } elseif (\is_object($value)) { + $converted[$field] = (array) $value; + } else { + $converted[$field] = $value; + } + } + + return $converted; + } +} diff --git a/src/Traits/ValueObjects/HasFieldCastingTrait.php b/src/Traits/ValueObjects/HasFieldCastingTrait.php new file mode 100644 index 0000000..72f1b80 --- /dev/null +++ b/src/Traits/ValueObjects/HasFieldCastingTrait.php @@ -0,0 +1,72 @@ + $value) { + // Builds up the Mutator's name. + $mutator = $this->createMutatorName(self::$CASTPREFIX, $field); + + // Checks if the mutator exists in the instance, and if so, loads the value into it. + if (\array_search($mutator, $this->castList) !== false) { + $this->{$mutator}($value); + } + } + } + + /** + * Creates and returns a mutator's method name, based on the supplied Prefix and Field's name. + * + * @param string $prefix - Prefix used in mutator's name. + * @param string $field - Name of the Field used as reference for the Mutator's name creation. + * @return string + */ + final protected function createMutatorName(string $prefix, string $field): string + { + return ($prefix . \str_replace('_', '', \ucwords($field, '_'))); + } + + /** + * Loads a list of casting mutator methods, available within the Instance for processing. + * + * @return void + */ + private function registerAttributeCastingList(): void + { + // Loops through all the class' methods, and loads the necessary ones in + // the corresponding containers. + foreach (\get_class_methods($this) as $method) { + // Loads casting mutators. + if (\strpos($method, self::$CASTPREFIX) === 0 && \strlen($method) > \strlen(self::$CASTPREFIX)) { + $this->castList[] = $method; + } + } + } +} diff --git a/src/Traits/ValueObjects/HasGuardedFieldsTrait.php b/src/Traits/ValueObjects/HasGuardedFieldsTrait.php new file mode 100644 index 0000000..502c555 --- /dev/null +++ b/src/Traits/ValueObjects/HasGuardedFieldsTrait.php @@ -0,0 +1,39 @@ +guarded as $guarded) { + if (\array_key_exists($guarded, $original)) { + unset($original[$guarded]); + } + } + + return $original; + } +} diff --git a/src/Traits/ValueObjects/HasMappedFieldsTrait.php b/src/Traits/ValueObjects/HasMappedFieldsTrait.php new file mode 100644 index 0000000..c2c2b5a --- /dev/null +++ b/src/Traits/ValueObjects/HasMappedFieldsTrait.php @@ -0,0 +1,59 @@ + $value) { + + /** + * The priority is to check if the field was loaded with the native name. + * If that's the case, we'll just set the value. + * + * Otherwise, we'll check if the field is mapped to another. + * In that case, we'll load the value into the mapped field. + * + * The default behavior, is just to keep the field=>value pair as initially + * supplied. + * Although the behavior is the same as in the initial condition, we are + * assigning in the else block, so that we keep the priorities in order. + */ + if (\array_search($field, $attributes) !== false) { + $sanitized[$field] = $value; + } elseif (\array_key_exists($field, $this->maps)) { + $sanitized[$this->maps[$field]] = $value; + } else { + $sanitized[$field] = $value; + } + } + + return $sanitized; + } +} diff --git a/src/Traits/ValueObjects/HasRequiredFieldsTrait.php b/src/Traits/ValueObjects/HasRequiredFieldsTrait.php new file mode 100644 index 0000000..f7e739a --- /dev/null +++ b/src/Traits/ValueObjects/HasRequiredFieldsTrait.php @@ -0,0 +1,45 @@ +required as $required) { + // First, we'll need to assess if the field already exists NATIVELY in the class. + // If not, we'll need to assess it is mapped. + if (! \array_key_exists($required, $fields)) { + throw new RequiredEntityValueMissingException( + "Required field '{$required}' was not supplied as a parameter." + ); + } + } + } +} diff --git a/src/Traits/ValueObjects/HasRuleProcessingTrait.php b/src/Traits/ValueObjects/HasRuleProcessingTrait.php new file mode 100644 index 0000000..73397ff --- /dev/null +++ b/src/Traits/ValueObjects/HasRuleProcessingTrait.php @@ -0,0 +1,55 @@ +ruleList as $method) { + $fields = $this->$method($fields); + } + + return $fields; + } + + /** + * Loads a list of rules and mutator methods, available within the Value Object for processing. + * + * @return void + */ + private function registerAttributeRuleList(): void + { + // Loops through all the class' methods, and loads the necessary ones in + // the corresponding containers. + foreach (\get_class_methods($this) as $method) { + // Loads mutators/setters and rules. + if (\strpos($method, self::$RULEPREFIX) === 0 && \strlen($method) > \strlen(self::$RULEPREFIX)) { + $this->ruleList[] = $method; + } + } + } +} diff --git a/src/Traits/ValueObjects/README.md b/src/Traits/ValueObjects/README.md new file mode 100755 index 0000000..f1558dd --- /dev/null +++ b/src/Traits/ValueObjects/README.md @@ -0,0 +1 @@ +# Value Object's configuration Traits diff --git a/src/ValueObjects/AbstractValueObject.php b/src/ValueObjects/AbstractValueObject.php new file mode 100644 index 0000000..911034d --- /dev/null +++ b/src/ValueObjects/AbstractValueObject.php @@ -0,0 +1,212 @@ +loadInstance($fields); + $this->triggerOnLoad(); + } + + /** + * Loads instance's state, either on instanciation, or de-serialization. + * + * @param array $fields - List of fields to be loaded into the class. + * + * @throws RequiredEntityValueMissingException - If any of the required fields are missing. + * @throws UnexpectedEntityValueException - If some of the supplied fields are invalid. + * @return void + */ + private function loadInstance(array $fields): void + { + // Configures Value Object's instance. + $this->registerUsableFields(); + $this->registerAttributeCastingList(); + $this->registerAttributeRuleList(); + + // Translates supplied fields, into existing ones. + $mapped = $this->translateToMappedFields($fields); + + // Validates and loads supplied data into class. + $mapped = $this->processRules($mapped); + $this->validateRequired($mapped); + $this->castAttributes($mapped); + } + + /** + * Loads a list of usable attributes, that can hold state in the Value Object. + * + * @return void + */ + private function registerUsableFields(): void + { + $this->attributeList = $this->filterSystemControlFields( + \get_object_vars($this) + ); + } + + /** + * Filters system fields from supplied array, and returns it. + * + * @param array $attrs - Array of fields to be filtered. + * @return array + */ + private function filterSystemControlFields(array $attrs): array + { + unset( + $attrs['maps'], + $attrs['guarded'], + $attrs['required'], + $attrs['ruleList'], + $attrs['castList'], + $attrs['attributeList'] + ); + + return $attrs; + } + + /** + * Retrieves a list containing all Value Object's fields. + * + * Returns an associative array containing all the fields from the Value Object. + * Nested Value Objects (records) will be included as a nested associative array. + * Datatype Value Objects will be converted to their primitive representation. + * + * @return array + */ + public function toArray(): array + { + // Collects a list of usable Attributes from the Value Object. + $fields = $this->filterSystemControlFields( + \get_object_vars($this) + ); + + // Converts all objects into primitives. + return $this->convertIntoPrimitiveValues($fields); + } + + /** + * Retrieves a list containing all Value Object's fields. + * + * Returns an associative array only containing fields which belong directly to record. + * Nested Value Objects will not be returned. + * Datatype Value Objects will be converted to their primitive representation. + * + * @return array + */ + public function getAttributes(): array + { + // Collects a list of usable Attributes from the Value Object. + $fields = $this->filterSystemControlFields( + \get_object_vars($this) + ); + + foreach ($fields as $name => $value) { + if ($value instanceof AbstractValueObject || $value instanceof EntityCollection) { + unset($fields[$name]); + } + } + + // Converts all objects into primitives. + return $this->convertIntoPrimitiveValues($fields); + } + + public function __serialize(): array + { + return $this->toArray(); + } + + public function __unserialize(array $data): void + { + $this->loadInstance($data); + } + + /** + * {@inheritDoc} + * + * @link http://www.php.net/manual/en/jsonserializable.jsonserialize.php + * @see \JsonSerializable::jsonSerialize() + */ + public function jsonSerialize(): \stdClass + { + // Collects a list of usable Attributes from the Value Object. + $fields = $this->filterSystemControlFields( + \get_object_vars($this) + ); + + return (object) $this->removeGuardedFields( + $this->convertIntoPrimitiveValues($fields, true) + ); + } + + /** + * This method is called by var_dump() when dumping an object to get the properties that should be shown. + * + * If the method isn't defined on an object, then all public, protected and private properties will be shown. + * + * Note: Might not work with xDebug. + * + * @link https://www.php.net/manual/en/language.oop5.magic.php#object.debuginfo + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } +} diff --git a/src/ValueObjects/README.md b/src/ValueObjects/README.md new file mode 100755 index 0000000..7199687 --- /dev/null +++ b/src/ValueObjects/README.md @@ -0,0 +1,329 @@ +# Value Objects & Entities + +**Value Object** are objects used to transport data **from** and **to** the **Domain**, but also play an important part +in validating data integrity on a record. + +3rd party objects, or _loosely typed_ values should be avoided at all costs. + +**Value Objects** are also used to enforce data consistency within a record, and validate internal rules. + +## Building a new Value Object + +To start building your ValueObjects/Entities with this package, you'll need to create your class extending `AbstractValueObject`. +Afterwards, you should include the corresponding `Traits` for each one of the fields your object will have. + +### Loading data into Value Object + +After you define your ValueObject/Entity structure, you can load data into it by passing an associative array to its Constructor. + +If the attribute is not set in the class through Attribute Traits, it will not be loaded into the object's state. + +```php +$user = new UserEntity([ + 'id' => 123, + 'active' => true, + 'name' => ' John Doe ', +]); + +echo $user->getName(); // Prints ' John Doe ' +echo $user->getName()->trim()->toUpper()->replace(' ', '-'); // Prints 'JOHN-DOE' +echo $user->getName(); // Prints ' John Doe ' again, as Attribute is immutable. +``` + +### Field Traits + +Each Field Trait will have 3 required class members: + +- Attribute (`protected`) - Holds field's state. +- Casting method (`protected`) - Casts primite value to strongly typed object (eg:. `"SomeString"` => `Str::class`). +- Getter (`public`) - Provides a way to access the field's value. + +```php +trait HasEmailTrait +{ + protected ?EmailAddress $email = null; + + protected function castEmail(string $email): void + { + $this->email = EmailAddress::create($email); + } + + public function getEmail(): ?EmailAddress + { + return $this->email; + } +} +``` + +Attributes are usually defined using "_snake-case_", and in order to more easily handling persisted data, usually map directly +to the names of the corresponding fields in the persistence layer. This also helps out, when serializing the record to JSON, +as it "_usually_" uses the same casing. + +Casting methods should be set as `protected`, and have `cast` prefix followed by the name of the existing attribute (_CamelCase_) +without any underscores. Eg: + +- `$email` would have the casting method `protected function castEmail()`. +- `$user_name` would have the casting method `protected function castUserName()`. +- `$date_of_birth` would have the casting method `protected function castDateOfBirth()`. + +Usually, each Attribute and Casting method should be accompanied by a Getter. Getter methods should account for the possibility of +an Attribute not being handed a value while instantiating, therefore, it's good policy to assign it a default one. + +### Using Field's Traits + +You can build whole objects using these `Traits`. + +By default, when adding these `Traits`, your object will be immutable. +If you're creating a mutable Entity, you'll need to define state mutation logic within your class. + +```php +class MyEntity extends AbstractValueObject +{ + use HasPositiveIntegerIDTrait, + HasActiveTrait, + HasNameTrait, + HasEmailTrait, + HasCreatedAtTrait, + HasUpdatedAtTrait; +} +``` + +### Field Mapping & Requirement + +As these ValueObjects/Entities are meant to be platform agnostic, they might be loaded into your application from different sources. +Eg:. Database, Webservice, WebRequests, Events, ... + +Therefore, and because the same information might be represented with slight structural changes, `AbstractValueObject` provides you +with field mapping when loading data into it. Mapping will occour before state is loaded into the object. + +You should define which fields names are mapped to which fields, in your class definition. + +You can also define which fields are required on object's instantiation, by adding them to a `$required` array. +If these fields are not present while loading the class, an Exception will be raised. + +In the following example, the names `full_name`, `fullname`, `fullName` will all be mapped to `name`, and we'll set `name` & `active` +as required fields for the class: + +```php +class MyEntity extends AbstractValueObject +{ + use HasPositiveIntegerIDTrait, + HasActiveTrait, + HasNameTrait; + + protected array $maps = [ + 'full_name' => 'name', + 'fullname' => 'name', + 'fullName' => 'name', + ]; + + protected array $required = [ + 'active', + 'name', + ]; +} +``` + +### Field Rules & onLoad Event Handlers + +You can define rules for the object, which will manipulate the loaded data bafore it is set on the object's state. You can also +add `onLoad` event handlers to manipulate data after class state is loaded, but before the object's instanciation is finalized. + +For rules, define as many `protected` methods as you'd like, with a prefix `rule`, that will take a `$fields` array, +and return it after manipulation. + +For the `onLoad` event handlers, define as many as you'de like with a prefix `onLoad`, with no parameters or returned type. +These handlers will get called, after the object's state is already loaded. + +```php +class UserEntity extends AbstractValueObject +{ + use HasPositiveIntegerIDTrait, + HasNameTrait; + + protected function ruleConcatenateFirstAndLastNames(array $fields): array + { + if (isset($fields['first']) && isset($fields['last'])) { + $fields['name'] = $fields['first'] . ' ' . $fields['last']; + } + + return fields; + } + + protected function onLoadConvertNameToUpperCase(): void + { + $this->name = $this->name->toUpper(); + } +} + +$user = new UserEntity([ + 'id' => 123, + 'first' => 'John', + 'last' => 'Doe', +]); + +echo $user->getName(); // Prints 'JOHN DOE' +``` + +## Accessing & Serializing data + +By default, your ValueObject's/Entity's attributes can be accessed by Getters available in the different `Traits`, but you might +want to retrieve the full record in your application. +`AbstractValueObject` provides you with two methods for this: + +- `getAttributes()` will get you the attributes, for the first level record only. +- `toArray()` will return a full representation of record, including nested ValueObjects/Entities in it (eg:. _Aggregate Roots_). + +You can also easily serialize and unserialize objects that extend `AbstractValueObject`: + +```php +$oldUser = new UserEntity([ + 'id' => 123, + 'name' => 'John Doe', +]); + +$serialized = serialize($oldUser); +$newUser = unserialize($serialized); + +echo $newUser->getName(); // Prints 'John Doe' +``` + +### JSON Serializing + +Apart from easy serializing, classes extending `AbstractValueObject` can also easily be converted to JSON, if you which to output +them, for example, in an API Response. + +On top of this, and because you might want to protect certain fields, you can also define which fields won't be serializable into +JSON, by filling in a `$guarded` internal array. + +```php +class MyEntity extends AbstractValueObject +{ + use HasPositiveIntegerIDTrait, + HasNameTrait, + HasEmailTrait, + HasPasswordTrait, + HasCreatedAtTrait; + + protected array $guarded = [ + 'email', + 'password', + ]; +} + +$user = new UserEntity([ + 'id' => 123, + 'name' => 'John Doe', + 'email' => 'john.doe@domain.tld', + 'password' => 'poiouashfiahsifuahpsdfphapsdfpoasopasyeytnracsyntaynetyaoeya', + 'created_at' => '2022-02-02 12:30:00', +]); + +$json = json_encode($user); +echo $json; // NO EMAIL OR PASSWORD + +/* +{ + "id": 123, + "name": "John Doe", + "created_at": "2022-02-02 12:30:00" +} +*/ +``` + +## Manipulating State + +If you want to keep track of the object's state, you can include the Trait `CanProcessEntityStateTrait` in your class. + +This `Trait` can provide you information, about attributes that have been changed since load. + +```php +class UserEntity extends AbstractValueObject +{ + use HasPositiveIntegerIDTrait, + HasActiveTrait, + HasNameTrait, + CanProcessEntityStateTrait; + + public function activate(): void + { + $this->active = true; + } +} + +$user = new UserEntity([ + 'id' => 123, + 'active' => false, + 'name' => 'John Doe', +]); + +$user->isDirty(); // FALSE +$user->activate(); +$user->isDirty(); // TRUE + +/* +$user->getDirty(); + +OUTPUTS +[ + 'active' => true, +] +*/ +``` + +### Reacting to State changes + +You can react to state changes, by adding a second Trait `CanProcessOnUpdateEventsTrait` to your class. + +Afterwards, and at this point, your class will need to call `$this->triggerOnUpdate();` inside your Mutators/Setters, +in order to trigger all "_onUpdate_" event handlers. This is not done automatically, at this point. + +If you need to react to this event, you can declare as many `protected` methods as you which, with the prefix `onUpdate`, +without any parameters or returned type. + +```php +class UserEntity extends AbstractValueObject +{ + use HasPositiveIntegerIDTrait, + HasActiveTrait, + HasNameTrait, + HasCreatedAtTrait, + CanProcessEntityStateTrait, + CanProcessOnUpdateEventsTrait; + + public function activate(): void + { + $this->active = true; + $this->triggerOnUpdate(); + } + + public function rename(Str $newName): void + { + $this->name = $newName; + $this->triggerOnUpdate(); + } + + protected function onUpdateChangeUpdatedAtField(): void + { + $this->updated_at = DateTime::now(); + } +} + +$user->getUpdatedAt(); // Initially loaded DateTime. +$user->activate(); +$user->rename( + Str::create('John Smith') +); +$user->getUpdatedAt(); // Updated DateTime. +``` + +## Loading Execution Order + +When instantiating a new ValueObject/Entity that extends `AbstractValueObject`, the order of events is as follows: + +- Register instance's structure (_available attributes, casting methods, rule methods, ..._) +- Map supplied fields to instance's attributes (_rename supplied keys using `$maps` as reference_) +- Processes Rules for supplied fields (_all `protected` methods with `rule` prefix_) +- Validates if all required fields were supplied (_compares supplied field's keys with `$required` array_) +- Load/cast supplied data into existing attributes (_loads supplied fields using all `protected` methods with `cast` prefix_) +- Trigger onLoad handlers (_calls all `protected` methods with `onLoad` prefix_) diff --git a/src/Web/EmailAddress.php b/src/Web/EmailAddress.php new file mode 100644 index 0000000..e91fe5f --- /dev/null +++ b/src/Web/EmailAddress.php @@ -0,0 +1,157 @@ +loadFromPrimitive($email); + } + + /** + * Loads supplied $email address string representation into the class. + * + * @param string $email - String representation of the e-mail. + * + * @throws \InvalidArgumentException - If the supplied email address is empty or invalid. + * @return void + */ + protected function loadFromPrimitive(string $email): void + { + // Converts supplied primitive to Str. + $voEmail = Str::create($email)->trim()->toLower(); + + // Validate supplied parameter. + if ($voEmail->getLength() === 0) { + throw NonEmptyStringException::withName('$email'); + } + if (!\filter_var((string) $email, FILTER_VALIDATE_EMAIL)) { + throw InvalidEmailException::withName($email); + } + + // Sanitizes and processes supplied e-mail address. + $parts = \explode('@', (string) $voEmail); + $this->username = Str::create($parts[0]); + + // Processes the right side of the e-mail address. + $domain = \explode('.', $parts[1]); + $this->tld = Str::create(\array_pop($domain)); + $this->domain = Str::create(\implode('.', $domain)); + } + + public function __serialize(): array + { + return [ + 'email'=> (string) $this, + ]; + } + + public function __unserialize(array $data): void + { + $this->loadFromPrimitive( + $data['email'] + ); + } + + /** + * Returns the String representation of the object. + * + * @return string + */ + public function __toString(): string + { + return (string) $this->getAddress(); + } + + /** + * Returns a string representation for the Email address. + * + * @return string + */ + public function getAddress(): Str + { + return Str::create( + \sprintf( + "%s@%s.%s", + (string) $this->username, + (string) $this->domain, + (string) $this->tld + ) + ); + } + + /** + * Return the Username part for the e-mail address. + * + * @return string + */ + public function getUsername(): Str + { + return $this->username; + } + + /** + * Return the Domain part for the e-mail address. + * + * @return string + */ + public function getDomain(): Str + { + return $this->domain; + } + + /** + * Return the Top Level Domain part for the e-mail address. + * + * @return string + */ + public function getTld(): Str + { + return $this->tld; + } +} diff --git a/src/Web/README.md b/src/Web/README.md new file mode 100644 index 0000000..fbb6f00 --- /dev/null +++ b/src/Web/README.md @@ -0,0 +1 @@ +# PHP Web Datatypes diff --git a/tests/AbstractBaseTestCase.php b/tests/AbstractBaseTestCase.php index 9ef22bd..b1fef2c 100644 --- a/tests/AbstractBaseTestCase.php +++ b/tests/AbstractBaseTestCase.php @@ -1,5 +1,8 @@ - * @author Hugo Rafael Azevedo + * @package HraDigital\Datatypes + * @copyright HraDigital\Datatypes * @license MIT - * @since 1.0.0 */ abstract class AbstractBaseTestCase extends TestCase { diff --git a/tests/Unit/Attributes/GeneralTraitsVO.php b/tests/Unit/Attributes/GeneralTraitsVO.php new file mode 100644 index 0000000..feae6ff --- /dev/null +++ b/tests/Unit/Attributes/GeneralTraitsVO.php @@ -0,0 +1,59 @@ +triggerOnUpdate(); + } +} diff --git a/tests/Unit/Attributes/GeneralTraitsVOTest.php b/tests/Unit/Attributes/GeneralTraitsVOTest.php new file mode 100644 index 0000000..e7a2cc1 --- /dev/null +++ b/tests/Unit/Attributes/GeneralTraitsVOTest.php @@ -0,0 +1,223 @@ + 54321, + 'active' => true, + 'is_featured' => true, + 'is_published' => true, + 'alias' => 'some_alias', + 'created_at' => '2022-01-20 12:30:00', + 'deleted_at' => null, + 'email' => 'user@domain.tld', + 'hits' => 1999, + 'name' => 'Name of Record', + 'surname' => 'Surname of Record', + 'ordering' => 2, + 'password' => 'usdhciucyneoyhnoerytos8ryt8svyeero8tyvwo8ryt8wryvutt89weysr8ncseryngngc8yeroiyncweryc8f', + 'seo_title' => 'My SEO Title', + 'seo_description' => 'This is a SEO description for the record', + 'seo_keywords' => 'word1, word2, word3', + 'title' => 'Title for the Record', + 'updated_at' => '2022-02-22 12:30:00', + 'deleted_at' => '2022-02-22 12:30:00', + 'uuid' => '123e4567-e89b-12d3-a456-426614174000', + ]; + + public function testLoadsSuccessfully(): void + { + $object = new GeneralTraitsVO(self::DATA); + + $this->assertFalse($object->isNew()); + $this->assertTrue($object->isActive()); + $this->assertTrue($object->isFeatured()); + $this->assertTrue($object->isPublished()); + $this->assertTrue($object->isDeleted()); + + $this->assertEquals(self::DATA['id'], (string) $object->getId()); + $this->assertEquals(self::DATA['uuid'], (string) $object->getUuid()); + $this->assertEquals(self::DATA['alias'], (string) $object->getAlias()); + $this->assertEquals(self::DATA['email'], (string) $object->getEmail()); + $this->assertEquals(self::DATA['name'], (string) $object->getName()); + $this->assertEquals(self::DATA['surname'], (string) $object->getSurname()); + $this->assertEquals(self::DATA['title'], (string) $object->getTitle()); + $this->assertEquals(self::DATA['password'], (string) $object->getPassword()); + + $this->assertEquals(self::DATA['hits'], (string) $object->getHits()); + $this->assertEquals(self::DATA['ordering'], (string) $object->getOrdering()); + + $this->assertEquals(self::DATA['seo_title'], (string) $object->getSeoTitle()); + $this->assertEquals(self::DATA['seo_description'], (string) $object->getSeoDescription()); + $this->assertEquals(self::DATA['seo_keywords'], (string) $object->getSeoKeywords()); + + $this->assertEquals(self::DATA['created_at'], $object->getCreatedAt()->toDatetimeString()); + $this->assertEquals(self::DATA['updated_at'], $object->getUpdatedAt()->toDatetimeString()); + $this->assertEquals(self::DATA['updated_at'], $object->getDeletedAt()->toDatetimeString()); + } + + public function testLoadsDefaultsSuccessfully(): void + { + $data = self::DATA; + unset( + $data['id'], + $data['ordering'], + $data['hits'], + $data['is_featured'], + $data['is_published'], + $data['active'], + $data['updated_at'], + $data['deleted_at'], + $data['email'] + ); + + $object = new GeneralTraitsVO($data); + + $this->assertTrue($object->isNew()); + $this->assertFalse($object->isActive()); + $this->assertFalse($object->isFeatured()); + $this->assertFalse($object->isPublished()); + + $this->assertNull($object->getEmail()); + + $this->assertEquals(0, $object->getHits()); + $this->assertEquals(0, $object->getOrdering()); + + $this->assertFalse($object->isDeleted()); + $this->assertNull($object->getDeletedAt()); + $this->assertNull($object->getUpdatedAt()); + } + + public function testBreaksIfIdIsNegative(): void + { + $this->expectException(PositiveIntegerException::class); + + $data = self::DATA; + $data['id'] = -1; + + new GeneralTraitsVO($data); + } + + public function testBreaksIfTitleIsEmpty(): void + { + $this->expectException(NonEmptyStringException::class); + + $data = self::DATA; + $data['title'] = ''; + + new GeneralTraitsVO($data); + } + + public function testBreaksIfNameIsEmpty(): void + { + $this->expectException(NonEmptyStringException::class); + + $data = self::DATA; + $data['name'] = ''; + + new GeneralTraitsVO($data); + } + + public function testBreaksIfAliasIsEmpty(): void + { + $this->expectException(NonEmptyStringException::class); + + $data = self::DATA; + $data['alias'] = ''; + + new GeneralTraitsVO($data); + } + + public function testBreaksIfHitsIsNegative(): void + { + $this->expectException(NonNegativeNumberException::class); + + $data = self::DATA; + $data['hits'] = -1; + + new GeneralTraitsVO($data); + } + + public function testBreaksIfOrderingIsNegative(): void + { + $this->expectException(NonNegativeNumberException::class); + + $data = self::DATA; + $data['ordering'] = -1; + + new GeneralTraitsVO($data); + } + + public function testBreaksIfSeoTitleTooLong(): void + { + $this->expectException(InvalidStringLengthException::class); + + $data = self::DATA; + $tenCharacterString = 'sodhgfsodh'; + $data['seo_title'] = str_repeat($tenCharacterString, 7) . 'a'; + + new GeneralTraitsVO($data); + } + + public function testBreaksIfSeoDescriptionTooLong(): void + { + $this->expectException(InvalidStringLengthException::class); + + $data = self::DATA; + $tenCharacterString = 'sodhgfsodh'; + $data['seo_description'] = str_repeat($tenCharacterString, 16) . 'a'; + + new GeneralTraitsVO($data); + } + + public function testBreaksIfSeoKeywordsTooLong(): void + { + $this->expectException(InvalidStringLengthException::class); + + $data = self::DATA; + $tenCharacterString = 'sodhgfsodh'; + $data['seo_keywords'] = str_repeat($tenCharacterString, 25) . 'hgfdsa'; + + new GeneralTraitsVO($data); + } + + public function testLoadsNullValueIfSeoFieldEmptyString(): void + { + $data = self::DATA; + $data['seo_title'] = ' '; + $data['seo_description'] = ' '; + $data['seo_keywords'] = ' '; + + $object = new GeneralTraitsVO($data); + + $this->assertNull($object->getSeoTitle()); + $this->assertNull($object->getSeoDescription()); + $this->assertNull($object->getSeoKeywords()); + } + + public function testUpdateDateTimeOnUpdateTrigger(): void + { + $object = new GeneralTraitsVO(self::DATA); + + $this->assertEquals(self::DATA['updated_at'], $object->getUpdatedAt()->toDatetimeString()); + $object->simulateUpdate(); + $this->assertNotEquals(self::DATA['updated_at'], $object->getUpdatedAt()->toDatetimeString()); + } +} diff --git a/tests/Unit/Attributes/LocationTraitsVO.php b/tests/Unit/Attributes/LocationTraitsVO.php new file mode 100644 index 0000000..2bc9ec8 --- /dev/null +++ b/tests/Unit/Attributes/LocationTraitsVO.php @@ -0,0 +1,44 @@ + 'Main Street', + 'city' => 'Gotham', + 'country_code' => '123', + 'country' => 'USA', + 'district' => 'Mid-West', + 'latitude' => 14.12345, + 'longitude' => 12.654231, + 'parish' => 'SoHo', + 'postal_code' => 'PS13456', + 'street_additional' => 'Wayne Lane', + 'street_no' => '456', + 'street' => 'Main Street', + ]; + + public function testLoadsSuccessfully(): void + { + $object = new LocationTraitsVO(self::DATA); + + $this->assertEquals(self::DATA['address'], (string) $object->getAddress()); + $this->assertEquals(self::DATA['city'], (string) $object->getCity()); + $this->assertEquals(self::DATA['country_code'], (string) $object->getCountryCode()); + $this->assertEquals(self::DATA['country'], (string) $object->getCountry()); + $this->assertEquals(self::DATA['district'], (string) $object->getDistrict()); + $this->assertEquals(self::DATA['latitude'], (string) $object->getLatitude()); + $this->assertEquals(self::DATA['longitude'], (string) $object->getLongitude()); + $this->assertEquals(self::DATA['parish'], (string) $object->getParish()); + $this->assertEquals(self::DATA['postal_code'], (string) $object->getPostalCode()); + $this->assertEquals(self::DATA['street_additional'], (string) $object->getStreetAdditional()); + $this->assertEquals(self::DATA['street_no'], (string) $object->getStreetNumber()); + $this->assertEquals(self::DATA['street'], (string) $object->getStreet()); + } + + public function testBreaksWithEmptyCity(): void + { + $data = self::DATA; + $data['city'] = ' '; + + $this->expectException(NonEmptyStringException::class); + + new LocationTraitsVO($data); + } + + public function testBreaksWithEmptyDistrict(): void + { + $data = self::DATA; + $data['district'] = ' '; + + $this->expectException(NonEmptyStringException::class); + + new LocationTraitsVO($data); + } + + public function testBreaksWithEmptyParish(): void + { + $data = self::DATA; + $data['parish'] = ' '; + + $this->expectException(NonEmptyStringException::class); + + new LocationTraitsVO($data); + } +} diff --git a/tests/Unit/Attributes/PersonalTraitsVO.php b/tests/Unit/Attributes/PersonalTraitsVO.php new file mode 100644 index 0000000..b798c4c --- /dev/null +++ b/tests/Unit/Attributes/PersonalTraitsVO.php @@ -0,0 +1,30 @@ + 'Ukraine', + 'dob' => '1980-02-05 00:00:00', + 'gender' => 'Male', + 'nationality' => 'Ukrainian', + 'photo' => '/path/to/some/photo.jpg', + ]; + + public function testLoadsSuccessfully(): void + { + $object = new PersonalTraitsVO(self::DATA); + + $this->assertEquals(self::DATA['country_of_birth'], (string) $object->getCountryOfBirth()); + $this->assertEquals(self::DATA['dob'], (string) $object->getDateOfBirth()); + $this->assertEquals(self::DATA['gender'], (string) $object->getGender()); + $this->assertEquals(self::DATA['nationality'], (string) $object->getNationality()); + $this->assertEquals(self::DATA['photo'], (string) $object->getPhoto()); + $this->assertTrue($object->hasPhoto()); + } + + public function testBreaksWithEmptyCoutryOfBirth(): void + { + $data = self::DATA; + $data['country_of_birth'] = ' '; + + $this->expectException(NonEmptyStringException::class); + + new PersonalTraitsVO($data); + } + + public function testBreaksWithEmptyNationality(): void + { + $data = self::DATA; + $data['nationality'] = ' '; + + $this->expectException(NonEmptyStringException::class); + + new PersonalTraitsVO($data); + } + + public function testBreaksWithEmptyPhoto(): void + { + $data = self::DATA; + $data['photo'] = ' '; + + $this->expectException(NonEmptyStringException::class); + + new PersonalTraitsVO($data); + } +} diff --git a/tests/Unit/Attributes/ProfessionalTraitsVO.php b/tests/Unit/Attributes/ProfessionalTraitsVO.php new file mode 100644 index 0000000..23b06be --- /dev/null +++ b/tests/Unit/Attributes/ProfessionalTraitsVO.php @@ -0,0 +1,22 @@ + 'Information Technology', + 'occupation' => 'Developer', + ]; + + public function testLoadsSuccessfully(): void + { + $object = new ProfessionalTraitsVO(self::DATA); + + $this->assertEquals(self::DATA['industry'], (string) $object->getIndustry()); + $this->assertEquals(self::DATA['occupation'], (string) $object->getOccupation()); + $this->assertTrue($object->hasOccupation()); + } + + public function testBreaksWithEmptyIndustry(): void + { + $data = self::DATA; + $data['industry'] = ' '; + + $this->expectException(NonEmptyStringException::class); + + new ProfessionalTraitsVO($data); + } + + public function testBreaksWithEmptyOccupation(): void + { + $data = self::DATA; + $data['occupation'] = ' '; + + $this->expectException(NonEmptyStringException::class); + + new ProfessionalTraitsVO($data); + } +} diff --git a/tests/Unit/Attributes/SocialMediaTraitsVO.php b/tests/Unit/Attributes/SocialMediaTraitsVO.php new file mode 100644 index 0000000..40eeeb2 --- /dev/null +++ b/tests/Unit/Attributes/SocialMediaTraitsVO.php @@ -0,0 +1,26 @@ + 'https://facebook.com/aoihfoiahf', + 'instagram' => 'https://instagram.com/aoihfoiahf', + 'linkedin' => 'https://linkedin.com/aoihfoiahf', + 'twitter' => 'https://twitter.com/aoihfoiahf', + ]; + + public function testLoadsSuccessfully(): void + { + $object = new SocialMediaTraitsVO(self::DATA); + + $this->assertEquals(self::DATA['facebook'], (string) $object->getFacebookUrl()); + $this->assertEquals(self::DATA['instagram'], (string) $object->getInstagramUrl()); + $this->assertEquals(self::DATA['linkedin'], (string) $object->getLinkedinUrl()); + $this->assertEquals(self::DATA['twitter'], (string) $object->getTwitterUrl()); + $this->assertTrue($object->hasFacebookProfileUrl()); + $this->assertTrue($object->hasInstagramProfileUrl()); + $this->assertTrue($object->hasLinkedinProfileUrl()); + $this->assertTrue($object->hasTwitterProfileUrl()); + } + + public function testLoadsSuccessfullyWithNoValues(): void + { + $object = new SocialMediaTraitsVO([]); + + $this->assertNull($object->getFacebookUrl()); + $this->assertNull($object->getInstagramUrl()); + $this->assertNull($object->getLinkedinUrl()); + $this->assertNull($object->getTwitterUrl()); + $this->assertFalse($object->hasFacebookProfileUrl()); + $this->assertFalse($object->hasInstagramProfileUrl()); + $this->assertFalse($object->hasLinkedinProfileUrl()); + $this->assertFalse($object->hasTwitterProfileUrl()); + } +} diff --git a/tests/Unit/Attributes/TimezoneTraitsVO.php b/tests/Unit/Attributes/TimezoneTraitsVO.php new file mode 100644 index 0000000..f428af2 --- /dev/null +++ b/tests/Unit/Attributes/TimezoneTraitsVO.php @@ -0,0 +1,20 @@ + 123456789, + ]; + + public function testLoadsSuccessfully(): void + { + $object = new TimezoneTraitsVO(self::DATA); + + $this->assertEquals(self::DATA['timestamp'], (string) $object->getTimestamp()); + } +} diff --git a/tests/Unit/Collections/Associative/StoreTest.php b/tests/Unit/Collections/Associative/StoreTest.php new file mode 100644 index 0000000..f9b3f7c --- /dev/null +++ b/tests/Unit/Collections/Associative/StoreTest.php @@ -0,0 +1,134 @@ +assertEquals(0, $store->count()); + } + + public function testCanSetAndGetValues(): void + { + $key = 'testKey'; + $value = 'TestValue'; + + $store = new Store(); + + // Tries aditing a value before setting it. + $editedBefore = $store->edit($key, 'New Value'); + $this->assertEquals(0, $store->count()); + $this->assertCount(0, $store->jsonSerialize()); + $this->assertFalse($editedBefore); + + // Sets value directly. + $store->set($key, $value); + $this->assertTrue($store->has($key)); + $this->assertEquals($value, $store->get($key)); + $this->assertEquals(1, $store->count()); + + // Now edits existing value. + $edited = $store->edit($key, 'New Value'); + $this->assertNotEquals($value, $store->get($key)); + $this->assertTrue($edited); + + // Unsuccessfully tries adding a value as a new. + $addedAfter = $store->add($key, 'Tries adding new Value to existing key'); + $this->assertFalse($addedAfter); + $this->assertEquals(1, $store->count()); + + // Forces/overrides setting value. + $store->set($key, $value); + $this->assertEquals($value, $store->get($key)); + $this->assertEquals(1, $store->count()); + $this->assertCount(1, $store->jsonSerialize()); + } + + public function testAddingValueAndThenDeletingIt(): void + { + $key = 'testKey'; + $value = 'TestValue'; + + $store = new Store('Context'); + $deletedBefore = $store->delete($key); + $this->assertFalse($deletedBefore); + $this->assertEquals(0, $store->count()); + $this->assertEquals('Unexisting', $store->get($key, 'Unexisting')); + + // First adds a new value to the Store. + $added = $store->add($key, $value); + $this->assertTrue($added); + $this->assertTrue($store->has($key)); + $this->assertEquals($value, $store->get($key, 'Unexisting')); + $this->assertEquals(1, $store->count()); + + // Now deletes the same value. + $deletedAfter = $store->delete($key); + $this->assertTrue($deletedAfter); + $this->assertFalse($store->has($key)); + $this->assertEquals(0, $store->count()); + } + + public function testBreaksIfContextIsEmpty(): void + { + $this->expectException(NonEmptyStringException::class); + + new Store(''); + } + + public function testBreaksIfTryingToSetEmptyKey(): void + { + $this->expectException(NonEmptyStringException::class); + + $store = new Store('Context'); + $store->set('', ''); + } + + public function testBreaksIfTryingToGetEmptyKey(): void + { + $this->expectException(NonEmptyStringException::class); + + $store = new Store('Context'); + $store->get(''); + } + + public function testBreaksIfTryingToAddEmptyKey(): void + { + $this->expectException(NonEmptyStringException::class); + + $store = new Store('Context'); + $store->add('', ''); + } + + public function testBreaksIfTryingToEditEmptyKey(): void + { + $this->expectException(NonEmptyStringException::class); + + $store = new Store('Context'); + $store->edit('', ''); + } + + public function testBreaksIfTryingToDeleteEmptyKey(): void + { + $this->expectException(NonEmptyStringException::class); + + $store = new Store('Context'); + $store->delete(''); + } +} diff --git a/tests/Unit/Collections/Linear/EntityCollectionTest.php b/tests/Unit/Collections/Linear/EntityCollectionTest.php new file mode 100644 index 0000000..74af85a --- /dev/null +++ b/tests/Unit/Collections/Linear/EntityCollectionTest.php @@ -0,0 +1,160 @@ +assertEquals(0, $collection->count()); + + $data = TestingValueObject::DATA; + $data['id'] = 1; + $collection->add( + new TestingValueObject($data) + ); + $data['id'] = 2; + $collection->add( + new TestingValueObject($data) + ); + $data['id'] = 3; + $collection->add( + new TestingValueObject($data) + ); + + $array = $collection->all(); + $json = $collection->jsonSerialize(); + $ids = $collection->ids(); + + $this->assertEquals(3, $collection->count()); + + $this->assertCount(3, $array); + $this->assertCount(3, $json); + $this->assertCount(3, $ids); + + $this->assertContains(1, $ids); + $this->assertContains(2, $ids); + $this->assertContains(3, $ids); + + $entity = $collection->get(2); + $this->assertEquals(2, $entity->{'getId'}()); + } + + public function testCanManageEntriesInCollection(): void + { + $collection = new EntityCollection(); + $this->assertEquals(0, $collection->count()); + $this->assertFalse($collection->valid()); + $this->assertNull($collection->current()); + $this->assertNull($collection->key()); + + $data = TestingValueObject::DATA; + $data['id'] = 1; + $collection->add( + new TestingValueObject($data) + ); + $data['id'] = 2; + $collection->add( + new TestingValueObject($data) + ); + $data['id'] = 3; + $collection->add( + new TestingValueObject($data) + ); + + $this->assertEquals(3, $collection->count()); + $this->assertTrue($collection->valid()); + $this->assertTrue($collection->has(1)); + $this->assertTrue($collection->has(2)); + $this->assertTrue($collection->has(3)); + $this->assertNotNull($collection->current()); + + $this->assertEquals(1, $collection->key()); + $collection->next(); + $collection->next(); + $this->assertEquals(3, $collection->key()); + + $collection->remove(2); + $collection->rewind(); + $this->assertEquals(1, $collection->key()); + $this->assertEquals(2, $collection->count()); + $this->assertCount(2, $collection->ids()); + $this->assertCount(2, $collection->all()); + $this->assertTrue($collection->has(1)); + $this->assertFalse($collection->has(2)); + $this->assertTrue($collection->has(3)); + + $collection->clear(); + $this->assertEquals(0, $collection->count()); + } + + public function testBreaksIfCheckingHasNonPositiveId(): void + { + $this->expectException(PositiveIntegerException::class); + + $collection = new EntityCollection(); + $collection->has(0); + } + + public function testBreaksIfCheckingGetNonPositiveId(): void + { + $this->expectException(PositiveIntegerException::class); + + $collection = new EntityCollection(); + $collection->get(0); + } + + public function testBreaksIfCheckingGetUnexistingId(): void + { + $this->expectException(ParameterOutOfRangeException::class); + + $collection = new EntityCollection(); + $collection->get(1); + } + + public function testBreaksIfAttemptToAddTwoObjectsWithSameId(): void + { + $this->expectException(DuplicatedEntryException::class); + + $collection = new EntityCollection(); + $collection->add( + new TestingValueObject(TestingValueObject::DATA) + ); + $collection->add( + new TestingValueObject(TestingValueObject::DATA) + ); + } + + public function testBreaksIfAttemptsRemovingNonPositiveId(): void + { + $this->expectException(PositiveIntegerException::class); + + $collection = new EntityCollection(); + $collection->remove(0); + } + + public function testBreaksIfAttemptsRemovingUnexistingId(): void + { + $this->expectException(ParameterOutOfRangeException::class); + + $collection = new EntityCollection(); + $collection->remove(1); + } +} diff --git a/tests/Unit/Collections/Linear/QueueTest.php b/tests/Unit/Collections/Linear/QueueTest.php new file mode 100644 index 0000000..e31bc43 --- /dev/null +++ b/tests/Unit/Collections/Linear/QueueTest.php @@ -0,0 +1,126 @@ +clone(); + + $this->assertEquals(0, $queue->count()); + $this->assertTrue($queue->isEmpty()); + + $this->assertEquals(0, $newQueue->count()); + $this->assertTrue($newQueue->isEmpty()); + + $this->assertFalse($queue === $newQueue); + $this->assertNull($queue->peek()); + $this->assertNull($queue->pop()); + $this->assertNull($queue->getCapacity()); + $this->assertFalse($queue->hasMaxCapacity()); + } + + public function testCanPushPeekAndPopElements(): void + { + $element1 = 'element1'; + $element2 = 'element2'; + $element3 = 'element3'; + + $queue = new Queue([ + $element1, + $element2, + ]); + + $this->assertEquals(2, $queue->count()); + $this->assertEquals($element1, (string) $queue->peek()); + $this->assertFalse($queue->isEmpty()); + + $queue->push($element3); + $this->assertEquals(3, $queue->count()); + $this->assertEquals($element1, (string) $queue->peek()); + + $popElement1 = $queue->pop(); + $this->assertEquals($element1, (string) $popElement1); + $popElement2 = $queue->pop(); + $this->assertEquals($element2, (string) $popElement2); + $this->assertEquals(1, $queue->count()); + $this->assertCount(1, $queue->jsonSerialize()); + } + + public function testBreaksIfEmptyStringIsPushed(): void + { + $this->expectException(NonEmptyStringException::class); + + $queue = new Queue(); + $queue->push(''); + } + + public function testCanClearQueue(): void + { + $queue = new Queue([ + 'element1', + 'element2', + 'element3', + ]); + + $this->assertEquals(3, $queue->count()); + $queue->clear(); + $this->assertEquals(0, $queue->count()); + } + + public function testAllocatedCapacityBehavior(): void + { + $queue = new Queue(); + $this->assertEquals(0, $queue->count()); + $this->assertNull($queue->getCapacity()); + $this->assertFalse($queue->hasMaxCapacity()); + + $queue->allocateCapacity(2); + $this->assertTrue($queue->hasMaxCapacity()); + $this->assertEquals(0, $queue->count()); + $this->assertEquals(2, $queue->getCapacity()); + + $this->expectException(ParameterOutOfRangeException::class); + + $queue->push('element1'); + $queue->push('element2'); + $queue->push('element3'); + } + + public function testBreaksIfAllocatedCapacityIsNotPositive(): void + { + $this->expectException(PositiveIntegerException::class); + + $queue = new Queue(); + $queue->allocateCapacity(-1); + } + + public function testBreaksIfAllocatedCapacityIsLessThenCount(): void + { + $this->expectException(ParameterOutOfRangeException::class); + + $queue = new Queue(); + $queue->push('element1'); + $queue->push('element2'); + $queue->push('element3'); + + $queue->allocateCapacity(2); + } +} diff --git a/tests/Unit/Collections/Linear/StackTest.php b/tests/Unit/Collections/Linear/StackTest.php new file mode 100644 index 0000000..8d939dd --- /dev/null +++ b/tests/Unit/Collections/Linear/StackTest.php @@ -0,0 +1,126 @@ +clone(); + + $this->assertEquals(0, $stack->count()); + $this->assertTrue($stack->isEmpty()); + + $this->assertEquals(0, $newStack->count()); + $this->assertTrue($newStack->isEmpty()); + + $this->assertFalse($stack === $newStack); + $this->assertNull($stack->peek()); + $this->assertNull($stack->pop()); + $this->assertNull($stack->getCapacity()); + $this->assertFalse($stack->hasMaxCapacity()); + } + + public function testCanPushPeekAndPopElements(): void + { + $element1 = 'element1'; + $element2 = 'element2'; + $element3 = 'element3'; + + $stack = new Stack([ + $element1, + $element2, + ]); + + $this->assertEquals(2, $stack->count()); + $this->assertEquals($element2, (string) $stack->peek()); + $this->assertFalse($stack->isEmpty()); + + $stack->push($element3); + $this->assertEquals(3, $stack->count()); + $this->assertEquals($element3, (string) $stack->peek()); + + $popElement3 = $stack->pop(); + $this->assertEquals($element3, (string) $popElement3); + $popElement2 = $stack->pop(); + $this->assertEquals($element2, (string) $popElement2); + $this->assertEquals(1, $stack->count()); + $this->assertCount(1, $stack->jsonSerialize()); + } + + public function testBreaksIfEmptyStringIsPushed(): void + { + $this->expectException(NonEmptyStringException::class); + + $stack = new Stack(); + $stack->push(''); + } + + public function testCanClearStack(): void + { + $stack = new Stack([ + 'element1', + 'element2', + 'element3', + ]); + + $this->assertEquals(3, $stack->count()); + $stack->clear(); + $this->assertEquals(0, $stack->count()); + } + + public function testAllocatedCapacityBehavior(): void + { + $stack = new Stack(); + $this->assertEquals(0, $stack->count()); + $this->assertNull($stack->getCapacity()); + $this->assertFalse($stack->hasMaxCapacity()); + + $stack->allocateCapacity(2); + $this->assertTrue($stack->hasMaxCapacity()); + $this->assertEquals(0, $stack->count()); + $this->assertEquals(2, $stack->getCapacity()); + + $this->expectException(ParameterOutOfRangeException::class); + + $stack->push('element1'); + $stack->push('element2'); + $stack->push('element3'); + } + + public function testBreaksIfAllocatedCapacityIsNotPositive(): void + { + $this->expectException(PositiveIntegerException::class); + + $queue = new Stack(); + $queue->allocateCapacity(-1); + } + + public function testBreaksIfAllocatedCapacityIsLessThenCount(): void + { + $this->expectException(ParameterOutOfRangeException::class); + + $queue = new Stack(); + $queue->push('element1'); + $queue->push('element2'); + $queue->push('element3'); + + $queue->allocateCapacity(2); + } +} diff --git a/tests/Unit/Datetime/DateIntervalTest.php b/tests/Unit/Datetime/DateIntervalTest.php new file mode 100644 index 0000000..03a264a --- /dev/null +++ b/tests/Unit/Datetime/DateIntervalTest.php @@ -0,0 +1,212 @@ +assertEquals(6, $di->getYears()); + $this->assertEquals(0, $di->getMonths()); + $this->assertEquals(0, $di->getDays()); + $this->assertEquals(0, $di->getHours()); + $this->assertEquals(0, $di->getMinutes()); + $this->assertEquals(0, $di->getSeconds()); + $this->assertFalse($di->isNegative()); + + $di = DateInterval::fromYears(-6); + + $this->assertEquals(-6, $di->getYears()); + $this->assertEquals(0, $di->getMonths()); + $this->assertEquals(0, $di->getDays()); + $this->assertEquals(0, $di->getHours()); + $this->assertEquals(0, $di->getMinutes()); + $this->assertEquals(0, $di->getSeconds()); + $this->assertTrue($di->isNegative()); + } + + public function testCanLoadSuccessfullyFromMonths(): void + { + $di = DateInterval::fromMonths(5); + + $this->assertEquals(0, $di->getYears()); + $this->assertEquals(5, $di->getMonths()); + $this->assertEquals(0, $di->getDays()); + $this->assertEquals(0, $di->getHours()); + $this->assertEquals(0, $di->getMinutes()); + $this->assertEquals(0, $di->getSeconds()); + $this->assertFalse($di->isNegative()); + + $di = DateInterval::fromMonths(-5); + + $this->assertEquals(0, $di->getYears()); + $this->assertEquals(-5, $di->getMonths()); + $this->assertEquals(0, $di->getDays()); + $this->assertEquals(0, $di->getHours()); + $this->assertEquals(0, $di->getMinutes()); + $this->assertEquals(0, $di->getSeconds()); + $this->assertTrue($di->isNegative()); + } + + public function testCanLoadSuccessfullyFromDays(): void + { + $di = DateInterval::fromDays(4); + + $this->assertEquals(0, $di->getYears()); + $this->assertEquals(0, $di->getMonths()); + $this->assertEquals(4, $di->getDays()); + $this->assertEquals(0, $di->getHours()); + $this->assertEquals(0, $di->getMinutes()); + $this->assertEquals(0, $di->getSeconds()); + $this->assertFalse($di->isNegative()); + + $di = DateInterval::fromDays(-4); + + $this->assertEquals(0, $di->getYears()); + $this->assertEquals(0, $di->getMonths()); + $this->assertEquals(-4, $di->getDays()); + $this->assertEquals(0, $di->getHours()); + $this->assertEquals(0, $di->getMinutes()); + $this->assertEquals(0, $di->getSeconds()); + $this->assertTrue($di->isNegative()); + } + + public function testCanLoadSuccessfullyFromHours(): void + { + $di = DateInterval::fromHours(3); + + $this->assertEquals(0, $di->getYears()); + $this->assertEquals(0, $di->getMonths()); + $this->assertEquals(0, $di->getDays()); + $this->assertEquals(3, $di->getHours()); + $this->assertEquals(0, $di->getMinutes()); + $this->assertEquals(0, $di->getSeconds()); + $this->assertFalse($di->isNegative()); + + $di = DateInterval::fromHours(-3); + + $this->assertEquals(0, $di->getYears()); + $this->assertEquals(0, $di->getMonths()); + $this->assertEquals(0, $di->getDays()); + $this->assertEquals(-3, $di->getHours()); + $this->assertEquals(0, $di->getMinutes()); + $this->assertEquals(0, $di->getSeconds()); + $this->assertTrue($di->isNegative()); + } + + public function testCanLoadSuccessfullyFromMinutes(): void + { + $di = DateInterval::fromMinutes(2); + + $this->assertEquals(0, $di->getYears()); + $this->assertEquals(0, $di->getMonths()); + $this->assertEquals(0, $di->getDays()); + $this->assertEquals(0, $di->getHours()); + $this->assertEquals(2, $di->getMinutes()); + $this->assertEquals(0, $di->getSeconds()); + $this->assertFalse($di->isNegative()); + + $di = DateInterval::fromMinutes(-2); + + $this->assertEquals(0, $di->getYears()); + $this->assertEquals(0, $di->getMonths()); + $this->assertEquals(0, $di->getDays()); + $this->assertEquals(0, $di->getHours()); + $this->assertEquals(-2, $di->getMinutes()); + $this->assertEquals(0, $di->getSeconds()); + $this->assertTrue($di->isNegative()); + } + + public function testCanLoadSuccessfullyFromSeconds(): void + { + $di = DateInterval::fromSeconds(1); + + $this->assertEquals(0, $di->getYears()); + $this->assertEquals(0, $di->getMonths()); + $this->assertEquals(0, $di->getDays()); + $this->assertEquals(0, $di->getHours()); + $this->assertEquals(0, $di->getMinutes()); + $this->assertEquals(1, $di->getSeconds()); + $this->assertFalse($di->isNegative()); + + $di = DateInterval::fromSeconds(-1); + + $this->assertEquals(0, $di->getYears()); + $this->assertEquals(0, $di->getMonths()); + $this->assertEquals(0, $di->getDays()); + $this->assertEquals(0, $di->getHours()); + $this->assertEquals(0, $di->getMinutes()); + $this->assertEquals(-1, $di->getSeconds()); + $this->assertTrue($di->isNegative()); + } + + public function testCanLoadSuccessfullyFromDuration(): void + { + $strFormat = '%r%Yy %Mm %Dd %H:%I:%S'; + + $di = DateInterval::fromDuration(Str::create("P3Y6M4DT12H30M5S")); + + $this->assertEquals(3, $di->getYears()); + $this->assertEquals(6, $di->getMonths()); + $this->assertEquals(4, $di->getDays()); + $this->assertEquals(12, $di->getHours()); + $this->assertEquals(30, $di->getMinutes()); + $this->assertEquals(5, $di->getSeconds()); + $this->assertFalse($di->isNegative()); + $this->assertEquals((string) $di, $di->toDatetimeString()); + $this->assertEquals("03y 06m 04d 12:30:05", $di->toDatetimeString()); + $this->assertEquals("03y 06m 04d 12:30:05", $di->toFormat(Str::create($strFormat))); + $this->assertEquals("03y 06m 04d 12:30:05", $di->format($strFormat)); + + $di = DateInterval::fromDuration(Str::create("P3Y6M4DT12H30M5S"), true); + + $this->assertEquals(-3, $di->getYears()); + $this->assertEquals(-6, $di->getMonths()); + $this->assertEquals(-4, $di->getDays()); + $this->assertEquals(-12, $di->getHours()); + $this->assertEquals(-30, $di->getMinutes()); + $this->assertEquals(-5, $di->getSeconds()); + $this->assertTrue($di->isNegative()); + $this->assertEquals((string) $di, $di->toDatetimeString()); + $this->assertEquals("-03y 06m 04d 12:30:05", $di->toDatetimeString()); + $this->assertEquals("-03y 06m 04d 12:30:05", $di->toFormat(Str::create($strFormat))); + $this->assertEquals("-03y 06m 04d 12:30:05", $di->format($strFormat)); + $this->assertEquals("-03y 06m 04d 12:30:05", $di->format($strFormat)); + } + + public function testCanLoadSuccessfullyFromDateString(): void + { + $di = DateInterval::createFromDateString("2 year + 3 day"); + + $this->assertEquals(2, $di->getYears()); + $this->assertEquals(0, $di->getMonths()); + $this->assertEquals(3, $di->getDays()); + $this->assertEquals(0, $di->getHours()); + $this->assertEquals(0, $di->getMinutes()); + $this->assertEquals(0, $di->getSeconds()); + $this->assertFalse($di->isNegative()); + + $di = DateInterval::createFromDateString("1 day + 12 hours"); + + $this->assertEquals(0, $di->getYears()); + $this->assertEquals(0, $di->getMonths()); + $this->assertEquals(1, $di->getDays()); + $this->assertEquals(12, $di->getHours()); + $this->assertEquals(0, $di->getMinutes()); + $this->assertEquals(0, $di->getSeconds()); + $this->assertFalse($di->isNegative()); + } +} diff --git a/tests/Unit/Datetime/DatetimeTest.php b/tests/Unit/Datetime/DatetimeTest.php new file mode 100644 index 0000000..1589fb2 --- /dev/null +++ b/tests/Unit/Datetime/DatetimeTest.php @@ -0,0 +1,233 @@ +assertInstanceOf(Datetime::class, $dt); + $this->assertEquals($dt->getYear(), 2021); + $this->assertEquals($dt->getMonth(), 5); + $this->assertEquals($dt->getDay(), 6); + $this->assertEquals($dt->getHour(), 10); + $this->assertEquals($dt->getMinute(), 11); + $this->assertEquals($dt->getSecond(), 12); + $this->assertEquals((string) $dt, self::DATETIME); + $this->assertEquals($dt->jsonSerialize(), self::DATETIME); + } + + public function testCanAddInterval(): void + { + $dt = Datetime::fromString(self::DATETIME); + $interval = new \DateInterval('P1Y'); + + $result = $dt->add($interval); + + $this->assertInstanceOf(Datetime::class, $result); + $this->assertEquals($result->getYear(), 2022); + $this->assertFalse($dt === $result); + } + + private function assertOutputInFormat(string $datetime, string $format, Str $expected): void + { + $native = new \Datetime(self::DATETIME); + $result = $native->format($format); + + $this->assertEquals( + $result, + (string) $expected + ); + } + + public function testCanOutputInFormatATOM(): void + { + $this->assertOutputInFormat( + self::DATETIME, + \Datetime::ATOM, + Datetime::fromString(self::DATETIME)->toATOM() + ); + } + + public function testCanOutputInFormatCOOKIE(): void + { + $this->assertOutputInFormat( + self::DATETIME, + \Datetime::COOKIE, + Datetime::fromString(self::DATETIME)->toCookie() + ); + } + + public function testCanOutputInFormatISO8601(): void + { + $this->assertOutputInFormat( + self::DATETIME, + \Datetime::ISO8601, + Datetime::fromString(self::DATETIME)->toISO8601() + ); + } + + public function testCanOutputInFormatRFC822(): void + { + $this->assertOutputInFormat( + self::DATETIME, + \Datetime::RFC822, + Datetime::fromString(self::DATETIME)->toRFC822() + ); + } + + public function testCanOutputInFormatRFC850(): void + { + $this->assertOutputInFormat( + self::DATETIME, + \Datetime::RFC850, + Datetime::fromString(self::DATETIME)->toRFC850() + ); + } + + public function testCanOutputInFormatRFC1036(): void + { + $this->assertOutputInFormat( + self::DATETIME, + \Datetime::RFC1036, + Datetime::fromString(self::DATETIME)->toRFC1036() + ); + } + + public function testCanOutputInFormatRFC1123(): void + { + $this->assertOutputInFormat( + self::DATETIME, + \Datetime::RFC1123, + Datetime::fromString(self::DATETIME)->toRFC1123() + ); + } + + public function testCanOutputInFormatRFC7231(): void + { + $this->assertOutputInFormat( + self::DATETIME, + \Datetime::RFC7231, + Datetime::fromString(self::DATETIME)->toRFC7231() + ); + } + + public function testCanOutputInFormatRFC2822(): void + { + $this->assertOutputInFormat( + self::DATETIME, + \Datetime::RFC2822, + Datetime::fromString(self::DATETIME)->toRFC2822() + ); + } + + public function testCanOutputInFormatRFC3339(): void + { + $this->assertOutputInFormat( + self::DATETIME, + \Datetime::RFC3339, + Datetime::fromString(self::DATETIME)->toRFC3339() + ); + } + + public function testCanOutputInFormatRFC3339Extended(): void + { + $this->assertOutputInFormat( + self::DATETIME, + \Datetime::RFC3339_EXTENDED, + Datetime::fromString(self::DATETIME)->toRFC3339Extended() + ); + } + + public function testCanOutputInFormatRSS(): void + { + $this->assertOutputInFormat( + self::DATETIME, + \Datetime::RSS, + Datetime::fromString(self::DATETIME)->toRSS() + ); + } + + public function testCanOutputInFormatW3C(): void + { + $this->assertOutputInFormat( + self::DATETIME, + \Datetime::W3C, + Datetime::fromString(self::DATETIME)->toW3C() + ); + } + + public function testCanOutputInFormatDatetimeString(): void + { + $dt = new \Datetime(self::DATETIME); + + $this->assertOutputInFormat( + self::DATETIME, + $dt->format('Y-m-d H:i:s'), + Datetime::fromString(self::DATETIME)->toDatetimeString() + ); + } + + public function testCanOutputInFormatTimeString(): void + { + $dt = new \Datetime(self::DATETIME); + + $this->assertOutputInFormat( + self::DATETIME, + $dt->format('H:i:s'), + Datetime::fromString(self::DATETIME)->toTimeString() + ); + } + + public function testCanOutputInFormat(): void + { + $dt = new \Datetime(self::DATETIME); + + $this->assertOutputInFormat( + self::DATETIME, + $dt->format('Y-m-d H:i:s'), + Datetime::fromString(self::DATETIME)->toFormat(Str::create('Y-m-d H:i:s')) + ); + } + + public function testCanAddUnitsIndependently(): void + { + $original = Datetime::fromUnits(2022, 2, 2, 10, 11, 12); + $calculated = $original + ->addYears(1) + ->addMonths(1) + ->addDays(1) + ->addHours(1) + ->addMinutes(1) + ->addSeconds(1); + + $this->assertFalse($original === $calculated); + $this->assertNotEquals( + (string) $original->toDatetimeString(), + (string) $calculated->toDatetimeString() + ); + $this->assertEquals($original->getYear(), ($calculated->getYear() - 1)); + $this->assertEquals($original->getMonth(), ($calculated->getMonth() - 1)); + $this->assertEquals($original->getDay(), ($calculated->getDay() - 1)); + $this->assertEquals($original->getHour(), ($calculated->getHour() - 1)); + $this->assertEquals($original->getMinute(), ($calculated->getMinute() - 1)); + $this->assertEquals($original->getSecond(), ($calculated->getSecond() - 1)); + } +} diff --git a/tests/Unit/Datetime/README.md b/tests/Unit/Datetime/README.md new file mode 100644 index 0000000..d6b0f97 --- /dev/null +++ b/tests/Unit/Datetime/README.md @@ -0,0 +1 @@ +# Datetime related Unit tests diff --git a/tests/Unit/Exceptions/CoreExceptionsTest.php b/tests/Unit/Exceptions/CoreExceptionsTest.php new file mode 100644 index 0000000..7d76870 --- /dev/null +++ b/tests/Unit/Exceptions/CoreExceptionsTest.php @@ -0,0 +1,168 @@ +assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertEquals(409, $exception->getCode()); + } + + public function testDeniedAccessExceptionIsProperlyInitialized(): void + { + $exception = new DeniedAccessException(); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertEquals(401, $exception->getCode()); + } + + public function testExpectationFailedExceptionIsProperlyInitialized(): void + { + $exception = new ExpectationFailedException(); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertEquals(417, $exception->getCode()); + } + + public function testFailedDependencyExceptionIsProperlyInitialized(): void + { + $exception = new FailedDependencyException(); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertEquals(424, $exception->getCode()); + } + + public function testForbiddenExceptionIsProperlyInitialized(): void + { + $exception = new ForbiddenException(); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertEquals(403, $exception->getCode()); + } + + public function testGoneExceptionIsProperlyInitialized(): void + { + $exception = new GoneException(); + $static = GoneException::withId(123); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertGreaterThan(0, \strlen($static->getMessage())); + $this->assertEquals(410, $exception->getCode()); + $this->assertNotEquals($exception->getMessage(), $static->getMessage()); + $this->assertStringContainsString('123', $static->getMessage()); + } + + public function testMethodNotAllowedExceptionIsProperlyInitialized(): void + { + $exception = new MethodNotAllowedException(); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertEquals(405, $exception->getCode()); + } + + public function testNotAcceptableExceptionIsProperlyInitialized(): void + { + $exception = new NotAcceptableException(); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertEquals(406, $exception->getCode()); + } + + public function testNotFoundExceptionIsProperlyInitialized(): void + { + $exception = new NotFoundException(); + $static = NotFoundException::withId(12345); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertGreaterThan(0, \strlen($static->getMessage())); + $this->assertEquals(404, $exception->getCode()); + $this->assertNotEquals($exception->getMessage(), $static->getMessage()); + $this->assertStringContainsString('12345', $static->getMessage()); + } + + public function testPreconditionFailedExceptionIsProperlyInitialized(): void + { + $exception = new PreconditionFailedException(); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertEquals(412, $exception->getCode()); + } + + public function testPreconditionRequiredExceptionIsProperlyInitialized(): void + { + $exception = new PreconditionRequiredException(); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertEquals(428, $exception->getCode()); + } + + public function testRequestedRangeNotSatisfiableExceptionIsProperlyInitialized(): void + { + $exception = new RequestedRangeNotSatisfiableException(); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertEquals(416, $exception->getCode()); + } + + public function testTooManyRequestsExceptionIsProperlyInitialized(): void + { + $exception = new TooManyRequestsException(); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertEquals(429, $exception->getCode()); + } + + public function testUnprocessableEntityExceptionIsProperlyInitialized(): void + { + $exception = new UnprocessableEntityException(); + $static = UnprocessableEntityException::withName('SomeField'); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertGreaterThan(0, \strlen($static->getMessage())); + $this->assertEquals(422, $exception->getCode()); + $this->assertNotEquals($exception->getMessage(), $static->getMessage()); + $this->assertStringContainsString('SomeField', $static->getMessage()); + } + + public function testUnsupportedMediaTypeExceptionIsProperlyInitialized(): void + { + $exception = new UnsupportedMediaTypeException(); + $static = UnsupportedMediaTypeException::withName('MediaType'); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertGreaterThan(0, \strlen($static->getMessage())); + $this->assertEquals(415, $exception->getCode()); + $this->assertNotEquals($exception->getMessage(), $static->getMessage()); + $this->assertStringContainsString('MediaType', $static->getMessage()); + } +} diff --git a/tests/Unit/Exceptions/Datatypes/DatatypeExceptionsTest.php b/tests/Unit/Exceptions/Datatypes/DatatypeExceptionsTest.php new file mode 100644 index 0000000..ca8f416 --- /dev/null +++ b/tests/Unit/Exceptions/Datatypes/DatatypeExceptionsTest.php @@ -0,0 +1,114 @@ +assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertGreaterThan(0, \strlen($static->getMessage())); + $this->assertEquals(422, $exception->getCode()); + $this->assertNotEquals($exception->getMessage(), $static->getMessage()); + $this->assertStringContainsString('SomeField', $static->getMessage()); + } + + public function testInvalidEmailExceptionIsProperlyInitialized(): void + { + $exception = new InvalidEmailException(); + $static = InvalidEmailException::withName('SomeField'); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertGreaterThan(0, \strlen($static->getMessage())); + $this->assertEquals(422, $exception->getCode()); + $this->assertNotEquals($exception->getMessage(), $static->getMessage()); + $this->assertStringContainsString('SomeField', $static->getMessage()); + } + + public function testInvalidStringLengthExceptionIsProperlyInitialized(): void + { + $exception = new InvalidStringLengthException(); + $static = InvalidStringLengthException::withName('SomeField'); + $staticWithLength = InvalidStringLengthException::withNameAndLength('SomeField', 150); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertGreaterThan(0, \strlen($static->getMessage())); + $this->assertGreaterThan(0, \strlen($staticWithLength->getMessage())); + $this->assertEquals(422, $exception->getCode()); + $this->assertNotEquals($exception->getMessage(), $static->getMessage()); + $this->assertNotEquals($exception->getMessage(), $staticWithLength->getMessage()); + $this->assertNotEquals($static->getMessage(), $staticWithLength->getMessage()); + $this->assertStringContainsString('SomeField', $static->getMessage()); + $this->assertStringContainsString('SomeField', $staticWithLength->getMessage()); + $this->assertStringContainsString('150', $staticWithLength->getMessage()); + } + + public function testNonEmptyStringExceptionIsProperlyInitialized(): void + { + $exception = new NonEmptyStringException(); + $static = NonEmptyStringException::withName('SomeField'); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertGreaterThan(0, \strlen($static->getMessage())); + $this->assertEquals(422, $exception->getCode()); + $this->assertNotEquals($exception->getMessage(), $static->getMessage()); + $this->assertStringContainsString('SomeField', $static->getMessage()); + } + + public function testNonNegativeNumberExceptionIsProperlyInitialized(): void + { + $exception = new NonNegativeNumberException(); + $static = NonNegativeNumberException::withName('SomeField'); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertGreaterThan(0, \strlen($static->getMessage())); + $this->assertEquals(422, $exception->getCode()); + $this->assertNotEquals($exception->getMessage(), $static->getMessage()); + $this->assertStringContainsString('SomeField', $static->getMessage()); + } + + public function testParameterOutOfRangeExceptionIsProperlyInitialized(): void + { + $exception = new ParameterOutOfRangeException(); + $static = ParameterOutOfRangeException::withName('SomeField'); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertGreaterThan(0, \strlen($static->getMessage())); + $this->assertEquals(422, $exception->getCode()); + $this->assertNotEquals($exception->getMessage(), $static->getMessage()); + $this->assertStringContainsString('SomeField', $static->getMessage()); + } + + public function testPositiveIntegerExceptionIsProperlyInitialized(): void + { + $exception = new PositiveIntegerException(); + $static = PositiveIntegerException::withName('SomeField'); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertGreaterThan(0, \strlen($static->getMessage())); + $this->assertEquals(422, $exception->getCode()); + $this->assertNotEquals($exception->getMessage(), $static->getMessage()); + $this->assertStringContainsString('SomeField', $static->getMessage()); + } +} diff --git a/tests/Unit/Exceptions/Datatypes/README.md b/tests/Unit/Exceptions/Datatypes/README.md new file mode 100644 index 0000000..fb46103 --- /dev/null +++ b/tests/Unit/Exceptions/Datatypes/README.md @@ -0,0 +1 @@ +# Datatype Exception's related Unit tests diff --git a/tests/Unit/Exceptions/Entities/EntitiesExceptionsTest.php b/tests/Unit/Exceptions/Entities/EntitiesExceptionsTest.php new file mode 100644 index 0000000..f05c475 --- /dev/null +++ b/tests/Unit/Exceptions/Entities/EntitiesExceptionsTest.php @@ -0,0 +1,43 @@ +assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertGreaterThan(0, \strlen($static->getMessage())); + $this->assertEquals(422, $exception->getCode()); + $this->assertNotEquals($exception->getMessage(), $static->getMessage()); + $this->assertStringContainsString('SomeField', $static->getMessage()); + } + + public function testUnexpectedEntityValueExceptionIsProperlyInitialized(): void + { + $exception = new UnexpectedEntityValueException(); + $static = UnexpectedEntityValueException::withName('SomeField'); + + $this->assertGreaterThan(0, \strlen($exception->getMessage())); + $this->assertGreaterThan(0, \strlen($static->getMessage())); + $this->assertEquals(422, $exception->getCode()); + $this->assertNotEquals($exception->getMessage(), $static->getMessage()); + $this->assertStringContainsString('SomeField', $static->getMessage()); + } +} diff --git a/tests/Unit/Exceptions/Entities/README.md b/tests/Unit/Exceptions/Entities/README.md new file mode 100644 index 0000000..65db58b --- /dev/null +++ b/tests/Unit/Exceptions/Entities/README.md @@ -0,0 +1 @@ +# Entity's Exception related Unit tests diff --git a/tests/Unit/Exceptions/README.md b/tests/Unit/Exceptions/README.md new file mode 100644 index 0000000..0af26bc --- /dev/null +++ b/tests/Unit/Exceptions/README.md @@ -0,0 +1 @@ +# Exception's related Unit tests diff --git a/tests/Unit/Scalar/ImmutableFloatTest.php b/tests/Unit/Scalar/ImmutableFloatTest.php deleted file mode 100644 index 29a5fd8..0000000 --- a/tests/Unit/Scalar/ImmutableFloatTest.php +++ /dev/null @@ -1,392 +0,0 @@ -ImmutableFloat and MutableFloat classes have similar behavior, therefore, - * you should extend this test case for the other type of String, overriding only the necessary - * tests, which shouldn't be too many. - * - * This way, we can test both types of objects, without repeating the same code/tests. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ImmutableFloatTest extends AbstractBaseTestCase -{ - /** - * Asserts that 2 integer instances do not match. - * - * @param AbstractReadFloat $original - Original Float instance. - * @param AbstractReadFloat $other - Second instance for comparison. - * - * @since 1.0.0 - * @return void - */ - protected function checkInstances(AbstractReadFloat $original, AbstractReadFloat $other): void - { - $this->assertFalse( - ($original === $other), - 'Instances are not meant to match.' - ); - } - - /** - * Asserts that the supplied instance, is from the correct type. - * - * @param AbstractReadFloat $instance - Instance to be validated. - * - * @since 1.0.0 - * @return void - */ - protected function checkCorrectInstanceType(AbstractReadFloat $instance): void - { - $this->assertInstanceOf( - ImmutableFloat::class, - $instance, - 'Instance type, does not match ImmutableFloat.' - ); - } - - /** - * Initializes and returns a new instance for testing. - * - * This method has no return value, due to inheritance overriding. - * - * @param string $initialValue - Instance's initial value. - * - * @since 1.0.0 - * @return ImmutableFloat - */ - protected function initializeInstance(string $initialValue) - { - return ImmutableFloat::fromString($initialValue); - } - - /** - * Checks instance can be cloned. - * - * @since 1.0.0 - * @return void - */ - public function testCanCloneObject(): void - { - // Performs test. - $original = $this->initializeInstance(123.0); - $other = $original->toMutable(); - - // Performs assertions. - $this->assertInstanceOf( - MutableFloat::class, - $other, - 'Instance type, does not match MutableFloat.' - ); - $this->assertEquals( - $original->value(), - $other->value(), - 'Instance values do not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests that the instance can be converted to an Integer. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertInstanceToInteger(): void - { - // Performs test. - $original = $this->initializeInstance(123.0); - $integer = $original->toInteger(); - - // Performs assertions. - $this->assertInstanceOf( - ImmutableInteger::class, - $integer, - 'Instance type, does not match ImmutableInteger.' - ); - } - - /** - * Tests that the instance can be converted to a String. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertInstanceToString(): void - { - // Performs test. - $original = $this->initializeInstance(123.0); - $string = $original->toString(); - - // Performs assertions. - $this->assertInstanceOf( - ImmutableString::class, - $string, - 'Instance type, does not match ImmutableString.' - ); - } - - /** - * Tests that trying to create a new Instance from an empty string breaks. - * - * @since 1.0.0 - * @return void - */ - public function testCreatingNewInstanceFromStringBreaksWithEmptyString(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - ImmutableFloat::fromString(''); - } - - /** - * Test that an instance can be created from a String. - * - * @since 1.0.0 - * @return void - */ - public function testCanCreateNewInstanceFromString(): void - { - // Performs test. - $instance = ImmutableFloat::fromString('123.0'); - - // Performs assertions. - $this->assertEquals( - 123.0, - $instance->value(), - 'Instance value does not match.' - ); - } - - /** - * Test that an instance can be created from a Float. - * - * @since 1.0.0 - * @return void - */ - public function testCanCreateNewInstanceFromFloat(): void - { - // Performs test. - $instance = ImmutableFloat::fromFloat(123.0); - - // Performs assertions. - $this->assertEquals( - 123.0, - $instance->value(), - 'Instance value does not match.' - ); - } - - /** - * Tests a value can be added to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanAddPositiveValue(): void - { - // Performs test. - $original = $this->initializeInstance(123.0); - $other = $original->add($this->initializeInstance(2.0)); - - // Performs assertions. - $this->assertEquals( - 125.0, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be added to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanAddNegativeValue(): void - { - // Performs test. - $original = $this->initializeInstance(123.0); - $other = $original->add($this->initializeInstance(-3.0)); - - // Performs assertions. - $this->assertEquals( - 120.0, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be subtracted to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanSubtractPositiveValue(): void - { - // Performs test. - $original = $this->initializeInstance(123.0); - $other = $original->subtract($this->initializeInstance(3.0)); - - // Performs assertions. - $this->assertEquals( - 120.0, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be subtracted to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanSubtractNegativeValue(): void - { - // Performs test. - $original = $this->initializeInstance(123.0); - $other = $original->subtract($this->initializeInstance(-2.0)); - - // Performs assertions. - $this->assertEquals( - 125.0, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be multiply to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanMultiplyPositiveValue(): void - { - // Performs test. - $original = $this->initializeInstance(123.0); - $other = $original->multiply($this->initializeInstance(2.0)); - - // Performs assertions. - $this->assertEquals( - 246.0, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be multiply to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanMultiplyNegativeValue(): void - { - // Performs test. - $original = $this->initializeInstance(123.0); - $other = $original->multiply($this->initializeInstance(-2.0)); - - // Performs assertions. - $this->assertEquals( - -246.0, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be divide to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanDividePositiveValue(): void - { - // Performs test. - $original = $this->initializeInstance(40.0); - $other = $original->divide($this->initializeInstance(2.0)); - - // Performs assertions. - $this->assertEquals( - 20.0, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be divide to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanDivideNegativeValue(): void - { - // Performs test. - $original = $this->initializeInstance(40.0); - $other = $original->divide($this->initializeInstance(-2.0)); - - // Performs assertions. - $this->assertEquals( - -20.0, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests that the number can be formatted correctly. - * - * @since 1.0.0 - * @return void - */ - public function testCanFormatTheInstance(): void - { - // Performs test. - $original = $this->initializeInstance(4000.1); - $formated = $original->format( - \NumberFormatter::create('en_US', \NumberFormatter::DECIMAL) - ); - - // Performs assertions. - $this->assertEquals( - '4,000.1', - $formated->__toString(), - 'Formatted number does not seam to match.' - ); - $this->assertInstanceOf( - ImmutableString::class, - $formated, - 'Instance type, does not match ImmutableString.' - ); - } -} diff --git a/tests/Unit/Scalar/ImmutableIntegerTest.php b/tests/Unit/Scalar/ImmutableIntegerTest.php deleted file mode 100644 index 63e8da1..0000000 --- a/tests/Unit/Scalar/ImmutableIntegerTest.php +++ /dev/null @@ -1,392 +0,0 @@ -ImmutableInteger and MutableInteger classes have similar behavior, therefore, - * you should extend this test case for the other type of String, overriding only the necessary - * tests, which shouldn't be too many. - * - * This way, we can test both types of objects, without repeating the same code/tests. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ImmutableIntegerTest extends AbstractBaseTestCase -{ - /** - * Asserts that 2 integer instances do not match. - * - * @param AbstractReadInteger $original - Original Integer instance. - * @param AbstractReadInteger $other - Second instance for comparison. - * - * @since 1.0.0 - * @return void - */ - protected function checkInstances(AbstractReadInteger $original, AbstractReadInteger $other): void - { - $this->assertFalse( - ($original === $other), - 'Instances are not meant to match.' - ); - } - - /** - * Asserts that the supplied instance, is from the correct type. - * - * @param AbstractReadInteger $instance - Instance to be validated. - * - * @since 1.0.0 - * @return void - */ - protected function checkCorrectInstanceType(AbstractReadInteger $instance): void - { - $this->assertInstanceOf( - ImmutableInteger::class, - $instance, - 'Instance type, does not match ImmutableInteger.' - ); - } - - /** - * Initializes and returns a new instance for testing. - * - * This method has no return value, due to inheritance overriding. - * - * @param string $initialValue - Instance's initial value. - * - * @since 1.0.0 - * @return ImmutableInteger - */ - protected function initializeInstance(string $initialValue) - { - return ImmutableInteger::fromString($initialValue); - } - - /** - * Checks instance can be cloned. - * - * @since 1.0.0 - * @return void - */ - public function testCanCloneObject(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $other = $original->toMutable(); - - // Performs assertions. - $this->assertInstanceOf( - MutableInteger::class, - $other, - 'Instance type, does not match MutableInteger.' - ); - $this->assertEquals( - $original->value(), - $other->value(), - 'Instance values do not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests that the instance can be converted to a Float. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertInstanceToFloat(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $float = $original->toFloat(); - - // Performs assertions. - $this->assertInstanceOf( - ImmutableFloat::class, - $float, - 'Instance type, does not match ImmutableFloat.' - ); - } - - /** - * Tests that the instance can be converted to a String. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertInstanceToString(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $string = $original->toString(); - - // Performs assertions. - $this->assertInstanceOf( - ImmutableString::class, - $string, - 'Instance type, does not match ImmutableString.' - ); - } - - /** - * Tests that trying to create a new Instance from an empty string breaks. - * - * @since 1.0.0 - * @return void - */ - public function testCreatingNewInstanceFromStringBreaksWithEmptyString(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - ImmutableInteger::fromString(''); - } - - /** - * Test that an instance can be created from a String. - * - * @since 1.0.0 - * @return void - */ - public function testCanCreateNewInstanceFromString(): void - { - // Performs test. - $instance = ImmutableInteger::fromString('123'); - - // Performs assertions. - $this->assertEquals( - 123, - $instance->value(), - 'Instance value does not match.' - ); - } - - /** - * Test that an instance can be created from an Integer. - * - * @since 1.0.0 - * @return void - */ - public function testCanCreateNewInstanceFromInteger(): void - { - // Performs test. - $instance = ImmutableInteger::fromInteger(123); - - // Performs assertions. - $this->assertEquals( - 123, - $instance->value(), - 'Instance value does not match.' - ); - } - - /** - * Tests a value can be added to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanAddPositiveValue(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $other = $original->add($this->initializeInstance(2)); - - // Performs assertions. - $this->assertEquals( - 125, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be added to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanAddNegativeValue(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $other = $original->add($this->initializeInstance(-3)); - - // Performs assertions. - $this->assertEquals( - 120, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be subtracted to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanSubtractPositiveValue(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $other = $original->subtract($this->initializeInstance(3)); - - // Performs assertions. - $this->assertEquals( - 120, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be subtracted to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanSubtractNegativeValue(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $other = $original->subtract($this->initializeInstance(-2)); - - // Performs assertions. - $this->assertEquals( - 125, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be multiply to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanMultiplyPositiveValue(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $other = $original->multiply($this->initializeInstance(2)); - - // Performs assertions. - $this->assertEquals( - 246, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be multiply to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanMultiplyNegativeValue(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $other = $original->multiply($this->initializeInstance(-2)); - - // Performs assertions. - $this->assertEquals( - -246, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be divide to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanDividePositiveValue(): void - { - // Performs test. - $original = $this->initializeInstance(40); - $other = $original->divide($this->initializeInstance(2)); - - // Performs assertions. - $this->assertEquals( - 20, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests a value can be divide to the instance. - * - * @since 1.0.0 - * @return void - */ - public function testCanDivideNegativeValue(): void - { - // Performs test. - $original = $this->initializeInstance(40); - $other = $original->divide($this->initializeInstance(-2)); - - // Performs assertions. - $this->assertEquals( - -20, - $other->value(), - 'Instance value does not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests that the number can be formatted correctly. - * - * @since 1.0.0 - * @return void - */ - public function testCanFormatTheInstance(): void - { - // Performs test. - $original = $this->initializeInstance(4000); - $formated = $original->format( - \NumberFormatter::create('en_US', \NumberFormatter::DEFAULT_STYLE) - ); - - // Performs assertions. - $this->assertEquals( - '4,000', - $formated->__toString(), - 'Formatted number does not seam to match.' - ); - $this->assertInstanceOf( - ImmutableString::class, - $formated, - 'Instance type, does not match ImmutableString.' - ); - } -} diff --git a/tests/Unit/Scalar/ImmutableStringTest.php b/tests/Unit/Scalar/ImmutableStringTest.php deleted file mode 100644 index 6339f6c..0000000 --- a/tests/Unit/Scalar/ImmutableStringTest.php +++ /dev/null @@ -1,955 +0,0 @@ -ImmutableString and MutableString classes have similar behavior, therefore, - * you should extend this test case for the other type of String, overriding only the necessary - * tests, which shouldn't be too many. - * - * This way, we can test both types of objects, without repeating the same code/tests. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ImmutableStringTest extends AbstractBaseTestCase -{ - /** - * Asserts that 2 string instances do not match. - * - * @param AbstractReadString $original - Original String instance. - * @param AbstractReadString $other - Second instance for comparison. - * - * @since 1.0.0 - * @return void - */ - protected function checkInstances(AbstractReadString $original, AbstractReadString $other): void - { - $this->assertFalse( - ($original === $other), - 'Instances are not meant to match.' - ); - } - - /** - * Asserts that the supplied instance, is from the correct type. - * - * @param AbstractReadString $instance - Instance to be validated. - * - * @since 1.0.0 - * @return void - */ - protected function checkCorrectInstanceType(AbstractReadString $instance): void - { - $this->assertInstanceOf( - ImmutableString::class, - $instance, - 'Instance type, does not match ImmutableString.' - ); - } - - /** - * Initializes and returns a new instance for testing. - * - * This method has no return value, due to inheritance overriding. - * - * @param string $initialValue - Instance's initial value. - * - * @since 1.0.0 - * @return ImmutableString - */ - protected function initializeInstance(string $initialValue) - { - return ImmutableString::fromString($initialValue); - } - - /** - * Checks instance can be cloned. - * - * @since 1.0.0 - * @return void - */ - public function testCanCloneObject(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable string."); - $other = $original->toMutable(); - - // Performs assertions. - $this->assertInstanceOf( - MutableString::class, - $other, - 'Instance type, does not match ImmutableString.' - ); - $this->assertEquals( - $original->__toString(), - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be trimmed. - * - * @since 1.0.0 - * @return void - */ - public function testCanTrimString(): void - { - // Performs test. - $original = $this->initializeInstance(" Immutable string. "); - $other = $original->trim(); - - // Performs assertions. - $this->assertEquals( - 'Immutable string.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be trimmed only on the left. - * - * @since 1.0.0 - * @return void - */ - public function testCanLeftTrimString(): void - { - // Performs test. - $original = $this->initializeInstance(" Immutable string. "); - $other = $original->trimLeft(); - - // Performs assertions. - $this->assertEquals( - 'Immutable string. ', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be trimmed only on the right. - * - * @since 1.0.0 - * @return void - */ - public function testCanRightTrimString(): void - { - // Performs test. - $original = $this->initializeInstance(" Immutable string. "); - $other = $original->trimRight(); - - // Performs assertions. - $this->assertEquals( - ' Immutable string.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be converted to UPPER case. - * - * @since 1.0.0 - * @return void - */ - public function testCanUpperCaseString(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable string."); - $other = $original->toUpper(); - - // Performs assertions. - $this->assertEquals( - 'IMMUTABLE STRING.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be converted to UPPER case. - * - * @since 1.0.0 - * @return void - */ - public function testCanUpperCaseFirst(): void - { - // Performs test. - $original = $this->initializeInstance("immutable string."); - $other = $original->toUpperFirst(); - - // Performs assertions. - $this->assertEquals( - 'Immutable string.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be convert to UPPER case, all words. - * - * @since 1.0.0 - * @return void - */ - public function testCanUpperCaseWords(): void - { - // Performs test. - $original = $this->initializeInstance("immutable string."); - $other = $original->toUpperWords(); - - // Performs assertions. - $this->assertEquals( - 'Immutable String.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be converted to LOWER case. - * - * @since 1.0.0 - * @return void - */ - public function testCanLowerCaseString(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $other = $original->toLower(); - - // Performs assertions. - $this->assertEquals( - 'immutable string.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be converted to LOWER case. - * - * @since 1.0.0 - * @return void - */ - public function testCanLowerCaseFirst(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $other = $original->toLowerFirst(); - - // Performs assertions. - $this->assertEquals( - 'immutable String.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be padded on the left. - * - * @since 1.0.0 - * @return void - */ - public function testCanPadOnTheLeft(): void - { - // Performs test. - $string = "Immutable String."; - $original = $this->initializeInstance($string); - $other = $original->padLeft(\strlen($string) + 2); - - // Performs assertions. - $this->assertEquals( - ' Immutable String.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string is not padded on the left, when the padding length is less than the string's length. - * - * @since 1.0.0 - * @return void - */ - public function testCanPadOnTheLeftWithoutResult(): void - { - // Performs test. - $string = "Immutable String."; - $original = $this->initializeInstance($string); - $other = $original->padLeft(2); - - // Performs assertions. - $this->assertEquals( - $string, - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be padded on the left. - * - * @since 1.0.0 - * @return void - */ - public function testCanPadOnTheLeftExtra(): void - { - // Performs test. - $string = "Immutable String."; - $original = $this->initializeInstance($string); - $other = $original->padLeftExtra(2); - - // Performs assertions. - $this->assertEquals( - ' Immutable String.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that padding breaks. - * - * @since 1.0.0 - * @return void - */ - public function testCanPaddingOnTheLeftBreaksWithInvalidLength(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->padLeft(0); - } - - /** - * Tests that padding breaks. - * - * @since 1.0.0 - * @return void - */ - public function testCanPaddingOnTheLeftBreaksWithInvalidPadString(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->padLeft(2, ''); - } - - /** - * Tests that padding breaks. - * - * @since 1.0.0 - * @return void - */ - public function testCanPaddingOnTheLeftExtraBreaksWithInvalidLength(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->padLeftExtra(0); - } - - /** - * Tests that padding breaks. - * - * @since 1.0.0 - * @return void - */ - public function testCanPaddingOnTheLeftExtraBreaksWithInvalidPadString(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->padLeftExtra(2, ''); - } - - /** - * Tests that a string can be padded on the left. - * - * @since 1.0.0 - * @return void - */ - public function testCanPadOnTheLeftWidthCharacter(): void - { - // Performs test. - $string = "Immutable String."; - $original = $this->initializeInstance($string); - $other = $original->padLeft(\strlen($string) + 2, '_'); - - // Performs assertions. - $this->assertEquals( - '__Immutable String.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be padded on the left. - * - * @since 1.0.0 - * @return void - */ - public function testCanPadOnTheLeftExtraWidthCharacter(): void - { - // Performs test. - $string = "Immutable String."; - $original = $this->initializeInstance($string); - $other = $original->padLeftExtra(2, '_'); - - // Performs assertions. - $this->assertEquals( - '__Immutable String.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be padded on the right. - * - * @since 1.0.0 - * @return void - */ - public function testCanPadOnTheRight(): void - { - // Performs test. - $string = "Immutable String."; - $original = $this->initializeInstance($string); - $other = $original->padRight(\strlen($string) + 2); - - // Performs assertions. - $this->assertEquals( - 'Immutable String. ', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be padded on the right. - * - * @since 1.0.0 - * @return void - */ - public function testCanPadOnTheRightExtra(): void - { - // Performs test. - $string = "Immutable String."; - $original = $this->initializeInstance($string); - $other = $original->padRightExtra(2); - - // Performs assertions. - $this->assertEquals( - 'Immutable String. ', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that padding breaks. - * - * @since 1.0.0 - * @return void - */ - public function testCanPadOnTheRightBreaksWithInvalidLength(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->padRight(0); - } - - /** - * Tests that padding breaks. - * - * @since 1.0.0 - * @return void - */ - public function testCanPadOnTheRightBreaksWithInvalidPadString(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->padRight(2, ''); - } - - /** - * Tests that padding breaks. - * - * @since 1.0.0 - * @return void - */ - public function testCanPadOnTheRightExtraBreaksWithInvalidLength(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->padRightExtra(0); - } - - /** - * Tests that padding breaks. - * - * @since 1.0.0 - * @return void - */ - public function testCanPadOnTheRightExtraBreaksWithInvalidPadString(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->padRightExtra(2, ''); - } - - /** - * Tests that a string can be padded on the right. - * - * @since 1.0.0 - * @return void - */ - public function testCanPadOnTheRightWidthCharacter(): void - { - // Performs test. - $string = "Immutable String."; - $original = $this->initializeInstance($string); - $other = $original->padRight(\strlen($string) + 2, '_'); - - // Performs assertions. - $this->assertEquals( - 'Immutable String.__', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a string can be padded on the right. - * - * @since 1.0.0 - * @return void - */ - public function testCanPadOnTheRightExtraWidthCharacter(): void - { - // Performs test. - $string = "Immutable String."; - $original = $this->initializeInstance($string); - $other = $original->padRightExtra(2, '_'); - - // Performs assertions. - $this->assertEquals( - 'Immutable String.__', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a simple substring can be retrieved. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringCanBeRetrieved(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $other = $original->subString(10); - - // Performs assertions. - $this->assertEquals( - 'String.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a simple substring can be retrieved. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringCanBeRetrievedWithNegativeStart(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $other = $original->subString(-7); - - // Performs assertions. - $this->assertEquals( - 'String.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a simple substring can be retrieved. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringCanBeRetrievedWithLength(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $other = $original->subString(2, 7); - - // Performs assertions. - $this->assertEquals( - 'mutable', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that a simple substring can be retrieved. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringCanBeRetrievedWithNegativeLength(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $other = $original->subString(2, -8); - - // Performs assertions. - $this->assertEquals( - 'mutable', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that substring breaks. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringBreaksWithShortStart(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->subString(-30); - } - - /** - * Tests that substring breaks. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringBreaksWithLongStart(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->subString(30); - } - - /** - * Tests that substring breaks. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringBreaksWithShortLength(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->subString(0, -30); - } - - /** - * Tests that substring breaks. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringBreaksWithLongLength(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->subString(0, 30); - } - - /** - * Tests that a simple subLeft can be retrieved. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringLeftCanBeRetrieved(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $other = $original->subLeft(10); - - // Performs assertions. - $this->assertEquals( - 'Immutable ', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that subLeft() breaks. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringLeftBreaksWithShortLength(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->subLeft(-1); - } - - /** - * Tests that subLeft() breaks. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringLeftBreaksWithLongLength(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->subLeft(30); - } - - /** - * Tests that a simple subRight() can be retrieved. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringRightCanBeRetrieved(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $other = $original->subRight(10); - - // Performs assertions. - $this->assertEquals( - 'le String.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that subRight() breaks. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringRightBreaksWithShortLength(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->subRight(-1); - } - - /** - * Tests that subRight() breaks. - * - * @since 1.0.0 - * @return void - */ - public function testSubStringRightBreaksWithLongLength(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->subRight(30); - } - - /** - * Tests that a string can be reversed. - * - * @since 1.0.0 - * @return void - */ - public function testStringCanBeReversed(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $other = $original->reverse(); - - // Performs assertions. - $this->assertEquals( - '.gnirtS elbatummI', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that text can be replaced in the String. - * - * @since 1.0.0 - * @return void - */ - public function testTextCanBeReplacedInString(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $other = $original->replace('String', 'Object'); - - // Performs assertions. - $this->assertEquals( - 'Immutable Object.', - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that text is not replaced if search is not found. - * - * @since 1.0.0 - * @return void - */ - public function testTextIsNotReplacedIfSearchNotFound(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $other = $original->replace('None', 'Object'); - - // Performs assertions. - $this->assertEquals( - $original->__toString(), - $other->__toString(), - 'Instance values do not match.' - ); - $this->checkCorrectInstanceType($other); - $this->checkInstances($original, $other); - } - - /** - * Tests that text replace breaks. - * - * @since 1.0.0 - * @return void - */ - public function testTextReplaceBreaksIfSearchIsEmpty(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $original = $this->initializeInstance("Immutable String."); - $original->replace('', 'Object'); - } -} diff --git a/tests/Unit/Scalar/MutableFloatTest.php b/tests/Unit/Scalar/MutableFloatTest.php deleted file mode 100644 index 0473e74..0000000 --- a/tests/Unit/Scalar/MutableFloatTest.php +++ /dev/null @@ -1,226 +0,0 @@ -ImmutableFloat and MutableFloat classes have similar behavior, therefore, - * you should extend this test case for the other type of String, overriding only the necessary - * tests, which shouldn't be too many. - * - * This way, we can test both types of objects, without repeating the same code/tests. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class MutableFloatTest extends ImmutableFloatTest -{ - /** - * Asserts that 2 integer instances do not match. - * - * @param AbstractReadFloat $original - Original Float instance. - * @param AbstractReadFloat $other - Second instance for comparison. - * - * @since 1.0.0 - * @return void - */ - protected function checkInstances(AbstractReadFloat $original, AbstractReadFloat $other): void - { - $this->assertTrue( - ($original === $other), - 'Instances are meant to match.' - ); - } - - /** - * Asserts that the supplied instance, is from the correct type. - * - * @param AbstractReadFloat $instance - Instance to be validated. - * - * @since 1.0.0 - * @return void - */ - protected function checkCorrectInstanceType(AbstractReadFloat $instance): void - { - $this->assertInstanceOf( - MutableFloat::class, - $instance, - 'Instance type, does not match MutableFloat.' - ); - } - - /** - * Initializes and returns a new instance for testing. - * - * This method has no return value, due to inheritance overriding. - * - * @param string $initialValue - Instance's initial value. - * - * @since 1.0.0 - * @return MutableInteger - */ - protected function initializeInstance(string $initialValue) - { - return MutableFloat::fromString($initialValue); - } - - /** - * Checks instance can be cloned. - * - * @since 1.0.0 - * @return void - */ - public function testCanCloneObject(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $other = $original->toImmutable(); - - // Performs assertions. - $this->assertInstanceOf( - ImmutableFloat::class, - $other, - 'Instance type, does not match ImmutableFloat.' - ); - $this->assertEquals( - $original->value(), - $other->value(), - 'Instance values do not match.' - ); - $this->assertFalse( - ($original === $other), - 'Instances are not meant to match.' - ); - } - - /** - * Tests that the instance can be converted to an Integer. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertInstanceToInteger(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $integer = $original->toInteger(); - - // Performs assertions. - $this->assertInstanceOf( - MutableInteger::class, - $integer, - 'Instance type, does not match MutableInteger.' - ); - } - - /** - * Tests that the instance can be converted to a String. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertInstanceToString(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $string = $original->toString(); - - // Performs assertions. - $this->assertInstanceOf( - MutableString::class, - $string, - 'Instance type, does not match MutableString.' - ); - } - - /** - * Tests that trying to create a new Instance from an empty string breaks. - * - * @since 1.0.0 - * @return void - */ - public function testCreatingNewInstanceFromStringBreaksWithEmptyString(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - MutableFloat::fromString(''); - } - - /** - * Test that an instance can be created from a String. - * - * @since 1.0.0 - * @return void - */ - public function testCanCreateNewInstanceFromString(): void - { - // Performs test. - $instance = MutableFloat::fromString('123.1'); - - // Performs assertions. - $this->assertEquals( - 123.1, - $instance->value(), - 'Instance value does not match.' - ); - } - - /** - * Test that an instance can be created from a Float. - * - * @since 1.0.0 - * @return void - */ - public function testCanCreateNewInstanceFromFloat(): void - { - // Performs test. - $instance = MutableFloat::fromFloat(123); - - // Performs assertions. - $this->assertEquals( - 123, - $instance->value(), - 'Instance value does not match.' - ); - } - - /** - * Tests that the number can be formatted correctly. - * - * @since 1.0.0 - * @return void - */ - public function testCanFormatTheInstance(): void - { - // Performs test. - $original = $this->initializeInstance(4000.1); - $formated = $original->format( - \NumberFormatter::create('en_US', \NumberFormatter::DECIMAL) - ); - - // Performs assertions. - $this->assertEquals( - '4,000.1', - $formated->__toString(), - 'Formatted number does not seam to match.' - ); - $this->assertInstanceOf( - MutableString::class, - $formated, - 'Instance type, does not match MutableString.' - ); - } -} diff --git a/tests/Unit/Scalar/MutableIntegerTest.php b/tests/Unit/Scalar/MutableIntegerTest.php deleted file mode 100644 index 2e0d453..0000000 --- a/tests/Unit/Scalar/MutableIntegerTest.php +++ /dev/null @@ -1,226 +0,0 @@ -ImmutableInteger and MutableInteger classes have similar behavior, therefore, - * you should extend this test case for the other type of String, overriding only the necessary - * tests, which shouldn't be too many. - * - * This way, we can test both types of objects, without repeating the same code/tests. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class MutableIntegerTest extends ImmutableIntegerTest -{ - /** - * Asserts that 2 integer instances do not match. - * - * @param AbstractReadInteger $original - Original Integer instance. - * @param AbstractReadInteger $other - Second instance for comparison. - * - * @since 1.0.0 - * @return void - */ - protected function checkInstances(AbstractReadInteger $original, AbstractReadInteger $other): void - { - $this->assertTrue( - ($original === $other), - 'Instances are meant to match.' - ); - } - - /** - * Asserts that the supplied instance, is from the correct type. - * - * @param AbstractReadInteger $instance - Instance to be validated. - * - * @since 1.0.0 - * @return void - */ - protected function checkCorrectInstanceType(AbstractReadInteger $instance): void - { - $this->assertInstanceOf( - MutableInteger::class, - $instance, - 'Instance type, does not match MutableInteger.' - ); - } - - /** - * Initializes and returns a new instance for testing. - * - * This method has no return value, due to inheritance overriding. - * - * @param string $initialValue - Instance's initial value. - * - * @since 1.0.0 - * @return MutableInteger - */ - protected function initializeInstance(string $initialValue) - { - return MutableInteger::fromString($initialValue); - } - - /** - * Checks instance can be cloned. - * - * @since 1.0.0 - * @return void - */ - public function testCanCloneObject(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $other = $original->toImmutable(); - - // Performs assertions. - $this->assertInstanceOf( - ImmutableInteger::class, - $other, - 'Instance type, does not match ImmutableInteger.' - ); - $this->assertEquals( - $original->value(), - $other->value(), - 'Instance values do not match.' - ); - $this->assertFalse( - ($original === $other), - 'Instances are not meant to match.' - ); - } - - /** - * Tests that the instance can be converted to a Float. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertInstanceToFloat(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $float = $original->toFloat(); - - // Performs assertions. - $this->assertInstanceOf( - MutableFloat::class, - $float, - 'Instance type, does not match MutableFloat.' - ); - } - - /** - * Tests that the instance can be converted to a String. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertInstanceToString(): void - { - // Performs test. - $original = $this->initializeInstance(123); - $string = $original->toString(); - - // Performs assertions. - $this->assertInstanceOf( - MutableString::class, - $string, - 'Instance type, does not match MutableString.' - ); - } - - /** - * Tests that trying to create a new Instance from an empty string breaks. - * - * @since 1.0.0 - * @return void - */ - public function testCreatingNewInstanceFromStringBreaksWithEmptyString(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - MutableInteger::fromString(''); - } - - /** - * Test that an instance can be created from a String. - * - * @since 1.0.0 - * @return void - */ - public function testCanCreateNewInstanceFromString(): void - { - // Performs test. - $instance = MutableInteger::fromString('123'); - - // Performs assertions. - $this->assertEquals( - 123, - $instance->value(), - 'Instance value does not match.' - ); - } - - /** - * Test that an instance can be created from an Integer. - * - * @since 1.0.0 - * @return void - */ - public function testCanCreateNewInstanceFromInteger(): void - { - // Performs test. - $instance = MutableInteger::fromInteger(123); - - // Performs assertions. - $this->assertEquals( - 123, - $instance->value(), - 'Instance value does not match.' - ); - } - - /** - * Tests that the number can be formatted correctly. - * - * @since 1.0.0 - * @return void - */ - public function testCanFormatTheInstance(): void - { - // Performs test. - $original = $this->initializeInstance(4000); - $formated = $original->format( - \NumberFormatter::create('en_US', \NumberFormatter::DEFAULT_STYLE) - ); - - // Performs assertions. - $this->assertEquals( - '4,000', - $formated->__toString(), - 'Formatted number does not seam to match.' - ); - $this->assertInstanceOf( - MutableString::class, - $formated, - 'Instance type, does not match MutableString.' - ); - } -} diff --git a/tests/Unit/Scalar/MutableStringTest.php b/tests/Unit/Scalar/MutableStringTest.php deleted file mode 100644 index 38203ca..0000000 --- a/tests/Unit/Scalar/MutableStringTest.php +++ /dev/null @@ -1,104 +0,0 @@ -ImmutableString and MutableString classes have similar behavior, therefore, - * this test case extends the parent for the other type of String, overriding only the necessary - * tests, which shouldn't be too many. - * - * This way, we can testboth types of objects, without repeating the same code/tests. - * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class MutableStringTest extends ImmutableStringTest -{ - /** - * Asserts that 2 string instances match. - * - * @param AbstractReadString $original - Original String instance. - * @param AbstractReadString $other - Second instance for comparison. - * - * @since 1.0.0 - * @return void - */ - protected function checkInstances(AbstractReadString $original, AbstractReadString $other): void - { - $this->assertTrue( - ($original === $other), - 'Instances are meant to match.' - ); - } - - /** - * Asserts that the supplied instance, is from the correct type. - * - * @param AbstractReadString $instance - Instance to be validated. - * - * @since 1.0.0 - * @return void - */ - protected function checkCorrectInstanceType(AbstractReadString $instance): void - { - $this->assertInstanceOf( - MutableString::class, - $instance, - 'Instance type, does not match MutableString.' - ); - } - - /** - * Initializes and returns a new instance for testing. - * - * This method has no return value, due to inheritance overriding. - * - * @param string $initialValue - Instance's initial value. - * - * @since 1.0.0 - * @return MutableString - */ - protected function initializeInstance(string $initialValue) - { - return MutableString::fromString($initialValue); - } - - /** - * Checks instance can be cloned. - * - * @since 1.0.0 - * @return void - */ - public function testCanCloneObject(): void - { - // Performs test. - $original = $this->initializeInstance("Immutable string."); - $other = $original->toImmutable(); - - // Performs assertions. - $this->assertInstanceOf( - ImmutableString::class, - $other, - 'Instance type, does not match ImmutableString.' - ); - $this->assertEquals( - $original->__toString(), - $other->__toString(), - 'Instance values do not match.' - ); - $this->assertFalse( - ($original === $other), - 'Instances are meant to match.' - ); - } -} diff --git a/tests/Unit/Scalar/ReadonlyBooleanTest.php b/tests/Unit/Scalar/ReadonlyBooleanTest.php deleted file mode 100644 index 4c03dfd..0000000 --- a/tests/Unit/Scalar/ReadonlyBooleanTest.php +++ /dev/null @@ -1,333 +0,0 @@ - - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ReadonlyBooleanTest extends AbstractBaseTestCase -{ - /** - * Performs various shared instance's tests. - * - * @param AbstractReadBoolean $instance - Instance to perform tests on. - * - * @since 1.0.0 - * @return void - */ - protected function instanceChecks(AbstractReadBoolean $instance): void - { - $this->assertInstanceOf( - ReadonlyBoolean::class, - $instance, - "Loaded instance doesn't seam to be from a ReadonlyBoolean type." - ); - } - - /** - * Tests loading from an empty string breaks. - * - * @since 1.0.0 - * @return void - */ - public function testBreaksIfLoadedWithEmptyString(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - ReadonlyBoolean::fromString(''); - } - - /** - * Tests that a filled boolean can be loaded successfully. - * - * @since 1.0.0 - * @return void - */ - public function testCanSuccessfullyLoadPositiveFloat(): void - { - // Performs test. - $value = ReadonlyBoolean::fromFloat(1.0); - - // Performs assertions. - $this->assertEquals( - 'True', - $value->toString(), - 'Boolean value does not seam to match.' - ); - $this->instanceChecks($value); - } - - /** - * Tests that a filled boolean can be loaded successfully. - * - * @since 1.0.0 - * @return void - */ - public function testCanSuccessfullyLoadNegativeFloat(): void - { - // Performs test. - $test = -1; - $value = ReadonlyBoolean::fromFloat($test); - - // Performs assertions. - $this->assertEquals( - 'False', - $value->toString(), - 'Boolean value does not seam to match.' - ); - $this->instanceChecks($value); - } - - /** - * Tests that Yes/No strings can be loaded as booleans. - * - * @since 1.0.0 - * @return void - */ - public function testCanSuccessfullyLoadYesNoString(): void - { - // Perform test. - $yes = ReadonlyBoolean::fromString('Yes'); - $no = ReadonlyBoolean::fromString('No'); - - // Performs assertions. - $this->assertEquals( - 'True', - $yes->toString(), - 'Boolean value does not seam to match.' - ); - $this->assertEquals( - 'False', - $no->toString(), - 'Boolean value does not seam to match.' - ); - $this->instanceChecks($yes); - $this->instanceChecks($no); - } - - /** - * Tests that True/False strings can be loaded as booleans. - * - * @since 1.0.0 - * @return void - */ - public function testCanSuccessfullyLoadTrueFalseString(): void - { - // Perform test. - $true = ReadonlyBoolean::fromString('True'); - $false = ReadonlyBoolean::fromString('False'); - - // Performs assertions. - $this->assertEquals( - 'True', - $true->toString(), - 'Boolean value does not seam to match.' - ); - $this->assertEquals( - 'False', - $false->toString(), - 'Boolean value does not seam to match.' - ); - $this->instanceChecks($true); - $this->instanceChecks($false); - } - - /** - * Tests that 1/0 strings can be loaded as booleans. - * - * @since 1.0.0 - * @return void - */ - public function testCanSuccessfullyLoadOneZeroStrings(): void - { - // Perform test. - $one = ReadonlyBoolean::fromString('1'); - $zero = ReadonlyBoolean::fromString('0'); - - // Performs assertions. - $this->assertEquals( - 'True', - $one->toString(), - 'Boolean value does not seam to match.' - ); - $this->assertEquals( - 'False', - $zero->toString(), - 'Boolean value does not seam to match.' - ); - $this->instanceChecks($one); - $this->instanceChecks($zero); - } - - /** - * Tests that 1/0 integers can be loaded as booleans. - * - * @since 1.0.0 - * @return void - */ - public function testCanSuccessfullyLoadOneZeroIntegers(): void - { - // Perform test. - $one = ReadonlyBoolean::fromInteger(1); - $zero = ReadonlyBoolean::fromInteger(0); - - // Performs assertions. - $this->assertEquals( - 'True', - $one->toString(), - 'Boolean value does not seam to match.' - ); - $this->assertEquals( - 'False', - $zero->toString(), - 'Boolean value does not seam to match.' - ); - $this->instanceChecks($one); - $this->instanceChecks($zero); - } - - /** - * Tests the integer can be converted to a float. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertToInteger(): void - { - // Perform test. - $one = ReadonlyBoolean::fromString('1'); - $zero = ReadonlyBoolean::fromString('0'); - - // Performs assertions. - $this->assertEquals( - '1', - $one->toInteger()->toString(), - 'Boolean value does not seam to match.' - ); - $this->assertEquals( - '0', - $zero->toInteger()->toString(), - 'Boolean value does not seam to match.' - ); - $this->instanceChecks($one); - $this->instanceChecks($zero); - } - - /** - * Tests that can be successfully converted to a String. - * - * @since 1.0.0 - * @return void - */ - public function testCanSuccessfullyEchoToString(): void - { - // Perform test. - $true = ReadonlyBoolean::fromString('True'); - - // Performs assertions. - $this->assertEquals( - 'True', - $true->toString(), - 'Boolean value does not seam to match.' - ); - $this->assertEquals( - 'true', - $true->__toString(), - 'Boolean value does not seam to match.' - ); - $this->instanceChecks($true); - } - - /** - * Tests that instances can be compared. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareEqualInstances(): void - { - // Perform test. - $string = ReadonlyBoolean::fromString('True'); - $number = ReadonlyBoolean::fromString('1'); - - // Performs assertions. - $this->assertTrue( - $string->equals($number), - 'Boolean value does not seam to match.' - ); - $this->instanceChecks($string); - $this->instanceChecks($number); - } - - /** - * Tests that instances can be compared. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareUnequalInstances(): void - { - // Perform test. - $string = ReadonlyBoolean::fromString('True'); - $number = ReadonlyBoolean::fromString('False'); - - // Performs assertions. - $this->assertFalse( - $string->equals($number), - 'Boolean values seam to match.' - ); - $this->instanceChecks($string); - $this->instanceChecks($number); - } - - /** - * Tests that instances can be compared. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareEqualNatives(): void - { - // Perform test. - $string = ReadonlyBoolean::fromString('True'); - $native = true; - - // Performs assertions. - $this->assertTrue( - $string->equalsNative($native), - 'Boolean value does not seam to match.' - ); - $this->instanceChecks($string); - } - - /** - * Tests that instances can be compared. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareUnequalNatives(): void - { - // Perform test. - $string = ReadonlyBoolean::fromString('True'); - $native = false; - - // Performs assertions. - $this->assertFalse( - $string->equalsNative($native), - 'Boolean values seam to match.' - ); - $this->instanceChecks($string); - } -} diff --git a/tests/Unit/Scalar/ReadonlyFloatTest.php b/tests/Unit/Scalar/ReadonlyFloatTest.php deleted file mode 100644 index 225c078..0000000 --- a/tests/Unit/Scalar/ReadonlyFloatTest.php +++ /dev/null @@ -1,509 +0,0 @@ - - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ReadonlyFloatTest extends AbstractBaseTestCase -{ - /** - * Performs various shared instance's tests. - * - * @param AbstractReadFloat $instance - Instance to perform tests on. - * - * @since 1.0.0 - * @return void - */ - protected function instanceChecks(AbstractReadFloat $instance): void - { - $this->assertInstanceOf( - ReadonlyFloat::class, - $instance, - "Loaded instance doesn't seam to be from a ReadonlyFloat type." - ); - } - - /** - * Assert can collect maximum and minimum values. - * - * @since 1.0.0 - * @return void - */ - public function testCanRetrieveMaximumAndMinimumAllowedValues(): void - { - $this->assertEquals( - PHP_FLOAT_MAX, - ReadonlyFloat::max(), - 'Values do not match.' - ); - $this->assertEquals( - PHP_FLOAT_MIN, - ReadonlyFloat::min(), - 'Values do not match.' - ); - } - - /** - * Tests that a filled float can be loaded successfully. - * - * @since 1.0.0 - * @return void - */ - public function testCanSuccessfullyLoadFilledFloat(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertEquals( - $test, - \intval($value->__toString()), - 'Float value does not seam to match.' - ); - $this->instanceChecks($value); - } - - /** - * Tests that a filled string can be loaded successfully. - * - * @since 1.0.0 - * @return void - */ - public function testCanSuccessfullyLoadFilledString(): void - { - // Performs test. - $test = "123.1"; - $value = ReadonlyFloat::fromString($test); - - // Performs assertions. - $this->assertEquals( - $test, - $value->__toString(), - 'Float value does not seam to match.' - ); - $this->instanceChecks($value); - } - - /** - * Tests that an empty string cannot be loaded successfully. - * - * @since 1.0.0 - * @return void - */ - public function testCanNotLoadSuccessfullyEmptyString(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - ReadonlyFloat::fromString(""); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareTwoEqualInstances(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertTrue( - $value->equals(ReadonlyFloat::fromFloat($test)), - 'Float values should have been equal.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareTwoUnequalInstances(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertFalse( - $value->equals(ReadonlyFloat::fromFloat(++$test)), - 'Float values should be unequal.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareTwoEqualValues(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertTrue( - $value->equalsNative($test), - 'Float values should have been equal.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareTwoUnequalValues(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertFalse( - $value->equalsNative(++$test), - 'Float values should be unequal.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsBiggerInstance(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertTrue( - $value->isBigger(ReadonlyFloat::fromFloat(--$test)), - 'Float should have been bigger.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsBiggerValue(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertTrue( - $value->isBiggerNative(--$test), - 'Float should have been bigger.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsNotBiggerInstance(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertFalse( - $value->isBigger(ReadonlyFloat::fromFloat(++$test)), - 'Float should have been smaller.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsNotBiggerValue(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertFalse( - $value->isBiggerNative($test), - 'Float should have been smaller.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsSmallerInstance(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertTrue( - $value->isSmaller(ReadonlyFloat::fromFloat(++$test)), - 'Float should have been smaller.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsSmallerValue(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertTrue( - $value->isSmallerNative(++$test), - 'Float should have been smaller.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsNotSmallerInstance(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertFalse( - $value->isSmaller(ReadonlyFloat::fromFloat(--$test)), - 'Float should have been smaller.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsNotSmallerValue(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertFalse( - $value->isSmallerNative($test), - 'Float should have been smaller.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can identify positive and negative integers. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateNegativeValue(): void - { - // Performs test. - $test = -123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertTrue( - $value->isNegative(), - 'Float should have been negative.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can identify positive and negative integers. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidatePositiveValue(): void - { - // Performs test. - $test = 123.0; - $value = ReadonlyFloat::fromFloat($test); - - // Performs assertions. - $this->assertFalse( - $value->isNegative(), - 'Float should have been positive.' - ); - $this->instanceChecks($value); - } - - /** - * Tests the integer can be correctly formatted. - * - * @since 1.0.0 - * @return void - */ - public function testCanFormatFloat(): void - { - // Performs test. - $value = ReadonlyFloat::fromString("1234560.1"); - $text = $value->format( - \NumberFormatter::create('en_US', \NumberFormatter::DECIMAL) - ); - - // Performs assertions. - $this->assertEquals( - "1,234,560.1", - $text->__toString(), - "The float wasn't formatted correctly." - ); - $this->assertInstanceOf( - ReadonlyString::class, - $text, - "Returned instance should have been of type ReadonlyString." - ); - } - - /** - * Tests the integer can be converted to a string. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertToString(): void - { - // Performs test. - $value = ReadonlyFloat::fromString("123456.1"); - $string = $value->toString(); - - // Performs assertions. - $this->assertEquals( - "123456.1", - $string->__toString(), - "The float wasn't converted to string correctly." - ); - $this->assertInstanceOf( - ReadonlyString::class, - $string, - "Returned instance should have been of type ReadonlyString." - ); - } - - /** - * Tests the integer can be converted to a float. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertToInteger(): void - { - // Performs test. - $value = ReadonlyFloat::fromString("123456.0"); - $integer = $value->toInteger(); - - // Performs assertions. - $this->assertEquals( - "123456", - $integer->__toString(), - "The float wasn't converted to integer correctly." - ); - $this->assertInstanceOf( - ReadonlyInteger::class, - $integer, - "Returned instance should have been of type ReadonlyInteger." - ); - } - - /** - * Tests that the instance can be converted to a Boolean. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertInstanceToBoolean(): void - { - // Performs test. - $true = ReadonlyFloat::fromFloat(123.0); - $false = ReadonlyFloat::fromFloat(0.0); - $trueBoolean = $true->toBoolean(); - $falseBoolean = $false->toBoolean(); - - // Performs assertions. - $this->assertInstanceOf( - ReadonlyBoolean::class, - $trueBoolean, - 'Instance type, does not match ReadonlyBoolean.' - ); - $this->assertEquals( - 'True', - $trueBoolean->toString(), - 'Instance value is not correct.' - ); - $this->assertInstanceOf( - ReadonlyBoolean::class, - $falseBoolean, - 'Instance type, does not match ReadonlyBoolean.' - ); - $this->assertEquals( - 'False', - $falseBoolean->toString(), - 'Instance value is not correct.' - ); - } -} diff --git a/tests/Unit/Scalar/ReadonlyIntegerTest.php b/tests/Unit/Scalar/ReadonlyIntegerTest.php deleted file mode 100644 index 208da00..0000000 --- a/tests/Unit/Scalar/ReadonlyIntegerTest.php +++ /dev/null @@ -1,507 +0,0 @@ - - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ReadonlyIntegerTest extends AbstractBaseTestCase -{ - /** - * Performs various shared instance's tests. - * - * @param AbstractReadInteger $instance - Instance to perform tests on. - * - * @since 1.0.0 - * @return void - */ - protected function instanceChecks(AbstractReadInteger $instance): void - { - $this->assertInstanceOf( - ReadonlyInteger::class, - $instance, - "Loaded instance doesn't seam to be from a ReadonlyInteger type." - ); - } - - /** - * Assert can collect maximum and minimum values. - * - * @since 1.0.0 - * @return void - */ - public function testCanRetrieveMaximumAndMinimumAllowedValues(): void - { - $this->assertEquals( - PHP_INT_MAX, - ReadonlyInteger::max(), - 'Values do not match.' - ); - $this->assertEquals( - PHP_INT_MIN, - ReadonlyInteger::min(), - 'Values do not match.' - ); - } - - /** - * Tests that a filled integer can be loaded successfully. - * - * @since 1.0.0 - * @return void - */ - public function testCanSuccessfullyLoadFilledInteger(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertEquals( - $test, - \intval($value->__toString()), - 'Integer value does not seam to match.' - ); - $this->instanceChecks($value); - } - - /** - * Tests that a filled string can be loaded successfully. - * - * @since 1.0.0 - * @return void - */ - public function testCanSuccessfullyLoadFilledString(): void - { - // Performs test. - $test = "123"; - $value = ReadonlyInteger::fromString($test); - - // Performs assertions. - $this->assertEquals( - $test, - $value->__toString(), - 'Integer value does not seam to match.' - ); - $this->instanceChecks($value); - } - - /** - * Tests that an empty string cannot be loaded successfully. - * - * @since 1.0.0 - * @return void - */ - public function testCanNotLoadSuccessfullyEmptyString(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - ReadonlyInteger::fromString(""); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareTwoEqualInstances(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertTrue( - $value->equals(ReadonlyInteger::fromInteger($test)), - 'Integer values should have been equal.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareTwoUnequalInstances(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertFalse( - $value->equals(ReadonlyInteger::fromInteger(++$test)), - 'Integer values should be unequal.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareTwoEqualValues(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertTrue( - $value->equalsNative($test), - 'Integer values should have been equal.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareTwoUnequalValues(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertFalse( - $value->equalsNative(++$test), - 'Integer values should be unequal.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsBiggerInstance(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertTrue( - $value->isBigger(ReadonlyInteger::fromInteger(--$test)), - 'Integer should have been bigger.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsBiggerValue(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertTrue( - $value->isBiggerNative(--$test), - 'Integer should have been bigger.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsNotBiggerInstance(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertFalse( - $value->isBigger(ReadonlyInteger::fromInteger(++$test)), - 'Integer should have been smaller.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsNotBiggerValue(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertFalse( - $value->isBiggerNative($test), - 'Integer should have been smaller.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsSmallerInstance(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertTrue( - $value->isSmaller(ReadonlyInteger::fromInteger(++$test)), - 'Integer should have been smaller.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsSmallerValue(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertTrue( - $value->isSmallerNative(++$test), - 'Integer should have been smaller.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsNotSmallerInstance(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertFalse( - $value->isSmaller(ReadonlyInteger::fromInteger(--$test)), - 'Integer should have been smaller.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can perform comparisons. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateIsNotSmallerValue(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertFalse( - $value->isSmallerNative($test), - 'Integer should have been smaller.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can identify positive and negative integers. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidateNegativeValue(): void - { - // Performs test. - $test = -123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertTrue( - $value->isNegative(), - 'Integer should have been negative.' - ); - $this->instanceChecks($value); - } - - /** - * Tests can identify positive and negative integers. - * - * @since 1.0.0 - * @return void - */ - public function testCanValidatePositiveValue(): void - { - // Performs test. - $test = 123; - $value = ReadonlyInteger::fromInteger($test); - - // Performs assertions. - $this->assertFalse( - $value->isNegative(), - 'Integer should have been positive.' - ); - $this->instanceChecks($value); - } - - /** - * Tests the integer can be correctly formatted. - * - * @since 1.0.0 - * @return void - */ - public function testCanFormatAnInteger(): void - { - // Performs test. - $value = ReadonlyInteger::fromString("123456"); - $text = $value->format(\NumberFormatter::create('en_US', \NumberFormatter::DEFAULT_STYLE)); - - // Performs assertions. - $this->assertEquals( - "123,456", - $text->__toString(), - "The integer wasn't formatted correctly." - ); - $this->assertInstanceOf( - ReadonlyString::class, - $text, - "Returned instance should have been of type ReadonlyString." - ); - } - - /** - * Tests the integer can be converted to a string. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertToString(): void - { - // Performs test. - $value = ReadonlyInteger::fromString("123456"); - $string = $value->toString(); - - // Performs assertions. - $this->assertEquals( - "123456", - $string->__toString(), - "The integer wasn't converted to string correctly." - ); - $this->assertInstanceOf( - ReadonlyString::class, - $string, - "Returned instance should have been of type ReadonlyString." - ); - } - - /** - * Tests the integer can be converted to a float. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertToFloat(): void - { - // Performs test. - $value = ReadonlyInteger::fromString("123456"); - $float = $value->toFloat(); - - // Performs assertions. - $this->assertEquals( - 123456.0, - $float->value(), - "The integer wasn't converted to float correctly." - ); - $this->assertInstanceOf( - ReadonlyFloat::class, - $float, - "Returned instance should have been of type ReadonlyFloat." - ); - } - - /** - * Tests that the instance can be converted to a Boolean. - * - * @since 1.0.0 - * @return void - */ - public function testCanConvertInstanceToBoolean(): void - { - // Performs test. - $true = ReadonlyInteger::fromInteger(123); - $false = ReadonlyInteger::fromInteger(0); - $trueBoolean = $true->toBoolean(); - $falseBoolean = $false->toBoolean(); - - // Performs assertions. - $this->assertInstanceOf( - ReadonlyBoolean::class, - $trueBoolean, - 'Instance type, does not match ReadonlyBoolean.' - ); - $this->assertEquals( - 'True', - $trueBoolean->toString(), - 'Instance value is not correct.' - ); - $this->assertInstanceOf( - ReadonlyBoolean::class, - $falseBoolean, - 'Instance type, does not match ReadonlyBoolean.' - ); - $this->assertEquals( - 'False', - $falseBoolean->toString(), - 'Instance value is not correct.' - ); - } -} diff --git a/tests/Unit/Scalar/ReadonlyStringTest.php b/tests/Unit/Scalar/ReadonlyStringTest.php deleted file mode 100644 index 3f46bbd..0000000 --- a/tests/Unit/Scalar/ReadonlyStringTest.php +++ /dev/null @@ -1,457 +0,0 @@ - - * @author Hugo Rafael Azevedo - * @license MIT - * @since 1.0.0 - */ -class ReadonlyStringTest extends AbstractBaseTestCase -{ - /** - * Tests that a filled string can be loaded successfully. - * - * @since 1.0.0 - * @return void - */ - public function testCanSuccessfullyLoadFilledString(): void - { - // Performs test. - $test = "This is a Testing String"; - $value = ReadonlyString::fromString($test); - - // Performs assertions. - $this->assertEquals( - $test, - $value->__toString(), - 'String value does not seam to match.' - ); - $this->assertEquals( - \strlen($test), - $value->length(), - 'String length does not seam to match.' - ); - } - - /** - * Tests that an empty string can be loaded successfully. - * - * @since 1.0.0 - * @return void - */ - public function testCanSuccessfullyLoadEmptyString(): void - { - // Performs test. - $value = ReadonlyString::fromString(""); - - // Performs assertions. - $this->assertEquals( - "", - $value->__toString(), - 'String value does not seam to match.' - ); - $this->assertEquals( - 0, - $value->length(), - 'String length does not seam to match.' - ); - } - - /** - * Tests instance can be cloned. - * - * @since 1.0.0 - * @return void - */ - public function testCanCopyInstance(): void - { - // Performs test. - $test = "This is a Testing String"; - $value1 = ReadonlyString::fromString($test); - $value2 = $value1->toReadonly(); - - // Performs assertions. - $this->assertTrue( - ($value1->__toString() === $value2->__toString()), - "Instance's values should have been equal." - ); - $this->assertFalse( - ($value1 === $value2), - "Instances should not have been the same." - ); - } - - /** - * Tests that equal strings are marked as equal. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareTwoStringsWithSameValue(): void - { - // Performs test. - $text = "This is a dummy string."; - $value1 = ReadonlyString::fromString($text); - $value2 = ReadonlyString::fromString($text); - - // Performs assertions. - $this->assertTrue( - $value1->equals($value2), - 'Both strings should have matched.' - ); - } - - /** - * Tests that different strings are not marked as equal. - * - * @since 1.0.0 - * @return void - */ - public function testCanCompareTwoStringsWithDifferentValue(): void - { - // Performs test. - $text = "This is a dummy string."; - $value1 = ReadonlyString::fromString($text); - $value2 = ReadonlyString::fromString($text . $text); - - // Performs assertions. - $this->assertFalse( - $value1->equals($value2), - 'Strings should not have matched.' - ); - } - - /** - * Tests that the indexOf of a string works properly. - * - * @since 1.0.0 - * @return void - */ - public function testIndexOfStringRelatedFunctionality(): void - { - // Performs test. - $text = "This is a dummy string."; - $value = ReadonlyString::fromString($text); - - // Performs assertions. - $this->assertEquals( - 10, - $value->indexOf("dummy"), - 'String index does not match.' - ); - $this->assertEquals( - 10, - $value->indexOf("dummy", -14), - 'String index does not match.' - ); - $this->assertEquals( - 5, - $value->indexOf("is", 4), - 'String index does not match.' - ); - $this->assertNull( - $value->indexOf("another"), - 'indexOf should have returned NULL.' - ); - $this->assertTrue( - $value->contains("is"), - 'String should have contained search.' - ); - $this->assertFalse( - $value->contains("another"), - 'String should not have contained search.' - ); - $this->assertTrue( - $value->startsWith("This"), - 'String should have started with search.' - ); - $this->assertFalse( - $value->startsWith("is"), - 'String should not have started with search.' - ); - $this->assertTrue( - $value->endsWith("string."), - 'String should have ended with search.' - ); - $this->assertFalse( - $value->endsWith("dummy"), - 'String should not have ended with search.' - ); - } - - /** - * Tests that indexOf doesn't accept an empty search. - * - * @since 1.0.0 - * @return void - */ - public function testIndexOfBreaksWithEmptySearch(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $value = ReadonlyString::fromString("This is a dummy string."); - $value->indexOf(""); - } - - /** - * Tests that indexOf doesn't accept an illegal starting search. - * - * @since 1.0.0 - * @return void - */ - public function testIndexOfBreaksWithInvalidPositiveStart(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $value = ReadonlyString::fromString("This is a dummy string."); - $value->indexOf("dummy", 30); - } - - /** - * Tests that indexOf doesn't accept an illegal starting search. - * - * @since 1.0.0 - * @return void - */ - public function testIndexOfBreaksWithInvalidNegativeStart(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $value = ReadonlyString::fromString("This is a dummy string."); - $value->indexOf("dummy", -30); - } - - /** - * Tests that contains() breaks with an empty Search value. - * - * @since 1.0.0 - * @return void - */ - public function testContainsBreakWithEmptySearch(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $value = ReadonlyString::fromString("This is a dummy string."); - $value->contains(""); - } - - /** - * Tests that startsWith() breaks with an empty Search value. - * - * @since 1.0.0 - * @return void - */ - public function testStartsWithBreakWithEmptySearch(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $value = ReadonlyString::fromString("This is a dummy string."); - $value->startsWith(""); - } - - /** - * Tests that endsWith() breaks with an empty Search value. - * - * @since 1.0.0 - * @return void - */ - public function testEndsWithBreakWithEmptySearch(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $value = ReadonlyString::fromString("This is a dummy string."); - $value->endsWith(""); - } - - /** - * Tests that the number of occurrences of a search, can be counted within the string. - * - * @since 1.0.0 - * @return void - */ - public function testCanCountNumberOfOccurrencesWithinString(): void - { - // Performs test. - $text = "This is a dummy string."; - $value = ReadonlyString::fromString($text); - - // Performs assertions. - $this->assertEquals( - 0, - $value->count("none"), - "String count for 'none' does not match." - ); - $this->assertEquals( - 1, - $value->count("dummy"), - "String count for 'dummy' does not match." - ); - $this->assertEquals( - 2, - $value->count("is"), - "String count for 'is' does not match." - ); - $this->assertEquals( - 1, - $value->count("is", 4), - "String count for 'is' does not match." - ); - $this->assertEquals( - 0, - $value->count("This", 4, 5), - "String count for 'This' does not match." - ); - $this->assertEquals( - 1, - $value->count("string", -8), - "String count for 'string' does not match." - ); - $this->assertEquals( - 1, - $value->count("dummy", -14, 6), - "String count for 'dummy' does not match." - ); - $this->assertEquals( - 1, - $value->count("dummy", -14, -2), - "String count for 'dummy' does not match." - ); - } - - /** - * Tests that count() breaks with an empty Search value. - * - * @since 1.0.0 - * @return void - */ - public function testCountBreakWithEmptySearch(): void - { - // Creates expectation. - $this->expectException(\InvalidArgumentException::class); - - // Performs test. - $value = ReadonlyString::fromString("This is a dummy string."); - $value->count(""); - } - - /** - * Tests that count() breaks with long Start value. - * - * @since 1.0.0 - * @return void - */ - public function testCountBreaksWithLongStart(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $text = "This is a dummy string."; - $value = ReadonlyString::fromString($text); - $value->count("search", (\strlen($text) + 1)); - } - - /** - * Tests that count() breaks with long negative Start value. - * - * @since 1.0.0 - * @return void - */ - public function testCountBreaksWithLongNegativeStart(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $text = "This is a dummy string."; - $value = ReadonlyString::fromString($text); - $value->count("search", (0 - \strlen($text) - 1)); - } - - /** - * Tests that count() breaks with long Length value. - * - * @since 1.0.0 - * @return void - */ - public function testCountBreaksWithLongLength(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $text = "This is a dummy string."; - $value = ReadonlyString::fromString($text); - $value->count("search", 0, (\strlen($text) + 1)); - } - - /** - * Tests that count() breaks with long negative Length value. - * - * @since 1.0.0 - * @return void - */ - public function testCountBreaksWithLongNegativeLength(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $text = "This is a dummy string."; - $value = ReadonlyString::fromString($text); - $value->count("search", 0, (0 - \strlen($text) - 1)); - } - - /** - * Tests that count() breaks with out of scope Length value. - * - * @since 1.0.0 - * @return void - */ - public function testCountBreaksWithOutOfScopeLength(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $text = "This is a dummy string."; - $value = ReadonlyString::fromString($text); - $value->count("search", -5, 6); - } - - /** - * Tests that count() breaks with out of scope negative Length value. - * - * @since 1.0.0 - * @return void - */ - public function testCountBreaksWithOutOfScopeNegativeLength(): void - { - // Creates expectation. - $this->expectException(\OutOfRangeException::class); - - // Performs test. - $text = "This is a dummy string."; - $value = ReadonlyString::fromString($text); - $value->count("search", -5, -6); - } -} diff --git a/tests/Unit/Scalar/StrTest.php b/tests/Unit/Scalar/StrTest.php new file mode 100644 index 0000000..0d7cc11 --- /dev/null +++ b/tests/Unit/Scalar/StrTest.php @@ -0,0 +1,1223 @@ +assertFalse( + ($original === $other), + 'Instances are not meant to match.' + ); + } + + /** @inheritDoc */ + protected function checkCorrectInstanceType(Str $instance): void + { + $this->assertInstanceOf( + Str::class, + $instance, + 'Instance type, does not match Str.' + ); + } + + /** + * Checks that Instance loads and holds value correctly. + * + * @return void + */ + public function checkLoadsDataCorrectly(): void + { + // Performs test. + $string = " Immutable string. "; + $instance = $this->getInstance($string); + + // Performs assertions. + $this->assertEquals( + $string, + (string) $instance, + 'Instance value does not seam to match.' + ); + $this->checkCorrectInstanceType($instance); + } + + /** + * Checks that String's length retrieval works. + * + * @return void + */ + public function testCanRetrieveLengthCorrectly(): void + { + // Performs test. + $string = " Immutable string. "; + $instance = $this->getInstance($string); + + // Performs assertions. + $this->assertEquals( + \strlen($string), + $instance->getLength(), + 'Instance character length does not seam to match.' + ); + } + + /** + * Checks that String's word count retrieval works. + * + * @return void + */ + public function testCanRetrieveWordCountCorrectly(): void + { + // Performs test. + $string = "This is my immutable string."; + $instance = $this->getInstance($string); + + // Performs assertions. + $this->assertEquals( + 5, + $instance->getWordCount(), + 'Instance word count does not seam to match.' + ); + } + + /** + * Tests that a string can be trimmed. + * + * @return void + */ + public function testCanTrimString(): void + { + // Performs test. + $original = $this->getInstance(" Immutable string. "); + $other = $original->trim(); + + // Performs assertions. + $this->assertEquals( + 'Immutable string.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a string can be trimmed only on the left. + * + * @return void + */ + public function testCanLeftTrimString(): void + { + // Performs test. + $original = $this->getInstance(" Immutable string. "); + $other = $original->trimLeft(); + + // Performs assertions. + $this->assertEquals( + 'Immutable string. ', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a string can be trimmed only on the right. + * + * @return void + */ + public function testCanRightTrimString(): void + { + // Performs test. + $original = $this->getInstance(" Immutable string. "); + $other = $original->trimRight(); + + // Performs assertions. + $this->assertEquals( + ' Immutable string.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a string can be converted to UPPER case. + * + * @return void + */ + public function testCanUpperCaseString(): void + { + // Performs test. + $original = $this->getInstance("Immutable string."); + $other = $original->toUpper(); + + // Performs assertions. + $this->assertEquals( + 'IMMUTABLE STRING.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a string can be converted to UPPER case. + * + * @return void + */ + public function testCanUpperCaseFirst(): void + { + // Performs test. + $original = $this->getInstance("immutable string."); + $other = $original->toUpperFirst(); + + // Performs assertions. + $this->assertEquals( + 'Immutable string.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a string can be convert to UPPER case, all words. + * + * @return void + */ + public function testCanUpperCaseWords(): void + { + // Performs test. + $original = $this->getInstance("immutable string."); + $other = $original->toUpperWords(); + + // Performs assertions. + $this->assertEquals( + 'Immutable String.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a string can be converted to LOWER case. + * + * @return void + */ + public function testCanLowerCaseString(): void + { + // Performs test. + $original = $this->getInstance("Immutable String."); + $other = $original->toLower(); + + // Performs assertions. + $this->assertEquals( + 'immutable string.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a string can be converted to LOWER case. + * + * @return void + */ + public function testCanLowerCaseFirst(): void + { + // Performs test. + $original = $this->getInstance("Immutable String."); + $other = $original->toLowerFirst(); + + // Performs assertions. + $this->assertEquals( + 'immutable String.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a string can be padded on the left. + * + * @return void + */ + public function testCanPadOnTheLeft(): void + { + // Performs test. + $string = "Immutable String."; + $original = $this->getInstance($string); + $other = $original->padLeft(\strlen($string) + 2); + + // Performs assertions. + $this->assertEquals( + ' Immutable String.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a string is not padded on the left, when the padding length is less than the string's length. + * + * @return void + */ + public function testCanPadOnTheLeftWithoutResult(): void + { + // Performs test. + $string = "Immutable String."; + $original = $this->getInstance($string); + $other = $original->padLeft(2); + + // Performs assertions. + $this->assertEquals( + $string, + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a string can be padded on the left. + * + * @return void + */ + public function testCanPadOnTheLeftExtra(): void + { + // Performs test. + $string = "Immutable String."; + $original = $this->getInstance($string); + $other = $original->padLeftExtra(2); + + // Performs assertions. + $this->assertEquals( + ' Immutable String.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that padding breaks. + * + * @return void + */ + public function testBreaksPaddingOnTheLeftWithInvalidLength(): void + { + // Creates expectation. + $this->expectException(ParameterOutOfRangeException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->padLeft(0); + } + + /** + * Tests that a string can be padded on the right. + * + * @return void + */ + public function testCanPadOnTheRight(): void + { + // Performs test. + $string = "Immutable String."; + $original = $this->getInstance($string); + $other = $original->padRight(\strlen($string) + 2); + + // Performs assertions. + $this->assertEquals( + 'Immutable String. ', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a string can be padded on the right. + * + * @return void + */ + public function testCanPadOnTheRightExtra(): void + { + // Performs test. + $string = "Immutable String."; + $original = $this->getInstance($string); + $other = $original->padRightExtra(2); + + // Performs assertions. + $this->assertEquals( + 'Immutable String. ', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that padding breaks. + * + * @return void + */ + public function testCanPadOnTheRightBreaksWithInvalidLength(): void + { + // Creates expectation. + $this->expectException(ParameterOutOfRangeException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->padRight(0); + } + + /** + * Tests that a simple substring can be retrieved. + * + * @return void + */ + public function testSubStringCanBeRetrieved(): void + { + // Performs test. + $original = $this->getInstance("Immutable String."); + $other = $original->subString(10); + + // Performs assertions. + $this->assertEquals( + 'String.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a simple substring can be retrieved. + * + * @return void + */ + public function testSubStringCanBeRetrievedWithNegativeStart(): void + { + // Performs test. + $original = $this->getInstance("Immutable String."); + $other = $original->subString(-7); + + // Performs assertions. + $this->assertEquals( + 'String.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a simple substring can be retrieved. + * + * @return void + */ + public function testSubStringCanBeRetrievedWithLength(): void + { + // Performs test. + $original = $this->getInstance("Immutable String."); + $other = $original->subString(2, 7); + + // Performs assertions. + $this->assertEquals( + 'mutable', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a simple substring can be retrieved. + * + * @return void + */ + public function testSubStringCanBeRetrievedWithNegativeLength(): void + { + // Performs test. + $original = $this->getInstance("Immutable String."); + $other = $original->subString(2, -8); + + // Performs assertions. + $this->assertEquals( + 'mutable', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that substring breaks. + * + * @return void + */ + public function testBreaksSubStringWithShortStart(): void + { + // Creates expectation. + $this->expectException(ParameterOutOfRangeException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->subString(-30); + } + + /** + * Tests that substring breaks. + * + * @return void + */ + public function testBreaksSubStringWithLongStart(): void + { + // Creates expectation. + $this->expectException(ParameterOutOfRangeException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->subString(30); + } + + /** + * Tests that substring breaks. + * + * @return void + */ + public function testBreaksSubStringWithShortLength(): void + { + // Creates expectation. + $this->expectException(ParameterOutOfRangeException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->subString(0, -30); + } + + /** + * Tests that substring breaks. + * + * @return void + */ + public function testBreaksSubStringWithLongLength(): void + { + // Creates expectation. + $this->expectException(ParameterOutOfRangeException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->subString(0, 30); + } + + /** + * Tests that a simple subLeft can be retrieved. + * + * @return void + */ + public function testSubStringLeftCanBeRetrieved(): void + { + // Performs test. + $original = $this->getInstance("Immutable String."); + $other = $original->subLeft(10); + + // Performs assertions. + $this->assertEquals( + 'Immutable ', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that subLeft() breaks. + * + * @return void + */ + public function testBreaksSubStringLeftWithShortLength(): void + { + // Creates expectation. + $this->expectException(ParameterOutOfRangeException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->subLeft(-1); + } + + /** + * Tests that subLeft() breaks. + * + * @return void + */ + public function testBreaksSubStringLeftWithLongLength(): void + { + // Creates expectation. + $this->expectException(ParameterOutOfRangeException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->subLeft(30); + } + + /** + * Tests that a simple subRight() can be retrieved. + * + * @return void + */ + public function testSubStringRightCanBeRetrieved(): void + { + // Performs test. + $original = $this->getInstance("Immutable String."); + $other = $original->subRight(10); + + // Performs assertions. + $this->assertEquals( + 'le String.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that subRight() breaks. + * + * @return void + */ + public function testBreaksSubStringRightWithShortLength(): void + { + // Creates expectation. + $this->expectException(ParameterOutOfRangeException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->subRight(-1); + } + + /** + * Tests that subRight() breaks. + * + * @return void + */ + public function testBreaksSubStringRightWithLongLength(): void + { + // Creates expectation. + $this->expectException(ParameterOutOfRangeException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->subRight(30); + } + + /** + * Tests that a string can be reversed. + * + * @return void + */ + public function testStringCanBeReversed(): void + { + // Performs test. + $original = $this->getInstance("Immutable String."); + $other = $original->reverse(); + + // Performs assertions. + $this->assertEquals( + '.gnirtS elbatummI', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Checks Matching between two distinct strings. + * + * @return void + */ + public function testCanCheckMatching(): void + { + // Performs test. + $original = $this->getInstance(" Immutable string. "); + $other = $this->getInstance(" Immutable string. "); + + // Performs assertions. + $this->assertTrue( + $original->match($other), + 'Instance values do not match.' + ); + } + + /** + * Checks Equality of two distinct strings. + * + * @return void + */ + public function testCanCheckEquality(): void + { + // Performs test. + $original = $this->getInstance(" Immutable string. "); + + // Performs assertions. + $this->assertTrue( + $original->equals(" Immutable string. "), + 'Instance values do not match.' + ); + } + + /** + * Tests that we can check of String contains a text portion. + * + * @return void + */ + public function testCanCheckIfStringContainsPortion(): void + { + // Performs test. + $original = $this->getInstance("Immutable string."); + + // Performs assertions. + $this->assertTrue( + $original->contains('muta'), + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($original); + } + + /** + * Tests that method breaks if invalid parameters are passed. + * + * @return void + */ + public function testBreaksCheckingIfStringContainsPortionWithEmptyText(): void + { + // Performs test. + $original = $this->getInstance("Immutable string."); + + // Create expectations. + $this->expectException(NonEmptyStringException::class); + + // Performs test. + $original->contains(''); + } + + /** + * Test indexOf() method. + * + * @return void + */ + public function testCanRetrieveIndexOf(): void + { + // Performs test. + $instance = $this->getInstance("Immutable string."); + $indexOf = $instance->indexOf('string'); + + // Performs assertions. + $this->assertEquals( + 10, + $indexOf, + 'Retrieved index is incorrect.' + ); + } + + /** + * Test indexOf() method. + * + * @return void + */ + public function testCanRetrieveIndexOfWithStart(): void + { + // Performs test. + $instance = $this->getInstance("Immutable string, or part of another string."); + $indexOf = $instance->indexOf('string', 15); + + // Performs assertions. + $this->assertEquals( + 37, + $indexOf, + 'Retrieved index is incorrect.' + ); + } + + /** + * Test indexOf() method. + * + * @return void + */ + public function testIndexOfReturnsNullIfNotFound(): void + { + // Performs test. + $instance = $this->getInstance("Immutable string."); + $indexOf = $instance->indexOf('another'); + + // Performs assertions. + $this->assertNull( + $indexOf, + 'Retrieved index should have been NULL.' + ); + } + + /** + * Test indexOf() breaks with illegal parameters. + * + * @return void + */ + public function testBreaksIndexOfIfEmptySearch(): void + { + // Creates expectation. + $this->expectException(NonEmptyStringException::class); + + // Performs test. + $instance = $this->getInstance("Immutable string."); + $instance->indexOf(''); + } + + /** + * Tests that we can check is a string begins with a given text. + * + * @return void + */ + public function testCanCheckIfStringStartsWithText(): void + { + // Performs test. + $instance = $this->getInstance("Immutable string."); + $true = $instance->startsWith("Immu"); + $false = $instance->startsWith("string"); + + // Performs assertions. + $this->assertTrue($true, 'String should have started with searched text.'); + $this->assertFalse($false, 'String should not have started with searched text.'); + } + + /** + * Tests that we can check is a string begins with a given text. + * + * @return void + */ + public function testBreaksCheckingIfStringStartsWithText(): void + { + // Creates expectation. + $this->expectException(NonEmptyStringException::class); + + // Performs test. + $instance = $this->getInstance("Immutable string."); + $instance->startsWith(''); + } + + /** + * Tests that we can check is a string begins with a given text. + * + * @return void + */ + public function testCanCheckIfStringEndsWithText(): void + { + // Performs test. + $instance = $this->getInstance("Immutable string."); + $true = $instance->endsWith("string."); + $false = $instance->endsWith("Immu"); + + // Performs assertions. + $this->assertTrue($true, 'String should have ended with searched text.'); + $this->assertFalse($false, 'String should not have ended with searched text.'); + } + + /** + * Tests that we can check is a string begins with a given text. + * + * @return void + */ + public function testBreaksCheckingIfStringEndsWithText(): void + { + // Creates expectation. + $this->expectException(NonEmptyStringException::class); + + // Performs test. + $instance = $this->getInstance("Immutable string."); + $instance->endsWith(''); + } + + /** + * Tests count() method. + * + * @return void + */ + public function testCanCountNumberOccurencesInString(): void + { + // Performs test. + $instance = $this->getInstance("Immutable string, or part of another string."); + $count = $instance->count('string'); + + // Performs assertions. + $this->assertEquals( + 2, + $count, + 'Retrieved count is incorrect.' + ); + } + + /** + * Tests count() method. + * + * @return void + */ + public function testCanCountNumberOccurencesInStringWithStartAndLength(): void + { + // Performs test. + $instance = $this->getInstance("Immutable string, or part of another string."); + $count = $instance->count('string', 15, 29); + + // Performs assertions. + $this->assertEquals( + 1, + $count, + 'Retrieved count is incorrect.' + ); + } + + /** + * Tests count() method. + * + * @return void + */ + public function testCanCountNumberOccurencesInStringIfNoneIsFound(): void + { + // Performs test. + $instance = $this->getInstance("Immutable string, or part of another string."); + $count = $instance->count('string', 38, 4); + + // Performs assertions. + $this->assertEquals( + 0, + $count, + 'Retrieved count is incorrect.' + ); + } + + /** + * Tests count() breaks if empty search is supplied. + * + * @return void + */ + public function testBreaksCountNumberOccurencesInStringIfSerachIsEmpty(): void + { + // Creates expectation. + $this->expectException(NonEmptyStringException::class); + + // Performs test. + $instance = $this->getInstance("Immutable string, or part of another string."); + $instance->count(''); + } + + /** + * Tests that padding breaks. + * + * @return void + */ + public function testBreaksPaddingOnTheLeftWithInvalidPadString(): void + { + // Creates expectation. + $this->expectException(NonEmptyStringException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->padLeft(2, ''); + } + + /** + * Tests that padding breaks. + * + * @return void + */ + public function testBreaksCanPaddingOnTheLeftExtraWithInvalidPadString(): void + { + // Creates expectation. + $this->expectException(NonEmptyStringException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->padLeftExtra(2, ''); + } + + /** + * Tests that a string can be padded on the left. + * + * @return void + */ + public function testCanPadOnTheLeftWidthCharacter(): void + { + // Performs test. + $string = "Immutable String."; + $original = $this->getInstance($string); + $other = $original->padLeft(\strlen($string) + 2, '_'); + + // Performs assertions. + $this->assertEquals( + '__Immutable String.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a string can be padded on the left. + * + * @return void + */ + public function testCanPadOnTheLeftExtraWidthCharacter(): void + { + // Performs test. + $string = "Immutable String."; + $original = $this->getInstance($string); + $other = $original->padLeftExtra(2, '_'); + + // Performs assertions. + $this->assertEquals( + '__Immutable String.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that padding breaks. + * + * @return void + */ + public function testBreaksCanPadOnTheRightWithInvalidPadString(): void + { + // Creates expectation. + $this->expectException(NonEmptyStringException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->padRight(2, ''); + } + + /** + * Tests that padding breaks. + * + * @return void + */ + public function testBreaksCanPadOnTheRightExtraWithInvalidPadString(): void + { + // Creates expectation. + $this->expectException(NonEmptyStringException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->padRightExtra(2, ''); + } + + /** + * Tests that a string can be padded on the right. + * + * @return void + */ + public function testCanPadOnTheRightWidthCharacter(): void + { + // Performs test. + $string = "Immutable String."; + $original = $this->getInstance($string); + $other = $original->padRight(\strlen($string) + 2, '_'); + + // Performs assertions. + $this->assertEquals( + 'Immutable String.__', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that a string can be padded on the right. + * + * @return void + */ + public function testCanPadOnTheRightExtraWidthCharacter(): void + { + // Performs test. + $string = "Immutable String."; + $original = $this->getInstance($string); + $other = $original->padRightExtra(2, '_'); + + // Performs assertions. + $this->assertEquals( + 'Immutable String.__', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that text can be replaced in the String. + * + * @return void + */ + public function testTextCanBeReplacedInString(): void + { + // Performs test. + $original = $this->getInstance("Immutable String."); + $other = $original->replace('String', 'Object'); + + // Performs assertions. + $this->assertEquals( + 'Immutable Object.', + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that text is not replaced if search is not found. + * + * @return void + */ + public function testTextIsNotReplacedIfSearchNotFound(): void + { + // Performs test. + $original = $this->getInstance("Immutable String."); + $other = $original->replace('None', 'Object'); + + // Performs assertions. + $this->assertEquals( + (string) $original, + (string) $other, + 'Instance values do not match.' + ); + $this->checkCorrectInstanceType($other); + $this->checkDifferentInstances($original, $other); + } + + /** + * Tests that text replace breaks. + * + * @return void + */ + public function testBreaksTextReplaceIfSearchIsEmpty(): void + { + // Creates expectation. + $this->expectException(NonEmptyStringException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->replace('', 'Object'); + } + + /** + * Tests that text replace breaks. + * + * @return void + */ + public function testBreaksTextReplaceIfReplaceIsEmpty(): void + { + // Creates expectation. + $this->expectException(NonEmptyStringException::class); + + // Performs test. + $original = $this->getInstance("Immutable String."); + $original->replace('Object', ''); + } + + /** + * Tests that text can be exploded into an array. + * + * @return void + */ + public function testTextCanBeExplodedIntoAnArray(): void + { + // Performs test. + $original = $this->getInstance("This is an immutable String."); + $array = $original->explode(' '); + + // Performs assertions. + $this->assertIsArray($array); + $this->assertCount(5, $array); + } + + /** + * Tests that text can be exploded into an array. + * + * @return void + */ + public function testTextCanBeExplodedIntoAnArrayWithLimit(): void + { + // Performs test. + $original = $this->getInstance("This is an immutable String."); + $array = $original->explode(' ', 3); + + // Performs assertions. + $this->assertIsArray($array); + $this->assertCount(3, $array); + } + + /** + * Tests that text is not replaced if search is not found. + * + * @return void + */ + public function testBreaksIfReplacementIsEmpty(): void + { + // Creates expectation. + $this->expectException(NonEmptyStringException::class); + + // Performs test. + $original = $this->getInstance("This is an immutable String."); + $original->explode(''); + } +} diff --git a/tests/Unit/ValueObjects/AbstractValueObjectTest.php b/tests/Unit/ValueObjects/AbstractValueObjectTest.php new file mode 100644 index 0000000..34a1514 --- /dev/null +++ b/tests/Unit/ValueObjects/AbstractValueObjectTest.php @@ -0,0 +1,267 @@ +assertFalse(TestingValueObject::DATA['is_active']); + $this->assertFalse($valueObject->isActive()); + + // Test Rule's processing works. + $this->assertEquals( + 'MY TITLE', + (string) $valueObject->getTitle()->toUpper() + ); + + // Test field casting works. + $this->assertInstanceOf(Str::class, $valueObject->getTitle()); + } + + public function testBreaksIfRequiredFieldIsMissing(): void + { + $data = TestingValueObject::DATA; + unset($data['is_active']); + + $this->expectException(RequiredEntityValueMissingException::class); + + new TestingValueObject($data); + } + + public function testCanRetrieveFirstLevelAttributes(): void + { + $valueObject = new TestingValueObject( + TestingValueObject::DATA + ); + $attributes = $valueObject->getAttributes(); + + $this->assertArrayHasKey('active', $attributes); + $this->assertArrayHasKey('email', $attributes); + $this->assertArrayHasKey('title', $attributes); + $this->assertArrayNotHasKey('inner', $attributes); + } + + public function testCanConvertToJsonWhileGuardingCertainAttributes(): void + { + $valueObject = new TestingValueObject( + TestingValueObject::DATA + ); + $json = \json_decode( + \json_encode($valueObject), + true + ); + + $this->assertArrayHasKey('active', $json); + $this->assertArrayNotHasKey('email', $json); + $this->assertArrayHasKey('title', $json); + $this->assertArrayHasKey('inner', $json); + + $this->assertArrayHasKey('title', $json['inner']); + $this->assertArrayNotHasKey('active', $json['inner']); + } + + public function testCanConvertToArray(): void + { + $valueObject = new TestingValueObject( + TestingValueObject::DATA + ); + $array = $valueObject->toArray(); + + $this->assertArrayHasKey('active', $array); + $this->assertArrayHasKey('email', $array); + $this->assertArrayHasKey('title', $array); + $this->assertArrayHasKey('inner', $array); + + $this->assertArrayHasKey('title', $array['inner']); + $this->assertArrayHasKey('active', $array['inner']); + } + + public function testCanSerializeAndUnserialize(): void + { + $valueObject = new TestingValueObject( + TestingValueObject::DATA + ); + + $serialized = \serialize($valueObject); + $unserialized = \unserialize($serialized); + + $this->assertInstanceOf(TestingValueObject::class, $valueObject); + $this->assertInstanceOf(TestingValueObject::class, $unserialized); + $this->assertEquals( + $valueObject->isActive(), + $unserialized->isActive() + ); + $this->assertEquals( + (string) $valueObject->getTitle(), + (string) $unserialized->getTitle() + ); + } + + public function testCanCallDebugInfoInValueObject(): void + { + $valueObject = new TestingValueObject( + TestingValueObject::DATA + ); + $array = $valueObject->__debugInfo(); + + $this->assertArrayHasKey('active', $array); + $this->assertArrayHasKey('email', $array); + $this->assertArrayHasKey('title', $array); + $this->assertArrayHasKey('inner', $array); + + $this->assertArrayHasKey('title', $array['inner']); + $this->assertArrayHasKey('active', $array['inner']); + } + + public function testCanChangeAndTrackState(): void + { + $valueObject = new TestingValueObject( + TestingValueObject::DATA + ); + + // Checks initially loaded state. + $this->assertFalse($valueObject->isActive()); + $this->assertFalse($valueObject->getInner()->isActive()); + $mainUpdatedAt = $valueObject->getUpdatedAt(); + $innerUpdatedAt = $valueObject->getInner()->getUpdatedAt(); + + // Perform state change operations on Aggregate. + $valueObject->activate(); + $valueObject->changeTitle(Str::create('Main Title')); + + // Checks it's now marked as Dirty + $this->assertTrue($valueObject->isActive()); + $this->assertFalse($valueObject->getInner()->isActive()); + $this->assertTrue($valueObject->isDirty()); + $dirty = $valueObject->getDirty(true); + + // Checks Dirty array contains only fields that have changed. + $this->assertArrayHasKey('active', $dirty); + $this->assertArrayNotHasKey('email', $dirty); + $this->assertArrayHasKey('title', $dirty); + $this->assertArrayHasKey('inner', $dirty); + + $this->assertArrayHasKey('title', $dirty['inner']); + $this->assertArrayNotHasKey('active', $dirty['inner']); + + // Asserts that UpdatedAt DateTimes have updated with calls. + $this->assertNotEquals( + (string) $mainUpdatedAt->toDatetimeString(), + (string) $valueObject->getUpdatedAt()->toDatetimeString() + ); + $this->assertNotEquals( + (string) $innerUpdatedAt->toDatetimeString(), + (string) $valueObject->getInner()->getUpdatedAt()->toDatetimeString() + ); + + // Asserts that Original UpdatedAt DateTimes were kept. + $original = $valueObject->getOriginal(); + $this->assertEquals( + (string) $mainUpdatedAt->toDatetimeString(), + (string) $original['updated_at'] + ); + $this->assertEquals( + (string) $innerUpdatedAt->toDatetimeString(), + (string) $original['inner']['updated_at'] + ); + } + + public function testCanResetState(): void + { + $valueObject = new TestingValueObject( + TestingValueObject::DATA + ); + + // Checks initially loaded state. + $this->assertFalse($valueObject->isActive()); + $this->assertFalse($valueObject->getInner()->isActive()); + + // Perform state change operations on Aggregate. + $valueObject->activate(); + $valueObject->changeTitle(Str::create('Main Title')); + $valueObject->resetState(); + + // Checks it's now marked as Dirty + $this->assertTrue($valueObject->isActive()); + $this->assertFalse($valueObject->getInner()->isActive()); + $this->assertFalse($valueObject->isDirty()); + $dirty = $valueObject->getDirty(true); + + // Checks Dirty array contains only fields that have changed. + $this->assertArrayNotHasKey('active', $dirty); + $this->assertArrayNotHasKey('email', $dirty); + $this->assertArrayNotHasKey('title', $dirty); + $this->assertArrayNotHasKey('inner', $dirty); + $this->assertCount(0, $dirty); + } + + public function testCanMassAssignValues(): void + { + $valueObject = new TestingValueObject( + TestingValueObject::DATA + ); + $mainUpdatedAt = $valueObject->getUpdatedAt(); + $innerUpdatedAt = $valueObject->getInner()->getUpdatedAt(); + + $valueObject->setAttributes([ + 'title' => 'Mass assigned Title', + 'inner' => [ + 'active' => true, + ], + ]); + + $dirty = $valueObject->getDirty(true); + + // Checks Dirty array contains only fields that have changed. + $this->assertArrayNotHasKey('email', $dirty); + $this->assertArrayHasKey('title', $dirty); + $this->assertArrayHasKey('inner', $dirty); + + $this->assertArrayNotHasKey('title', $dirty['inner']); + $this->assertArrayHasKey('active', $dirty['inner']); + + // Asserts that Original UpdatedAt DateTimes were kept. + $original = $valueObject->getOriginal(); + $this->assertEquals( + (string) $mainUpdatedAt->toDatetimeString(), + (string) $original['updated_at'] + ); + $this->assertEquals( + (string) $innerUpdatedAt->toDatetimeString(), + (string) $original['inner']['updated_at'] + ); + } + + public function testBreaksWhenMassAssignmentIsEmpty(): void + { + $this->expectException(ParameterOutOfRangeException::class); + + $valueObject = new TestingValueObject( + TestingValueObject::DATA + ); + + $valueObject->setAttributes([]); + } +} diff --git a/tests/Unit/ValueObjects/TestingAggregate.php b/tests/Unit/ValueObjects/TestingAggregate.php new file mode 100644 index 0000000..469c2ff --- /dev/null +++ b/tests/Unit/ValueObjects/TestingAggregate.php @@ -0,0 +1,34 @@ +string = $string; + $this->datetime = $datetime; + $this->vo = $vo; + $this->boolean = $boolean; + } +} diff --git a/tests/Unit/ValueObjects/TestingAggregateTest.php b/tests/Unit/ValueObjects/TestingAggregateTest.php new file mode 100644 index 0000000..2d047b2 --- /dev/null +++ b/tests/Unit/ValueObjects/TestingAggregateTest.php @@ -0,0 +1,43 @@ +assertIsArray($array); + $this->assertArrayHasKey('string', $array); + $this->assertArrayHasKey('datetime', $array); + $this->assertArrayHasKey('vo', $array); + $this->assertArrayHasKey('boolean', $array); + } +} diff --git a/tests/Unit/ValueObjects/TestingNestedValueObject.php b/tests/Unit/ValueObjects/TestingNestedValueObject.php new file mode 100644 index 0000000..e2b4d7a --- /dev/null +++ b/tests/Unit/ValueObjects/TestingNestedValueObject.php @@ -0,0 +1,56 @@ +addHours(-1); + } + + return $fields; + } + + public function activate(): void + { + $this->active = true; + $this->triggerOnUpdate(); + } + + public function changeTitle(Str $title): void + { + $this->title = $title; + $this->triggerOnUpdate(); + } +} diff --git a/tests/Unit/ValueObjects/TestingValueObject.php b/tests/Unit/ValueObjects/TestingValueObject.php new file mode 100644 index 0000000..8b9eedb --- /dev/null +++ b/tests/Unit/ValueObjects/TestingValueObject.php @@ -0,0 +1,101 @@ + 123, + 'is_active' => false, + 'address' => 'user@domain.tld', + 'title' => 'my title', + 'inner' => [ + 'active' => false, + 'title' => 'My Inner Title', + ], + ]; + + use HasPositiveIntegerIDTrait, + HasActiveTrait, + CanMassAssignStateTrait, + CanProcessEntityStateTrait, + CanProcessOnUpdateEventsTrait, + HasEmailTrait, + HasTitleTrait, + HasUpdatableUpdatedAtTrait; + + /** @var array $guarded - List of fields that should not be serializable into JSON. */ + protected array $guarded = ['email']; + + protected array $maps = [ + 'is_active' => 'active', + 'address' => 'email', + ]; + + protected array $required = [ + 'active', + ]; + + protected TestingNestedValueObject $inner; + + protected function castInner(array $inner): void + { + $this->inner = new TestingNestedValueObject($inner); + } + + public function getInner(): TestingNestedValueObject + { + return $this->inner; + } + + /** + * Initial Rule's testing method. + * + * @param array $fields - Original fields being loaded into the Value Object. + * @return array + */ + protected function ruleSetUpdatedAtIfMissing(array $fields): array + { + if (!isset($fields['updated_at'])) { + $fields['updated_at'] = (string) Datetime::now()->addHours(-1); + } + + return $fields; + } + + public function activate(): void + { + $this->active = true; + + $this->triggerOnUpdate(); + } + + public function changeTitle(Str $title): void + { + $this->title = Str::create('My ' . (string) $title); + $this->inner->changeTitle($this->title->replace('My ', 'My Other ')); + + $this->triggerOnUpdate(); + } +} diff --git a/tests/Unit/Web/EmailAddressTest.php b/tests/Unit/Web/EmailAddressTest.php new file mode 100644 index 0000000..e82eada --- /dev/null +++ b/tests/Unit/Web/EmailAddressTest.php @@ -0,0 +1,146 @@ +assertInstanceOf( + EmailAddress::class, + $email, + 'Returned instance is not of type EmailAddress.' + ); + $this->assertEquals( + $emailString, + (string) $email, + 'Loaded state does not match inital value.' + ); + $this->assertEquals( + $emailString, + (string) $email->getAddress(), + 'Addresses do not match.' + ); + $this->assertEquals( + 'user', + (string) $email->getUsername(), + 'Usernames do not match.' + ); + $this->assertEquals( + 'domain', + (string) $email->getDomain(), + 'Domains do not match.' + ); + $this->assertEquals( + 'tld', + (string) $email->getTld(), + 'TLDs do not match.' + ); + } + + /** + * Tests supplied e-mail address is converted to lower case. + * + * @return void + */ + public function testConvertsToLowerCase(): void + { + // Performs test. + $emailString = 'User@DoMaiN.Tld'; + $email = EmailAddress::create($emailString); + + // Performs assertions. + $this->assertEquals( + \strtolower(\trim($emailString)), + (string) $email, + 'Trimmed and lower cased strings do not match.' + ); + } + + /** + * Tests breaks instantiation, if invalid data is supplied. + * + * @return void + */ + public function testBreaksIfEmptyAddressSupplied(): void + { + // Creates expectation. + $this->expectException(NonEmptyStringException::class); + + // Performs test. + EmailAddress::create(''); + } + + /** + * Tests breaks instantiation, if invalid data is supplied. + * + * @return void + */ + public function testBreaksIfInvalidAddressSupplied(): void + { + // Creates expectation. + $this->expectException(InvalidEmailException::class); + + // Performs test. + EmailAddress::create('This is not an email address'); + } + + /** + * Tests instance can be serialized and deserialized correctly. + * + * @return void + */ + public function testSerializesAndDeserializesCorrectly(): void + { + // Performs test. + $emailString = 'user@domain.tld'; + $email1 = EmailAddress::create($emailString); + + $serialized = \serialize($email1); + $email2 = \unserialize($serialized); + + // Performs assertions. + $this->assertInstanceOf( + EmailAddress::class, + $email1, + 'Returned instance is not of type EmailAddress.' + ); + $this->assertInstanceOf( + EmailAddress::class, + $email2, + 'Returned instance is not of type EmailAddress.' + ); + $this->assertEquals( + (string) $email1, + (string) $email2, + 'Addresses do not match.' + ); + $this->assertNotEquals( + $serialized, + (string) $email2, + 'Addresses do not match.' + ); + } +} diff --git a/tests/Unit/Web/README.md b/tests/Unit/Web/README.md new file mode 100644 index 0000000..accad97 --- /dev/null +++ b/tests/Unit/Web/README.md @@ -0,0 +1 @@ +# Web related Unit tests diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 2e46a13..9155980 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -4,12 +4,10 @@ * * For Unit testing only, please link PHPUnit to this file. This file will autoload Composer's dependencies. * - * @package Hradigital\Datatypes - * @copyright Hugo Rafael Azevedo - * @author Hugo Rafael Azevedo + * @package HraDigital\Datatypes + * @copyright HraDigital\Datatypes * @license MIT - * @since 1.0.0 */ -// Autoload Composer's dependencies, and Aesir-Base's namespace. +// Autoload Composer's dependencies. require __DIR__ . '/../vendor/autoload.php';