diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..38bd628 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,2 @@ +coverage_clover: build/reports/coveralls/clover.xml +json_path: build/reports/coveralls/upload.json diff --git a/.env.dist b/.env similarity index 80% rename from .env.dist rename to .env index 4c093b4..1adfa20 100644 --- a/.env.dist +++ b/.env @@ -1,6 +1,6 @@ -# ----------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ ### Docker -# ----------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # # All containers @@ -12,4 +12,4 @@ DOCKER_CONTAINER_PROJECT=common-library # PHP configuration: # - timezone # -TIMEZONE=Europe/Warsaw +PHP_DATE_TIMEZONE=Europe/London diff --git a/.gitignore b/.gitignore index cb92a06..52730bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,59 +1,19 @@ # ------------------------------------------------------------------------------ -### Environment-related parameters +### Linux template # ------------------------------------------------------------------------------ -.env +*~ +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* -# ------------------------------------------------------------------------------ -### Vendors -# ------------------------------------------------------------------------------ -/vendor/ +# KDE directory preferences +.directory +# Linux trash folder which might appear on any partition or disk +.Trash-* -# ------------------------------------------------------------------------------ -### Composer -# ------------------------------------------------------------------------------ -/composer.lock -/composer.phar - - -# ------------------------------------------------------------------------------ -### Phing -# ------------------------------------------------------------------------------ -/phing/properties - - -# ------------------------------------------------------------------------------ -### PHPUnit -# ------------------------------------------------------------------------------ -/phpunit.xml - - -# ------------------------------------------------------------------------------ -### PHP Coding Standards Fixer -# ------------------------------------------------------------------------------ -/.php_cs -/.php_cs.cache - - -# ----------------------------------------------------------------------------- -### Build files -# ----------------------------------------------------------------------------- -/build/ - - -# ------------------------------------------------------------------------------ -### Generated databases -# ------------------------------------------------------------------------------ -/data/tmp -*.sql -*.sqlite - - -# ------------------------------------------------------------------------------ -### JetBrains template -# ------------------------------------------------------------------------------ -/.idea +# .nfs files are created when an open file is removed but is still being accessed +.nfs* # ------------------------------------------------------------------------------ @@ -87,29 +47,12 @@ Temporary Items .apdisk -# ------------------------------------------------------------------------------ -### Linux template -# ------------------------------------------------------------------------------ -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - - # ------------------------------------------------------------------------------ ### Windows template # ------------------------------------------------------------------------------ # Windows thumbnail cache files Thumbs.db +Thumbs.db:encryptable ehthumbs.db ehthumbs_vista.db @@ -131,3 +74,131 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk + + +# ------------------------------------------------------------------------------ +### JetBrains template +# ------------------------------------------------------------------------------ +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + + +# ------------------------------------------------------------------------------ +### Build files +# ------------------------------------------------------------------------------ +/build/ + + +# ------------------------------------------------------------------------------ +### Phing +# ------------------------------------------------------------------------------ +/phing/properties + + +# ------------------------------------------------------------------------------ +### Infection +# ------------------------------------------------------------------------------ +/infection.json + + +# ------------------------------------------------------------------------------ +### Temporary data & databases +# ------------------------------------------------------------------------------ +/data/tmp +*.sqlite + + +# ------------------------------------------------------------------------------ +### Composer & vendors +# ------------------------------------------------------------------------------ +/composer.lock +/composer.phar +/vendor/ + + +# ------------------------------------------------------------------------------ +### PHPUnit +# ------------------------------------------------------------------------------ +/phpunit.xml +/.phpunit.result.cache +/tests/Resources/var + + +# ------------------------------------------------------------------------------ +### PHP Coding Standards Fixer +# ------------------------------------------------------------------------------ +/.php_cs +/.php_cs.cache + + +# ------------------------------------------------------------------------------ +### PHP_CodeSniffer +# ------------------------------------------------------------------------------ +/.phpcs-cache +/phpcs.xml diff --git a/.php_cs.dist b/.php_cs.dist index d2f2f3c..d925f44 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -1,27 +1,52 @@ in([ - __DIR__ . '/src', - __DIR__ . '/tests', + ->in(__DIR__) + ->exclude([ + 'build', + 'vendor', + ]) + ->notPath([ + 'tests/Resources/var/', ]); return PhpCsFixer\Config::create() ->setRules([ - '@Symfony' => true, - 'binary_operator_spaces' => [ + '@Symfony' => true, + '@PhpCsFixer' => true, + '@PHP71Migration' => true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => [ 'align_double_arrow' => true, ], - 'blank_line_before_return' => false, - 'cast_spaces' => false, - 'concat_space' => [ + 'blank_line_before_return' => false, + 'cast_spaces' => false, + 'concat_space' => [ 'spacing' => 'one', ], + 'ordered_class_elements' => [ + 'order' => [ + 'use_trait', + 'constant_public', + 'constant_protected', + 'constant_private', + 'property_public', + 'property_protected', + 'property_private', + 'construct', + 'destruct', + 'magic', + 'method_public', + 'method_protected', + 'method_private', + ], + ], 'phpdoc_add_missing_param_annotation' => true, - 'phpdoc_align' => false, - 'phpdoc_order' => true, - 'phpdoc_separation' => false, - 'phpdoc_summary' => false, - 'trim_array_spaces' => false, + 'phpdoc_align' => false, + 'phpdoc_order' => true, + 'phpdoc_separation' => false, + 'phpdoc_summary' => false, + 'trim_array_spaces' => false, ]) - ->setFinder($finder); + ->setFinder($finder) +; diff --git a/.travis-php-config.ini b/.travis-php-config.ini new file mode 100644 index 0000000..c0f1249 --- /dev/null +++ b/.travis-php-config.ini @@ -0,0 +1,2 @@ +date.timezone = Europe/London +memory_limit = 2G diff --git a/.travis.yml b/.travis.yml index f1bd310..8f3d997 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,23 @@ language: php php: - - 5.6 - - 7.0 - - 7.1 - 7.2 + - 7.3 + - 7.4 before_install: - sudo locale-gen de_DE.UTF-8 es_ES.UTF-8 en_GB.UTF-8 en_US.UTF-8 fr_FR.UTF-8 it_IT.UTF-8 pl_PL.UTF-8 ru_RU.UTF-8 - - composer global require hirak/prestissimo + - pear channel-discover pear.phing.info install: - - travis_wait 30 composer install -v + - pear install phing/phing + - phpenv rehash + +before_script: + - phpenv config-add .travis-php-config.ini script: - - php ./vendor/bin/phpunit + - phing + +after_success: + - travis_retry php vendor/bin/php-coveralls -v diff --git a/CHANGELOG.md b/CHANGELOG.md index 475b1be..f7ee8f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,106 @@ Common and useful classes, methods, exceptions etc. +# 1.1.8 + +1. [Composer] Upgrade all dev packages (to the latest, stable versions for PHP `7.4`) +2. [BaseCollection] Interfaces of different types of collections. May be used to build specific collections. + +# 1.1.7 + +1. [Arrays] Allow to define a key of next level elements in a function that returns elements from given level + +# 1.1.6 + +1. [Arrays] Function that returns elements from given level + +# 1.1.5 + +1. [BaseCollection] Prepare elements while adding them by `addMultiple()` method in the same way as passing them in + constructor. + +# 1.1.4 + +1. [BaseCollection] Fix incorrectly working limit() method + +# 1.1.3 + +1. Move `Renderable` class: `Meritoo\Common` -> `Meritoo\Common\Contract` +2. Create and implement `CollectionInterface` as contract of all collections (e.g. based on the `BaseCollection` class) + +# 1.1.2 + +1. Change mode of `Xdebug` to `coverage` in Docker's configuration to make it possible to generate code coverage by + `PHPUnit` +2. Mark PHPUnit test as risky when it does not have a `@covers` annotation + +# 1.1.1 + +1. [BaseCollection] Treat the `null` index as "no index" only while adding new element, iow. do not treat empty string + as "no index" behaviour. +2. [Miscellaneous] [Regex] Use simpler & stronger pattern to match name of file +3. Do not install `hirak/prestissimo` package while running Travis CI (incompatible with your PHP version, PHP + extensions and Composer version) +4. Use PHP `7.4` while running build in Travis CI + +# 1.1.0 + +1. Rename Meritoo\Common\Collection\Collection class to Meritoo\Common\Collection\BaseCollection. Add BaseCollection:: + isValidType() method to validate type of element before add it to collection. Add BaseCollection ::prepareElements() + method to allow preparation of elements in custom way. + +# 1.0.6 + +1. Use `.env` instead of `.env.dist` +2. Docker > use images (instead of Dockerfiles) +3. composer > squizlabs/php_codesniffer package > use ^3.4 (instead of ^2.9) +4. Do not require name of class by BaseTestCaseTrait::assertMethodVisibilityAndArguments() method +5. PHP CS Fixer > configuration > make more readable & remove unnecessary code +6. Update .gitignore, docker-compose.yml, phpunit.xml.dist + +# 1.0.5 + +1. Collection > trait > return "void" where "self" causes type hinting problem and is not required + +# 1.0.4 + +1. PHP Coding Standards Fixer > update configuration +2. Phing > tests > add task for Psalm (https://psalm.dev) +3. Collection > trait > split into smaller traits (to make it more flexible) + +# 1.0.3 + +1. Travis CI > run many tasks using Phing > update configuration +2. Template with placeholders > verification of placeholders without values > make stronger and point out which are + missing +3. Reflection > getPropertyValue() method > look for the property in parent classes + +# 1.0.2 + +1. Phing > remove old and unused tools +2. Phing > configuration > minor updates +3. Implement Mutation Testing Framework (infection/infection package) +4. Travis CI > run many tasks using Phing (instead of PHPUnit only) +5. Fix integration with [Coveralls](https://www.coveralls.io) (available as the badge in [README.md](README.md)) +6. Implement [PHPStan](https://github.com/phpstan/phpstan) +7. PHPUnit > execute tests in random order +8. Implement [Psalm](https://github.com/vimeo/psalm) +9. Infection (Mutation Testing Framework) > fix bugs while running (generate proper code coverage, bugs while running + tests randomly) +10. Phing > php-coveralls > add task + +# 1.0.1 + +1. Regex > make compatible with PHP 7.3 Tests > Regex > fix "preg_match(): Compilation failed: invalid range in + character class at offset 4" bug +2. Collection/storage of templates +3. Template with placeholders that may be filled by real data +4. RenderableInterface > something that may be rendered + +# 1.0.0 + +1. Composer > support/require PHP 7.2+ (instead of 5.6+) + # 0.1.8 1. Size, e.g. of image @@ -40,7 +140,8 @@ Common and useful classes, methods, exceptions etc. 1. Tests > refactoring & minor improvements 2. Utilities > CssSelector > useful methods related to CSS selectors -3. Utilities > Bootstrap4CssSelector > useful methods related to CSS selectors and the Bootstrap4 (front-end component library) +3. Utilities > Bootstrap4CssSelector > useful methods related to CSS selectors and the Bootstrap4 (front-end component + library) # 0.1.2 @@ -79,5 +180,6 @@ Common and useful classes, methods, exceptions etc. 4. StyleCI & PHP Coding Standards Fixer: update configuration 5. Documentation > Docker > add paragraph for PHP Coding Standards Fixer 6. Coding standard > fix automatically -7. StyleCI configuration > fix bug "The provided fixer 'binary_operator_spaces' cannot be enabled again because it was already enabled" +7. StyleCI configuration > fix bug "The provided fixer 'binary_operator_spaces' cannot be enabled again because it was + already enabled" 8. StyleCI > disable & remove diff --git a/README.md b/README.md index f3b06ea..c1c26b4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,12 @@ Common and useful classes, methods, exceptions etc. -[![PHP Version](https://img.shields.io/badge/php-%3E%3D5.6-blue.svg)](https://img.shields.io/badge/php-%3E%3D5.6-blue.svg) [![Travis](https://img.shields.io/travis/rust-lang/rust.svg?style=flat-square)](https://travis-ci.org/meritoo/common-library) [![Packagist](https://img.shields.io/packagist/v/meritoo/common-library.svg?style=flat-square)](https://packagist.org/packages/meritoo/common-library) [![license](https://img.shields.io/github/license/meritoo/common-library.svg?style=flat-square)](https://github.com/meritoo/common-library) [![GitHub commits](https://img.shields.io/github/commits-since/meritoo/common-library/0.0.1.svg?style=flat-square)](https://github.com/meritoo/common-library) [![Coverage Status](https://coveralls.io/repos/github/meritoo/common-library/badge.svg?branch=master)](https://coveralls.io/github/meritoo/common-library?branch=master) +![PHP from Packagist](https://img.shields.io/packagist/php-v/meritoo/common-library.svg?style=flat-square) +[![Build Status](https://travis-ci.com/meritoo/common-library.svg?branch=master&style=flat-square)](https://travis-ci.com/meritoo/common-library) +[![Packagist](https://img.shields.io/packagist/v/meritoo/common-library.svg?style=flat-square)](https://packagist.org/packages/meritoo/common-library) +[![license](https://img.shields.io/github/license/meritoo/common-library.svg?style=flat-square)](https://github.com/meritoo/common-library) +[![GitHub commits](https://img.shields.io/github/commits-since/meritoo/common-library/0.0.1.svg?style=flat-square)](https://github.com/meritoo/common-library) +[![Coverage Status](https://coveralls.io/repos/github/meritoo/common-library/badge.svg?branch=master&style=flat-square)](https://coveralls.io/github/meritoo/common-library) # Installation @@ -29,11 +34,12 @@ composer require wiosna-dev/common-library # Usage 1. [Base test case (with common methods and data providers)](docs/Base-test-case.md) -2. [Collection of elements](docs/Collection-of-elements.md) +2. [Collection of elements](docs/Collection/BaseCollection.md) 3. [Exceptions](docs/Static-methods.md) 4. [Static methods](docs/Static-methods.md) - 1. [Arrays](docs/Static-methods/Arrays.md) - 2. [Regex](docs/Static-methods/Regex.md) + 1. [Arrays](docs/Static-methods/Arrays.md) + 2. [Regex](docs/Static-methods/Regex.md) + 3. [Uri](docs/Static-methods/Uri.md) 5. [Value Objects](docs/Value-Objects.md) # Development diff --git a/VERSION b/VERSION index 699c6c6..18efdb9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.8 +1.1.8 diff --git a/build.xml b/build.xml index 594cf4b..5275a88 100644 --- a/build.xml +++ b/build.xml @@ -3,12 +3,12 @@ - + - + - + @@ -20,11 +20,11 @@ - + - + diff --git a/composer.json b/composer.json index 44804c5..f119e81 100644 --- a/composer.json +++ b/composer.json @@ -10,24 +10,25 @@ } ], "require": { + "php": "^7.4", "ext-dom": "*", "ext-fileinfo": "*", - "ext-json": "*", - "ext-simplexml": "*", - "php": ">=5.6", "ext-intl": "*", + "ext-json": "*", "ext-pcre": "*", - "doctrine/orm": "^2.5", - "gedmo/doctrine-extensions": "^2.4" + "ext-simplexml": "*", + "doctrine/orm": "^2.6", + "gedmo/doctrine-extensions": "^3.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.2", - "pdepend/pdepend": "^2.5", - "phploc/phploc": "^2.1", - "phpmd/phpmd": "^2.6", - "phpunit/phpunit": "^4.8", - "sebastian/phpcpd": "^2.0", - "squizlabs/php_codesniffer": "^2.9" + "friendsofphp/php-cs-fixer": "^3.11", + "infection/infection": "^0.26", + "php-coveralls/php-coveralls": "^2.5", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9", + "sebastian/phpcpd": "^6.0", + "squizlabs/php_codesniffer": "^3.7", + "vimeo/psalm": "^4.0" }, "autoload": { "psr-4": { diff --git a/docker-compose.yml b/docker-compose.yml index 6224f02..98a0357 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,29 +5,29 @@ services: # Required to run project # php: - image: ${DOCKER_CONTAINER_OWNER}/${DOCKER_CONTAINER_PROJECT}-php + image: meritoo/php:7.4 container_name: ${DOCKER_CONTAINER_OWNER}-${DOCKER_CONTAINER_PROJECT}-php entrypoint: php command: -S 0.0.0.0:9999 - build: - context: ./docker/config - args: - - TIMEZONE=${TIMEZONE} + environment: + PHP_DATE_TIMEZONE: ${PHP_DATE_TIMEZONE} volumes: - - .:/project:cached + - .:/var/www/application:cached composer: - image: ${DOCKER_CONTAINER_OWNER}/${DOCKER_CONTAINER_PROJECT}-php + image: meritoo/php:7.4 container_name: ${DOCKER_CONTAINER_OWNER}-${DOCKER_CONTAINER_PROJECT}-composer entrypoint: php -d memory_limit=-1 /usr/local/bin/composer volumes: - - .:/project:cached + - .:/var/www/application:cached # # Required to run PHPUnit's tests # phpunit: - image: ${DOCKER_CONTAINER_OWNER}/${DOCKER_CONTAINER_PROJECT}-php + image: meritoo/php:7.4 container_name: ${DOCKER_CONTAINER_OWNER}-${DOCKER_CONTAINER_PROJECT}-phpunit entrypoint: ./vendor/bin/phpunit command: --version volumes: - - .:/project:cached + - .:/var/www/application:cached + environment: + XDEBUG_MODE: coverage diff --git a/docker/config/Dockerfile b/docker/config/Dockerfile deleted file mode 100644 index 277e672..0000000 --- a/docker/config/Dockerfile +++ /dev/null @@ -1,131 +0,0 @@ -FROM php:5.6-cli -MAINTAINER Meritoo - -# -# Tools & libraries -# -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - vim \ - git \ - unzip \ - libicu-dev \ - locales \ - && apt-get clean \ - && rm -rf \ - /var/lib/apt/lists/* \ - /tmp/* \ - /var/tmp/* - -# -# Generating locales: -# - de_DE -# - es_ES -# - en_GB -# - en_US -# - fr_FR -# - it_IT -# - pl_PL -# - ru_RU -# -RUN sed -i 's/^# de_DE/de_DE/g; \ - s/^# es_ES/es_ES/g; \ - s/^# en_GB/en_GB/g; \ - s/^# en_US/en_US/g; \ - s/^# fr_FR/fr_FR/g; \ - s/^# it_IT/it_IT/g; \ - s/^# pl_PL/pl_PL/g; \ - s/^# ru_RU/ru_RU/g;' /etc/locale.gen \ - && locale-gen - -# -# Set default language -# -# Required to avoid problem with using strange language by error messages. -# Example: "chmod(): Aucun fichier ou dossier de ce type" -# -ENV LANGUAGE=en_US.UTF-8 - -# -# PHP extensions -# -RUN docker-php-ext-install \ - intl \ - mbstring - -# -# PHP extensions (PECL): -# - Xdebug -# -RUN pecl install \ - xdebug-2.5.5 \ - && docker-php-ext-enable \ - xdebug - -COPY xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini - -# -# PHP configuration: -# - default configuration -# - timezone -# -COPY php.ini /usr/local/etc/php/php.ini -ARG TIMEZONE -RUN ln -snf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \ - && echo ${TIMEZONE} > /etc/timezone \ - && printf '[PHP]\ndate.timezone = "%s"\n' ${TIMEZONE} > /usr/local/etc/php/conf.d/tzone.ini \ - && "date" -#RUN echo "\n""date.timezone = $TIMEZONE""\n" >> /usr/local/etc/php/php.ini - -# -# Phing -# -RUN pear channel-discover pear.phing.info \ - && pear install [--alldeps] phing/phing - -# -# Composer - environment variables: -# - disable warning about running commands as root/super user -# - disable automatic clearing of sudo sessions -# -# More: -# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser -# -ENV COMPOSER_ALLOW_SUPERUSER 1 - -# -# Composer + https://packagist.org/packages/hirak/prestissimo package -# -RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ - && php -r "if (hash_file('SHA384', 'composer-setup.php') === \ - '93b54496392c062774670ac18b134c3b3a95e5a5e5c8f1a9f115f203b75bf9a129d5daa8ba6a13e2cc8a1da0806388a8') { echo \ - 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \ - && php composer-setup.php --install-dir=/usr/local/bin --filename=composer \ - && php -r "unlink('composer-setup.php');" \ - && composer global require \ - --no-plugins \ - --no-scripts \ - --no-progress \ - --no-suggest \ - --no-interaction \ - --prefer-dist \ - --optimize-autoloader \ - --classmap-authoritative \ - hirak/prestissimo \ - && rm -rf ~/.composer/cache/* \ - && composer clear-cache \ - && composer --version - -# -# Bash -# -RUN sed -i 's/^# export/export/g; \ - s/^# alias/alias/g;' ~/.bashrc \ - && echo 'COLUMNS=200'"\n" >> ~/.bashrc - -# -# Use project-related binaries globally -# -ENV PATH="/project/vendor/bin:${PATH}" - -WORKDIR /project diff --git a/docker/config/php.ini b/docker/config/php.ini deleted file mode 100644 index b984c5d..0000000 --- a/docker/config/php.ini +++ /dev/null @@ -1,3 +0,0 @@ -display_errors = On -display_startup_errors = On -error_reporting = E_ALL diff --git a/docker/config/xdebug.ini b/docker/config/xdebug.ini deleted file mode 100644 index 22b2547..0000000 --- a/docker/config/xdebug.ini +++ /dev/null @@ -1,6 +0,0 @@ -[xdebug] -zend_extension=xdebug.so - -xdebug.remote_enable=1 -xdebug.remote_port=9001 -xdebug.remote_host=10.254.254.254 diff --git a/docs/Base-test-case.md b/docs/Base-test-case.md index b35c744..87e2a04 100644 --- a/docs/Base-test-case.md +++ b/docs/Base-test-case.md @@ -4,7 +4,15 @@ Common and useful classes, methods, exceptions etc. # Base test case (with common methods and data providers) -Located here: `Meritoo\Common\Test\Base\BaseTestCase`. Just extend the `BaseTestCase` class and use it like in `Meritoo\Common\Test\Utilities\DateTest` class: +Located here: `Meritoo\Common\Test\Base\BaseTestCase`. + +##### Usage + +1. Just extend the `BaseTestCase` class or implement `Meritoo\Common\Traits\Test\Base\BaseTestCaseTrait` trait. +2. Use one of available data providers, e.g. `@dataProvider provideEmptyValue`, or asserts, + e.g. `static::assertMethodVisibility($method, $visibilityType);` + +##### Examples ```php class DateTest extends BaseTestCase @@ -22,8 +30,6 @@ class DateTest extends BaseTestCase } ``` -or in `Meritoo\Common\Test\Utilities\MimeTypesTest` class: - ```php class MimeTypesTest extends BaseTestCase { @@ -45,11 +51,13 @@ class MimeTypesTest extends BaseTestCase # More 1. [**Base test case (with common methods and data providers)**](Base-test-case.md) -2. [Collection of elements](Collection-of-elements.md) -3. [Exceptions](Exceptions.md) -4. [Static methods](Static-methods.md) - 1. [Arrays](Static-methods/Arrays.md) - 2. [Regex](Static-methods/Regex.md) -5. [Value Objects](Value-Objects.md) +2. [Collection of elements](Collection/BaseCollection.md) +3. [Templates](Collection/Templates.md) +4. [Exceptions](Exceptions.md) +5. [Static methods](Static-methods.md) + 1. [Arrays](Static-methods/Arrays.md) + 2. [Regex](Static-methods/Regex.md) + 3. [Uri](Static-methods/Uri.md) +6. [Value Objects](Value-Objects.md) [‹ Back to `Readme`](../README.md) diff --git a/docs/Collection-of-elements.md b/docs/Collection-of-elements.md deleted file mode 100644 index 2493afc..0000000 --- a/docs/Collection-of-elements.md +++ /dev/null @@ -1,53 +0,0 @@ -# Meritoo Common Library - -Common and useful classes, methods, exceptions etc. - -# Collection of elements - -Located here: `Meritoo\Common\Collection\Collection`. It's a set of some elements, e.g. objects. It's iterable and countable. Provides very useful methods. Some of them: -- `getFirst()` - returns the first element in the collection -- `getLast()` - returns the last element in the collection -- `isEmpty()` - returns information if collection is empty -- `add($element, $index = null)` - adds given element (at the end of collection) -- `addMultiple($elements, $useIndexes = false)` - adds given elements (at the end of collection) -- `prepend($element)` - prepends given element (adds given element at the beginning of collection) -- `remove($element)` - removes given element - -Examples of usage below. - -### An empty collection - -```php -use Meritoo\Common\Collection\Collection; - -$emptyCollection = new Collection(); -var_dump($emptyCollection->isEmpty()); // bool(true) -``` - -### Simple collection - -```php -use Meritoo\Common\Collection\Collection; - -$elements = [ - 'lorem', - 'ipsum', - 123 => 'dolor', - 345 => 'sit', -]; - -$simpleCollection = new Collection($elements); -var_dump($simpleCollection->has('dolor')); // bool(true) -``` - -# More - -1. [Base test case (with common methods and data providers)](Base-test-case.md) -2. [**Collection of elements**](Collection-of-elements.md) -3. [Exceptions](Exceptions.md) -4. [Static methods](Static-methods.md) - 1. [Arrays](Static-methods/Arrays.md) - 2. [Regex](Static-methods/Regex.md) -5. [Value Objects](Value-Objects.md) - -[‹ Back to `Readme`](../README.md) diff --git a/docs/Collection/BaseCollection.md b/docs/Collection/BaseCollection.md new file mode 100644 index 0000000..b31e6a9 --- /dev/null +++ b/docs/Collection/BaseCollection.md @@ -0,0 +1,90 @@ +# Meritoo Common Library + +Common and useful classes, methods, exceptions etc. + +# BaseCollection + +### Namespace + +`Meritoo\Common\Collection\BaseCollection` + +### Info + +It's a set of some elements with the same type, e.g. objects. It's iterable and countable. Provides very useful methods. +Some of them: + +- `getFirst()` - returns the first element in the collection +- `getLast()` - returns the last element in the collection +- `isEmpty()` - returns information if collection is empty +- `add($element, $index = null)` - adds given element (at the end of collection) +- `addMultiple($elements, $useIndexes = false)` - adds given elements (at the end of collection) +- `prepend($element)` - prepends given element (adds given element at the beginning of collection) +- `remove($element)` - removes given element + +### Implementation + +You have to implement: + +```php +abstract protected function isValidType($element): bool; +``` + +This method verifies 1 element before it will be added to collection. Returns information if the element has valid, +expected type. + +Example (from `Meritoo\Common\Collection\Templates` class): + +```php +protected function isValidType($element): bool +{ + return $element instanceof Template; +} +``` + +### Methods to overwrite + +You can, if you wish, overwrite these methods: + +1. To prepare elements used to initialize the collection in your own way: + + ```php + protected function prepareElements(array $elements): array + ``` + +2. To validate type of elements in your own way: + + ```php + protected function getElementsWithValidType(array $elements): array + ``` + +### Examples of usage + +```php +use Meritoo\Common\Collection\StringCollection; + +$emptyCollection = new StringCollection(); +var_dump($emptyCollection->isEmpty()); // bool(true) + +$elements = [ + 'lorem', + 'ipsum', + 123 => 'dolor', + 345 => 'sit', +]; + +$simpleCollection = new StringCollection($elements); +var_dump($simpleCollection->has('dolor')); // bool(true) +``` + +# More + +1. [Base test case (with common methods and data providers)](../Base-test-case.md) +2. [**Collection of elements**](BaseCollection.md) +3. [Templates](Templates.md) +4. [Exceptions](../Exceptions.md) +5. [Static methods](../Static-methods.md) + 1. [Arrays](../Static-methods/Arrays.md) + 2. [Regex](../Static-methods/Regex.md) +6. [Value Objects](../Value-Objects.md) + +[‹ Back to `Readme`](../../README.md) diff --git a/docs/Collection/Templates.md b/docs/Collection/Templates.md new file mode 100644 index 0000000..48258f5 --- /dev/null +++ b/docs/Collection/Templates.md @@ -0,0 +1,67 @@ +# Meritoo Common Library + +Common and useful classes, methods, exceptions etc. + +# Templates + +### Namespace + +`Meritoo\Common\Collection\Templates` + +### Info + +Collection/storage of templates, instance of `Meritoo\Common\ValueObject\Template` class. + +##### New instance + +New instance can be created using: + +1. Constructor: + + ```php + new Templates([ + 'first' => new Template('First name: %first_name%'), + 'last' => new Template('Last name: %last_name%'), + ]); + ``` + +2. Static method `fromArray(array $templates)` - creates and returns the collection from given array + + ```php + Templates::fromArray([ + 'first' => 'First name: %first_name%', + 'last' => 'Last name: %last_name%', + ]); + ``` + +##### Methods + +Has all methods of parent class `Meritoo\Common\Collection\Collection` + `findTemplate(string $index)` method that finds +and returns template with given index. + +Example of usage: + +```php +$templates = new Templates([ + 'first' => new Template('First name: %first_name%'), + 'last' => new Template('Last name: %last_name%'), +]); + +$template = $templates->findTemplate('first'); // new Template('First name: %first_name%') +``` + +Throws an `Meritoo\Common\Exception\ValueObject\Template\TemplateNotFoundException` exception if template with given +index was not found. + +# More + +1. [Base test case (with common methods and data providers)](../Base-test-case.md) +2. [Collection of elements](BaseCollection.md) +3. [**Templates**](Templates.md) +4. [Exceptions](../Exceptions.md) +5. [Static methods](../Static-methods.md) + 1. [Arrays](../Static-methods/Arrays.md) + 2. [Regex](../Static-methods/Regex.md) +6. [Value Objects](../Value-Objects.md) + +[‹ Back to `Readme`](../../README.md) diff --git a/docs/Development.md b/docs/Development.md index 7f60fb0..589a44c 100644 --- a/docs/Development.md +++ b/docs/Development.md @@ -17,9 +17,9 @@ Development-related information 2. Rebuild project by running command (installs packages, prepares required directories and runs tests): - ```bash - docker-compose exec php phing - ``` + ```bash + docker-compose exec php phing + ``` > [What is Docker?](https://www.docker.com/what-docker) @@ -105,12 +105,48 @@ docker-compose exec php phing -f phing/tests.xml test:phpunit docker-compose run --rm phpunit --verbose --no-coverage ``` -# Versions of packages +# Infection - Mutation Testing -### squizlabs/php_codesniffer +Served by [Infection — Mutation Testing Framework](https://infection.github.io). -I have to use [squizlabs/php_codesniffer](https://packagist.org/packages/squizlabs/php_codesniffer) `^2.9` instead of -`^3.3`, because [Phing doesn't support 3.x PHP_CodeSniffer](https://github.com/phingofficial/phing/issues/716). +### Running tests + +```bash +$ docker-compose exec php bash +root@18f2f0cfaa5d:/var/www/application# XDEBUG_MODE=coverage ./vendor/bin/infection --threads=$(nproc) +``` + +or + +```bash +$ docker-compose exec php bash +root@18f2f0cfaa5d:/var/www/application# XDEBUG_MODE=coverage phing -f phing/tests.xml test:infection +``` + +### Result of testing + +##### Terminal + +Example of output: + +```bash +125 mutations were generated: + 105 mutants were killed + 3 mutants were not covered by tests + 5 covered mutants were not detected + 0 errors were encountered + 12 time outs were encountered + +Metrics: + Mutation Score Indicator (MSI): 93% + Mutation Code Coverage: 97% + Covered Code MSI: 95% +``` + +##### Stored in `build/reports/infection` directory + +* `build/reports/infection/infection-log.txt` +* `build/reports/infection/summary-log.txt` # Other diff --git a/docs/Exceptions.md b/docs/Exceptions.md index 14d7021..ccbb022 100644 --- a/docs/Exceptions.md +++ b/docs/Exceptions.md @@ -6,7 +6,8 @@ Common and useful classes, methods, exceptions etc. ### Create instance of exception -This package contains a lot of exceptions. Each of them contains static method `create()` with proper arguments that is used to create instance of the exception. Example: +This package contains a lot of exceptions. Each of them contains static method `create()` with proper arguments that is +used to create instance of the exception. Example: ```php use Meritoo\Common\Exception\Bundle\IncorrectBundleNameException; @@ -17,11 +18,14 @@ throw IncorrectBundleNameException::create('RisusIpsum'); ##### Short description -It's a `Meritoo\Common\Exception\Base\UnknownTypeException` class. Related to `Meritoo\Common\Type\Base\BaseType` class that represents type of something, e.g. type of button, order. +It's a `Meritoo\Common\Exception\Base\UnknownTypeException` class. Related to `Meritoo\Common\Type\Base\BaseType` class +that represents type of something, e.g. type of button, order. ##### Usage -You can extend `Meritoo\Common\Exception\Base\UnknownTypeException` class and create your own static method, e.g. `createException()`, which will be used create instance of the exception. Inside the `createException()` method you can call `parent::create()` method. +You can extend `Meritoo\Common\Exception\Base\UnknownTypeException` class and create your own static method, +e.g. `createException()`, which will be used create instance of the exception. Inside the `createException()` method you +can call `parent::create()` method. ##### Example @@ -54,11 +58,13 @@ class UnknownSimpleTypeException extends UnknownTypeException # More 1. [Base test case (with common methods and data providers)](Base-test-case.md) -2. [Collection of elements](Collection-of-elements.md) -3. [**Exceptions**](Exceptions.md) -4. [Static methods](Static-methods.md) - 1. [Arrays](Static-methods/Arrays.md) - 2. [Regex](Static-methods/Regex.md) -5. [Value Objects](Value-Objects.md) +2. [Collection of elements](Collection/BaseCollection.md) +3. [Templates](Collection/Templates.md) +4. [**Exceptions**](Exceptions.md) +5. [Static methods](Static-methods.md) + 1. [Arrays](Static-methods/Arrays.md) + 2. [Regex](Static-methods/Regex.md) + 3. [Uri](Static-methods/Uri.md) +6. [Value Objects](Value-Objects.md) [‹ Back to `Readme`](../README.md) diff --git a/docs/Static-methods.md b/docs/Static-methods.md index d1d2227..19c8fff 100644 --- a/docs/Static-methods.md +++ b/docs/Static-methods.md @@ -4,7 +4,8 @@ Common and useful classes, methods, exceptions etc. # Static methods -This package contains a lot of class with static methods, so usage is not so complicated. Just run the static method who would you like to use. Example: +This package contains a lot of class with static methods, so usage is not so complicated. Just run the static method who +would you like to use. Example: ```php use Meritoo\Common\Utilities\Arrays; @@ -16,11 +17,13 @@ var_dump($firstElement); // string(5) "lorem" # More 1. [Base test case (with common methods and data providers)](Base-test-case.md) -2. [Collection of elements](Collection-of-elements.md) -3. [Exceptions](Exceptions.md) -4. [**Static methods**](Static-methods.md) - 1. [Arrays](Static-methods/Arrays.md) - 2. [Regex](Static-methods/Regex.md) -5. [Value Objects](Value-Objects.md) +2. [Collection of elements](Collection/BaseCollection.md) +3. [Templates](Collection/Templates.md) +4. [Exceptions](Exceptions.md) +5. [**Static methods**](Static-methods.md) + 1. [Arrays](Static-methods/Arrays.md) + 2. [Regex](Static-methods/Regex.md) + 3. [Uri](Static-methods/Uri.md) +6. [Value Objects](Value-Objects.md) [‹ Back to `Readme`](../README.md) diff --git a/docs/Static-methods/Arrays.md b/docs/Static-methods/Arrays.md index b798505..ade1780 100644 --- a/docs/Static-methods/Arrays.md +++ b/docs/Static-methods/Arrays.md @@ -9,6 +9,31 @@ Common and useful classes, methods, exceptions etc. Class: `Meritoo\Common\Utilities\Arrays` File: `src/Utilities/Arrays.php` +### containsEmptyStringsOnly(array): bool + +> Returns information if given array contains an empty strings only + +##### Arguments + +- `array $array` - The array to verify + +##### Examples + +1) + +- array: `[]` (an empty array) +- result: `false` + +2) + +- array: `["", -1]` +- result: `false` + +3) + +- array: `["", null, ""]` +- result: `true` + ### getNonEmptyValues(array $values) > Returns non-empty values, e.g. without "" (empty string), null or [] @@ -17,17 +42,19 @@ File: `src/Utilities/Arrays.php` - `array $values` - The values to filter -##### Example 1 +##### Examples + +1) - values: `[]` (no values) - result: `[]` (an empty array) -##### Example 2 +2) - values: `[null, ""]` (all empty values) - result: `[]` (an empty array) -##### Example 3 +3) - values: `["test 1", "", 123, null, 0]` - result: `["test 1", 123, 0]` @@ -41,25 +68,27 @@ File: `src/Utilities/Arrays.php` - `array $values` - The values to filter - `[string $separator]` - (optional) Separator used to implode the values. Default: ", ". -##### Example 1 +##### Examples + +1) - values: `[]` (no values) - separator: default or any other string - result: `""` (an empty string) -##### Example 2 +2) - values: `[null, ""]` (all empty values) - separator: default or any other string - result: `""` (an empty string) -##### Example 3 +3) - values: `["test 1", "", 123, null, 0]` - separator: `", "` (default) - result: `"test 1, 123, 0"` -##### Example 4 +4) - values: `["test 1", "", 123, null, 0]` - separator: `" | "` @@ -68,11 +97,13 @@ File: `src/Utilities/Arrays.php` # More 1. [Base test case (with common methods and data providers)](../Base-test-case.md) -2. [Collection of elements](../Collection-of-elements.md) -3. [Exceptions](../Exceptions.md) -4. [Static methods](../Static-methods.md) - 1. [**Arrays**](Arrays.md) - 2. [Regex](Regex.md) -5. [Value Objects](../Value-Objects.md) +2. [Collection of elements](../Collection/BaseCollection.md) +3. [Templates](../Collection/Templates.md) +4. [Exceptions](../Exceptions.md) +5. [Static methods](../Static-methods.md) + 1. [**Arrays**](Arrays.md) + 2. [Regex](Regex.md) + 3. [Uri](Uri.md) +6. [Value Objects](../Value-Objects.md) [‹ Back to `Readme`](../../README.md) diff --git a/docs/Static-methods/Regex.md b/docs/Static-methods/Regex.md index e4ec57c..7e5e697 100644 --- a/docs/Static-methods/Regex.md +++ b/docs/Static-methods/Regex.md @@ -17,29 +17,83 @@ File: `src/Utilities/Regex.php` - `string $value` - Value that should be transformed to slug -##### Example 1 +##### Examples + +1) - value: non-scalar or `null` - result: `false` -##### Example 2 +2) - value: `""` (an empty string) - result: `""` (an empty string) -##### Example 3 +3) - value: `"Lorem ipsum. Dolor sit 12.34 amet."` - result: `"lorem-ipsum-dolor-sit-1234-amet"` +### clearBeginningSlash(string): string + +> Clears, removes slash from the beginning of given string + +##### Arguments + +- `string $string` - String that may contains slash as the 1st character + +##### Examples + +1) + +- string: `"lorem ipsum"` +- result: `"lorem ipsum"` + +2) + +- string: `"/lorem ipsum"` +- result: `"lorem ipsum"` + +3) + +- string: `"/ lorem 123 ipsum"` +- result: `" lorem 123 ipsum"` + +### clearEndingSlash(string): string + +> Clears, removes slash from the end of given string + +##### Arguments + +- `string $string` - String that may contains slash as the last character + +##### Examples + +1) + +- string: `"lorem ipsum"` +- result: `"lorem ipsum"` + +2) + +- string: `"lorem ipsum/"` +- result: `"lorem ipsum"` + +3) + +- string: `"lorem 123 ipsum /"` +- result: `"lorem 123 ipsum "` + # More 1. [Base test case (with common methods and data providers)](../Base-test-case.md) -2. [Collection of elements](../Collection-of-elements.md) -3. [Exceptions](../Exceptions.md) -4. [Static methods](../Static-methods.md) - 1. [Arrays](../Static-methods/Arrays.md) - 2. [**Regex**](Regex.md) -5. [Value Objects](../Value-Objects.md) +2. [Collection of elements](../Collection/BaseCollection.md) +3. [Templates](../Collection/Templates.md) +4. [Exceptions](../Exceptions.md) +5. [Static methods](../Static-methods.md) + 1. [Arrays](../Static-methods/Arrays.md) + 2. [**Regex**](Regex.md) + 3. [Uri](Uri.md) +6. [Value Objects](../Value-Objects.md) [‹ Back to `Readme`](../../README.md) diff --git a/docs/Static-methods/Uri.md b/docs/Static-methods/Uri.md new file mode 100644 index 0000000..33cd80c --- /dev/null +++ b/docs/Static-methods/Uri.md @@ -0,0 +1,47 @@ +# Meritoo Common Library + +Common and useful classes, methods, exceptions etc. + +# Uri + +> Useful methods related to uri + +Class: `Meritoo\Common\Utilities\Uri` +File: `src/Utilities/Uri.php` + +### buildUrl(string, string ...): string + +> Builds url with given root url and parts of url (concatenates them using "/") + +##### Arguments + +- `string $rootUrl` - Protocol and domain (or domain only) +- `string ...$urlParts` - Parts of url that will be concatenated with the rool url by "/" + +##### Examples + +1) + +- rootUrl: `"http://my.example"` +- urlParts: `""` (an empty string) +- result: `"http://my.example"` + +2) + +- rootUrl: `"http://my.example"` +- urlParts: `"/test", "/123"` +- result: `"http://my.example/test/123"` + +# More + +1. [Base test case (with common methods and data providers)](../Base-test-case.md) +2. [Collection of elements](../Collection/BaseCollection.md) +3. [Templates](../Collection/Templates.md) +4. [Exceptions](../Exceptions.md) +5. [Static methods](../Static-methods.md) + 1. [Arrays](Arrays.md) + 2. [Regex](Regex.md) + 3. [**Uri**](Uri.md) +6. [Value Objects](../Value-Objects.md) + +[‹ Back to `Readme`](../../README.md) diff --git a/docs/Value-Objects.md b/docs/Value-Objects.md index 0d2f6ef..77a47dc 100644 --- a/docs/Value-Objects.md +++ b/docs/Value-Objects.md @@ -15,6 +15,7 @@ Located in `Meritoo\Common\ValueObject` namespace and in `src/ValueObject/` dire ##### Info Represents address of company, institution, user etc. Contains properties: + 1. `$street` - the street 2. `$buildingNumber` - the number of building 3. `$flatNumber` - the number of flat @@ -66,6 +67,7 @@ $asString = (string)$address; // "4th Avenue 10/200, 00123, New York" ##### Info Represents bank account. Contains properties: + 1. `$bankName` - name of bank 2. `$accountNumber` - number of bank's account @@ -101,6 +103,7 @@ $asString = (string)$bank; // "Bank of America, 1234567890" ##### Info Represents a company. Contains properties: + 1. `$name` - name of company 2. `$address` - address of company 3. `$bankAccount` - bank account of company @@ -145,7 +148,9 @@ $asString = (string)$company; // "Test 1, 4th Avenue 10/200, 00123, New York, Ba ##### Info -Represents human. Based on `\Meritoo\Common\Traits\ValueObject\HumanTrait` trait. Contains properties same as `HumanTrait` trait: +Represents human. Based on `\Meritoo\Common\Traits\ValueObject\HumanTrait` trait. Contains properties same +as `HumanTrait` trait: + 1. `$firstName` - first name 2. `$lastName` - last name 3. `$email` - email address @@ -186,6 +191,7 @@ $asString2 = (string)$human2; // "John Scott " ##### Info Size, e.g. of image. Contains properties: + 1. `width` - the width 2. `height` - the height 3. `unit` - unit used when width or height should be returned with unit, default: `"px"` @@ -218,13 +224,15 @@ New instance can be created using static methods: ##### Methods Has: + - getters and setters for `width` and `height` properties. - setter for `separator` property - `toString()` and `toArray()` methods that returns size represented as string and array ##### Conversion to string (using `__toString()` method) -Instance of `Size` may be represented as string that contains width and height separated by separator (default: `" x "`). +Instance of `Size` may be represented as string that contains width and height separated by separator (default: `" x "`) +. Example: @@ -239,6 +247,56 @@ $size->setSeparator('X'); $asString2 = (string)$size; // "200X100" ``` +### Template + +##### Namespace + +`Meritoo\Common\ValueObject\Template` + +##### Info + +Template with placeholders that may be filled by real data. Contains properties: + +1. `$content` - raw string with placeholders (content of the template) + +##### New instance + +New instance can be created using constructor: + +```php +new Template('First name: %first_name%'); +``` + +Each placeholder should be wrapped by `%` character, e.g. `%first_name%`. If content of template is an empty string or +does not contain 1 placeholder at least, an `Meritoo\Common\Exception\ValueObject\Template\InvalidContentException` +exception will be thrown. + +Examples of invalid content of template: + +```php +new Template(''); // An empty string +new Template('test'); // Without placeholders +new Template('This is %test'); // With starting tag only (invalid placeholder) +``` + +##### Methods + +Has 1 public method: `fill(array $values)`. Returns content of the template filled with given values (by replacing +placeholders with their proper values). + +Example of usage: + +```php +$template = new Template('My name is %name% and I am %profession%'); +$result = $template->fill([ + 'name' => 'Jane', + 'profession' => 'photographer', +]); // "My name is Jane and I am photographer" +``` + +Throws an `Meritoo\Common\Exception\ValueObject\Template\NotEnoughValuesException` exception if there is not enough +values (iow. more placeholders than values). + ### Version ##### Namespace @@ -248,6 +306,7 @@ $asString2 = (string)$size; // "200X100" ##### Info Represents version of software. Contains properties: + 1. `$majorPart` - the "major" part of version 2. `$minorPart` - the "minor" part of version 3. `$patchPart` - the "patch" part of version @@ -258,22 +317,22 @@ New instance can be created using: 1. Constructor: - ```php - new Version(1, 0, 2); - ``` + ```php + new Version(1, 0, 2); + ``` 2. Static methods: - 1. `fromArray()` - creates new instance using given version as array + 1. `fromArray(array $version)` - creates new instance using given version as array - ```php - Version::fromArray([1, 0, 2]); - ``` + ```php + Version::fromArray([1, 0, 2]); + ``` - 2. `fromString()` - creates new instance using given version as string: + 2. `fromString(string $version)` - creates new instance using given version as string: - ```php - Version::fromString('1.0.2'); - ``` + ```php + Version::fromString('1.0.2'); + ``` ##### Methods @@ -281,7 +340,8 @@ Has getters for each property: `getMajorPart()`, `getMinorPart()`, `getPatchPart ##### Conversion to string (using `__toString()` method) -Instance of `Version` may be represented as string that contains all properties separated by `.` (`$majorPart`.`$minorPart`.`$patchPart`). +Instance of `Version` may be represented as string that contains all properties separated by `.` (`$majorPart` +.`$minorPart`.`$patchPart`). Example: @@ -293,11 +353,13 @@ $asString = (string)$version; // "1.0.2" # More 1. [Base test case (with common methods and data providers)](Base-test-case.md) -2. [Collection of elements](Collection-of-elements.md) -3. [Exceptions](Exceptions.md) -4. [Static methods](Static-methods.md) - 1. [Arrays](Static-methods/Arrays.md) - 2. [Regex](Static-methods/Regex.md) -5. [**Value Objects**](Value-Objects.md) +2. [Collection of elements](Collection/BaseCollection.md) +3. [Templates](Collection/Templates.md) +4. [Exceptions](Exceptions.md) +5. [Static methods](Static-methods.md) + 1. [Arrays](Static-methods/Arrays.md) + 2. [Regex](Static-methods/Regex.md) + 3. [Uri](Static-methods/Uri.md) +6. [**Value Objects**](Value-Objects.md) [‹ Back to `Readme`](../README.md) diff --git a/infection.json.dist b/infection.json.dist new file mode 100644 index 0000000..06b8a4c --- /dev/null +++ b/infection.json.dist @@ -0,0 +1,13 @@ +{ + "timeout": 10, + "source": { + "directories": [ + "src" + ] + }, + "logs": { + "text": "build/reports/infection/infection-log.txt", + "summary": "build/reports/infection/summary-log.txt", + "debug": "build/reports/infection/debug-log.txt" + } +} diff --git a/phing/app.xml b/phing/app.xml index 5688569..68c33cf 100644 --- a/phing/app.xml +++ b/phing/app.xml @@ -3,17 +3,17 @@ - + - + - + - + - + - + - + - + @@ -50,46 +50,41 @@ - + - - - + + + - + - + - + - - - - - - - + + - + - + @@ -98,20 +93,20 @@ - + - - - + + + - + - + @@ -121,24 +116,24 @@ - - + + - + - + - - + + @@ -147,15 +142,15 @@ - - + + - + @@ -163,10 +158,10 @@ - + - + @@ -174,11 +169,11 @@ - + - - - + + + diff --git a/phing/filesets.xml b/phing/filesets.xml index c3aea0c..59355ff 100644 --- a/phing/filesets.xml +++ b/phing/filesets.xml @@ -1,36 +1,14 @@ - - - - - - - - - - - - - - diff --git a/phing/properties.dist b/phing/properties.dist index bcb2fb5..76237a2 100644 --- a/phing/properties.dist +++ b/phing/properties.dist @@ -24,11 +24,11 @@ cache.clearWithWarmup = false # composer.download_command = bash ${project.basedir}/phing/composer-install.sh -# Path to composer executable or downloaded composer.phar file +# Path to Composer executable or downloaded composer.phar file # composer.path = ${project.basedir}/composer.phar -# Path to php executable used by composer +# Path to PHP executable used by Composer # composer.php = php @@ -39,10 +39,6 @@ composer.php = php # System directories # dir.src = ${project.basedir}/src -dir.var = ${project.basedir}/tests/Resources/var -dir.cache = ${dir.var}/cache -dir.logs = ${dir.var}/log -dir.sessions = ${dir.var}/sessions dir.data = ${project.basedir}/data dir.tests = ${project.basedir}/tests @@ -50,19 +46,24 @@ dir.tests = ${project.basedir}/tests # dir.build = ${project.basedir}/build dir.reports = ${dir.build}/reports -dir.reports.pdepend = ${dir.reports}/pdepend -dir.reports.coverage = ${dir.reports}/phpunit_coverage +dir.reports.coverage = ${dir.reports}/phpunit-coverage +dir.reports.code_sniffer = ${dir.reports}/code_sniffer # Data directories # dir.data.tests = ${dir.data}/tests dir.data.temporary = ${dir.data}/tmp -# Docker directories +# -------------------------------------------------------------------------------- +# Static Analysis +# -------------------------------------------------------------------------------- + +# Paths of frameworks used to run analysis: +# - PHPStan # -dir.docker = ${project.basedir}/docker -dir.docker.data = ${dir.docker}/data/db -dir.docker.logs = ${dir.docker}/logs/nginx +check.phpstan.command = ./vendor/bin/phpstan analyse +check.psalm.command = ./vendor/bin/psalm --report=build/reports/psalm.json +check.php_coveralls.command = ./vendor/bin/php-coveralls --ansi -v # -------------------------------------------------------------------------------- # Testing @@ -72,11 +73,17 @@ dir.docker.logs = ${dir.docker}/logs/nginx # tests.cs_fixer.command = ./vendor/bin/php-cs-fixer fix --verbose +# Path of the PHP_CodeSniffer (https://github.com/squizlabs/PHP_CodeSniffer) +# +tests.code_sniffer.command = ./vendor/bin/phpcs --report-full=${dir.reports.code_sniffer}/full.txt --report-summary=${dir.reports.code_sniffer}/summary.txt --report-checkstyle=${dir.reports.code_sniffer}/checkstyle.xml + # Test database path # tests.database = ${dir.data.temporary}/database.sqlite # Paths of frameworks used to run tests: # - PHPUnit (unit tests) +# - Infection (mutation tests) # tests.phpunit.command = ./vendor/bin/phpunit --verbose +tests.mutation.command = ./vendor/bin/infection --ansi --threads=$(nproc) --coverage=build/reports/infection diff --git a/phing/tests.xml b/phing/tests.xml index 92f6190..4005fde 100644 --- a/phing/tests.xml +++ b/phing/tests.xml @@ -1,30 +1,30 @@ - + - + - + - + - - - - - - + + + + + + - + @@ -37,97 +37,87 @@ - + - + - - - - - - + - - + + - - - - - - - + + + - - - - - - - - + + + - - - - - + + + - + + + + + + - + - + - - - + + + - + - - - + + + diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..2930822 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,16 @@ + + + + + + + + + + + + src/ + tests/ + + diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..31f6166 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,5 @@ +parameters: + level: 1 + paths: + - src + - tests diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f54393f..7e7cc7a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,30 +1,37 @@ - - + + + + src + + + + + + + - + - - tests/ + tests + + + tests/Collection - - - - src/ - - - - + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..c0db670 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Collection/BaseCollection.php b/src/Collection/BaseCollection.php new file mode 100644 index 0000000..f6ff521 --- /dev/null +++ b/src/Collection/BaseCollection.php @@ -0,0 +1,349 @@ + + * @copyright Meritoo + */ +abstract class BaseCollection implements CollectionInterface +{ + /** + * The elements of collection + * + * @var array + */ + private array $elements; + + /** + * Class constructor + * + * @param array $elements (optional) Elements of collection + */ + public function __construct(array $elements = []) + { + $validated = $this->getElementsWithValidType($elements); + $this->elements = $this->prepareElements($validated); + } + + /** + * {@inheritdoc} + */ + public function add($element, $index = null): void + { + if (!$this->isValidType($element)) { + return; + } + + if (null === $index) { + $this->elements[] = $element; + + return; + } + + $this->elements[$index] = $element; + } + + /** + * {@inheritdoc} + */ + public function addMultiple($elements, bool $useIndexes = false): void + { + if (empty($elements)) { + return; + } + + $prepared = $this->prepareElements($elements); + + foreach ($prepared as $index => $element) { + if ($useIndexes) { + $this->add($element, $index); + + continue; + } + + $this->add($element); + } + } + + /** + * {@inheritdoc} + */ + public function append($element): void + { + $this->elements[] = $element; + } + + /** + * {@inheritdoc} + */ + public function clear(): void + { + $this->elements = []; + } + + /** + * {@inheritdoc} + */ + public function count(): int + { + return count($this->elements); + } + + /** + * {@inheritdoc} + */ + public function getByIndex($index) + { + return $this->elements[$index] ?? null; + } + + /** + * {@inheritdoc} + */ + public function getFirst() + { + return Arrays::getFirstElement($this->elements); + } + + /** + * {@inheritdoc} + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->elements); + } + + /** + * {@inheritdoc} + */ + public function getLast() + { + return Arrays::getLastElement($this->elements); + } + + /** + * {@inheritdoc} + */ + public function getNext($element) + { + return Arrays::getNextElement($this->elements, $element); + } + + /** + * {@inheritdoc} + */ + public function getPrevious($element) + { + return Arrays::getPreviousElement($this->elements, $element); + } + + /** + * {@inheritdoc} + */ + public function has($element): bool + { + $index = Arrays::getIndexOf($this->elements, $element); + + return null !== $index && false !== $index; + } + + /** + * {@inheritdoc} + */ + public function isEmpty(): bool + { + return empty($this->elements); + } + + /** + * {@inheritdoc} + */ + public function isFirst($element): bool + { + return reset($this->elements) === $element; + } + + /** + * {@inheritdoc} + */ + public function isLast($element): bool + { + return end($this->elements) === $element; + } + + /** + * {@inheritdoc} + */ + public function limit(int $max, int $offset = 0): self + { + $result = clone $this; + + $negativeMax = $max <= 0; + $exceededMax = $max >= $this->count(); + + if ($negativeMax || $exceededMax) { + if ($negativeMax) { + $result->clear(); + } + + return $result; + } + + $iteration = -1; + + foreach ($result as $index => $element) { + $iteration++; + + if ($iteration >= $offset && $iteration < $offset + $max) { + continue; + } + + unset($result[$index]); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset): bool + { + return $this->exists($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + if ($this->exists($offset)) { + return $this->elements[$offset]; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value): void + { + $this->elements[$offset] = $value; + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset): void + { + if ($this->exists($offset)) { + unset($this->elements[$offset]); + } + } + + /** + * {@inheritdoc} + */ + public function prepend($element): void + { + array_unshift($this->elements, $element); + } + + /** + * {@inheritdoc} + */ + public function remove($element): void + { + if (0 === $this->count()) { + return; + } + + foreach ($this->elements as $index => $existing) { + if ($element === $existing) { + unset($this->elements[$index]); + + break; + } + } + } + + /** + * {@inheritdoc} + */ + public function toArray(): array + { + return $this->elements; + } + + /** + * Returns information if given element has valid type + * + * @param mixed $element Element of collection + * @return bool + */ + abstract protected function isValidType($element): bool; + + /** + * Prepares elements to initialize the collection. + * Feel free to override and prepare elements in your way. + * + * @param array $elements The elements of collection to prepare + * @return array + */ + protected function prepareElements(array $elements): array + { + return $elements; + } + + /** + * Returns information if element with given index/key exists + * + * @param int|string $index The index/key of element + * @return bool + */ + private function exists($index): bool + { + return isset($this->elements[$index]) || array_key_exists($index, $this->elements); + } + + /** + * Returns elements of collection with valid types + * + * @param array $elements The elements of collection to verify + * @return array + */ + private function getElementsWithValidType(array $elements): array + { + if (empty($elements)) { + return []; + } + + $result = []; + + foreach ($elements as $index => $element) { + if (!$this->isValidType($element)) { + continue; + } + + $result[$index] = $element; + } + + return $result; + } +} diff --git a/src/Collection/Collection.php b/src/Collection/Collection.php deleted file mode 100644 index 6206ce5..0000000 --- a/src/Collection/Collection.php +++ /dev/null @@ -1,36 +0,0 @@ - - * @copyright Meritoo - */ -class Collection implements Countable, ArrayAccess, IteratorAggregate -{ - use CollectionTrait; - - /** - * Class constructor - * - * @param array $elements (optional) The elements of collection - */ - public function __construct(array $elements = []) - { - $this->elements = $elements; - } -} diff --git a/src/Collection/DateTimeCollection.php b/src/Collection/DateTimeCollection.php new file mode 100644 index 0000000..f31c4f1 --- /dev/null +++ b/src/Collection/DateTimeCollection.php @@ -0,0 +1,27 @@ + + * @copyright Meritoo + */ +class DateTimeCollection extends BaseCollection +{ + protected function isValidType($element): bool + { + return $element instanceof DateTime; + } +} diff --git a/src/Traits/Collection/CountableTrait.php b/src/Collection/IntegerCollection.php similarity index 56% rename from src/Traits/Collection/CountableTrait.php rename to src/Collection/IntegerCollection.php index 4579d40..d284f71 100644 --- a/src/Traits/Collection/CountableTrait.php +++ b/src/Collection/IntegerCollection.php @@ -6,21 +6,20 @@ * file that was distributed with this source code. */ -namespace Meritoo\Common\Traits\Collection; +declare(strict_types=1); + +namespace Meritoo\Common\Collection; /** - * Trait for the Collection required by Countable interface + * Collection of integers * * @author Meritoo * @copyright Meritoo */ -trait CountableTrait +class IntegerCollection extends BaseCollection { - /** - * {@inheritdoc} - */ - public function count() + protected function isValidType($element): bool { - return count($this->elements); + return is_int($element); } } diff --git a/src/Collection/StringCollection.php b/src/Collection/StringCollection.php new file mode 100644 index 0000000..2d9a191 --- /dev/null +++ b/src/Collection/StringCollection.php @@ -0,0 +1,25 @@ + + * @copyright Meritoo + */ +class StringCollection extends BaseCollection +{ + protected function isValidType($element): bool + { + return is_string($element); + } +} diff --git a/src/Collection/Templates.php b/src/Collection/Templates.php new file mode 100644 index 0000000..6555228 --- /dev/null +++ b/src/Collection/Templates.php @@ -0,0 +1,72 @@ + + * @copyright Meritoo + */ +class Templates extends BaseCollection +{ + /** + * Finds and returns template with given index + * + * @param string $index Index that contains required template + * @return Template + * @throws TemplateNotFoundException + */ + public function findTemplate(string $index): Template + { + $template = $this->getByIndex($index); + + if ($template instanceof Template) { + return $template; + } + + // Oops, template not found + throw TemplateNotFoundException::create($index); + } + + /** + * Creates and returns the collection from given array + * + * @param array $templates Pairs of key-value where: key - template's index, value - template's content + * @return Templates + */ + public static function fromArray(array $templates): Templates + { + // No templates. Nothing to do. + if (empty($templates)) { + return new static(); + } + + $result = new static(); + + foreach ($templates as $index => $template) { + $result->add(new Template($template), $index); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + protected function isValidType($element): bool + { + return $element instanceof Template; + } +} diff --git a/src/Contract/Collection/AddableCollectionInterface.php b/src/Contract/Collection/AddableCollectionInterface.php new file mode 100644 index 0000000..e3e30cb --- /dev/null +++ b/src/Contract/Collection/AddableCollectionInterface.php @@ -0,0 +1,55 @@ + + * @copyright Meritoo + */ +interface AddableCollectionInterface +{ + /** + * Adds given element (at the end of collection) + * + * @param mixed $element The element to add + * @param mixed $index (optional) Index / key of the element + * @return void + */ + public function add($element, $index = null): void; + + /** + * Adds given elements (at the end of collection) + * + * @param array|CollectionInterface $elements The elements to add + * @param bool $useIndexes (optional) If is set to true, indexes of given elements will be + * used in this collection. Otherwise - not. + * @return void + */ + public function addMultiple(array $elements, bool $useIndexes = false): void; + + /** + * Appends given element (adds given element at the end of collection) + * + * @param mixed $element The element to add at the end + * @return void + */ + public function append($element): void; + + /** + * Prepends given element (adds given element at the beginning of collection) + * + * @param mixed $element The element to add at the beginning + * @return void + */ + public function prepend($element): void; +} diff --git a/src/Contract/Collection/ClearableCollectionInterface.php b/src/Contract/Collection/ClearableCollectionInterface.php new file mode 100644 index 0000000..96b6186 --- /dev/null +++ b/src/Contract/Collection/ClearableCollectionInterface.php @@ -0,0 +1,27 @@ + + * @copyright Meritoo + */ +interface ClearableCollectionInterface +{ + /** + * Removes all elements of the collection + * + * @return void + */ + public function clear(): void; +} diff --git a/src/Contract/Collection/CollectionInterface.php b/src/Contract/Collection/CollectionInterface.php new file mode 100644 index 0000000..e97ec1e --- /dev/null +++ b/src/Contract/Collection/CollectionInterface.php @@ -0,0 +1,32 @@ + + * @copyright Meritoo + */ +interface CollectionInterface extends ArrayAccess, IteratorAggregate, AddableCollectionInterface, + RemovableCollectionInterface, CountableCollectionInterface, ClearableCollectionInterface, + GettableCollectionInterface, VerifiableCollectionInterface, ReducibleCollectionInterface +{ + /** + * Returns representation of object as array + * + * @return array + */ + public function toArray(): array; +} diff --git a/src/Contract/Collection/CountableCollectionInterface.php b/src/Contract/Collection/CountableCollectionInterface.php new file mode 100644 index 0000000..063cf07 --- /dev/null +++ b/src/Contract/Collection/CountableCollectionInterface.php @@ -0,0 +1,23 @@ + + * @copyright Meritoo + */ +interface CountableCollectionInterface extends Countable +{ +} diff --git a/src/Contract/Collection/GettableCollectionInterface.php b/src/Contract/Collection/GettableCollectionInterface.php new file mode 100644 index 0000000..5b2ca54 --- /dev/null +++ b/src/Contract/Collection/GettableCollectionInterface.php @@ -0,0 +1,58 @@ + + * @copyright Meritoo + */ +interface GettableCollectionInterface +{ + /** + * Returns element with given index + * + * @param mixed $index Index / key of element to return + * @return mixed + */ + public function getByIndex($index); + + /** + * Returns first element + * + * @return mixed + */ + public function getFirst(); + + /** + * Returns last element + * + * @return mixed + */ + public function getLast(); + + /** + * Returns element next after given element + * + * @param mixed $element The element whose next element should be returned + * @return mixed + */ + public function getNext($element); + + /** + * Returns element preceding given element + * + * @param mixed $element The element whose previous element should be returned + * @return mixed + */ + public function getPrevious($element); +} diff --git a/src/Contract/Collection/ReducibleCollectionInterface.php b/src/Contract/Collection/ReducibleCollectionInterface.php new file mode 100644 index 0000000..d2b2584 --- /dev/null +++ b/src/Contract/Collection/ReducibleCollectionInterface.php @@ -0,0 +1,29 @@ + + * @copyright Meritoo + */ +interface ReducibleCollectionInterface +{ + /** + * Returns new instance of this collection with limited elements + * + * @param int $max Maximum elements to return + * @param int $offset (optional) Position of element from which limitation should start + * @return $this + */ + public function limit(int $max, int $offset = 0): self; +} diff --git a/src/Contract/Collection/RemovableCollectionInterface.php b/src/Contract/Collection/RemovableCollectionInterface.php new file mode 100644 index 0000000..d0a95fa --- /dev/null +++ b/src/Contract/Collection/RemovableCollectionInterface.php @@ -0,0 +1,28 @@ + + * @copyright Meritoo + */ +interface RemovableCollectionInterface +{ + /** + * Removes given element + * + * @param mixed $element The element to remove + * @return void + */ + public function remove($element): void; +} diff --git a/src/Contract/Collection/VerifiableCollectionInterface.php b/src/Contract/Collection/VerifiableCollectionInterface.php new file mode 100644 index 0000000..98e7a67 --- /dev/null +++ b/src/Contract/Collection/VerifiableCollectionInterface.php @@ -0,0 +1,51 @@ + + * @copyright Meritoo + */ +interface VerifiableCollectionInterface +{ + /** + * Returns information if given element exists in collection + * + * @param mixed $element The element to verify + * @return bool + */ + public function has($element): bool; + + /** + * Returns information if collection is empty (has not any element) + * + * @return bool + */ + public function isEmpty(): bool; + + /** + * Returns information if given element is the first element in collection + * + * @param mixed $element The element to verify + * @return bool + */ + public function isFirst($element): bool; + + /** + * Returns information if given element is the last element in collection + * + * @param mixed $element The element to verify + * @return bool + */ + public function isLast($element): bool; +} diff --git a/src/Contract/Renderable/RenderableInterface.php b/src/Contract/Renderable/RenderableInterface.php new file mode 100644 index 0000000..12581b0 --- /dev/null +++ b/src/Contract/Renderable/RenderableInterface.php @@ -0,0 +1,31 @@ + + * @copyright Meritoo + */ +interface RenderableInterface +{ + /** + * Renders this object using given templates + * + * @param Templates $templates Collection/storage of templates that will be required while rendering this and + * related objects, e.g. children of this object + * @return string + */ + public function render(Templates $templates): string; +} diff --git a/src/Exception/Base/UnknownTypeException.php b/src/Exception/Base/UnknownTypeException.php index 43abda2..7354524 100644 --- a/src/Exception/Base/UnknownTypeException.php +++ b/src/Exception/Base/UnknownTypeException.php @@ -28,13 +28,13 @@ abstract class UnknownTypeException extends Exception * @param string $typeName Name of the something * @return UnknownTypeException */ - public static function create($unknownType, BaseType $typeInstance, $typeName) + public static function create($unknownType, BaseType $typeInstance, string $typeName): UnknownTypeException { $template = 'The \'%s\' type of %s is unknown. Probably doesn\'t exist or there is a typo. You should use one' - . ' of these types: %s.'; + .' of these types: %s.'; $allTypes = $typeInstance->getAll(); - $types = Arrays::values2string($allTypes, '', ', '); + $types = Arrays::values2string($allTypes, '', ', ') ?? '[types not found]'; $message = sprintf($template, $unknownType, $typeName, $types); return new static($message); diff --git a/src/Exception/Bundle/IncorrectBundleNameException.php b/src/Exception/Bundle/IncorrectBundleNameException.php index 2c34199..6e7506a 100644 --- a/src/Exception/Bundle/IncorrectBundleNameException.php +++ b/src/Exception/Bundle/IncorrectBundleNameException.php @@ -27,7 +27,7 @@ class IncorrectBundleNameException extends Exception public static function create($bundleName) { $template = 'Name of bundle \'%s\' is incorrect. It should start with big letter and end with "Bundle". Is' - . ' there everything ok?'; + .' there everything ok?'; $message = sprintf($template, $bundleName); diff --git a/src/Exception/File/EmptyFileException.php b/src/Exception/File/EmptyFileException.php index 4ff9441..fea0600 100644 --- a/src/Exception/File/EmptyFileException.php +++ b/src/Exception/File/EmptyFileException.php @@ -8,13 +8,15 @@ namespace Meritoo\Common\Exception\File; +use Exception; + /** * An exception used while file with given path is empty (has no content) * * @author Meritoo * @copyright Meritoo */ -class EmptyFileException extends \Exception +class EmptyFileException extends Exception { /** * Creates exception diff --git a/src/Exception/File/EmptyFilePathException.php b/src/Exception/File/EmptyFilePathException.php index 33333f5..51ca44d 100644 --- a/src/Exception/File/EmptyFilePathException.php +++ b/src/Exception/File/EmptyFilePathException.php @@ -8,18 +8,22 @@ namespace Meritoo\Common\Exception\File; +use Exception; + /** * An exception used while path of given file is empty * * @author Meritoo * @copyright Meritoo */ -class EmptyFilePathException extends \Exception +class EmptyFilePathException extends Exception { /** * Creates exception + * + * @return EmptyFilePathException */ - public static function create() + public static function create(): EmptyFilePathException { return new static('Path of the file is empty. Did you provide path of proper file?'); } diff --git a/src/Exception/File/NotExistingFileException.php b/src/Exception/File/NotExistingFileException.php index c64a8f8..6ddc2aa 100644 --- a/src/Exception/File/NotExistingFileException.php +++ b/src/Exception/File/NotExistingFileException.php @@ -8,13 +8,15 @@ namespace Meritoo\Common\Exception\File; +use Exception; + /** * An exception used while file with given path does not exist * * @author Meritoo * @copyright Meritoo */ -class NotExistingFileException extends \Exception +class NotExistingFileException extends Exception { /** * Creates exception diff --git a/src/Exception/Reflection/CannotResolveClassNameException.php b/src/Exception/Reflection/CannotResolveClassNameException.php index dee9dde..faaf4d0 100644 --- a/src/Exception/Reflection/CannotResolveClassNameException.php +++ b/src/Exception/Reflection/CannotResolveClassNameException.php @@ -21,13 +21,12 @@ class CannotResolveClassNameException extends Exception /** * Creates exception * - * @param array|object|string $source Source of the class's / trait's name. It can be an array of objects, - * namespaces, object or namespace. - * @param bool $forClass (optional) If is set to true, message of this exception for class is - * prepared. Otherwise - for trait. + * @param string $source Source of name of the class or trait + * @param bool $forClass (optional) If is set to true, message of this exception for class is prepared. Otherwise + * - for trait. * @return CannotResolveClassNameException */ - public static function create($source, $forClass = true) + public static function create(string $source, bool $forClass = true): CannotResolveClassNameException { $forWho = 'trait'; $value = ''; @@ -37,7 +36,7 @@ class CannotResolveClassNameException extends Exception } if (is_scalar($source)) { - $value = sprintf(' %s', (string)$source); + $value = sprintf(' %s', $source); } $template = 'Name of %s from given \'%s\'%s cannot be resolved. Is there everything ok?'; diff --git a/src/Exception/Reflection/ClassWithoutConstructorException.php b/src/Exception/Reflection/ClassWithoutConstructorException.php new file mode 100644 index 0000000..b67a44a --- /dev/null +++ b/src/Exception/Reflection/ClassWithoutConstructorException.php @@ -0,0 +1,34 @@ + + * @copyright Meritoo + */ +class ClassWithoutConstructorException extends Exception +{ + /** + * Creates exception + * + * @param string $className Fully-qualified name of class that hasn't constructor + * @return ClassWithoutConstructorException + */ + public static function create(string $className): ClassWithoutConstructorException + { + $template = 'Oops, class \'%s\' hasn\'t constructor. Did you use proper class?'; + $message = sprintf($template, $className); + + return new static($message); + } +} diff --git a/src/Exception/Reflection/MissingChildClassesException.php b/src/Exception/Reflection/MissingChildClassesException.php index 27183ff..9a367b3 100644 --- a/src/Exception/Reflection/MissingChildClassesException.php +++ b/src/Exception/Reflection/MissingChildClassesException.php @@ -26,12 +26,12 @@ class MissingChildClassesException extends Exception * strings, object or string. * @return MissingChildClassesException */ - public static function create($parentClass) + public static function create($parentClass): MissingChildClassesException { $template = 'The \'%s\' class requires one child class at least who will extend her (maybe is an abstract' - . ' class), but the child classes are missing. Did you forget to extend this class?'; + .' class), but the child classes are missing. Did you forget to extend this class?'; - $parentClassName = Reflection::getClassName($parentClass); + $parentClassName = Reflection::getClassName($parentClass) ?? '[unknown class]'; $message = sprintf($template, $parentClassName); return new static($message); diff --git a/src/Exception/Reflection/NotExistingPropertyException.php b/src/Exception/Reflection/NotExistingPropertyException.php index 4b7484f..c5b10ff 100644 --- a/src/Exception/Reflection/NotExistingPropertyException.php +++ b/src/Exception/Reflection/NotExistingPropertyException.php @@ -8,22 +8,24 @@ namespace Meritoo\Common\Exception\Reflection; +use Exception; + /** * An exception used while property does not exist in instance of class * * @author Meritoo * @copyright Meritoo */ -class NotExistingPropertyException extends \Exception +class NotExistingPropertyException extends Exception { /** * Creates exception * - * @param mixed $object Object that should contains given property - * @param string $property Name of the property + * @param mixed $object Object that should contains given property + * @param null|string $property Name of the property * @return NotExistingPropertyException */ - public static function create($object, $property) + public static function create($object, ?string $property): NotExistingPropertyException { $template = 'Property \'%s\' does not exist in instance of class \'%s\'. Did you use proper name of property?'; $message = sprintf($template, $property, get_class($object)); diff --git a/src/Exception/Reflection/TooManyChildClassesException.php b/src/Exception/Reflection/TooManyChildClassesException.php index f06dab2..dea2970 100644 --- a/src/Exception/Reflection/TooManyChildClassesException.php +++ b/src/Exception/Reflection/TooManyChildClassesException.php @@ -27,12 +27,12 @@ class TooManyChildClassesException extends Exception * @param array $childClasses Child classes * @return TooManyChildClassesException */ - public static function create($parentClass, array $childClasses) + public static function create($parentClass, array $childClasses): TooManyChildClassesException { $template = "The '%s' class requires one child class at most who will extend her, but more than one child" - . " class was found:\n- %s\n\nWhy did you create more than one classes that extend '%s' class?"; + ." class was found:\n- %s\n\nWhy did you create more than one classes that extend '%s' class?"; - $parentClassName = Reflection::getClassName($parentClass); + $parentClassName = Reflection::getClassName($parentClass) ?? '[unknown class]'; $message = sprintf($template, $parentClassName, implode("\n- ", $childClasses), $parentClassName); return new static($message); diff --git a/src/Exception/Regex/IncorrectColorHexLengthException.php b/src/Exception/Regex/IncorrectColorHexLengthException.php index 86af0a3..9dcca58 100644 --- a/src/Exception/Regex/IncorrectColorHexLengthException.php +++ b/src/Exception/Regex/IncorrectColorHexLengthException.php @@ -8,13 +8,15 @@ namespace Meritoo\Common\Exception\Regex; +use Exception; + /** * An exception used while length of given hexadecimal value of color is incorrect * * @author Meritoo * @copyright Meritoo */ -class IncorrectColorHexLengthException extends \Exception +class IncorrectColorHexLengthException extends Exception { /** * Creates exception @@ -25,7 +27,7 @@ class IncorrectColorHexLengthException extends \Exception public static function create($color) { $template = 'Length of hexadecimal value of color \'%s\' is incorrect. It\'s %d, but it should be 3 or 6.' - . ' Is there everything ok?'; + .' Is there everything ok?'; $message = sprintf($template, $color, strlen($color)); diff --git a/src/Exception/Regex/InvalidColorHexValueException.php b/src/Exception/Regex/InvalidColorHexValueException.php index 69b1682..1523be0 100644 --- a/src/Exception/Regex/InvalidColorHexValueException.php +++ b/src/Exception/Regex/InvalidColorHexValueException.php @@ -8,13 +8,15 @@ namespace Meritoo\Common\Exception\Regex; +use Exception; + /** * An exception used while given hexadecimal value of color is invalid * * @author Meritoo * @copyright Meritoo */ -class InvalidColorHexValueException extends \Exception +class InvalidColorHexValueException extends Exception { /** * Creates exception diff --git a/src/Exception/Regex/InvalidHtmlAttributesException.php b/src/Exception/Regex/InvalidHtmlAttributesException.php index cac3480..4504fd2 100644 --- a/src/Exception/Regex/InvalidHtmlAttributesException.php +++ b/src/Exception/Regex/InvalidHtmlAttributesException.php @@ -8,13 +8,15 @@ namespace Meritoo\Common\Exception\Regex; +use Exception; + /** * An exception used while html attributes are invalid * * @author Meritoo * @copyright Meritoo */ -class InvalidHtmlAttributesException extends \Exception +class InvalidHtmlAttributesException extends Exception { /** * Creates exception diff --git a/src/Exception/Regex/InvalidUrlException.php b/src/Exception/Regex/InvalidUrlException.php index 080fcf7..4d47d1d 100644 --- a/src/Exception/Regex/InvalidUrlException.php +++ b/src/Exception/Regex/InvalidUrlException.php @@ -8,13 +8,15 @@ namespace Meritoo\Common\Exception\Regex; +use Exception; + /** * An exception used while url is invalid * * @author Meritoo * @copyright Meritoo */ -class InvalidUrlException extends \Exception +class InvalidUrlException extends Exception { /** * Creates exception diff --git a/src/Exception/Type/UnknownDatePartTypeException.php b/src/Exception/Type/UnknownDatePartTypeException.php index 8155a50..29e1d5a 100644 --- a/src/Exception/Type/UnknownDatePartTypeException.php +++ b/src/Exception/Type/UnknownDatePartTypeException.php @@ -26,11 +26,8 @@ class UnknownDatePartTypeException extends UnknownTypeException * @param string $value Incorrect value * @return UnknownDatePartTypeException */ - public static function createException($unknownDatePart, $value) + public static function createException(string $unknownDatePart, string $value): UnknownDatePartTypeException { - /* @var UnknownDatePartTypeException $exception */ - $exception = parent::create($unknownDatePart, new DatePartType(), sprintf('date part (with value %s)', $value)); - - return $exception; + return parent::create($unknownDatePart, new DatePartType(), sprintf('date part (with value %s)', $value)); } } diff --git a/src/Exception/Type/UnknownOopVisibilityTypeException.php b/src/Exception/Type/UnknownOopVisibilityTypeException.php index f8eb21d..6463362 100644 --- a/src/Exception/Type/UnknownOopVisibilityTypeException.php +++ b/src/Exception/Type/UnknownOopVisibilityTypeException.php @@ -25,11 +25,8 @@ class UnknownOopVisibilityTypeException extends UnknownTypeException * @param string $unknownType Unknown visibility of a property, a method or (as of PHP 7.1.0) a constant * @return UnknownOopVisibilityTypeException */ - public static function createException($unknownType) + public static function createException(string $unknownType): UnknownOopVisibilityTypeException { - /* @var UnknownOopVisibilityTypeException $exception */ - $exception = parent::create($unknownType, new OopVisibilityType(), 'OOP-related visibility'); - - return $exception; + return parent::create($unknownType, new OopVisibilityType(), 'OOP-related visibility'); } } diff --git a/src/Exception/ValueObject/InvalidSizeDimensionsException.php b/src/Exception/ValueObject/InvalidSizeDimensionsException.php index 8f922e1..15f8838 100644 --- a/src/Exception/ValueObject/InvalidSizeDimensionsException.php +++ b/src/Exception/ValueObject/InvalidSizeDimensionsException.php @@ -8,13 +8,15 @@ namespace Meritoo\Common\Exception\ValueObject; +use Exception; + /** * An exception used while dimensions of size, passed to the instance of Size class, are invalid * * @author Meritoo * @copyright Meritoo */ -class InvalidSizeDimensionsException extends \Exception +class InvalidSizeDimensionsException extends Exception { /** * Creates exception diff --git a/src/Exception/ValueObject/Template/InvalidContentException.php b/src/Exception/ValueObject/Template/InvalidContentException.php new file mode 100644 index 0000000..c9fb140 --- /dev/null +++ b/src/Exception/ValueObject/Template/InvalidContentException.php @@ -0,0 +1,36 @@ + + * @copyright Meritoo + */ +class InvalidContentException extends Exception +{ + /** + * Creates an exception + * + * @param string $content Invalid content of template + * @return InvalidContentException + */ + public static function create(string $content): InvalidContentException + { + $template = 'Content of template \'%s\' is invalid. Did you use string with 1 placeholder at least?'; + $message = sprintf($template, $content); + + return new static($message); + } +} diff --git a/src/Exception/ValueObject/Template/MissingPlaceholdersInValuesException.php b/src/Exception/ValueObject/Template/MissingPlaceholdersInValuesException.php new file mode 100644 index 0000000..af93d18 --- /dev/null +++ b/src/Exception/ValueObject/Template/MissingPlaceholdersInValuesException.php @@ -0,0 +1,36 @@ + + * @copyright Meritoo + */ +class MissingPlaceholdersInValuesException extends Exception +{ + /** + * Creates an exception + * + * @param string $content Content of template + * @param array $missingPlaceholders Missing placeholders in provided values, iow. placeholders without values + * @return MissingPlaceholdersInValuesException + */ + public static function create(string $content, array $missingPlaceholders): MissingPlaceholdersInValuesException + { + $template = 'Cannot fill template \'%s\', because of missing values for placeholder(s): %s. Did you provide all' + .' required values?'; + $message = sprintf($template, $content, implode(', ', $missingPlaceholders)); + + return new static($message); + } +} diff --git a/src/Exception/ValueObject/Template/TemplateNotFoundException.php b/src/Exception/ValueObject/Template/TemplateNotFoundException.php new file mode 100644 index 0000000..aee1843 --- /dev/null +++ b/src/Exception/ValueObject/Template/TemplateNotFoundException.php @@ -0,0 +1,36 @@ + + * @copyright Meritoo + */ +class TemplateNotFoundException extends Exception +{ + /** + * Creates the exception + * + * @param string $index Index that should contain template, but it was not found + * @return TemplateNotFoundException + */ + public static function create(string $index): TemplateNotFoundException + { + $template = 'Template with \'%s\' index was not found. Did you provide all required templates?'; + $message = sprintf($template, $index); + + return new static($message); + } +} diff --git a/src/Traits/Collection/ArrayAccessTrait.php b/src/Traits/Collection/ArrayAccessTrait.php deleted file mode 100644 index ddb8527..0000000 --- a/src/Traits/Collection/ArrayAccessTrait.php +++ /dev/null @@ -1,67 +0,0 @@ - - * @copyright Meritoo - */ -trait ArrayAccessTrait -{ - /** - * {@inheritdoc} - */ - public function offsetExists($offset) - { - return $this->exists($offset); - } - - /** - * {@inheritdoc} - */ - public function offsetGet($offset) - { - if ($this->exists($offset)) { - return $this->elements[$offset]; - } - - return null; - } - - /** - * {@inheritdoc} - */ - public function offsetSet($offset, $value) - { - $this->elements[$offset] = $value; - } - - /** - * {@inheritdoc} - */ - public function offsetUnset($offset) - { - if ($this->exists($offset)) { - unset($this->elements[$offset]); - } - } - - /** - * Returns information if element with given index/key exists - * - * @param string|int $index The index/key of element - * @return bool - */ - private function exists($index) - { - return isset($this->elements[$index]) || array_key_exists($index, $this->elements); - } -} diff --git a/src/Traits/Collection/MainTrait.php b/src/Traits/Collection/MainTrait.php deleted file mode 100644 index 8c401fa..0000000 --- a/src/Traits/Collection/MainTrait.php +++ /dev/null @@ -1,215 +0,0 @@ - - * @copyright Meritoo - */ -trait MainTrait -{ - /** - * The elements of collection - * - * @var array - */ - private $elements; - - /** - * Adds given element (at the end of collection) - * - * @param mixed $element The element to add - * @param mixed $index (optional) Index / key of the element - * @return $this - */ - public function add($element, $index = null) - { - if (null === $index || '' === $index) { - $this->elements[] = $element; - } else { - $this->elements[$index] = $element; - } - - return $this; - } - - /** - * Adds given elements (at the end of collection) - * - * @param array|Collection $elements The elements to add - * @param bool|false $useIndexes (optional) If is set to true, indexes of given elements will be used in - * this collection. Otherwise - not. - * @return $this - */ - public function addMultiple($elements, $useIndexes = false) - { - if (!empty($elements)) { - foreach ($elements as $index => $element) { - if ($useIndexes) { - $this->add($element, $index); - continue; - } - - $this->add($element); - } - } - - return $this; - } - - /** - * Prepends given element (adds given element at the beginning of collection) - * - * @param mixed $element The element to prepend - * @return $this - */ - public function prepend($element) - { - array_unshift($this->elements, $element); - - return $this; - } - - /** - * Removes given element - * - * @param mixed $element The element to remove - * @return $this - */ - public function remove($element) - { - if ($this->count() > 0) { - foreach ($this->elements as $index => $existing) { - if ($element === $existing) { - unset($this->elements[$index]); - break; - } - } - } - - return $this; - } - - /** - * Returns information if collection is empty - * - * @return bool - */ - public function isEmpty() - { - return empty($this->elements); - } - - /** - * Returns information if given element is first in the collection - * - * @param mixed $element The element to verify - * @return bool - */ - public function isFirst($element) - { - return reset($this->elements) === $element; - } - - /** - * Returns information if given element is last in the collection - * - * @param mixed $element The element to verify - * @return bool - */ - public function isLast($element) - { - return end($this->elements) === $element; - } - - /** - * Returns information if the collection has given element, iow. if given element exists in the collection - * - * @param mixed $element The element to verify - * @return bool - */ - public function has($element) - { - $index = Arrays::getIndexOf($this->elements, $element); - - return null !== $index && false !== $index; - } - - /** - * Returns previous element for given element - * - * @param mixed $element The element to verify - * @return mixed|null - */ - public function getPrevious($element) - { - return Arrays::getPreviousElement($this->elements, $element); - } - - /** - * Returns next element for given element - * - * @param mixed $element The element to verify - * @return mixed|null - */ - public function getNext($element) - { - return Arrays::getNextElement($this->elements, $element); - } - - /** - * Returns the first element in the collection - * - * @return mixed - */ - public function getFirst() - { - return Arrays::getFirstElement($this->elements); - } - - /** - * Returns the last element in the collection - * - * @return mixed - */ - public function getLast() - { - return Arrays::getLastElement($this->elements); - } - - /** - * Returns element with given index - * - * @param mixed $index Index / key of the element - * @return mixed|null - */ - public function getByIndex($index) - { - if (isset($this->elements[$index])) { - return $this->elements[$index]; - } - - return null; - } - - /** - * Returns representation of object as array - * - * @return array - */ - public function toArray() - { - return $this->elements; - } -} diff --git a/src/Traits/CollectionTrait.php b/src/Traits/CollectionTrait.php deleted file mode 100644 index 8cb0cad..0000000 --- a/src/Traits/CollectionTrait.php +++ /dev/null @@ -1,28 +0,0 @@ - - * @copyright Meritoo - */ -trait CollectionTrait -{ - use MainTrait; - use CountableTrait; - use ArrayAccessTrait; - use IteratorAggregateTrait; -} diff --git a/src/Traits/CssSelector/FormCssSelector.php b/src/Traits/CssSelector/FormCssSelector.php index 964ce71..9766814 100644 --- a/src/Traits/CssSelector/FormCssSelector.php +++ b/src/Traits/CssSelector/FormCssSelector.php @@ -16,6 +16,24 @@ namespace Meritoo\Common\Traits\CssSelector; */ trait FormCssSelector { + /** + * Returns selector of field-set using index/position of the field-set + * + * @param string $formName Name of form (value of the "name" attribute) + * @param int $fieldSetIndex Index/Position of the field-set + * @return string + */ + public static function getFieldSetByIndexSelector($formName, $fieldSetIndex) + { + $formSelector = static::getFormByNameSelector($formName); + + if (empty($formSelector) || 0 > $fieldSetIndex) { + return ''; + } + + return sprintf('%s fieldset:nth-of-type(%d)', $formSelector, $fieldSetIndex); + } + /** * Returns selector of form based on its name * @@ -33,25 +51,6 @@ trait FormCssSelector return sprintf('form[name="%s"]', $formName); } - /** - * Returns selector of the input field based on its name - * - * @param string $formName Name of form (value of the "name" attribute) - * @param string $fieldName Name of field (value of the "name" attribute) - * @return string - */ - public static function getInputByNameSelector($formName, $fieldName) - { - $formSelector = static::getFormByNameSelector($formName); - $fieldName = trim($fieldName); - - if (empty($formSelector) || empty($fieldName)) { - return ''; - } - - return sprintf('%s input[name="%s"]', $formSelector, $fieldName); - } - /** * Returns selector of the input field based on its ID * @@ -71,6 +70,25 @@ trait FormCssSelector return sprintf('%s input#%s', $formSelector, $fieldId); } + /** + * Returns selector of the input field based on its name + * + * @param string $formName Name of form (value of the "name" attribute) + * @param string $fieldName Name of field (value of the "name" attribute) + * @return string + */ + public static function getInputByNameSelector($formName, $fieldName) + { + $formSelector = static::getFormByNameSelector($formName); + $fieldName = trim($fieldName); + + if (empty($formSelector) || empty($fieldName)) { + return ''; + } + + return sprintf('%s input[name="%s"]', $formSelector, $fieldName); + } + /** * Returns selector of label * @@ -89,22 +107,4 @@ trait FormCssSelector return sprintf('%s label[for="%s"]', $formSelector, $fieldId); } - - /** - * Returns selector of field-set using index/position of the field-set - * - * @param string $formName Name of form (value of the "name" attribute) - * @param int $fieldSetIndex Index/Position of the field-set - * @return string - */ - public static function getFieldSetByIndexSelector($formName, $fieldSetIndex) - { - $formSelector = static::getFormByNameSelector($formName); - - if (empty($formSelector) || 0 > $fieldSetIndex) { - return ''; - } - - return sprintf('%s fieldset:nth-of-type(%d)', $formSelector, $fieldSetIndex); - } } diff --git a/src/Traits/Test/Base/BaseTestCaseTrait.php b/src/Traits/Test/Base/BaseTestCaseTrait.php index 74eea20..6ed450b 100644 --- a/src/Traits/Test/Base/BaseTestCaseTrait.php +++ b/src/Traits/Test/Base/BaseTestCaseTrait.php @@ -6,15 +6,19 @@ * file that was distributed with this source code. */ +declare(strict_types=1); + namespace Meritoo\Common\Traits\Test\Base; use DateTime; use Generator; +use Meritoo\Common\Exception\Reflection\ClassWithoutConstructorException; use Meritoo\Common\Exception\Type\UnknownOopVisibilityTypeException; use Meritoo\Common\Type\OopVisibilityType; use Meritoo\Common\Utilities\Miscellaneous; use ReflectionClass; use ReflectionMethod; +use RuntimeException; use stdClass; /** @@ -32,44 +36,15 @@ trait BaseTestCaseTrait */ private static $testsDataDirPath = 'data/tests'; - /** - * Provides an empty value - * - * @return Generator - */ - public function provideEmptyValue() - { - yield['']; - yield[' ']; - yield[null]; - yield[0]; - yield[false]; - yield[[]]; - } - - /** - * Provides an empty scalar value - * - * @return Generator - */ - public function provideEmptyScalarValue() - { - yield['']; - yield[' ']; - yield[null]; - yield[0]; - yield[false]; - } - /** * Provides boolean value * * @return Generator */ - public function provideBooleanValue() + public function provideBooleanValue(): ?Generator { - yield[false]; - yield[true]; + yield [false]; + yield [true]; } /** @@ -77,12 +52,12 @@ trait BaseTestCaseTrait * * @return Generator */ - public function provideDateTimeInstance() + public function provideDateTimeInstance(): ?Generator { - yield[new DateTime()]; - yield[new DateTime('yesterday')]; - yield[new DateTime('now')]; - yield[new DateTime('tomorrow')]; + yield [new DateTime()]; + yield [new DateTime('yesterday')]; + yield [new DateTime('now')]; + yield [new DateTime('tomorrow')]; } /** @@ -90,31 +65,48 @@ trait BaseTestCaseTrait * * @return Generator */ - public function provideDateTimeRelativeFormat() + public function provideDateTimeRelativeFormat(): ?Generator { - yield['now']; - yield['yesterday']; - yield['tomorrow']; - yield['back of 10']; - yield['front of 10']; - yield['last day of February']; - yield['first day of next month']; - yield['last day of previous month']; - yield['last day of next month']; - yield['Y-m-d']; - yield['Y-m-d 10:00']; + yield ['now']; + yield ['yesterday']; + yield ['tomorrow']; + yield ['back of 10']; + yield ['front of 10']; + yield ['last day of February']; + yield ['first day of next month']; + yield ['last day of previous month']; + yield ['last day of next month']; + yield ['Y-m-d']; + yield ['Y-m-d 10:00']; } /** - * Provides path of not existing file, e.g. "lorem/ipsum.jpg" + * Provides an empty scalar value * * @return Generator */ - public function provideNotExistingFilePath() + public function provideEmptyScalarValue(): ?Generator { - yield['lets-test.doc']; - yield['lorem/ipsum.jpg']; - yield['surprise/me/one/more/time.txt']; + yield ['']; + yield [' ']; + yield [null]; + yield [0]; + yield [false]; + } + + /** + * Provides an empty value + * + * @return Generator + */ + public function provideEmptyValue(): ?Generator + { + yield ['']; + yield [' ']; + yield [null]; + yield [0]; + yield [false]; + yield [[]]; } /** @@ -122,19 +114,114 @@ trait BaseTestCaseTrait * * @return Generator */ - public function provideNonScalarValue() + public function provideNonScalarValue(): ?Generator { - yield[ - [], - ]; + yield [[]]; + yield [null]; + yield [new stdClass()]; + } - yield[ - null, - ]; + /** + * Provides path of not existing file, e.g. "lorem/ipsum.jpg" + * + * @return Generator + */ + public function provideNotExistingFilePath(): ?Generator + { + yield ['lets-test.doc']; + yield ['lorem/ipsum.jpg']; + yield ['surprise/me/one/more/time.txt']; + } - yield[ - new stdClass(), - ]; + /** + * Verifies visibility and arguments of class constructor + * + * @param string $className Fully-qualified name of class that contains constructor to verify + * @param string $visibilityType Expected visibility of verified method. One of OopVisibilityType class + * constants. + * @param int $argumentsCount (optional) Expected count/amount of arguments of the verified method + * @param int $requiredArgumentsCount (optional) Expected count/amount of required arguments of the verified + * method + * @throws ClassWithoutConstructorException + */ + protected static function assertConstructorVisibilityAndArguments( + string $className, + string $visibilityType, + int $argumentsCount = 0, + int $requiredArgumentsCount = 0 + ): void { + $reflection = new ReflectionClass($className); + $method = $reflection->getConstructor(); + + if (null === $method) { + throw ClassWithoutConstructorException::create($className); + } + + static::assertMethodVisibility($method, $visibilityType); + static::assertMethodArgumentsCount($method, $argumentsCount, $requiredArgumentsCount); + } + + /** + * Asserts that class with given namespace has no constructor + * + * @param string $className Fully-qualified name of class that contains constructor to verify + */ + protected static function assertHasNoConstructor(string $className): void + { + $reflection = new ReflectionClass($className); + $constructor = $reflection->getConstructor(); + + static::assertNull($constructor); + } + + /** + * Verifies count of method's arguments + * + * @param ReflectionMethod $method Name of method or just the method to verify + * @param int $argumentsCount (optional) Expected count/amount of arguments of the verified method + * @param int $requiredCount (optional) Expected count/amount of required arguments of the verified + * method + * @throws RuntimeException + */ + protected static function assertMethodArgumentsCount( + ReflectionMethod $method, + int $argumentsCount = 0, + int $requiredCount = 0 + ): void { + static::assertSame($argumentsCount, $method->getNumberOfParameters()); + static::assertSame($requiredCount, $method->getNumberOfRequiredParameters()); + } + + /** + * Verifies visibility of method + * + * @param ReflectionMethod $method Name of method or just the method to verify + * @param string $visibilityType Expected visibility of verified method. One of OopVisibilityType + * class constants. + * @throws UnknownOopVisibilityTypeException + * @throws RuntimeException + */ + protected static function assertMethodVisibility(ReflectionMethod $method, string $visibilityType): void + { + // Type of visibility is not correct? + if (!OopVisibilityType::isCorrectType($visibilityType)) { + throw UnknownOopVisibilityTypeException::createException($visibilityType); + } + + switch ($visibilityType) { + case OopVisibilityType::IS_PUBLIC: + static::assertTrue($method->isPublic()); + + break; + case OopVisibilityType::IS_PROTECTED: + static::assertTrue($method->isProtected()); + + break; + case OopVisibilityType::IS_PRIVATE: + static::assertTrue($method->isPrivate()); + + break; + } } /** @@ -145,7 +232,7 @@ trait BaseTestCaseTrait * @param string $directoryPath (optional) Path of directory containing the file * @return string */ - public function getFilePathForTesting($fileName, $directoryPath = '') + protected function getFilePathForTesting(string $fileName, string $directoryPath = ''): string { $rootPath = Miscellaneous::getProjectRootPath(); @@ -159,118 +246,12 @@ trait BaseTestCaseTrait return Miscellaneous::concatenatePaths($paths); } - /** - * Verifies visibility and arguments of method - * - * @param string $classNamespace Namespace of class that contains method to verify - * @param string|ReflectionMethod $method Name of method or just the method to verify - * @param string $visibilityType Expected visibility of verified method. One of - * OopVisibilityType class constants. - * @param int $argumentsCount (optional) Expected count/amount of arguments of the - * verified method - * @param int $requiredArgumentsCount (optional) Expected count/amount of required arguments - * of the verified method - * @throws UnknownOopVisibilityTypeException - * - * Attention. 2nd argument, the $method, may be: - * - string - name of the method - * - instance of ReflectionMethod - just the method (provided by ReflectionClass::getMethod() method) - */ - protected static function assertMethodVisibilityAndArguments( - $classNamespace, - $method, - $visibilityType, - $argumentsCount = 0, - $requiredArgumentsCount = 0 - ) { - /* - * Type of visibility is correct? - */ - if (!(new OopVisibilityType())->isCorrectType($visibilityType)) { - throw new UnknownOopVisibilityTypeException($visibilityType); - } - - $reflection = new ReflectionClass($classNamespace); - - /* - * Name of method provided only? - * Let's find instance of the method (based on reflection) - */ - if (!$method instanceof ReflectionMethod) { - $method = $reflection->getMethod($method); - } - - switch ($visibilityType) { - case OopVisibilityType::IS_PUBLIC: - static::assertTrue($method->isPublic()); - break; - - case OopVisibilityType::IS_PROTECTED: - static::assertTrue($method->isProtected()); - break; - - case OopVisibilityType::IS_PRIVATE: - static::assertTrue($method->isPrivate()); - break; - } - - static::assertEquals($argumentsCount, $method->getNumberOfParameters()); - static::assertEquals($requiredArgumentsCount, $method->getNumberOfRequiredParameters()); - } - - /** - * Verifies visibility and arguments of class constructor - * - * @param string $classNamespace Namespace of class that contains constructor to verify - * @param string $visibilityType Expected visibility of verified method. One of OopVisibilityType class - * constants. - * @param int $argumentsCount (optional) Expected count/amount of arguments of the verified method - * @param int $requiredArgumentsCount (optional) Expected count/amount of required arguments of the verified - * method - */ - protected static function assertConstructorVisibilityAndArguments( - $classNamespace, - $visibilityType, - $argumentsCount = 0, - $requiredArgumentsCount = 0 - ) { - /* - * Let's grab the constructor - */ - $reflection = new ReflectionClass($classNamespace); - $method = $reflection->getConstructor(); - - static::assertMethodVisibilityAndArguments( - $classNamespace, - $method, - $visibilityType, - $argumentsCount, - $requiredArgumentsCount - ); - } - - /** - * Asserts that class with given namespace has no constructor - * - * @param string $classNamespace Namespace of class that contains constructor to verify - */ - protected static function assertHasNoConstructor($classNamespace) - { - /* - * Let's grab the constructor - */ - $reflection = new ReflectionClass($classNamespace); - $constructor = $reflection->getConstructor(); - - static::assertNull($constructor); - } - /** * Sets path of directory with data used by test cases * * @param string $testsDataDirPath Path of directory with data used by test cases */ - protected static function setTestsDataDirPath($testsDataDirPath) + protected static function setTestsDataDirPath(string $testsDataDirPath): void { static::$testsDataDirPath = $testsDataDirPath; } diff --git a/src/Traits/Test/Base/BaseTypeTestCaseTrait.php b/src/Traits/Test/Base/BaseTypeTestCaseTrait.php index 5633c8d..7c1d85f 100644 --- a/src/Traits/Test/Base/BaseTypeTestCaseTrait.php +++ b/src/Traits/Test/Base/BaseTypeTestCaseTrait.php @@ -6,6 +6,8 @@ * file that was distributed with this source code. */ +declare(strict_types=1); + namespace Meritoo\Common\Traits\Test\Base; use Generator; @@ -19,10 +21,17 @@ use Meritoo\Common\Type\Base\BaseType; */ trait BaseTypeTestCaseTrait { + /** + * Provides type to verify and information if it's correct + * + * @return Generator + */ + abstract public function provideTypeToVerify(): Generator; + /** * Verifies availability of all types */ - public function testAvailabilityOfAllTypes() + public function testAvailabilityOfAllTypes(): void { $available = $this->getTestedTypeInstance()->getAll(); $all = $this->getAllExpectedTypes(); @@ -33,34 +42,27 @@ trait BaseTypeTestCaseTrait /** * Verifies whether given type is correct or not * - * @param string $type Type to verify - * @param bool $expected Information if given type is correct or not + * @param bool $isCorrect Information if processed type is correct + * @param bool $expected Expected information if processed type is correct * * @dataProvider provideTypeToVerify */ - public function testIfGivenTypeIsCorrect($type, $expected) + public function testIfGivenTypeIsCorrect(bool $isCorrect, bool $expected): void { - static::assertEquals($expected, $this->getTestedTypeInstance()->isCorrectType($type)); + static::assertEquals($expected, $isCorrect); } - /** - * Provides type to verify and information if it's correct - * - * @return Generator - */ - abstract public function provideTypeToVerify(); - - /** - * Returns instance of the tested type - * - * @return BaseType - */ - abstract protected function getTestedTypeInstance(); - /** * Returns all expected types of the tested type * * @return array */ - abstract protected function getAllExpectedTypes(); + abstract protected function getAllExpectedTypes(): array; + + /** + * Returns instance of the tested type + * + * @return BaseType + */ + abstract protected function getTestedTypeInstance(): BaseType; } diff --git a/src/Traits/ValueObject/HumanTrait.php b/src/Traits/ValueObject/HumanTrait.php index b8b7cd8..71d586a 100644 --- a/src/Traits/ValueObject/HumanTrait.php +++ b/src/Traits/ValueObject/HumanTrait.php @@ -6,8 +6,12 @@ * file that was distributed with this source code. */ +declare(strict_types=1); + namespace Meritoo\Common\Traits\ValueObject; +use DateTime; + /** * Methods and properties related to human * @@ -33,26 +37,26 @@ trait HumanTrait /** * Email address * - * @var string + * @var null|string */ protected $email; /** * Birth date * - * @var \DateTime + * @var null|DateTime */ protected $birthDate; /** * Class constructor * - * @param string $firstName First name - * @param string $lastName Last name - * @param string $email (optional) Email address - * @param \DateTime $birthDate (optional) Birth date + * @param string $firstName First name + * @param string $lastName Last name + * @param null|string $email (optional) Email address. Default: null. + * @param null|DateTime $birthDate (optional) Birth date. Default: null. */ - public function __construct($firstName, $lastName, $email = null, \DateTime $birthDate = null) + public function __construct(string $firstName, string $lastName, ?string $email = null, ?DateTime $birthDate = null) { $this->firstName = $firstName; $this->lastName = $lastName; @@ -68,12 +72,34 @@ trait HumanTrait public function __toString() { $template = '%s'; + $email = ''; if ('' !== $this->email && null !== $this->email) { $template .= ' <%s>'; + $email = $this->email; } - return sprintf($template, $this->getFullName(), $this->email); + return sprintf($template, $this->getFullName(), $email); + } + + /** + * Returns birth date + * + * @return null|DateTime + */ + public function getBirthDate(): ?DateTime + { + return $this->birthDate; + } + + /** + * Returns email address + * + * @return null|string + */ + public function getEmail(): ?string + { + return $this->email; } /** @@ -81,48 +107,19 @@ trait HumanTrait * * @return string */ - public function getFirstName() + public function getFirstName(): string { return $this->firstName; } - /** - * Returns last name - * - * @return string - */ - public function getLastName() - { - return $this->lastName; - } - - /** - * Returns email address - * - * @return string|null - */ - public function getEmail() - { - return $this->email; - } - - /** - * Returns birth date - * - * @return \DateTime|null - */ - public function getBirthDate() - { - return $this->birthDate; - } - /** * Returns the full name * - * @param bool $firstNameFirst (optional) If is set to true, first name is the first part. Otherwise - last name. + * @param bool $firstNameFirst (optional) If is set to true, first name is the first part (default behaviour). + * Otherwise - name. * @return string */ - public function getFullName($firstNameFirst = true) + public function getFullName(bool $firstNameFirst = true): string { $beginning = $this->lastName; $finish = $this->firstName; @@ -134,4 +131,14 @@ trait HumanTrait return trim(sprintf('%s %s', $beginning, $finish)); } + + /** + * Returns last name + * + * @return string + */ + public function getLastName(): string + { + return $this->lastName; + } } diff --git a/src/Type/Base/BaseType.php b/src/Type/Base/BaseType.php index ec8dafa..26706d4 100644 --- a/src/Type/Base/BaseType.php +++ b/src/Type/Base/BaseType.php @@ -22,7 +22,7 @@ abstract class BaseType /** * All types * - * @var array + * @var null|array */ private $all; @@ -31,7 +31,7 @@ abstract class BaseType * * @return array */ - public function getAll() + public function getAll(): array { if (null === $this->all) { $this->all = Reflection::getConstants($this); @@ -43,11 +43,11 @@ abstract class BaseType /** * Returns information if given type is correct * - * @param mixed $type The type to check + * @param null|string $type The type to check * @return bool */ - public function isCorrectType($type) + public static function isCorrectType(?string $type): bool { - return in_array($type, $this->getAll(), true); + return in_array($type, (new static())->getAll()); } } diff --git a/src/Type/DatePartType.php b/src/Type/DatePartType.php index dfa91c4..e6d8b2b 100644 --- a/src/Type/DatePartType.php +++ b/src/Type/DatePartType.php @@ -23,40 +23,40 @@ class DatePartType extends BaseType * * @var string */ - const DAY = 'day'; + public const DAY = 'day'; /** * The "hour" date part * * @var string */ - const HOUR = 'hour'; + public const HOUR = 'hour'; /** * The "minute" date part * * @var string */ - const MINUTE = 'minute'; + public const MINUTE = 'minute'; /** * The "month" date part * * @var string */ - const MONTH = 'month'; + public const MONTH = 'month'; /** * The "second" date part * * @var string */ - const SECOND = 'second'; + public const SECOND = 'second'; /** * The "year" date part * * @var string */ - const YEAR = 'year'; + public const YEAR = 'year'; } diff --git a/src/Type/DatePeriod.php b/src/Type/DatePeriod.php index abfe4db..7fb1fc0 100644 --- a/src/Type/DatePeriod.php +++ b/src/Type/DatePeriod.php @@ -24,113 +24,134 @@ class DatePeriod extends BaseType /** * The period constant: last month * - * @var int + * @var string */ - const LAST_MONTH = 4; + public const LAST_MONTH = '4'; /** * The period constant: last week * - * @var int + * @var string */ - const LAST_WEEK = 1; + public const LAST_WEEK = '1'; /** * The period constant: last year * - * @var int + * @var string */ - const LAST_YEAR = 7; + public const LAST_YEAR = '7'; /** * The period constant: next month * - * @var int + * @var string */ - const NEXT_MONTH = 6; + public const NEXT_MONTH = '6'; /** * The period constant: next week * - * @var int + * @var string */ - const NEXT_WEEK = 3; + public const NEXT_WEEK = '3'; /** * The period constant: next year * - * @var int + * @var string */ - const NEXT_YEAR = 9; + public const NEXT_YEAR = '9'; /** * The period constant: this month * - * @var int + * @var string */ - const THIS_MONTH = 5; + public const THIS_MONTH = '5'; /** * The period constant: this week * - * @var int + * @var string */ - const THIS_WEEK = 2; + public const THIS_WEEK = '2'; /** * The period constant: this year * - * @var int + * @var string */ - const THIS_YEAR = 8; + public const THIS_YEAR = '8'; /** * The start date of period * - * @var DateTime + * @var null|DateTime */ private $startDate; /** * The end date of period * - * @var DateTime + * @var null|DateTime */ private $endDate; /** * Class constructor * - * @param DateTime $startDate (optional) The start date of period - * @param DateTime $endDate (optional) The end date of period + * @param null|DateTime $startDate (optional) The start date of period + * @param null|DateTime $endDate (optional) The end date of period */ - public function __construct(DateTime $startDate = null, DateTime $endDate = null) + public function __construct(?DateTime $startDate = null, ?DateTime $endDate = null) { $this->startDate = $startDate; $this->endDate = $endDate; } + /** + * Returns the end date of period + * + * @return null|DateTime + */ + public function getEndDate(): ?DateTime + { + return $this->endDate; + } + + /** + * Sets the end date of period + * + * @param null|DateTime $endDate (optional) The end date of period. Default: null. + * @return $this + */ + public function setEndDate(?DateTime $endDate = null): self + { + $this->endDate = $endDate; + + return $this; + } + /** * Returns formatted one of the period's date: start date or end date * * @param string $format Format used to format the date - * @param bool $startDate (optional) If is set to true, start date is formatted. Otherwise - end date. + * @param bool $startDate (optional) If is set to true, start date will be formatted (default behaviour). + * Otherwise - end date. * @return string */ - public function getFormattedDate($format, $startDate = true) + public function getFormattedDate(string $format, bool $startDate = true): string { $date = $this->getEndDate(); - /* - * Start date should be formatted? - */ + // Start date should be formatted? if ($startDate) { $date = $this->getStartDate(); } - /* - * Unknown date or format is invalid? - */ + // Unknown date or format is invalid? + // Nothing to do if (null === $date || !Date::isValidDateFormat($format)) { return ''; } @@ -138,35 +159,12 @@ class DatePeriod extends BaseType return $date->format($format); } - /** - * Returns the end date of period - * - * @return DateTime - */ - public function getEndDate() - { - return $this->endDate; - } - - /** - * Sets the end date of period - * - * @param DateTime $endDate (optional) The end date of period - * @return $this - */ - public function setEndDate(DateTime $endDate = null) - { - $this->endDate = $endDate; - - return $this; - } - /** * Returns the start date of period * - * @return DateTime + * @return null|DateTime */ - public function getStartDate() + public function getStartDate(): ?DateTime { return $this->startDate; } @@ -174,10 +172,10 @@ class DatePeriod extends BaseType /** * Sets the start date of period * - * @param DateTime $startDate (optional) The start date of period + * @param null|DateTime $startDate (optional) The start date of period. Default: null. * @return $this */ - public function setStartDate(DateTime $startDate = null) + public function setStartDate(?DateTime $startDate = null): self { $this->startDate = $startDate; diff --git a/src/Type/OopVisibilityType.php b/src/Type/OopVisibilityType.php index 1929710..1299eea 100644 --- a/src/Type/OopVisibilityType.php +++ b/src/Type/OopVisibilityType.php @@ -17,21 +17,21 @@ class OopVisibilityType extends BaseType /** * The "private" visibility of OOP * - * @var int + * @var string */ - const IS_PRIVATE = 3; + public const IS_PRIVATE = '3'; /** * The "protected" visibility of OOP * - * @var int + * @var string */ - const IS_PROTECTED = 2; + public const IS_PROTECTED = '2'; /** * The "public" visibility of OOP * - * @var int + * @var string */ - const IS_PUBLIC = 1; + public const IS_PUBLIC = '1'; } diff --git a/src/Utilities/Arrays.php b/src/Utilities/Arrays.php index f994950..5c82257 100644 --- a/src/Utilities/Arrays.php +++ b/src/Utilities/Arrays.php @@ -21,794 +21,104 @@ class Arrays * * @var string */ - const POSITION_KEY_NAME = 'position'; + public const POSITION_KEY_NAME = 'position'; /** - * Converts given array's column to string. - * Recursive call is made for multi-dimensional arrays. + * Returns information if keys / indexes of given array are integers, in other words if the array contains + * zero-based keys / indexes * - * @param array $array Data to be converted - * @param string|int $arrayColumnKey (optional) Column name. Default: "". - * @param string $separator (optional) Separator used between values. Default: ",". - * @return string|null - */ - public static function values2string(array $array, $arrayColumnKey = '', $separator = ',') - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $values = []; - - foreach ($array as $key => $value) { - $appendMe = null; - - if (is_array($value)) { - $appendMe = self::values2string($value, $arrayColumnKey, $separator); - } elseif (empty($arrayColumnKey)) { - $appendMe = $value; - } elseif ($key === $arrayColumnKey) { - $appendMe = $array[$arrayColumnKey]; - } - - /* - * Part to append is unknown? - * Let's go to next part - */ - if (null === $appendMe) { - continue; - } - - $values[] = $appendMe; - } - - /* - * No values found? - * Nothing to do - */ - if (empty($values)) { - return null; - } - - return implode($separator, $values); - } - - /** - * Converts given array to string with keys, e.g. abc=1&def=2 or abc="1" def="2" - * - * @param array $array Data to be converted - * @param string $separator (optional) Separator used between name-value pairs. Default: ",". - * @param string $valuesKeysSeparator (optional) Separator used between name and value. Default: "=". - * @param string $valuesWrapper (optional) Wrapper used to wrap values, e.g. double-quote: key="value". - * Default: "". - * @return string|null - */ - public static function valuesKeys2string( - array $array, - $separator = ',', - $valuesKeysSeparator = '=', - $valuesWrapper = '' - ) { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $result = ''; - - foreach ($array as $key => $value) { - if (!empty($result)) { - $result .= $separator; - } - - if (!empty($valuesWrapper)) { - $value = sprintf('%s%s%s', $valuesWrapper, $value, $valuesWrapper); - } - - $result .= $key . $valuesKeysSeparator . $value; - } - - return $result; - } - - /** - * Converts given array's rows to csv string - * - * @param array $array Data to be converted. It have to be an array that represents database table. - * @param string $separator (optional) Separator used between values. Default: ",". - * @return string|null - */ - public static function values2csv(array $array, $separator = ',') - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $rows = []; - $lineSeparator = "\n"; - - foreach ($array as $row) { - /* - * I have to use html_entity_decode() function here, because some string values can contain - * entities with semicolon and this can destroy the CSV column order. - */ - - if (is_array($row) && !empty($row)) { - foreach ($row as $key => $value) { - $row[$key] = html_entity_decode($value); - } - - $rows[] = implode($separator, $row); - } - } - - if (empty($rows)) { - return ''; - } - - return implode($lineSeparator, $rows); - } - - /** - * Returns information if given element is the first one - * - * @param array $array The array to get the first element of - * @param mixed $element The element to check / verify - * @param bool $firstLevelOnly (optional) If is set to true, first element is returned. Otherwise - totally - * first element is returned (first of the First array). + * @param array $array The array to check + * @param bool $firstLevelOnly (optional) If is set to true, all keys / indexes are checked. Otherwise - from the + * first level only (default behaviour). * @return bool */ - public static function isFirstElement(array $array, $element, $firstLevelOnly = true) + public static function areAllKeysIntegers(array $array, $firstLevelOnly = false) { - $firstElement = self::getFirstElement($array, $firstLevelOnly); + $pattern = '\d+'; - return $element === $firstElement; + return self::areAllKeysMatchedByPattern($array, $pattern, $firstLevelOnly); } /** - * Returns the first element of given array + * Returns information if keys / indexes of given array are matched by given pattern * - * It may be first element of given array or the totally first element from the all elements (first element of the - * first array). - * - * @param array $array The array to get the first element of - * @param bool $firstLevelOnly (optional) If is set to true, first element is returned. Otherwise - totally - * first element is returned (first of the first array). - * @return mixed - */ - public static function getFirstElement(array $array, $firstLevelOnly = true) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $firstKey = self::getFirstKey($array); - $first = $array[$firstKey]; - - if (!$firstLevelOnly && is_array($first)) { - $first = self::getFirstElement($first, $firstLevelOnly); - } - - return $first; - } - - /** - * Returns first key of array - * - * @param array $array The array to get the first key of - * @return mixed - */ - public static function getFirstKey(array $array) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $keys = array_keys($array); - - return $keys[0]; - } - - /** - * Returns information if given element is the last one - * - * @param array $array The array to get the last element of - * @param mixed $element The element to check / verify - * @param bool $firstLevelOnly (optional) If is set to true, last element is returned. Otherwise - totally - * last element is returned (last of the latest array). + * @param array $array The array to check + * @param string $pattern The pattern which keys / indexes should match, e.g. "\d+" + * @param bool $firstLevelOnly (optional) If is set to true, all keys / indexes are checked. Otherwise - from the + * first level only. * @return bool */ - public static function isLastElement(array $array, $element, $firstLevelOnly = true) + public static function areAllKeysMatchedByPattern(array $array, string $pattern, bool $firstLevelOnly = false): bool { - $lastElement = self::getLastElement($array, $firstLevelOnly); - - return $element === $lastElement; - } - - /** - * Returns the last element of given array - * - * It may be last element of given array or the totally last element from the all elements (last element of the - * latest array). - * - * @param array $array The array to get the last element of - * @param bool $firstLevelOnly (optional) If is set to true, last element is returned. Otherwise - totally - * last element is returned (last of the latest array). - * @return mixed - */ - public static function getLastElement(array $array, $firstLevelOnly = true) - { - /* - * No elements? - * Nothing to do - */ + // No elements? Nothing to do if (empty($array)) { - return null; - } - - $last = end($array); - - if (!$firstLevelOnly && is_array($last)) { - $last = self::getLastElement($last, $firstLevelOnly); - } - - return $last; - } - - /** - * Returns breadcrumb (a path) to the last element of array - * - * @param array $array Data to get the breadcrumb - * @param string $separator (optional) Separator used to stick the elements. Default: "/". - * @return string|null - */ - public static function getLastElementBreadCrumb(array $array, $separator = '/') - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $keys = array_keys($array); - $keysCount = count($keys); - - $lastKey = $keys[$keysCount - 1]; - $last = end($array); - - $breadCrumb = $lastKey; - - if (is_array($last)) { - $crumb = self::getLastElementBreadCrumb($last, $separator); - } else { - $crumb = $last; - } - - return $breadCrumb . $separator . $crumb; - } - - /** - * Returns the last row of array - * - * @param array $array The array to get the last row of - * @return mixed - */ - public static function getLastRow(array $array) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $effect = []; - $last = end($array); - - if (is_array($last)) { - /* - * We've got an array, so looking for the last row of array will be done recursively - */ - $effect = self::getLastRow($last); - - /* - * The last row is not an array or it's an empty array? - * Let's use the previous candidate - */ - if (!is_array($effect) || (is_array($effect) && empty($effect))) { - $effect = $last; - } - } - - return $effect; - } - - /** - * Replaces array keys that match given pattern with new key name - * - * @param array $dataArray The array - * @param string $oldKeyPattern Old key pattern - * @param string $newKey New key name - * @return array - */ - public static function replaceArrayKeys($dataArray, $oldKeyPattern, $newKey) - { - $effect = []; - - if (is_array($dataArray) && !empty($dataArray)) { - foreach ($dataArray as $key => $value) { - if (preg_match($oldKeyPattern, $key)) { - $key = $newKey; - } - - if (is_array($value)) { - $value = self::replaceArrayKeys($value, $oldKeyPattern, $newKey); - } - - $effect[$key] = $value; - } - } - - return $effect; - } - - /** - * Generates JavaScript code for given PHP array - * - * @param array $array The array that should be generated to JavaScript - * @param string $jsVariableName (optional) Name of the variable that will be in generated JavaScript code - * @param bool $preserveIndexes (optional) If is set to true and $jsVariableName isn't empty, indexes also - * will be added to the JavaScript code. Otherwise not. - * @return string|null - */ - public static function array2JavaScript(array $array, $jsVariableName = '', $preserveIndexes = false) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $result = ''; - $counter = 0; - - $arrayCount = count($array); - $arrayPrepared = self::quoteStrings($array); - $isMultiDimensional = self::isMultiDimensional($arrayPrepared); - - /* - * Name of the variable was not provided and it's a multi dimensional array? - * Let's create the name, because variable is required for later usage (related to multi dimensional array) - */ - if (empty($jsVariableName) && $isMultiDimensional) { - $jsVariableName = 'autoGeneratedVariable'; - } - - if (!empty($jsVariableName) && is_string($jsVariableName)) { - $result .= sprintf('var %s = ', $jsVariableName); - } - - $result .= 'new Array('; - - if ($preserveIndexes || $isMultiDimensional) { - $result .= $arrayCount; - $result .= ');'; - } - - foreach ($arrayPrepared as $index => $value) { - ++$counter; - - if (is_array($value)) { - $variable = $index; - - if (is_int($index)) { - $variable = 'value_' . $variable; - } - - $value = self::array2JavaScript($value, $variable, $preserveIndexes); - - if (null !== $value && '' !== $value) { - /* - * Add an empty line for the 1st iteration only. Required to avoid missing empty line after - * declaration of variable: - * - * var autoGeneratedVariable = new Array(...);autoGeneratedVariable[0] = new Array(...); - * autoGeneratedVariable[1] = new Array(...); - */ - if (1 === $counter) { - $result .= "\n"; - } - - $result .= $value . "\n"; - $result .= sprintf('%s[%s] = %s;', $jsVariableName, Miscellaneous::quoteValue($index), $variable); - - if ($counter !== $arrayCount) { - $result .= "\n"; - } - } - } elseif ($preserveIndexes) { - if (!empty($jsVariableName)) { - $index = Miscellaneous::quoteValue($index); - $result .= sprintf("\n%s[%s] = %s;", $jsVariableName, $index, $value); - } - } else { - $format = '%s'; - - if ($counter < $arrayCount) { - $format .= ', '; - } - - $result .= sprintf($format, $value); - } - } - - if (!$preserveIndexes && !$isMultiDimensional) { - $result .= ');'; - } - - return $result; - } - - /** - * Quotes (adds quotes) to elements that are strings and returns new array (with quoted elements) - * - * @param array $array The array to check for string values - * @return array|null - */ - public static function quoteStrings(array $array) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $result = []; - - foreach ($array as $index => $value) { - if (is_array($value)) { - $value = self::quoteStrings($value); - } elseif (is_string($value)) { - if (!Regex::isQuoted($value)) { - $value = '\'' . $value . '\''; - } - } - - $result[$index] = $value; - } - - return $result; - } - - /** - * Removes marginal element (first or last) - * - * @param string|array $item The item which should be shortened - * @param bool $last (optional) If is set to true, last element is removed. Otherwise - first. - * @return string|array - */ - public static function removeMarginalElement($item, $last = true) - { - if (is_string($item)) { - if ($last) { - $item = substr($item, 0, -1); - } else { - $item = substr($item, 1); - } - } elseif (is_array($item)) { - $key = self::getFirstKey($item); - - if ($last) { - $key = self::getLastKey($item); - } - - unset($item[$key]); - } - - return $item; - } - - /** - * Returns last key of array - * - * @param array $array The array to get the last key of - * @return mixed - */ - public static function getLastKey(array $array) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $keys = array_keys($array); - - return end($keys); - } - - /** - * Removes element / item of given array - * - * @param array $array The array that contains element / item which should be removed - * @param mixed $item The element / item which should be removed - * @return bool|array - */ - public static function removeElement(array $array, $item) - { - /* - * No elements or the element does not exist? - * Nothing to do - */ - if (empty($array) || !in_array($item, $array, true)) { return false; } /* - * Flip the array to make it looks like: value => key + * I suppose that all are keys are matched + * and then I have to look for keys that don't matches */ - $arrayFlipped = array_flip($array); + $areMatched = true; - /* - * Take the key of element / item that should be removed - */ - $key = $arrayFlipped[$item]; - - /* - * ...and remove the element / item - */ - unset($array[$key]); - - return $array; - } - - /** - * Removes items from given array starting at given element (before or after the element) - * - * @param array $array The array which contains items to remove - * @param mixed $needle The element which is start point of deletion - * @param bool $before (optional) If is set to true, all elements before given needle are removed. Otherwise - all - * after needle. - */ - public static function removeElements(array &$array, $needle, $before = true) - { - if (!empty($array)) { - if (!$before) { - $array = array_reverse($array, true); - } - - foreach ($array as $key => &$value) { - $remove = false; - $isArray = is_array($value); - - if ($isArray) { - self::removeElements($value, $needle, $before); - - if ($isArray && empty($value)) { - $remove = true; - } - } elseif ($value === $needle) { - break; - } else { - $remove = true; - } - - if ($remove) { - unset($array[$key]); - } - } - - if (!$before) { - $array = array_reverse($array, true); - } - } - } - - /** - * Sets keys as values and values as keys in given array. - * Replaces keys with values. - * - * @param array $array The array to change values with keys - * @param bool $ignoreDuplicatedValues (optional) If is set to true, duplicated values are ignored. This means that - * when there is more than 1 value and that values become key, only the last - * value will be used with it's key, because other will be overridden. - * Otherwise - values are preserved and keys assigned to that values are - * returned as an array. - * @return array|null - * - * Example of $ignoreDuplicatedValues = false: - * - provided array - * $array = [ - * 'lorem' => 100, // <-- Duplicated value - * 'ipsum' => 200, - * 'dolor' => 100, // <-- Duplicated value - * ]; - * - * - result - * $replaced = [ - * 100 => [ - * 'lorem', // <-- Key of duplicated value - * 'dolor', // <-- Key of duplicated value - * ], - * 200 => 'ipsum', - * ]; - */ - public static function setKeysAsValues(array $array, $ignoreDuplicatedValues = true) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $replaced = []; + // Building the pattern + $rawPattern = $pattern; + $pattern = sprintf('|%s|', $rawPattern); foreach ($array as $key => $value) { /* - * The value it's an array? - * Let's replace keys with values in this array first + * Not matched? So I have to stop the iteration, because one not matched key + * means that not all keys are matched by given pattern */ - if (is_array($value)) { - $replaced[$key] = self::setKeysAsValues($value, $ignoreDuplicatedValues); - continue; + if (!preg_match($pattern, $key)) { + $areMatched = false; + + break; } /* - * Duplicated values shouldn't be ignored and processed value is used as key already? - * Let's use an array and that will contain all values (to avoid ignoring / overriding duplicated values) + * The not matching key was not found and the value is an array? + * Let's begin recursive looking for result */ - if (!$ignoreDuplicatedValues && isset($replaced[$value])) { - $existing = self::makeArray($replaced[$value]); - - $replaced[$value] = array_merge($existing, [ - $key, - ]); - - continue; + if ($areMatched && is_array($value) && !$firstLevelOnly) { + $areMatched = self::areAllKeysMatchedByPattern($value, $rawPattern, $firstLevelOnly); } + } + return $areMatched; + } + + /** + * Returns information if given array is empty, iow. information if all elements of given array are empty + * + * @param array $array The array to verify + * @param bool $strictNull (optional) If is set to true elements are verified if they are null. Otherwise - only + * if they are empty (e.g. null, '', 0, array()). + * @return bool + */ + public static function areAllValuesEmpty(array $array, $strictNull = false) + { + // No elements? Nothing to do + if (empty($array)) { + return false; + } + + foreach ($array as $element) { /* - * Standard behaviour + * If elements are verified if they are exactly null and the element is: + * - not an array + * - not null + * or elements are NOT verified if they are exactly null and the element is: + * - not empty (e.g. null, '', 0, array()) + * + * If one of the above is true, not all elements of given array are empty */ - $replaced[$value] = $key; - } - - return $replaced; - } - - /** - * Applies ksort() function recursively in the given array - * - * @param array $array The array to sort - * @param int $sortFlags (optional) Options of ksort() function - * @return array|null - */ - public static function ksortRecursive(array &$array, $sortFlags = SORT_REGULAR) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $effect = &$array; - ksort($effect, $sortFlags); - - foreach ($effect as &$value) { - if (is_array($value)) { - ksort($value, $sortFlags); + if ((!is_array($element) && $strictNull && null !== $element) || !empty($element)) { + return false; } } - return $effect; - } - - /** - * Returns count / amount of elements that are not array - * - * @param array $array The array to count - * @return int|null - */ - public static function getNonArrayElementsCount(array $array) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $count = 0; - - foreach ($array as &$value) { - if (is_array($value)) { - $count += self::getNonArrayElementsCount($value); - continue; - } - - ++$count; - } - - return $count; - } - - /** - * Converts given string with special separators to array - * - * Example: - * ~ string: - * "light:jasny|dark:ciemny" - * - * ~ array as a result: - * [ - * 'light' => 'jasny', - * 'dark' => 'ciemny', - * ] - * - * @param string $string The string to be converted - * @param string $separator (optional) Separator used between name-value pairs in the string. - * Default: "|". - * @param string $valuesKeysSeparator (optional) Separator used between name and value in the string. Default: ":". - * @return array - */ - public static function string2array($string, $separator = '|', $valuesKeysSeparator = ':') - { - /* - * Empty string? - * Nothing to do - */ - if (empty($string)) { - return null; - } - - $array = []; - $exploded = explode($separator, $string); - - foreach ($exploded as $item) { - $exploded2 = explode($valuesKeysSeparator, $item); - - if (2 === count($exploded2)) { - $key = trim($exploded2[0]); - $value = trim($exploded2[1]); - - $array[$key] = $value; - } - } - - return $array; + return true; } /** @@ -851,16 +161,457 @@ class Arrays return $result; } + /** + * Generates JavaScript code for given PHP array + * + * @param array $array The array that should be generated to JavaScript + * @param string $jsVariableName (optional) Name of the variable that will be in generated JavaScript code. + * Default: "autoGeneratedVariable". + * @param bool $preserveIndexes (optional) If is set to true and $jsVariableName isn't empty, indexes also + * will be added to the JavaScript code. Otherwise not (default behaviour). + * @return null|string + */ + public static function array2JavaScript( + array $array, + string $jsVariableName = '', + bool $preserveIndexes = false + ): ?string { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + $result = ''; + $counter = 0; + + $arrayCount = count($array); + $arrayPrepared = self::quoteStrings($array); + $isMultiDimensional = false; + + if (null !== $arrayPrepared) { + $isMultiDimensional = self::isMultiDimensional($arrayPrepared); + } + + /* + * Name of the variable was not provided and it's a multi dimensional array? + * Let's create the name, because variable is required for later usage (related to multi dimensional array) + */ + if (empty($jsVariableName) && $isMultiDimensional) { + $jsVariableName = 'autoGeneratedVariable'; + } + + if (!empty($jsVariableName)) { + $result .= sprintf('var %s = ', $jsVariableName); + } + + $result .= 'new Array('; + + if ($preserveIndexes || $isMultiDimensional) { + $result .= $arrayCount; + $result .= ');'; + } + + if (null !== $arrayPrepared) { + foreach ($arrayPrepared as $index => $value) { + ++$counter; + + if (is_array($value)) { + $variable = $index; + + if (is_int($index)) { + $variable = 'value_'.$variable; + } + + $value = self::array2JavaScript($value, $variable, $preserveIndexes); + + if (null !== $value && '' !== $value) { + /* + * Add an empty line for the 1st iteration only. Required to avoid missing empty line after + * declaration of variable: + * + * var autoGeneratedVariable = new Array(...);autoGeneratedVariable[0] = new Array(...); + * autoGeneratedVariable[1] = new Array(...); + */ + if (1 === $counter) { + $result .= "\n"; + } + + $result .= $value."\n"; + $result .= sprintf('%s[%s] = %s;', $jsVariableName, Miscellaneous::quoteValue($index), $variable); + + if ($counter !== $arrayCount) { + $result .= "\n"; + } + } + } elseif ($preserveIndexes) { + if (!empty($jsVariableName)) { + $index = Miscellaneous::quoteValue($index); + $result .= sprintf("\n%s[%s] = %s;", $jsVariableName, $index, $value); + } + } else { + $format = '%s'; + + if ($counter < $arrayCount) { + $format .= ', '; + } + + $result .= sprintf($format, $value); + } + } + } + + if (!$preserveIndexes && !$isMultiDimensional) { + $result .= ');'; + } + + return $result; + } + + /** + * Returns an array containing all the entries from 1st array that are not present in 2nd array. + * An item from 1st array is the same as in 2nd array if both, keys and values, are the same. + * + * Example of difference: + * $array1 = [ + * 1 => 'Lorem', + * 2 => 'ipsum, + * ]; + * + * $array2 = [ + * 1 => 'Lorem', + * 5 => 'ipsum, // <-- The same values, but different key. Here we got 5, in 1st array - 2. + * ]; + * + * @param array $array1 The 1st array to verify + * @param array $array2 The 2nd array to verify + * @param bool $valuesOnly (optional) If is set to true, compares values only. Otherwise - keys and values + * (default behaviour). + * @return array + */ + public static function arrayDiffRecursive(array $array1, array $array2, bool $valuesOnly = false): array + { + $result = []; + + /* + * Values should be compared only and both arrays are one-dimensional? + * Let's find difference by using simple function + */ + if ($valuesOnly && 1 === self::getDimensionsCount($array1) && 1 === self::getDimensionsCount($array2)) { + return array_diff($array1, $array2); + } + + foreach ($array1 as $key => $value) { + $array2HasKey = array_key_exists($key, $array2); + + // Values should be compared only? + if ($valuesOnly) { + $difference = null; + + if (is_array($value)) { + if ($array2HasKey && is_array($array2[$key])) { + $difference = self::arrayDiffRecursive($value, $array2[$key], $valuesOnly); + } + } elseif (!$array2HasKey || $value !== $array2[$key]) { + /* + * We are here, because: + * a) 2nd array hasn't key from 1st array + * OR + * b) key exists in both, 1st and 2nd array, but values are different + */ + $difference = $value; + } + + if (null !== $difference) { + $result[] = $difference; + } + + // The key exists in 2nd array? + } elseif ($array2HasKey) { + // The value it's an array (it's a nested array)? + if (is_array($value)) { + $diff = []; + + if (is_array($array2[$key])) { + // Let's verify the nested array + $diff = self::arrayDiffRecursive($value, $array2[$key], $valuesOnly); + } + + if (empty($diff)) { + continue; + } + + $result[$key] = $diff; + } elseif ($value !== $array2[$key]) { + // Value is different than in 2nd array? + // OKay, I've got difference + $result[$key] = $value; + } + } else { + // OKay, I've got difference + $result[$key] = $value; + } + } + + return $result; + } + + public static function containsEmptyStringsOnly(array $array): bool + { + if (empty($array)) { + return false; + } + + return '' === trim(implode('', $array)); + } + + /** + * Returns all values of given key. + * It may be useful when you want to retrieve all values of one column. + * + * @param array $array The array which should contain values of the key + * @param string $key The key + * @return null|array + */ + public static function getAllValuesOfKey(array $array, $key) + { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + $values = []; + + foreach ($array as $index => $value) { + if ($index === $key) { + $values[] = $value; + + continue; + } + + if (is_array($value)) { + $recursiveValues = self::getAllValuesOfKey($value, $key); + + if (!empty($recursiveValues)) { + $merged = array_merge($values, $recursiveValues); + $values = $merged; + } + } + } + + return $values; + } + + /** + * Returns count of dimensions, maximum nesting level actually, in given array + * + * @param array $array The array to verify + * @return int + */ + public static function getDimensionsCount(array $array): int + { + // No elements? Nothing to do + if (empty($array)) { + return 0; + } + + $dimensionsCount = 1; + + foreach ($array as $value) { + if (is_array($value)) { + /* + * I have to increment returned value, because that means we've got 1 level more (if the value is an + * array) + */ + $count = self::getDimensionsCount($value) + 1; + + if ($count > $dimensionsCount) { + $dimensionsCount = $count; + } + } + } + + return $dimensionsCount; + } + + public static function getElementsFromLevel(array $array, int $level, ?string $childrenKey = null): ?array + { + if (empty($array) || $level <= 0) { + return null; + } + + $result = []; + + foreach ($array as $key => $value) { + // This is the expected level (the deepest). Comparing with 1, because level will be decreased by 1 (later), + // and finally we will get the latest/deepest level that equals 1. + if ($level === 1) { + // No key of children (next level) provided or this is the same key as processed? + // We've got the expected value + if ($childrenKey === null || $key === $childrenKey) { + $result[] = $value; + } + + continue; + } + + // There is no deeper level + if (!is_array($value)) { + continue; + } + + // Let's dive one level down/deeper + $elements = self::getElementsFromLevel($value, $level - 1, $childrenKey); + + if ($elements === null) { + continue; + } + + // I have to load each element separately to avoid issue with incorrectly nested values + foreach ($elements as $element) { + $result[] = $element; + } + } + + return $result; + } + + /** + * Returns the first element of given array + * + * It may be first element of given array or the totally first element from the all elements (first element of the + * first array). + * + * @param array $array The array to get the first element of + * @param bool $firstLevelOnly (optional) If is set to true, first element is returned. Otherwise - totally + * first element is returned (first of the first array). + * @return mixed + */ + public static function getFirstElement(array $array, bool $firstLevelOnly = true) + { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + $firstKey = static::getFirstKey($array); + $result = $array[$firstKey]; + + if (!$firstLevelOnly && is_array($result)) { + $result = static::getFirstElement($result, $firstLevelOnly); + } + + return $result; + } + + /** + * Returns first key of array + * + * @param array $array The array to get the first key of + * @return mixed + */ + public static function getFirstKey(array $array) + { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + $keys = array_keys($array); + + return $keys[0]; + } + + /** + * Returns an index / key of given element in given array + * + * @param array $array The array to verify + * @param mixed $element The element who index / key is needed + * @return null|bool|mixed + */ + public static function getIndexOf(array $array, $element) + { + // No elements? Nothing to do + if (empty($array)) { + return false; + } + + foreach ($array as $index => $value) { + if ($value === $element) { + return $index; + } + } + + return null; + } + + /** + * Returns the last element of given array + * + * It may be last element of given array or the totally last element from the all elements (last element of the + * latest array). + * + * @param array $array The array to get the last element of + * @param bool $firstLevelOnly (optional) If is set to true, last element is returned. Otherwise - totally + * last element is returned (last of the latest array). + * @return mixed + */ + public static function getLastElement(array $array, bool $firstLevelOnly = true) + { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + $last = end($array); + + if (!$firstLevelOnly && is_array($last)) { + $last = static::getLastElement($last, $firstLevelOnly); + } + + return $last; + } + + /** + * Returns breadcrumb (a path) to the last element of array + * + * @param array $array Data to get the breadcrumb + * @param string $separator (optional) Separator used to stick the elements. Default: "/". + * @return null|string + */ + public static function getLastElementBreadCrumb(array $array, $separator = '/') + { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + $keys = array_keys($array); + $keysCount = count($keys); + + $lastKey = $keys[$keysCount - 1]; + $last = end($array); + + $breadCrumb = $lastKey; + + if (is_array($last)) { + $crumb = self::getLastElementBreadCrumb($last, $separator); + } else { + $crumb = $last; + } + + return $breadCrumb.$separator.$crumb; + } + /** * Returns paths of the last elements * - * @param array $array The array with elements - * @param string $separator (optional) Separator used between elements. Default: ".". - * @param string $parentPath (optional) Path of the parent element. Default: "". - * @param string|array $stopIfMatchedBy (optional) Patterns of keys or paths that matched will stop the process - * of path building and including children of those keys or paths (recursive - * will not be used for keys in lower level of given array). Default: "". - * @return array|null + * @param array $array The array with elements + * @param string $separator (optional) Separator used between elements. Default: ".". + * @param string $parentPath (optional) Path of the parent element. Default: "". + * @param array $stopIfMatchedBy (optional) Patterns of keys or paths when matched will stop the process of path + * building and including children of those keys or paths (recursive will not be + * used for keys in lower level of given array). Default: []. + * @return null|array * * Examples - $stopIfMatchedBy argument: * a) "\d+" @@ -869,21 +620,18 @@ class Arrays * "\d+", * ]; */ - public static function getLastElementsPaths(array $array, $separator = '.', $parentPath = '', $stopIfMatchedBy = '') - { - /* - * No elements? - * Nothing to do - */ + public static function getLastElementsPaths( + array $array, + string $separator = '.', + string $parentPath = '', + array $stopIfMatchedBy = [] + ): ?array { + // No elements? Nothing to do if (empty($array)) { return null; } - if (!empty($stopIfMatchedBy)) { - $stopIfMatchedBy = self::makeArray($stopIfMatchedBy); - } - - $paths = []; + $result = []; foreach ($array as $key => $value) { $path = $key; @@ -910,6 +658,7 @@ class Arrays if (preg_match($pattern, $key) || preg_match($pattern, $path)) { $stopRecursion = true; + break; } } @@ -917,109 +666,174 @@ class Arrays /* * The value is passed to the returned array if: + * - the process is stopped, recursive is not used + * or * - it's not an array * or - * - the process is stopped, recursive is not used + * - it's an array, but empty */ - if (!$valueIsArray || ($valueIsArray && empty($value)) || $stopRecursion) { - $paths[$path] = $value; + if ($stopRecursion || !$valueIsArray || self::isEmptyArray($value)) { + $result[$path] = $value; + continue; } - /* - * Let's iterate through the next level, using recursive - */ - if ($valueIsArray) { - $recursivePaths = self::getLastElementsPaths($value, $separator, $path, $stopIfMatchedBy); - $paths += $recursivePaths; + // Let's iterate through the next level, using recursive + $recursivePaths = self::getLastElementsPaths($value, $separator, $path, $stopIfMatchedBy); + + if (null !== $recursivePaths) { + $result += $recursivePaths; } } - return $paths; + return $result; } /** - * Makes and returns an array for given variable + * Returns last key of array * - * @param mixed $variable Variable that should be an array - * @return array + * @param array $array The array to get the last key of + * @return mixed */ - public static function makeArray($variable) + public static function getLastKey(array $array) { - if (is_array($variable)) { - return $variable; - } - - return [$variable]; - } - - /** - * Returns information if keys / indexes of given array are matched by given pattern - * - * @param array $array The array to check - * @param string $pattern The pattern which keys / indexes should match, e.g. "\d+" - * @param bool $firstLevelOnly (optional) If is set to true, all keys / indexes are checked. Otherwise - from the - * first level only. - * @return bool - */ - public static function areAllKeysMatchedByPattern(array $array, $pattern, $firstLevelOnly = false) - { - /* - * No elements? - * Nothing to do - */ + // No elements? Nothing to do if (empty($array)) { - return false; + return null; } - /* - * I suppose that all are keys are matched - * and then I have to look for keys that don't matches - */ - $areMatched = true; + $keys = array_keys($array); - /* - * Building the pattern - */ - $rawPattern = $pattern; - $pattern = sprintf('|%s|', $rawPattern); - - foreach ($array as $key => $value) { - /* - * Not matched? So I have to stop the iteration, because one not matched key - * means that not all keys are matched by given pattern - */ - if (!preg_match($pattern, $key)) { - $areMatched = false; - break; - } - - /* - * The not matching key was not found and the value is an array? - * Let's begin recursive looking for result - */ - if ($areMatched && is_array($value) && !$firstLevelOnly) { - $areMatched = self::areAllKeysMatchedByPattern($value, $rawPattern, $firstLevelOnly); - } - } - - return $areMatched; + return end($keys); } /** - * Returns information if keys / indexes of given array are integers, in other words if the array contains - * zero-based keys / indexes + * Returns the last row of array * - * @param array $array The array to check - * @param bool $firstLevelOnly (optional) If is set to true, all keys / indexes are checked. Otherwise - from the - * first level only (default behaviour). - * @return bool + * @param array $array The array to get the last row of + * @return mixed */ - public static function areAllKeysIntegers(array $array, $firstLevelOnly = false) + public static function getLastRow(array $array): ?array { - $pattern = '\d+'; + // No elements? Nothing to do + if (empty($array)) { + return null; + } - return self::areAllKeysMatchedByPattern($array, $pattern, $firstLevelOnly); + $result = []; + $last = end($array); + + if (is_array($last)) { + // We've got an array, so looking for the last row of array will be done recursively + $result = static::getLastRow($last); + + /* + * The last row is not an array or it's an empty array? + * Let's use the previous candidate + */ + if (!is_array($result) || static::isEmptyArray($result)) { + $result = $last; + } + } + + return $result; + } + + /** + * Returns next element of given array related to given element + * + * @param array $array The array with elements + * @param mixed $element Element for who next element should be returned + * @return null|mixed + */ + public static function getNextElement(array $array, $element) + { + return self::getNeighbour($array, $element); + } + + /** + * Returns count / amount of elements that are not array + * + * @param array $array The array to count + * @return null|int + */ + public static function getNonArrayElementsCount(array $array): ?int + { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + $count = 0; + + foreach ($array as $value) { + if (is_array($value)) { + $count += (int) self::getNonArrayElementsCount($value); + + continue; + } + + ++$count; + } + + return $count; + } + + /** + * Returns non-empty values, e.g. without "" (empty string), null or [] + * + * @param array $values The values to filter + * @return null|array + */ + public static function getNonEmptyValues(array $values): ?array + { + // No values? Nothing to do + if (empty($values)) { + return null; + } + + return array_filter($values, static function ($value): bool { + $nonEmptyScalar = is_scalar($value) && '' !== $value; + $nonEmptyArray = self::isNotEmptyArray($value); + + return $nonEmptyScalar || $nonEmptyArray || is_object($value); + }); + } + + /** + * Returns non-empty values concatenated by given separator + * + * @param array $values The values to filter + * @param string $separator (optional) Separator used to implode the values. Default: ", ". + * @return null|string + */ + public static function getNonEmptyValuesAsString(array $values, string $separator = ', '): ?string + { + // No elements? Nothing to do + if (empty($values)) { + return null; + } + + $nonEmpty = self::getNonEmptyValues($values); + + // No values? Nothing to do + if (empty($nonEmpty)) { + return ''; + } + + return implode($separator, $nonEmpty); + } + + /** + * Returns previous element of given array related to given element + * + * @param array $array The array with elements + * @param mixed $element Element for who previous element should be returned + * @return null|mixed + */ + public static function getPreviousElement(array $array, $element) + { + return self::getNeighbour($array, $element, false); } /** @@ -1053,10 +867,7 @@ class Arrays */ public static function getValueByKeysPath(array $array, array $keys) { - /* - * No elements? - * Nothing to do - */ + // No elements? Nothing to do if (empty($array)) { return null; } @@ -1079,6 +890,170 @@ class Arrays return $value; } + /** + * Returns smartly imploded string + * + * Separators located at the beginning or end of elements are removed. + * It's required to avoid problems with duplicated separator, e.g. "first//second/third", where separator is a + * "/" string. + * + * @param array $array The array with elements to implode + * @param string $separator Separator used to stick together elements of given array + * @return null|string + */ + public static function implodeSmart(array $array, $separator) + { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + foreach ($array as &$element) { + if (is_array($element)) { + $element = self::implodeSmart($element, $separator); + } + + if (Regex::startsWith($element, $separator)) { + $element = substr($element, 1); + } + + if (Regex::endsWith($element, $separator)) { + $element = substr($element, 0, -1); + } + } + + return implode($separator, $array); + } + + /** + * Returns an array with incremented indexes / keys + * + * @param array $array The array which indexes / keys should be incremented + * @param null|int $startIndex (optional) Index from which incrementation should be started. If not provided, + * the first index / key will be used. + * @param int $incrementStep (optional) Value used for incrementation. The step of incrementation. + * @return null|array + */ + public static function incrementIndexes(array $array, ?int $startIndex = null, int $incrementStep = 1): ?array + { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + $valuesToIncrement = []; + + /* + * Start index not provided? + * Let's look for the first index / key of given array + */ + if (null === $startIndex) { + $startIndex = self::getFirstKey($array); + } + + /* + * Is the start index a numeric value? + * Other indexes / keys cannot be incremented + */ + if (is_numeric($startIndex)) { + /* + * 1st step: + * Get values which indexes should be incremented and remove those values from given array + */ + foreach ($array as $index => $value) { + if ($index < $startIndex) { + continue; + } + + $valuesToIncrement[$index] = $value; + unset($array[$index]); + } + + /* + * 2nd step: + * Increment indexes of gathered values + */ + if (!empty($valuesToIncrement)) { + foreach ($valuesToIncrement as $oldIndex => $value) { + $newIndex = (int) $oldIndex + $incrementStep; + $array[$newIndex] = $value; + } + } + } + + return $array; + } + + /** + * Returns information if given value is an array and is empty + * + * @param mixed $value The value to verify + * @return bool + */ + public static function isEmptyArray($value): bool + { + return is_array($value) && empty($value); + } + + /** + * Returns information if given element is the first one + * + * @param array $array The array to get the first element of + * @param mixed $element The element to check / verify + * @param bool $firstLevelOnly (optional) If is set to true, first element is returned. Otherwise - totally + * first element is returned (first of the First array). + * @return bool + */ + public static function isFirstElement(array $array, $element, bool $firstLevelOnly = true): bool + { + $firstElement = static::getFirstElement($array, $firstLevelOnly); + + return $element === $firstElement; + } + + /** + * Returns information if given element is the last one + * + * @param array $array The array to get the last element of + * @param mixed $element The element to check / verify + * @param bool $firstLevelOnly (optional) If is set to true, last element is returned. Otherwise - totally + * last element is returned (last of the latest array). + * @return bool + */ + public static function isLastElement(array $array, $element, bool $firstLevelOnly = true): bool + { + $lastElement = static::getLastElement($array, $firstLevelOnly); + + return $element === $lastElement; + } + + /** + * Returns information if given array is a multi dimensional array + * + * @param array $array The array to verify + * @return null|bool + */ + public static function isMultiDimensional(array $array): ?bool + { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + return count($array) !== count($array, COUNT_RECURSIVE); + } + + /** + * Returns information if given value is non-empty array + * + * @param mixed $value The value to verify + * @return bool + */ + public static function isNotEmptyArray($value): bool + { + return is_array($value) && !empty($value); + } + /** * Returns information if given path of keys are set is given array. * The keys should be delivered in the same order as used by source array. @@ -1107,10 +1082,7 @@ class Arrays */ public static function issetRecursive(array $array, array $keys) { - /* - * No elements? - * Nothing to do - */ + // No elements? Nothing to do if (empty($array)) { return false; } @@ -1136,42 +1108,264 @@ class Arrays } /** - * Returns all values of given key. - * It may be useful when you want to retrieve all values of one column. + * Applies ksort() function recursively in the given array * - * @param array $array The array which should contain values of the key - * @param string $key The key - * @return array|null + * @param array $array The array to sort + * @param int $sortFlags (optional) Options of ksort() function + * @return null|array */ - public static function getAllValuesOfKey(array $array, $key) + public static function ksortRecursive(array &$array, $sortFlags = SORT_REGULAR) { - /* - * No elements? - * Nothing to do - */ + // No elements? Nothing to do if (empty($array)) { return null; } - $values = []; - - foreach ($array as $index => $value) { - if ($index === $key) { - $values[] = $value; - continue; - } + $effect = &$array; + ksort($effect, $sortFlags); + foreach ($effect as &$value) { if (is_array($value)) { - $recursiveValues = self::getAllValuesOfKey($value, $key); - - if (!empty($recursiveValues)) { - $merged = array_merge($values, $recursiveValues); - $values = $merged; - } + ksort($value, $sortFlags); } } - return $values; + return $effect; + } + + /** + * Makes and returns an array for given variable + * + * @param mixed $variable Variable that should be an array + * @return array + */ + public static function makeArray($variable): array + { + if (is_array($variable)) { + return $variable; + } + + return [$variable]; + } + + /** + * Quotes (adds quotes) to elements that are strings and returns new array (with quoted elements) + * + * @param array $array The array to check for string values + * @return null|array + */ + public static function quoteStrings(array $array): ?array + { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + $result = []; + + foreach ($array as $index => $value) { + if (is_array($value)) { + $value = self::quoteStrings($value); + } elseif (is_string($value) && !Regex::isQuoted($value)) { + $value = '\''.$value.'\''; + } + + $result[$index] = $value; + } + + return $result; + } + + /** + * Removes element / item of given array + * + * @param array $array The array that contains element / item which should be removed + * @param mixed $item The element / item which should be removed + * @return array|bool + */ + public static function removeElement(array $array, $item) + { + // No elements or the element does not exist? Nothing to do + if (empty($array) || !in_array($item, $array, true)) { + return false; + } + + // Flip the array to make it looks like: value => key + $arrayFlipped = array_flip($array); + + // Take the key of element / item that should be removed + $key = $arrayFlipped[$item]; + + // ...and remove the element / item + unset($array[$key]); + + return $array; + } + + /** + * Removes items from given array starting at given element (before or after the element) + * + * @param array $array The array which contains items to remove + * @param mixed $needle The element which is start point of deletion + * @param bool $before (optional) If is set to true, all elements before given needle are removed. Otherwise - all + * after needle. + */ + public static function removeElements(array &$array, $needle, $before = true): void + { + if (!empty($array)) { + if (!$before) { + $array = array_reverse($array, true); + } + + foreach ($array as $key => &$value) { + $remove = false; + $isArray = is_array($value); + + if ($isArray) { + self::removeElements($value, $needle, $before); + + if (empty($value)) { + $remove = true; + } + } elseif ($value === $needle) { + break; + } else { + $remove = true; + } + + if ($remove) { + unset($array[$key]); + } + } + + if (!$before) { + $array = array_reverse($array, true); + } + } + } + + /** + * Removes marginal element (first or last) from given array + * + * @param array $array The array which should be shortened + * @param bool $last (optional) If is set to true, last element is removed (default behaviour). Otherwise - first. + * @return null|array + */ + public static function removeMarginalElement(array $array, bool $last = true): ?array + { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + $key = self::getFirstKey($array); + + if ($last) { + $key = self::getLastKey($array); + } + + unset($array[$key]); + + return $array; + } + + /** + * Replaces array keys that match given pattern with new key name + * + * @param array $array Array which keys should be replaced + * @param string $oldKeyPattern Regular expression of the old key + * @param string $newKey Name of the new key + * @return null|array + */ + public static function replaceKeys(array $array, string $oldKeyPattern, string $newKey): ?array + { + if (empty($array)) { + return null; + } + + $effect = []; + + foreach ($array as $key => $value) { + if (preg_match($oldKeyPattern, $key)) { + $key = $newKey; + } + + if (is_array($value)) { + $value = self::replaceKeys($value, $oldKeyPattern, $newKey); + } + + $effect[$key] = $value; + } + + return $effect; + } + + /** + * Sets keys as values and values as keys in given array. + * Replaces keys with values. + * + * @param array $array The array to change values with keys + * @param bool $ignoreDuplicatedValues (optional) If is set to true, duplicated values are ignored. This means that + * when there is more than 1 value and that values become key, only the last + * value will be used with it's key, because other will be overridden. + * Otherwise - values are preserved and keys assigned to that values are + * returned as an array. + * @return null|array + * + * Example of $ignoreDuplicatedValues = false: + * - provided array + * $array = [ + * 'lorem' => 100, // <-- Duplicated value + * 'ipsum' => 200, + * 'dolor' => 100, // <-- Duplicated value + * ]; + * + * - result + * $replaced = [ + * 100 => [ + * 'lorem', // <-- Key of duplicated value + * 'dolor', // <-- Key of duplicated value + * ], + * 200 => 'ipsum', + * ]; + */ + public static function setKeysAsValues(array $array, $ignoreDuplicatedValues = true) + { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + $replaced = []; + + foreach ($array as $key => $value) { + /* + * The value it's an array? + * Let's replace keys with values in this array first + */ + if (is_array($value)) { + $replaced[$key] = self::setKeysAsValues($value, $ignoreDuplicatedValues); + + continue; + } + + // Duplicated values shouldn't be ignored and processed value is used as key already? + // Let's use an array and that will contain all values (to avoid ignoring / overriding duplicated values) + if (!$ignoreDuplicatedValues && isset($replaced[$value])) { + $existing = self::makeArray($replaced[$value]); + + $replaced[$value] = array_merge($existing, [ + $key, + ]); + + continue; + } + + // Standard behaviour + $replaced[$value] = $key; + } + + return $replaced; } /** @@ -1184,14 +1378,11 @@ class Arrays * @param string $keyName (optional) Name of key which will contain the position value * @param int $startPosition (optional) Default, start value of the position for main / given array, not the * children - * @return array|null + * @return null|array */ public static function setPositions(array $array, $keyName = self::POSITION_KEY_NAME, $startPosition = null) { - /* - * No elements? - * Nothing to do - */ + // No elements? Nothing to do if (empty($array)) { return null; } @@ -1212,40 +1403,6 @@ class Arrays return $array; } - /** - * Trims string values of given array and returns the new array - * - * @param array $array The array which values should be trimmed - * @return array - */ - public static function trimRecursive(array $array) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return []; - } - - $result = []; - - foreach ($array as $key => $value) { - if (is_array($value)) { - $result[$key] = self::trimRecursive($value); - continue; - } - - if (is_string($value)) { - $value = trim($value); - } - - $result[$key] = $value; - } - - return $result; - } - /** * Sorts an array by keys given in second array as values. * Keys which are not in array with order are pushed after sorted elements. @@ -1287,14 +1444,11 @@ class Arrays * * @param array $array An array to sort * @param array $keysOrder An array with keys of the 1st argument in proper / required order - * @return array|null + * @return null|array */ public static function sortByCustomKeysOrder(array $array, array $keysOrder) { - /* - * No elements? - * Nothing to do - */ + // No elements? Nothing to do if (empty($array)) { return null; } @@ -1328,261 +1482,45 @@ class Arrays } /** - * Returns smartly imploded string + * Converts given string with special separators to array * - * Separators located at the beginning or end of elements are removed. - * It's required to avoid problems with duplicated separator, e.g. "first//second/third", where separator is a - * "/" string. + * Example: + * ~ string: + * "light:jasny|dark:ciemny" * - * @param array $array The array with elements to implode - * @param string $separator Separator used to stick together elements of given array - * @return string|null + * ~ array as a result: + * [ + * 'light' => 'jasny', + * 'dark' => 'ciemny', + * ] + * + * @param string $string The string to be converted + * @param string $separator (optional) Separator used between name-value pairs in the string. + * Default: "|". + * @param string $valuesKeysSeparator (optional) Separator used between name and value in the string. Default: ":". + * @return null|array */ - public static function implodeSmart(array $array, $separator) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { + public static function string2array( + string $string, + string $separator = '|', + string $valuesKeysSeparator = ':' + ): ?array { + // Empty string? Nothing to do + if (empty($string)) { return null; } - foreach ($array as &$element) { - if (is_array($element)) { - $element = self::implodeSmart($element, $separator); - } + $array = []; + $exploded = explode($separator, $string); - if (Regex::startsWith($element, $separator)) { - $element = substr($element, 1); - } + foreach ($exploded as $item) { + $exploded2 = explode($valuesKeysSeparator, $item); - if (Regex::endsWith($element, $separator)) { - $element = substr($element, 0, -1); - } - } + if (2 === count($exploded2)) { + $key = trim($exploded2[0]); + $value = trim($exploded2[1]); - return implode($separator, $array); - } - - /** - * Returns information if given array is empty, iow. information if all elements of given array are empty - * - * @param array $array The array to verify - * @param bool $strictNull (optional) If is set to true elements are verified if they are null. Otherwise - only - * if they are empty (e.g. null, '', 0, array()). - * @return bool - */ - public static function areAllValuesEmpty(array $array, $strictNull = false) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return false; - } - - foreach ($array as $element) { - /* - * If elements are verified if they are exactly null and the element is: - * - not an array - * - not null - * or elements are NOT verified if they are exactly null and the element is: - * - not empty (e.g. null, '', 0, array()) - * - * If one of the above is true, not all elements of given array are empty - */ - if ((!is_array($element) && $strictNull && null !== $element) || !empty($element)) { - return false; - } - } - - return true; - } - - /** - * Returns an array containing all the entries from 1st array that are not present in 2nd array. - * An item from 1st array is the same as in 2nd array if both, keys and values, are the same. - * - * Example of difference: - * $array1 = [ - * 1 => 'Lorem', - * 2 => 'ipsum, - * ]; - * - * $array2 = [ - * 1 => 'Lorem', - * 5 => 'ipsum, // <-- The same values, but different key. Here we got 5, in 1st array - 2. - * ]; - * - * @param array $array1 The 1st array to verify - * @param array $array2 The 2nd array to verify - * @param bool $valuesOnly (optional) If is set to true, compares values only. Otherwise - keys and values - * (default behaviour). - * @return array - */ - public static function arrayDiffRecursive(array $array1, array $array2, $valuesOnly = false) - { - $effect = []; - - /* - * Values should be compared only and both arrays are one-dimensional? - * Let's find difference by using simple function - */ - if ($valuesOnly && 1 === self::getDimensionsCount($array1) && 1 === self::getDimensionsCount($array2)) { - return array_diff($array1, $array2); - } - - foreach ($array1 as $key => $value) { - $array2HasKey = array_key_exists($key, $array2); - - /* - * Values should be compared only? - */ - if ($valuesOnly) { - $difference = null; - - if (is_array($value)) { - if ($array2HasKey && is_array($array2[$key])) { - $difference = self::arrayDiffRecursive($value, $array2[$key], $valuesOnly); - } - } elseif (!$array2HasKey || ($array2HasKey && $value !== $array2[$key])) { - /* - * We are here, because: - * a) 2nd array hasn't key from 1st array - * OR - * b) key exists in both, 1st and 2nd array, but values are different - */ - $difference = $value; - } - - if (null !== $difference) { - $effect[] = $difference; - } - - /* - * The key exists in 2nd array? - */ - } elseif ($array2HasKey) { - /* - * The value it's an array (it's a nested array)? - */ - if (is_array($value)) { - $diff = []; - - if (is_array($array2[$key])) { - /* - * Let's verify the nested array - */ - $diff = self::arrayDiffRecursive($value, $array2[$key], $valuesOnly); - } - - if (empty($diff)) { - continue; - } - - $effect[$key] = $diff; - } elseif ($value !== $array2[$key]) { - /* - * Value is different than in 2nd array? - * OKay, I've got difference - */ - $effect[$key] = $value; - } - } else { - /* - * OKay, I've got difference - */ - $effect[$key] = $value; - } - } - - return $effect; - } - - /** - * Returns an index / key of given element in given array - * - * @param array $array The array to verify - * @param mixed $element The element who index / key is needed - * @return bool|null|mixed - */ - public static function getIndexOf(array $array, $element) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return false; - } - - foreach ($array as $index => $value) { - if ($value === $element) { - return $index; - } - } - - return null; - } - - /** - * Returns an array with incremented indexes / keys - * - * @param array $array The array which indexes / keys should be incremented - * @param int|null $startIndex (optional) Index from which incrementation should be started. If not provided, - * the first index / key will be used. - * @param int $incrementStep (optional) Value used for incrementation. The step of incrementation. - * @return array|null - */ - public static function incrementIndexes(array $array, $startIndex = null, $incrementStep = 1) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return null; - } - - $valuesToIncrement = []; - - /* - * Start index not provided? - * Let's look for the first index / key of given array - */ - if (null === $startIndex) { - $startIndex = self::getFirstKey($array); - } - - /* - * Is the start index a numeric value? - * Other indexes / keys cannot be incremented - */ - if (is_numeric($startIndex)) { - /* - * 1st step: - * Get values which indexes should be incremented and remove those values from given array - */ - foreach ($array as $index => $value) { - if ($index < $startIndex) { - continue; - } - - $valuesToIncrement[$index] = $value; - unset($array[$index]); - } - - /* - * 2nd step: - * Increment indexes of gathered values - */ - if (!empty($valuesToIncrement)) { - foreach ($valuesToIncrement as $oldIndex => $value) { - $newIndex = $oldIndex + $incrementStep; - $array[$newIndex] = $value; - } + $array[$key] = $value; } } @@ -1590,135 +1528,160 @@ class Arrays } /** - * Returns next element of given array related to given element + * Trims string values of given array and returns the new array * - * @param array $array The array with elements - * @param mixed $element Element for who next element should be returned - * @return null|mixed + * @param array $array The array which values should be trimmed + * @return array */ - public static function getNextElement(array $array, $element) + public static function trimRecursive(array $array) { - return self::getNeighbour($array, $element); + // No elements? Nothing to do + if (empty($array)) { + return []; + } + + $result = []; + + foreach ($array as $key => $value) { + if (is_array($value)) { + $result[$key] = self::trimRecursive($value); + + continue; + } + + if (is_string($value)) { + $value = trim($value); + } + + $result[$key] = $value; + } + + return $result; } /** - * Returns previous element of given array related to given element + * Converts given array's rows to csv string * - * @param array $array The array with elements - * @param mixed $element Element for who previous element should be returned - * @return null|mixed + * @param array $array Data to be converted. It have to be an array that represents database table. + * @param string $separator (optional) Separator used between values. Default: ",". + * @return null|string */ - public static function getPreviousElement(array $array, $element) + public static function values2csv(array $array, string $separator = ','): ?string { - return self::getNeighbour($array, $element, false); - } - - /** - * Returns information if given array is a multi dimensional array - * - * @param array $array The array to verify - * @return bool|null - */ - public static function isMultiDimensional(array $array) - { - /* - * No elements? - * Nothing to do - */ + // No elements? Nothing to do if (empty($array)) { return null; } - return count($array) !== count($array, COUNT_RECURSIVE); - } + $rows = []; + $lineSeparator = "\n"; - /** - * Returns count of dimensions, maximum nesting level actually, in given array - * - * @param array $array The array to verify - * @return int - */ - public static function getDimensionsCount(array $array) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return 0; - } + foreach ($array as $row) { + /* + * I have to use html_entity_decode() function here, because some string values can contain + * entities with semicolon and this can destroy the CSV column order. + */ - $dimensionsCount = 1; - - foreach ($array as $value) { - if (is_array($value)) { - /* - * I have to increment returned value, because that means we've got 1 level more (if the value is an - * array) - */ - $count = self::getDimensionsCount($value) + 1; - - if ($count > $dimensionsCount) { - $dimensionsCount = $count; + if (is_array($row) && !empty($row)) { + foreach ($row as $key => $value) { + $row[$key] = html_entity_decode($value); } + + $rows[] = implode($separator, $row); } } - return $dimensionsCount; - } - - /** - * Returns non-empty values, e.g. without "" (empty string), null or [] - * - * @param array $values The values to filter - * @return array|null - */ - public static function getNonEmptyValues(array $values) - { - /* - * No values? - * Nothing to do - */ - if (empty($values)) { - return null; - } - - return array_filter($values, function ($value) { - $nonEmptyScalar = is_scalar($value) && '' !== $value; - $nonEmptyArray = is_array($value) && !empty($value); - - return $nonEmptyScalar || $nonEmptyArray || is_object($value); - }); - } - - /** - * Returns non-empty values concatenated by given separator - * - * @param array $values The values to filter - * @param string $separator (optional) Separator used to implode the values. Default: ", ". - * @return string|null - */ - public static function getNonEmptyValuesAsString(array $values, $separator = ', ') - { - /* - * No elements? - * Nothing to do - */ - if (empty($values)) { - return null; - } - - $nonEmpty = self::getNonEmptyValues($values); - - /* - * No values? - * Nothing to do - */ - if (empty($nonEmpty)) { + if (empty($rows)) { return ''; } - return implode($separator, $nonEmpty); + return implode($lineSeparator, $rows); + } + + /** + * Converts given array's column to string. + * Recursive call is made for multi-dimensional arrays. + * + * @param array $array Data to be converted + * @param int|string $arrayColumnKey (optional) Column name. Default: "". + * @param string $separator (optional) Separator used between values. Default: ",". + * @return null|string + */ + public static function values2string(array $array, $arrayColumnKey = '', $separator = ',') + { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + $values = []; + + foreach ($array as $key => $value) { + $appendMe = null; + + if (is_array($value)) { + $appendMe = self::values2string($value, $arrayColumnKey, $separator); + } elseif (empty($arrayColumnKey)) { + $appendMe = $value; + } elseif ($key === $arrayColumnKey) { + $appendMe = $array[$arrayColumnKey]; + } + + /* + * Part to append is unknown? + * Let's go to next part + */ + if (null === $appendMe) { + continue; + } + + $values[] = $appendMe; + } + + // No values found? Nothing to do + if (empty($values)) { + return null; + } + + return implode($separator, $values); + } + + /** + * Converts given array to string with keys, e.g. abc=1&def=2 or abc="1" def="2" + * + * @param array $array Data to be converted + * @param string $separator (optional) Separator used between name-value pairs. Default: ",". + * @param string $valuesKeysSeparator (optional) Separator used between name and value. Default: "=". + * @param string $valuesWrapper (optional) Wrapper used to wrap values, e.g. double-quote: key="value". + * Default: "". + * @return null|string + */ + public static function valuesKeys2string( + array $array, + $separator = ',', + $valuesKeysSeparator = '=', + $valuesWrapper = '' + ) { + // No elements? Nothing to do + if (empty($array)) { + return null; + } + + $result = ''; + + foreach ($array as $key => $value) { + if (!empty($result)) { + $result .= $separator; + } + + if (!empty($valuesWrapper)) { + $value = sprintf('%s%s%s', $valuesWrapper, $value, $valuesWrapper); + } + + $result .= $key.$valuesKeysSeparator.$value; + } + + return $result; } /** @@ -1727,14 +1690,11 @@ class Arrays * @param array $array The array with elements * @param mixed $element Element for who next element should be returned * @param bool $next (optional) If is set to true, returns next neighbour. Otherwise - previous. - * @return mixed|null + * @return null|mixed */ - private static function getNeighbour(array $array, $element, $next = true) + private static function getNeighbour(array $array, $element, bool $next = true) { - /* - * No elements? - * Nothing to do - */ + // No elements? Nothing to do if (empty($array)) { return null; } @@ -1753,7 +1713,7 @@ class Arrays * * Nothing to do */ - if ($noPrevious || $noNext || empty($array) || !in_array($element, $array, true)) { + if ($noPrevious || $noNext || !in_array($element, $array, true)) { return null; } @@ -1770,9 +1730,7 @@ class Arrays return null; } - /* - * Looking for key of the neighbour (next or previous element) - */ + // Looking for key of the neighbour (next or previous element) if ($next) { ++$indexOfKey; } else { diff --git a/src/Utilities/Bootstrap4CssSelector.php b/src/Utilities/Bootstrap4CssSelector.php index 6933274..35d12d1 100644 --- a/src/Utilities/Bootstrap4CssSelector.php +++ b/src/Utilities/Bootstrap4CssSelector.php @@ -46,6 +46,23 @@ class Bootstrap4CssSelector return sprintf('%s %s', $labelSelector, $errorContainerSelector); } + /** + * Returns selector of field's group + * + * @param string $formName Name of form (value of the "name" attribute) + * @return string + */ + public static function getFieldGroupSelector($formName) + { + $formSelector = CssSelector::getFormByNameSelector($formName); + + if (empty($formSelector)) { + return ''; + } + + return sprintf('%s .form-group', $formSelector); + } + /** * Returns selector of radio-button's validation error * @@ -65,21 +82,4 @@ class Bootstrap4CssSelector return sprintf('%s legend.col-form-label %s', $fieldSetSelector, $errorContainerSelector); } - - /** - * Returns selector of field's group - * - * @param string $formName Name of form (value of the "name" attribute) - * @return string - */ - public static function getFieldGroupSelector($formName) - { - $formSelector = CssSelector::getFormByNameSelector($formName); - - if (empty($formSelector)) { - return ''; - } - - return sprintf('%s .form-group', $formSelector); - } } diff --git a/src/Utilities/Bundle.php b/src/Utilities/Bundle.php index 105d8d9..691787c 100644 --- a/src/Utilities/Bundle.php +++ b/src/Utilities/Bundle.php @@ -21,62 +21,50 @@ class Bundle /** * Returns path of given bundle to view / template with given extension * - * @param string $viewPath Path of the view / template, e.g. "MyDirectory/my-template" + * @param string $viewPath Path of the view / template, e.g. "MyDirectory/my-template". Extension is not required. * @param string $bundleName Full name of the bundle, e.g. "MyExtraBundle" * @param string $extension (optional) Extension of the view / template (default: "html.twig") + * @return null|string * @throws IncorrectBundleNameException - * @return string|null */ - public static function getBundleViewPath($viewPath, $bundleName, $extension = 'html.twig') - { - /* - * Unknown path, extension of the view / template or name of the bundle? - * Nothing to do - */ + public static function getBundleViewPath( + string $viewPath, + string $bundleName, + string $extension = 'html.twig' + ): ?string { + // Nothing to do, because at least one unknown argument provided if (empty($viewPath) || empty($bundleName) || empty($extension)) { return null; } - /* - * Given name of bundle is invalid? - */ + // Oops, given name of bundle is invalid if (!Regex::isValidBundleName($bundleName)) { throw IncorrectBundleNameException::create($bundleName); } - /* - * Path of the view / template doesn't end with given extension? - */ + // Make sure that path of the view / template ends with given extension if (!Regex::endsWith($viewPath, $extension)) { $viewPath = sprintf('%s.%s', $viewPath, $extension); } - /* - * Prepare short name of bundle and path of view / template with "/" (instead of ":") - */ - $shortBundleName = static::getShortBundleName($bundleName); - $viewPath = str_replace(':', '/', $viewPath); + // Prepare short name of bundle and path of view / template with "/" (instead of ":") + $shortName = static::getShortBundleName($bundleName); + $path = str_replace(':', '/', $viewPath); - return sprintf('@%s/%s', $shortBundleName, $viewPath); + return sprintf('@%s/%s', $shortName, $path); } /** * Returns short name of bundle (without "Bundle") * * @param string $fullBundleName Full name of the bundle, e.g. "MyExtraBundle" + * @return null|string * @throws IncorrectBundleNameException - * @return string|null */ - public static function getShortBundleName($fullBundleName) + public static function getShortBundleName(string $fullBundleName): ?string { - /* - * Given name of bundle is invalid? - */ + // Oops, given name of bundle is invalid if (!Regex::isValidBundleName($fullBundleName)) { - if (!is_string($fullBundleName)) { - $fullBundleName = gettype($fullBundleName); - } - throw new IncorrectBundleNameException($fullBundleName); } diff --git a/src/Utilities/Composer.php b/src/Utilities/Composer.php index 28e91c7..24ee2ce 100644 --- a/src/Utilities/Composer.php +++ b/src/Utilities/Composer.php @@ -8,8 +8,6 @@ namespace Meritoo\Common\Utilities; -use stdClass; - /** * Useful Composer-related methods (only static functions) * @@ -23,23 +21,18 @@ class Composer * * @var string */ - const FILE_NAME_MAIN = 'composer.json'; + public const FILE_NAME_MAIN = 'composer.json'; /** * Returns value from composer.json file * * @param string $composerJsonPath Path of composer.json file * @param string $nodeName Name of node who value should be returned - * @return string|null + * @return null|string */ - public static function getValue($composerJsonPath, $nodeName) + public static function getValue(string $composerJsonPath, string $nodeName): ?string { - $composerJsonString = is_string($composerJsonPath); - $composerJsonReadable = false; - - if ($composerJsonString) { - $composerJsonReadable = is_readable($composerJsonPath); - } + $composerJsonReadable = is_readable($composerJsonPath); /* * Provided path or name of node are invalid? @@ -48,12 +41,12 @@ class Composer * * Nothing to do */ - if (!$composerJsonString || !is_string($nodeName) || !$composerJsonReadable || empty($nodeName)) { + if (!$composerJsonReadable || empty($nodeName)) { return null; } $content = file_get_contents($composerJsonPath); - $data = json_decode($content); + $data = json_decode($content, false); /* * Unknown data from the composer.json file or there is no node with given name? @@ -63,7 +56,6 @@ class Composer return null; } - /* @var stdClass $data */ return $data->{$nodeName}; } } diff --git a/src/Utilities/Date.php b/src/Utilities/Date.php index b6f33c4..0f501b0 100644 --- a/src/Utilities/Date.php +++ b/src/Utilities/Date.php @@ -29,7 +29,7 @@ class Date * * @var string */ - const DATE_DIFFERENCE_UNIT_DAYS = 'days'; + public const DATE_DIFFERENCE_UNIT_DAYS = 'days'; /** * The 'hours' unit of date difference. @@ -37,7 +37,7 @@ class Date * * @var string */ - const DATE_DIFFERENCE_UNIT_HOURS = 'hours'; + public const DATE_DIFFERENCE_UNIT_HOURS = 'hours'; /** * The 'minutes' unit of date difference. @@ -45,7 +45,7 @@ class Date * * @var string */ - const DATE_DIFFERENCE_UNIT_MINUTES = 'minutes'; + public const DATE_DIFFERENCE_UNIT_MINUTES = 'minutes'; /** * The 'months' unit of date difference. @@ -53,7 +53,7 @@ class Date * * @var string */ - const DATE_DIFFERENCE_UNIT_MONTHS = 'months'; + public const DATE_DIFFERENCE_UNIT_MONTHS = 'months'; /** * The 'years' unit of date difference. @@ -61,131 +61,16 @@ class Date * * @var string */ - const DATE_DIFFERENCE_UNIT_YEARS = 'years'; - - /** - * Returns date's period (that contains start and end date) for given period - * - * @param int $period The period, type of period. One of DatePeriod class constants, e.g. DatePeriod::LAST_WEEK. - * @throws Exception - * @return null|DatePeriod - */ - public static function getDatesForPeriod($period) - { - /* - * Type of period is incorrect? - * Nothing to do - */ - if (!(new DatePeriod())->isCorrectType($period)) { - return null; - } - - $dateStart = null; - $dateEnd = null; - - switch ($period) { - case DatePeriod::LAST_WEEK: - $thisWeekStart = new DateTime('this week'); - - $dateStart = clone $thisWeekStart; - $dateEnd = clone $thisWeekStart; - - $dateStart->sub(new DateInterval('P7D')); - $dateEnd->sub(new DateInterval('P1D')); - - break; - case DatePeriod::THIS_WEEK: - $dateStart = new DateTime('this week'); - - $dateEnd = clone $dateStart; - $dateEnd->add(new DateInterval('P6D')); - - break; - case DatePeriod::NEXT_WEEK: - $dateStart = new DateTime('this week'); - $dateStart->add(new DateInterval('P7D')); - - $dateEnd = clone $dateStart; - $dateEnd->add(new DateInterval('P6D')); - - break; - case DatePeriod::LAST_MONTH: - $dateStart = new DateTime('first day of last month'); - $dateEnd = new DateTime('last day of last month'); - - break; - case DatePeriod::THIS_MONTH: - $lastMonth = self::getDatesForPeriod(DatePeriod::LAST_MONTH); - $nextMonth = self::getDatesForPeriod(DatePeriod::NEXT_MONTH); - - if (null !== $lastMonth) { - $dateStart = $lastMonth->getEndDate(); - $dateStart->add(new DateInterval('P1D')); - } - - if (null !== $nextMonth) { - $dateEnd = $nextMonth->getStartDate(); - $dateEnd->sub(new DateInterval('P1D')); - } - - break; - case DatePeriod::NEXT_MONTH: - $dateStart = new DateTime('first day of next month'); - $dateEnd = new DateTime('last day of next month'); - - break; - case DatePeriod::LAST_YEAR: - case DatePeriod::THIS_YEAR: - case DatePeriod::NEXT_YEAR: - $dateStart = new DateTime(); - $dateEnd = new DateTime(); - - $yearPeriod = [ - DatePeriod::LAST_YEAR, - DatePeriod::NEXT_YEAR, - ]; - - if (in_array($period, $yearPeriod, true)) { - $yearDifference = 1; - - if (DatePeriod::LAST_YEAR === $period) { - $yearDifference *= -1; - } - - $modifyString = sprintf('%s year', $yearDifference); - $dateStart->modify($modifyString); - $dateEnd->modify($modifyString); - } - - $year = $dateStart->format('Y'); - $dateStart->setDate($year, 1, 1); - $dateEnd->setDate($year, 12, 31); - - break; - } - - /* - * Start or end date is unknown? - * Nothing to do - */ - if (null === $dateStart || null === $dateEnd) { - return null; - } - - $dateStart->setTime(0, 0); - $dateEnd->setTime(23, 59, 59); - - return new DatePeriod($dateStart, $dateEnd); - } + public const DATE_DIFFERENCE_UNIT_YEARS = 'years'; /** * Generates and returns random time (the hour, minute and second values) * * @param string $format (optional) Format of returned value. A string acceptable by the DateTime::format() * method. - * @return string|null + * @return null|string */ - public static function generateRandomTime($format = 'H:i:s') + public static function generateRandomTime($format = 'H:i:s'): ?string { $dateTime = new DateTime(); @@ -213,9 +98,7 @@ class Date $seconds[] = $i; } - /* - * Prepare random time (hour, minute and second) - */ + // Prepare random time (hour, minute and second) $hour = $hours[array_rand($hours)]; $minute = $minutes[array_rand($minutes)]; $second = $seconds[array_rand($seconds)]; @@ -223,7 +106,7 @@ class Date return $dateTime ->setTime($hour, $minute, $second) ->format($format) - ; + ; } /** @@ -231,113 +114,33 @@ class Date * * @return int */ - public static function getCurrentDayOfWeek() + public static function getCurrentDayOfWeek(): int { $now = new DateTime(); - $year = $now->format('Y'); - $month = $now->format('m'); - $day = $now->format('d'); + $year = (int) $now->format('Y'); + $month = (int) $now->format('m'); + $day = (int) $now->format('d'); return self::getDayOfWeek($year, $month, $day); } - /** - * Returns day of week (number 0 to 6, 0 - sunday, 6 - saturday). - * Based on the Zeller's algorithm (http://pl.wikipedia.org/wiki/Kalendarz_wieczny). - * - * @param int $year The year value - * @param int $month The month value - * @param int $day The day value - * - * @throws UnknownDatePartTypeException - * @return int - */ - public static function getDayOfWeek($year, $month, $day) - { - $year = (int)$year; - $month = (int)$month; - $day = (int)$day; - - /* - * Oops, incorrect year - */ - if ($year <= 0) { - throw UnknownDatePartTypeException::createException(DatePartType::YEAR, $year); - } - - /* - * Oops, incorrect month - */ - if ($month < 1 || $month > 12) { - throw UnknownDatePartTypeException::createException(DatePartType::MONTH, $month); - } - - /* - * Oops, incorrect day - */ - if ($day < 1 || $day > 31) { - throw UnknownDatePartTypeException::createException(DatePartType::DAY, $day); - } - - if ($month < 3) { - $count = 0; - $yearValue = $year - 1; - } else { - $count = 2; - $yearValue = $year; - } - - $firstPart = floor(23 * $month / 9); - $secondPart = floor($yearValue / 4); - $thirdPart = floor($yearValue / 100); - $fourthPart = floor($yearValue / 400); - - return ($firstPart + $day + 4 + $year + $secondPart - $thirdPart + $fourthPart - $count) % 7; - } - /** * Returns based on locale name of current weekday * * @return string */ - public static function getCurrentDayOfWeekName() + public static function getCurrentDayOfWeekName(): string { $now = new DateTime(); - $year = $now->format('Y'); - $month = $now->format('m'); - $day = $now->format('d'); + $year = (int) $now->format('Y'); + $month = (int) $now->format('m'); + $day = (int) $now->format('d'); return self::getDayOfWeekName($year, $month, $day); } - /** - * Returns name of weekday based on locale - * - * @param int $year The year value - * @param int $month The month value - * @param int $day The day value - * @return string - */ - public static function getDayOfWeekName($year, $month, $day) - { - $hour = 0; - $minute = 0; - $second = 0; - - $time = mktime($hour, $minute, $second, $month, $day, $year); - $name = strftime('%A', $time); - - $encoding = mb_detect_encoding($name); - - if (false === $encoding) { - $name = mb_convert_encoding($name, 'UTF-8', 'ISO-8859-2'); - } - - return $name; - } - /** * Returns difference between given dates. * @@ -353,8 +156,8 @@ class Date * If the unit of date difference is null, all units are returned in array (units are keys of the array). * Otherwise - one, integer value is returned. * - * @param string|DateTime $dateStart The start date - * @param string|DateTime $dateEnd The end date + * @param DateTime|string $dateStart The start date + * @param DateTime|string $dateEnd The end date * @param string $differenceUnit (optional) Unit of date difference. One of this class * DATE_DIFFERENCE_UNIT_* constants. If is set to null all units are * returned in the array. @@ -398,26 +201,26 @@ class Date self::DATE_DIFFERENCE_UNIT_MINUTES, ]; - if (null === $differenceUnit || self::DATE_DIFFERENCE_UNIT_YEARS === $differenceUnit) { + $differenceYear = self::DATE_DIFFERENCE_UNIT_YEARS === $differenceUnit; + $differenceMonth = self::DATE_DIFFERENCE_UNIT_MONTHS === $differenceUnit; + $differenceMinutes = self::DATE_DIFFERENCE_UNIT_MINUTES === $differenceUnit; + + if (null === $differenceUnit || $differenceYear) { $diff = $end->diff($start); - /* - * Difference between dates in years should be returned only? - */ - if (self::DATE_DIFFERENCE_UNIT_YEARS === $differenceUnit) { + // Difference between dates in years should be returned only? + if ($differenceYear) { return $diff->y; } $difference[self::DATE_DIFFERENCE_UNIT_YEARS] = $diff->y; } - if (null === $differenceUnit || self::DATE_DIFFERENCE_UNIT_MONTHS === $differenceUnit) { + if (null === $differenceUnit || $differenceMonth) { $diff = $end->diff($start); - /* - * Difference between dates in months should be returned only? - */ - if (self::DATE_DIFFERENCE_UNIT_MONTHS === $differenceUnit) { + // Difference between dates in months should be returned only? + if ($differenceMonth) { return $diff->m; } @@ -425,58 +228,44 @@ class Date } if (null === $differenceUnit || in_array($differenceUnit, $relatedUnits, true)) { - $days = (int)floor($dateDiff / $daySeconds); + $days = (int) floor($dateDiff / $daySeconds); - /* - * Difference between dates in days should be returned only? - */ + // Difference between dates in days should be returned only? if (self::DATE_DIFFERENCE_UNIT_DAYS === $differenceUnit) { return $days; } - /* - * All units should be returned? - */ + // All units should be returned? if (null === $differenceUnit) { $difference[self::DATE_DIFFERENCE_UNIT_DAYS] = $days; } - /* - * Calculation for later usage - */ + // Calculation for later usage $daysInSeconds = $days * $daySeconds; } if (null === $differenceUnit || in_array($differenceUnit, $relatedUnits, true)) { - $hours = (int)floor(($dateDiff - $daysInSeconds) / $hourSeconds); + $hours = (int) floor(($dateDiff - $daysInSeconds) / $hourSeconds); - /* - * Difference between dates in hours should be returned only? - */ + // Difference between dates in hours should be returned only? if (self::DATE_DIFFERENCE_UNIT_HOURS === $differenceUnit) { return $hours; } - /* - * All units should be returned? - */ + // All units should be returned? if (null === $differenceUnit) { $difference[self::DATE_DIFFERENCE_UNIT_HOURS] = $hours; } - /* - * Calculation for later usage - */ + // Calculation for later usage $hoursInSeconds = $hours * $hourSeconds; } - if (null === $differenceUnit || self::DATE_DIFFERENCE_UNIT_MINUTES === $differenceUnit) { - $minutes = (int)floor(($dateDiff - $daysInSeconds - $hoursInSeconds) / 60); + if (null === $differenceUnit || $differenceMinutes) { + $minutes = (int) floor(($dateDiff - $daysInSeconds - $hoursInSeconds) / 60); - /* - * Difference between dates in minutes should be returned only? - */ - if (self::DATE_DIFFERENCE_UNIT_MINUTES === $differenceUnit) { + // Difference between dates in minutes should be returned only? + if ($differenceMinutes) { return $minutes; } @@ -486,91 +275,6 @@ class Date return $difference; } - /** - * Returns collection / set of dates for given start date and count of dates. - * Start from given date, add next, iterated value to given date interval and returns requested count of dates. - * - * @param DateTime $startDate The start date. Start of the collection / set. - * @param int $datesCount Count of dates in resulting collection / set - * @param string $intervalTemplate (optional) Template used to build date interval. It should contain "%d" as the - * placeholder which is replaced with a number that represents each iteration. - * Default: interval for days. - * @throws Exception - * @return array - */ - public static function getDatesCollection(DateTime $startDate, $datesCount, $intervalTemplate = 'P%dD') - { - $dates = []; - - /* - * The template used to build date interval have to be string. - * Otherwise cannot run preg_match() function and an error occurs. - */ - if (is_string($intervalTemplate)) { - /* - * Let's verify the interval template. It should contains the "%d" placeholder and something before and - * after it. - * - * Examples: - * - P%dD - * - P%dM - * - P1Y%dMT1H - */ - $intervalPattern = '/^(\w*)\%d(\w*)$/'; - $matches = []; - $matchCount = preg_match($intervalPattern, $intervalTemplate, $matches); - - if ($matchCount > 0 && (!empty($matches[1]) || !empty($matches[2]))) { - $datesCount = (int)$datesCount; - - for ($index = 1; $index <= $datesCount; ++$index) { - $date = clone $startDate; - $dates[$index] = $date->add(new DateInterval(sprintf($intervalTemplate, $index))); - } - } - } - - return $dates; - } - - /** - * Returns random date based on given start date - * - * @param DateTime $startDate (optional) Beginning of the random date. If not provided, current date will - * be used (default behaviour). - * @param int $start (optional) Start of random partition. If not provided, 1 will be used - * (default behaviour). - * @param int $end (optional) End of random partition. If not provided, 100 will be used - * (default behaviour). - * @param string $intervalTemplate (optional) Template used to build date interval. The placeholder is replaced - * with next, iterated value. If not provided, "P%sD" will be used (default - * behaviour). - * @throws Exception - * @return DateTime - */ - public static function getRandomDate(DateTime $startDate = null, $start = 1, $end = 100, $intervalTemplate = 'P%sD') - { - if (null === $startDate) { - $startDate = new DateTime(); - } - - $start = (int)$start; - $end = (int)$end; - - /* - * Incorrect end of random partition? - * Use start as the end of random partition - */ - if ($end < $start) { - $end = $start; - } - - $randomDate = clone $startDate; - $randomInterval = new DateInterval(sprintf($intervalTemplate, mt_rand($start, $end))); - - return $randomDate->add($randomInterval); - } - /** * Returns the DateTime object for given value. * If the DateTime object cannot be created, false is returned. @@ -583,7 +287,7 @@ class Date * @param string $dateFormat (optional) Format of date used to verify if given value is actually a date. * It should be format matched to the given value, e.g. "Y-m-d H:i" for * "2015-01-01 10:00" value. Default: "Y-m-d". - * @return DateTime|bool + * @return bool|DateTime */ public static function getDateTime($value, $allowCompoundFormats = false, $dateFormat = 'Y-m-d') { @@ -658,12 +362,10 @@ class Date * Verify instance of the DateTime created by constructor and by createFromFormat() method. * After formatting, these dates should be the same. */ - else { - if ($dateFromFormat->format($dateFormat) === $value) { - return $date; - } + elseif ($dateFromFormat->format($dateFormat) === $value) { + return $date; } - } catch (\Exception $exception) { + } catch (Exception $exception) { if (!$allowCompoundFormats) { return false; } @@ -675,15 +377,284 @@ class Date */ $dateString = (new DateTime())->format($value); - if ($dateString !== (string)$value) { + if ($dateString !== (string) $value) { return new DateTime($dateString); } - } catch (\Exception $exception) { + } catch (Exception $exception) { } return false; } + /** + * Returns collection / set of dates for given start date and count of dates. + * Start from given date, add next, iterated value to given date interval and returns requested count of dates. + * + * @param DateTime $startDate The start date. Start of the collection / set. + * @param int $datesCount Count of dates in resulting collection / set + * @param string $intervalTemplate (optional) Template used to build date interval. It should contain "%d" as the + * placeholder which is replaced with a number that represents each iteration. + * Default: interval for days. + * @return array + * @throws Exception + */ + public static function getDatesCollection(DateTime $startDate, $datesCount, $intervalTemplate = 'P%dD'): array + { + $dates = []; + + /* + * The template used to build date interval have to be string. + * Otherwise cannot run preg_match() function and an error occurs. + */ + if (is_string($intervalTemplate)) { + /* + * Let's verify the interval template. It should contains the "%d" placeholder and something before and + * after it. + * + * Examples: + * - P%dD + * - P%dM + * - P1Y%dMT1H + */ + $intervalPattern = '/^(\w*)\%d(\w*)$/'; + $matches = []; + $matchCount = preg_match($intervalPattern, $intervalTemplate, $matches); + + if ($matchCount > 0 && (!empty($matches[1]) || !empty($matches[2]))) { + $datesCount = (int) $datesCount; + + for ($index = 1; $index <= $datesCount; ++$index) { + $date = clone $startDate; + $dates[$index] = $date->add(new DateInterval(sprintf($intervalTemplate, $index))); + } + } + } + + return $dates; + } + + /** + * Returns date's period (that contains start and end date) for given period + * + * @param string $period The period, type of period. One of DatePeriod class constants, e.g. DatePeriod::LAST_WEEK. + * @return null|DatePeriod + * @throws Exception + */ + public static function getDatesForPeriod(string $period): ?DatePeriod + { + /* + * Type of period is incorrect? + * Nothing to do + */ + if (!DatePeriod::isCorrectType($period)) { + return null; + } + + $dateStart = null; + $dateEnd = null; + + switch ($period) { + case DatePeriod::LAST_WEEK: + $thisWeekStart = new DateTime('this week'); + + $dateStart = clone $thisWeekStart; + $dateEnd = clone $thisWeekStart; + + $dateStart->sub(new DateInterval('P7D')); + $dateEnd->sub(new DateInterval('P1D')); + + break; + case DatePeriod::THIS_WEEK: + $dateStart = new DateTime('this week'); + + $dateEnd = clone $dateStart; + $dateEnd->add(new DateInterval('P6D')); + + break; + case DatePeriod::NEXT_WEEK: + $dateStart = new DateTime('this week'); + $dateStart->add(new DateInterval('P7D')); + + $dateEnd = clone $dateStart; + $dateEnd->add(new DateInterval('P6D')); + + break; + case DatePeriod::LAST_MONTH: + $dateStart = new DateTime('first day of last month'); + $dateEnd = new DateTime('last day of last month'); + + break; + case DatePeriod::THIS_MONTH: + $lastMonth = self::getDatesForPeriod(DatePeriod::LAST_MONTH); + $nextMonth = self::getDatesForPeriod(DatePeriod::NEXT_MONTH); + + if (null !== $lastMonth) { + $dateStart = $lastMonth->getEndDate(); + + if (null !== $dateStart) { + $dateStart->add(new DateInterval('P1D')); + } + } + + if (null !== $nextMonth) { + $dateEnd = $nextMonth->getStartDate(); + + if (null !== $dateEnd) { + $dateEnd->sub(new DateInterval('P1D')); + } + } + + break; + case DatePeriod::NEXT_MONTH: + $dateStart = new DateTime('first day of next month'); + $dateEnd = new DateTime('last day of next month'); + + break; + case DatePeriod::LAST_YEAR: + case DatePeriod::THIS_YEAR: + case DatePeriod::NEXT_YEAR: + $dateStart = new DateTime(); + $dateEnd = new DateTime(); + + $yearPeriod = [ + DatePeriod::LAST_YEAR, + DatePeriod::NEXT_YEAR, + ]; + + if (in_array($period, $yearPeriod, true)) { + $yearDifference = 1; + + if (DatePeriod::LAST_YEAR === $period) { + $yearDifference *= -1; + } + + $modifyString = sprintf('%s year', $yearDifference); + $dateStart->modify($modifyString); + $dateEnd->modify($modifyString); + } + + $year = (int) $dateStart->format('Y'); + $dateStart->setDate($year, 1, 1); + $dateEnd->setDate($year, 12, 31); + + break; + } + + /* + * Start or end date is unknown? + * Nothing to do + */ + if (null === $dateStart || null === $dateEnd) { + return null; + } + + $dateStart->setTime(0, 0); + $dateEnd->setTime(23, 59, 59); + + return new DatePeriod($dateStart, $dateEnd); + } + + /** + * Returns day of week (number 0 to 6, 0 - sunday, 6 - saturday). + * Based on the Zeller's algorithm (https://en.wikipedia.org/wiki/Perpetual_calendar). + * + * @param int $year The year value + * @param int $month The month value + * @param int $day The day value + * + * @return int + * @throws UnknownDatePartTypeException + */ + public static function getDayOfWeek(int $year, int $month, int $day): int + { + static::validateYear($year); + static::validateMonth($month); + static::validateDay($day); + + if ($month < 3) { + $count = 0; + $yearValue = $year - 1; + } else { + $count = 2; + $yearValue = $year; + } + + $firstPart = floor(23 * $month / 9); + $secondPart = floor($yearValue / 4); + $thirdPart = floor($yearValue / 100); + $fourthPart = floor($yearValue / 400); + + return ($firstPart + $day + 4 + $year + $secondPart - $thirdPart + $fourthPart - $count) % 7; + } + + /** + * Returns name of weekday based on locale + * + * @param int $year The year value + * @param int $month The month value + * @param int $day The day value + * @return string + */ + public static function getDayOfWeekName($year, $month, $day): string + { + $hour = 0; + $minute = 0; + $second = 0; + + $time = mktime($hour, $minute, $second, $month, $day, $year); + $name = strftime('%A', $time); + + $encoding = mb_detect_encoding($name); + + if (false === $encoding) { + $name = mb_convert_encoding($name, 'UTF-8', 'ISO-8859-2'); + } + + return $name; + } + + /** + * Returns random date based on given start date + * + * @param DateTime $startDate (optional) Beginning of the random date. If not provided, current date will + * be used (default behaviour). + * @param int $start (optional) Start of random partition. If not provided, 1 will be used + * (default behaviour). + * @param int $end (optional) End of random partition. If not provided, 100 will be used + * (default behaviour). + * @param string $intervalTemplate (optional) Template used to build date interval. The placeholder is replaced + * with next, iterated value. If not provided, "P%sD" will be used (default + * behaviour). + * @return DateTime + * @throws Exception + */ + public static function getRandomDate( + DateTime $startDate = null, + $start = 1, + $end = 100, + $intervalTemplate = 'P%sD' + ): DateTime { + if (null === $startDate) { + $startDate = new DateTime(); + } + + $start = (int) $start; + $end = (int) $end; + + /* + * Incorrect end of random partition? + * Use start as the end of random partition + */ + if ($end < $start) { + $end = $start; + } + + $randomDate = clone $startDate; + $randomInterval = new DateInterval(sprintf($intervalTemplate, random_int($start, $end))); + + return $randomDate->add($randomInterval); + } + /** * Returns information if given value is valid date * @@ -693,7 +664,7 @@ class Date * month", "yyyy"). Otherwise - not and every incorrect value is refused. * @return bool */ - public static function isValidDate($value, $allowCompoundFormats = false) + public static function isValidDate($value, $allowCompoundFormats = false): bool { return self::getDateTime($value, $allowCompoundFormats) instanceof DateTime; } @@ -704,38 +675,77 @@ class Date * @param string $format The validated format of date * @return bool */ - public static function isValidDateFormat($format) + public static function isValidDateFormat($format): bool { if (empty($format) || !is_string($format)) { return false; } - /* - * Datetime to string - */ $formatted = (new DateTime())->format($format); - /* - * Formatted date it's the format who is validated? - * The format is invalid - */ + // Formatted date it's the format who is validated? + // The format is invalid if ($formatted === $format) { return false; } - /* - * Validate the format used to create the datetime - */ + // Validate the format used to create the datetime $fromFormat = DateTime::createFromFormat($format, $formatted); - /* - * It's instance of DateTime? - * The format is valid - */ + // It's instance of DateTime? + // The format is valid if ($fromFormat instanceof DateTime) { return true; } return $fromFormat instanceof DateTime; } + + /** + * Verifies/validates given day + * + * @param int $day Day to verify/validate + * @throws UnknownDatePartTypeException + */ + private static function validateDay(int $day): void + { + // Oops, given day is incorrect + if ($day >= 1 && $day <= 31) { + return; + } + + throw UnknownDatePartTypeException::createException(DatePartType::DAY, $day); + } + + /** + * Verifies/validates given month + * + * @param int $month Month to verify/validate + * @throws UnknownDatePartTypeException + */ + private static function validateMonth(int $month): void + { + // Oops, given month is incorrect + if ($month >= 1 && $month <= 12) { + return; + } + + throw UnknownDatePartTypeException::createException(DatePartType::MONTH, $month); + } + + /** + * Verifies/validates given year + * + * @param int $year Year to verify/validate + * @throws UnknownDatePartTypeException + */ + private static function validateYear(int $year): void + { + // Oops, given year is incorrect + if ($year >= 0) { + return; + } + + throw UnknownDatePartTypeException::createException(DatePartType::YEAR, $year); + } } diff --git a/src/Utilities/Locale.php b/src/Utilities/Locale.php index 770c01b..b7f7787 100644 --- a/src/Utilities/Locale.php +++ b/src/Utilities/Locale.php @@ -16,51 +16,6 @@ namespace Meritoo\Common\Utilities; */ class Locale { - /** - * Sets locale for given category using given language and country code - * - * @param int $category Named constant specifying the category of the functions affected by the locale - * setting. It's the same constant as required by setlocale() function. - * @param string $languageCode Language code, in ISO 639-1 format. Short form of the locale, e.g. "fr". - * @param string $countryCode (optional) Country code, in ISO 3166-1 alpha-2 format, e.g. "FR" - * @return false|string - * - * Available categories (values of $category argument): - * - LC_ALL for all of the below - * - LC_COLLATE for string comparison, see strcoll() - * - LC_CTYPE for character classification and conversion, for example strtoupper() - * - LC_MONETARY for localeconv() - * - LC_NUMERIC for decimal separator (See also localeconv()) - * - LC_TIME for date and time formatting with strftime() - * - LC_MESSAGES for system responses (available if PHP was compiled with libintl) - */ - public static function setLocale($category, $languageCode, $countryCode = '') - { - $category = (int)$category; - - if (is_string($languageCode)) { - $languageCode = trim($languageCode); - } - - $availableCategories = [ - LC_ALL, - LC_COLLATE, - LC_CTYPE, - LC_MONETARY, - LC_NUMERIC, - LC_TIME, - LC_MESSAGES, - ]; - - if (empty($languageCode) || !in_array($category, $availableCategories, true)) { - return false; - } - - $localeLongForm = self::getLongForm($languageCode, $countryCode); - - return setlocale($category, $localeLongForm); - } - /** * Returns locale for given category * @@ -123,4 +78,49 @@ class Locale return sprintf('%s_%s%s', $languageCode, strtoupper($countryCode), $encoding); } + + /** + * Sets locale for given category using given language and country code + * + * @param int $category Named constant specifying the category of the functions affected by the locale + * setting. It's the same constant as required by setlocale() function. + * @param string $languageCode Language code, in ISO 639-1 format. Short form of the locale, e.g. "fr". + * @param string $countryCode (optional) Country code, in ISO 3166-1 alpha-2 format, e.g. "FR" + * @return false|string + * + * Available categories (values of $category argument): + * - LC_ALL for all of the below + * - LC_COLLATE for string comparison, see strcoll() + * - LC_CTYPE for character classification and conversion, for example strtoupper() + * - LC_MONETARY for localeconv() + * - LC_NUMERIC for decimal separator (See also localeconv()) + * - LC_TIME for date and time formatting with strftime() + * - LC_MESSAGES for system responses (available if PHP was compiled with libintl) + */ + public static function setLocale($category, $languageCode, $countryCode = '') + { + $category = (int) $category; + + if (is_string($languageCode)) { + $languageCode = trim($languageCode); + } + + $availableCategories = [ + LC_ALL, + LC_COLLATE, + LC_CTYPE, + LC_MONETARY, + LC_NUMERIC, + LC_TIME, + LC_MESSAGES, + ]; + + if (empty($languageCode) || !in_array($category, $availableCategories, true)) { + return false; + } + + $localeLongForm = self::getLongForm($languageCode, $countryCode); + + return setlocale($category, $localeLongForm); + } } diff --git a/src/Utilities/MimeTypes.php b/src/Utilities/MimeTypes.php index 2c02b10..f300738 100644 --- a/src/Utilities/MimeTypes.php +++ b/src/Utilities/MimeTypes.php @@ -8,6 +8,9 @@ namespace Meritoo\Common\Utilities; +use finfo; +use RuntimeException; + /** * Useful methods for mime types of files * @@ -22,663 +25,680 @@ class MimeTypes * @var array */ private static $mimeTypes = [ - '7z' => 'application/x-7z-compressed', - 'ez' => 'application/andrew-inset', - 'atom' => 'application/atom+xml', - 'atomcat' => 'application/atomcat+xml', - 'atomsvc' => 'application/atomsvc+xml', - 'ccxml' => 'application/ccxml+xml', - 'davmount' => 'application/davmount+xml', - 'ecma' => 'application/ecmascript', - 'pfr' => 'application/font-tdpfr', - 'stk' => 'application/hyperstudio', - 'js' => 'application/javascript', - 'json' => 'application/json', - 'hqx' => 'application/mac-binhex40', - 'cpt' => 'application/mac-compactpro', - 'mrc' => 'application/marc', - 'ma' => 'application/mathematica', - 'nb' => 'application/mathematica', - 'mb' => 'application/mathematica', - 'mathml' => 'application/mathml+xml', - 'mbox' => 'application/mbox', - 'mscml' => 'application/mediaservercontrol+xml', - 'mp4s' => 'application/mp4', - 'dot' => 'application/msword', - 'doc' => 'application/msword', + '7z' => 'application/x-7z-compressed', + 'ez' => 'application/andrew-inset', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'ccxml' => 'application/ccxml+xml', + 'davmount' => 'application/davmount+xml', + 'ecma' => 'application/ecmascript', + 'pfr' => 'application/font-tdpfr', + 'stk' => 'application/hyperstudio', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'mrc' => 'application/marc', + 'ma' => 'application/mathematica', + 'nb' => 'application/mathematica', + 'mb' => 'application/mathematica', + 'mathml' => 'application/mathml+xml', + 'mbox' => 'application/mbox', + 'mscml' => 'application/mediaservercontrol+xml', + 'mp4s' => 'application/mp4', + 'dot' => 'application/msword', + 'doc' => 'application/msword', /* * MS Office system file format MIME types * http://technet.microsoft.com/en-us/library/ee309278%28office.12%29.aspx */ - 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'mxf' => 'application/mxf', - 'bin' => 'application/octet-stream', - 'dms' => 'application/octet-stream', - 'lha' => 'application/octet-stream', - 'lzh' => 'application/octet-stream', - 'class' => 'application/octet-stream', - 'so' => 'application/octet-stream', - 'iso' => 'application/octet-stream', - 'dmg' => 'application/octet-stream', - 'dist' => 'application/octet-stream', - 'distz' => 'application/octet-stream', - 'pkg' => 'application/octet-stream', - 'bpk' => 'application/octet-stream', - 'dump' => 'application/octet-stream', - 'elc' => 'application/octet-stream', - 'scpt' => 'application/octet-stream', - 'oda' => 'application/oda', - 'ogg' => 'application/ogg', - 'pdf' => 'application/pdf', - 'pgp' => 'application/pgp-encrypted', - 'asc' => 'application/pgp-signature', - 'sig' => 'application/pgp-signature', - 'prf' => 'application/pics-rules', - 'p10' => 'application/pkcs10', - 'p7m' => 'application/pkcs7-mime', - 'p7c' => 'application/pkcs7-mime', - 'p7s' => 'application/pkcs7-signature', - 'cer' => 'application/pkix-cert', - 'crl' => 'application/pkix-crl', - 'pkipath' => 'application/pkix-pkipath', - 'pki' => 'application/pkixcmp', - 'pls' => 'application/pls+xml', - 'ai' => 'application/postscript', - 'eps' => 'application/postscript', - 'ps' => 'application/postscript', - 'cww' => 'application/prs.cww', - 'rdf' => 'application/rdf+xml', - 'rif' => 'application/reginfo+xml', - 'rnc' => 'application/relax-ng-compact-syntax', - 'rl' => 'application/resource-lists+xml', - 'rs' => 'application/rls-services+xml', - 'rsd' => 'application/rsd+xml', - 'rss' => 'application/rss+xml', - 'rtf' => 'application/rtf', - 'sbml' => 'application/sbml+xml', - 'sdp' => 'application/sdp', - 'setpay' => 'application/set-payment-initiation', - 'setreg' => 'application/set-registration-initiation', - 'shf' => 'application/shf+xml', - 'smi' => 'application/smil+xml', - 'smil' => 'application/smil+xml', - 'gram' => 'application/srgs', - 'grxml' => 'application/srgs+xml', - 'ssml' => 'application/ssml+xml', - 'plb' => 'application/vnd.3gpp.pic-bw-large', - 'psb' => 'application/vnd.3gpp.pic-bw-small', - 'pvb' => 'application/vnd.3gpp.pic-bw-var', - 'pwn' => 'application/vnd.3m.post-it-notes', - 'aso' => 'application/vnd.accpac.simply.aso', - 'imp' => 'application/vnd.accpac.simply.imp', - 'acu' => 'application/vnd.acucobol', - 'atc' => 'application/vnd.acucorp', - 'acutc' => 'application/vnd.acucorp', - 'xdp' => 'application/vnd.adobe.xdp+xml', - 'xfdf' => 'application/vnd.adobe.xfdf', - 'ami' => 'application/vnd.amiga.ami', - 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', - 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', - 'atx' => 'application/vnd.antix.game-component', - 'mpkg' => 'application/vnd.apple.installer+xml', - 'aep' => 'application/vnd.audiograph', - 'mpm' => 'application/vnd.blueice.multipass', - 'bmi' => 'application/vnd.bmi', - 'rep' => 'application/vnd.businessobjects', - 'cdxml' => 'application/vnd.chemdraw+xml', - 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', - 'cdy' => 'application/vnd.cinderella', - 'cla' => 'application/vnd.claymore', - 'c4g' => 'application/vnd.clonk.c4group', - 'c4d' => 'application/vnd.clonk.c4group', - 'c4f' => 'application/vnd.clonk.c4group', - 'c4p' => 'application/vnd.clonk.c4group', - 'c4u' => 'application/vnd.clonk.c4group', - 'csp' => 'application/vnd.commonspace', - 'cst' => 'application/vnd.commonspace', - 'cdbcmsg' => 'application/vnd.contact.cmsg', - 'cmc' => 'application/vnd.cosmocaller', - 'clkx' => 'application/vnd.crick.clicker', - 'clkk' => 'application/vnd.crick.clicker.keyboard', - 'clkp' => 'application/vnd.crick.clicker.palette', - 'clkt' => 'application/vnd.crick.clicker.template', - 'clkw' => 'application/vnd.crick.clicker.wordbank', - 'wbs' => 'application/vnd.criticaltools.wbs+xml', - 'pml' => 'application/vnd.ctc-posml', - 'ppd' => 'application/vnd.cups-ppd', - 'curl' => 'application/vnd.curl', - 'rdz' => 'application/vnd.data-vision.rdz', - 'dna' => 'application/vnd.dna', - 'mlp' => 'application/vnd.dolby.mlp', - 'dpg' => 'application/vnd.dpgraph', - 'dfac' => 'application/vnd.dreamfactory', - 'mag' => 'application/vnd.ecowin.chart', - 'nml' => 'application/vnd.enliven', - 'esf' => 'application/vnd.epson.esf', - 'msf' => 'application/vnd.epson.msf', - 'qam' => 'application/vnd.epson.quickanime', - 'slt' => 'application/vnd.epson.salt', - 'ssf' => 'application/vnd.epson.ssf', - 'es3' => 'application/vnd.eszigno3+xml', - 'et3' => 'application/vnd.eszigno3+xml', - 'ez2' => 'application/vnd.ezpix-album', - 'ez3' => 'application/vnd.ezpix-package', - 'fdf' => 'application/vnd.fdf', - 'gph' => 'application/vnd.flographit', - 'ftc' => 'application/vnd.fluxtime.clip', - 'fm' => 'application/vnd.framemaker', - 'frame' => 'application/vnd.framemaker', - 'maker' => 'application/vnd.framemaker', - 'fnc' => 'application/vnd.frogans.fnc', - 'ltf' => 'application/vnd.frogans.ltf', - 'fsc' => 'application/vnd.fsc.weblaunch', - 'oas' => 'application/vnd.fujitsu.oasys', - 'oa2' => 'application/vnd.fujitsu.oasys2', - 'oa3' => 'application/vnd.fujitsu.oasys3', - 'fg5' => 'application/vnd.fujitsu.oasysgp', - 'bh2' => 'application/vnd.fujitsu.oasysprs', - 'ddd' => 'application/vnd.fujixerox.ddd', - 'xdw' => 'application/vnd.fujixerox.docuworks', - 'xbd' => 'application/vnd.fujixerox.docuworks.binder', - 'fzs' => 'application/vnd.fuzzysheet', - 'txd' => 'application/vnd.genomatix.tuxedo', - 'kml' => 'application/vnd.google-earth.kml+xml', - 'kmz' => 'application/vnd.google-earth.kmz', - 'gqf' => 'application/vnd.grafeq', - 'gqs' => 'application/vnd.grafeq', - 'gac' => 'application/vnd.groove-account', - 'ghf' => 'application/vnd.groove-help', - 'gim' => 'application/vnd.groove-identity-message', - 'grv' => 'application/vnd.groove-injector', - 'gtm' => 'application/vnd.groove-tool-message', - 'tpl' => 'application/vnd.groove-tool-template', - 'vcg' => 'application/vnd.groove-vcard', - 'zmm' => 'application/vnd.handheld-entertainment+xml', - 'hbci' => 'application/vnd.hbci', - 'les' => 'application/vnd.hhe.lesson-player', - 'hpgl' => 'application/vnd.hp-hpgl', - 'hpid' => 'application/vnd.hp-hpid', - 'hps' => 'application/vnd.hp-hps', - 'jlt' => 'application/vnd.hp-jlyt', - 'pcl' => 'application/vnd.hp-pcl', - 'pclxl' => 'application/vnd.hp-pclxl', - 'x3d' => 'application/vnd.hzn-3d-crossword', - 'mpy' => 'application/vnd.ibm.minipay', - 'afp' => 'application/vnd.ibm.modcap', - 'listafp' => 'application/vnd.ibm.modcap', - 'list3820' => 'application/vnd.ibm.modcap', - 'irm' => 'application/vnd.ibm.rights-management', - 'sc' => 'application/vnd.ibm.secure-container', - 'igl' => 'application/vnd.igloader', - 'ivp' => 'application/vnd.immervision-ivp', - 'ivu' => 'application/vnd.immervision-ivu', - 'xpw' => 'application/vnd.intercon.formnet', - 'xpx' => 'application/vnd.intercon.formnet', - 'qbo' => 'application/vnd.intu.qbo', - 'qfx' => 'application/vnd.intu.qfx', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'mxf' => 'application/mxf', + 'bin' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'class' => 'application/octet-stream', + 'so' => 'application/octet-stream', + 'iso' => 'application/octet-stream', + 'dmg' => 'application/octet-stream', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'pkg' => 'application/octet-stream', + 'bpk' => 'application/octet-stream', + 'dump' => 'application/octet-stream', + 'elc' => 'application/octet-stream', + 'scpt' => 'application/octet-stream', + 'oda' => 'application/oda', + 'ogg' => 'application/ogg', + 'pdf' => 'application/pdf', + 'pgp' => 'application/pgp-encrypted', + 'asc' => 'application/pgp-signature', + 'sig' => 'application/pgp-signature', + 'prf' => 'application/pics-rules', + 'p10' => 'application/pkcs10', + 'p7m' => 'application/pkcs7-mime', + 'p7c' => 'application/pkcs7-mime', + 'p7s' => 'application/pkcs7-signature', + 'cer' => 'application/pkix-cert', + 'crl' => 'application/pkix-crl', + 'pkipath' => 'application/pkix-pkipath', + 'pki' => 'application/pkixcmp', + 'pls' => 'application/pls+xml', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'cww' => 'application/prs.cww', + 'rdf' => 'application/rdf+xml', + 'rif' => 'application/reginfo+xml', + 'rnc' => 'application/relax-ng-compact-syntax', + 'rl' => 'application/resource-lists+xml', + 'rs' => 'application/rls-services+xml', + 'rsd' => 'application/rsd+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'sbml' => 'application/sbml+xml', + 'sdp' => 'application/sdp', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'shf' => 'application/shf+xml', + 'smi' => 'application/smil+xml', + 'smil' => 'application/smil+xml', + 'gram' => 'application/srgs', + 'grxml' => 'application/srgs+xml', + 'ssml' => 'application/ssml+xml', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'aso' => 'application/vnd.accpac.simply.aso', + 'imp' => 'application/vnd.accpac.simply.imp', + 'acu' => 'application/vnd.acucobol', + 'atc' => 'application/vnd.acucorp', + 'acutc' => 'application/vnd.acucorp', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'ami' => 'application/vnd.amiga.ami', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'atx' => 'application/vnd.antix.game-component', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'aep' => 'application/vnd.audiograph', + 'mpm' => 'application/vnd.blueice.multipass', + 'bmi' => 'application/vnd.bmi', + 'rep' => 'application/vnd.businessobjects', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'cdy' => 'application/vnd.cinderella', + 'cla' => 'application/vnd.claymore', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'csp' => 'application/vnd.commonspace', + 'cst' => 'application/vnd.commonspace', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cmc' => 'application/vnd.cosmocaller', + 'clkx' => 'application/vnd.crick.clicker', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'pml' => 'application/vnd.ctc-posml', + 'ppd' => 'application/vnd.cups-ppd', + 'curl' => 'application/vnd.curl', + 'rdz' => 'application/vnd.data-vision.rdz', + 'dna' => 'application/vnd.dna', + 'mlp' => 'application/vnd.dolby.mlp', + 'dpg' => 'application/vnd.dpgraph', + 'dfac' => 'application/vnd.dreamfactory', + 'mag' => 'application/vnd.ecowin.chart', + 'nml' => 'application/vnd.enliven', + 'esf' => 'application/vnd.epson.esf', + 'msf' => 'application/vnd.epson.msf', + 'qam' => 'application/vnd.epson.quickanime', + 'slt' => 'application/vnd.epson.salt', + 'ssf' => 'application/vnd.epson.ssf', + 'es3' => 'application/vnd.eszigno3+xml', + 'et3' => 'application/vnd.eszigno3+xml', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'fdf' => 'application/vnd.fdf', + 'gph' => 'application/vnd.flographit', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fm' => 'application/vnd.framemaker', + 'frame' => 'application/vnd.framemaker', + 'maker' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'ltf' => 'application/vnd.frogans.ltf', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'oas' => 'application/vnd.fujitsu.oasys', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'fzs' => 'application/vnd.fuzzysheet', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gac' => 'application/vnd.groove-account', + 'ghf' => 'application/vnd.groove-help', + 'gim' => 'application/vnd.groove-identity-message', + 'grv' => 'application/vnd.groove-injector', + 'gtm' => 'application/vnd.groove-tool-message', + 'tpl' => 'application/vnd.groove-tool-template', + 'vcg' => 'application/vnd.groove-vcard', + 'zmm' => 'application/vnd.handheld-entertainment+xml', + 'hbci' => 'application/vnd.hbci', + 'les' => 'application/vnd.hhe.lesson-player', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'jlt' => 'application/vnd.hp-jlyt', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'x3d' => 'application/vnd.hzn-3d-crossword', + 'mpy' => 'application/vnd.ibm.minipay', + 'afp' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'list3820' => 'application/vnd.ibm.modcap', + 'irm' => 'application/vnd.ibm.rights-management', + 'sc' => 'application/vnd.ibm.secure-container', + 'igl' => 'application/vnd.igloader', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', - 'irp' => 'application/vnd.irepository.package+xml', - 'xpr' => 'application/vnd.is-xpr', - 'jam' => 'application/vnd.jam', - 'rms' => 'application/vnd.jcp.javame.midlet-rms', - 'jisp' => 'application/vnd.jisp', - 'ktz' => 'application/vnd.kahootz', - 'ktr' => 'application/vnd.kahootz', - 'karbon' => 'application/vnd.kde.karbon', - 'chrt' => 'application/vnd.kde.kchart', - 'kfo' => 'application/vnd.kde.kformula', - 'flw' => 'application/vnd.kde.kivio', - 'kon' => 'application/vnd.kde.kontour', - 'kpr' => 'application/vnd.kde.kpresenter', - 'kpt' => 'application/vnd.kde.kpresenter', - 'ksp' => 'application/vnd.kde.kspread', - 'kwd' => 'application/vnd.kde.kword', - 'kwt' => 'application/vnd.kde.kword', - 'htke' => 'application/vnd.kenameaapp', - 'kia' => 'application/vnd.kidspiration', - 'kne' => 'application/vnd.kinar', - 'knp' => 'application/vnd.kinar', - 'skp' => 'application/vnd.koan', - 'skd' => 'application/vnd.koan', - 'skt' => 'application/vnd.koan', - 'skm' => 'application/vnd.koan', - 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', - 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', - '123' => 'application/vnd.lotus-1-2-3', - 'apr' => 'application/vnd.lotus-approach', - 'pre' => 'application/vnd.lotus-freelance', - 'nsf' => 'application/vnd.lotus-notes', - 'org' => 'application/vnd.lotus-organizer', - 'scm' => 'application/vnd.lotus-screencam', - 'lwp' => 'application/vnd.lotus-wordpro', - 'portpkg' => 'application/vnd.macports.portpkg', - 'mcd' => 'application/vnd.mcd', - 'mc1' => 'application/vnd.medcalcdata', - 'cdkey' => 'application/vnd.mediastation.cdkey', - 'mwf' => 'application/vnd.mfer', - 'mfm' => 'application/vnd.mfmp', - 'flo' => 'application/vnd.micrografx.flo', - 'igx' => 'application/vnd.micrografx.igx', - 'mif' => 'application/vnd.mif', - 'daf' => 'application/vnd.mobius.daf', - 'dis' => 'application/vnd.mobius.dis', - 'mbk' => 'application/vnd.mobius.mbk', - 'mqy' => 'application/vnd.mobius.mqy', - 'msl' => 'application/vnd.mobius.msl', - 'plc' => 'application/vnd.mobius.plc', - 'txf' => 'application/vnd.mobius.txf', - 'mpn' => 'application/vnd.mophun.application', - 'mpc' => 'application/vnd.mophun.certificate', - 'xul' => 'application/vnd.mozilla.xul+xml', - 'cil' => 'application/vnd.ms-artgalry', - 'asf' => 'application/vnd.ms-asf', - 'cab' => 'application/vnd.ms-cab-compressed', - 'xls' => 'application/vnd.ms-excel', - 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'xlm' => 'application/vnd.ms-excel', - 'xla' => 'application/vnd.ms-excel', - 'xlc' => 'application/vnd.ms-excel', - 'xlt' => 'application/vnd.ms-excel', - 'xlw' => 'application/vnd.ms-excel', - 'eot' => 'application/vnd.ms-fontobject', - 'chm' => 'application/vnd.ms-htmlhelp', - 'ims' => 'application/vnd.ms-ims', - 'lrm' => 'application/vnd.ms-lrm', - 'ppt' => 'application/vnd.ms-powerpoint', - 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'pps' => 'application/vnd.ms-powerpoint', - 'pot' => 'application/vnd.ms-powerpoint', - 'mpp' => 'application/vnd.ms-project', - 'mpt' => 'application/vnd.ms-project', - 'wps' => 'application/vnd.ms-works', - 'wks' => 'application/vnd.ms-works', - 'wcm' => 'application/vnd.ms-works', - 'wdb' => 'application/vnd.ms-works', - 'wpl' => 'application/vnd.ms-wpl', - 'xps' => 'application/vnd.ms-xpsdocument', - 'mseq' => 'application/vnd.mseq', - 'mus' => 'application/vnd.musician', - 'nlu' => 'application/vnd.neurolanguage.nlu', - 'nnd' => 'application/vnd.noblenet-directory', - 'nns' => 'application/vnd.noblenet-sealer', - 'nnw' => 'application/vnd.noblenet-web', - 'ngdat' => 'application/vnd.nokia.n-gage.data', - 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', - 'rpst' => 'application/vnd.nokia.radio-preset', - 'rpss' => 'application/vnd.nokia.radio-presets', - 'edm' => 'application/vnd.novadigm.edm', - 'edx' => 'application/vnd.novadigm.edx', - 'ext' => 'application/vnd.novadigm.ext', - 'odc' => 'application/vnd.oasis.opendocument.chart', - 'otc' => 'application/vnd.oasis.opendocument.chart-template', - 'odf' => 'application/vnd.oasis.opendocument.formula', - 'otf' => 'application/vnd.oasis.opendocument.formula-template', - 'odg' => 'application/vnd.oasis.opendocument.graphics', - 'otg' => 'application/vnd.oasis.opendocument.graphics-template', - 'odi' => 'application/vnd.oasis.opendocument.image', - 'oti' => 'application/vnd.oasis.opendocument.image-template', - 'odp' => 'application/vnd.oasis.opendocument.presentation', - 'otp' => 'application/vnd.oasis.opendocument.presentation-template', - 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', - 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', - 'odt' => 'application/vnd.oasis.opendocument.text', - 'otm' => 'application/vnd.oasis.opendocument.text-master', - 'ott' => 'application/vnd.oasis.opendocument.text-template', - 'oth' => 'application/vnd.oasis.opendocument.text-web', - 'xo' => 'application/vnd.olpc-sugar', - 'dd2' => 'application/vnd.oma.dd2+xml', - 'oxt' => 'application/vnd.openofficeorg.extension', - 'dp' => 'application/vnd.osgi.dp', - 'prc' => 'application/vnd.palm', - 'pdb' => 'application/vnd.palm', - 'pqa' => 'application/vnd.palm', - 'oprc' => 'application/vnd.palm', - 'str' => 'application/vnd.pg.format', - 'ei6' => 'application/vnd.pg.osasli', - 'efif' => 'application/vnd.picsel', - 'plf' => 'application/vnd.pocketlearn', - 'pbd' => 'application/vnd.powerbuilder6', - 'box' => 'application/vnd.previewsystems.box', - 'mgz' => 'application/vnd.proteus.magazine', - 'qps' => 'application/vnd.publishare-delta-tree', - 'ptid' => 'application/vnd.pvi.ptid1', - 'qxd' => 'application/vnd.quark.quarkxpress', - 'qxt' => 'application/vnd.quark.quarkxpress', - 'qwd' => 'application/vnd.quark.quarkxpress', - 'qwt' => 'application/vnd.quark.quarkxpress', - 'qxl' => 'application/vnd.quark.quarkxpress', - 'qxb' => 'application/vnd.quark.quarkxpress', - 'mxl' => 'application/vnd.recordare.musicxml', - 'rm' => 'application/vnd.rn-realmedia', - 'see' => 'application/vnd.seemail', - 'sema' => 'application/vnd.sema', - 'semd' => 'application/vnd.semd', - 'semf' => 'application/vnd.semf', - 'ifm' => 'application/vnd.shana.informed.formdata', - 'itp' => 'application/vnd.shana.informed.formtemplate', - 'iif' => 'application/vnd.shana.informed.interchange', - 'ipk' => 'application/vnd.shana.informed.package', - 'twd' => 'application/vnd.simtech-mindmapper', - 'twds' => 'application/vnd.simtech-mindmapper', - 'mmf' => 'application/vnd.smaf', - 'sdkm' => 'application/vnd.solent.sdkm+xml', - 'sdkd' => 'application/vnd.solent.sdkm+xml', - 'dxp' => 'application/vnd.spotfire.dxp', - 'sfs' => 'application/vnd.spotfire.sfs', - 'sus' => 'application/vnd.sus-calendar', - 'susp' => 'application/vnd.sus-calendar', - 'svd' => 'application/vnd.svd', - 'xsm' => 'application/vnd.syncml+xml', - 'bdm' => 'application/vnd.syncml.dm+wbxml', - 'xdm' => 'application/vnd.syncml.dm+xml', - 'tao' => 'application/vnd.tao.intent-module-archive', - 'tmo' => 'application/vnd.tmobile-livetv', - 'tpt' => 'application/vnd.trid.tpt', - 'mxs' => 'application/vnd.triscape.mxs', - 'tra' => 'application/vnd.trueapp', - 'ufd' => 'application/vnd.ufdl', - 'ufdl' => 'application/vnd.ufdl', - 'utz' => 'application/vnd.uiq.theme', - 'umj' => 'application/vnd.umajin', - 'unityweb' => 'application/vnd.unity', - 'uoml' => 'application/vnd.uoml+xml', - 'vcx' => 'application/vnd.vcx', - 'vsd' => 'application/vnd.visio', - 'vst' => 'application/vnd.visio', - 'vss' => 'application/vnd.visio', - 'vsw' => 'application/vnd.visio', - 'vis' => 'application/vnd.visionary', - 'vsf' => 'application/vnd.vsf', - 'wbxml' => 'application/vnd.wap.wbxml', - 'wmlc' => 'application/vnd.wap.wmlc', - 'wmlsc' => 'application/vnd.wap.wmlscriptc', - 'wtb' => 'application/vnd.webturbo', - 'wpd' => 'application/vnd.wordperfect', - 'wqd' => 'application/vnd.wqd', - 'stf' => 'application/vnd.wt.stf', - 'xar' => 'application/vnd.xara', - 'xfdl' => 'application/vnd.xfdl', - 'hvd' => 'application/vnd.yamaha.hv-dic', - 'hvs' => 'application/vnd.yamaha.hv-script', - 'hvp' => 'application/vnd.yamaha.hv-voice', - 'saf' => 'application/vnd.yamaha.smaf-audio', - 'spf' => 'application/vnd.yamaha.smaf-phrase', - 'cmp' => 'application/vnd.yellowriver-custom-menu', - 'zaz' => 'application/vnd.zzazz.deck+xml', - 'vxml' => 'application/voicexml+xml', - 'hlp' => 'application/winhlp', - 'wsdl' => 'application/wsdl+xml', - 'wspolicy' => 'application/wspolicy+xml', - 'ace' => 'application/x-ace-compressed', - 'bcpio' => 'application/x-bcpio', - 'torrent' => 'application/x-bittorrent', - 'bz' => 'application/x-bzip', - 'bz2' => 'application/x-bzip2', - 'boz' => 'application/x-bzip2', - 'vcd' => 'application/x-cdlink', - 'chat' => 'application/x-chat', - 'pgn' => 'application/x-chess-pgn', - 'cpio' => 'application/x-cpio', - 'csh' => 'application/x-csh', - 'dcr' => 'application/x-director', - 'dir' => 'application/x-director', - 'dxr' => 'application/x-director', - 'fgd' => 'application/x-director', - 'dvi' => 'application/x-dvi', - 'spl' => 'application/x-futuresplash', - 'gtar' => 'application/x-gtar', - 'hdf' => 'application/x-hdf', - 'jnlp' => 'application/x-java-jnlp-file', - 'latex' => 'application/x-latex', - 'wmd' => 'application/x-ms-wmd', - 'wmz' => 'application/x-ms-wmz', - 'mdb' => 'application/x-msaccess', - 'obd' => 'application/x-msbinder', - 'crd' => 'application/x-mscardfile', - 'clp' => 'application/x-msclip', - 'exe' => 'application/x-msdownload', - 'dll' => 'application/x-msdownload', - 'com' => 'application/x-msdownload', - 'bat' => 'application/x-msdownload', - 'msi' => 'application/x-msdownload', - 'mvb' => 'application/x-msmediaview', - 'm13' => 'application/x-msmediaview', - 'm14' => 'application/x-msmediaview', - 'wmf' => 'application/x-msmetafile', - 'mny' => 'application/x-msmoney', - 'pub' => 'application/x-mspublisher', - 'scd' => 'application/x-msschedule', - 'trm' => 'application/x-msterminal', - 'wri' => 'application/x-mswrite', - 'nc' => 'application/x-netcdf', - 'cdf' => 'application/x-netcdf', - 'p12' => 'application/x-pkcs12', - 'pfx' => 'application/x-pkcs12', - 'p7b' => 'application/x-pkcs7-certificates', - 'spc' => 'application/x-pkcs7-certificates', - 'p7r' => 'application/x-pkcs7-certreqresp', - 'rar' => 'application/x-rar-compressed', - 'sh' => 'application/x-sh', - 'shar' => 'application/x-shar', - 'swf' => 'application/x-shockwave-flash', - 'sit' => 'application/x-stuffit', - 'sitx' => 'application/x-stuffitx', - 'sv4cpio' => 'application/x-sv4cpio', - 'sv4crc' => 'application/x-sv4crc', - 'tar' => 'application/x-tar', - 'tcl' => 'application/x-tcl', - 'tex' => 'application/x-tex', - 'texinfo' => 'application/x-texinfo', - 'texi' => 'application/x-texinfo', - 'ustar' => 'application/x-ustar', - 'src' => 'application/x-wais-source', - 'der' => 'application/x-x509-ca-cert', - 'crt' => 'application/x-x509-ca-cert', - 'xenc' => 'application/xenc+xml', - 'xhtml' => 'application/xhtml+xml', - 'xht' => 'application/xhtml+xml', - 'xml' => 'application/xml', - 'xsl' => 'application/xml', - 'dtd' => 'application/xml-dtd', - 'xop' => 'application/xop+xml', - 'xslt' => 'application/xslt+xml', - 'xspf' => 'application/xspf+xml', - 'mxml' => 'application/xv+xml', - 'xhvml' => 'application/xv+xml', - 'xvml' => 'application/xv+xml', - 'xvm' => 'application/xv+xml', - 'zip' => 'application/zip', - 'au' => 'audio/basic', - 'snd' => 'audio/basic', - 'mid' => 'audio/midi', - 'midi' => 'audio/midi', - 'kar' => 'audio/midi', - 'rmi' => 'audio/midi', - 'mp4a' => 'audio/mp4', - 'm4a' => 'audio/mp4a-latm', - 'm4p' => 'audio/mp4a-latm', - 'mpga' => 'audio/mpeg', - 'mp2' => 'audio/mpeg', - 'mp2a' => 'audio/mpeg', - 'mp3' => 'audio/mpeg', - 'm2a' => 'audio/mpeg', - 'm3a' => 'audio/mpeg', - 'eol' => 'audio/vnd.digital-winds', - 'lvp' => 'audio/vnd.lucent.voice', + 'irp' => 'application/vnd.irepository.package+xml', + 'xpr' => 'application/vnd.is-xpr', + 'jam' => 'application/vnd.jam', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'jisp' => 'application/vnd.jisp', + 'ktz' => 'application/vnd.kahootz', + 'ktr' => 'application/vnd.kahootz', + 'karbon' => 'application/vnd.kde.karbon', + 'chrt' => 'application/vnd.kde.kchart', + 'kfo' => 'application/vnd.kde.kformula', + 'flw' => 'application/vnd.kde.kivio', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'ksp' => 'application/vnd.kde.kspread', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'htke' => 'application/vnd.kenameaapp', + 'kia' => 'application/vnd.kidspiration', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'skp' => 'application/vnd.koan', + 'skd' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + '123' => 'application/vnd.lotus-1-2-3', + 'apr' => 'application/vnd.lotus-approach', + 'pre' => 'application/vnd.lotus-freelance', + 'nsf' => 'application/vnd.lotus-notes', + 'org' => 'application/vnd.lotus-organizer', + 'scm' => 'application/vnd.lotus-screencam', + 'lwp' => 'application/vnd.lotus-wordpro', + 'portpkg' => 'application/vnd.macports.portpkg', + 'mcd' => 'application/vnd.mcd', + 'mc1' => 'application/vnd.medcalcdata', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'mwf' => 'application/vnd.mfer', + 'mfm' => 'application/vnd.mfmp', + 'flo' => 'application/vnd.micrografx.flo', + 'igx' => 'application/vnd.micrografx.igx', + 'mif' => 'application/vnd.mif', + 'daf' => 'application/vnd.mobius.daf', + 'dis' => 'application/vnd.mobius.dis', + 'mbk' => 'application/vnd.mobius.mbk', + 'mqy' => 'application/vnd.mobius.mqy', + 'msl' => 'application/vnd.mobius.msl', + 'plc' => 'application/vnd.mobius.plc', + 'txf' => 'application/vnd.mobius.txf', + 'mpn' => 'application/vnd.mophun.application', + 'mpc' => 'application/vnd.mophun.certificate', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'cil' => 'application/vnd.ms-artgalry', + 'asf' => 'application/vnd.ms-asf', + 'cab' => 'application/vnd.ms-cab-compressed', + 'xls' => 'application/vnd.ms-excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlm' => 'application/vnd.ms-excel', + 'xla' => 'application/vnd.ms-excel', + 'xlc' => 'application/vnd.ms-excel', + 'xlt' => 'application/vnd.ms-excel', + 'xlw' => 'application/vnd.ms-excel', + 'eot' => 'application/vnd.ms-fontobject', + 'chm' => 'application/vnd.ms-htmlhelp', + 'ims' => 'application/vnd.ms-ims', + 'lrm' => 'application/vnd.ms-lrm', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pps' => 'application/vnd.ms-powerpoint', + 'pot' => 'application/vnd.ms-powerpoint', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'wps' => 'application/vnd.ms-works', + 'wks' => 'application/vnd.ms-works', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'wpl' => 'application/vnd.ms-wpl', + 'xps' => 'application/vnd.ms-xpsdocument', + 'mseq' => 'application/vnd.mseq', + 'mus' => 'application/vnd.musician', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'ext' => 'application/vnd.novadigm.ext', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'otf' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'otm' => 'application/vnd.oasis.opendocument.text-master', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'xo' => 'application/vnd.olpc-sugar', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'dp' => 'application/vnd.osgi.dp', + 'prc' => 'application/vnd.palm', + 'pdb' => 'application/vnd.palm', + 'pqa' => 'application/vnd.palm', + 'oprc' => 'application/vnd.palm', + 'str' => 'application/vnd.pg.format', + 'ei6' => 'application/vnd.pg.osasli', + 'efif' => 'application/vnd.picsel', + 'plf' => 'application/vnd.pocketlearn', + 'pbd' => 'application/vnd.powerbuilder6', + 'box' => 'application/vnd.previewsystems.box', + 'mgz' => 'application/vnd.proteus.magazine', + 'qps' => 'application/vnd.publishare-delta-tree', + 'ptid' => 'application/vnd.pvi.ptid1', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'mxl' => 'application/vnd.recordare.musicxml', + 'rm' => 'application/vnd.rn-realmedia', + 'see' => 'application/vnd.seemail', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'iif' => 'application/vnd.shana.informed.interchange', + 'ipk' => 'application/vnd.shana.informed.package', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'mmf' => 'application/vnd.smaf', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'dxp' => 'application/vnd.spotfire.dxp', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'svd' => 'application/vnd.svd', + 'xsm' => 'application/vnd.syncml+xml', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tmo' => 'application/vnd.tmobile-livetv', + 'tpt' => 'application/vnd.trid.tpt', + 'mxs' => 'application/vnd.triscape.mxs', + 'tra' => 'application/vnd.trueapp', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'utz' => 'application/vnd.uiq.theme', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'vcx' => 'application/vnd.vcx', + 'vsd' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vss' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vis' => 'application/vnd.visionary', + 'vsf' => 'application/vnd.vsf', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wtb' => 'application/vnd.webturbo', + 'wpd' => 'application/vnd.wordperfect', + 'wqd' => 'application/vnd.wqd', + 'stf' => 'application/vnd.wt.stf', + 'xar' => 'application/vnd.xara', + 'xfdl' => 'application/vnd.xfdl', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'vxml' => 'application/voicexml+xml', + 'hlp' => 'application/winhlp', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'ace' => 'application/x-ace-compressed', + 'bcpio' => 'application/x-bcpio', + 'torrent' => 'application/x-bittorrent', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'boz' => 'application/x-bzip2', + 'vcd' => 'application/x-cdlink', + 'chat' => 'application/x-chat', + 'pgn' => 'application/x-chess-pgn', + 'cpio' => 'application/x-cpio', + 'csh' => 'application/x-csh', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'fgd' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'spl' => 'application/x-futuresplash', + 'gtar' => 'application/x-gtar', + 'hdf' => 'application/x-hdf', + 'jnlp' => 'application/x-java-jnlp-file', + 'latex' => 'application/x-latex', + 'wmd' => 'application/x-ms-wmd', + 'wmz' => 'application/x-ms-wmz', + 'mdb' => 'application/x-msaccess', + 'obd' => 'application/x-msbinder', + 'crd' => 'application/x-mscardfile', + 'clp' => 'application/x-msclip', + 'exe' => 'application/x-msdownload', + 'dll' => 'application/x-msdownload', + 'com' => 'application/x-msdownload', + 'bat' => 'application/x-msdownload', + 'msi' => 'application/x-msdownload', + 'mvb' => 'application/x-msmediaview', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'wmf' => 'application/x-msmetafile', + 'mny' => 'application/x-msmoney', + 'pub' => 'application/x-mspublisher', + 'scd' => 'application/x-msschedule', + 'trm' => 'application/x-msterminal', + 'wri' => 'application/x-mswrite', + 'nc' => 'application/x-netcdf', + 'cdf' => 'application/x-netcdf', + 'p12' => 'application/x-pkcs12', + 'pfx' => 'application/x-pkcs12', + 'p7b' => 'application/x-pkcs7-certificates', + 'spc' => 'application/x-pkcs7-certificates', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'rar' => 'application/x-rar-compressed', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'tar' => 'application/x-tar', + 'tcl' => 'application/x-tcl', + 'tex' => 'application/x-tex', + 'texinfo' => 'application/x-texinfo', + 'texi' => 'application/x-texinfo', + 'ustar' => 'application/x-ustar', + 'src' => 'application/x-wais-source', + 'der' => 'application/x-x509-ca-cert', + 'crt' => 'application/x-x509-ca-cert', + 'xenc' => 'application/xenc+xml', + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'xml' => 'application/xml', + 'xsl' => 'application/xml', + 'dtd' => 'application/xml-dtd', + 'xop' => 'application/xop+xml', + 'xslt' => 'application/xslt+xml', + 'xspf' => 'application/xspf+xml', + 'mxml' => 'application/xv+xml', + 'xhvml' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xvm' => 'application/xv+xml', + 'zip' => 'application/zip', + 'au' => 'audio/basic', + 'snd' => 'audio/basic', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'kar' => 'audio/midi', + 'rmi' => 'audio/midi', + 'mp4a' => 'audio/mp4', + 'm4a' => 'audio/mp4a-latm', + 'm4p' => 'audio/mp4a-latm', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'm2a' => 'audio/mpeg', + 'm3a' => 'audio/mpeg', + 'eol' => 'audio/vnd.digital-winds', + 'lvp' => 'audio/vnd.lucent.voice', 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', - 'wav' => 'audio/wav', - 'aif' => 'audio/x-aiff', - 'aiff' => 'audio/x-aiff', - 'aifc' => 'audio/x-aiff', - 'm3u' => 'audio/x-mpegurl', - 'wax' => 'audio/x-ms-wax', - 'wma' => 'audio/x-ms-wma', - 'ram' => 'audio/x-pn-realaudio', - 'ra' => 'audio/x-pn-realaudio', - 'rmp' => 'audio/x-pn-realaudio-plugin', - 'cdx' => 'chemical/x-cdx', - 'cif' => 'chemical/x-cif', - 'cmdf' => 'chemical/x-cmdf', - 'cml' => 'chemical/x-cml', - 'csml' => 'chemical/x-csml', - 'xyz' => 'chemical/x-xyz', - 'bmp' => 'image/bmp', - 'cgm' => 'image/cgm', - 'g3' => 'image/g3fax', - 'gif' => 'image/gif', - 'ief' => 'image/ief', - 'jp2' => 'image/jp2', - 'jpeg' => 'image/jpeg', - 'jpe' => 'image/jpeg', - 'jpg' => 'image/jpeg', - 'pict' => 'image/pict', - 'pic' => 'image/pict', - 'pct' => 'image/pict', - 'png' => 'image/png', - 'btif' => 'image/prs.btif', - 'svg' => 'image/svg+xml', - 'svgz' => 'image/svg+xml', - 'tiff' => 'image/tiff', - 'tif' => 'image/tiff', - 'psd' => 'image/vnd.adobe.photoshop', - 'djvu' => 'image/vnd.djvu', - 'djv' => 'image/vnd.djvu', - 'dwg' => 'image/vnd.dwg', - 'dxf' => 'image/vnd.dxf', - 'fbs' => 'image/vnd.fastbidsheet', - 'fpx' => 'image/vnd.fpx', - 'fst' => 'image/vnd.fst', - 'mmr' => 'image/vnd.fujixerox.edmics-mmr', - 'rlc' => 'image/vnd.fujixerox.edmics-rlc', - 'ico' => 'image/vnd.microsoft.icon', - 'mdi' => 'image/vnd.ms-modi', - 'npx' => 'image/vnd.net-fpx', - 'wbmp' => 'image/vnd.wap.wbmp', - 'xif' => 'image/vnd.xiff', - 'ras' => 'image/x-cmu-raster', - 'cmx' => 'image/x-cmx', - 'pntg' => 'image/x-macpaint', - 'pnt' => 'image/x-macpaint', - 'mac' => 'image/x-macpaint', - 'pcx' => 'image/x-pcx', - 'pnm' => 'image/x-portable-anymap', - 'pbm' => 'image/x-portable-bitmap', - 'pgm' => 'image/x-portable-graymap', - 'ppm' => 'image/x-portable-pixmap', - 'qtif' => 'image/x-quicktime', - 'qti' => 'image/x-quicktime', - 'rgb' => 'image/x-rgb', - 'xbm' => 'image/x-xbitmap', - 'xpm' => 'image/x-xpixmap', - 'xwd' => 'image/x-xwindowdump', - 'eml' => 'message/rfc822', - 'mime' => 'message/rfc822', - 'igs' => 'model/iges', - 'iges' => 'model/iges', - 'msh' => 'model/mesh', - 'mesh' => 'model/mesh', - 'silo' => 'model/mesh', - 'dwf' => 'model/vnd.dwf', - 'gdl' => 'model/vnd.gdl', - 'gtw' => 'model/vnd.gtw', - 'mts' => 'model/vnd.mts', - 'vtu' => 'model/vnd.vtu', - 'wrl' => 'model/vrml', - 'vrml' => 'model/vrml', - 'ics' => 'text/calendar', - 'ifb' => 'text/calendar', - 'css' => 'text/css', - 'csv' => 'text/csv', - 'html' => 'text/html', - 'htm' => 'text/html', - 'txt' => 'text/plain', - 'text' => 'text/plain', - 'conf' => 'text/plain', - 'def' => 'text/plain', - 'list' => 'text/plain', - 'log' => 'text/plain', - 'in' => 'text/plain', - 'dsc' => 'text/prs.lines.tag', - 'rtx' => 'text/richtext', - 'sgml' => 'text/sgml', - 'sgm' => 'text/sgml', - 'tsv' => 'text/tab-separated-values', - 't' => 'text/troff', - 'tr' => 'text/troff', - 'roff' => 'text/troff', - 'man' => 'text/troff', - 'me' => 'text/troff', - 'ms' => 'text/troff', - 'uri' => 'text/uri-list', - 'uris' => 'text/uri-list', - 'urls' => 'text/uri-list', - 'fly' => 'text/vnd.fly', - 'flx' => 'text/vnd.fmi.flexstor', - '3dml' => 'text/vnd.in3d.3dml', - 'spot' => 'text/vnd.in3d.spot', - 'jad' => 'text/vnd.sun.j2me.app-descriptor', - 'wml' => 'text/vnd.wap.wml', - 'wmls' => 'text/vnd.wap.wmlscript', - 's' => 'text/x-asm', - 'asm' => 'text/x-asm', - 'c' => 'text/x-c', - 'cc' => 'text/x-c', - 'cxx' => 'text/x-c', - 'cpp' => 'text/x-c', - 'h' => 'text/x-c', - 'hh' => 'text/x-c', - 'dic' => 'text/x-c', - 'f' => 'text/x-fortran', - 'for' => 'text/x-fortran', - 'f77' => 'text/x-fortran', - 'f90' => 'text/x-fortran', - 'p' => 'text/x-pascal', - 'pas' => 'text/x-pascal', - 'java' => 'text/x-java-source', - 'etx' => 'text/x-setext', - 'uu' => 'text/x-uuencode', - 'vcs' => 'text/x-vcalendar', - 'vcf' => 'text/x-vcard', - '3gp' => 'video/3gpp', - '3g2' => 'video/3gpp2', - 'h261' => 'video/h261', - 'h263' => 'video/h263', - 'h264' => 'video/h264', - 'jpgv' => 'video/jpeg', - 'jpm' => 'video/jpm', - 'jpgm' => 'video/jpm', - 'mj2' => 'video/mj2', - 'mjp2' => 'video/mj2', - 'mp4' => 'video/mp4', - 'mp4v' => 'video/mp4', - 'mpg4' => 'video/mp4', - 'm4v' => 'video/mp4', - 'mpeg' => 'video/mpeg', - 'mpg' => 'video/mpeg', - 'mpe' => 'video/mpeg', - 'm1v' => 'video/mpeg', - 'm2v' => 'video/mpeg', - 'qt' => 'video/quicktime', - 'mov' => 'video/quicktime', - 'fvt' => 'video/vnd.fvt', - 'mxu' => 'video/vnd.mpegurl', - 'm4u' => 'video/vnd.mpegurl', - 'viv' => 'video/vnd.vivo', - 'dv' => 'video/x-dv', - 'dif' => 'video/x-dv', - 'fli' => 'video/x-fli', - 'asx' => 'video/x-ms-asf', - 'wm' => 'video/x-ms-wm', - 'wmv' => 'video/x-ms-wmv', - 'wmx' => 'video/x-ms-wmx', - 'wvx' => 'video/x-ms-wvx', - 'avi' => 'video/x-msvideo', - 'movie' => 'video/x-sgi-movie', - 'ice' => 'x-conference/x-cooltalk', + 'wav' => 'audio/wav', + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'm3u' => 'audio/x-mpegurl', + 'wax' => 'audio/x-ms-wax', + 'wma' => 'audio/x-ms-wma', + 'ram' => 'audio/x-pn-realaudio', + 'ra' => 'audio/x-pn-realaudio', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'cdx' => 'chemical/x-cdx', + 'cif' => 'chemical/x-cif', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'csml' => 'chemical/x-csml', + 'xyz' => 'chemical/x-xyz', + 'bmp' => 'image/bmp', + 'cgm' => 'image/cgm', + 'g3' => 'image/g3fax', + 'gif' => 'image/gif', + 'ief' => 'image/ief', + 'jp2' => 'image/jp2', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'pict' => 'image/pict', + 'pic' => 'image/pict', + 'pct' => 'image/pict', + 'png' => 'image/png', + 'btif' => 'image/prs.btif', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'psd' => 'image/vnd.adobe.photoshop', + 'djvu' => 'image/vnd.djvu', + 'djv' => 'image/vnd.djvu', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'fbs' => 'image/vnd.fastbidsheet', + 'fpx' => 'image/vnd.fpx', + 'fst' => 'image/vnd.fst', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'ico' => 'image/vnd.microsoft.icon', + 'mdi' => 'image/vnd.ms-modi', + 'npx' => 'image/vnd.net-fpx', + 'wbmp' => 'image/vnd.wap.wbmp', + 'xif' => 'image/vnd.xiff', + 'ras' => 'image/x-cmu-raster', + 'cmx' => 'image/x-cmx', + 'pntg' => 'image/x-macpaint', + 'pnt' => 'image/x-macpaint', + 'mac' => 'image/x-macpaint', + 'pcx' => 'image/x-pcx', + 'pnm' => 'image/x-portable-anymap', + 'pbm' => 'image/x-portable-bitmap', + 'pgm' => 'image/x-portable-graymap', + 'ppm' => 'image/x-portable-pixmap', + 'qtif' => 'image/x-quicktime', + 'qti' => 'image/x-quicktime', + 'rgb' => 'image/x-rgb', + 'xbm' => 'image/x-xbitmap', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'eml' => 'message/rfc822', + 'mime' => 'message/rfc822', + 'igs' => 'model/iges', + 'iges' => 'model/iges', + 'msh' => 'model/mesh', + 'mesh' => 'model/mesh', + 'silo' => 'model/mesh', + 'dwf' => 'model/vnd.dwf', + 'gdl' => 'model/vnd.gdl', + 'gtw' => 'model/vnd.gtw', + 'mts' => 'model/vnd.mts', + 'vtu' => 'model/vnd.vtu', + 'wrl' => 'model/vrml', + 'vrml' => 'model/vrml', + 'ics' => 'text/calendar', + 'ifb' => 'text/calendar', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'html' => 'text/html', + 'htm' => 'text/html', + 'txt' => 'text/plain', + 'text' => 'text/plain', + 'conf' => 'text/plain', + 'def' => 'text/plain', + 'list' => 'text/plain', + 'log' => 'text/plain', + 'in' => 'text/plain', + 'dsc' => 'text/prs.lines.tag', + 'rtx' => 'text/richtext', + 'sgml' => 'text/sgml', + 'sgm' => 'text/sgml', + 'tsv' => 'text/tab-separated-values', + 't' => 'text/troff', + 'tr' => 'text/troff', + 'roff' => 'text/troff', + 'man' => 'text/troff', + 'me' => 'text/troff', + 'ms' => 'text/troff', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'fly' => 'text/vnd.fly', + 'flx' => 'text/vnd.fmi.flexstor', + '3dml' => 'text/vnd.in3d.3dml', + 'spot' => 'text/vnd.in3d.spot', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'wml' => 'text/vnd.wap.wml', + 'wmls' => 'text/vnd.wap.wmlscript', + 's' => 'text/x-asm', + 'asm' => 'text/x-asm', + 'c' => 'text/x-c', + 'cc' => 'text/x-c', + 'cxx' => 'text/x-c', + 'cpp' => 'text/x-c', + 'h' => 'text/x-c', + 'hh' => 'text/x-c', + 'dic' => 'text/x-c', + 'f' => 'text/x-fortran', + 'for' => 'text/x-fortran', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'p' => 'text/x-pascal', + 'pas' => 'text/x-pascal', + 'java' => 'text/x-java-source', + 'etx' => 'text/x-setext', + 'uu' => 'text/x-uuencode', + 'vcs' => 'text/x-vcalendar', + 'vcf' => 'text/x-vcard', + '3gp' => 'video/3gpp', + '3g2' => 'video/3gpp2', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'jpgv' => 'video/jpeg', + 'jpm' => 'video/jpm', + 'jpgm' => 'video/jpm', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mp4' => 'video/mp4', + 'mp4v' => 'video/mp4', + 'mpg4' => 'video/mp4', + 'm4v' => 'video/mp4', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'm1v' => 'video/mpeg', + 'm2v' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'fvt' => 'video/vnd.fvt', + 'mxu' => 'video/vnd.mpegurl', + 'm4u' => 'video/vnd.mpegurl', + 'viv' => 'video/vnd.vivo', + 'dv' => 'video/x-dv', + 'dif' => 'video/x-dv', + 'fli' => 'video/x-fli', + 'asx' => 'video/x-ms-asf', + 'wm' => 'video/x-ms-wm', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wvx' => 'video/x-ms-wvx', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'ice' => 'x-conference/x-cooltalk', ]; + /** + * Returns extension for given mime type + * + * @param string $mimeType The mime type, e.g. "video/mpeg" + * @return array|string + */ + public static function getExtension($mimeType) + { + if (is_string($mimeType) && in_array($mimeType, self::$mimeTypes, true)) { + $data = Arrays::setKeysAsValues(self::$mimeTypes, false); + + return $data[$mimeType]; + } + + return ''; + } + /** * Returns extensions for given mimes types * @@ -706,9 +726,6 @@ class MimeTypes continue; } - /* - * Extensions should be returned as upper case? - */ if ($asUpperCase) { if (is_array($extension)) { array_walk($extension, function (&$value) { @@ -726,20 +743,56 @@ class MimeTypes } /** - * Returns extension for given mime type + * Returns mime type of given file * - * @param string $mimeType The mime type, e.g. "video/mpeg" - * @return string|array + * @param string $filePath Path of the file to check + * @return string + * @throws RuntimeException */ - public static function getExtension($mimeType) + public static function getMimeType($filePath) { - if (is_string($mimeType) && in_array($mimeType, self::$mimeTypes, true)) { - $data = Arrays::setKeysAsValues(self::$mimeTypes, false); - - return $data[$mimeType]; + /* + * The file does not exist? + * Nothing to do + */ + if (!is_string($filePath) || !is_readable($filePath)) { + return ''; } - return ''; + // 1st possibility: the finfo class + if (class_exists('finfo')) { + $finfo = new finfo(); + + return $finfo->file($filePath, FILEINFO_MIME_TYPE); + } + + // 2nd possibility: the mime_content_type function + if (function_exists('mime_content_type')) { + return mime_content_type($filePath); + } + + // Oops, there is no possibility to read the mime type + $template = 'Neither \'finfo\' class nor \'mime_content_type\' function exists. There is no way to read the' + .' mime type of file \'%s\'.'; + + $message = sprintf($template, $filePath); + + throw new RuntimeException($message); + } + + /** + * Returns information whether the given file type is an image + * + * @param string $mimeType The mime type of file + * @return bool + */ + public static function isImage($mimeType) + { + if (in_array($mimeType, self::$mimeTypes, true)) { + return (bool) preg_match('|^image/.+$|', $mimeType); + } + + return false; } /** @@ -754,62 +807,4 @@ class MimeTypes return self::isImage($mimeType); } - - /** - * Returns mime type of given file - * - * @param string $filePath Path of the file to check - * @throws \RuntimeException - * @return string - */ - public static function getMimeType($filePath) - { - /* - * The file does not exist? - * Nothing to do - */ - if (!is_string($filePath) || !is_readable($filePath)) { - return ''; - } - - /* - * 1st possibility: the finfo class - */ - if (class_exists('finfo')) { - $finfo = new \finfo(); - - return $finfo->file($filePath, FILEINFO_MIME_TYPE); - } - - /* - * 2nd possibility: the mime_content_type function - */ - if (function_exists('mime_content_type')) { - return mime_content_type($filePath); - } - - /* - * Oops, there is no possibility to read the mime type - */ - $template = 'Neither \'finfo\' class nor \'mime_content_type\' function exists. There is no way to read the' - . ' mime type of file \'%s\'.'; - - $message = sprintf($template, $filePath); - throw new \RuntimeException($message); - } - - /** - * Returns information whether the given file type is an image - * - * @param string $mimeType The mime type of file - * @return bool - */ - public static function isImage($mimeType) - { - if (in_array($mimeType, self::$mimeTypes, true)) { - return (bool)preg_match('|^image/.+$|', $mimeType); - } - - return false; - } } diff --git a/src/Utilities/Miscellaneous.php b/src/Utilities/Miscellaneous.php index cdc1ef7..f04e3af 100644 --- a/src/Utilities/Miscellaneous.php +++ b/src/Utilities/Miscellaneous.php @@ -19,6 +19,285 @@ use Transliterator; */ class Miscellaneous { + /** + * Breaks long text + * + * @param string $text The text to check and break + * @param int $perLine (optional) Characters count per line. Default: 100. + * @param string $separator (optional) Separator that is placed between lines. Default: "
". + * @param string $encoding (optional) Character encoding. Used by mb_substr(). Default: "UTF-8". + * @param int $proportionalAberration (optional) Proportional aberration for chars (percent value). Default: 20. + * @return string + */ + public static function breakLongText( + $text, + $perLine = 100, + $separator = '
', + $encoding = 'utf-8', + $proportionalAberration = 20 + ) { + $effect = $text; + $textLength = mb_strlen($text); + + if (!empty($text) && $textLength > $perLine) { + /* + * The html_entity_decode() function is used here, because while operating + * on string that contains only special characters the string is divided + * incorrectly, e.g. "<<<<<" -> "<<<<&
lt;". + */ + //$text = htmlspecialchars_decode($text); + $text = html_entity_decode($text, ENT_QUOTES); + + $effect = ''; + $currentPosition = 0; + + $charsAberration = ceil($perLine * ($proportionalAberration / 100)); + $charsPerLineDefault = $perLine; + + while ($currentPosition <= $textLength) { + $insertSeparator = false; + + /* + * Looking for spaces before and after current position. It was done, because text wasn't + * broken properly and some words were breaked and placed into two lines. + */ + if ($charsAberration > 0) { + $length = $perLine + $charsAberration; + $lineWithAberration = mb_substr($text, $currentPosition, $length, $encoding); + + if (!Regex::contains($lineWithAberration, ' ')) { + $length = $perLine - $charsAberration; + $lineWithAberration = mb_substr($text, $currentPosition, $length, $encoding); + } + + if (Regex::startsWith($lineWithAberration, ' ')) { + ++$currentPosition; + $lineWithAberration = ltrim($lineWithAberration); + } + + $spacePosition = mb_strrpos($lineWithAberration, ' ', 0, $encoding); + + if (false !== $spacePosition && 0 < $spacePosition) { + /** @var int $spacePosition */ + $perLine = $spacePosition; + $insertSeparator = true; + } + } + + $charsOneLine = mb_substr($text, $currentPosition, $perLine, $encoding); + + /* + * The htmlspecialchars() function is used here, because... + * Reason and comment the same as above for html_entity_decode() function. + */ + + $effect .= htmlspecialchars($charsOneLine); + //$effect .= $charsOneLine; + + $currentPosition += $perLine; + $oneLineContainsSpace = Regex::contains($charsOneLine, ' '); + + if (($insertSeparator || !$oneLineContainsSpace) && $currentPosition <= $textLength) { + $effect .= $separator; + } + + $perLine = $charsPerLineDefault; + } + } + + return $effect; + } + + public static function calculateGreatestCommonDivisor(int $first, int $second): int + { + if (0 === $second) { + return $first; + } + + return static::calculateGreatestCommonDivisor($second, $first % $second); + } + + /** + * Converts checkbox value to boolean + * + * @param string $checkboxValue Checkbox value + * @return bool + */ + public static function checkboxValue2Boolean($checkboxValue) + { + $mapping = [ + 'on' => true, + 'off' => false, + ]; + + $clearValue = strtolower(trim($checkboxValue)); + + if (isset($mapping[$clearValue])) { + return $mapping[$clearValue]; + } + + return false; + } + + /** + * Converts checkbox value to integer + * + * @param string $checkboxValue Checkbox value + * @return int + */ + public static function checkboxValue2Integer($checkboxValue) + { + return (int) self::checkboxValue2Boolean($checkboxValue); + } + + /** + * Returns concatenated given paths + * + * The paths may be passed as: + * - an array of paths / strings + * - strings passed as following arguments + * + * Examples: + * - concatenatePaths(['path/first', 'path/second', 'path/third']); + * - concatenatePaths('path/first', 'path/second', 'path/third'); + * + * @param array|string $paths Paths co concatenate. As described above: an array of paths / strings or strings + * passed as following arguments. + * @return string + */ + public static function concatenatePaths($paths) + { + // If paths are not provided as array, get the paths from methods' arguments + if (!is_array($paths)) { + $paths = func_get_args(); + } + + // No paths provided? + // Nothing to do + if (empty($paths)) { + return ''; + } + + $concatenated = ''; + $firstWindowsBased = false; + $separator = DIRECTORY_SEPARATOR; + + foreach ($paths as $path) { + $path = trim($path); + + // Empty paths are useless + if (empty($path)) { + continue; + } + + // Does the first path is a Windows-based path? + if (Arrays::isFirstElement($paths, $path)) { + $firstWindowsBased = Regex::isWindowsBasedPath($path); + + if ($firstWindowsBased) { + $separator = '\\'; + } + } + + // Remove the starting / beginning directory's separator + $path = self::removeStartingDirectorySeparator($path, $separator); + + // Removes the ending directory's separator + $path = self::removeEndingDirectorySeparator($path, $separator); + + /* + * If OS is Windows, first part of the concatenated path should be the first passed path, + * because in Windows paths starts with drive letter, e.g. "C:", and the directory separator is not + * necessary at the beginning. + */ + if ($firstWindowsBased && empty($concatenated)) { + $concatenated = $path; + + continue; + } + + // Concatenate the paths / strings with OS-related directory separator between them (slash or backslash) + $concatenated = sprintf('%s%s%s', $concatenated, $separator, $path); + } + + return $concatenated; + } + + /** + * Adds missing the "0" characters to given number until given length is reached + * + * Example: + * - number: 201 + * - length: 6 + * - will be returned: 000201 + * + * If "before" parameter is false, zeros will be inserted after given number. If given number is longer than + * given length the number will be returned as it was given to the method. + * + * @param mixed $number Number for who the "0" characters should be inserted + * @param int $length Wanted length of final number + * @param bool $before (optional) If false, 0 characters will be inserted after given number + * @return string + */ + public static function fillMissingZeros($number, $length, $before = true) + { + /* + * It's not a number? Empty string is not a number too. + * Nothing to do + */ + if (!is_numeric($number)) { + return ''; + } + + $text = trim($number); + $textLength = mb_strlen($text); + + if ($length <= $textLength) { + return $text; + } + + for ($i = ($length - $textLength); 0 < $i; --$i) { + if ($before) { + $text = '0'.$text; + + continue; + } + + $text .= '0'; + } + + return $text; + } + + /** + * Returns the string in camel case + * + * @param string $string The string to convert e.g. this-is-eXamplE (return: thisIsExample) + * @param string $separator (optional) Separator used to find parts of the string, e.g. '-' or ',' + * @return string + */ + public static function getCamelCase($string, $separator = ' ') + { + if (empty($string)) { + return ''; + } + + $effect = ''; + $members = explode($separator, $string); + + foreach ($members as $key => $value) { + $value = mb_strtolower($value); + + if (0 === $key) { + $effect .= self::lowercaseFirst($value); + } else { + $effect .= self::uppercaseFirst($value); + } + } + + return $effect; + } + /** * Returns directory's content (names of directories and files) * @@ -27,7 +306,7 @@ class Miscellaneous * Otherwise - only content of given directory is returned. * @param int $maxFilesCount (optional) Maximum files that will be returned. If it's null, all files are * returned. - * @return array|null + * @return null|array */ public static function getDirectoryContent($directoryPath, $recursive = false, $maxFilesCount = null) { @@ -75,8 +354,8 @@ class Miscellaneous continue; } - if ($recursive && is_dir($directoryPath . $fileName)) { - $content = self::getDirectoryContent($directoryPath . $fileName, true, $maxFilesCount - $count); + if ($recursive && is_dir($directoryPath.$fileName)) { + $content = self::getDirectoryContent($directoryPath.$fileName, true, $maxFilesCount - $count); } if (null !== $content) { @@ -103,74 +382,6 @@ class Miscellaneous return $files; } - /** - * Returns information if given path it's a file's path, if the path contains file name - * - * @param string $path The path to check - * @return bool - */ - public static function isFilePath($path) - { - $info = pathinfo($path); - - return isset($info['extension']) && !empty($info['extension']); - } - - /** - * Converts checkbox value to boolean - * - * @param string $checkboxValue Checkbox value - * @return bool - */ - public static function checkboxValue2Boolean($checkboxValue) - { - $mapping = [ - 'on' => true, - 'off' => false, - ]; - - $clearValue = strtolower(trim($checkboxValue)); - - if (isset($mapping[$clearValue])) { - return $mapping[$clearValue]; - } - - return false; - } - - /** - * Converts checkbox value to integer - * - * @param string $checkboxValue Checkbox value - * @return int - */ - public static function checkboxValue2Integer($checkboxValue) - { - return (int)self::checkboxValue2Boolean($checkboxValue); - } - - /** - * Returns name of file with given extension after verification if it contains the extension - * - * @param string $fileName The file name to verify - * @param string $extension The extension to verify and include - * @return string - */ - public static function includeFileExtension($fileName, $extension) - { - $fileExtension = self::getFileExtension($fileName, true); - - /* - * File has given extension? - * Nothing to do - */ - if ($fileExtension === strtolower($extension)) { - return $fileName; - } - - return sprintf('%s.%s', $fileName, $extension); - } - /** * Returns file extension * @@ -200,67 +411,18 @@ class Miscellaneous * @param string $path A path that contains file name * @return string */ - public static function getFileNameFromPath($path) + public static function getFileNameFromPath(string $path): string { $matches = []; - $pattern = sprintf('|([^\%s.]+\.[A-Za-z0-9.]+)$|', DIRECTORY_SEPARATOR); + $pattern = Regex::getFileNamePattern(); - if ((bool)preg_match($pattern, $path, $matches)) { - return $matches[1]; + if ((bool) preg_match($pattern, $path, $matches)) { + return $matches[0]; } return ''; } - /** - * Returns unique name for file based on given original name - * - * @param string $originalFileName Original name of the file - * @param int $objectId (optional) Object ID, the ID of database's row. May be included into the - * generated / unique name. - * @return string - */ - public static function getUniqueFileName($originalFileName, $objectId = 0) - { - /* - * Get parts of the file name: - * - without extension - * - and... the extension - */ - $withoutExtension = self::getFileNameWithoutExtension($originalFileName); - $extension = self::getFileExtension($originalFileName, true); - - /* - * Let's clear name of file - * - * Attention. - * The name without extension may be cleared / urlized only to avoid incorrect name by replacing "." with "-". - */ - $withoutExtension = Urlizer::urlize($withoutExtension); - - /* - * Now I have to complete the template used to build / generate unique name - */ - $template = '%s-%s.%s'; // [file's name]-[unique key].[file's extension] - - /* - * Add some uniqueness - */ - $unique = self::getUniqueString(mt_rand()); - - /* - * Finally build and return the unique name - */ - - if ($objectId > 0) { - $template = '%s-%s-%s.%s'; // [file's name]-[unique key]-[object ID].[file's extension] - - return sprintf($template, $withoutExtension, $unique, $objectId, $extension); - } - - return sprintf($template, $withoutExtension, $unique, $extension); - } - /** * Returns file name without extension * @@ -271,7 +433,7 @@ class Miscellaneous { $matches = []; - if (is_string($fileName) && (bool)preg_match('|(.+)\.(.+)|', $fileName, $matches)) { + if (is_string($fileName) && (bool) preg_match('|(.+)\.(.+)|', $fileName, $matches)) { return $matches[1]; } @@ -279,92 +441,291 @@ class Miscellaneous } /** - * Converts value to non-negative integer (element of the set {0, 1, 2, 3, ...}) + * Returns size (of file or directory) in human readable format * - * @param mixed $value Value to convert - * @param int $negativeReplacement (optional) Replacement for negative value - * @return int + * @param int $sizeInBytes The size in bytes + * @return string */ - public static function value2NonNegativeInteger($value, $negativeReplacement = 0) + public static function getHumanReadableSize($sizeInBytes) { - $effect = (int)$value; + $units = [ + 'B', + 'KB', + 'MB', + 'GB', + 'TB', + 'PB', + ]; - if ($effect < 0) { - return $negativeReplacement; + $index = floor(log($sizeInBytes, 1024)); + $size = round($sizeInBytes / (1024 ** $index), 2); + $unit = $units[(int) $index]; + + return sprintf('%s %s', $size, $unit); + } + + /** + * Returns inverted value of color for given color + * + * @param string $color Hexadecimal value of color to invert (with or without hash), e.g. "dd244c" or "#22a5fe" + * @return string + */ + public static function getInvertedColor($color) + { + // Prepare the color for later usage + $color = trim($color); + $withHash = Regex::startsWith($color, '#'); + + /* + * Verify and get valid value of color. + * An exception will be thrown if the value is not a color. + */ + $validColor = Regex::getValidColorHexValue($color); + + // Grab color's components + $red = hexdec(substr($validColor, 0, 2)); + $green = hexdec(substr($validColor, 2, 2)); + $blue = hexdec(substr($validColor, 4, 2)); + + // Calculate inverted color's components + $redInverted = self::getValidColorComponent(255 - $red); + $greenInverted = self::getValidColorComponent(255 - $green); + $blueInverted = self::getValidColorComponent(255 - $blue); + + // Voila, here is the inverted color + $invertedColor = sprintf('%s%s%s', $redInverted, $greenInverted, $blueInverted); + + if ($withHash) { + return sprintf('#%s', $invertedColor); + } + + return $invertedColor; + } + + /** + * Returns the last element of given string divided by given separator + * + * @param string $string The string to check + * @param string $separator The separator which divides elements of string + * @return null|string + */ + public static function getLastElementOfString($string, $separator): ?string + { + $elements = self::getStringElements($string, $separator); + + if (empty($elements)) { + return null; + } + + return Arrays::getLastElement($elements); + } + + /** + * Returns new file name after adding prefix or suffix (or both of them) to the name + * + * @param string $fileName The file name + * @param string $prefix File name prefix + * @param string $suffix File name suffix + * @return string + */ + public static function getNewFileName($fileName, $prefix, $suffix) + { + $effect = $fileName; + + if (!empty($fileName) && (!empty($prefix) || !empty($suffix))) { + $name = self::getFileNameWithoutExtension($fileName); + $extension = self::getFileExtension($fileName); + + $effect = sprintf('%s%s%s.%s', $prefix, $name, $suffix, $extension); } return $effect; } /** - * Returns information if given PHP module is compiled and loaded + * Returns operating system name PHP is running on * - * @param string $phpModuleName PHP module name - * @return bool + * @return string */ - public static function isPhpModuleLoaded($phpModuleName) + public static function getOperatingSystemNameServer() { - $phpModulesArray = get_loaded_extensions(); - - return in_array($phpModuleName, $phpModulesArray, false); + return PHP_OS; + /* + * Previous version: + * return php_uname('s'); + */ } /** - * Converts given string characters to latin characters + * Returns project's root path. + * Looks for directory that contains composer.json. * - * @param string $string String to convert - * @param bool $lowerCaseHuman (optional) If is set to true, converted string is returned as lowercase and - * human-readable. Otherwise - as original. - * @param string $replacementChar (optional) Replacement character for all non-latin characters and uppercase - * letters, if 2nd argument is set to true * @return string */ - public static function toLatin($string, $lowerCaseHuman = true, $replacementChar = '-') + public static function getProjectRootPath(): string { - if (is_string($string)) { - $string = trim($string); + $projectRootPath = ''; + + $fileName = 'composer.json'; + $directoryPath = __DIR__; + + // Path of directory it's not the path of last directory? + while (DIRECTORY_SEPARATOR !== $directoryPath) { + $filePath = static::concatenatePaths($directoryPath, $fileName); + + /* + * Is here file we are looking for? + * Maybe it's a project's root path + */ + if (file_exists($filePath)) { + $projectRootPath = $directoryPath; + } + + $directoryPath = dirname($directoryPath); } - /* - * Empty value? - * Nothing to do - */ - if (empty($string)) { - return ''; - } + return $projectRootPath; + } - $converter = Transliterator::create('Latin-ASCII;'); + /** + * Returns safely value of global variable, found in one of the global arrays / variables, e.g. $_GET + * + * @param int $globalSourceType Represents the global array / variable. One of constants: INPUT_GET, INPUT_POST, + * INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV. + * @param string $variableName Name of the variable to return value + * @return mixed + */ + public static function getSafelyGlobalVariable($globalSourceType, $variableName) + { + $value = filter_input($globalSourceType, $variableName); - /* - * Oops, cannot instantiate converter - * Nothing to do - */ - if (null === $converter) { - return ''; - } + if (null === $value) { + $globalSource = null; - $converted = $converter->transliterate($string); + switch ($globalSourceType) { + case INPUT_GET: + $globalSource = $_GET; - /* - * Make the string lowercase and human-readable - */ - if ($lowerCaseHuman) { - $matches = []; - $matchCount = preg_match_all('|[A-Z]{1}[^A-Z]*|', $converted, $matches); + break; + case INPUT_POST: + $globalSource = $_POST; - if ($matchCount > 0) { - $parts = $matches[0]; - $converted = mb_strtolower(implode($replacementChar, $parts)); + break; + case INPUT_COOKIE: + $globalSource = $_COOKIE; + + break; + case INPUT_SERVER: + $globalSource = $_SERVER; + + break; + case INPUT_ENV: + $globalSource = $_ENV; + + break; + } + + if (null !== $globalSource && isset($globalSource[$variableName])) { + $value = $globalSource[$variableName]; } } - /* - * Let's replace special characters to spaces - * ...and finally spaces to $replacementChar - */ - $replaced = preg_replace('|[^a-zA-Z0-9]|', ' ', $converted); + return $value; + } - return preg_replace('| +|', $replacementChar, trim($replaced)); + /** + * Returns elements of given string divided by given separator + * + * @param string $string The string to check + * @param string $separator The separator which divides elements of string + * @return array + */ + public static function getStringElements(string $string, string $separator): array + { + if (empty($string) || empty($separator)) { + return []; + } + + $matches = []; + $pattern = sprintf('|[^\%s]+|', $separator); + $matchCount = preg_match_all($pattern, $string, $matches); + + if ($matchCount > 1) { + return $matches[0]; + } + + return []; + } + + /** + * Returns string without the last element. + * The string should contain given separator. + * + * @param string $string The string to check + * @param string $separator The separator which divides elements of string + * @return string + */ + public static function getStringWithoutLastElement($string, $separator) + { + $elements = self::getStringElements($string, $separator); + $lastKey = Arrays::getLastKey($elements); + + unset($elements[$lastKey]); + + return implode($separator, $elements); + } + + /** + * Returns type of given variable. + * If it's an object, full class name is returned. + * + * @param mixed $variable Variable who type should be returned + * @return string + */ + public static function getType($variable) + { + if (is_object($variable)) { + return Reflection::getClassName($variable); + } + + return gettype($variable); + } + + /** + * Returns unique name for file based on given original name + * + * @param string $originalFileName Original name of the file + * @param int $objectId (optional) Object ID, the ID of database's row. May be included into the + * generated / unique name. + * @return string + */ + public static function getUniqueFileName($originalFileName, $objectId = 0) + { + $withoutExtension = self::getFileNameWithoutExtension($originalFileName); + $extension = self::getFileExtension($originalFileName, true); + + /* + * Let's clear name of file + * + * Attention. + * The name without extension should be cleared to avoid incorrect name by replacing "." with "-". + */ + $withoutExtension = Urlizer::urlize($withoutExtension); + + // Now I have to complete the template used to build / generate unique name + $template = '%s-%s.%s'; // [file's name]-[unique key].[file's extension] + + // Add some uniqueness + $unique = self::getUniqueString(mt_rand()); + + // Finally build and return the unique name + if ($objectId > 0) { + $template = '%s-%s-%s.%s'; // [file's name]-[unique key]-[object ID].[file's extension] + + return sprintf($template, $withoutExtension, $unique, $objectId, $extension); + } + + return sprintf($template, $withoutExtension, $unique, $extension); } /** @@ -386,14 +747,295 @@ class Miscellaneous return $unique; } + /** + * Returns valid value of color's component (e.g. red). + * If given value is greater than 0, returns the value. Otherwise - 0. + * + * @param int $colorComponent Color's component to verify. Decimal value, e.g. 255. + * @param bool $asHexadecimal (optional) If is set to true, hexadecimal value is returned (default behaviour). + * Otherwise - decimal. + * @return int|string + */ + public static function getValidColorComponent($colorComponent, $asHexadecimal = true) + { + $colorComponent = (int) $colorComponent; + + if ($colorComponent < 0 || $colorComponent > 255) { + $colorComponent = 0; + } + + if ($asHexadecimal) { + $hexadecimal = dechex($colorComponent); + + if (1 === strlen($hexadecimal)) { + return sprintf('0%s', $hexadecimal); + } + + return $hexadecimal; + } + + return $colorComponent; + } + + /** + * Returns name of file with given extension after verification if it contains the extension + * + * @param string $fileName The file name to verify + * @param string $extension The extension to verify and include + * @return string + */ + public static function includeFileExtension($fileName, $extension) + { + $fileExtension = self::getFileExtension($fileName, true); + + /* + * File has given extension? + * Nothing to do + */ + if ($fileExtension === strtolower($extension)) { + return $fileName; + } + + return sprintf('%s.%s', $fileName, $extension); + } + + /** + * Returns information if given value is located in interval between given utmost left and right values + * + * @param float|int $value Value to verify + * @param float|int $left Left utmost value of interval + * @param float|int $right Right utmost value of interval + * @return bool + */ + public static function isBetween($value, $left, $right) + { + return $value > $left && $value < $right; + } + + /** + * Returns information if value is decimal + * + * @param mixed $value The value to check + * @return bool + */ + public static function isDecimal($value) + { + return is_scalar($value) && is_numeric($value) && floor($value) !== (float) $value; + } + + /** + * Returns information if given path it's a file's path, if the path contains file name + * + * @param string $path The path to check + * @return bool + */ + public static function isFilePath($path) + { + $info = pathinfo($path); + + return isset($info['extension']) && !empty($info['extension']); + } + + /** + * Returns information if given PHP module is compiled and loaded + * + * @param string $phpModuleName PHP module name + * @return bool + */ + public static function isPhpModuleLoaded($phpModuleName) + { + $phpModulesArray = get_loaded_extensions(); + + return in_array($phpModuleName, $phpModulesArray, false); + } + + /** + * Make a string's first character lowercase + * + * @param string $text The text to get first character lowercase + * @param null|bool $restLowercase (optional) Information that to do with rest of given string + * @return string + * + * Values of the $restLowercase argument: + * - null (default): nothing is done with the string + * - true: the rest of string is lowercased + * - false: the rest of string is uppercased + */ + public static function lowercaseFirst($text, $restLowercase = null) + { + if (empty($text)) { + return ''; + } + + $effect = $text; + + if ($restLowercase) { + $effect = mb_strtolower($effect); + } elseif (false === $restLowercase) { + $effect = mb_strtoupper($effect); + } + + return lcfirst($effect); + } + + /** + * Quotes given value with apostrophes or quotation marks + * + * @param mixed $value The value to quote + * @param bool $useApostrophe (optional) If is set to true, apostrophes are used. Otherwise - quotation marks. + * @return string + */ + public static function quoteValue($value, $useApostrophe = true) + { + if (is_string($value)) { + $quotes = '"'; + + if ($useApostrophe) { + $quotes = '\''; + } + + $value = sprintf('%s%s%s', $quotes, $value, $quotes); + } + + return $value; + } + + /** + * Removes the directory. + * If not empty, removes also contents. + * + * @param string $directoryPath Directory path + * @param bool $contentOnly (optional) If is set to true, only content of the directory is removed, not + * directory itself. Otherwise - directory is removed too (default behaviour). + * @return null|bool + */ + public static function removeDirectory($directoryPath, $contentOnly = false) + { + /* + * Directory does not exist? + * Nothing to do + */ + if (!file_exists($directoryPath)) { + return null; + } + + /* + * It's not a directory? + * Let's treat it like file + */ + if (!is_dir($directoryPath)) { + return unlink($directoryPath); + } + + foreach (scandir($directoryPath, SCANDIR_SORT_ASCENDING) as $item) { + if ('.' === $item || '..' === $item) { + continue; + } + + if (!self::removeDirectory($directoryPath.DIRECTORY_SEPARATOR.$item)) { + return false; + } + } + + // Directory should be removed too? + if (!$contentOnly) { + return rmdir($directoryPath); + } + + return true; + } + + /** + * Removes the ending directory's separator + * + * @param string $text Text that may contain a directory's separator at the end + * @param string $separator (optional) The directory's separator, e.g. "/". If is empty (not provided), system's + * separator is used. + * @return string + */ + public static function removeEndingDirectorySeparator($text, $separator = '') + { + /* + * Not a string? + * Nothing to do + */ + if (!is_string($text)) { + return ''; + } + + if (empty($separator)) { + $separator = DIRECTORY_SEPARATOR; + } + + $effect = trim($text); + + if (Regex::endsWithDirectorySeparator($effect, $separator)) { + $effect = mb_substr($effect, 0, mb_strlen($effect) - mb_strlen($separator)); + } + + return $effect; + } + + /** + * Removes marginal character (first or last) from given string + * + * @param string $string The string which should be shortened + * @param bool $last (optional) If is set to true, last element is removed (default behaviour). Otherwise - + * first. + * @return null|string + */ + public static function removeMarginalCharacter(string $string, bool $last = true): ?string + { + if (empty($string)) { + return null; + } + + if ($last) { + return substr($string, 0, -1); + } + + return substr($string, 1); + } + + /** + * Removes the starting / beginning directory's separator + * + * @param string $text Text that may contain a directory's separator at the start / beginning + * @param string $separator (optional) The directory's separator, e.g. "/". If is empty (not provided), separator + * provided by operating system will be used. + * @return string + */ + public static function removeStartingDirectorySeparator($text, $separator = '') + { + /* + * Not a string? + * Nothing to do + */ + if (!is_string($text)) { + return ''; + } + + if (empty($separator)) { + $separator = DIRECTORY_SEPARATOR; + } + + $effect = trim($text); + + if (Regex::startsWithDirectorySeparator($effect, $separator)) { + $effect = mb_substr($effect, mb_strlen($separator)); + } + + return $effect; + } + /** * Replaces part of string with other string or strings. * There is a few combination of what should be searched and with what it should be replaced. * - * @param string|array $subject The string or an array of strings to search and replace - * @param string|array $search String or pattern or array of patterns to find. It may be: string, an array + * @param array|string $subject The string or an array of strings to search and replace + * @param array|string $search String or pattern or array of patterns to find. It may be: string, an array * of strings or an array of patterns. - * @param string|array $replacement The string or an array of strings to replace. It may be: string or an array + * @param array|string $replacement The string or an array of strings to replace. It may be: string or an array * of strings. * @param bool $quoteStrings (optional) If is set to true, strings are surrounded with single quote sign * @return string @@ -454,11 +1096,11 @@ class Miscellaneous if ($quoteStrings) { if ($replacementIsString) { - $replacement = '\'' . $replacement . '\''; + $replacement = '\''.$replacement.'\''; } elseif ($replacementIsArray) { foreach ($replacement as &$item) { if (is_string($item)) { - $item = '\'' . $item . '\''; + $item = '\''.$item.'\''; } } @@ -466,9 +1108,7 @@ class Miscellaneous } } - /* - * 1st step: replace strings, simple operation with strings - */ + // 1st step: replace strings, simple operation with strings if ($bothAreStrings) { $effect = str_replace($search, $replacement, $subject); } @@ -503,15 +1143,10 @@ class Miscellaneous $effect = []; } - /* - * I have to make the subject an array... - */ $subject = Arrays::makeArray($subject); - /* - * ...and use iterate through the subjects, - * because explode() function expects strings as both arguments (1st and 2nd) - */ + // I have to iterate through the subjects, because explode() function expects strings as both arguments + // (1st and 2nd) foreach ($subject as $subSubject) { $subEffect = ''; @@ -521,9 +1156,7 @@ class Miscellaneous foreach ($exploded as $key => $item) { $subEffect .= $item; - /* - * The replacement shouldn't be included when the searched string was not found - */ + // The replacement shouldn't be included when the searched string was not found if ($explodedCount > 1 && $key < $explodedCount - 1 && isset($replacement[$key])) { $subEffect .= $replacement[$key]; } @@ -531,6 +1164,7 @@ class Miscellaneous if ($subjectIsArray) { $effect[] = $subEffect; + continue; } @@ -541,43 +1175,6 @@ class Miscellaneous return $effect; } - /** - * Returns new file name after adding prefix or suffix (or both of them) to the name - * - * @param string $fileName The file name - * @param string $prefix File name prefix - * @param string $suffix File name suffix - * @return string - */ - public static function getNewFileName($fileName, $prefix, $suffix) - { - $effect = $fileName; - - if (!empty($fileName) && (!empty($prefix) || !empty($suffix))) { - $name = self::getFileNameWithoutExtension($fileName); - $extension = self::getFileExtension($fileName); - - $effect = sprintf('%s%s%s.%s', $prefix, $name, $suffix, $extension); - } - - return $effect; - } - - /** - * Returns operating system name PHP is running on - * - * @return string - */ - public static function getOperatingSystemNameServer() - { - return PHP_OS; - - /* - * Previous version: - * return php_uname('s'); - */ - } - /** * Returns part of string preserving words * @@ -586,21 +1183,22 @@ class Miscellaneous * @param string $suffix (optional) The suffix to add at the end of string * @return string */ - public static function substringToWord($text, $maxLength, $suffix = '...') + public static function substringToWord(string $text, int $maxLength, string $suffix = '...'): string { $effect = $text; + $encoding = 'utf-8'; - $textLength = mb_strlen($text, 'utf-8'); - $suffixLength = mb_strlen($suffix, 'utf-8'); + $textLength = mb_strlen($text, $encoding); + $suffixLength = mb_strlen($suffix, $encoding); $maxLength -= $suffixLength; if ($textLength > $maxLength) { - $effect = mb_substr($text, 0, $maxLength, 'utf-8'); - $lastSpacePosition = mb_strrpos($effect, ' ', 'utf-8'); + $effect = mb_substr($text, 0, $maxLength, $encoding); + $lastSpacePosition = mb_strrpos($effect, ' ', 0, $encoding); if (false !== $lastSpacePosition) { - $effect = mb_substr($effect, 0, $lastSpacePosition, 'utf-8'); + $effect = mb_substr($effect, 0, $lastSpacePosition, $encoding); } $effect .= $suffix; @@ -610,215 +1208,84 @@ class Miscellaneous } /** - * Breaks long text + * Converts given string characters to latin characters * - * @param string $text The text to check and break - * @param int $perLine (optional) Characters count per line. Default: 100. - * @param string $separator (optional) Separator that is placed between lines. Default: "
". - * @param string $encoding (optional) Character encoding. Used by mb_substr(). Default: "UTF-8". - * @param int $proportionalAberration (optional) Proportional aberration for chars (percent value). Default: 20. + * @param string $string String to convert + * @param bool $lowerCaseHuman (optional) If is set to true, converted string is returned as lowercase and + * human-readable. Otherwise - as original. + * @param string $replacementChar (optional) Replacement character for all non-latin characters and uppercase + * letters, if 2nd argument is set to true * @return string */ - public static function breakLongText( - $text, - $perLine = 100, - $separator = '
', - $encoding = 'utf-8', - $proportionalAberration = 20 - ) { - $effect = $text; - $textLength = mb_strlen($text); - - if (!empty($text) && $textLength > $perLine) { - /* - * The html_entity_decode() function is used here, because while operating - * on string that contains only special characters the string is divided - * incorrectly, e.g. "<<<<<" -> "<<<<&
lt;". - */ - //$text = htmlspecialchars_decode($text); - $text = html_entity_decode($text, ENT_QUOTES); - - $effect = ''; - $currentPosition = 0; - - $charsAberration = ceil($perLine * ($proportionalAberration / 100)); - $charsPerLineDefault = $perLine; - - while ($currentPosition <= $textLength) { - $insertSeparator = false; - - /* - * Looking for spaces before and after current position. It was done, because text wasn't - * broken properly and some words were breaked and placed into two lines. - */ - if ($charsAberration > 0) { - $length = $perLine + $charsAberration; - $lineWithAberration = mb_substr($text, $currentPosition, $length, $encoding); - - if (!Regex::contains($lineWithAberration, ' ')) { - $length = $perLine - $charsAberration; - $lineWithAberration = mb_substr($text, $currentPosition, $length, $encoding); - } - - if (Regex::startsWith($lineWithAberration, ' ')) { - ++$currentPosition; - $lineWithAberration = ltrim($lineWithAberration); - } - - $spacePosition = mb_strrpos($lineWithAberration, ' ', 0, $encoding); - - if (false !== $spacePosition && 0 < $spacePosition) { - /* @var int $spacePosition */ - $perLine = $spacePosition; - $insertSeparator = true; - } - } - - $charsOneLine = mb_substr($text, $currentPosition, $perLine, $encoding); - - /* - * The htmlspecialchars() function is used here, because... - * Reason and comment the same as above for html_entity_decode() function. - */ - - $effect .= htmlspecialchars($charsOneLine); - //$effect .= $charsOneLine; - - $currentPosition += $perLine; - $oneLineContainsSpace = Regex::contains($charsOneLine, ' '); - - if (($insertSeparator || !$oneLineContainsSpace) && $currentPosition <= $textLength) { - $effect .= $separator; - } - - $perLine = $charsPerLineDefault; - } + public static function toLatin($string, $lowerCaseHuman = true, $replacementChar = '-') + { + if (is_string($string)) { + $string = trim($string); } - return $effect; - } - - /** - * Removes the directory. - * If not empty, removes also contents. - * - * @param string $directoryPath Directory path - * @param bool $contentOnly (optional) If is set to true, only content of the directory is removed, not - * directory itself. Otherwise - directory is removed too (default behaviour). - * @return bool|null - */ - public static function removeDirectory($directoryPath, $contentOnly = false) - { /* - * Directory does not exist? + * Empty value? * Nothing to do */ - if (!file_exists($directoryPath)) { - return null; - } - - /* - * It's not a directory? - * Let's treat it like file - */ - if (!is_dir($directoryPath)) { - return unlink($directoryPath); - } - - foreach (scandir($directoryPath, SCANDIR_SORT_ASCENDING) as $item) { - if ('.' === $item || '..' === $item) { - continue; - } - - if (!self::removeDirectory($directoryPath . DIRECTORY_SEPARATOR . $item)) { - return false; - } - } - - /* - * Directory should be removed too? - */ - if (!$contentOnly) { - return rmdir($directoryPath); - } - - return true; - } - - /** - * Returns information if value is decimal - * - * @param mixed $value The value to check - * @return bool - */ - public static function isDecimal($value) - { - return is_scalar($value) && floor($value) !== (float)$value; - } - - /** - * Returns the string in camel case - * - * @param string $string The string to convert e.g. this-is-eXamplE (return: thisIsExample) - * @param string $separator (optional) Separator used to find parts of the string, e.g. '-' or ',' - * @return string - */ - public static function getCamelCase($string, $separator = ' ') - { if (empty($string)) { return ''; } - $effect = ''; - $members = explode($separator, $string); + $converter = Transliterator::create('Latin-ASCII;'); - foreach ($members as $key => $value) { - $value = mb_strtolower($value); - - if (0 === $key) { - $effect .= self::lowercaseFirst($value); - } else { - $effect .= self::uppercaseFirst($value); - } - } - - return $effect; - } - - /** - * Make a string's first character lowercase - * - * @param string $text The text to get first character lowercase - * @param bool|null $restLowercase (optional) Information that to do with rest of given string - * @return string - * - * Values of the $restLowercase argument: - * - null (default): nothing is done with the string - * - true: the rest of string is lowercased - * - false: the rest of string is uppercased - */ - public static function lowercaseFirst($text, $restLowercase = null) - { - if (empty($text)) { + /* + * Oops, cannot instantiate converter + * Nothing to do + */ + if (null === $converter) { return ''; } - $effect = $text; + $converted = $converter->transliterate($string); - if ($restLowercase) { - $effect = mb_strtolower($effect); - } elseif (false === $restLowercase) { - $effect = mb_strtoupper($effect); + // Make the string lowercase and human-readable + if ($lowerCaseHuman) { + $matches = []; + $matchCount = preg_match_all('|[A-Z]{1}[^A-Z]*|', $converted, $matches); + + if ($matchCount > 0) { + $parts = $matches[0]; + $converted = mb_strtolower(implode($replacementChar, $parts)); + } } - return lcfirst($effect); + /* + * Let's replace special characters to spaces + * ...and finally spaces to $replacementChar + */ + $replaced = preg_replace('|[^a-zA-Z0-9]|', ' ', $converted); + + return preg_replace('| +|', $replacementChar, trim($replaced)); + } + + /** + * Returns smartly trimmed string. + * If the string is empty, contains only spaces, e.g. " ", nothing is done and the original string is returned. + * + * @param string $string The string to trim + * @return string + */ + public static function trimSmart($string) + { + $trimmed = trim($string); + + if (empty($trimmed)) { + return $string; + } + + return $trimmed; } /** * Make a string's first character uppercase * * @param string $text The text to get uppercase - * @param bool|null $restLowercase (optional) Information that to do with rest of given string + * @param null|bool $restLowercase (optional) Information that to do with rest of given string * @return string * * Values of the $restLowercase argument: @@ -844,507 +1311,20 @@ class Miscellaneous } /** - * Quotes given value with apostrophes or quotation marks + * Converts value to non-negative integer (element of the set {0, 1, 2, 3, ...}) * - * @param mixed $value The value to quote - * @param bool $useApostrophe (optional) If is set to true, apostrophes are used. Otherwise - quotation marks. - * @return string + * @param mixed $value Value to convert + * @param int $negativeReplacement (optional) Replacement for negative value + * @return int */ - public static function quoteValue($value, $useApostrophe = true) + public static function value2NonNegativeInteger($value, $negativeReplacement = 0) { - if (is_string($value)) { - $quotes = '"'; + $effect = (int) $value; - if ($useApostrophe) { - $quotes = '\''; - } - - $value = sprintf('%s%s%s', $quotes, $value, $quotes); - } - - return $value; - } - - /** - * Returns size (of file or directory) in human readable format - * - * @param int $sizeInBytes The size in bytes - * @return string - */ - public static function getHumanReadableSize($sizeInBytes) - { - $units = [ - 'B', - 'KB', - 'MB', - 'GB', - 'TB', - 'PB', - ]; - - $index = floor(log($sizeInBytes, 1024)); - $size = round($sizeInBytes / (1024 ** $index), 2); - $unit = $units[(int)$index]; - - return sprintf('%s %s', $size, $unit); - } - - /** - * Returns string without the last element. - * The string should contain given separator. - * - * @param string $string The string to check - * @param string $separator The separator which divides elements of string - * @return string - */ - public static function getStringWithoutLastElement($string, $separator) - { - $elements = self::getStringElements($string, $separator); - $lastKey = Arrays::getLastKey($elements); - - unset($elements[$lastKey]); - - return implode($separator, $elements); - } - - /** - * Returns elements of given string divided by given separator - * - * @param string $string The string to check - * @param string $separator The separator which divides elements of string - * @return array - */ - public static function getStringElements($string, $separator) - { - $matches = []; - $pattern = sprintf('|[^\%s]+|', $separator); - $matchCount = preg_match_all($pattern, $string, $matches); - - if ($matchCount > 1) { - return $matches[0]; - } - - return []; - } - - /** - * Returns the last element of given string divided by given separator - * - * @param string $string The string to check - * @param string $separator The separator which divides elements of string - * @return string|null - */ - public static function getLastElementOfString($string, $separator) - { - $elements = self::getStringElements($string, $separator); - - /* - * No elements? - * Nothing to do - */ - if (empty($elements)) { - return null; - } - - $element = Arrays::getLastElement($elements); - - return trim($element); - } - - /** - * Returns smartly trimmed string. - * If the string is empty, contains only spaces, e.g. " ", nothing is done and the original string is returned. - * - * @param string $string The string to trim - * @return string - */ - public static function trimSmart($string) - { - $trimmed = trim($string); - - if (empty($trimmed)) { - return $string; - } - - return $trimmed; - } - - /** - * Returns concatenated given paths - * - * The paths may be passed as: - * - an array of paths / strings - * - strings passed as following arguments - * - * Examples: - * - concatenatePaths(['path/first', 'path/second', 'path/third']); - * - concatenatePaths('path/first', 'path/second', 'path/third'); - * - * @param string|array $paths Paths co concatenate. As described above: an array of paths / strings or strings - * passed as following arguments. - * @return string - */ - public static function concatenatePaths($paths) - { - /* - * If paths are not provided as array, get the paths from methods' arguments - */ - if (!is_array($paths)) { - $paths = func_get_args(); - } - - /* - * No paths provided? - * Nothing to do - */ - if (empty($paths)) { - return ''; - } - - /* - * Some useful variables - */ - $concatenated = ''; - $firstWindowsBased = false; - $separator = DIRECTORY_SEPARATOR; - - foreach ($paths as $path) { - $path = trim($path); - - /* - * Empty paths are useless - */ - if (empty($path)) { - continue; - } - - /* - * Does the first path is a Windows-based path? - */ - if (Arrays::isFirstElement($paths, $path)) { - $firstWindowsBased = Regex::isWindowsBasedPath($path); - - if ($firstWindowsBased) { - $separator = '\\'; - } - } - - /* - * Remove the starting / beginning directory's separator - */ - $path = self::removeStartingDirectorySeparator($path, $separator); - - /* - * Removes the ending directory's separator - */ - $path = self::removeEndingDirectorySeparator($path, $separator); - - /* - * If OS is Windows, first part of the concatenated path should be the first passed path, - * because in Windows paths starts with drive letter, e.g. "C:", and the directory separator is not - * necessary at the beginning. - */ - if ($firstWindowsBased && empty($concatenated)) { - $concatenated = $path; - continue; - } - - /* - * Concatenate the paths / strings with OS-related directory separator between them (slash or backslash) - */ - $concatenated = sprintf('%s%s%s', $concatenated, $separator, $path); - } - - return $concatenated; - } - - /** - * Removes the starting / beginning directory's separator - * - * @param string $text Text that may contain a directory's separator at the start / beginning - * @param string $separator (optional) The directory's separator, e.g. "/". If is empty (not provided), separator - * provided by operating system will be used. - * @return string - */ - public static function removeStartingDirectorySeparator($text, $separator = '') - { - /* - * Not a string? - * Nothing to do - */ - if (!is_string($text)) { - return ''; - } - - if (empty($separator)) { - $separator = DIRECTORY_SEPARATOR; - } - - $effect = trim($text); - - if (Regex::startsWithDirectorySeparator($effect, $separator)) { - $effect = mb_substr($effect, mb_strlen($separator)); + if ($effect < 0) { + return $negativeReplacement; } return $effect; } - - /** - * Removes the ending directory's separator - * - * @param string $text Text that may contain a directory's separator at the end - * @param string $separator (optional) The directory's separator, e.g. "/". If is empty (not provided), system's - * separator is used. - * @return string - */ - public static function removeEndingDirectorySeparator($text, $separator = '') - { - /* - * Not a string? - * Nothing to do - */ - if (!is_string($text)) { - return ''; - } - - if (empty($separator)) { - $separator = DIRECTORY_SEPARATOR; - } - - $effect = trim($text); - - if (Regex::endsWithDirectorySeparator($effect, $separator)) { - $effect = mb_substr($effect, 0, mb_strlen($effect) - mb_strlen($separator)); - } - - return $effect; - } - - /** - * Returns safely value of global variable, found in one of the global arrays / variables, e.g. $_GET - * - * @param int $globalSourceType Represents the global array / variable. One of constants: INPUT_GET, INPUT_POST, - * INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV. - * @param string $variableName Name of the variable to return value - * @return mixed - */ - public static function getSafelyGlobalVariable($globalSourceType, $variableName) - { - $value = filter_input($globalSourceType, $variableName); - - if (null === $value) { - $globalSource = null; - - switch ($globalSourceType) { - case INPUT_GET: - $globalSource = $_GET; - break; - - case INPUT_POST: - $globalSource = $_POST; - break; - - case INPUT_COOKIE: - $globalSource = $_COOKIE; - break; - - case INPUT_SERVER: - $globalSource = $_SERVER; - break; - - case INPUT_ENV: - $globalSource = $_ENV; - break; - } - - if (null !== $globalSource && isset($globalSource[$variableName])) { - $value = $globalSource[$variableName]; - } - } - - return $value; - } - - /** - * Adds missing the "0" characters to given number until given length is reached - * - * Example: - * - number: 201 - * - length: 6 - * - will be returned: 000201 - * - * If "before" parameter is false, zeros will be inserted after given number. If given number is longer than - * given length the number will be returned as it was given to the method. - * - * @param mixed $number Number for who the "0" characters should be inserted - * @param int $length Wanted length of final number - * @param bool $before (optional) If false, 0 characters will be inserted after given number - * @return string - */ - public static function fillMissingZeros($number, $length, $before = true) - { - /* - * It's not a number? Empty string is not a number too. - * Nothing to do - */ - if (!is_numeric($number)) { - return ''; - } - - $text = trim($number); - $textLength = mb_strlen($text); - - if ($length <= $textLength) { - return $text; - } - - for ($i = ($length - $textLength); 0 < $i; --$i) { - if ($before) { - $text = '0' . $text; - continue; - } - - $text .= '0'; - } - - return $text; - } - - /** - * Returns information if given value is located in interval between given utmost left and right values - * - * @param int|float $value Value to verify - * @param int|float $left Left utmost value of interval - * @param int|float $right Right utmost value of interval - * @return bool - */ - public static function isBetween($value, $left, $right) - { - return $value > $left && $value < $right; - } - - /** - * Returns type of given variable. - * If it's an object, full class name is returned. - * - * @param mixed $variable Variable who type should be returned - * @return string - */ - public static function getType($variable) - { - if (is_object($variable)) { - return Reflection::getClassName($variable); - } - - return gettype($variable); - } - - /** - * Returns valid value of color's component (e.g. red). - * If given value is greater than 0, returns the value. Otherwise - 0. - * - * @param int $colorComponent Color's component to verify. Decimal value, e.g. 255. - * @param bool $asHexadecimal (optional) If is set to true, hexadecimal value is returned (default behaviour). - * Otherwise - decimal. - * @return int|string - */ - public static function getValidColorComponent($colorComponent, $asHexadecimal = true) - { - $colorComponent = (int)$colorComponent; - - if ($colorComponent < 0 || $colorComponent > 255) { - $colorComponent = 0; - } - - if ($asHexadecimal) { - $hexadecimal = dechex($colorComponent); - - if (1 === strlen($hexadecimal)) { - return sprintf('0%s', $hexadecimal); - } - - return $hexadecimal; - } - - return $colorComponent; - } - - /** - * Returns inverted value of color for given color - * - * @param string $color Hexadecimal value of color to invert (with or without hash), e.g. "dd244c" or "#22a5fe" - * @return string - */ - public static function getInvertedColor($color) - { - /* - * Prepare the color for later usage - */ - $color = trim($color); - $withHash = Regex::startsWith($color, '#'); - - /* - * Verify and get valid value of color. - * An exception will be thrown if the value is not a color. - */ - $validColor = Regex::getValidColorHexValue($color); - - /* - * Grab color's components - */ - $red = hexdec(substr($validColor, 0, 2)); - $green = hexdec(substr($validColor, 2, 2)); - $blue = hexdec(substr($validColor, 4, 2)); - - /* - * Calculate inverted color's components - */ - $redInverted = self::getValidColorComponent(255 - $red); - $greenInverted = self::getValidColorComponent(255 - $green); - $blueInverted = self::getValidColorComponent(255 - $blue); - - /* - * Voila, here is the inverted color - */ - $invertedColor = sprintf('%s%s%s', $redInverted, $greenInverted, $blueInverted); - - if ($withHash) { - return sprintf('#%s', $invertedColor); - } - - return $invertedColor; - } - - /** - * Returns project's root path. - * Looks for directory that contains composer.json. - * - * @return string - */ - public static function getProjectRootPath() - { - $projectRootPath = ''; - - $fileName = 'composer.json'; - $directoryPath = __DIR__; - - /* - * Path of directory it's not the path of last directory? - */ - while (DIRECTORY_SEPARATOR !== $directoryPath) { - $filePath = static::concatenatePaths($directoryPath, $fileName); - - /* - * Is here file we are looking for? - * Maybe it's a project's root path - */ - if (file_exists($filePath)) { - $projectRootPath = $directoryPath; - } - - $directoryPath = dirname($directoryPath); - } - - return $projectRootPath; - } } diff --git a/src/Utilities/QueryBuilderUtility.php b/src/Utilities/QueryBuilderUtility.php index 0e87ed1..392525b 100644 --- a/src/Utilities/QueryBuilderUtility.php +++ b/src/Utilities/QueryBuilderUtility.php @@ -23,25 +23,68 @@ use Doctrine\ORM\QueryBuilder; class QueryBuilderUtility { /** - * Returns root alias of given query builder. - * If null is returned, alias was not found. + * Adds given parameters to given query builder. + * Attention. Existing parameters will be overridden. * - * @param QueryBuilder $queryBuilder The query builder to retrieve root alias - * @return null|string + * @param QueryBuilder $queryBuilder The query builder + * @param array|ArrayCollection $parameters Parameters to add. Collection of Doctrine\ORM\Query\Parameter + * instances or an array with key-value pairs. + * @return QueryBuilder */ - public static function getRootAlias(QueryBuilder $queryBuilder) + public static function addParameters(QueryBuilder $queryBuilder, $parameters) { - $aliases = $queryBuilder->getRootAliases(); - /* - * No aliases? + * No parameters? * Nothing to do */ - if (empty($aliases)) { - return null; + if (empty($parameters)) { + return $queryBuilder; } - return Arrays::getFirstElement($aliases); + foreach ($parameters as $key => $parameter) { + $name = $key; + $value = $parameter; + + if ($parameter instanceof Parameter) { + $name = $parameter->getName(); + $value = $parameter->getValue(); + } + + $queryBuilder->setParameter($name, $value); + } + + return $queryBuilder; + } + + /** + * Deletes given entities + * + * @param EntityManager $entityManager The entity manager + * @param array|ArrayCollection $entities The entities to delete + * @param bool $flushDeleted (optional) If is set to true, flushes the deleted objects (default + * behaviour). Otherwise - not. + * @return bool + */ + public static function deleteEntities(EntityManager $entityManager, $entities, $flushDeleted = true) + { + /* + * No entities provided? + * Nothing to do + */ + if (empty($entities)) { + return false; + } + + foreach ($entities as $entity) { + $entityManager->remove($entity); + } + + // The deleted objects should be flushed? + if ($flushDeleted) { + $entityManager->flush(); + } + + return true; } /** @@ -70,7 +113,7 @@ class QueryBuilderUtility $pattern = sprintf($patternTemplate, $property); foreach ($joins as $joinExpressions) { - /* @var $expression Join */ + /** @var Join $expression */ foreach ($joinExpressions as $expression) { $joinedProperty = $expression->getJoin(); @@ -83,6 +126,28 @@ class QueryBuilderUtility return null; } + /** + * Returns root alias of given query builder. + * If null is returned, alias was not found. + * + * @param QueryBuilder $queryBuilder The query builder to retrieve root alias + * @return null|string + */ + public static function getRootAlias(QueryBuilder $queryBuilder) + { + $aliases = $queryBuilder->getRootAliases(); + + /* + * No aliases? + * Nothing to do + */ + if (empty($aliases)) { + return null; + } + + return Arrays::getFirstElement($aliases); + } + /** * Sets the WHERE criteria in given query builder * @@ -90,7 +155,7 @@ class QueryBuilderUtility * @param array $criteria (optional) The criteria used in WHERE clause. It may simple array with pairs * key-value or an array of arrays where second element of sub-array is the * comparison operator. Example below. - * @param string|null $alias (optional) Alias used in the query + * @param null|string $alias (optional) Alias used in the query * @return QueryBuilder * * Example of the $criteria argument: @@ -149,71 +214,4 @@ class QueryBuilderUtility return $queryBuilder; } - - /** - * Deletes given entities - * - * @param EntityManager $entityManager The entity manager - * @param array|ArrayCollection $entities The entities to delete - * @param bool $flushDeleted (optional) If is set to true, flushes the deleted objects (default - * behaviour). Otherwise - not. - * @return bool - */ - public static function deleteEntities(EntityManager $entityManager, $entities, $flushDeleted = true) - { - /* - * No entities provided? - * Nothing to do - */ - if (empty($entities)) { - return false; - } - - foreach ($entities as $entity) { - $entityManager->remove($entity); - } - - /* - * The deleted objects should be flushed? - */ - if ($flushDeleted) { - $entityManager->flush(); - } - - return true; - } - - /** - * Adds given parameters to given query builder. - * Attention. Existing parameters will be overridden. - * - * @param QueryBuilder $queryBuilder The query builder - * @param array|ArrayCollection $parameters Parameters to add. Collection of Doctrine\ORM\Query\Parameter - * instances or an array with key-value pairs. - * @return QueryBuilder - */ - public static function addParameters(QueryBuilder $queryBuilder, $parameters) - { - /* - * No parameters? - * Nothing to do - */ - if (empty($parameters)) { - return $queryBuilder; - } - - foreach ($parameters as $key => $parameter) { - $name = $key; - $value = $parameter; - - if ($parameter instanceof Parameter) { - $name = $parameter->getName(); - $value = $parameter->getValue(); - } - - $queryBuilder->setParameter($name, $value); - } - - return $queryBuilder; - } } diff --git a/src/Utilities/Reflection.php b/src/Utilities/Reflection.php index c3fbafe..be86a2a 100644 --- a/src/Utilities/Reflection.php +++ b/src/Utilities/Reflection.php @@ -8,13 +8,18 @@ namespace Meritoo\Common\Utilities; -use Doctrine\Common\Util\ClassUtils; -use Doctrine\Common\Util\Inflector; -use Meritoo\Common\Collection\Collection; +use Doctrine\Inflector\InflectorFactory; +use Doctrine\Persistence\Proxy; +use Meritoo\Common\Contract\Collection\CollectionInterface; use Meritoo\Common\Exception\Reflection\CannotResolveClassNameException; use Meritoo\Common\Exception\Reflection\MissingChildClassesException; use Meritoo\Common\Exception\Reflection\NotExistingPropertyException; use Meritoo\Common\Exception\Reflection\TooManyChildClassesException; +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use ReflectionObject; +use ReflectionProperty; /** * Useful reflection methods @@ -25,289 +30,56 @@ use Meritoo\Common\Exception\Reflection\TooManyChildClassesException; class Reflection { /** - * Returns names of methods for given class / object + * Returns child classes of given class. + * It's an array of namespaces of the child classes or null (if given class has not child classes). * - * @param object|string $class The object or name of object's class - * @param bool $withoutInheritance (optional) If is set to true, only methods for given class are returned. - * Otherwise - all methods, with inherited methods too. - * @return array + * @param array|object|string $class Class who child classes should be returned. An array of objects, strings, + * object or string. + * @return null|array + * @throws CannotResolveClassNameException */ - public static function getMethods($class, $withoutInheritance = false) + public static function getChildClasses($class): ?array { - $effect = []; + $allClasses = get_declared_classes(); - $reflection = new \ReflectionClass($class); - $methods = $reflection->getMethods(); - - if (!empty($methods)) { - $className = self::getClassName($class); - - foreach ($methods as $method) { - if ($method instanceof \ReflectionMethod) { - if ($withoutInheritance && $className !== $method->class) { - continue; - } - - $effect[] = $method->name; - } - } - } - - return $effect; - } - - /** - * Returns constants of given class / object - * - * @param object|string $class The object or name of object's class - * @return array - */ - public static function getConstants($class) - { - $reflection = new \ReflectionClass($class); - - return $reflection->getConstants(); - } - - /** - * Returns maximum integer value of constant of given class / object. - * Constants whose values are integers are considered only. - * - * @param object|string $class The object or name of object's class - * @return int|null - */ - public static function getMaxNumberConstant($class) - { - $constants = self::getConstants($class); - - if (empty($constants)) { + /* + * No classes? + * Nothing to do + */ + if (empty($allClasses)) { return null; } - $maxNumber = 0; + $className = self::getClassName($class); - foreach ($constants as $constant) { - if (is_numeric($constant) && $constant > $maxNumber) { - $maxNumber = $constant; - } + // Oops, cannot resolve class + if (null === $className) { + throw CannotResolveClassNameException::create(''); } - return $maxNumber; - } + $childClasses = []; - /** - * Returns information if given class / object has given method - * - * @param object|string $class The object or name of object's class - * @param string $method Name of the method to find - * @return bool - */ - public static function hasMethod($class, $method) - { - $reflection = new \ReflectionClass($class); - - return $reflection->hasMethod($method); - } - - /** - * Returns information if given class / object has given property - * - * @param object|string $class The object or name of object's class - * @param string $property Name of the property to find - * @return bool - */ - public static function hasProperty($class, $property) - { - $reflection = new \ReflectionClass($class); - - return $reflection->hasProperty($property); - } - - /** - * Returns information if given class / object has given constant - * - * @param object|string $class The object or name of object's class - * @param string $constant Name of the constant to find - * @return bool - */ - public static function hasConstant($class, $constant) - { - $reflection = new \ReflectionClass($class); - - return $reflection->hasConstant($constant); - } - - /** - * Returns value of given constant - * - * @param object|string $class The object or name of object's class - * @param string $constant Name of the constant that contains a value - * @return mixed - */ - public static function getConstantValue($class, $constant) - { - $reflection = new \ReflectionClass($class); - - if (self::hasConstant($class, $constant)) { - return $reflection->getConstant($constant); - } - - return null; - } - - /** - * Returns value of given property. - * Looks for proper getter for the property. - * - * @param mixed $object Object that should contains given property - * @param string $property Name of the property that contains a value. It may be also multiple properties - * dot-separated, e.g. "invoice.user.email". - * @param bool $force (optional) If is set to true, try to retrieve value even if the object doesn't have - * property. Otherwise - not. - * @return mixed - */ - public static function getPropertyValue($object, $property, $force = false) - { - $value = null; - - /* - * Property is a dot-separated string? - * Let's find all values of the chain, of the dot-separated properties - */ - if (Regex::contains($property, '.')) { - $exploded = explode('.', $property); - - $property = $exploded[0]; - $object = self::getPropertyValue($object, $property, $force); - - /* - * Value of processed property from the chain is not null? - * Let's dig more and get proper value - * - * Required to avoid bug: - * \ReflectionObject::__construct() expects parameter 1 to be object, null given - * (...) - * 4. at \ReflectionObject->__construct (null) - * 5. at Reflection ::getPropertyValue (null, 'name', true) - * 6. at ListService->getItemValue (object(Deal), 'project.name', '0') - * - * while using "project.name" as property - $project has $name property ($project exists in the Deal class) - * and the $project equals null - * - * Meritoo - * 2016-11-07 - */ - if (null !== $object) { - unset($exploded[0]); - - $property = implode('.', $exploded); - $value = self::getPropertyValue($object, $property, $force); - } - } else { - $className = self::getClassName($object); - $reflectionProperty = null; - - /* - * 1st try: - * Use \ReflectionObject class - */ - try { - $reflectionProperty = new \ReflectionProperty($className, $property); - $value = $reflectionProperty->getValue($object); - } catch (\ReflectionException $exception) { + foreach ($allClasses as $oneClass) { + if (self::isChildOfClass($oneClass, $className)) { /* - * 2nd try: - * Look for the get / has / is methods + * Attention. I have to use static::getRealClass() method to avoid problem with the proxy / cache + * classes. Example: + * - My\ExtraBundle\Entity\MyEntity + * - Proxies\__CG__\My\ExtraBundle\Entity\MyEntity + * + * It's actually the same class, so I have to skip it. */ - $class = new \ReflectionObject($object); - $valueFound = false; + $realClass = static::getRealClass($oneClass); - if ($force || $class->hasProperty($property)) { - $property = Inflector::classify($property); - - $getterPrefixes = [ - 'get', - 'has', - 'is', - ]; - - foreach ($getterPrefixes as $prefix) { - $getterName = sprintf('%s%s', $prefix, $property); - - if ($class->hasMethod($getterName)) { - $method = new \ReflectionMethod($object, $getterName); - - /* - * Getter is not accessible publicly? - * I have to skip it, to avoid an error like this: - * - * Call to protected method My\ExtraClass::getExtraProperty() from context 'My\ExtraClass' - */ - if ($method->isProtected() || $method->isPrivate()) { - continue; - } - - $value = $object->{$getterName}(); - $valueFound = true; - break; - } - } + if (in_array($realClass, $childClasses, true)) { + continue; } - if (!$valueFound && null !== $reflectionProperty) { - /* - * Oops, value of the property is still unknown - * - * 3rd try: - * Let's modify accessibility of the property and try again to get value - */ - $reflectionProperty->setAccessible(true); - $value = $reflectionProperty->getValue($object); - $reflectionProperty->setAccessible(false); - } + $childClasses[] = $realClass; } } - return $value; - } - - /** - * Returns values of given property for given objects. - * Looks for proper getter for the property. - * - * @param Collection|object|array $objects The objects that should contain given property. It may be also one - * object. - * @param string $property Name of the property that contains a value - * @param bool $force (optional) If is set to true, try to retrieve value even if the - * object does not have property. Otherwise - not. - * @return array - */ - public static function getPropertyValues($objects, $property, $force = false) - { - /* - * No objects? - * Nothing to do - */ - if (empty($objects)) { - return []; - } - - if ($objects instanceof Collection) { - $objects = $objects->toArray(); - } - - $values = []; - $objects = Arrays::makeArray($objects); - - foreach ($objects as $entity) { - $value = self::getPropertyValue($entity, $property, $force); - - if (null !== $value) { - $values[] = $value; - } - } - - return $values; + return $childClasses; } /** @@ -316,9 +88,9 @@ class Reflection * @param array|object|string $source An array of objects, namespaces, object or namespace * @param bool $withoutNamespace (optional) If is set to true, namespace is omitted. Otherwise - * not, full name of class is returned, with namespace. - * @return string|null + * @return null|string */ - public static function getClassName($source, $withoutNamespace = false) + public static function getClassName($source, bool $withoutNamespace = false): ?string { /* * First argument is not proper source of class? @@ -338,9 +110,7 @@ class Reflection $source = Arrays::getFirstElement($source); } - /* - * Let's prepare name of class - */ + // Let's prepare name of class if (is_object($source)) { $name = get_class($source); } elseif (is_string($source) && (class_exists($source) || trait_exists($source))) { @@ -369,7 +139,7 @@ class Reflection return $name; } - return ClassUtils::getRealClass($name); + return static::getRealClass($name); } /** @@ -378,7 +148,7 @@ class Reflection * @param array|object|string $source An array of objects, namespaces, object or namespace * @return string */ - public static function getClassNamespace($source) + public static function getClassNamespace($source): string { $fullClassName = self::getClassName($source); @@ -396,145 +166,92 @@ class Reflection } /** - * Returns information if given interface is implemented by given class / object + * Returns value of given constant * - * @param array|object|string $source An array of objects, namespaces, object or namespace - * @param string $interface The interface that should be implemented - * @return bool + * @param object|string $class The object or name of object's class + * @param string $constant Name of the constant that contains a value + * @return mixed */ - public static function isInterfaceImplemented($source, $interface) + public static function getConstantValue($class, string $constant) { - $className = self::getClassName($source); - $interfaces = class_implements($className); + $reflection = new ReflectionClass($class); - return in_array($interface, $interfaces, true); - } - - /** - * Returns information if given child class is a subclass of given parent class - * - * @param array|object|string $childClass The child class. An array of objects, namespaces, object or namespace. - * @param array|object|string $parentClass The parent class. An array of objects, namespaces, object or namespace. - * @return bool - */ - public static function isChildOfClass($childClass, $parentClass) - { - $childClassName = self::getClassName($childClass); - $parentClassName = self::getClassName($parentClass); - - $parents = class_parents($childClassName); - - if (is_array($parents) && 0 < count($parents)) { - return in_array($parentClassName, $parents, true); + if (self::hasConstant($class, $constant)) { + return $reflection->getConstant($constant); } - return false; + return null; } /** - * Returns given object properties + * Returns constants of given class / object * - * @param array|object|string $source An array of objects, namespaces, object or namespace - * @param int $filter (optional) Filter of properties. Uses \ReflectionProperty class - * constants. By default all properties are returned. - * @param bool $includeParents (optional) If is set to true, properties of parent classes are - * included (recursively). Otherwise - not. - * @return array|\ReflectionProperty + * @param object|string $class The object or name of object's class + * @return array */ - public static function getProperties($source, $filter = null, $includeParents = false) + public static function getConstants($class): array { - $className = self::getClassName($source); - $reflection = new \ReflectionClass($className); + $reflection = new ReflectionClass($class); - if (null === $filter) { - $filter = \ReflectionProperty::IS_PRIVATE - + \ReflectionProperty::IS_PROTECTED - + \ReflectionProperty::IS_PUBLIC - + \ReflectionProperty::IS_STATIC; - } - - $properties = $reflection->getProperties($filter); - $parentProperties = []; - - if ($includeParents) { - $parent = self::getParentClass($source); - - if (false !== $parent) { - $parentClass = $parent->getName(); - $parentProperties = self::getProperties($parentClass, $filter, $includeParents); - } - } - - return array_merge($properties, $parentProperties); + return $reflection->getConstants(); } /** - * Returns a parent class or false if there is no parent class + * Returns maximum integer value of constant of given class / object. + * Constants whose values are integers are considered only. * - * @param array|object|string $source An array of objects, namespaces, object or namespace - * @return \ReflectionClass|bool + * @param object|string $class The object or name of object's class + * @return null|int */ - public static function getParentClass($source) + public static function getMaxNumberConstant($class): ?int { - $className = self::getClassName($source); - $reflection = new \ReflectionClass($className); + $constants = self::getConstants($class); - return $reflection->getParentClass(); - } - - /** - * Returns child classes of given class. - * It's an array of namespaces of the child classes or null (if given class has not child classes). - * - * @param array|object|string $class Class who child classes should be returned. An array of objects, strings, - * object or string. - * @throws CannotResolveClassNameException - * @return array|null - */ - public static function getChildClasses($class) - { - $allClasses = get_declared_classes(); - - /* - * No classes? - * Nothing to do - */ - if (empty($allClasses)) { + if (empty($constants)) { return null; } - $className = self::getClassName($class); + $maxNumber = 0; - /* - * Oops, cannot resolve class - */ - if (null === $className) { - throw CannotResolveClassNameException::create($class); - } - - $childClasses = []; - - foreach ($allClasses as $oneClass) { - if (self::isChildOfClass($oneClass, $className)) { - /* - * Attention. I have to use ClassUtils::getRealClass() method to avoid problem with the proxy / cache - * classes. Example: - * - My\ExtraBundle\Entity\MyEntity - * - Proxies\__CG__\My\ExtraBundle\Entity\MyEntity - * - * It's actually the same class, so I have to skip it. - */ - $realClass = ClassUtils::getRealClass($oneClass); - - if (in_array($realClass, $childClasses, true)) { - continue; - } - - $childClasses[] = $realClass; + foreach ($constants as $constant) { + if (is_numeric($constant) && $constant > $maxNumber) { + $maxNumber = $constant; } } - return $childClasses; + return $maxNumber; + } + + /** + * Returns names of methods for given class / object + * + * @param object|string $class The object or name of object's class + * @param bool $withoutInheritance (optional) If is set to true, only methods for given class are returned. + * Otherwise - all methods, with inherited methods too. + * @return array + */ + public static function getMethods($class, bool $withoutInheritance = false): array + { + $effect = []; + + $reflection = new ReflectionClass($class); + $methods = $reflection->getMethods(); + + if (!empty($methods)) { + $className = self::getClassName($class); + + foreach ($methods as $method) { + if ($method instanceof ReflectionMethod) { + if ($withoutInheritance && $className !== $method->class) { + continue; + } + + $effect[] = $method->name; + } + } + } + + return $effect; } /** @@ -543,9 +260,8 @@ class Reflection * * @param array|object|string $parentClass Class who child class should be returned. An array of objects, * namespaces, object or namespace. - * @throws MissingChildClassesException - * @throws TooManyChildClassesException * @return mixed + * @throws TooManyChildClassesException|MissingChildClassesException|CannotResolveClassNameException */ public static function getOneChildClass($parentClass) { @@ -570,22 +286,92 @@ class Reflection return trim($childClasses[0]); } + /** + * Returns a parent class or false if there is no parent class + * + * @param array|object|string $source An array of objects, namespaces, object or namespace + * @return false|ReflectionClass + */ + public static function getParentClass($source) + { + $className = self::getClassName($source); + $reflection = new ReflectionClass($className); + + return $reflection->getParentClass(); + } + + /** + * Returns name of the parent class. + * If given class does not extend another, returns null. + * + * @param array|object|string $class An array of objects, namespaces, object or namespace + * @return null|string + */ + public static function getParentClassName($class): ?string + { + $className = self::getClassName($class); + $reflection = new ReflectionClass($className); + $parentClass = $reflection->getParentClass(); + + if (null === $parentClass || false === $parentClass) { + return null; + } + + return $parentClass->getName(); + } + + /** + * Returns given object properties + * + * @param array|object|string $source An array of objects, namespaces, object or namespace + * @param int $filter (optional) Filter of properties. Uses \ReflectionProperty class + * constants. By default all properties are returned. + * @param bool $includeParents (optional) If is set to true, properties of parent classes are + * included (recursively). Otherwise - not. + * @return ReflectionProperty[] + */ + public static function getProperties($source, int $filter = null, bool $includeParents = false): array + { + $className = self::getClassName($source); + $reflection = new ReflectionClass($className); + + if (null === $filter) { + $filter = ReflectionProperty::IS_PRIVATE + + ReflectionProperty::IS_PROTECTED + + ReflectionProperty::IS_PUBLIC + + ReflectionProperty::IS_STATIC; + } + + $properties = $reflection->getProperties($filter); + $parentProperties = []; + + if ($includeParents) { + $parent = self::getParentClass($source); + + if (false !== $parent) { + $parentClass = $parent->getName(); + $parentProperties = self::getProperties($parentClass, $filter, $includeParents); + } + } + + return array_merge($properties, $parentProperties); + } + /** * Returns property, the \ReflectionProperty instance, of given object * * @param array|object|string $class An array of objects, namespaces, object or namespace * @param string $property Name of the property - * @param int $filter (optional) Filter of properties. Uses \ReflectionProperty class constants. + * @param int|null $filter (optional) Filter of properties. Uses \ReflectionProperty class constants. * By default all properties are allowed / processed. - * @return null|\ReflectionProperty + * @return null|ReflectionProperty */ - public static function getProperty($class, $property, $filter = null) + public static function getProperty($class, string $property, int $filter = null): ?ReflectionProperty { $className = self::getClassName($class); $properties = self::getProperties($className, $filter); if (!empty($properties)) { - /* @var $reflectionProperty \ReflectionProperty */ foreach ($properties as $reflectionProperty) { if ($reflectionProperty->getName() === $property) { return $reflectionProperty; @@ -596,6 +382,217 @@ class Reflection return null; } + /** + * Returns value of given property + * + * @param mixed $source Object that should contains given property + * @param string $property Name of the property that contains a value. It may be also multiple properties + * dot-separated, e.g. "invoice.user.email". + * @param bool $force (optional) If is set to true, try to retrieve value even if the object doesn't have + * property. Otherwise - not. + * @return mixed + */ + public static function getPropertyValue($source, string $property, bool $force = false) + { + if (Regex::contains($property, '.')) { + return self::getPropertyValueByPropertiesChain($source, $property, $force); + } + + [ + $value, + $valueFound, + ] = self::getPropertyValueByReflectionProperty($source, $property); + + if (!$valueFound) { + [ + $value, + $valueFound, + ] = self::getPropertyValueByParentClasses($source, $property); + } + + if (!$valueFound && ($force || self::hasProperty($source, $property))) { + [ + $value, + $valueFound, + ] = self::getPropertyValueByGetter($source, $property); + } + + if (!$valueFound) { + $byReflectionProperty = self::getPropertyValueByReflectionProperty($source, $property); + $value = $byReflectionProperty[0]; + } + + return $value; + } + + /** + * Returns values of given property for given objects. + * Looks for proper getter for the property. + * + * @param array|CollectionInterface|object $objects The objects that should contain given property. It may be also + * one object. + * @param string $property Name of the property that contains a value + * @param bool $force (optional) If is set to true, try to retrieve value even if + * the object does not have property. Otherwise - not. + * @return array + */ + public static function getPropertyValues($objects, string $property, bool $force = false): array + { + /* + * No objects? + * Nothing to do + */ + if (empty($objects)) { + return []; + } + + if ($objects instanceof CollectionInterface) { + $objects = $objects->toArray(); + } + + $values = []; + $objects = Arrays::makeArray($objects); + + foreach ($objects as $object) { + $value = self::getPropertyValue($object, $property, $force); + + if (null !== $value) { + $values[] = $value; + } + } + + return $values; + } + + /** + * Returns information if given class / object has given constant + * + * @param object|string $class The object or name of object's class + * @param string $constant Name of the constant to find + * @return bool + */ + public static function hasConstant($class, string $constant): bool + { + $reflection = new ReflectionClass($class); + + return $reflection->hasConstant($constant); + } + + /** + * Returns information if given class / object has given method + * + * @param object|string $class The object or name of object's class + * @param string $method Name of the method to find + * @return bool + */ + public static function hasMethod($class, string $method): bool + { + $reflection = new ReflectionClass($class); + + return $reflection->hasMethod($method); + } + + /** + * Returns information if given class / object has given property + * + * @param object|string $class The object or name of object's class + * @param string $property Name of the property to find + * @return bool + */ + public static function hasProperty($class, string $property): bool + { + $reflection = new ReflectionClass($class); + + return $reflection->hasProperty($property); + } + + /** + * Returns information if given child class is a subclass of given parent class + * + * @param array|object|string $childClass The child class. An array of objects, namespaces, object or namespace. + * @param array|object|string $parentClass The parent class. An array of objects, namespaces, object or namespace. + * @return bool + */ + public static function isChildOfClass($childClass, $parentClass): bool + { + $childClassName = self::getClassName($childClass); + $parentClassName = self::getClassName($parentClass); + + $parents = class_parents($childClassName); + + if (is_array($parents) && 0 < count($parents)) { + return in_array($parentClassName, $parents, true); + } + + return false; + } + + /** + * Returns information if given interface is implemented by given class / object + * + * @param array|object|string $source An array of objects, namespaces, object or namespace + * @param string $interface The interface that should be implemented + * @return bool + */ + public static function isInterfaceImplemented($source, string $interface): bool + { + $className = self::getClassName($source); + $interfaces = class_implements($className); + + return in_array($interface, $interfaces, true); + } + + /** + * Sets values of properties in given object + * + * @param mixed $object Object that should contains given property + * @param array $propertiesValues Key-value pairs, where key - name of the property, value - value of the property + */ + public static function setPropertiesValues($object, array $propertiesValues): void + { + /* + * No properties? + * Nothing to do + */ + if (empty($propertiesValues)) { + return; + } + + foreach ($propertiesValues as $property => $value) { + static::setPropertyValue($object, $property, $value); + } + } + + /** + * Sets value of given property in given object + * + * @param mixed $object Object that should contains given property + * @param string $property Name of the property + * @param mixed $value Value of the property + * @throws NotExistingPropertyException + */ + public static function setPropertyValue($object, string $property, $value): void + { + $reflectionProperty = self::getProperty($object, $property); + + // Oops, property does not exist + if (null === $reflectionProperty) { + throw NotExistingPropertyException::create($object, $property); + } + + $isPublic = $reflectionProperty->isPublic(); + + if (!$isPublic) { + $reflectionProperty->setAccessible(true); + } + + $reflectionProperty->setValue($object, $value); + + if (!$isPublic) { + $reflectionProperty->setAccessible(false); + } + } + /** * Returns information if given class / object uses / implements given trait * @@ -603,29 +600,25 @@ class Reflection * @param array|string $trait An array of strings or string * @param bool $verifyParents If is set to true, parent classes are verified if they use given * trait. Otherwise - not. - * @throws CannotResolveClassNameException - * @return bool|null + * @return null|bool + * @throws CannotResolveClassNameException|ReflectionException */ - public static function usesTrait($class, $trait, $verifyParents = false) + public static function usesTrait($class, $trait, bool $verifyParents = false): ?bool { $className = self::getClassName($class); $traitName = self::getClassName($trait); - /* - * Oops, cannot resolve class - */ + // Oops, cannot resolve class if (null === $className || '' === $className) { - throw CannotResolveClassNameException::create($class); + throw CannotResolveClassNameException::create(''); } - /* - * Oops, cannot resolve trait - */ + // Oops, cannot resolve trait if (null === $traitName || '' === $traitName) { - throw new CannotResolveClassNameException($class, false); + throw CannotResolveClassNameException::create('', false); } - $reflection = new \ReflectionClass($className); + $reflection = new ReflectionClass($className); $traitsNames = $reflection->getTraitNames(); $uses = in_array($traitName, $traitsNames, true); @@ -642,75 +635,197 @@ class Reflection } /** - * Returns name of the parent class. - * If given class does not extend another, returns null. + * Returns value of given property using getter of the property * - * @param array|object|string $class An array of objects, namespaces, object or namespace - * @return string|null + * An array with 2 elements is returned: + * - value of given property + * - information if the value was found (because null may be returned) + * + * @param mixed $source Object that should contains given property + * @param string $property Name of the property that contains a value + * @return array */ - public static function getParentClassName($class) + private static function getPropertyValueByGetter($source, string $property): array { - $className = self::getClassName($class); - $reflection = new \ReflectionClass($className); - $parentClass = $reflection->getParentClass(); + $value = null; + $valueFound = false; - if (null === $parentClass || false === $parentClass) { - return null; + $reflectionObject = new ReflectionObject($source); + $inflector = InflectorFactory::create()->build(); + $property = $inflector->classify($property); + + $gettersPrefixes = [ + 'get', + 'has', + 'is', + ]; + + foreach ($gettersPrefixes as $prefix) { + $getter = sprintf('%s%s', $prefix, $property); + + if ($reflectionObject->hasMethod($getter)) { + $method = new ReflectionMethod($source, $getter); + + /* + * Getter is not accessible publicly? + * I have to skip it, to avoid an error like this: + * + * Call to protected method My\ExtraClass::getExtraProperty() from context 'My\ExtraClass' + */ + if ($method->isProtected() || $method->isPrivate()) { + continue; + } + + $value = $source->{$getter}(); + $valueFound = true; + + break; + } } - return $parentClass->getName(); + return [ + $value, + $valueFound, + ]; } /** - * Sets value of given property in given object + * Returns value of given property using parent classes * - * @param mixed $object Object that should contains given property - * @param string $property Name of the property - * @param mixed $value Value of the property - * @throws NotExistingPropertyException + * @param mixed $source Object that should contains given property + * @param string $property Name of the property that contains a value + * @return array */ - public static function setPropertyValue($object, $property, $value) + private static function getPropertyValueByParentClasses($source, string $property): array { - $reflectionProperty = self::getProperty($object, $property); + $properties = self::getProperties($source, null, true); + + if (empty($properties)) { + return [ + null, + false, + ]; + } + + foreach ($properties as $reflectionProperty) { + if ($reflectionProperty->getName() === $property) { + $byReflectionProperty = self::getPropertyValueByReflectionProperty( + $source, + $property, + $reflectionProperty + ); + + return [ + $byReflectionProperty[0], + true, + ]; + } + } + + return [ + null, + false, + ]; + } + + /** + * Returns value of given property represented as chain of properties + * + * @param mixed $source Object that should contains given property + * @param string $property Dot-separated properties, e.g. "invoice.user.email" + * @param bool $force (optional) If is set to true, try to retrieve value even if the object doesn't have + * property. Otherwise - not. + * @return mixed + */ + private static function getPropertyValueByPropertiesChain($source, string $property, bool $force) + { + $exploded = explode('.', $property); + + $property = $exploded[0]; + $source = self::getPropertyValue($source, $property, $force); /* - * Oops, property does not exist + * Value of processed property from the chain is not null? + * Let's dig more and get proper value + * + * Required to avoid bug: + * \ReflectionObject::__construct() expects parameter 1 to be object, null given + * (...) + * 4. at \ReflectionObject->__construct (null) + * 5. at Reflection ::getPropertyValue (null, 'name', true) + * 6. at ListService->getItemValue (object(Deal), 'project.name', '0') + * + * while using "project.name" as property - $project has $name property ($project exists in the Deal class) + * and the $project equals null + * + * Meritoo + * 2016-11-07 */ - if (null === $reflectionProperty) { - throw NotExistingPropertyException::create($object, $property); + if (null !== $source) { + unset($exploded[0]); + $property = implode('.', $exploded); + + return self::getPropertyValue($source, $property, $force); } - $notAccessible = $reflectionProperty->isPrivate() || $reflectionProperty->isProtected(); + return null; + } - if ($notAccessible) { + /** + * Returns value of given property using the property represented by reflection. + * If value cannot be fetched, makes the property accessible temporarily. + * + * @param mixed $object Object that should contains given property + * @param string $property Name of the property that contains a value + * @param null|ReflectionProperty $reflectionProperty (optional) Property represented by reflection + * @return mixed + */ + private static function getPropertyValueByReflectionProperty( + $object, + string $property, + ?ReflectionProperty $reflectionProperty = null + ) { + $value = null; + $valueFound = false; + $className = self::getClassName($object); + + try { + if (null === $reflectionProperty) { + $reflectionProperty = new ReflectionProperty($className, $property); + } + + $value = $reflectionProperty->getValue($object); + $valueFound = true; + } catch (ReflectionException $exception) { + } + + if (null !== $reflectionProperty) { $reflectionProperty->setAccessible(true); - } - $reflectionProperty->setValue($object, $value); + $value = $reflectionProperty->getValue($object); + $valueFound = true; - if ($notAccessible) { $reflectionProperty->setAccessible(false); } + + return [ + $value, + $valueFound, + ]; } /** - * Sets values of properties in given object + * Returns the real class name of a class name that could be a proxy * - * @param mixed $object Object that should contains given property - * @param array $propertiesValues Key-value pairs, where key - name of the property, value - value of the property + * @param string $class Class to verify + * @return string */ - public static function setPropertiesValues($object, array $propertiesValues) + private static function getRealClass(string $class): string { - /* - * No properties? - * Nothing to do - */ - if (empty($propertiesValues)) { - return; + if (false === $pos = strrpos($class, '\\'.Proxy::MARKER.'\\')) { + return $class; } - foreach ($propertiesValues as $property => $value) { - static::setPropertyValue($object, $property, $value); - } + return substr($class, $pos + Proxy::MARKER_LENGTH + 2); } } diff --git a/src/Utilities/Regex.php b/src/Utilities/Regex.php index 3f8f78b..78fab4d 100644 --- a/src/Utilities/Regex.php +++ b/src/Utilities/Regex.php @@ -10,6 +10,7 @@ namespace Meritoo\Common\Utilities; use Meritoo\Common\Exception\Regex\IncorrectColorHexLengthException; use Meritoo\Common\Exception\Regex\InvalidColorHexValueException; +use Transliterator; /** * Useful methods related to regular expressions @@ -25,21 +26,23 @@ class Regex * @var array */ private static $patterns = [ - 'email' => '/^[\w-.]{2,}@[\w-]+\.[\w]{2,}+$/', - 'phone' => '/^\+?[0-9 ]+$/', - 'camelCasePart' => '/([a-z]|[A-Z]){1}[a-z]*/', - 'urlProtocol' => '/^([a-z]+:\/\/)', - 'urlDomain' => '([\da-z\.-]+)\.([a-z\.]{2,6})(\/)?([\w\.\-]*)?(\?)?([\w \.\-\/=&]*)\/?$/i', - 'letterOrDigit' => '/[a-zA-Z0-9]+/', - 'htmlEntity' => '/&[a-z0-9]+;/', - 'htmlAttribute' => '/([\w-]+)="([\w -]+)"/', - 'fileName' => '/.+\.\w+$/', - 'isQuoted' => '/^[\'"]{1}.+[\'"]{1}$/', + 'email' => '/^[\w\-.]{2,}@[\w\-]+\.[\w]{2,}+$/', + 'phone' => '/^\+?[0-9 ]+$/', + 'camelCasePart' => '/([a-z]|[A-Z]){1}[a-z]*/', + 'urlProtocol' => '/^([a-z]+:\/\/)', + 'urlDomain' => '([\da-z\.-]+)\.([a-z\.]{2,6})(\/)?([\w\.\-]*)?(\?)?([\w \.\-\/=&]*)\/?$/i', + 'letterOrDigit' => '/[a-zA-Z0-9]+/', + 'htmlEntity' => '/&[a-z0-9]+;/', + 'htmlAttribute' => '/([\w-]+)="([\w -]+)"/', + 'fileName' => '/[\w.\- +=!@$&()?]+\.\w+$/', // e.g. "this-1_2 3 & my! 4+file.jpg" + 'isQuoted' => '/^[\'"]{1}.+[\'"]{1}$/', 'windowsBasedPath' => '/^[A-Z]{1}:\\\.*$/', - 'money' => '/^[-+]?\d+([\.,]{1}\d*)?$/', - 'color' => '/^[a-f0-9]{6}$/i', - 'bundleName' => '/^(([A-Z]{1}[a-z0-9]+)((?2))*)(Bundle)$/', - 'binaryValue' => '/[^\x20-\x7E\t\r\n]/', + 'money' => '/^[-+]?\d+([\.,]{1}\d*)?$/', + 'color' => '/^[a-f0-9]{6}$/i', + 'bundleName' => '/^(([A-Z]{1}[a-z0-9]+)((?2))*)(Bundle)$/', + 'binaryValue' => '/[^\x20-\x7E\t\r\n]/', + 'beginningSlash' => '|^\/|', + 'endingSlash' => '|\/$|', /* * Matches: @@ -51,9 +54,655 @@ class Regex * * Contains "%s" that should be replaced with separator used to split width and height. */ - 'size' => '/^[\ ]*(\d+)[\ ]*%s[\ ]*(\d+)[\ ]*$/', + 'size' => '/^[\ ]*(\d+)[\ ]*%s[\ ]*(\d+)[\ ]*$/', ]; + /** + * Returns information if given html attributes are valid + * + * @param string $htmlAttributes The html attributes to verify + * @return bool + */ + public static function areValidHtmlAttributes($htmlAttributes) + { + /* + * Not a string? + * Nothing to do + */ + if (!is_string($htmlAttributes)) { + return false; + } + + $pattern = self::getHtmlAttributePattern(); + + return (bool) preg_match_all($pattern, $htmlAttributes); + } + + /** + * Filters array by given expression and column + * + * Expression can be simple compare expression, like " == 2", or regular expression. + * Returns filtered array. + * + * @param array $array The 2-dimensional array that should be filtered + * @param string $arrayColumnKey Column name + * @param string $filterExpression Simple filter expression (e.g. "== 2" or "!= \'home\'") or regular + * expression (e.g. "/\d+/" or "/[a-z]+[,;]{2,}/") + * @param bool $itsRegularExpression (optional) If is set to true, means that filter expression is a regular + * expression. Otherwise - not (default behaviour). + * @return array + */ + public static function arrayFilter($array, $arrayColumnKey, $filterExpression, $itsRegularExpression = false) + { + /* + * No elements? + * Nothing to do + */ + if (empty($array)) { + return []; + } + + $effect = $array; + + foreach ($effect as $key => &$item) { + if (!isset($item[$arrayColumnKey])) { + continue; + } + + $value = $item[$arrayColumnKey]; + + if ($itsRegularExpression) { + $matchesCount = preg_match($filterExpression, $value); + $remove = 0 === $matchesCount; + } else { + if (is_string($value)) { + $value = sprintf('\'%s\'', $value); + } elseif (is_bool($value)) { + if (true === $value) { + $value = 'true'; + } else { + $value = 'false'; + } + } + + eval(sprintf('$isEqual = %s%s;', $value, $filterExpression)); + + /** @var bool $isEqual */ + $remove = !$isEqual; + } + + if ($remove) { + unset($effect[$key]); + } + } + + return $effect; + } + + /** + * Returns string in human readable style generated from given camel case string / text + * + * @param string $string The string / text to convert + * @param bool $applyUpperCaseFirst (optional) If is set to true, first word / element from the converted + * string is uppercased. Otherwise - not. + * @return string + */ + public static function camelCase2humanReadable($string, $applyUpperCaseFirst = false) + { + $parts = self::getCamelCaseParts($string); + + if (!empty($parts)) { + $elements = []; + + foreach ($parts as $part) { + $elements[] = strtolower($part); + } + + $string = implode(' ', $elements); + + if ($applyUpperCaseFirst) { + $string = ucfirst($string); + } + } + + return $string; + } + + /** + * Returns simple, lowercase string generated from given camel case string / text + * + * @param string $string The string / text to convert + * @param string $separator (optional) Separator used to concatenate parts of the string, e.g. '-' or '_' + * @param bool $applyLowercase (optional) If is set to true, returned string will be lowercased. Otherwise - not. + * @return string + */ + public static function camelCase2simpleLowercase($string, $separator = '', $applyLowercase = true) + { + $parts = self::getCamelCaseParts($string); + + if (!empty($parts)) { + $string = implode($separator, $parts); + + if ($applyLowercase) { + $string = strtolower($string); + } + } + + return $string; + } + + public static function clearBeginningSlash(string $string): string + { + $pattern = static::$patterns['beginningSlash']; + + return preg_replace($pattern, '', $string); + } + + public static function clearEndingSlash(string $string): string + { + $pattern = static::$patterns['endingSlash']; + + return preg_replace($pattern, '', $string); + } + + /** + * Returns information if one string contains another string + * + * @param string $haystack The string to search in + * @param string $needle The string to be search for + * @return bool + */ + public static function contains($haystack, $needle) + { + if (1 === strlen($needle) && !self::isLetterOrDigit($needle)) { + $needle = '\\'.$needle; + } + + return (bool) preg_match('|.*'.$needle.'.*|', $haystack); + } + + /** + * Returns information if the string contains html entities + * + * @param string $string String to check + * @return bool + */ + public static function containsEntities($string) + { + $pattern = self::getHtmlEntityPattern(); + + return (bool) preg_match_all($pattern, $string); + } + + /** + * Returns slug for given value + * + * @param string $value Value that should be transformed to slug + * @return bool|string + */ + public static function createSlug($value) + { + /* + * Not a scalar value? + * Nothing to do + */ + if (!is_scalar($value)) { + return false; + } + + /* + * It's an empty string? + * Nothing to do + */ + if ('' === $value) { + return ''; + } + + $id = 'Latin-ASCII; NFD; [:Nonspacing Mark:] Remove; NFC; [:Punctuation:] Remove; Lower();'; + $transliterator = Transliterator::create($id); + + $cleanValue = trim($value); + $result = $transliterator->transliterate($cleanValue); + + return preg_replace('/[-\s]+/', '-', $result); + } + + /** + * Returns information if the string ends with given ending / characters + * + * @param string $string String to check + * @param string $ending The ending of string, one or more characters + * @return bool + */ + public static function endsWith($string, $ending) + { + if (1 === strlen($ending) && !self::isLetterOrDigit($ending)) { + $ending = '\\'.$ending; + } + + return (bool) preg_match('|'.$ending.'$|', $string); + } + + /** + * Returns information if the string ends with directory's separator + * + * @param string $text String that may contain a directory's separator at the end + * @param string $separator (optional) The directory's separator, e.g. "/". If is empty (not provided), system's + * separator is used. + * @return string + */ + public static function endsWithDirectorySeparator($text, $separator = '') + { + if (empty($separator)) { + $separator = DIRECTORY_SEPARATOR; + } + + return self::endsWith($text, $separator); + } + + /** + * Returns array values that match given pattern (or values that keys match the pattern) + * + * @param string $pattern Pattern to match + * @param array $array The array (scalar values only) + * @param bool $itsKeyPattern (optional) If is set to true, keys will be checked if they match pattern. + * Otherwise - values will be checked (default behaviour). + * @return array + */ + public static function getArrayValuesByPattern($pattern, array $array, $itsKeyPattern = false) + { + /* + * No elements? + * Nothing to do + */ + if (empty($array)) { + return []; + } + + if ($itsKeyPattern) { + $effect = []; + + foreach ($array as $key => $value) { + if ((bool) preg_match($pattern, $key)) { + $effect[$key] = $value; + } + } + + return $effect; + } + + return preg_grep($pattern, $array); + } + + /** + * Returns pattern used to validate / verify name of bundle + * + * @return string + */ + public static function getBundleNamePattern() + { + return self::$patterns['bundleName']; + } + + /** + * Returns pattern used to validate / verify or get camel case parts of string + * + * @return string + */ + public static function getCamelCasePartPattern() + { + return self::$patterns['camelCasePart']; + } + + /** + * Returns parts of given camel case string / text + * + * @param string $string The string / text to retrieve parts + * @return array + */ + public static function getCamelCaseParts($string) + { + $pattern = self::getCamelCasePartPattern(); + $matches = []; + preg_match_all($pattern, $string, $matches); + + return $matches[0]; + } + + /** + * Returns pattern used to validate / verify or get e-mail address + * + * @return string + */ + public static function getEmailPattern() + { + return self::$patterns['email']; + } + + /** + * Returns pattern used to validate / verify name of file + * + * @return string + */ + public static function getFileNamePattern(): string + { + return self::$patterns['fileName']; + } + + /** + * Returns pattern used to validate / verify html attribute + * + * @return string + */ + public static function getHtmlAttributePattern() + { + return self::$patterns['htmlAttribute']; + } + + /** + * Returns pattern used to validate / verify html entity + * + * @return string + */ + public static function getHtmlEntityPattern() + { + return self::$patterns['htmlEntity']; + } + + /** + * Returns pattern used to validate / verify if value is quoted (by apostrophes or quotation marks) + * + * @return string + */ + public static function getIsQuotedPattern() + { + return self::$patterns['isQuoted']; + } + + /** + * Returns pattern used to validate / verify letter or digit + * + * @return string + */ + public static function getLetterOrDigitPattern() + { + return self::$patterns['letterOrDigit']; + } + + /** + * Returns pattern used to validate / verify if given value is money-related value + * + * @return string + */ + public static function getMoneyPattern() + { + return self::$patterns['money']; + } + + /** + * Returns pattern used to validate / verify or get phone number + * + * @return string + */ + public static function getPhoneNumberPattern() + { + return self::$patterns['phone']; + } + + /** + * Returns pattern used to validate / verify size + * + * @param string $separator (optional) Separator used to split width and height. Default: " x ". + * @return string + */ + public static function getSizePattern($separator = ' x ') + { + $escapeMe = [ + '/', + '|', + '.', + '(', + ')', + '[', + ']', + ]; + + $cleanSeparator = trim($separator); + + if (in_array($cleanSeparator, $escapeMe, true)) { + // I have to escape special character of regular expression that may be used as separator + $separator = str_replace($cleanSeparator, '\\'.$cleanSeparator, $separator); + } + + return sprintf(self::$patterns['size'], $separator); + } + + /** + * Returns pattern used to validate / verify or get url address + * + * @param bool $requireProtocol (optional) If is set to true, the protocol is required to be passed in the url. + * Otherwise - not. + * @return string + */ + public static function getUrlPattern($requireProtocol = false) + { + $urlProtocol = self::$patterns['urlProtocol']; + $urlDomain = self::$patterns['urlDomain']; + $protocolPatternPart = '?'; + + if ($requireProtocol) { + $protocolPatternPart = ''; + } + + return sprintf('%s%s%s', $urlProtocol, $protocolPatternPart, $urlDomain); + } + + /** + * Returns valid given hexadecimal value of color. + * If the value is invalid, throws an exception or returns false. + * + * @param string $color Color to verify + * @param bool $throwException (optional) If is set to true, throws an exception if given color is invalid + * (default behaviour). Otherwise - not. + * @return bool|string + * @throws InvalidColorHexValueException + * @throws IncorrectColorHexLengthException + */ + public static function getValidColorHexValue($color, $throwException = true) + { + // Not a scalar value? + if (!is_scalar($color)) { + return false; + } + + $color = Miscellaneous::replace($color, '/#/', ''); + $length = strlen($color); + + // Color hasn't 3 or 6 characters length? + if (3 !== $length && 6 !== $length) { + if ($throwException) { + throw new IncorrectColorHexLengthException($color); + } + + return false; + } + + // Make the color 6 characters length, if has 3 + if (3 === $length) { + $color = Miscellaneous::replace($color, '/(.)(.)(.)/', '$1$1$2$2$3$3'); + } + + $pattern = self::$patterns['color']; + $match = (bool) preg_match($pattern, $color); + + // It's not a valid color + if (!$match) { + if ($throwException) { + throw new InvalidColorHexValueException($color); + } + + return false; + } + + return strtolower($color); + } + + /** + * Returns pattern used to validate / verify if given path is a Windows-based path, e.g. "C:\path\to\file.jpg" + * + * @return string + */ + public static function getWindowsBasedPathPattern() + { + return self::$patterns['windowsBasedPath']; + } + + /** + * Returns information if given value is a binary value + * + * @param string $value Value to verify + * @return bool + */ + public static function isBinaryValue($value) + { + /* + * Not a string? + * Nothing to do + */ + if (!is_string($value)) { + return false; + } + + $pattern = self::$patterns['binaryValue']; + + return (bool) preg_match($pattern, $value); + } + + /** + * Returns information if given name of file is a really name of file. + * Verifies if given name contains a dot and an extension, e.g. "My File 001.jpg". + * + * @param string $fileName Name of file to check. It may be path of file also. + * @return bool + */ + public static function isFileName(string $fileName): bool + { + $pattern = self::getFileNamePattern(); + + return (bool) preg_match($pattern, $fileName); + } + + /** + * Returns information if given character is a letter or digit + * + * @param string $char Character to check + * @return bool + */ + public static function isLetterOrDigit($char) + { + $pattern = self::getLetterOrDigitPattern(); + + return is_scalar($char) && (bool) preg_match($pattern, $char); + } + + /** + * Returns information if given value is quoted (by apostrophes or quotation marks) + * + * @param mixed $value The value to check + * @return bool + */ + public static function isQuoted($value) + { + $pattern = self::getIsQuotedPattern(); + + return is_scalar($value) && (bool) preg_match($pattern, $value); + } + + /** + * Returns information if uri contains parameter + * + * @param string $uri Uri string (e.g. $_SERVER['REQUEST_URI']) + * @param string $parameterName Uri parameter name + * @return bool + */ + public static function isSetUriParameter($uri, $parameterName) + { + return (bool) preg_match('|[?&]{1}'.$parameterName.'=|', $uri); // e.g. ?name=phil&type=4 -> '$type=' + } + + /** + * Returns information if given value is a size value + * + * @param string $value Value to verify + * @param string $separator (optional) Separator used to split width and height. Default: " x ". + * @return bool + */ + public static function isSizeValue($value, $separator = ' x ') + { + /* + * Not a string? + * Nothing to do + */ + if (!is_string($value)) { + return false; + } + + $pattern = self::getSizePattern($separator); + + return (bool) preg_match($pattern, $value); + } + + /** + * Returns information if given path is sub-path of another path, e.g. path file is owned by path of directory + * + * @param string $subPath Path to verify, probably sub-path + * @param string $path Main / parent path + * @return bool + */ + public static function isSubPathOf($subPath, $path) + { + /* + * Empty path? + * Nothing to do + */ + if (empty($path) || empty($subPath)) { + return false; + } + + // I have to escape all slashes (directory separators): "/" -> "\/" + $prepared = preg_quote($path, '/'); + + // Slash at the ending is optional + if (self::endsWith($path, '/')) { + $prepared .= '?'; + } + + $pattern = sprintf('/^%s.*/', $prepared); + + return (bool) preg_match($pattern, $subPath); + } + + /** + * Returns information if given name of bundle is valid + * + * @param string $bundleName Full name of bundle to verify, e.g. "MyExtraBundle" + * @return bool + */ + public static function isValidBundleName($bundleName) + { + /* + * Not a string? + * Nothing to do + */ + if (!is_string($bundleName)) { + return false; + } + + $pattern = self::getBundleNamePattern(); + + return (bool) preg_match($pattern, $bundleName); + } + /** * Returns information if given e-mail address is valid * @@ -81,7 +730,114 @@ class Regex $pattern = self::getEmailPattern(); - return (bool)preg_match($pattern, $email); + return (bool) preg_match($pattern, $email); + } + + /** + * Returns information if given html attribute is valid + * + * @param string $htmlAttribute The html attribute to verify + * @return bool + */ + public static function isValidHtmlAttribute($htmlAttribute) + { + /* + * Not a string? + * Nothing to do + */ + if (!is_string($htmlAttribute)) { + return false; + } + + $pattern = self::getHtmlAttributePattern(); + + return (bool) preg_match($pattern, $htmlAttribute); + } + + /** + * Returns information if given value is valid money-related value + * + * @param mixed $value Value to verify + * @return bool + */ + public static function isValidMoneyValue($value) + { + /* + * Not a scalar value? + * Nothing to do + */ + if (!is_scalar($value)) { + return false; + } + + $pattern = self::getMoneyPattern(); + + return (bool) preg_match($pattern, $value); + } + + /** + * Returns information if given NIP number is valid + * + * @param string $nip A given NIP number + * @return bool + * + * @see https://pl.wikipedia.org/wiki/NIP#Znaczenie_numeru + */ + public static function isValidNip($nip) + { + $nip = preg_replace('/[\D]/', '', $nip); + + $invalidNips = [ + '1234567890', + '0000000000', + ]; + + if (!preg_match('/^[\d]{10}$/', $nip) || in_array($nip, $invalidNips, true)) { + return false; + } + + $sum = 0; + $weights = [ + 6, + 5, + 7, + 2, + 3, + 4, + 5, + 6, + 7, + ]; + + for ($i = 0; $i < 9; ++$i) { + $sum += $weights[$i] * $nip[$i]; + } + + $modulo = $sum % 11; + $numberControl = (10 === $modulo) ? 0 : $modulo; + + return $numberControl === (int) $nip[9]; + } + + /** + * Returns information if given phone number is valid + * + * @param string $phoneNumber The phone number to validate / verify + * @return bool + */ + public static function isValidPhoneNumber($phoneNumber) + { + /* + * Not a string? + * Nothing to do + */ + if (!is_string($phoneNumber)) { + return false; + } + + $pattern = self::getPhoneNumberPattern(); + + return (bool) preg_match($pattern, trim($phoneNumber)); } /** @@ -141,7 +897,7 @@ class Regex * Tax ID is valid */ - return $sum % 11 === (int)$taxId[9]; + return $sum % 11 === (int) $taxId[9]; } /** @@ -164,123 +920,20 @@ class Regex $pattern = self::getUrlPattern($requireProtocol); - return (bool)preg_match($pattern, $url); + return (bool) preg_match($pattern, $url); } /** - * Returns information if given phone number is valid + * Returns information if given path is a Windows-based path, e.g. "C:\path\to\file.jpg" * - * @param string $phoneNumber The phone number to validate / verify + * @param string $path The path to verify * @return bool */ - public static function isValidPhoneNumber($phoneNumber) + public static function isWindowsBasedPath($path) { - /* - * Not a string? - * Nothing to do - */ - if (!is_string($phoneNumber)) { - return false; - } + $pattern = self::getWindowsBasedPathPattern(); - $pattern = self::getPhoneNumberPattern(); - - return (bool)preg_match($pattern, trim($phoneNumber)); - } - - /** - * Returns array values that match given pattern (or values that keys match the pattern) - * - * @param string $pattern Pattern to match - * @param array $array The array (scalar values only) - * @param bool $itsKeyPattern (optional) If is set to true, keys will be checked if they match pattern. - * Otherwise - values will be checked (default behaviour). - * @return array - */ - public static function getArrayValuesByPattern($pattern, array $array, $itsKeyPattern = false) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return []; - } - - if ($itsKeyPattern) { - $effect = []; - - foreach ($array as $key => $value) { - if ((bool)preg_match($pattern, $key)) { - $effect[$key] = $value; - } - } - - return $effect; - } - - return preg_grep($pattern, $array); - } - - /** - * Filters array by given expression and column - * - * Expression can be simple compare expression, like " == 2", or regular expression. - * Returns filtered array. - * - * @param array $array The 2-dimensional array that should be filtered - * @param string $arrayColumnKey Column name - * @param string $filterExpression Simple filter expression (e.g. "== 2" or "!= \'home\'") or regular - * expression (e.g. "/\d+/" or "/[a-z]+[,;]{2,}/") - * @param bool $itsRegularExpression (optional) If is set to true, means that filter expression is a regular - * expression. Otherwise - not (default behaviour). - * @return array - */ - public static function arrayFilter($array, $arrayColumnKey, $filterExpression, $itsRegularExpression = false) - { - /* - * No elements? - * Nothing to do - */ - if (empty($array)) { - return []; - } - - $effect = $array; - - foreach ($effect as $key => &$item) { - if (!isset($item[$arrayColumnKey])) { - continue; - } - - $value = $item[$arrayColumnKey]; - - if ($itsRegularExpression) { - $matchesCount = preg_match($filterExpression, $value); - $remove = 0 === $matchesCount; - } else { - if (is_string($value)) { - $value = sprintf('\'%s\'', $value); - } elseif (is_bool($value)) { - if (true === $value) { - $value = 'true'; - } else { - $value = 'false'; - } - } - - eval(sprintf('$isEqual = %s%s;', $value, $filterExpression)); - - /* @var bool $isEqual */ - $remove = !$isEqual; - } - - if ($remove) { - unset($effect[$key]); - } - } - - return $effect; + return (bool) preg_match($pattern, $path); } /** @@ -311,12 +964,13 @@ class Regex } foreach ($patterns as $pattern) { - $matched = (bool)preg_match_all($pattern, $subject); + $matched = (bool) preg_match_all($pattern, $subject); if ($mustAllMatch) { $effect = $effect && $matched; } elseif ($matched) { $effect = $matched; + break; } } @@ -324,180 +978,6 @@ class Regex return $effect; } - /** - * Returns string in human readable style generated from given camel case string / text - * - * @param string $string The string / text to convert - * @param bool $applyUpperCaseFirst (optional) If is set to true, first word / element from the converted - * string is uppercased. Otherwise - not. - * @return string - */ - public static function camelCase2humanReadable($string, $applyUpperCaseFirst = false) - { - $parts = self::getCamelCaseParts($string); - - if (!empty($parts)) { - $elements = []; - - foreach ($parts as $part) { - $elements[] = strtolower($part); - } - - $string = implode(' ', $elements); - - if ($applyUpperCaseFirst) { - $string = ucfirst($string); - } - } - - return $string; - } - - /** - * Returns parts of given camel case string / text - * - * @param string $string The string / text to retrieve parts - * @return array - */ - public static function getCamelCaseParts($string) - { - $pattern = self::getCamelCasePartPattern(); - $matches = []; - preg_match_all($pattern, $string, $matches); - - return $matches[0]; - } - - /** - * Returns simple, lowercase string generated from given camel case string / text - * - * @param string $string The string / text to convert - * @param string $separator (optional) Separator used to concatenate parts of the string, e.g. '-' or '_' - * @param bool $applyLowercase (optional) If is set to true, returned string will be lowercased. Otherwise - not. - * @return string - */ - public static function camelCase2simpleLowercase($string, $separator = '', $applyLowercase = true) - { - $parts = self::getCamelCaseParts($string); - - if (!empty($parts)) { - $string = implode($separator, $parts); - - if ($applyLowercase) { - $string = strtolower($string); - } - } - - return $string; - } - - /** - * Returns pattern used to validate / verify or get e-mail address - * - * @return string - */ - public static function getEmailPattern() - { - return self::$patterns['email']; - } - - /** - * Returns pattern used to validate / verify or get phone number - * - * @return string - */ - public static function getPhoneNumberPattern() - { - return self::$patterns['phone']; - } - - /** - * Returns pattern used to validate / verify or get camel case parts of string - * - * @return string - */ - public static function getCamelCasePartPattern() - { - return self::$patterns['camelCasePart']; - } - - /** - * Returns pattern used to validate / verify or get url address - * - * @param bool $requireProtocol (optional) If is set to true, the protocol is required to be passed in the url. - * Otherwise - not. - * @return string - */ - public static function getUrlPattern($requireProtocol = false) - { - $urlProtocol = self::$patterns['urlProtocol']; - $urlDomain = self::$patterns['urlDomain']; - $protocolPatternPart = '?'; - - if ($requireProtocol) { - $protocolPatternPart = ''; - } - - return sprintf('%s%s%s', $urlProtocol, $protocolPatternPart, $urlDomain); - } - - /** - * Returns information if given path is sub-path of another path, e.g. path file is owned by path of directory - * - * @param string $subPath Path to verify, probably sub-path - * @param string $path Main / parent path - * @return bool - */ - public static function isSubPathOf($subPath, $path) - { - /* - * Empty path? - * Nothing to do - */ - if (empty($path) || empty($subPath)) { - return false; - } - - /* - * I have to escape all slashes (directory separators): "/" -> "\/" - */ - $prepared = preg_quote($path, '/'); - - /* - * Slash at the ending is optional - */ - if (self::endsWith($path, '/')) { - $prepared .= '?'; - } - - $pattern = sprintf('/^%s.*/', $prepared); - - return (bool)preg_match($pattern, $subPath); - } - - /** - * Returns pattern used to validate / verify letter or digit - * - * @return string - */ - public static function getLetterOrDigitPattern() - { - return self::$patterns['letterOrDigit']; - } - - /** - * Returns information if given character is a letter or digit - * - * @param string $char Character to check - * @return bool - */ - public static function isLetterOrDigit($char) - { - $pattern = self::getLetterOrDigitPattern(); - - return is_scalar($char) && (bool)preg_match($pattern, $char); - } - /** * Returns information if the string starts with given beginning / characters * @@ -509,33 +989,17 @@ class Regex { if (!empty($string) && !empty($beginning)) { if (1 === strlen($beginning) && !self::isLetterOrDigit($beginning)) { - $beginning = '\\' . $beginning; + $beginning = '\\'.$beginning; } $pattern = sprintf('|^%s|', $beginning); - return (bool)preg_match($pattern, $string); + return (bool) preg_match($pattern, $string); } return false; } - /** - * Returns information if the string ends with given ending / characters - * - * @param string $string String to check - * @param string $ending The ending of string, one or more characters - * @return bool - */ - public static function endsWith($string, $ending) - { - if (1 === strlen($ending) && !self::isLetterOrDigit($ending)) { - $ending = '\\' . $ending; - } - - return (bool)preg_match('|' . $ending . '$|', $string); - } - /** * Returns information if the string starts with directory's separator * @@ -552,466 +1016,4 @@ class Regex return self::startsWith($string, $separator); } - - /** - * Returns information if the string ends with directory's separator - * - * @param string $text String that may contain a directory's separator at the end - * @param string $separator (optional) The directory's separator, e.g. "/". If is empty (not provided), system's - * separator is used. - * @return string - */ - public static function endsWithDirectorySeparator($text, $separator = '') - { - if (empty($separator)) { - $separator = DIRECTORY_SEPARATOR; - } - - return self::endsWith($text, $separator); - } - - /** - * Returns information if uri contains parameter - * - * @param string $uri Uri string (e.g. $_SERVER['REQUEST_URI']) - * @param string $parameterName Uri parameter name - * @return bool - */ - public static function isSetUriParameter($uri, $parameterName) - { - return (bool)preg_match('|[?&]{1}' . $parameterName . '=|', $uri); // e.g. ?name=phil&type=4 -> '$type=' - } - - /** - * Returns pattern used to validate / verify html entity - * - * @return string - */ - public static function getHtmlEntityPattern() - { - return self::$patterns['htmlEntity']; - } - - /** - * Returns information if the string contains html entities - * - * @param string $string String to check - * @return bool - */ - public static function containsEntities($string) - { - $pattern = self::getHtmlEntityPattern(); - - return (bool)preg_match_all($pattern, $string); - } - - /** - * Returns information if one string contains another string - * - * @param string $haystack The string to search in - * @param string $needle The string to be search for - * @return bool - */ - public static function contains($haystack, $needle) - { - if (1 === strlen($needle) && !self::isLetterOrDigit($needle)) { - $needle = '\\' . $needle; - } - - return (bool)preg_match('|.*' . $needle . '.*|', $haystack); - } - - /** - * Returns pattern used to validate / verify name of file - * - * @return string - */ - public static function getFileNamePattern() - { - return self::$patterns['fileName']; - } - - /** - * Returns information if given name of file is a really name of file. - * Verifies if given name contains a dot and an extension, e.g. "My File 001.jpg". - * - * @param string $fileName Name of file to check. It may be path of file also. - * @return bool - */ - public static function isFileName($fileName) - { - $pattern = self::getFileNamePattern(); - - return (bool)preg_match($pattern, $fileName); - } - - /** - * Returns pattern used to validate / verify if value is quoted (by apostrophes or quotation marks) - * - * @return string - */ - public static function getIsQuotedPattern() - { - return self::$patterns['isQuoted']; - } - - /** - * Returns information if given value is quoted (by apostrophes or quotation marks) - * - * @param mixed $value The value to check - * @return bool - */ - public static function isQuoted($value) - { - $pattern = self::getIsQuotedPattern(); - - return is_scalar($value) && (bool)preg_match($pattern, $value); - } - - /** - * Returns pattern used to validate / verify if given path is a Windows-based path, e.g. "C:\path\to\file.jpg" - * - * @return string - */ - public static function getWindowsBasedPathPattern() - { - return self::$patterns['windowsBasedPath']; - } - - /** - * Returns information if given path is a Windows-based path, e.g. "C:\path\to\file.jpg" - * - * @param string $path The path to verify - * @return bool - */ - public static function isWindowsBasedPath($path) - { - $pattern = self::getWindowsBasedPathPattern(); - - return (bool)preg_match($pattern, $path); - } - - /** - * Returns information if given NIP number is valid - * - * @param string $nip A given NIP number - * @return bool - * - * @see https://pl.wikipedia.org/wiki/NIP#Znaczenie_numeru - */ - public static function isValidNip($nip) - { - $nip = preg_replace('/[\D]/', '', $nip); - - $invalidNips = [ - '1234567890', - '0000000000', - ]; - - if (!preg_match('/^[\d]{10}$/', $nip) || in_array($nip, $invalidNips, true)) { - return false; - } - - $sum = 0; - $weights = [ - 6, - 5, - 7, - 2, - 3, - 4, - 5, - 6, - 7, - ]; - - for ($i = 0; $i < 9; ++$i) { - $sum += $weights[$i] * $nip[$i]; - } - - $modulo = $sum % 11; - $numberControl = (10 === $modulo) ? 0 : $modulo; - - return $numberControl === (int)$nip[9]; - } - - /** - * Returns pattern used to validate / verify if given value is money-related value - * - * @return string - */ - public static function getMoneyPattern() - { - return self::$patterns['money']; - } - - /** - * Returns information if given value is valid money-related value - * - * @param mixed $value Value to verify - * @return bool - */ - public static function isValidMoneyValue($value) - { - /* - * Not a scalar value? - * Nothing to do - */ - if (!is_scalar($value)) { - return false; - } - - $pattern = self::getMoneyPattern(); - - return (bool)preg_match($pattern, $value); - } - - /** - * Returns valid given hexadecimal value of color. - * If the value is invalid, throws an exception or returns false. - * - * @param string $color Color to verify - * @param bool $throwException (optional) If is set to true, throws an exception if given color is invalid - * (default behaviour). Otherwise - not. - * @throws IncorrectColorHexLengthException - * @throws InvalidColorHexValueException - * @return string|bool - */ - public static function getValidColorHexValue($color, $throwException = true) - { - /* - * Not a scalar value? - * Nothing to do - */ - if (!is_scalar($color)) { - return false; - } - - $color = Miscellaneous::replace($color, '/#/', ''); - $length = strlen($color); - - /* - * Color is not 3 or 6 characters long? - * Nothing to do - */ - if (3 !== $length && 6 !== $length) { - if ($throwException) { - throw new IncorrectColorHexLengthException($color); - } - - return false; - } - - /* - * Color is 3 characters long? - * Let's make it 6 characters long - */ - if (3 === $length) { - $color = Miscellaneous::replace($color, '/(.)(.)(.)/', '$1$1$2$2$3$3'); - } - - $pattern = self::$patterns['color']; - $match = (bool)preg_match($pattern, $color); - - /* - * It's valid color - * Nothing to do more - */ - if ($match) { - return strtolower($color); - } - - if ($throwException) { - throw new InvalidColorHexValueException($color); - } - - return false; - } - - /** - * Returns information if given name of bundle is valid - * - * @param string $bundleName Full name of bundle to verify, e.g. "MyExtraBundle" - * @return bool - */ - public static function isValidBundleName($bundleName) - { - /* - * Not a string? - * Nothing to do - */ - if (!is_string($bundleName)) { - return false; - } - - $pattern = self::getBundleNamePattern(); - - return (bool)preg_match($pattern, $bundleName); - } - - /** - * Returns pattern used to validate / verify name of bundle - * - * @return string - */ - public static function getBundleNamePattern() - { - return self::$patterns['bundleName']; - } - - /** - * Returns pattern used to validate / verify html attribute - * - * @return string - */ - public static function getHtmlAttributePattern() - { - return self::$patterns['htmlAttribute']; - } - - /** - * Returns information if given html attribute is valid - * - * @param string $htmlAttribute The html attribute to verify - * @return bool - */ - public static function isValidHtmlAttribute($htmlAttribute) - { - /* - * Not a string? - * Nothing to do - */ - if (!is_string($htmlAttribute)) { - return false; - } - - $pattern = self::getHtmlAttributePattern(); - - return (bool)preg_match($pattern, $htmlAttribute); - } - - /** - * Returns information if given html attributes are valid - * - * @param string $htmlAttributes The html attributes to verify - * @return bool - */ - public static function areValidHtmlAttributes($htmlAttributes) - { - /* - * Not a string? - * Nothing to do - */ - if (!is_string($htmlAttributes)) { - return false; - } - - $pattern = self::getHtmlAttributePattern(); - - return (bool)preg_match_all($pattern, $htmlAttributes); - } - - /** - * Returns information if given value is a binary value - * - * @param string $value Value to verify - * @return bool - */ - public static function isBinaryValue($value) - { - /* - * Not a string? - * Nothing to do - */ - if (!is_string($value)) { - return false; - } - - $pattern = self::$patterns['binaryValue']; - - return (bool)preg_match($pattern, $value); - } - - /** - * Returns pattern used to validate / verify size - * - * @param string $separator (optional) Separator used to split width and height. Default: " x ". - * @return string - */ - public static function getSizePattern($separator = ' x ') - { - $escapeMe = [ - '/', - '|', - '.', - '(', - ')', - '[', - ']', - ]; - - $cleanSeparator = trim($separator); - - if (in_array($cleanSeparator, $escapeMe, true)) { - // I have to escape special character of regular expression that may be used as separator - $separator = str_replace($cleanSeparator, '\\' . $cleanSeparator, $separator); - } - - return sprintf(self::$patterns['size'], $separator); - } - - /** - * Returns information if given value is a size value - * - * @param string $value Value to verify - * @param string $separator (optional) Separator used to split width and height. Default: " x ". - * @return bool - */ - public static function isSizeValue($value, $separator = ' x ') - { - /* - * Not a string? - * Nothing to do - */ - if (!is_string($value)) { - return false; - } - - $pattern = self::getSizePattern($separator); - - return (bool)preg_match($pattern, $value); - } - - /** - * Returns slug for given value - * - * @param string $value Value that should be transformed to slug - * @return string|bool - */ - public static function createSlug($value) - { - /* - * Not a scalar value? - * Nothing to do - */ - if (!is_scalar($value)) { - return false; - } - - /* - * It's an empty string? - * Nothing to do - */ - if ('' === $value) { - return ''; - } - - $id = 'Latin-ASCII; NFD; [:Nonspacing Mark:] Remove; NFC; [:Punctuation:] Remove; Lower();'; - $transliterator = \Transliterator::create($id); - - $cleanValue = trim($value); - $result = $transliterator->transliterate($cleanValue); - - return preg_replace('/[-\s]+/', '-', $result); - } } diff --git a/src/Utilities/Repository.php b/src/Utilities/Repository.php index 32dc95c..323d557 100644 --- a/src/Utilities/Repository.php +++ b/src/Utilities/Repository.php @@ -24,7 +24,87 @@ class Repository * * @var string */ - const POSITION_KEY = 'position'; + public const POSITION_KEY = 'position'; + + /** + * Returns query builder for given entity's repository. + * The entity should contain given property, e.g. "name". + * + * @param EntityRepository $repository Repository of the entity + * @param string $property (optional) Name of property used by the ORDER BY clause + * @param string $direction (optional) Direction used by the ORDER BY clause ("ASC" or "DESC") + * @return QueryBuilder + */ + public static function getEntityOrderedQueryBuilder( + EntityRepository $repository, + $property = 'name', + $direction = 'ASC' + ) { + $alias = 'qb'; + $queryBuilder = $repository->createQueryBuilder($alias); + + if (empty($property)) { + return $queryBuilder; + } + + return $queryBuilder->orderBy(sprintf('%s.%s', $alias, $property), $direction); + } + + /** + * Returns extreme position (max or min) of given items + * + * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays + * @param bool $max (optional) If is set to true, maximum value is returned. Otherwise - minimum. + * @return int + */ + public static function getExtremePosition(array $items, $max = true) + { + /* + * No items? + * Nothing to do + */ + if (empty($items)) { + return null; + } + + $extreme = null; + + foreach ($items as $item) { + // Not sortable? + if (!self::isSortable($item)) { + continue; + } + + $position = null; + + // Let's grab the position + if (is_object($item)) { + $position = $item->getPosition(); + } elseif (array_key_exists(static::POSITION_KEY, $item)) { + $position = $item[static::POSITION_KEY]; + } + + // Maximum value is expected? + if ($max) { + // Position was found and it's larger than previously found position (the extreme position)? + if (null === $extreme || (null !== $position && $position > $extreme)) { + $extreme = $position; + } + + continue; + } + + /* + * Minimum value is expected here. + * Position was found and it's smaller than previously found position (the extreme position)? + */ + if (null === $extreme || (null !== $position && $position < $extreme)) { + $extreme = $position; + } + } + + return $extreme; + } /** * Replenishes positions of given items @@ -57,24 +137,17 @@ class Repository } foreach ($items as &$item) { - /* - * The item is not sortable? - */ + // Not sortable? if (!self::isSortable($item)) { continue; } - /* - * Position has been set? - * Nothing to do - */ + // Sorted already (position has been set)? if (self::isSorted($item)) { continue; } - /* - * Calculate position - */ + // Calculate position if ($asLast) { ++$position; } else { @@ -87,6 +160,7 @@ class Repository */ if (is_object($item)) { $item->setPosition($position); + continue; } @@ -98,94 +172,6 @@ class Repository } } - /** - * Returns extreme position (max or min) of given items - * - * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays - * @param bool $max (optional) If is set to true, maximum value is returned. Otherwise - minimum. - * @return int - */ - public static function getExtremePosition(array $items, $max = true) - { - /* - * No items? - * Nothing to do - */ - if (empty($items)) { - return null; - } - - $extreme = null; - - foreach ($items as $item) { - /* - * The item is not sortable? - */ - if (!self::isSortable($item)) { - continue; - } - - $position = null; - - /* - * Let's grab the position - */ - if (is_object($item)) { - $position = $item->getPosition(); - } elseif (array_key_exists(static::POSITION_KEY, $item)) { - $position = $item[static::POSITION_KEY]; - } - - /* - * Maximum value is expected? - */ - if ($max) { - /* - * Position was found and it's larger than previously found position (the extreme position)? - */ - if (null === $extreme || (null !== $position && $position > $extreme)) { - $extreme = $position; - } - - continue; - } - - /* - * Minimum value is expected here. - * Position was found and it's smaller than previously found position (the extreme position)? - */ - if (null === $extreme || (null !== $position && $position < $extreme)) { - $extreme = $position; - } - } - - return $extreme; - } - - /** - * Returns query builder for given entity's repository. - * The entity should contain given property, e.g. "name". - * - * @param EntityRepository $repository Repository of the entity - * @param string $property (optional) Name of property used by the ORDER BY clause - * @param string $direction (optional) Direction used by the ORDER BY clause ("ASC" or "DESC") - * @return QueryBuilder - */ - public static function getEntityOrderedQueryBuilder( - EntityRepository $repository, - $property = 'name', - $direction = 'ASC' - ) { - $alias = 'qb'; - $queryBuilder = $repository->createQueryBuilder($alias); - - if (empty($property)) { - return $queryBuilder; - } - - return $queryBuilder->orderBy(sprintf('%s.%s', $alias, $property), $direction); - } - /** * Returns information if given item is sortable * @@ -218,18 +204,6 @@ class Repository */ private static function isSorted($item) { - /* - * Given item is not sortable? - */ - if (!self::isSortable($item)) { - return false; - } - - /* - * It's an object or it's an array - * and position has been set? - */ - return (is_object($item) && null !== $item->getPosition()) || diff --git a/src/Utilities/Uri.php b/src/Utilities/Uri.php index ebc1142..d740f0b 100644 --- a/src/Utilities/Uri.php +++ b/src/Utilities/Uri.php @@ -9,13 +9,52 @@ namespace Meritoo\Common\Utilities; /** - * Useful uri methods (only static functions) + * Useful methods related to uri * * @author Meritoo * @copyright Meritoo */ class Uri { + /** + * Adds protocol to given url, if the url does not contain given protocol. + * Returns the new url. + * + * @param string $url Url string + * @param string $protocol (optional) Protocol string + * @return string + */ + public static function addProtocolToUrl($url, $protocol = 'http') + { + $pattern = sprintf('/^%s.*/', $protocol); + + if ((bool) preg_match($pattern, $url)) { + return $url; + } + + return sprintf('%s://%s', $protocol, $url); + } + + public static function buildUrl(string $rootUrl, string ...$urlParts): string + { + $rootUrl = Regex::clearEndingSlash($rootUrl); + + if (empty($urlParts) || Arrays::containsEmptyStringsOnly($urlParts)) { + return $rootUrl; + } + + array_walk($urlParts, static function (&$part) { + $part = Regex::clearBeginningSlash($part); + $part = Regex::clearEndingSlash($part); + }); + + return sprintf( + '%s/%s', + $rootUrl, + implode('/', $urlParts) + ); + } + /** * Returns full uri string * @@ -42,7 +81,75 @@ class Uri return $requestedUrl; } - return self::getServerNameOrIp(true) . $requestedUrl; + return self::getServerNameOrIp(true).$requestedUrl; + } + + /** + * Returns protocol name + * + * @return string + */ + public static function getProtocolName() + { + $matches = []; + $protocolData = Miscellaneous::getSafelyGlobalVariable(INPUT_SERVER, 'SERVER_PROTOCOL'); // e.g. HTTP/1.1 + $matchCount = preg_match('|(.+)\/(.+)|', $protocolData, $matches); + + /* + * $matches[1] - protocol name, e.g. HTTP + * $matches[2] - protocol version, e.g. 1.1 + */ + + // Oops, cannot match protocol + if (0 === $matchCount) { + return ''; + } + + return strtolower($matches[1]); + } + + /** + * Returns http referer uri + * + * @return string + */ + public static function getRefererUri() + { + return Miscellaneous::getSafelyGlobalVariable(INPUT_SERVER, 'HTTP_REFERER'); + } + + /** + * Returns url to resource secured by given htpasswd login and password + * + * @param string $url A path / url to some resource, e.g. page, image, css file + * @param string $user (optional) User name used to log in + * @param string $password (optional) User password used to log in + * @return string + */ + public static function getSecuredUrl($url, $user = '', $password = '') + { + /* + * Url is not provided? + * Nothing to do + */ + if (empty($url)) { + return ''; + } + + $protocol = self::getProtocolName(); + $host = self::getServerNameOrIp(); + + if (!Regex::startsWith($url, '/')) { + $url = sprintf('/%s', $url); + } + + $url = $host.$url; + + if (!empty($user) && !empty($password)) { + $url = sprintf('%s:%s@%s', $user, $password, $url); + } + + return sprintf('%s://%s', $protocol, $url); } /** @@ -74,42 +181,6 @@ class Uri return $host; } - /** - * Returns protocol name - * - * @return string - */ - public static function getProtocolName() - { - $matches = []; - $protocolData = Miscellaneous::getSafelyGlobalVariable(INPUT_SERVER, 'SERVER_PROTOCOL'); // e.g. HTTP/1.1 - $matchCount = preg_match('|(.+)\/(.+)|', $protocolData, $matches); - - /* - * $matches[1] - protocol name, e.g. HTTP - * $matches[2] - protocol version, e.g. 1.1 - */ - - /* - * Oops, cannot match protocol - */ - if (0 === $matchCount) { - return ''; - } - - return strtolower($matches[1]); - } - - /** - * Returns http referer uri - * - * @return string - */ - public static function getRefererUri() - { - return Miscellaneous::getSafelyGlobalVariable(INPUT_SERVER, 'HTTP_REFERER'); - } - /** * Returns user's IP address * @@ -121,35 +192,26 @@ class Uri } /** - * Returns name and version of user's web browser + * Returns name of user's operating system * - * @param bool $withVersion (optional) If is set to true, version of the browser is returned too. Otherwise - - * name only. * @return string */ - public static function getUserWebBrowserName($withVersion = false) + public static function getUserOperatingSystemName() { $info = self::getUserWebBrowserInfo(); - $knownBrowsers = [ - 'Firefox/([\d\.]+)$' => 'Mozilla Firefox', - 'OPR/([\d\.]+)$' => 'Opera', - 'Chrome/([\d\.]+)$' => 'Google Chrome', - 'Safari/([\d\.]+)$' => 'Apple Safari', + $knownSystems = [ + 'Linux' => 'Linux', + 'Win' => 'Windows', + 'Mac' => 'Mac OS', ]; - foreach ($knownBrowsers as $pattern => $browserName) { + foreach ($knownSystems as $pattern => $systemName) { $matches = []; $matchCount = preg_match(sprintf('|%s|', $pattern), $info, $matches); if ($matchCount > 0) { - if ($withVersion) { - $version = $matches[1]; - - return sprintf('%s %s', $browserName, $version); - } - - return $browserName; + return $systemName; } } @@ -183,48 +245,41 @@ class Uri } /** - * Returns name of user's operating system + * Returns name and version of user's web browser * + * @param bool $withVersion (optional) If is set to true, version of the browser is returned too. Otherwise - + * name only. * @return string */ - public static function getUserOperatingSystemName() + public static function getUserWebBrowserName($withVersion = false) { $info = self::getUserWebBrowserInfo(); - $knownSystems = [ - 'Linux' => 'Linux', - 'Win' => 'Windows', - 'Mac' => 'Mac OS', + $knownBrowsers = [ + 'Firefox/([\d\.]+)$' => 'Mozilla Firefox', + 'OPR/([\d\.]+)$' => 'Opera', + 'Chrome/([\d\.]+)$' => 'Google Chrome', + 'Safari/([\d\.]+)$' => 'Apple Safari', ]; - foreach ($knownSystems as $pattern => $systemName) { + foreach ($knownBrowsers as $pattern => $browserName) { $matches = []; $matchCount = preg_match(sprintf('|%s|', $pattern), $info, $matches); if ($matchCount > 0) { - return $systemName; + if ($withVersion) { + $version = $matches[1]; + + return sprintf('%s %s', $browserName, $version); + } + + return $browserName; } } return ''; } - /** - * Returns information if running server is localhost - * - * @return bool - */ - public static function isServerLocalhost() - { - $serverNameOrIp = strtolower(self::getServerNameOrIp()); - - return in_array($serverNameOrIp, [ - 'localhost', - '127.0.0.1', - '127.0.1.1', - ]); - } - /** * Returns information if given url is external, from another server / domain * @@ -244,9 +299,7 @@ class Uri $currentUrl = self::getServerNameOrIp(true); $url = self::replenishProtocol($url); - /* - * Let's prepare pattern of current url - */ + // Let's prepare pattern of current url $search = [ ':', '/', @@ -264,6 +317,22 @@ class Uri return !Regex::contains($url, $currentUrlPattern); } + /** + * Returns information if running server is localhost + * + * @return bool + */ + public static function isServerLocalhost() + { + $serverNameOrIp = strtolower(self::getServerNameOrIp()); + + return in_array($serverNameOrIp, [ + 'localhost', + '127.0.0.1', + '127.0.1.1', + ]); + } + /** * Replenishes protocol in the given url * @@ -274,9 +343,7 @@ class Uri */ public static function replenishProtocol($url, $protocol = '') { - /* - * Let's trim the url - */ + // Let's trim the url if (is_string($url)) { $url = trim($url); } @@ -297,66 +364,11 @@ class Uri return $url; } - /* - * Protocol is not provided? - */ + // Protocol is not provided? if (empty($protocol)) { $protocol = self::getProtocolName(); } return sprintf('%s://%s', $protocol, $url); } - - /** - * Returns url to resource secured by given htpasswd login and password - * - * @param string $url A path / url to some resource, e.g. page, image, css file - * @param string $user (optional) User name used to log in - * @param string $password (optional) User password used to log in - * @return string - */ - public static function getSecuredUrl($url, $user = '', $password = '') - { - /* - * Url is not provided? - * Nothing to do - */ - if (empty($url)) { - return ''; - } - - $protocol = self::getProtocolName(); - $host = self::getServerNameOrIp(); - - if (!Regex::startsWith($url, '/')) { - $url = sprintf('/%s', $url); - } - - $url = $host . $url; - - if (!empty($user) && !empty($password)) { - $url = sprintf('%s:%s@%s', $user, $password, $url); - } - - return sprintf('%s://%s', $protocol, $url); - } - - /** - * Adds protocol to given url, if the url does not contain given protocol. - * Returns the new url. - * - * @param string $url Url string - * @param string $protocol (optional) Protocol string - * @return string - */ - public static function addProtocolToUrl($url, $protocol = 'http') - { - $pattern = sprintf('/^%s.*/', $protocol); - - if ((bool)preg_match($pattern, $url)) { - return $url; - } - - return sprintf('%s://%s', $protocol, $url); - } } diff --git a/src/ValueObject/Address.php b/src/ValueObject/Address.php index 337f7ca..708c64b 100644 --- a/src/ValueObject/Address.php +++ b/src/ValueObject/Address.php @@ -88,13 +88,33 @@ class Address } /** - * Returns street + * Returns number of building * * @return string */ - public function getStreet() + public function getBuildingNumber() { - return $this->street; + return $this->buildingNumber; + } + + /** + * Returns city, location + * + * @return string + */ + public function getCity() + { + return $this->city; + } + + /** + * Returns number of flat + * + * @return string + */ + public function getFlatNumber() + { + return $this->flatNumber; } /** @@ -118,23 +138,13 @@ class Address } /** - * Returns number of building + * Returns street * * @return string */ - public function getBuildingNumber() + public function getStreet() { - return $this->buildingNumber; - } - - /** - * Returns number of flat - * - * @return string - */ - public function getFlatNumber() - { - return $this->flatNumber; + return $this->street; } /** @@ -146,14 +156,4 @@ class Address { return $this->zipCode; } - - /** - * Returns city, location - * - * @return string - */ - public function getCity() - { - return $this->city; - } } diff --git a/src/ValueObject/BankAccount.php b/src/ValueObject/BankAccount.php index 0b2c83f..db80cce 100644 --- a/src/ValueObject/BankAccount.php +++ b/src/ValueObject/BankAccount.php @@ -59,16 +59,6 @@ class BankAccount return Arrays::getNonEmptyValuesAsString($values); } - /** - * Returns name of bank - * - * @return string - */ - public function getBankName() - { - return $this->bankName; - } - /** * Returns number of bank's account * @@ -78,4 +68,14 @@ class BankAccount { return $this->accountNumber; } + + /** + * Returns name of bank + * + * @return string + */ + public function getBankName() + { + return $this->bankName; + } } diff --git a/src/ValueObject/Company.php b/src/ValueObject/Company.php index 05facea..ebe1aac 100644 --- a/src/ValueObject/Company.php +++ b/src/ValueObject/Company.php @@ -44,7 +44,7 @@ class Company * * @param string $name Name of company * @param Address $address Address of company - * @param BankAccount|null $bankAccount (optional) Bank account of company + * @param null|BankAccount $bankAccount (optional) Bank account of company */ public function __construct($name, Address $address, BankAccount $bankAccount = null) { @@ -69,16 +69,6 @@ class Company return Arrays::getNonEmptyValuesAsString($values); } - /** - * Returns name of company - * - * @return string - */ - public function getName() - { - return $this->name; - } - /** * Returns address of company * @@ -92,10 +82,20 @@ class Company /** * Returns bank account of company * - * @return BankAccount|null + * @return null|BankAccount */ public function getBankAccount() { return $this->bankAccount; } + + /** + * Returns name of company + * + * @return string + */ + public function getName() + { + return $this->name; + } } diff --git a/src/ValueObject/Size.php b/src/ValueObject/Size.php index 1336ca1..b6f4cd0 100644 --- a/src/ValueObject/Size.php +++ b/src/ValueObject/Size.php @@ -62,8 +62,8 @@ class Size */ private function __construct($width = null, $height = null, $unit = 'px') { - $width = (int)$width; - $height = (int)$height; + $width = (int) $width; + $height = (int) $height; if ($width < 0 || $height < 0) { throw new InvalidSizeDimensionsException($width, $height); @@ -88,14 +88,84 @@ class Size } /** - * Sets separator used when converting to string + * Creates new instance from given array * - * @param string $separator The separator + * The array should contain 2 elements: width and height. + * Examples: ['800', '600'], [800, 600]. + * + * @param array $array The size represented as array + * @param string $unit (optional) Unit used when width or height should be returned with unit. Default: "px". + * @return null|Size + */ + public static function fromArray(array $array, $unit = 'px') + { + // Requirements for given array: + // - indexes "0" and "1" + // - should contains exactly 2 elements + if ( + array_key_exists(0, $array) + && array_key_exists(1, $array) + && 2 === count($array) + ) { + [$width, $height] = $array; + + return new self($width, $height, $unit); + } + + return null; + } + + /** + * Creates new instance from given string + * + * @param string $size The size represented as string (width and height separated by given separator) + * @param string $unit (optional) Unit used when width or height should be returned with unit. Default: "px". + * @param string $separator (optional) Separator used to split width and height. Default: " x ". + * @return null|Size + */ + public static function fromString($size, $unit = 'px', $separator = ' x ') + { + if (is_string($size)) { + $matches = []; + $pattern = Regex::getSizePattern($separator); + + if ((bool) preg_match($pattern, $size, $matches)) { + $width = (int) $matches[1]; + $height = (int) $matches[2]; + $sizeObject = new self($width, $height, $unit); + + return $sizeObject->setSeparator($separator); + } + } + + return null; + } + + /** + * Returns the height + * + * @param bool $withUnit (optional) If is set to true, height is returned with unit ("px"). Otherwise - without + * (default behaviour). + * @return int|string + */ + public function getHeight($withUnit = false) + { + if ($withUnit) { + return sprintf('%d %s', $this->height, $this->unit); + } + + return $this->height; + } + + /** + * Sets the height + * + * @param int $height The height * @return Size */ - public function setSeparator($separator) + public function setHeight($height) { - $this->separator = $separator; + $this->height = (int) $height; return $this; } @@ -124,55 +194,24 @@ class Size */ public function setWidth($width) { - $this->width = (int)$width; + $this->width = (int) $width; return $this; } /** - * Returns the height + * Sets separator used when converting to string * - * @param bool $withUnit (optional) If is set to true, height is returned with unit ("px"). Otherwise - without - * (default behaviour). - * @return int|string - */ - public function getHeight($withUnit = false) - { - if ($withUnit) { - return sprintf('%d %s', $this->height, $this->unit); - } - - return $this->height; - } - - /** - * Sets the height - * - * @param int $height The height + * @param string $separator The separator * @return Size */ - public function setHeight($height) + public function setSeparator($separator) { - $this->height = (int)$height; + $this->separator = $separator; return $this; } - /** - * Returns string representation of instance of this class, e.g. '200 x 100' or '200x100' - * - * @param bool $withUnit (optional) If is set to true, width and height are returned with unit ("px"). Otherwise - * - without (default behaviour). - * @return string - */ - public function toString($withUnit = false) - { - $width = $this->getWidth($withUnit); - $height = $this->getHeight($withUnit); - - return sprintf('%s%s%s', $width, $this->separator, $height); - } - /** * Returns instance of this class as an array. * Values of the array are width and height, eg. [800, 600] or ['800px', '600px']. @@ -190,56 +229,17 @@ class Size } /** - * Creates new instance from given string + * Returns string representation of instance of this class, e.g. '200 x 100' or '200x100' * - * @param string $size The size represented as string (width and height separated by given separator) - * @param string $unit (optional) Unit used when width or height should be returned with unit. Default: "px". - * @param string $separator (optional) Separator used to split width and height. Default: " x ". - * @return Size|null + * @param bool $withUnit (optional) If is set to true, width and height are returned with unit ("px"). Otherwise + * - without (default behaviour). + * @return string */ - public static function fromString($size, $unit = 'px', $separator = ' x ') + public function toString($withUnit = false) { - if (is_string($size)) { - $matches = []; - $pattern = Regex::getSizePattern($separator); + $width = $this->getWidth($withUnit); + $height = $this->getHeight($withUnit); - if ((bool)preg_match($pattern, $size, $matches)) { - $width = (int)$matches[1]; - $height = (int)$matches[2]; - $sizeObject = new self($width, $height, $unit); - - return $sizeObject->setSeparator($separator); - } - } - - return null; - } - - /** - * Creates new instance from given array - * - * The array should contain 2 elements: width and height. - * Examples: ['800', '600'], [800, 600]. - * - * @param array $array The size represented as array - * @param string $unit (optional) Unit used when width or height should be returned with unit. Default: "px". - * @return Size|null - */ - public static function fromArray(array $array, $unit = 'px') - { - // Requirements for given array: - // - indexes "0" and "1" - // - should contains exactly 2 elements - if ( - array_key_exists(0, $array) - && array_key_exists(1, $array) - && 2 === count($array) - ) { - list($width, $height) = $array; - - return new self($width, $height, $unit); - } - - return null; + return sprintf('%s%s%s', $width, $this->separator, $height); } } diff --git a/src/ValueObject/Template.php b/src/ValueObject/Template.php new file mode 100644 index 0000000..98cecf5 --- /dev/null +++ b/src/ValueObject/Template.php @@ -0,0 +1,151 @@ + + * @copyright Meritoo + */ +class Template +{ + /** + * Tag used at beginning and ending of placeholder + * + * @var string + */ + private const PLACEHOLDER_TAG = '%'; + + /** + * Raw string with placeholders (content of the template) + * + * @var string + */ + private $content; + + /** + * Class constructor + * + * @param string $content Raw string with placeholders (content of the template) + * @throws InvalidContentException + */ + public function __construct(string $content) + { + if (!static::isValid($content)) { + throw InvalidContentException::create($content); + } + + $this->content = $content; + } + + /** + * Returns content of the template filled with given values (by replacing placeholders with their proper values) + * + * @param array $values Pairs of key-value where: key - name of placeholder, value - value of the placeholder + * @return string + * @throws MissingPlaceholdersInValuesException + */ + public function fill(array $values): string + { + $placeholders = static::getPlaceholders($this->content); + $providedPlaceholders = array_keys($values); + $missingPlaceholders = array_diff($placeholders[1], $providedPlaceholders); + + // Oops, there are placeholders without values (iow. provided values are different than placeholders) + if (!empty($missingPlaceholders)) { + throw MissingPlaceholdersInValuesException::create($this->content, $missingPlaceholders); + } + + $result = $this->content; + + foreach ($placeholders[0] as $index => $placeholder) { + $placeholderName = $placeholders[1][$index]; + + if (isset($values[$placeholderName])) { + $value = $values[$placeholderName]; + $result = str_replace($placeholder, (string) $value, $result); + } + } + + return $result; + } + + /** + * Returns regular expression that defines format of placeholder + * + * Expectations: + * - surrounded by the placeholder's tags (at beginning and at the end) + * - at least 1 character + * - no placeholder's tag inside name of placeholder + * + * Invalid placeholders: + * - test + * - test% + * - % test% + * + * Valid placeholders: + * - %test% + * - %another_test% + * - %another-test% + * - %anotherTest% + * - %another test% + * + * @return string + */ + private static function getPlaceholderPattern(): string + { + return sprintf( + '/%s([^%s]+)%s/', + static::PLACEHOLDER_TAG, + static::PLACEHOLDER_TAG, + static::PLACEHOLDER_TAG + ); + } + + /** + * Returns placeholders of given template + * + * @param string $content Content of template + * @return array + */ + private static function getPlaceholders(string $content): array + { + $result = []; + $matchCount = preg_match_all(static::getPlaceholderPattern(), $content, $result); + + if (false !== $matchCount && 0 < $matchCount) { + foreach ($result as $index => $placeholders) { + $result[$index] = array_unique($placeholders); + } + } + + return $result; + } + + /** + * Returns information if given template is valid + * + * @param string $content Raw string with placeholders to validate (content of the template) + * @return bool + */ + private static function isValid(string $content): bool + { + if ('' === $content) { + return false; + } + + return (bool) preg_match_all(static::getPlaceholderPattern(), $content); + } +} diff --git a/src/ValueObject/Version.php b/src/ValueObject/Version.php index 55e7cb4..344f95b 100644 --- a/src/ValueObject/Version.php +++ b/src/ValueObject/Version.php @@ -54,6 +54,96 @@ class Version $this->patchPart = $patchPart; } + /** + * Returns representation of object as string + * + * @return string + */ + public function __toString() + { + return sprintf('%d.%d.%d', $this->getMajorPart(), $this->getMinorPart(), $this->getPatchPart()); + } + + /** + * Returns new instance based on given version as array. + * Given version should contain 3 integers, 1 per each part ("major", "minor" and "patch"). + * + * Examples: + * [1, 0, 2]; + * [10, 4, 0]; + * + * @param array $version The version + * @return null|Version + */ + public static function fromArray(array $version) + { + /* + * No version provided? + * Nothing to do + */ + if (empty($version)) { + return null; + } + + $count = count($version); + + /* + * Incorrect version? + * Nothing to do + */ + if (3 !== $count) { + return null; + } + + $majorPart = (int) $version[0]; + $minorPart = (int) $version[1]; + $patchPart = (int) $version[2]; + + return new static($majorPart, $minorPart, $patchPart); + } + + /** + * Returns new instance based on given version as string. + * Given version should contain 3 dot-separated integers, 1 per each part ("major", "minor" and "patch"). + * + * Examples: + * "1.0.2"; + * "10.4.0"; + * + * @param string $version The version + * @return null|Version + */ + public static function fromString(string $version) + { + $version = trim($version); + + /* + * No version provided? + * Nothing to do + */ + if (empty($version)) { + return null; + } + + $matches = []; + $pattern = '/^(\d+)\.(\d+)\.(\d+)$/'; // e.g. "1.0.2" + $matched = preg_match($pattern, $version, $matches); + + /* + * Incorrect version? + * Nothing to do + */ + if (0 === $matched || false === $matched) { + return null; + } + + $majorPart = (int) $matches[1]; + $minorPart = (int) $matches[2]; + $patchPart = (int) $matches[3]; + + return new static($majorPart, $minorPart, $patchPart); + } + /** * Returns the "major" part. * Incremented when you make incompatible API changes. @@ -86,94 +176,4 @@ class Version { return $this->patchPart; } - - /** - * Returns representation of object as string - * - * @return string - */ - public function __toString() - { - return sprintf('%d.%d.%d', $this->getMajorPart(), $this->getMinorPart(), $this->getPatchPart()); - } - - /** - * Returns new instance based on given version as string. - * Given version should contain 3 dot-separated integers, 1 per each part ("major", "minor" and "patch"). - * - * Examples: - * "1.0.2"; - * "10.4.0"; - * - * @param string $version The version - * @return Version|null - */ - public static function fromString($version) - { - $version = trim($version); - - /* - * No version provided? - * Nothing to do - */ - if (empty($version)) { - return null; - } - - $matches = []; - $pattern = '/^(\d+)\.(\d+)\.(\d+)$/'; // e.g. "1.0.2" - $matched = preg_match($pattern, $version, $matches); - - /* - * Incorrect version? - * Nothing to do - */ - if (0 === $matched || false === $matched) { - return null; - } - - $majorPart = (int)$matches[1]; - $minorPart = (int)$matches[2]; - $patchPart = (int)$matches[3]; - - return new static($majorPart, $minorPart, $patchPart); - } - - /** - * Returns new instance based on given version as array. - * Given version should contain 3 integers, 1 per each part ("major", "minor" and "patch"). - * - * Examples: - * [1, 0, 2]; - * [10, 4, 0]; - * - * @param array $version The version - * @return Version|null - */ - public static function fromArray(array $version) - { - /* - * No version provided? - * Nothing to do - */ - if (empty($version)) { - return null; - } - - $count = count($version); - - /* - * Incorrect version? - * Nothing to do - */ - if (3 !== $count) { - return null; - } - - $majorPart = (int)$version[0]; - $minorPart = (int)$version[1]; - $patchPart = (int)$version[2]; - - return new static($majorPart, $minorPart, $patchPart); - } } diff --git a/tests/Collection/BaseCollection/FirstNamesCollection.php b/tests/Collection/BaseCollection/FirstNamesCollection.php new file mode 100644 index 0000000..1dc7c56 --- /dev/null +++ b/tests/Collection/BaseCollection/FirstNamesCollection.php @@ -0,0 +1,42 @@ + + * @copyright Meritoo + * + * @internal + * @coversNothing + */ +class FirstNamesCollection extends BaseCollection +{ + protected function isValidType($element): bool + { + return $element instanceof User; + } + + protected function prepareElements(array $elements): array + { + $result = []; + + /** @var User $element */ + foreach ($elements as $element) { + $result[] = $element->getFirstName(); + } + + return $result; + } +} diff --git a/tests/Collection/BaseCollection/User.php b/tests/Collection/BaseCollection/User.php new file mode 100644 index 0000000..642ef56 --- /dev/null +++ b/tests/Collection/BaseCollection/User.php @@ -0,0 +1,45 @@ + + * @copyright Meritoo + * + * @internal + * @coversNothing + */ +final class User +{ + /** @var string */ + private $firstName; + + /** @var string */ + private $lastName; + + public function __construct(string $firstName, string $lastName) + { + $this->firstName = $firstName; + $this->lastName = $lastName; + } + + public function getFirstName(): string + { + return $this->firstName; + } + + public function getLastName(): string + { + return $this->lastName; + } +} diff --git a/tests/Collection/BaseCollectionTest.php b/tests/Collection/BaseCollectionTest.php new file mode 100644 index 0000000..77c7170 --- /dev/null +++ b/tests/Collection/BaseCollectionTest.php @@ -0,0 +1,806 @@ + + * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Collection\BaseCollection + */ +class BaseCollectionTest extends BaseTestCase +{ + /** + * An empty collection + * + * @var StringCollection + */ + private $emptyCollection; + + /** + * Simple collection + * + * @var StringCollection + */ + private $simpleCollection; + + /** + * Elements of simple collection + * + * @var array + */ + private $simpleElements; + + public function provideElementGetByIndex() + { + yield [ + 'An empty collection and empty index', + new StringCollection(), + '', + null, + ]; + + yield [ + 'An empty collection and non-empty index', + new StringCollection(), + 'test', + null, + ]; + + yield [ + 'Non-empty collection and not existing index', + new StringCollection([ + 'lorem' => 'ipsum', + 'dolor' => 'sit', + ]), + 'test', + null, + ]; + + yield [ + 'Collection with existing index', + new StringCollection([ + 'lorem' => 'ipsum', + 'dolor' => 'sit', + ]), + 'lorem', + 'ipsum', + ]; + + yield [ + 'Collection with existing index (collection of arrays)', + new FirstNamesCollection([ + new User('John', 'Scott'), + new User('Jane', 'Brown'), + ]), + 0, + 'John', + ]; + + yield [ + 'Collection with existing index (collection of objects)', + new DateTimeCollection([ + 'x' => new DateTime(), + 'y' => new DateTime('2001-01-01'), + 'z' => new DateTime('yesterday'), + ]), + 'y', + new DateTime('2001-01-01'), + ]; + + yield [ + 'Collection with first names', + new FirstNamesCollection([ + new User('John', 'Scott'), + new User('Jane', 'Brown'), + ]), + 1, + 'Jane', + ]; + } + + /** + * Provides element to add to collection + * + * @return Generator + */ + public function provideElementToAdd() + { + yield [ + 'This is test 1', + 1, + 0, + new StringCollection(), + ]; + + yield [ + 'This is test 2', + 2, + 1, + new StringCollection([ + 'I am 1st', + ]), + ]; + + yield [ + 'This is test 3', + 3, + 2, + new StringCollection([ + 'I am 1st', + 'I am 2nd', + ]), + ]; + } + + /** + * Provides element with index to add to collection + * + * @return Generator + */ + public function provideElementToAddWithIndex() + { + yield [ + 'This is test 1', + 'test1', + 1, + 'test1', + new StringCollection(), + ]; + + yield [ + 'This is test 2', + 'test2', + 2, + 'test2', + new StringCollection([ + 'test1' => 'I am 1st', + ]), + ]; + + yield [ + 'This is test 3', + null, + 3, + 0, + new StringCollection([ + 'test1' => 'I am 1st', + 'test2' => 'I am 2nd', + ]), + ]; + + yield [ + 'This is test 4', + '', + 4, + '', + new StringCollection([ + 'test1' => 'I am 1st', + 'test2' => 'I am 2nd', + 'I am 3rd', + ]), + ]; + + yield [ + 'This is test 5', + 'test5', + 5, + 'test5', + new StringCollection([ + 'test1' => 'I am 1st', + 'test2' => 'I am 2nd', + 2 => 'I am 3rd', + 3 => 'I am 4th', + ]), + ]; + + yield [ + 'This is test 6', + 'test2', + 4, + 'test2', + new StringCollection([ + 'test1' => 'I am 1st', + 'test2' => 'I am 2nd', + 2 => 'I am 3rd', + 3 => 'I am 4th', + ]), + ]; + } + + public function provideElementToAddWithInvalidType(): ?Generator + { + yield [ + ['test'], + 0, + new StringCollection(), + ]; + + yield [ + 123, + 2, + new StringCollection([ + 'I am 1st', + 'I am 2nd', + ]), + ]; + } + + public function provideElementsToValidateType(): ?Generator + { + yield [ + 'An empty array', + [], + [], + ]; + + yield [ + 'Valid elements only', + [ + new User('John', 'Scott'), + new User('Jane', 'Brown'), + ], + [ + 'John', + 'Jane', + ], + ]; + + yield [ + 'Mixed elements', + [ + 1, + 'test', + '', + new User('John', 'Scott'), + [], + 234, + 'test', + new User('Jane', 'Brown'), + ], + [ + 'John', + 'Jane', + ], + ]; + } + + public function provideResultOfLimit(): ?Generator + { + yield 'Negative value of maximum & negative offset' => [ + [], + -1, + -2, + ]; + + yield 'Negative value of maximum & positive offset' => [ + [], + -1, + 2, + ]; + + yield 'Maximum set to 0 & negative offset' => [ + [], + 0, + -2, + ]; + + yield 'Maximum set to 0 & positive offset' => [ + [], + 0, + 2, + ]; + + yield 'Maximum set to 1 & offset set to 2' => [ + [ + 123 => 'dolor', + ], + 1, + 2, + ]; + + yield 'Maximum set to 2 & offset set to 2' => [ + [ + 123 => 'dolor', + 345 => 'sit', + ], + 2, + 2, + ]; + + yield 'Maximum set to 3 & offset set to latest element' => [ + [ + 346 => 'adipiscing elit', + ], + 3, + 6, + ]; + + yield 'Maximum set to 1 & offset greater than size of collection' => [ + [], + 1, + 10, + ]; + } + + public function provideResultOfLimitWithDefaultOffset(): ?Generator + { + yield 'Negative value of maximum' => [ + [], + -1, + ]; + + yield 'Maximum set to 0' => [ + [], + 0, + ]; + + yield 'Maximum set to 1' => [ + [ + 'lorem', + ], + 1, + ]; + + yield 'Maximum set to 3' => [ + [ + 'lorem', + 'ipsum', + 123 => 'dolor', + ], + 3, + ]; + + yield 'Maximum greater than size of collection' => [ + [ + 'lorem', + 'ipsum', + 123 => 'dolor', + 345 => 'sit', + 'a' => 'amet', + 'c' => 'consectetur', + 346 => 'adipiscing elit', + ], + 10, + ]; + } + + public function testAddMultiple() + { + $elements = [ + 'test1', + 'test2', + 1234 => 'test3', + 5678 => 'test4', + ]; + + $this->emptyCollection->addMultiple($elements); + + static::assertFalse($this->emptyCollection->isEmpty()); + static::assertSame(4, $this->emptyCollection->count()); + + static::assertSame('test1', $this->emptyCollection[0]); + static::assertSame('test2', $this->emptyCollection[1]); + static::assertSame('test3', $this->emptyCollection[2]); + static::assertSame('test4', $this->emptyCollection[3]); + } + + public function testAddMultipleUsingEmptyArray() + { + $this->emptyCollection->addMultiple([]); + + static::assertSame(0, $this->emptyCollection->count()); + static::assertTrue($this->emptyCollection->isEmpty()); + } + + public function testAddMultipleUsingIndexes() + { + $elements = [ + 'test1', + 'test2', + 1234 => 'test3', + 5678 => 'test4', + ]; + + $this->emptyCollection->addMultiple($elements, true); + + static::assertFalse($this->emptyCollection->isEmpty()); + static::assertSame(4, $this->emptyCollection->count()); + + static::assertSame('test1', $this->emptyCollection[0]); + static::assertSame('test2', $this->emptyCollection[1]); + static::assertSame('test3', $this->emptyCollection[1234]); + static::assertSame('test4', $this->emptyCollection[5678]); + } + + /** + * @param mixed $element The element to add + * @param mixed $index Index of element to add + * @param int $expectedCount Expected count of elements in collection + * @param int $expectedIndex Expected index of added element in collection + * @param CollectionInterface $collection The collection + * + * @dataProvider provideElementToAddWithIndex + */ + public function testAddWithIndex($element, $index, $expectedCount, $expectedIndex, CollectionInterface $collection) + { + $collection->add($element, $index); + + static::assertTrue($collection->has($element)); + static::assertSame($expectedCount, $collection->count()); + static::assertSame($element, $collection[$expectedIndex]); + } + + /** + * @param mixed $element The element to add + * @param int $expectedCount Expected count of elements in collection + * @param CollectionInterface $collection The collection + * + * @dataProvider provideElementToAddWithInvalidType + */ + public function testAddWithInvalidType( + $element, + int $expectedCount, + CollectionInterface $collection + ): void { + $collection->add($element); + + static::assertFalse($collection->has($element)); + static::assertSame($expectedCount, $collection->count()); + } + + /** + * @param mixed $element The element to add + * @param int $expectedCount Expected count of elements in collection + * @param int $expectedIndex Expected index of added element in collection + * @param CollectionInterface $collection The collection + * + * @dataProvider provideElementToAdd + */ + public function testAddWithoutIndex( + $element, + int $expectedCount, + int $expectedIndex, + CollectionInterface $collection + ) { + $collection->add($element); + + static::assertTrue($collection->has($element)); + static::assertSame($expectedCount, $collection->count()); + static::assertSame($element, $collection[$expectedIndex]); + } + + public function testClear(): void + { + self::assertCount(7, $this->simpleCollection); + $this->simpleCollection->clear(); + self::assertCount(0, $this->simpleCollection); + } + + public function testClearIfIsEmpty(): void + { + self::assertCount(0, $this->emptyCollection); + $this->emptyCollection->clear(); + self::assertCount(0, $this->emptyCollection); + } + + public function testCount() + { + static::assertSame(0, $this->emptyCollection->count()); + static::assertSame(7, $this->simpleCollection->count()); + } + + public function testEmptyCollection() + { + static::assertSame(0, $this->emptyCollection->count()); + static::assertCount(0, $this->emptyCollection); + static::assertEmpty($this->emptyCollection); + + static::assertTrue($this->emptyCollection->isEmpty()); + static::assertSame([], $this->emptyCollection->toArray()); + static::assertEmpty($this->emptyCollection->toArray()); + + static::assertNull($this->emptyCollection->getFirst()); + static::assertNull($this->emptyCollection->getLast()); + static::assertNull($this->emptyCollection[1]); + static::assertNull($this->emptyCollection['abc']); + } + + public function testExistsVisibilityAndArguments() + { + $reflectionClass = new ReflectionClass(BaseCollection::class); + $method = $reflectionClass->getMethod('exists'); + + static::assertMethodVisibility($method, OopVisibilityType::IS_PRIVATE); + static::assertMethodArgumentsCount($method, 1, 1); + } + + /** + * @param string $description Description of test + * @param CollectionInterface $collection Collection to search for element with given index + * @param mixed $index Index / key of the element + * @param mixed $expected Expected element with given index + * + * @dataProvider provideElementGetByIndex + */ + public function testGetByIndex($description, CollectionInterface $collection, $index, $expected) + { + static::assertEquals($expected, $collection->getByIndex($index), $description); + } + + /** + * @param string $description + * @param array $elements + * @param array $expected + * + * @dataProvider provideElementsToValidateType + */ + public function testGetElementsWithValidType(string $description, array $elements, array $expected): void + { + $collection = new FirstNamesCollection($elements); + static::assertSame($expected, $collection->toArray(), $description); + } + + public function testGetFirst() + { + static::assertNull($this->emptyCollection->getFirst()); + static::assertSame('lorem', $this->simpleCollection->getFirst()); + } + + public function testGetIterator() + { + static::assertInstanceOf(ArrayIterator::class, $this->simpleCollection->getIterator()); + } + + public function testGetLast() + { + static::assertNull($this->emptyCollection->getLast()); + static::assertSame('adipiscing elit', $this->simpleCollection->getLast()); + } + + public function testGetNext() + { + static::assertNull($this->emptyCollection->getNext('abc')); + static::assertNull($this->simpleCollection->getNext('abc')); + static::assertNull($this->simpleCollection->getNext('adipiscing elit')); + + static::assertSame('dolor', $this->simpleCollection->getNext('ipsum')); + static::assertSame('sit', $this->simpleCollection->getNext('dolor')); + } + + public function testGetPrevious() + { + static::assertNull($this->emptyCollection->getPrevious('abc')); + static::assertNull($this->simpleCollection->getPrevious('abc')); + static::assertNull($this->simpleCollection->getPrevious('lorem')); + + static::assertSame('lorem', $this->simpleCollection->getPrevious('ipsum')); + static::assertSame('dolor', $this->simpleCollection->getPrevious('sit')); + } + + public function testHas() + { + static::assertFalse($this->emptyCollection->has('abc')); + static::assertFalse($this->simpleCollection->has('abc')); + static::assertTrue($this->simpleCollection->has('lorem')); + static::assertTrue($this->simpleCollection->has('dolor')); + } + + public function testIsEmpty() + { + static::assertTrue($this->emptyCollection->isEmpty()); + static::assertFalse($this->simpleCollection->isEmpty()); + } + + public function testIsFirst() + { + static::assertFalse($this->emptyCollection->isFirst('abc')); + static::assertFalse($this->simpleCollection->isFirst('abc')); + static::assertFalse($this->simpleCollection->isFirst('dolor')); + static::assertTrue($this->simpleCollection->isFirst('lorem')); + } + + public function testIsLast() + { + static::assertFalse($this->emptyCollection->isLast('abc')); + static::assertFalse($this->simpleCollection->isLast('abc')); + static::assertFalse($this->simpleCollection->isLast('dolor')); + static::assertTrue($this->simpleCollection->isLast('adipiscing elit')); + } + + /** + * @param array $expected + * @param int $max + * @param int $offset + * + * @dataProvider provideResultOfLimit + */ + public function testLimit(array $expected, int $max, int $offset): void + { + $result = $this->simpleCollection->limit($max, $offset); + self::assertSame($expected, $result->toArray()); + } + + public function testLimitIfIsEmpty(): void + { + $result = $this->emptyCollection->limit(10); + self::assertEquals(new StringCollection(), $result); + } + + /** + * @param array $expected + * @param int $max + * + * @dataProvider provideResultOfLimitWithDefaultOffset + */ + public function testLimitWithDefaultOffset(array $expected, int $max): void + { + $result = $this->simpleCollection->limit($max); + self::assertSame($expected, $result->toArray()); + } + + public function testNotEmptyCollection() + { + static::assertSame(7, $this->simpleCollection->count()); + static::assertCount(7, $this->simpleCollection); + static::assertNotEmpty($this->simpleCollection); + + static::assertFalse($this->simpleCollection->isEmpty()); + static::assertSame($this->simpleElements, $this->simpleCollection->toArray()); + static::assertNotEmpty($this->simpleCollection->toArray()); + + static::assertSame('lorem', $this->simpleCollection->getFirst()); + static::assertSame('adipiscing elit', $this->simpleCollection->getLast()); + static::assertSame('dolor', $this->simpleCollection[123]); + } + + public function testOffsetExists() + { + static::assertFalse(isset($this->emptyCollection['abc'])); + static::assertFalse(isset($this->simpleCollection['abc'])); + + static::assertTrue(isset($this->simpleCollection[0])); + static::assertTrue(isset($this->simpleCollection[345])); + } + + public function testOffsetGet() + { + static::assertNull($this->emptyCollection['abc']); + static::assertNull($this->simpleCollection['abc']); + + static::assertSame('lorem', $this->simpleCollection[0]); + static::assertSame('sit', $this->simpleCollection[345]); + } + + public function testOffsetSet() + { + $this->emptyCollection['test1'] = 1234; + $this->simpleCollection['test2'] = 5678; + + static::assertTrue($this->emptyCollection->has(1234)); + static::assertSame(1234, $this->emptyCollection['test1']); + + static::assertTrue($this->simpleCollection->has(5678)); + static::assertSame(5678, $this->simpleCollection['test2']); + } + + public function testOffsetUnset() + { + unset($this->simpleCollection[0]); + + static::assertFalse($this->simpleCollection->has('lorem')); + static::assertSame('ipsum', $this->simpleCollection[1]); + static::assertSame(6, $this->simpleCollection->count()); + + unset($this->simpleCollection[123]); + + static::assertFalse($this->simpleCollection->has('dolor')); + static::assertSame('ipsum', $this->simpleCollection[1]); + static::assertSame(5, $this->simpleCollection->count()); + } + + public function testAppend(): void + { + $this->emptyCollection->append('lorem-ipsum'); + + static::assertFalse($this->emptyCollection->isEmpty()); + static::assertSame(1, $this->emptyCollection->count()); + static::assertSame('lorem-ipsum', $this->emptyCollection[0]); + + $this->simpleCollection->append('lorem-ipsum'); + + static::assertFalse($this->simpleCollection->isEmpty()); + static::assertSame(8, $this->simpleCollection->count()); + static::assertSame('lorem-ipsum', $this->simpleCollection[347]); + } + + public function testPrepend() + { + $this->emptyCollection->prepend('lorem-ipsum'); + + static::assertFalse($this->emptyCollection->isEmpty()); + static::assertSame(1, $this->emptyCollection->count()); + static::assertSame('lorem-ipsum', $this->emptyCollection[0]); + + $this->simpleCollection->prepend('lorem-ipsum'); + + static::assertFalse($this->simpleCollection->isEmpty()); + static::assertSame(8, $this->simpleCollection->count()); + static::assertSame('lorem-ipsum', $this->simpleCollection[0]); + } + + public function testRemove() + { + static::assertFalse($this->simpleCollection->isEmpty()); + static::assertSame(7, $this->simpleCollection->count()); + static::assertSame('ipsum', $this->simpleCollection[1]); + + $this->simpleCollection->remove('ipsum'); + + static::assertFalse($this->simpleCollection->isEmpty()); + static::assertSame(6, $this->simpleCollection->count()); + static::assertNull($this->simpleCollection[1]); + } + + public function testRemoveNotExistingElement() + { + $this->emptyCollection->remove('abc'); + + static::assertTrue($this->emptyCollection->isEmpty()); + static::assertSame(0, $this->emptyCollection->count()); + + $this->simpleCollection->remove('abc'); + + static::assertFalse($this->simpleCollection->isEmpty()); + static::assertSame(7, $this->simpleCollection->count()); + } + + public function testToArray() + { + static::assertSame([], $this->emptyCollection->toArray()); + static::assertSame($this->simpleElements, $this->simpleCollection->toArray()); + } + + /** + * {@inheritdoc} + */ + protected function setUp(): void + { + parent::setUp(); + + $this->simpleElements = [ + 'lorem', + 'ipsum', + 123 => 'dolor', + 345 => 'sit', + 'a' => 'amet', + 'c' => 'consectetur', + 'adipiscing elit', + ]; + + $this->emptyCollection = new StringCollection(); + $this->simpleCollection = new StringCollection($this->simpleElements); + } +} diff --git a/tests/Collection/CollectionTest.php b/tests/Collection/CollectionTest.php deleted file mode 100644 index 37e9be2..0000000 --- a/tests/Collection/CollectionTest.php +++ /dev/null @@ -1,508 +0,0 @@ - - * @copyright Meritoo - */ -class CollectionTest extends BaseTestCase -{ - /** - * An empty collection - * - * @var Collection - */ - private $emptyCollection; - - /** - * Simple collection - * - * @var Collection - */ - private $simpleCollection; - - /** - * Elements of simple collection - * - * @var array - */ - private $simpleElements; - - public function testEmptyCollection() - { - static::assertSame(0, $this->emptyCollection->count()); - static::assertCount(0, $this->emptyCollection); - static::assertEmpty($this->emptyCollection); - - static::assertTrue($this->emptyCollection->isEmpty()); - static::assertSame([], $this->emptyCollection->toArray()); - static::assertEmpty($this->emptyCollection->toArray()); - - static::assertNull($this->emptyCollection->getFirst()); - static::assertNull($this->emptyCollection->getLast()); - static::assertNull($this->emptyCollection[1]); - static::assertNull($this->emptyCollection['abc']); - } - - public function testNotEmptyCollection() - { - static::assertSame(4, $this->simpleCollection->count()); - static::assertCount(4, $this->simpleCollection); - static::assertNotEmpty($this->simpleCollection); - - static::assertFalse($this->simpleCollection->isEmpty()); - static::assertSame($this->simpleElements, $this->simpleCollection->toArray()); - static::assertNotEmpty($this->simpleCollection->toArray()); - - static::assertSame('lorem', $this->simpleCollection->getFirst()); - static::assertSame('sit', $this->simpleCollection->getLast()); - static::assertSame('dolor', $this->simpleCollection[123]); - } - - public function testCount() - { - static::assertSame(0, $this->emptyCollection->count()); - static::assertSame(4, $this->simpleCollection->count()); - } - - public function testOffsetExists() - { - static::assertFalse(isset($this->emptyCollection['abc'])); - static::assertFalse(isset($this->simpleCollection['abc'])); - - static::assertTrue(isset($this->simpleCollection[0])); - static::assertTrue(isset($this->simpleCollection[345])); - } - - public function testOffsetGet() - { - static::assertNull($this->emptyCollection['abc']); - static::assertNull($this->simpleCollection['abc']); - - static::assertSame('lorem', $this->simpleCollection[0]); - static::assertSame('sit', $this->simpleCollection[345]); - } - - public function testOffsetSet() - { - $this->emptyCollection['test1'] = 1234; - $this->simpleCollection['test2'] = 5678; - - static::assertTrue($this->emptyCollection->has(1234)); - static::assertSame(1234, $this->emptyCollection['test1']); - - static::assertTrue($this->simpleCollection->has(5678)); - static::assertSame(5678, $this->simpleCollection['test2']); - } - - public function testOffsetUnset() - { - unset($this->simpleCollection[0]); - - static::assertFalse($this->simpleCollection->has('lorem')); - static::assertSame('ipsum', $this->simpleCollection[1]); - static::assertSame(3, $this->simpleCollection->count()); - - unset($this->simpleCollection[123]); - - static::assertFalse($this->simpleCollection->has('dolor')); - static::assertSame('ipsum', $this->simpleCollection[1]); - static::assertSame(2, $this->simpleCollection->count()); - } - - public function testGetIterator() - { - static::assertInstanceOf(ArrayIterator::class, $this->simpleCollection->getIterator()); - } - - /** - * @param mixed $element The element to add - * @param int $expectedCount Expected count of elements in collection - * @param int $expectedIndex Expected index of added element in collection - * @param Collection $collection The collection - * - * @dataProvider provideElementToAdd - */ - public function testAddWithoutIndex($element, $expectedCount, $expectedIndex, Collection $collection) - { - $collection->add($element); - - static::assertTrue($collection->has($element)); - static::assertSame($expectedCount, $collection->count()); - static::assertSame($element, $collection[$expectedIndex]); - } - - /** - * @param mixed $element The element to add - * @param mixed $index Index of element to add - * @param int $expectedCount Expected count of elements in collection - * @param int $expectedIndex Expected index of added element in collection - * @param Collection $collection The collection - * - * @dataProvider provideElementToAddWithIndex - */ - public function testAddWithIndex($element, $index, $expectedCount, $expectedIndex, Collection $collection) - { - $collection->add($element, $index); - - static::assertTrue($collection->has($element)); - static::assertSame($expectedCount, $collection->count()); - static::assertSame($element, $collection[$expectedIndex]); - } - - public function testAddMultipleUsingEmptyArray() - { - $this->emptyCollection->addMultiple([]); - - static::assertSame(0, $this->emptyCollection->count()); - static::assertTrue($this->emptyCollection->isEmpty()); - } - - public function testAddMultiple() - { - $elements = [ - 'test1', - 'test2', - 1234 => 'test3', - 5678 => 'test4', - ]; - - $this->emptyCollection->addMultiple($elements); - - static::assertFalse($this->emptyCollection->isEmpty()); - static::assertSame(4, $this->emptyCollection->count()); - - static::assertSame('test1', $this->emptyCollection[0]); - static::assertSame('test2', $this->emptyCollection[1]); - static::assertSame('test3', $this->emptyCollection[2]); - static::assertSame('test4', $this->emptyCollection[3]); - } - - public function testAddMultipleUsingIndexes() - { - $elements = [ - 'test1', - 'test2', - 1234 => 'test3', - 5678 => 'test4', - ]; - - $this->emptyCollection->addMultiple($elements, true); - - static::assertFalse($this->emptyCollection->isEmpty()); - static::assertSame(4, $this->emptyCollection->count()); - - static::assertSame('test1', $this->emptyCollection[0]); - static::assertSame('test2', $this->emptyCollection[1]); - static::assertSame('test3', $this->emptyCollection[1234]); - static::assertSame('test4', $this->emptyCollection[5678]); - } - - public function testPrepend() - { - $this->emptyCollection->prepend('lorem-ipsum'); - - static::assertFalse($this->emptyCollection->isEmpty()); - static::assertSame(1, $this->emptyCollection->count()); - static::assertSame('lorem-ipsum', $this->emptyCollection[0]); - - $this->simpleCollection->prepend('lorem-ipsum'); - - static::assertFalse($this->simpleCollection->isEmpty()); - static::assertSame(5, $this->simpleCollection->count()); - static::assertSame('lorem-ipsum', $this->simpleCollection[0]); - } - - public function testRemoveNotExistingElement() - { - $this->emptyCollection->remove('abc'); - - static::assertTrue($this->emptyCollection->isEmpty()); - static::assertSame(0, $this->emptyCollection->count()); - - $this->simpleCollection->remove('abc'); - - static::assertFalse($this->simpleCollection->isEmpty()); - static::assertSame(4, $this->simpleCollection->count()); - } - - public function testRemove() - { - static::assertFalse($this->simpleCollection->isEmpty()); - static::assertSame(4, $this->simpleCollection->count()); - static::assertSame('ipsum', $this->simpleCollection[1]); - - $this->simpleCollection->remove('ipsum'); - - static::assertFalse($this->simpleCollection->isEmpty()); - static::assertSame(3, $this->simpleCollection->count()); - static::assertNull($this->simpleCollection[1]); - } - - public function testIsEmpty() - { - static::assertTrue($this->emptyCollection->isEmpty()); - static::assertFalse($this->simpleCollection->isEmpty()); - } - - public function testIsFirst() - { - static::assertFalse($this->emptyCollection->isFirst('abc')); - static::assertFalse($this->simpleCollection->isFirst('abc')); - static::assertFalse($this->simpleCollection->isFirst('dolor')); - static::assertTrue($this->simpleCollection->isFirst('lorem')); - } - - public function testIsLast() - { - static::assertFalse($this->emptyCollection->isLast('abc')); - static::assertFalse($this->simpleCollection->isLast('abc')); - static::assertFalse($this->simpleCollection->isLast('dolor')); - static::assertTrue($this->simpleCollection->isLast('sit')); - } - - public function testHas() - { - static::assertFalse($this->emptyCollection->has('abc')); - static::assertFalse($this->simpleCollection->has('abc')); - static::assertTrue($this->simpleCollection->has('lorem')); - static::assertTrue($this->simpleCollection->has('dolor')); - } - - public function testGetPrevious() - { - static::assertNull($this->emptyCollection->getPrevious('abc')); - static::assertNull($this->simpleCollection->getPrevious('abc')); - static::assertNull($this->simpleCollection->getPrevious('lorem')); - - static::assertSame('lorem', $this->simpleCollection->getPrevious('ipsum')); - static::assertSame('dolor', $this->simpleCollection->getPrevious('sit')); - } - - public function testGetNext() - { - static::assertNull($this->emptyCollection->getNext('abc')); - static::assertNull($this->simpleCollection->getNext('abc')); - static::assertNull($this->simpleCollection->getNext('sit')); - - static::assertSame('dolor', $this->simpleCollection->getNext('ipsum')); - static::assertSame('sit', $this->simpleCollection->getNext('dolor')); - } - - public function testGetFirst() - { - static::assertNull($this->emptyCollection->getFirst()); - static::assertSame('lorem', $this->simpleCollection->getFirst()); - } - - public function testGetLast() - { - static::assertNull($this->emptyCollection->getLast()); - static::assertSame('sit', $this->simpleCollection->getLast()); - } - - public function testToArray() - { - static::assertSame([], $this->emptyCollection->toArray()); - static::assertSame($this->simpleElements, $this->simpleCollection->toArray()); - } - - public function testExistsVisibilityAndArguments() - { - static::assertMethodVisibilityAndArguments(Collection::class, 'exists', OopVisibilityType::IS_PRIVATE, 1, 1); - } - - /** - * @param string $description Description of test - * @param Collection $collection Collection to search for element with given index - * @param mixed $index Index / key of the element - * @param mixed $expected Expected element with given index - * - * @dataProvider provideElementGetByIndex - */ - public function testGetByIndex($description, Collection $collection, $index, $expected) - { - static::assertEquals($expected, $collection->getByIndex($index), $description); - } - - /** - * Provides element to add to collection - * - * @return Generator - */ - public function provideElementToAdd() - { - $collection = new Collection(); - - yield[ - 'test1', - 1, - 0, - $collection, - ]; - - yield[ - 'test2', - 2, - 1, - $collection, - ]; - - yield[ - 'test3', - 3, - 2, - $collection, - ]; - } - - /** - * Provides element with index to add to collection - * - * @return Generator - */ - public function provideElementToAddWithIndex() - { - $collection = new Collection(); - - yield[ - 'test1', - 'aa', - 1, - 'aa', - $collection, - ]; - - yield[ - 'test2', - 'oo', - 2, - 'oo', - $collection, - ]; - - yield[ - 'test3', - null, - 3, - 0, - $collection, - ]; - - yield[ - 'test4', - '', - 4, - 1, - $collection, - ]; - - yield[ - 'test5', - 'vv', - 5, - 'vv', - $collection, - ]; - } - - public function provideElementGetByIndex() - { - yield[ - 'An empty collection and empty index', - new Collection(), - '', - null, - ]; - - yield[ - 'An empty collection and non-empty index', - new Collection(), - 'test', - null, - ]; - - yield[ - 'Non-empty collection and not existing index', - new Collection([ - 'lorem' => 'ipsum', - 'dolor' => 'sit', - ]), - 'test', - null, - ]; - - yield[ - 'Collection with existing index', - new Collection([ - 'lorem' => 'ipsum', - 'dolor' => 'sit', - ]), - 'lorem', - 'ipsum', - ]; - - yield[ - 'Collection with existing index (collection of arrays)', - new Collection([ - [ - 'lorem', - 'ipsum', - ], - [ - 'dolor', - 'sit', - ], - ]), - 0, - [ - 'lorem', - 'ipsum', - ], - ]; - - yield[ - 'Collection with existing index (collection of objects)', - new Collection([ - 'x' => new \DateTime(), - 'y' => new \DateTime('2001-01-01'), - 'z' => new \DateTime('yesterday'), - ]), - 'y', - new \DateTime('2001-01-01'), - ]; - } - - /** - * {@inheritdoc} - */ - protected function setUp() - { - parent::setUp(); - - $this->simpleElements = [ - 'lorem', - 'ipsum', - 123 => 'dolor', - 345 => 'sit', - ]; - - $this->emptyCollection = new Collection(); - $this->simpleCollection = new Collection($this->simpleElements); - } -} diff --git a/tests/Collection/DateTimeCollectionTest.php b/tests/Collection/DateTimeCollectionTest.php new file mode 100644 index 0000000..e68415c --- /dev/null +++ b/tests/Collection/DateTimeCollectionTest.php @@ -0,0 +1,92 @@ + + * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Collection\DateTimeCollection + */ +class DateTimeCollectionTest extends BaseTestCase +{ + public function provideDifferentTypesOfElements(): ?Generator + { + yield [ + 'An empty array', + [], + [], + ]; + + yield [ + 'Valid elements only', + [ + new DateTime('2001-01-01'), + new DateTime('2001-01-02'), + ], + [ + new DateTime('2001-01-01'), + new DateTime('2001-01-02'), + ], + ]; + + yield [ + 'Mixed elements', + [ + 1, + 'test', + new DateTime('2001-01-01'), + '', + [], + 234, + new DateTime('2001-01-02'), + ], + [ + 2 => new DateTime('2001-01-01'), + 6 => new DateTime('2001-01-02'), + ], + ]; + } + + public function testConstructor(): void + { + static::assertConstructorVisibilityAndArguments( + DateTimeCollection::class, + OopVisibilityType::IS_PUBLIC, + 1 + ); + } + + /** + * @param string $description + * @param array $elements + * @param array $expectedElements + * + * @dataProvider provideDifferentTypesOfElements + */ + public function testCreateWithDifferentTypesOfElements( + string $description, + array $elements, + array $expectedElements + ): void { + $collection = new DateTimeCollection($elements); + static::assertEquals($expectedElements, $collection->toArray(), $description); + } +} diff --git a/tests/Collection/IntegerCollectionTest.php b/tests/Collection/IntegerCollectionTest.php new file mode 100644 index 0000000..53ffcb6 --- /dev/null +++ b/tests/Collection/IntegerCollectionTest.php @@ -0,0 +1,92 @@ + + * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Collection\IntegerCollection + */ +class IntegerCollectionTest extends BaseTestCase +{ + public function provideDifferentTypesOfElements(): ?Generator + { + yield [ + 'An empty array', + [], + [], + ]; + + yield [ + 'Valid elements only', + [ + 1, + 2, + 3, + ], + [ + 1, + 2, + 3, + ], + ]; + + yield [ + 'Mixed elements', + [ + 1, + 'test', + '', + [], + 234, + 'test', + ], + [ + 0 => 1, + 4 => 234, + ], + ]; + } + + public function testConstructor(): void + { + static::assertConstructorVisibilityAndArguments( + IntegerCollection::class, + OopVisibilityType::IS_PUBLIC, + 1 + ); + } + + /** + * @param string $description + * @param array $elements + * @param array $expectedElements + * + * @dataProvider provideDifferentTypesOfElements + */ + public function testCreateWithDifferentTypesOfElements( + string $description, + array $elements, + array $expectedElements + ): void { + $collection = new IntegerCollection($elements); + static::assertSame($expectedElements, $collection->toArray(), $description); + } +} diff --git a/tests/Collection/StringCollectionTest.php b/tests/Collection/StringCollectionTest.php new file mode 100644 index 0000000..cbab50a --- /dev/null +++ b/tests/Collection/StringCollectionTest.php @@ -0,0 +1,93 @@ + + * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Collection\StringCollection + */ +class StringCollectionTest extends BaseTestCase +{ + public function provideDifferentTypesOfElements(): ?Generator + { + yield [ + 'An empty array', + [], + [], + ]; + + yield [ + 'Valid elements only', + [ + '1', + 'test', + '', + ], + [ + '1', + 'test', + '', + ], + ]; + + yield [ + 'Mixed elements', + [ + 1, + 'test', + '', + [], + 234, + 'test', + ], + [ + 1 => 'test', + 2 => '', + 5 => 'test', + ], + ]; + } + + public function testConstructor(): void + { + static::assertConstructorVisibilityAndArguments( + StringCollection::class, + OopVisibilityType::IS_PUBLIC, + 1 + ); + } + + /** + * @param string $description + * @param array $elements + * @param array $expectedElements + * + * @dataProvider provideDifferentTypesOfElements + */ + public function testCreateWithDifferentTypesOfElements( + string $description, + array $elements, + array $expectedElements + ): void { + $collection = new StringCollection($elements); + static::assertSame($expectedElements, $collection->toArray(), $description); + } +} diff --git a/tests/Collection/TemplatesTest.php b/tests/Collection/TemplatesTest.php new file mode 100644 index 0000000..adc84dd --- /dev/null +++ b/tests/Collection/TemplatesTest.php @@ -0,0 +1,188 @@ + + * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Collection\Templates + */ +class TemplatesTest extends BaseTestCase +{ + public function provideArrayWithTemplates(): ?Generator + { + yield [ + 'An empty array', + [], + new Templates(), + ]; + + yield [ + 'Number-based indexes', + [ + 'First name: %first_name%', + 'Last name: %last_name%', + ], + new Templates([ + new Template('First name: %first_name%'), + new Template('Last name: %last_name%'), + ]), + ]; + + yield [ + 'String-based indexes', + [ + 'first' => 'First name: %first_name%', + 'last' => 'Last name: %last_name%', + ], + new Templates([ + 'first' => new Template('First name: %first_name%'), + 'last' => new Template('Last name: %last_name%'), + ]), + ]; + } + + public function provideTemplatesToFind(): ?Generator + { + yield [ + '2 templates only', + new Templates([ + 'first' => new Template('First name: %first_name%'), + 'last' => new Template('Last name: %last_name%'), + ]), + 'first', + new Template('First name: %first_name%'), + ]; + + yield [ + 'Different indexes', + new Templates([ + 'first' => new Template('First name: %first_name%'), + 'last' => new Template('Last name: %last_name%'), + 1 => new Template('Hi %name%, how are you?'), + '2' => new Template('Your score is: %score%'), + ]), + '1', + new Template('Hi %name%, how are you?'), + ]; + } + + public function provideTemplatesWithNotExistingIndex(): ?Generator + { + $template = 'Template with \'%s\' index was not found. Did you provide all required templates?'; + + yield [ + new Templates(), + 'test', + sprintf($template, 'test'), + ]; + + yield [ + new Templates([ + 'first' => new Template('First name: %first_name%'), + 'last' => new Template('Last name: %last_name%'), + ]), + 'test', + sprintf($template, 'test'), + ]; + + yield [ + new Templates([ + 'first' => new Template('First name: %first_name%'), + 'last' => new Template('Last name: %last_name%'), + ]), + '', + sprintf($template, ''), + ]; + + yield [ + new Templates([ + 'first' => new Template('First name: %first_name%'), + 'last' => new Template('Last name: %last_name%'), + ]), + '4', + sprintf($template, 4), + ]; + } + + public function testConstructor(): void + { + static::assertConstructorVisibilityAndArguments( + Templates::class, + OopVisibilityType::IS_PUBLIC, + 1 + ); + } + + /** + * @param string $description Description of test + * @param Templates $templates All templates + * @param string $index Index that contains required template + * @param Template $expected Expected template + * + * @dataProvider provideTemplatesToFind + */ + public function testFindTemplate(string $description, Templates $templates, string $index, Template $expected): void + { + static::assertEquals($expected, $templates->findTemplate($index), $description); + } + + public function testFindTemplateUsingEmptyCollection(): void + { + $template = 'Template with \'%s\' index was not found. Did you provide all required templates?'; + $message = sprintf($template, 'test'); + + $this->expectException(TemplateNotFoundException::class); + $this->expectExceptionMessage($message); + + $templates = new Templates(); + $templates->findTemplate('test'); + } + + /** + * @param Templates $templates All templates + * @param string $index Index that contains required template + * @param string $expectedMessage Expected message of exception + * + * @dataProvider provideTemplatesWithNotExistingIndex + */ + public function testFindTemplateUsingNotExistingIndex( + Templates $templates, + string $index, + string $expectedMessage + ): void { + $this->expectException(TemplateNotFoundException::class); + $this->expectExceptionMessage($expectedMessage); + + $templates->findTemplate($index); + } + + /** + * @param string $description Description of test + * @param array $templates Pairs of key-value where: key - template's index, value - template's content + * @param Templates $expected Expected collection/storage of templates + * + * @dataProvider provideArrayWithTemplates + */ + public function testFromArray(string $description, array $templates, Templates $expected): void + { + static::assertEquals($expected, Templates::fromArray($templates), $description); + } +} diff --git a/tests/Exception/Base/UnknownTypeExceptionTest.php b/tests/Exception/Base/UnknownTypeExceptionTest.php index ce910d3..c91547a 100644 --- a/tests/Exception/Base/UnknownTypeExceptionTest.php +++ b/tests/Exception/Base/UnknownTypeExceptionTest.php @@ -18,24 +18,27 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Base\UnknownTypeException */ class UnknownTypeExceptionTest extends BaseTestCase { public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(UnknownTestTypeException::class, OopVisibilityType::IS_PUBLIC, 3); + static::assertConstructorVisibilityAndArguments(UnknownTypeException::class, OopVisibilityType::IS_PUBLIC, 3); + } + + public function testTheException() + { + $this->expectException(UnknownTestTypeException::class); + self::assertEmpty((new TestService())->getTranslatedType('test_3')); } public function testWithoutException() { self::assertEquals('Test 2', (new TestService())->getTranslatedType('test_2')); } - - public function testTheException() - { - $this->setExpectedException(UnknownTestTypeException::class); - self::assertEmpty((new TestService())->getTranslatedType('test_3')); - } } /** @@ -46,9 +49,9 @@ class UnknownTypeExceptionTest extends BaseTestCase */ class TestType extends BaseType { - const TEST_1 = 'test_1'; + public const TEST_1 = 'test_1'; - const TEST_2 = 'test_2'; + public const TEST_2 = 'test_2'; } /** @@ -56,6 +59,9 @@ class TestType extends BaseType * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Base\UnknownTypeException */ class UnknownTestTypeException extends UnknownTypeException { @@ -65,12 +71,9 @@ class UnknownTestTypeException extends UnknownTypeException * @param string $unknownType The unknown type of something (for testing purposes) * @return UnknownTestTypeException */ - public static function createException($unknownType) + public static function createException(string $unknownType): UnknownTestTypeException { - /* @var UnknownTestTypeException $exception */ - $exception = parent::create($unknownType, new TestType(), 'type of something used for testing'); - - return $exception; + return parent::create($unknownType, new TestType(), 'type of something used for testing'); } } @@ -86,12 +89,12 @@ class TestService * Returns translated type (for testing purposes) * * @param string $type Type of something (for testing purposes) - * @throws UnknownTestTypeException * @return string + * @throws UnknownTestTypeException */ - public function getTranslatedType($type) + public function getTranslatedType(string $type): string { - if ((new TestType())->isCorrectType($type)) { + if (TestType::isCorrectType($type)) { return ucfirst(str_replace('_', ' ', $type)); } diff --git a/tests/Exception/Bundle/IncorrectBundleNameExceptionTest.php b/tests/Exception/Bundle/IncorrectBundleNameExceptionTest.php index 5c2d420..d862480 100644 --- a/tests/Exception/Bundle/IncorrectBundleNameExceptionTest.php +++ b/tests/Exception/Bundle/IncorrectBundleNameExceptionTest.php @@ -8,6 +8,7 @@ namespace Meritoo\Test\Common\Exception\Bundle; +use Generator; use Meritoo\Common\Exception\Bundle\IncorrectBundleNameException; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; @@ -17,10 +18,37 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Bundle\IncorrectBundleNameException */ class IncorrectBundleNameExceptionTest extends BaseTestCase { - public function testConstructor() + public function provideBundleNameAndMessage(): Generator + { + $template = 'Name of bundle \'%s\' is incorrect. It should start with big letter and end with "Bundle". Is' + .' there everything ok?'; + + yield [ + 'An empty string as name of bundle', + '', + sprintf($template, ''), + ]; + + yield [ + 'String with spaces as name of bundle', + 'This is test', + sprintf($template, 'This is test'), + ]; + + yield [ + 'String without spaces as name of bundle', + 'ThisIsTest', + sprintf($template, 'ThisIsTest'), + ]; + } + + public function testConstructor(): void { static::assertConstructorVisibilityAndArguments( IncorrectBundleNameException::class, @@ -36,39 +64,9 @@ class IncorrectBundleNameExceptionTest extends BaseTestCase * * @dataProvider provideBundleNameAndMessage */ - public function testCreate($description, $bundleName, $expectedMessage) + public function testCreate(string $description, string $bundleName, string $expectedMessage): void { $exception = IncorrectBundleNameException::create($bundleName); static::assertSame($expectedMessage, $exception->getMessage(), $description); } - - public function provideBundleNameAndMessage() - { - $template = 'Name of bundle \'%s\' is incorrect. It should start with big letter and end with "Bundle". Is' - . ' there everything ok?'; - - yield[ - 'An empty string as name of bundle', - '', - sprintf($template, ''), - ]; - - yield[ - 'Null as name of bundle', - null, - sprintf($template, ''), - ]; - - yield[ - 'String with spaces as name of bundle', - 'This is test', - sprintf($template, 'This is test'), - ]; - - yield[ - 'String without spaces as name of bundle', - 'ThisIsTest', - sprintf($template, 'ThisIsTest'), - ]; - } } diff --git a/tests/Exception/Date/UnknownDatePartTypeExceptionTest.php b/tests/Exception/Date/UnknownDatePartTypeExceptionTest.php index f694ef1..4bc5b7b 100644 --- a/tests/Exception/Date/UnknownDatePartTypeExceptionTest.php +++ b/tests/Exception/Date/UnknownDatePartTypeExceptionTest.php @@ -19,9 +19,41 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Type\UnknownDatePartTypeException */ class UnknownDatePartTypeExceptionTest extends BaseTestCase { + /** + * Provides type of date part, incorrect value and expected exception's message + * + * @return Generator + */ + public function provideDatePartAndValue() + { + $template = 'The \'%s\' type of date part (with value %s) is unknown. Probably doesn\'t exist or there is a' + .' typo. You should use one of these types: day, hour, minute, month, second, year.'; + + yield [ + DatePartType::DAY, + '44', + sprintf($template, DatePartType::DAY, '44'), + ]; + + yield [ + DatePartType::MONTH, + '22', + sprintf($template, DatePartType::MONTH, '22'), + ]; + + yield [ + DatePartType::MINUTE, + '77', + sprintf($template, DatePartType::MINUTE, '77'), + ]; + } + public function testConstructorVisibilityAndArguments() { static::assertConstructorVisibilityAndArguments(UnknownDatePartTypeException::class, OopVisibilityType::IS_PUBLIC, 3); @@ -39,33 +71,4 @@ class UnknownDatePartTypeExceptionTest extends BaseTestCase $exception = UnknownDatePartTypeException::createException($unknownDatePart, $value); static::assertSame($expectedMessage, $exception->getMessage()); } - - /** - * Provides type of date part, incorrect value and expected exception's message - * - * @return Generator - */ - public function provideDatePartAndValue() - { - $template = 'The \'%s\' type of date part (with value %s) is unknown. Probably doesn\'t exist or there is a' - . ' typo. You should use one of these types: day, hour, minute, month, second, year.'; - - yield[ - DatePartType::DAY, - '44', - sprintf($template, DatePartType::DAY, '44'), - ]; - - yield[ - DatePartType::MONTH, - '22', - sprintf($template, DatePartType::MONTH, '22'), - ]; - - yield[ - DatePartType::MINUTE, - '77', - sprintf($template, DatePartType::MINUTE, '77'), - ]; - } } diff --git a/tests/Exception/File/EmptyFileExceptionTest.php b/tests/Exception/File/EmptyFileExceptionTest.php index a2ee08d..0825b32 100644 --- a/tests/Exception/File/EmptyFileExceptionTest.php +++ b/tests/Exception/File/EmptyFileExceptionTest.php @@ -18,9 +18,32 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\File\EmptyFileException */ class EmptyFileExceptionTest extends BaseTestCase { + /** + * Provides path of the empty file and expected exception's message + * + * @return Generator + */ + public function providePathOfFile() + { + $template = 'File with path \'%s\' is empty (has no content). Did you provide path of proper file?'; + + yield [ + 'aa/bb/cc', + sprintf($template, 'aa/bb/cc'), + ]; + + yield [ + 'images/show/car.jpg', + sprintf($template, 'images/show/car.jpg'), + ]; + } + public function testConstructorVisibilityAndArguments() { static::assertConstructorVisibilityAndArguments(EmptyFileException::class, OopVisibilityType::IS_PUBLIC, 3); @@ -37,24 +60,4 @@ class EmptyFileExceptionTest extends BaseTestCase $exception = EmptyFileException::create($emptyFilePath); static::assertSame($expectedMessage, $exception->getMessage()); } - - /** - * Provides path of the empty file and expected exception's message - * - * @return Generator - */ - public function providePathOfFile() - { - $template = 'File with path \'%s\' is empty (has no content). Did you provide path of proper file?'; - - yield[ - 'aa/bb/cc', - sprintf($template, 'aa/bb/cc'), - ]; - - yield[ - 'images/show/car.jpg', - sprintf($template, 'images/show/car.jpg'), - ]; - } } diff --git a/tests/Exception/File/EmptyFilePathExceptionTest.php b/tests/Exception/File/EmptyFilePathExceptionTest.php index b582579..560351c 100644 --- a/tests/Exception/File/EmptyFilePathExceptionTest.php +++ b/tests/Exception/File/EmptyFilePathExceptionTest.php @@ -17,17 +17,20 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\File\EmptyFilePathException */ class EmptyFilePathExceptionTest extends BaseTestCase { - public function testConstructorVisibilityAndArguments() - { - static::assertConstructorVisibilityAndArguments(EmptyFilePathException::class, OopVisibilityType::IS_PUBLIC, 3); - } - public function testConstructorMessage() { $exception = EmptyFilePathException::create(); static::assertSame('Path of the file is empty. Did you provide path of proper file?', $exception->getMessage()); } + + public function testConstructorVisibilityAndArguments() + { + static::assertConstructorVisibilityAndArguments(EmptyFilePathException::class, OopVisibilityType::IS_PUBLIC, 3); + } } diff --git a/tests/Exception/File/NotExistingFileExceptionTest.php b/tests/Exception/File/NotExistingFileExceptionTest.php index 08f8868..b8c6cb6 100644 --- a/tests/Exception/File/NotExistingFileExceptionTest.php +++ b/tests/Exception/File/NotExistingFileExceptionTest.php @@ -18,12 +18,30 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\File\NotExistingFileException */ class NotExistingFileExceptionTest extends BaseTestCase { - public function testConstructorVisibilityAndArguments() + /** + * Provides path of not existing file and expected exception's message + * + * @return Generator + */ + public function providePathOfFile() { - static::assertConstructorVisibilityAndArguments(NotExistingFileException::class, OopVisibilityType::IS_PUBLIC, 3); + $template = 'File with path \'%s\' does not exist (or is not readable). Did you provide path of proper file?'; + + yield [ + 'aa/bb/cc', + sprintf($template, 'aa/bb/cc'), + ]; + + yield [ + 'images/show/car.jpg', + sprintf($template, 'images/show/car.jpg'), + ]; } /** @@ -38,23 +56,8 @@ class NotExistingFileExceptionTest extends BaseTestCase static::assertSame($expectedMessage, $exception->getMessage()); } - /** - * Provides path of not existing file and expected exception's message - * - * @return Generator - */ - public function providePathOfFile() + public function testConstructorVisibilityAndArguments() { - $template = 'File with path \'%s\' does not exist (or is not readable). Did you provide path of proper file?'; - - yield[ - 'aa/bb/cc', - sprintf($template, 'aa/bb/cc'), - ]; - - yield[ - 'images/show/car.jpg', - sprintf($template, 'images/show/car.jpg'), - ]; + static::assertConstructorVisibilityAndArguments(NotExistingFileException::class, OopVisibilityType::IS_PUBLIC, 3); } } diff --git a/tests/Exception/Method/DisabledMethodExceptionTest.php b/tests/Exception/Method/DisabledMethodExceptionTest.php index a815b4e..18af361 100644 --- a/tests/Exception/Method/DisabledMethodExceptionTest.php +++ b/tests/Exception/Method/DisabledMethodExceptionTest.php @@ -18,12 +18,33 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Method\DisabledMethodException */ class DisabledMethodExceptionTest extends BaseTestCase { - public function testConstructorVisibilityAndArguments() + /** + * Provides name of the disabled method, name of the alternative method and expected exception's message + * + * @return Generator + */ + public function provideMethodsNames() { - static::assertConstructorVisibilityAndArguments(DisabledMethodException::class, OopVisibilityType::IS_PUBLIC, 3); + $templateShort = 'Method %s() cannot be called, because is disabled.'; + $templateLong = $templateShort.' Use %s() instead.'; + + yield [ + 'FooBar::loremIpsum', + '', + sprintf($templateShort, 'FooBar::loremIpsum'), + ]; + + yield [ + 'FooBar::loremIpsum', + 'AnotherClass::alternativeMethod', + sprintf($templateLong, 'FooBar::loremIpsum', 'AnotherClass::alternativeMethod'), + ]; } /** @@ -40,26 +61,8 @@ class DisabledMethodExceptionTest extends BaseTestCase static::assertSame($expectedMessage, $exception->getMessage()); } - /** - * Provides name of the disabled method, name of the alternative method and expected exception's message - * - * @return Generator - */ - public function provideMethodsNames() + public function testConstructorVisibilityAndArguments() { - $templateShort = 'Method %s() cannot be called, because is disabled.'; - $templateLong = $templateShort . ' Use %s() instead.'; - - yield[ - 'FooBar::loremIpsum', - '', - sprintf($templateShort, 'FooBar::loremIpsum'), - ]; - - yield[ - 'FooBar::loremIpsum', - 'AnotherClass::alternativeMethod', - sprintf($templateLong, 'FooBar::loremIpsum', 'AnotherClass::alternativeMethod'), - ]; + static::assertConstructorVisibilityAndArguments(DisabledMethodException::class, OopVisibilityType::IS_PUBLIC, 3); } } diff --git a/tests/Exception/Reflection/CannotResolveClassNameExceptionTest.php b/tests/Exception/Reflection/CannotResolveClassNameExceptionTest.php index c6f9082..059b8c3 100644 --- a/tests/Exception/Reflection/CannotResolveClassNameExceptionTest.php +++ b/tests/Exception/Reflection/CannotResolveClassNameExceptionTest.php @@ -12,62 +12,74 @@ use Generator; use Meritoo\Common\Exception\Reflection\CannotResolveClassNameException; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; +use stdClass; /** * Test case of an exception used while name of class or trait cannot be resolved * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Reflection\CannotResolveClassNameException */ class CannotResolveClassNameExceptionTest extends BaseTestCase { - public function testConstructorVisibilityAndArguments() - { - static::assertConstructorVisibilityAndArguments(CannotResolveClassNameException::class, OopVisibilityType::IS_PUBLIC, 3); - } - - /** - * @param array|object|string $source Source of the class's / trait's name. It can be an array of objects, - * namespaces, object or namespace. - * @param bool $forClass If is set to true, message of this exception for class is prepared. - * Otherwise - for trait. - * @param string $expectedMessage Expected exception's message - * - * @dataProvider provideClassName - */ - public function testConstructorMessage($source, $forClass, $expectedMessage) - { - $exception = CannotResolveClassNameException::create($source, $forClass); - static::assertSame($expectedMessage, $exception->getMessage()); - } - /** * Provides source of the class's / trait's name, information if message of this exception should be prepared for * class and the expected exception's message * * @return Generator */ - public function provideClassName() + public function provideClassName(): Generator { - yield[ + yield [ 'Not\Existing\Class', true, 'Name of class from given \'string\' Not\Existing\Class cannot be resolved. Is there everything ok?', ]; - yield[ + yield [ 'Not\Existing\Trait', false, 'Name of trait from given \'string\' Not\Existing\Trait cannot be resolved. Is there everything ok?', ]; - yield[ - [ - new \stdClass(), - new \stdClass(), - ], + yield [ + stdClass::class, true, - 'Name of class from given \'array\' cannot be resolved. Is there everything ok?', + 'Name of class from given \'string\' stdClass cannot be resolved. Is there everything ok?', ]; } + + public function testConstructorVisibilityAndArguments(): void + { + static::assertConstructorVisibilityAndArguments( + CannotResolveClassNameException::class, + OopVisibilityType::IS_PUBLIC, + 3 + ); + } + + /** + * @param string $source Source of name of the class or trait + * @param bool $forClass (optional) If is set to true, message of this exception for class is prepared. + * Otherwise - for trait. + * @param string $expectedMessage Expected exception's message + * + * @dataProvider provideClassName + */ + public function testCreate(string $source, bool $forClass, string $expectedMessage): void + { + $exception = CannotResolveClassNameException::create($source, $forClass); + static::assertSame($expectedMessage, $exception->getMessage()); + } + + public function testCreateUsingDefaults(): void + { + $exception = CannotResolveClassNameException::create(stdClass::class); + $expectedMessage = 'Name of class from given \'string\' stdClass cannot be resolved. Is there everything ok?'; + + static::assertSame($expectedMessage, $exception->getMessage()); + } } diff --git a/tests/Exception/Reflection/ClassWithoutConstructorExceptionTest.php b/tests/Exception/Reflection/ClassWithoutConstructorExceptionTest.php new file mode 100644 index 0000000..13e9eff --- /dev/null +++ b/tests/Exception/Reflection/ClassWithoutConstructorExceptionTest.php @@ -0,0 +1,66 @@ + + * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Reflection\ClassWithoutConstructorException + */ +class ClassWithoutConstructorExceptionTest extends BaseTestCase +{ + public function provideClassName(): Generator + { + $template = 'Oops, class \'%s\' hasn\'t constructor. Did you use proper class?'; + + yield [ + 'An empty name of class', + '', + sprintf($template, ''), + ]; + + yield [ + 'The Arrays class', + Arrays::class, + sprintf($template, Arrays::class), + ]; + } + + public function testConstructor(): void + { + static::assertConstructorVisibilityAndArguments( + ClassWithoutConstructorException::class, + OopVisibilityType::IS_PUBLIC, + 3 + ); + } + + /** + * @param string $description Description of test case + * @param string $className Fully-qualified name of class that hasn't constructor + * @param string $expectedMessage Expected exception's message + * + * @dataProvider provideClassName + */ + public function testCreate(string $description, string $className, string $expectedMessage): void + { + $exception = ClassWithoutConstructorException::create($className); + static::assertSame($expectedMessage, $exception->getMessage(), $description); + } +} diff --git a/tests/Exception/Reflection/MissingChildClassesExceptionTest.php b/tests/Exception/Reflection/MissingChildClassesExceptionTest.php index 900d6e2..fccac2e 100644 --- a/tests/Exception/Reflection/MissingChildClassesExceptionTest.php +++ b/tests/Exception/Reflection/MissingChildClassesExceptionTest.php @@ -12,18 +12,50 @@ use Generator; use Meritoo\Common\Exception\Reflection\MissingChildClassesException; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; +use stdClass; /** * Test case of an exception used while given class has no child classes * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Reflection\MissingChildClassesException */ class MissingChildClassesExceptionTest extends BaseTestCase { - public function testConstructorVisibilityAndArguments() + /** + * Provides name of class that hasn't child classes, but it should, and expected exception's message + * + * @return Generator + */ + public function provideParentClass(): ?Generator { - static::assertConstructorVisibilityAndArguments(MissingChildClassesException::class, OopVisibilityType::IS_PUBLIC, 3); + $template = 'The \'%s\' class requires one child class at least who will extend her (maybe is an abstract' + .' class), but the child classes are missing. Did you forget to extend this class?'; + + yield [ + MissingChildClassesException::class, + sprintf($template, MissingChildClassesException::class), + ]; + + yield [ + [ + new stdClass(), + new stdClass(), + ], + sprintf($template, stdClass::class), + ]; + } + + public function testConstructorVisibilityAndArguments(): void + { + static::assertConstructorVisibilityAndArguments( + MissingChildClassesException::class, + OopVisibilityType::IS_PUBLIC, + 3 + ); } /** @@ -33,33 +65,9 @@ class MissingChildClassesExceptionTest extends BaseTestCase * * @dataProvider provideParentClass */ - public function testConstructorMessage($parentClass, $expectedMessage) + public function testCreate($parentClass, string $expectedMessage): void { $exception = MissingChildClassesException::create($parentClass); static::assertSame($expectedMessage, $exception->getMessage()); } - - /** - * Provides name of class that hasn't child classes, but it should, and expected exception's message - * - * @return Generator - */ - public function provideParentClass() - { - $template = 'The \'%s\' class requires one child class at least who will extend her (maybe is an abstract' - . ' class), but the child classes are missing. Did you forget to extend this class?'; - - yield[ - MissingChildClassesException::class, - sprintf($template, MissingChildClassesException::class), - ]; - - yield[ - [ - new \stdClass(), - new \stdClass(), - ], - sprintf($template, \stdClass::class), - ]; - } } diff --git a/tests/Exception/Reflection/NotExistingPropertyExceptionTest.php b/tests/Exception/Reflection/NotExistingPropertyExceptionTest.php index e172003..256a75c 100644 --- a/tests/Exception/Reflection/NotExistingPropertyExceptionTest.php +++ b/tests/Exception/Reflection/NotExistingPropertyExceptionTest.php @@ -8,19 +8,57 @@ namespace Meritoo\Test\Common\Exception\Reflection; +use Generator; use Meritoo\Common\Exception\Reflection\NotExistingPropertyException; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; +use stdClass; /** * Class NotExistingPropertyExceptionTest * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Reflection\NotExistingPropertyException */ class NotExistingPropertyExceptionTest extends BaseTestCase { - public function testConstructor() + public function provideObjectPropertyAndMessage(): ?Generator + { + $template = 'Property \'%s\' does not exist in instance of class \'%s\'. Did you use proper name of property?'; + + yield [ + 'An empty string as name of property', + new stdClass(), + '', + sprintf($template, '', get_class(new stdClass())), + ]; + + yield [ + 'Null as name of property', + new stdClass(), + null, + sprintf($template, '', get_class(new stdClass())), + ]; + + yield [ + 'String with spaces as name of property', + new stdClass(), + 'This is test', + sprintf($template, 'This is test', get_class(new stdClass())), + ]; + + yield [ + 'String without spaces as name of property', + new stdClass(), + 'ThisIsTest', + sprintf($template, 'ThisIsTest', get_class(new stdClass())), + ]; + } + + public function testConstructor(): void { static::assertConstructorVisibilityAndArguments( NotExistingPropertyException::class, @@ -30,49 +68,16 @@ class NotExistingPropertyExceptionTest extends BaseTestCase } /** - * @param string $description Description of test - * @param mixed $object Object that should contains given property - * @param string $property Name of the property - * @param string $expectedMessage Expected exception's message + * @param string $description Description of test + * @param mixed $object Object that should contains given property + * @param null|string $property Name of the property + * @param string $expectedMessage Expected exception's message * * @dataProvider provideObjectPropertyAndMessage */ - public function testCreate($description, $object, $property, $expectedMessage) + public function testCreate(string $description, $object, ?string $property, string $expectedMessage): void { $exception = NotExistingPropertyException::create($object, $property); static::assertSame($expectedMessage, $exception->getMessage(), $description); } - - public function provideObjectPropertyAndMessage() - { - $template = 'Property \'%s\' does not exist in instance of class \'%s\'. Did you use proper name of property?'; - - yield[ - 'An empty string as name of property', - new \stdClass(), - '', - sprintf($template, '', get_class(new \stdClass())), - ]; - - yield[ - 'Null as name of property', - new \stdClass(), - null, - sprintf($template, '', get_class(new \stdClass())), - ]; - - yield[ - 'String with spaces as name of property', - new \stdClass(), - 'This is test', - sprintf($template, 'This is test', get_class(new \stdClass())), - ]; - - yield[ - 'String without spaces as name of property', - new \stdClass(), - 'ThisIsTest', - sprintf($template, 'ThisIsTest', get_class(new \stdClass())), - ]; - } } diff --git a/tests/Exception/Reflection/TooManyChildClassesExceptionTest.php b/tests/Exception/Reflection/TooManyChildClassesExceptionTest.php index 3c19cad..9d0c58d 100644 --- a/tests/Exception/Reflection/TooManyChildClassesExceptionTest.php +++ b/tests/Exception/Reflection/TooManyChildClassesExceptionTest.php @@ -12,18 +12,58 @@ use Generator; use Meritoo\Common\Exception\Reflection\TooManyChildClassesException; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; +use stdClass; /** * Test case of an exception used while given class has more than one child class * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Reflection\TooManyChildClassesException */ class TooManyChildClassesExceptionTest extends BaseTestCase { - public function testConstructorVisibilityAndArguments() + /** + * Provides name of class that has more than one child class, but it shouldn't, child classes, and expected + * exception's message + * + * @return Generator + */ + public function provideParentAndChildClasses(): ?Generator { - static::assertConstructorVisibilityAndArguments(TooManyChildClassesException::class, OopVisibilityType::IS_PUBLIC, 3); + $template = "The '%s' class requires one child class at most who will extend her, but more than one child" + ." class was found:\n- %s\n\nWhy did you create more than one classes that extend '%s' class?"; + + yield [ + BaseTestCase::class, + [ + stdClass::class, + OopVisibilityType::class, + ], + sprintf($template, BaseTestCase::class, implode("\n- ", [ + stdClass::class, + OopVisibilityType::class, + ]), BaseTestCase::class), + ]; + + yield [ + TooManyChildClassesException::class, + [ + stdClass::class, + ], + sprintf($template, TooManyChildClassesException::class, implode("\n- ", [stdClass::class]), TooManyChildClassesException::class), + ]; + } + + public function testConstructor(): void + { + static::assertConstructorVisibilityAndArguments( + TooManyChildClassesException::class, + OopVisibilityType::IS_PUBLIC, + 3 + ); } /** @@ -34,41 +74,9 @@ class TooManyChildClassesExceptionTest extends BaseTestCase * * @dataProvider provideParentAndChildClasses */ - public function testConstructorMessage($parentClass, array $childClasses, $expectedMessage) + public function testCreate($parentClass, array $childClasses, string $expectedMessage): void { $exception = TooManyChildClassesException::create($parentClass, $childClasses); static::assertSame($expectedMessage, $exception->getMessage()); } - - /** - * Provides name of class that has more than one child class, but it shouldn't, child classes, and expected - * exception's message - * - * @return Generator - */ - public function provideParentAndChildClasses() - { - $template = "The '%s' class requires one child class at most who will extend her, but more than one child" - . " class was found:\n- %s\n\nWhy did you create more than one classes that extend '%s' class?"; - - yield[ - BaseTestCase::class, - [ - \stdClass::class, - OopVisibilityType::class, - ], - sprintf($template, BaseTestCase::class, implode("\n- ", [ - \stdClass::class, - OopVisibilityType::class, - ]), BaseTestCase::class), - ]; - - yield[ - TooManyChildClassesException::class, - [ - \stdClass::class, - ], - sprintf($template, TooManyChildClassesException::class, implode("\n- ", [\stdClass::class]), TooManyChildClassesException::class), - ]; - } } diff --git a/tests/Exception/Regex/IncorrectColorHexLengthExceptionTest.php b/tests/Exception/Regex/IncorrectColorHexLengthExceptionTest.php index 9482dbd..754f8ab 100644 --- a/tests/Exception/Regex/IncorrectColorHexLengthExceptionTest.php +++ b/tests/Exception/Regex/IncorrectColorHexLengthExceptionTest.php @@ -18,12 +18,30 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Regex\IncorrectColorHexLengthException */ class IncorrectColorHexLengthExceptionTest extends BaseTestCase { - public function testConstructorVisibilityAndArguments() + /** + * Provides incorrect hexadecimal value of color and expected exception's message + * + * @return Generator + */ + public function provideColor() { - static::assertConstructorVisibilityAndArguments(IncorrectColorHexLengthException::class, OopVisibilityType::IS_PUBLIC, 3); + $template = 'Length of hexadecimal value of color \'%s\' is incorrect. It\'s %d, but it should be 3 or 6. Is there everything ok?'; + + yield [ + '', + sprintf($template, '', strlen('')), + ]; + + yield [ + 'aa-bb-cc', + sprintf($template, 'aa-bb-cc', strlen('aa-bb-cc')), + ]; } /** @@ -38,23 +56,8 @@ class IncorrectColorHexLengthExceptionTest extends BaseTestCase static::assertSame($expectedMessage, $exception->getMessage()); } - /** - * Provides incorrect hexadecimal value of color and expected exception's message - * - * @return Generator - */ - public function provideColor() + public function testConstructorVisibilityAndArguments() { - $template = 'Length of hexadecimal value of color \'%s\' is incorrect. It\'s %d, but it should be 3 or 6. Is there everything ok?'; - - yield[ - '', - sprintf($template, '', strlen('')), - ]; - - yield[ - 'aa-bb-cc', - sprintf($template, 'aa-bb-cc', strlen('aa-bb-cc')), - ]; + static::assertConstructorVisibilityAndArguments(IncorrectColorHexLengthException::class, OopVisibilityType::IS_PUBLIC, 3); } } diff --git a/tests/Exception/Regex/InvalidColorHexValueExceptionTest.php b/tests/Exception/Regex/InvalidColorHexValueExceptionTest.php index ec32268..38b4cf0 100644 --- a/tests/Exception/Regex/InvalidColorHexValueExceptionTest.php +++ b/tests/Exception/Regex/InvalidColorHexValueExceptionTest.php @@ -18,12 +18,30 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Regex\InvalidColorHexValueException */ class InvalidColorHexValueExceptionTest extends BaseTestCase { - public function testConstructorVisibilityAndArguments() + /** + * Provides invalid hexadecimal value of color and expected exception's message + * + * @return Generator + */ + public function provideColor() { - static::assertConstructorVisibilityAndArguments(InvalidColorHexValueException::class, OopVisibilityType::IS_PUBLIC, 3); + $template = 'Hexadecimal value of color \'%s\' is invalid. Is there everything ok?'; + + yield [ + '', + sprintf($template, ''), + ]; + + yield [ + 'aa-bb-cc', + sprintf($template, 'aa-bb-cc'), + ]; } /** @@ -38,23 +56,8 @@ class InvalidColorHexValueExceptionTest extends BaseTestCase static::assertSame($expectedMessage, $exception->getMessage()); } - /** - * Provides invalid hexadecimal value of color and expected exception's message - * - * @return Generator - */ - public function provideColor() + public function testConstructorVisibilityAndArguments() { - $template = 'Hexadecimal value of color \'%s\' is invalid. Is there everything ok?'; - - yield[ - '', - sprintf($template, ''), - ]; - - yield[ - 'aa-bb-cc', - sprintf($template, 'aa-bb-cc'), - ]; + static::assertConstructorVisibilityAndArguments(InvalidColorHexValueException::class, OopVisibilityType::IS_PUBLIC, 3); } } diff --git a/tests/Exception/Regex/InvalidHtmlAttributesExceptionTest.php b/tests/Exception/Regex/InvalidHtmlAttributesExceptionTest.php index 6772791..799169d 100644 --- a/tests/Exception/Regex/InvalidHtmlAttributesExceptionTest.php +++ b/tests/Exception/Regex/InvalidHtmlAttributesExceptionTest.php @@ -18,12 +18,35 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Regex\InvalidHtmlAttributesException */ class InvalidHtmlAttributesExceptionTest extends BaseTestCase { - public function testConstructorVisibilityAndArguments() + /** + * Provides html attributes + * + * @return Generator + */ + public function provideHtmlAttributes() { - static::assertConstructorVisibilityAndArguments(InvalidHtmlAttributesException::class, OopVisibilityType::IS_PUBLIC, 3); + $template = 'HTML attributes \'%s\' are invalid. Is there everything ok?'; + + yield [ + 'abc = def', + sprintf($template, 'abc = def'), + ]; + + yield [ + 'abc = def ghi = jkl', + sprintf($template, 'abc = def ghi = jkl'), + ]; + + yield [ + 'abc=def ghi=jkl', + sprintf($template, 'abc=def ghi=jkl'), + ]; } /** @@ -38,28 +61,8 @@ class InvalidHtmlAttributesExceptionTest extends BaseTestCase static::assertSame($expectedMessage, $exception->getMessage()); } - /** - * Provides html attributes - * - * @return Generator - */ - public function provideHtmlAttributes() + public function testConstructorVisibilityAndArguments() { - $template = 'HTML attributes \'%s\' are invalid. Is there everything ok?'; - - yield[ - 'abc = def', - sprintf($template, 'abc = def'), - ]; - - yield[ - 'abc = def ghi = jkl', - sprintf($template, 'abc = def ghi = jkl'), - ]; - - yield[ - 'abc=def ghi=jkl', - sprintf($template, 'abc=def ghi=jkl'), - ]; + static::assertConstructorVisibilityAndArguments(InvalidHtmlAttributesException::class, OopVisibilityType::IS_PUBLIC, 3); } } diff --git a/tests/Exception/Regex/InvalidUrlExceptionTest.php b/tests/Exception/Regex/InvalidUrlExceptionTest.php index 0e36d77..cee47d9 100644 --- a/tests/Exception/Regex/InvalidUrlExceptionTest.php +++ b/tests/Exception/Regex/InvalidUrlExceptionTest.php @@ -18,12 +18,30 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Regex\InvalidUrlException */ class InvalidUrlExceptionTest extends BaseTestCase { - public function testConstructorVisibilityAndArguments() + /** + * Provides invalid url and expected exception's message + * + * @return Generator + */ + public function provideUrl() { - static::assertConstructorVisibilityAndArguments(InvalidUrlException::class, OopVisibilityType::IS_PUBLIC, 3); + $template = 'Url \'%s\' is invalid. Is there everything ok?'; + + yield [ + 'aa/bb/cc', + sprintf($template, 'aa/bb/cc'), + ]; + + yield [ + 'http:/images\show\car.jpg', + sprintf($template, 'http:/images\show\car.jpg'), + ]; } /** @@ -38,23 +56,8 @@ class InvalidUrlExceptionTest extends BaseTestCase static::assertSame($expectedMessage, $exception->getMessage()); } - /** - * Provides invalid url and expected exception's message - * - * @return Generator - */ - public function provideUrl() + public function testConstructorVisibilityAndArguments() { - $template = 'Url \'%s\' is invalid. Is there everything ok?'; - - yield[ - 'aa/bb/cc', - sprintf($template, 'aa/bb/cc'), - ]; - - yield[ - 'http:/images\show\car.jpg', - sprintf($template, 'http:/images\show\car.jpg'), - ]; + static::assertConstructorVisibilityAndArguments(InvalidUrlException::class, OopVisibilityType::IS_PUBLIC, 3); } } diff --git a/tests/Exception/Type/UnknownOopVisibilityTypeExceptionTest.php b/tests/Exception/Type/UnknownOopVisibilityTypeExceptionTest.php index 3f8377b..fb71f5b 100644 --- a/tests/Exception/Type/UnknownOopVisibilityTypeExceptionTest.php +++ b/tests/Exception/Type/UnknownOopVisibilityTypeExceptionTest.php @@ -19,12 +19,33 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\Type\UnknownOopVisibilityTypeException */ class UnknownOopVisibilityTypeExceptionTest extends BaseTestCase { - public function testConstructorVisibilityAndArguments() + /** + * Provides path of the empty file and expected exception's message + * + * @return Generator + */ + public function provideUnknownType(): Generator { - static::assertConstructorVisibilityAndArguments(UnknownOopVisibilityTypeException::class, OopVisibilityType::IS_PUBLIC, 3); + $allTypes = (new OopVisibilityType())->getAll(); + + $template = 'The \'%s\' type of OOP-related visibility is unknown. Probably doesn\'t exist or there is a typo.' + .' You should use one of these types: %s.'; + + yield [ + '', + sprintf($template, '', implode(', ', $allTypes)), + ]; + + yield [ + 123, + sprintf($template, 123, implode(', ', $allTypes)), + ]; } /** @@ -33,32 +54,18 @@ class UnknownOopVisibilityTypeExceptionTest extends BaseTestCase * * @dataProvider provideUnknownType */ - public function testConstructorMessage($unknownType, $expectedMessage) + public function testConstructorMessage($unknownType, $expectedMessage): void { $exception = UnknownOopVisibilityTypeException::createException($unknownType); static::assertSame($expectedMessage, $exception->getMessage()); } - /** - * Provides path of the empty file and expected exception's message - * - * @return Generator - */ - public function provideUnknownType() + public function testConstructorVisibilityAndArguments(): void { - $allTypes = (new OopVisibilityType())->getAll(); - - $template = 'The \'%s\' type of OOP-related visibility is unknown. Probably doesn\'t exist or there is a typo.' - . ' You should use one of these types: %s.'; - - yield[ - '', - sprintf($template, '', implode(', ', $allTypes)), - ]; - - yield[ - 123, - sprintf($template, 123, implode(', ', $allTypes)), - ]; + static::assertConstructorVisibilityAndArguments( + UnknownOopVisibilityTypeException::class, + OopVisibilityType::IS_PUBLIC, + 3 + ); } } diff --git a/tests/Exception/ValueObject/InvalidSizeDimensionsExceptionTest.php b/tests/Exception/ValueObject/InvalidSizeDimensionsExceptionTest.php index ece3449..6487c98 100644 --- a/tests/Exception/ValueObject/InvalidSizeDimensionsExceptionTest.php +++ b/tests/Exception/ValueObject/InvalidSizeDimensionsExceptionTest.php @@ -17,9 +17,35 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\ValueObject\InvalidSizeDimensionsException */ class InvalidSizeDimensionsExceptionTest extends BaseTestCase { + public function provideWidthAndHeight() + { + $template = 'Dimensions of size should be positive, but they are not: %d, %d. Is there everything ok?'; + + yield [ + 0, + 0, + sprintf($template, 0, 0), + ]; + + yield [ + -1, + -1, + sprintf($template, -1, -1), + ]; + + yield [ + 200, + 100, + sprintf($template, 200, 100), + ]; + } + public function testConstructorVisibilityAndArguments() { static::assertConstructorVisibilityAndArguments( @@ -41,27 +67,4 @@ class InvalidSizeDimensionsExceptionTest extends BaseTestCase $exception = InvalidSizeDimensionsException::create($width, $height); static::assertSame($expectedMessage, $exception->getMessage()); } - - public function provideWidthAndHeight() - { - $template = 'Dimensions of size should be positive, but they are not: %d, %d. Is there everything ok?'; - - yield[ - 0, - 0, - sprintf($template, 0, 0), - ]; - - yield[ - -1, - -1, - sprintf($template, -1, -1), - ]; - - yield[ - 200, - 100, - sprintf($template, 200, 100), - ]; - } } diff --git a/tests/Exception/ValueObject/Template/InvalidContentExceptionTest.php b/tests/Exception/ValueObject/Template/InvalidContentExceptionTest.php new file mode 100644 index 0000000..6392407 --- /dev/null +++ b/tests/Exception/ValueObject/Template/InvalidContentExceptionTest.php @@ -0,0 +1,71 @@ + + * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\ValueObject\Template\InvalidContentException + */ +class InvalidContentExceptionTest extends BaseTestCase +{ + public function provideContent(): ?Generator + { + $template = 'Content of template \'%s\' is invalid. Did you use string with 1 placeholder at least?'; + + yield [ + 'An empty string', + '', + sprintf($template, ''), + ]; + + yield [ + 'Simple string', + 'Lorem ipsum', + sprintf($template, 'Lorem ipsum'), + ]; + + yield [ + 'One sentence', + 'Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh.', + sprintf($template, 'Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh.'), + ]; + } + + public function testConstructorVisibilityAndArguments(): void + { + static::assertConstructorVisibilityAndArguments( + InvalidContentException::class, + OopVisibilityType::IS_PUBLIC, + 3 + ); + } + + /** + * @param string $description Description of test + * @param string $content Invalid content of template + * @param string $expectedMessage Expected exception's message + * + * @dataProvider provideContent + */ + public function testCreate(string $description, string $content, string $expectedMessage): void + { + $exception = InvalidContentException::create($content); + static::assertSame($expectedMessage, $exception->getMessage(), $description); + } +} diff --git a/tests/Exception/ValueObject/Template/MissingPlaceholdersInValuesExceptionTest.php b/tests/Exception/ValueObject/Template/MissingPlaceholdersInValuesExceptionTest.php new file mode 100644 index 0000000..86a6602 --- /dev/null +++ b/tests/Exception/ValueObject/Template/MissingPlaceholdersInValuesExceptionTest.php @@ -0,0 +1,86 @@ + + * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\ValueObject\Template\MissingPlaceholdersInValuesException + */ +class MissingPlaceholdersInValuesExceptionTest extends BaseTestCase +{ + public function provideContentAndMissingPlaceholders(): ?Generator + { + $template = 'Cannot fill template \'%s\', because of missing values for placeholder(s): %s. Did you provide all' + .' required values?'; + + yield [ + 'Missing 2nd placeholder', + '%test1% - %test2%', + [ + 'test2', + ], + sprintf( + $template, + '%test1% - %test2%', + 'test2' + ), + ]; + + yield [ + 'Missing 2nd and 3rd placeholder', + '%test1% / %test2% / %test3%', + [ + 'test2', + 'test3', + ], + sprintf( + $template, + '%test1% / %test2% / %test3%', + 'test2, test3' + ), + ]; + } + + public function testConstructorVisibilityAndArguments(): void + { + static::assertConstructorVisibilityAndArguments( + MissingPlaceholdersInValuesException::class, + OopVisibilityType::IS_PUBLIC, + 3 + ); + } + + /** + * @param string $description Description of test + * @param string $content Content of template + * @param array $missingPlaceholders Missing placeholders in provided values, iow. placeholders without values + * @param string $expectedMessage Expected exception's message + * + * @dataProvider provideContentAndMissingPlaceholders + */ + public function testCreate( + string $description, + string $content, + array $missingPlaceholders, + string $expectedMessage + ): void { + $exception = MissingPlaceholdersInValuesException::create($content, $missingPlaceholders); + static::assertSame($expectedMessage, $exception->getMessage(), $description); + } +} diff --git a/tests/Exception/ValueObject/Template/TemplateNotFoundExceptionTest.php b/tests/Exception/ValueObject/Template/TemplateNotFoundExceptionTest.php new file mode 100644 index 0000000..4616f7a --- /dev/null +++ b/tests/Exception/ValueObject/Template/TemplateNotFoundExceptionTest.php @@ -0,0 +1,71 @@ + + * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Exception\ValueObject\Template\TemplateNotFoundException + */ +class TemplateNotFoundExceptionTest extends BaseTestCase +{ + public function provideIndexAndException(): ?Generator + { + $template = 'Template with \'%s\' index was not found. Did you provide all required templates?'; + + yield [ + 'An empty string', + '', + new TemplateNotFoundException(sprintf($template, '')), + ]; + + yield [ + 'Non-empty string', + 'test', + new TemplateNotFoundException(sprintf($template, 'test')), + ]; + + yield [ + 'Integer', + '2', + new TemplateNotFoundException(sprintf($template, 2)), + ]; + } + + public function testConstructor(): void + { + static::assertConstructorVisibilityAndArguments( + TemplateNotFoundException::class, + OopVisibilityType::IS_PUBLIC, + 3 + ); + } + + /** + * @param string $description Description of test + * @param string $index Index that should contain template, but it was not found + * @param TemplateNotFoundException $expected Expected exception + * + * @dataProvider provideIndexAndException + */ + public function testCreate(string $description, string $index, TemplateNotFoundException $expected): void + { + $created = TemplateNotFoundException::create($index); + static::assertEquals($expected, $created, $description); + } +} diff --git a/tests/Test/Base/BaseTestCaseTest.php b/tests/Test/Base/BaseTestCaseTest.php index 71dc496..83c2308 100644 --- a/tests/Test/Base/BaseTestCaseTest.php +++ b/tests/Test/Base/BaseTestCaseTest.php @@ -19,27 +19,61 @@ use Meritoo\Common\Utilities\GeneratorUtility; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Test\Base\BaseTestCase */ class BaseTestCaseTest extends BaseTestCase { + /** + * Provides name of file and path of directory containing the file + * + * @return Generator + */ + public function provideFileNameAndDirectoryPath() + { + yield [ + 'abc.jpg', + '', + ]; + + yield [ + 'abc.def.jpg', + '', + ]; + + yield [ + 'abc.jpg', + 'def', + ]; + + yield [ + 'abc.def.jpg', + 'def', + ]; + } + public function testConstructor() { static::assertConstructorVisibilityAndArguments(BaseTestCase::class, OopVisibilityType::IS_PUBLIC, 3); } - public function testProvideEmptyValue() + /** + * @param string $fileName Name of file + * @param string $directoryPath Path of directory containing the file + * + * @dataProvider provideFileNameAndDirectoryPath + */ + public function testGetFilePathForTesting($fileName, $directoryPath) { - $elements = [ - [''], - [' '], - [null], - [0], - [false], - [[]], - ]; + $path = (new SimpleTestCase())->getFilePathForTesting($fileName, $directoryPath); - $generator = (new SimpleTestCase())->provideEmptyValue(); - self::assertEquals($elements, GeneratorUtility::getGeneratorElements($generator)); + if (!empty($directoryPath)) { + $directoryPath .= '/'; + } + + $expectedContains = sprintf('/data/tests/%s%s', $directoryPath, $fileName); + static::assertStringContainsString($expectedContains, $path); } public function testProvideBooleanValue() @@ -67,16 +101,16 @@ class BaseTestCaseTest extends BaseTestCase $generator = (new SimpleTestCase())->provideDateTimeInstance(); $generatedElements = GeneratorUtility::getGeneratorElements($generator); - /* @var DateTime $instance1 */ + /** @var DateTime $instance1 */ $instance1 = $generatedElements[0][0]; - /* @var DateTime $instance2 */ + /** @var DateTime $instance2 */ $instance2 = $generatedElements[1][0]; - /* @var DateTime $instance3 */ + /** @var DateTime $instance3 */ $instance3 = $generatedElements[2][0]; - /* @var DateTime $instance4 */ + /** @var DateTime $instance4 */ $instance4 = $generatedElements[3][0]; self::assertCount(count($expectedElements), $generatedElements); @@ -106,6 +140,21 @@ class BaseTestCaseTest extends BaseTestCase self::assertEquals($elements, GeneratorUtility::getGeneratorElements($generator)); } + public function testProvideEmptyValue() + { + $elements = [ + [''], + [' '], + [null], + [0], + [false], + [[]], + ]; + + $generator = (new SimpleTestCase())->provideEmptyValue(); + self::assertEquals($elements, GeneratorUtility::getGeneratorElements($generator)); + } + public function testProvideNotExistingFilePath() { $elements = [ @@ -117,52 +166,6 @@ class BaseTestCaseTest extends BaseTestCase $generator = (new SimpleTestCase())->provideNotExistingFilePath(); self::assertEquals($elements, GeneratorUtility::getGeneratorElements($generator)); } - - /** - * @param string $fileName Name of file - * @param string $directoryPath Path of directory containing the file - * - * @dataProvider provideFileNameAndDirectoryPath - */ - public function testGetFilePathForTesting($fileName, $directoryPath) - { - $path = (new SimpleTestCase())->getFilePathForTesting($fileName, $directoryPath); - - if (!empty($directoryPath)) { - $directoryPath .= '/'; - } - - $expectedContains = sprintf('/data/tests/%s%s', $directoryPath, $fileName); - static::assertContains($expectedContains, $path); - } - - /** - * Provides name of file and path of directory containing the file - * - * @return Generator - */ - public function provideFileNameAndDirectoryPath() - { - yield[ - 'abc.jpg', - '', - ]; - - yield[ - 'abc.def.jpg', - '', - ]; - - yield[ - 'abc.jpg', - 'def', - ]; - - yield[ - 'abc.def.jpg', - 'def', - ]; - } } /** @@ -170,6 +173,9 @@ class BaseTestCaseTest extends BaseTestCase * * @author Meritoo * @copyright Meritoo + * + * @internal + * @coversNothing */ class SimpleTestCase extends BaseTestCase { diff --git a/tests/Traits/Test/Base/BaseTestCaseTrait/SimpleTestCase.php b/tests/Traits/Test/Base/BaseTestCaseTrait/SimpleTestCase.php new file mode 100644 index 0000000..02258b6 --- /dev/null +++ b/tests/Traits/Test/Base/BaseTestCaseTrait/SimpleTestCase.php @@ -0,0 +1,26 @@ + + * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Traits\Test\Base\BaseTestCaseTrait + */ +class BaseTestCaseTraitTest extends BaseTestCase +{ + use BaseTestCaseTrait; + + public function testAssertConstructorVisibilityAndArgumentsUsingClassWithoutConstructor(): void + { + $this->expectException(ClassWithoutConstructorException::class); + static::assertConstructorVisibilityAndArguments(SimpleTestCase::class, OopVisibilityType::IS_PUBLIC); + } + + public function testAssertHasNoConstructor(): void + { + static::assertHasNoConstructor(SimpleTestCase::class); + } + + public function testAssertMethodVisibility(): void + { + $method = new ReflectionMethod(SimpleTestCase::class, 'assertMethodVisibility'); + static::assertMethodVisibility($method, OopVisibilityType::IS_PROTECTED); + } + + public function testAssertMethodVisibilityUsingIncorrectVisibility(): void + { + $this->expectException(UnknownOopVisibilityTypeException::class); + + $method = new ReflectionMethod(SimpleTestCase::class, 'assertMethodVisibility'); + static::assertMethodVisibility($method, '4'); + } + + public function testAssertMethodVisibilityUsingPrivate(): void + { + $method = new ReflectionMethod(SimpleTestCase::class, 'thePrivateMethod'); + static::assertMethodVisibility($method, OopVisibilityType::IS_PRIVATE); + } + + public function testProvideBooleanValue(): void + { + $testCase = new SimpleTestCase(); + $values = $testCase->provideBooleanValue(); + + $expected = [ + [false], + [true], + ]; + + foreach ($values as $index => $value) { + static::assertSame($expected[$index], $value); + } + } + + public function testProvideDateTimeInstance(): void + { + $testCase = new SimpleTestCase(); + $instances = $testCase->provideDateTimeInstance(); + + $expected = [ + [new DateTime()], + [new DateTime('yesterday')], + [new DateTime('now')], + [new DateTime('tomorrow')], + ]; + + foreach ($instances as $index => $instance) { + /** @var DateTime $expectedInstance */ + $expectedInstance = $expected[$index][0]; + + /** @var DateTime $instance */ + $instance = $instance[0]; + + static::assertInstanceOf(DateTime::class, $instance); + static::assertEquals($expectedInstance->getTimestamp(), $instance->getTimestamp()); + } + } + + public function testProvideDateTimeRelativeFormatInstance(): void + { + $testCase = new SimpleTestCase(); + $formats = $testCase->provideDateTimeRelativeFormat(); + + $expected = [ + ['now'], + ['yesterday'], + ['tomorrow'], + ['back of 10'], + ['front of 10'], + ['last day of February'], + ['first day of next month'], + ['last day of previous month'], + ['last day of next month'], + ['Y-m-d'], + ['Y-m-d 10:00'], + ]; + + foreach ($formats as $index => $format) { + static::assertSame($expected[$index], $format); + } + } + + public function testProvideEmptyScalarValue(): void + { + $testCase = new SimpleTestCase(); + $values = $testCase->provideEmptyScalarValue(); + + $expected = [ + [''], + [' '], + [null], + [0], + [false], + ]; + + foreach ($values as $index => $value) { + static::assertSame($expected[$index], $value); + } + } + + public function testProvideEmptyValue(): void + { + $testCase = new SimpleTestCase(); + $values = $testCase->provideEmptyValue(); + + $expected = [ + [''], + [' '], + [null], + [0], + [false], + [[]], + ]; + + foreach ($values as $index => $value) { + static::assertSame($expected[$index], $value); + } + } + + public function testProvideNonScalarValue(): void + { + $testCase = new SimpleTestCase(); + $values = $testCase->provideNonScalarValue(); + + $expected = [ + [[]], + [null], + [new stdClass()], + ]; + + foreach ($values as $index => $value) { + static::assertEquals($expected[$index], $value); + } + } + + public function testProvideNotExistingFilePath(): void + { + $testCase = new SimpleTestCase(); + $paths = $testCase->provideNotExistingFilePath(); + + $expected = [ + ['lets-test.doc'], + ['lorem/ipsum.jpg'], + ['surprise/me/one/more/time.txt'], + ]; + + foreach ($paths as $index => $path) { + static::assertSame($expected[$index], $path); + } + } +} diff --git a/tests/Type/Base/BaseTypeTest.php b/tests/Type/Base/BaseTypeTest.php index f21a1dc..bf43078 100644 --- a/tests/Type/Base/BaseTypeTest.php +++ b/tests/Type/Base/BaseTypeTest.php @@ -17,51 +17,25 @@ use Meritoo\Common\Type\Base\BaseType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Type\Base\BaseType */ class BaseTypeTest extends BaseTestCase { - public function testConstructor() - { - static::assertHasNoConstructor(BaseType::class); - } - - /** - * @param BaseType $type Type of something - * @param array $expectedTypes Expected concrete types of given instance of type - * - * @dataProvider provideType - */ - public function testGetAll(BaseType $type, array $expectedTypes) - { - $all = $type->getAll(); - self::assertEquals($expectedTypes, $all); - } - - /** - * @param BaseType $type Type of something - * @param string $toVerifyType Concrete type to verify (of given instance of type) - * @param bool $isCorrect Expected information if given type is correct - * - * @dataProvider provideTypeWithConcreteType - */ - public function testIsCorrectType(BaseType $type, $toVerifyType, $isCorrect) - { - self::assertEquals($isCorrect, $type->isCorrectType($toVerifyType)); - } - /** * Provides type of something for testing the getAll() method * * @return Generator */ - public function provideType() + public function provideType(): ?Generator { - yield[ + yield [ new TestEmptyType(), [], ]; - yield[ + yield [ new TestType(), [ 'TEST_1' => TestType::TEST_1, @@ -75,110 +49,150 @@ class BaseTypeTest extends BaseTestCase * * @return Generator */ - public function provideTypeWithConcreteType() + public function provideTypeToVerifyUsingTestEmptyType(): ?Generator { - yield[ - new TestEmptyType(), + yield [ null, false, ]; - yield[ - new TestEmptyType(), - false, + yield [ + 'null', false, ]; - yield[ - new TestEmptyType(), - true, + yield [ + 'false', false, ]; - yield[ - new TestEmptyType(), + yield [ + 'true', + false, + ]; + + yield [ '', false, ]; - yield[ - new TestEmptyType(), - 0, + yield [ + '0', false, ]; - yield[ - new TestEmptyType(), - 1, + yield [ + '1', false, ]; - yield[ - new TestEmptyType(), + yield [ 'lorem', false, ]; + } - yield[ - new TestType(), + /** + * Provides type of something for testing the isCorrectType() method + * + * @return Generator + */ + public function provideTypeToVerifyUsingTestType(): ?Generator + { + yield [ null, false, ]; - yield[ - new TestType(), - false, + yield [ + 'null', false, ]; - yield[ - new TestType(), - true, + yield [ + 'false', false, ]; - yield[ - new TestType(), + yield [ + 'true', + false, + ]; + + yield [ '', false, ]; - yield[ - new TestType(), - 0, + yield [ + '0', false, ]; - yield[ - new TestType(), - 1, + yield [ + '1', false, ]; - yield[ - new TestType(), + yield [ 'lorem', false, ]; - yield[ - new TestType(), + yield [ 'test', false, ]; - yield[ - new TestType(), - TestType::TEST_1, + yield [ + 'test_1', true, ]; - yield[ - new TestType(), - TestType::TEST_2, + yield [ + 'test_2', true, ]; } + + public function testConstructor(): void + { + static::assertHasNoConstructor(BaseType::class); + } + + /** + * @param BaseType $type Type of something + * @param array $expectedTypes Expected concrete types of given instance of type + * + * @dataProvider provideType + */ + public function testGetAll(BaseType $type, array $expectedTypes): void + { + $all = $type->getAll(); + self::assertEquals($expectedTypes, $all); + } + + /** + * @param string $toVerifyType Concrete type to verify + * @param bool $isCorrect Expected information if given type is correct + * + * @dataProvider provideTypeToVerifyUsingTestEmptyType + */ + public function testIsCorrectTypeUsingTestEmptyType(?string $toVerifyType, bool $isCorrect): void + { + self::assertEquals($isCorrect, TestEmptyType::isCorrectType($toVerifyType)); + } + + /** + * @param string $toVerifyType Concrete type to verify + * @param bool $isCorrect Expected information if given type is correct + * + * @dataProvider provideTypeToVerifyUsingTestType + */ + public function testIsCorrectTypeUsingTestType(?string $toVerifyType, bool $isCorrect): void + { + self::assertEquals($isCorrect, TestType::isCorrectType($toVerifyType)); + } } /** @@ -199,7 +213,7 @@ class TestEmptyType extends BaseType */ class TestType extends BaseType { - const TEST_1 = 'test_1'; + public const TEST_1 = 'test_1'; - const TEST_2 = 'test_2'; + public const TEST_2 = 'test_2'; } diff --git a/tests/Type/DatePartTypeTest.php b/tests/Type/DatePartTypeTest.php index 782c259..5a24fe1 100644 --- a/tests/Type/DatePartTypeTest.php +++ b/tests/Type/DatePartTypeTest.php @@ -8,7 +8,9 @@ namespace Meritoo\Test\Common\Type; +use Generator; use Meritoo\Common\Test\Base\BaseTypeTestCase; +use Meritoo\Common\Type\Base\BaseType; use Meritoo\Common\Type\DatePartType; /** @@ -16,61 +18,64 @@ use Meritoo\Common\Type\DatePartType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Type\DatePartType */ class DatePartTypeTest extends BaseTypeTestCase { /** * {@inheritdoc} */ - public function provideTypeToVerify() + public function provideTypeToVerify(): Generator { - yield[ - '', + yield [ + DatePartType::isCorrectType(''), false, ]; - yield[ - null, + yield [ + DatePartType::isCorrectType(null), false, ]; - yield[ - 0, + yield [ + DatePartType::isCorrectType('0'), false, ]; - yield[ - 1, + yield [ + DatePartType::isCorrectType('1'), false, ]; - yield[ - 'day', + yield [ + DatePartType::isCorrectType('day'), true, ]; - yield[ - 'hour', + yield [ + DatePartType::isCorrectType('hour'), true, ]; - yield[ - 'minute', + yield [ + DatePartType::isCorrectType('minute'), true, ]; - yield[ - 'month', + yield [ + DatePartType::isCorrectType('month'), true, ]; - yield[ - 'second', + yield [ + DatePartType::isCorrectType('second'), true, ]; - yield[ - 'year', + yield [ + DatePartType::isCorrectType('year'), true, ]; } @@ -78,22 +83,22 @@ class DatePartTypeTest extends BaseTypeTestCase /** * {@inheritdoc} */ - protected function getAllExpectedTypes() + protected function getAllExpectedTypes(): array { return [ - 'DAY' => DatePartType::DAY, - 'HOUR' => DatePartType::HOUR, - 'MINUTE' => DatePartType::MINUTE, - 'MONTH' => DatePartType::MONTH, - 'SECOND' => DatePartType::SECOND, - 'YEAR' => DatePartType::YEAR, + 'DAY' => 'day', + 'HOUR' => 'hour', + 'MINUTE' => 'minute', + 'MONTH' => 'month', + 'SECOND' => 'second', + 'YEAR' => 'year', ]; } /** * {@inheritdoc} */ - protected function getTestedTypeInstance() + protected function getTestedTypeInstance(): BaseType { return new DatePartType(); } diff --git a/tests/Type/DatePeriodTest.php b/tests/Type/DatePeriodTest.php index a73ceaf..a45bb3d 100644 --- a/tests/Type/DatePeriodTest.php +++ b/tests/Type/DatePeriodTest.php @@ -11,6 +11,7 @@ namespace Meritoo\Test\Common\Type; use DateTime; use Generator; use Meritoo\Common\Test\Base\BaseTypeTestCase; +use Meritoo\Common\Type\Base\BaseType; use Meritoo\Common\Type\DatePeriod; use Meritoo\Common\Type\OopVisibilityType; @@ -19,12 +20,228 @@ use Meritoo\Common\Type\OopVisibilityType; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Type\DatePeriod */ class DatePeriodTest extends BaseTypeTestCase { - public function testConstructorVisibilityAndArguments() + /** + * Provides the start and end date of date period + * + * @return Generator + */ + public function provideDatePeriod(): Generator { - static::assertConstructorVisibilityAndArguments(DatePeriod::class, OopVisibilityType::IS_PUBLIC, 2, 0); + $startDate = new DateTime('2001-01-01'); + $endDate = new DateTime('2002-02-02'); + + yield [ + null, + null, + ]; + + yield [ + $startDate, + $startDate, + null, + ]; + + yield [ + null, + null, + $endDate, + ]; + + yield [ + $startDate, + $endDate, + ]; + } + + /** + * Provides period and format of date to verify + * + * @return Generator + */ + public function provideDatePeriodAndDateFormat(): Generator + { + $startDate = new DateTime('2001-01-01'); + $endDate = new DateTime('2002-02-02'); + + // For start date + yield [ + new DatePeriod($startDate, $endDate), + 'Y', + true, + '2001', + ]; + + yield [ + new DatePeriod($startDate, $endDate), + 'D', + true, + 'Mon', + ]; + + yield [ + new DatePeriod($startDate, $endDate), + 'Y-m-d', + true, + '2001-01-01', + ]; + + yield [ + new DatePeriod($startDate, $endDate), + 'Y-m-d H:i', + true, + '2001-01-01 00:00', + ]; + + // For end date + yield [ + new DatePeriod($startDate, $endDate), + 'Y', + false, + '2002', + ]; + + yield [ + new DatePeriod($startDate, $endDate), + 'D', + false, + 'Sat', + ]; + + yield [ + new DatePeriod($startDate, $endDate), + 'Y-m-d', + false, + '2002-02-02', + ]; + + yield [ + new DatePeriod($startDate, $endDate), + 'Y-m-d H:i', + false, + '2002-02-02 00:00', + ]; + } + + /** + * Provides period and format of date to verify using the start date + * + * @return Generator + */ + public function provideDatePeriodAndDateFormatUsingStartDateOnly(): Generator + { + $startDate = new DateTime('2001-01-01'); + $endDate = new DateTime('2002-02-02'); + + yield [ + new DatePeriod($startDate, $endDate), + 'Y', + '2001', + ]; + + yield [ + new DatePeriod($startDate, $endDate), + 'D', + 'Mon', + ]; + + yield [ + new DatePeriod($startDate, $endDate), + 'Y-m-d', + '2001-01-01', + ]; + + yield [ + new DatePeriod($startDate, $endDate), + 'Y-m-d H:i', + '2001-01-01 00:00', + ]; + } + + /** + * Provides period and incorrect format of date to verify + * + * @return Generator + */ + public function provideDatePeriodAndIncorrectDateFormat(): Generator + { + $startDate = new DateTime('2001-01-01'); + $endDate = new DateTime('2002-02-02'); + + yield [ + new DatePeriod($startDate, $endDate), + '', + ]; + + yield [ + new DatePeriod($startDate, $endDate), + false, + ]; + } + + public function provideDatePeriodAndUnknownDate(): ?Generator + { + $date = new DateTime('2001-01-01'); + + yield [ + new DatePeriod(), + 'Y-m-d', + false, + ]; + + yield [ + new DatePeriod(), + 'Y-m-d', + true, + ]; + + yield [ + new DatePeriod($date), + 'Y-m-d', + false, + ]; + + yield [ + new DatePeriod(null, $date), + 'Y-m-d', + true, + ]; + } + + /** + * {@inheritdoc} + */ + public function provideTypeToVerify(): Generator + { + yield [ + DatePeriod::isCorrectType(''), + false, + ]; + + yield [ + DatePeriod::isCorrectType('-1'), + false, + ]; + + yield [ + DatePeriod::isCorrectType('4'), + true, + ]; + + yield [ + DatePeriod::isCorrectType('3'), + true, + ]; + + yield [ + DatePeriod::isCorrectType('8'), + true, + ]; } /** @@ -33,7 +250,7 @@ class DatePeriodTest extends BaseTypeTestCase * * @dataProvider provideDatePeriod */ - public function testConstruct(DateTime $startDate = null, DateTime $endDate = null) + public function testConstruct(DateTime $startDate = null, DateTime $endDate = null): void { $period = new DatePeriod($startDate, $endDate); @@ -41,13 +258,70 @@ class DatePeriodTest extends BaseTypeTestCase self::assertEquals($endDate, $period->getEndDate()); } + public function testConstructorVisibilityAndArguments(): void + { + static::assertConstructorVisibilityAndArguments( + DatePeriod::class, + OopVisibilityType::IS_PUBLIC, + 2 + ); + } + + /** + * @param DatePeriod $period The date period to verify + * @param string $format Format used to format the date + * @param bool $startDate If is set to true, start date is formatted. Otherwise - end date. + * @param string $expected Expected, formatted date + * + * @dataProvider provideDatePeriodAndDateFormat + */ + public function testGetFormattedDate(DatePeriod $period, $format, $startDate, $expected): void + { + self::assertEquals($expected, $period->getFormattedDate($format, $startDate)); + } + + /** + * @param DatePeriod $period The date period to verify + * @param string $format Format used to format the date + * + * @dataProvider provideDatePeriodAndIncorrectDateFormat + */ + public function testGetFormattedDateUsingIncorrectDateFormat(DatePeriod $period, $format): void + { + self::assertEquals('', $period->getFormattedDate($format)); + } + + /** + * @param DatePeriod $period The date period to verify + * @param string $format Format used to format the date + * @param string $expected Expected, formatted date + * + * @dataProvider provideDatePeriodAndDateFormatUsingStartDateOnly + */ + public function testGetFormattedDateUsingStartDateOnly(DatePeriod $period, $format, $expected): void + { + self::assertEquals($expected, $period->getFormattedDate($format)); + } + + /** + * @param DatePeriod $period The date period to verify + * @param string $format Format used to format the date + * @param bool $startDate If is set to true, start date is formatted. Otherwise - end date. + * + * @dataProvider provideDatePeriodAndUnknownDate + */ + public function testGetFormattedDateUsingUnknownDate(DatePeriod $period, $format, $startDate): void + { + self::assertEquals('', $period->getFormattedDate($format, $startDate)); + } + /** * @param DateTime $startDate (optional) Start date of period * @param DateTime $endDate (optional) End date of period * * @dataProvider provideDatePeriod */ - public function testGettersAndSetters(DateTime $startDate = null, DateTime $endDate = null) + public function testGettersAndSetters(DateTime $startDate = null, DateTime $endDate = null): void { $period = new DatePeriod(); @@ -58,222 +332,30 @@ class DatePeriodTest extends BaseTypeTestCase self::assertEquals($endDate, $period->getEndDate()); } - /** - * @param DatePeriod $period The date period to verify - * @param string $format Format used to format the date - * - * @dataProvider provideDatePeriodAndIncorrectDateFormat - */ - public function testGetFormattedDateIncorrectDateFormat(DatePeriod $period, $format) - { - self::assertEquals('', $period->getFormattedDate($format)); - } - - /** - * @param DatePeriod $period The date period to verify - * @param string $format Format used to format the date - * @param bool $startDate If is set to true, start date is formatted. Otherwise - end date. - * @param string $expected Expected, formatted date - * - * @dataProvider provideDatePeriodAndDateFormat - */ - public function testGetFormattedDate(DatePeriod $period, $format, $startDate, $expected) - { - self::assertEquals($expected, $period->getFormattedDate($format, $startDate)); - } - - /** - * Provides the start and end date of date period - * - * @return Generator - */ - public function provideDatePeriod() - { - $startDate = new DateTime('2001-01-01'); - $endDate = new DateTime('2002-02-02'); - - yield[ - null, - null, - ]; - - yield[ - $startDate, - $startDate, - null, - ]; - - yield[ - null, - null, - $endDate, - ]; - - yield[ - $startDate, - $endDate, - ]; - } - - /** - * Provides period and incorrect format of date to verify - * - * @return Generator - */ - public function provideDatePeriodAndIncorrectDateFormat() - { - $startDate = new DateTime('2001-01-01'); - $endDate = new DateTime('2002-02-02'); - - yield[ - new DatePeriod($startDate, $endDate), - '', - ]; - - yield[ - new DatePeriod($startDate, $endDate), - null, - ]; - - yield[ - new DatePeriod($startDate, $endDate), - false, - ]; - } - - /** - * Provides period and format of date to verify - * - * @return Generator - */ - public function provideDatePeriodAndDateFormat() - { - $startDate = new DateTime('2001-01-01'); - $endDate = new DateTime('2002-02-02'); - - /* - * For start date - */ - yield[ - new DatePeriod($startDate, $endDate), - 'Y', - true, - '2001', - ]; - - yield[ - new DatePeriod($startDate, $endDate), - 'D', - true, - 'Mon', - ]; - - yield[ - new DatePeriod($startDate, $endDate), - 'Y-m-d', - true, - '2001-01-01', - ]; - - yield[ - new DatePeriod($startDate, $endDate), - 'Y-m-d H:i', - true, - '2001-01-01 00:00', - ]; - - /* - * For end date - */ - yield[ - new DatePeriod($startDate, $endDate), - 'Y', - false, - '2002', - ]; - - yield[ - new DatePeriod($startDate, $endDate), - 'D', - false, - 'Sat', - ]; - - yield[ - new DatePeriod($startDate, $endDate), - 'Y-m-d', - false, - '2002-02-02', - ]; - - yield[ - new DatePeriod($startDate, $endDate), - 'Y-m-d H:i', - false, - '2002-02-02 00:00', - ]; - } - - /** - * {@inheritdoc} - */ - public function provideTypeToVerify() - { - yield[ - '', - false, - ]; - - yield[ - -1, - false, - ]; - - yield[ - true, - false, - ]; - - yield[ - DatePeriod::LAST_MONTH, - true, - ]; - - yield[ - DatePeriod::NEXT_WEEK, - true, - ]; - - yield[ - DatePeriod::THIS_YEAR, - true, - ]; - } - /** * Returns all expected types of the tested type * * @return array */ - protected function getAllExpectedTypes() + protected function getAllExpectedTypes(): array { return [ - 'LAST_MONTH' => DatePeriod::LAST_MONTH, - 'LAST_WEEK' => DatePeriod::LAST_WEEK, - 'LAST_YEAR' => DatePeriod::LAST_YEAR, - 'NEXT_MONTH' => DatePeriod::NEXT_MONTH, - 'NEXT_WEEK' => DatePeriod::NEXT_WEEK, - 'NEXT_YEAR' => DatePeriod::NEXT_YEAR, - 'THIS_MONTH' => DatePeriod::THIS_MONTH, - 'THIS_WEEK' => DatePeriod::THIS_WEEK, - 'THIS_YEAR' => DatePeriod::THIS_YEAR, + 'LAST_MONTH' => 4, + 'LAST_WEEK' => 1, + 'LAST_YEAR' => 7, + 'NEXT_MONTH' => 6, + 'NEXT_WEEK' => 3, + 'NEXT_YEAR' => 9, + 'THIS_MONTH' => 5, + 'THIS_WEEK' => 2, + 'THIS_YEAR' => 8, ]; } /** * {@inheritdoc} */ - protected function getTestedTypeInstance() + protected function getTestedTypeInstance(): BaseType { return new DatePeriod(); } diff --git a/tests/Type/OopVisibilityTypeTest.php b/tests/Type/OopVisibilityTypeTest.php new file mode 100644 index 0000000..cc929c3 --- /dev/null +++ b/tests/Type/OopVisibilityTypeTest.php @@ -0,0 +1,82 @@ + + * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Type\OopVisibilityType + */ +class OopVisibilityTypeTest extends BaseTypeTestCase +{ + /** + * {@inheritdoc} + */ + public function provideTypeToVerify(): Generator + { + yield [ + OopVisibilityType::isCorrectType(''), + false, + ]; + + yield [ + OopVisibilityType::isCorrectType(null), + false, + ]; + + yield [ + OopVisibilityType::isCorrectType('-1'), + false, + ]; + + yield [ + OopVisibilityType::isCorrectType('1'), + true, + ]; + + yield [ + OopVisibilityType::isCorrectType('2'), + true, + ]; + + yield [ + OopVisibilityType::isCorrectType('3'), + true, + ]; + } + + /** + * {@inheritdoc} + */ + protected function getAllExpectedTypes(): array + { + return [ + 'IS_PRIVATE' => 3, + 'IS_PROTECTED' => 2, + 'IS_PUBLIC' => 1, + ]; + } + + /** + *{@inheritdoc} + */ + protected function getTestedTypeInstance(): BaseType + { + return new OopVisibilityType(); + } +} diff --git a/tests/Utilities/Arrays/SimpleToString.php b/tests/Utilities/Arrays/SimpleToString.php index dd51827..190f821 100644 --- a/tests/Utilities/Arrays/SimpleToString.php +++ b/tests/Utilities/Arrays/SimpleToString.php @@ -14,6 +14,8 @@ namespace Meritoo\Test\Common\Utilities\Arrays; * * @author Meritoo * @copyright Meritoo + * + * @coversNothing */ class SimpleToString { diff --git a/tests/Utilities/ArraysTest.php b/tests/Utilities/ArraysTest.php index 168fac7..e119f82 100644 --- a/tests/Utilities/ArraysTest.php +++ b/tests/Utilities/ArraysTest.php @@ -8,8 +8,10 @@ namespace Meritoo\Test\Common\Utilities; +use Generator; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Utilities\Arrays; +use Meritoo\Common\Utilities\Locale; use Meritoo\Test\Common\Utilities\Arrays\SimpleToString; /** @@ -17,6 +19,9 @@ use Meritoo\Test\Common\Utilities\Arrays\SimpleToString; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\Arrays */ class ArraysTest extends BaseTestCase { @@ -26,241 +31,2320 @@ class ArraysTest extends BaseTestCase private $complexArray; private $superComplexArray; - public function testConstructor() + public function provideArrayToQuoteStrings() { - static::assertHasNoConstructor(Arrays::class); - } - - /** - * @param string $description Description of test - * @param string $expected Expected array converted to string - * @param array $array Data to be converted - * @param string $arrayColumnKey (optional) Column name. Default: "". - * @param string $separator (optional) Separator used between values. Default: ",". - * - * @dataProvider provideArrayValues2string - */ - public function testValues2string($description, $expected, array $array, $arrayColumnKey = '', $separator = ',') - { - self::assertSame($expected, Arrays::values2string($array, $arrayColumnKey, $separator), $description); - } - - /** - * @param string $description Description of test - * @param string $expected Expected array converted to string - * @param array $array Data to be converted - * @param string $separator (optional) Separator used between name-value pairs. Default: ",". - * @param string $valuesKeysSeparator (optional) Separator used between name and value. Default: "=". - * @param string $valuesWrapper (optional) Wrapper used to wrap values, e.g. double-quote: key="value". - * Default: "". - * - * @dataProvider provideArrayValuesKeysConverted2string - */ - public function testValuesKeys2string( - $description, - $expected, - array $array, - $separator = ',', - $valuesKeysSeparator = '=', - $valuesWrapper = '' - ) { - self::assertSame( - $expected, - Arrays::valuesKeys2string($array, $separator, $valuesKeysSeparator, $valuesWrapper), - $description - ); - - self::assertSame( - '0=Lorem,1=ipsum,2=dolor,3=sit,4=amet', - Arrays::valuesKeys2string($this->simpleArray), - 'Simple array' - ); - - self::assertSame( - '0=Lorem;1=ipsum;2=dolor;3=sit;4=amet', - Arrays::valuesKeys2string($this->simpleArray, ';'), - 'Simple array (with custom separator)' - ); - - self::assertSame( - '0=Lorem 1=ipsum 2=dolor 3=sit 4=amet', - Arrays::valuesKeys2string($this->simpleArray, ' '), - 'Simple array (with custom separator)' - ); - - self::assertSame( - '0="Lorem" 1="ipsum" 2="dolor" 3="sit" 4="amet"', - Arrays::valuesKeys2string($this->simpleArray, ' ', '=', '"'), - 'Simple array (with custom separators)' - ); - - self::assertSame( - '0="Lorem", 1="ipsum", 2="dolor", 3="sit", 4="amet"', - Arrays::valuesKeys2string($this->simpleArray, ', ', '=', '"'), - 'Simple array (with custom separators)' - ); - } - - /** - * @param string $description Description of test - * @param string $expected Expected array converted to csv string - * @param array $array Data to be converted. It have to be an array that represents database table. - * @param string $separator (optional) Separator used between values. Default: ",". - * - * @dataProvider provideArrayValues2csv - */ - public function testValues2csv($description, $expected, array $array, $separator = ',') - { - self::assertSame($expected, Arrays::values2csv($array, $separator), $description); - self::assertSame('', Arrays::values2csv($this->simpleArray), 'Simple array'); - - self::assertSame("lorem,ipsum,dolor,sit,amet\n" - . "consectetur,adipiscing,elit\n" - . 'donec,sagittis,fringilla,eleifend', - Arrays::values2csv($this->twoDimensionsArray), - 'Two dimensions array' - ); - } - - public function testGetFirstKey() - { - /* - * Negative cases - */ - self::assertNull(Arrays::getFirstKey([])); - - /* - * Positive cases - */ - self::assertEquals(0, Arrays::getFirstKey($this->simpleArray)); - self::assertEquals('lorem', Arrays::getFirstKey($this->complexArray)); - } - - public function testGetLastKey() - { - self::assertNull(Arrays::getLastKey([])); - self::assertEquals(4, Arrays::getLastKey($this->simpleArray)); - self::assertEquals('amet', Arrays::getLastKey($this->complexArray)); - } - - public function testGetFirstElement() - { - /* - * Negative cases - */ - self::assertNull(Arrays::getFirstElement([])); - - /* - * Positive cases - */ - self::assertEquals('Lorem', Arrays::getFirstElement($this->simpleArray)); - self::assertEquals('Lorem', Arrays::getFirstElement($this->simpleArray)); - self::assertEquals('lorem', Arrays::getFirstElement($this->twoDimensionsArray, false)); - self::assertEquals('sit', Arrays::getFirstElement($this->complexArray, false)); - } - - public function testIsFirstElement() - { - self::assertTrue(Arrays::isFirstElement($this->simpleArray, 'Lorem')); - self::assertFalse(Arrays::isFirstElement($this->simpleArray, 'dolor')); - self::assertFalse(Arrays::isFirstElement($this->simpleArray, ' ')); - self::assertFalse(Arrays::isFirstElement($this->simpleArray, null)); - } - - public function testGetLastElement() - { - /* - * Negative cases - */ - self::assertNull(Arrays::getLastElement([])); - - /* - * Positive cases - */ - self::assertEquals('amet', Arrays::getLastElement($this->simpleArray)); - self::assertEquals('eleifend', Arrays::getLastElement($this->twoDimensionsArray, false)); - self::assertEquals('primis', Arrays::getLastElement($this->complexArray, false)); - } - - public function testIsLastElement() - { - self::assertTrue(Arrays::isLastElement($this->simpleArray, 'amet')); - self::assertFalse(Arrays::isLastElement($this->simpleArray, 'ipsum')); - self::assertFalse(Arrays::isLastElement($this->simpleArray, '')); - self::assertFalse(Arrays::isLastElement($this->simpleArray, null)); - } - - public function testGetLastElementBreadCrumb() - { - self::assertNull(Arrays::getLastElementBreadCrumb([])); - self::assertEquals('4/amet', Arrays::getLastElementBreadCrumb($this->simpleArray)); - self::assertEquals('2/3/eleifend', Arrays::getLastElementBreadCrumb($this->twoDimensionsArray)); - self::assertEquals('amet/1/primis', Arrays::getLastElementBreadCrumb($this->complexArray)); - } - - public function testGetLastRow() - { - /* - * Negative cases - */ - self::assertNull(Arrays::getLastRow([])); - - /* - * Positive cases - */ - self::assertEquals([], Arrays::getLastRow($this->simpleArray)); - self::assertEquals([], Arrays::getLastRow($this->simpleArrayWithKeys)); - - self::assertEquals([ - 'donec', - 'sagittis', - 'fringilla', - 'eleifend', - ], Arrays::getLastRow($this->twoDimensionsArray)); - - self::assertEquals([ - 'iaculis', - 'primis', - ], Arrays::getLastRow($this->complexArray)); - } - - public function testReplaceArrayKeys() - { - $effect = [ - 'nullam' => 'donec', - 'x' => [ - 'vitae' => [ - 'x' => 'quis', - ], - ], - 'elit', + yield [ + 'An empty array', + null, + [], ]; - $dataArray = $this->complexArray['sit']; - self::assertEquals($effect, Arrays::replaceArrayKeys($dataArray, '|.*li.*|', 'x')); + yield [ + 'Simple array', + [ + 1, + 2, + 3, + '\'1\'', + '\'2\'', + ], + [ + 1, + 2, + 3, + '1', + '2', + ], + ]; - self::assertEquals([ - 'x' => 'sit', - 4 => 'amet', - ], Arrays::replaceArrayKeys($this->simpleArray, '|[0-3]+|', 'x')); + yield [ + 'Complex array', + [ + 123, + '\'456\'', + [ + 'x' => [ + 0, + '\'0\'', + 1 => '\'1\'', + 2 => 2, + ], + '\'y\'', + ], + 444 => '\'\'', + [ + [ + [ + '\'test\'', + ], + ], + ], + ], + [ + 123, + '456', + [ + 'x' => [ + 0, + '0', + 1 => '1', + 2 => 2, + ], + 'y', + ], + 444 => '', + [ + [ + [ + 'test', + ], + ], + ], + ], + ]; } - public function testMakeArray() + public function provideArrayToRemoveMarginalElement(): Generator { - self::assertSame($this->simpleArray, Arrays::makeArray($this->simpleArray)); - self::assertSame(['test'], Arrays::makeArray('test')); + yield [ + 'An empty array - remove last element', + [], + true, + null, + ]; + + yield [ + 'An empty array - remove first element', + [], + false, + null, + ]; + + yield [ + 'One-dimensional array - remove last element', + [ + 'Lorem', + 'ipsum', + 'dolor', + 'sit', + 'amet', + ], + true, + [ + 0 => 'Lorem', + 1 => 'ipsum', + 2 => 'dolor', + 3 => 'sit', + ], + ]; + + yield [ + 'One-dimensional array - remove first element', + [ + 'Lorem', + 'ipsum', + 'dolor', + 'sit', + 'amet', + ], + false, + [ + 1 => 'ipsum', + 2 => 'dolor', + 3 => 'sit', + 4 => 'amet', + ], + ]; + + yield [ + 'Multi-dimensional array - remove last element', + [ + 'lorem' => [ + 'ipsum' => [ + 'dolor' => 'sit', + 'diam' => [ + 'non' => 'egestas', + ], + ], + ], + 'consectetur' => 'adipiscing', + 'mollis' => 1234, + 2 => [], + 'sit' => [ + 'nullam' => 'donec', + 'aliquet' => [ + 'vitae' => [ + 'ligula' => 'quis', + ], + ], + 'elit', + ], + 'amet' => [ + 'iaculis', + 'primis', + ], + ], + true, + [ + 'lorem' => [ + 'ipsum' => [ + 'dolor' => 'sit', + 'diam' => [ + 'non' => 'egestas', + ], + ], + ], + 'consectetur' => 'adipiscing', + 'mollis' => 1234, + 2 => [], + 'sit' => [ + 'nullam' => 'donec', + 'aliquet' => [ + 'vitae' => [ + 'ligula' => 'quis', + ], + ], + 'elit', + ], + ], + ]; + + yield [ + 'Multi-dimensional array - remove first element', + [ + 'lorem' => [ + 'ipsum' => [ + 'dolor' => 'sit', + 'diam' => [ + 'non' => 'egestas', + ], + ], + ], + 'consectetur' => 'adipiscing', + 'mollis' => 1234, + 2 => [], + 'sit' => [ + 'nullam' => 'donec', + 'aliquet' => [ + 'vitae' => [ + 'ligula' => 'quis', + ], + ], + 'elit', + ], + 'amet' => [ + 'iaculis', + 'primis', + ], + ], + false, + [ + 'consectetur' => 'adipiscing', + 'mollis' => 1234, + 2 => [], + 'sit' => [ + 'nullam' => 'donec', + 'aliquet' => [ + 'vitae' => [ + 'ligula' => 'quis', + ], + ], + 'elit', + ], + 'amet' => [ + 'iaculis', + 'primis', + ], + ], + ]; } - public function testArray2JavaScript() + public function provideArrayToReplaceKeys(): Generator { + yield [ + 'An empty array', + [], + '', + '', + null, + ]; + + yield [ + '1st case', + [ + 'nullam' => 'donec', + 'aliquet' => [ + 'vitae' => [ + 'ligula' => 'quis', + ], + ], + 'elit', + ], + '|.*li.*|', + 'x', + [ + 'nullam' => 'donec', + 'x' => [ + 'vitae' => [ + 'x' => 'quis', + ], + ], + 'elit', + ], + ]; + + yield [ + '2nd case', + [ + 'Lorem', + 'ipsum', + 'dolor', + 'sit', + 'amet', + ], + '|[0-3]+|', + 'x', + [ + 'x' => 'sit', + 4 => 'amet', + ], + ]; + } + + public function provideArrayToVerifyIfContainsEmptyStringsOnly(): ?Generator + { + yield [ + [], + false, + ]; + + yield [ + [ + '', + 1, + ], + false, + ]; + + yield [ + [ + '', + null, + 1, + ], + false, + ]; + + yield [ + [ + '', + null, + ], + true, + ]; + + yield [ + [ + '', + null, + '', + ], + true, + ]; + } + + public function provideArrayValues2csv(): ?Generator + { + yield [ + 'An empty array', + null, + [], + ]; + + yield [ + 'Empty string, and empty array and null as row', + "1,2,3\n5,6,", + [ + 'test_1' => '', + 'test_2' => [], + 'test_3' => null, + 'test_4' => [ + 'aa' => 1, + 'bb' => 2, + 'cc' => 3, + ], + [ + 'dd' => 5, + 'ee' => 6, + 'ff' => '', + ], + ], + ]; + + yield [ + 'Empty string, and empty array and null as row (with custom separator)', + "1, 2, 3\n5, 6, ", + [ + 'test_1' => '', + 'test_2' => [], + 'test_3' => null, + 'test_4' => [ + 'aa' => 1, + 'bb' => 2, + 'cc' => 3, + ], + [ + 'dd' => 5, + 'ee' => 6, + 'ff' => '', + ], + ], + ', ', + ]; + + yield [ + 'Empty string as key, non-array as value', + "1,2,3\n5,6,", + [ + '' => 'test_1', + 1 => 'test_2', + '3' => [ + 'aa' => 1, + 'bb' => 2, + 'cc' => 3, + ], + [ + 'dd' => 5, + 'ee' => 6, + 'ff' => '', + ], + ], + ]; + + yield [ + 'Empty string as key, non-array as value (with custom separator)', + "1 | 2 | 3\n5 | 6 | ", + [ + '' => 'test_1', + 1 => 'test_2', + '3' => [ + 'aa' => 1, + 'bb' => 2, + 'cc' => 3, + ], + [ + 'dd' => 5, + 'ee' => 6, + 'ff' => '', + ], + ], + ' | ', + ]; + + yield [ + 'Invalid structure, not like database table', + "1,2,3\n5,6\n7,8,9,10", + [ + [ + 'aa' => 1, + 'bb' => 2, + 'cc' => 3, + ], + [ + 'dd' => 5, + 'ee' => 6, + ], + [ + 7, + 8, + 9, + 10, + ], + ], + ]; + + yield [ + 'Invalid structure, not like database table (with custom separator)', + "1 <-> 2 <-> 3\n5 <-> 6\n7 <-> 8 <-> 9 <-> 10", + [ + [ + 'aa' => 1, + 'bb' => 2, + 'cc' => 3, + ], + [ + 'dd' => 5, + 'ee' => 6, + ], + [ + 7, + 8, + 9, + 10, + ], + ], + ' <-> ', + ]; + + yield [ + 'Mixed types of keys and values', + "1,2,3.45\n5,6,\n7,8,9,,10", + [ + [ + 'aa' => 1, + 'bb' => 2, + 'cc' => 3.45, + ], + [ + 'dd' => 5, + 'ee' => 6, + null, + ], + [ + 7, + 8, + 'qq' => 9, + '', + 10, + ], + ], + ]; + + yield [ + 'Mixed types of keys and values (with custom separator)', + "1 // 2 // 3.45\n5 // 6 // \n7 // 8 // 9 // // 10", + [ + [ + 'aa' => 1, + 'bb' => 2, + 'cc' => 3.45, + ], + [ + 'dd' => 5, + 'ee' => 6, + null, + ], + [ + 7, + 8, + 'qq' => 9, + '', + 10, + ], + ], + ' // ', + ]; + + yield [ + 'With HTML code', + "
abc
,def,
ghi
\nc,d", + [ + [ + '<div>abc</div>', + 'def', + '<div>ghi</div>', + ], + [ + 'c', + 'd', + ], + ], + ]; + } + + public function provideArrayValues2string() + { + yield [ + 'An empty array', + null, + [], + ]; + + yield [ + 'Simple array', + 'Test 1,Test 2,Test 3', + [ + 1 => 'Test 1', + 2 => 'Test 2', + 3 => 'Test 3', + ], + ]; + + yield [ + 'Simple array (with custom separator)', + 'Test 1.Test 2.Test 3', + [ + 1 => 'Test 1', + 2 => 'Test 2', + 3 => 'Test 3', + ], + '', + '.', + ]; + + yield [ + 'Simple array (concrete column)', + 'Test 2', + [ + 1 => 'Test 1', + 2 => 'Test 2', + 3 => 'Test 3', + ], + 2, + ]; + + yield [ + 'Simple array (concrete column with custom separator)', + 'Test 2', + [ + 1 => 'Test 1', + 2 => 'Test 2', + 3 => 'Test 3', + ], + 2, + '.', + ]; + + yield [ + 'Complex array', + '1,2,3,test 1,test 2,test 3,,test 4,,bbb,3.45', + [ + [ + 1, + 2, + 3, + ], + [ + 'test 1', + 'test 2', + [ + 'test 3', + '', + 'test 4', + ], + ], + [], + [ + 'a' => '', + 'b' => 'bbb', + [], + 'c' => 3.45, + ], + ], + ]; + + yield [ + '1st complex array (concrete column)', + '2,test 2,', + [ + [ + 1, + 2, + 3, + ], + [ + 'test 1', + 'test 2', + [ + 'test 3', + '', + 'test 4', + ], + ], + [], + [ + 'a' => '', + 'b' => 'bbb', + [], + 'c' => 3.45, + ], + ], + 1, + ]; + + yield [ + '2nd complex array (concrete column)', + 'bb,1234,0xb', + [ + [ + 1, + 2, + 3, + ], + [ + 'a' => 'aa', + 'b' => 'bb', + 'c' => 'cc', + ], + [ + 'a', + 'b', + 'c', + ], + [ + 'a' => '', + 'b' => 1234, + ], + [ + 'c' => 5678, + 'b' => '0xb', + ], + ], + 'b', + ]; + + yield [ + '3rd complex array (concrete column with custom separator)', + 'bb - 1234 - 3xb - bbb', + [ + [ + 1, + 2, + 3, + ], + [ + 'a' => 'aa', + 'b' => 'bb', + 'c' => 'cc', + ], + [ + 'a', + 'b' => [], + 'c', + ], + [ + 'a' => '', + 'b' => 1234, + ], + [ + 'c' => 5678, + 'b' => [ + 'b1' => '0xb', + 'b2' => '1xb', + 'b' => '3xb', + ], + [ + 1, + 2, + 'a' => 'aaa', + 'b' => 'bbb', + ], + ], + ], + 'b', + ' - ', + ]; + } + + public function provideArrayValuesKeysConverted2string() + { + yield [ + 'An empty array', + null, + [], + ]; + + yield [ + 'Empty string and null as value', + 'test_1=,test_2=,test_3=3', + [ + 'test_1' => null, + 'test_2' => '', + 'test_3' => '3', + ], + ]; + + yield [ + 'Empty string and null as value (with custom separators)', + 'test_1="" test_2="" test_3="3"', + [ + 'test_1' => null, + 'test_2' => '', + 'test_3' => '3', + ], + ' ', + '=', + '"', + ]; + + yield [ + 'Empty string as key', + '1=test_1,=test_2,3=test_3', + [ + 1 => 'test_1', + '' => 'test_2', + '3' => 'test_3', + ], + ]; + + yield [ + 'Empty string as key (with custom separators)', + '1 => "test_1"; => "test_2"; 3 => "test_3"', + [ + 1 => 'test_1', + '' => 'test_2', + '3' => 'test_3', + ], + '; ', + ' => ', + '"', + ]; + + yield [ + 'Mixed types of keys and values', + 'test_1=test test,test_2=2,test_3=3.45', + [ + 'test_1' => 'test test', + 'test_2' => 2, + 'test_3' => 3.45, + ], + ]; + + yield [ + 'Mixed types of keys and values (with custom separators)', + 'test_1 --> *test test* | test_2 --> *2* | test_3 --> *3.45*', + [ + 'test_1' => 'test test', + 'test_2' => 2, + 'test_3' => 3.45, + ], + ' | ', + ' --> ', + '*', + ]; + } + + /** + * Provides an array with duplicated values to set/replace values with keys + * + * @return Generator + */ + public function provideArrayWithDuplicatedValuesToSetKeysAsValues() + { + yield [ + [ + 'lorem' => 'ipsum', + 'dolor' => 'ipsum', + 'sit' => 'amet', + 'diam' => 'non', + 'elit' => 'non', + 'in' => 'non', + ], + [ + 'ipsum' => [ + 'lorem', + 'dolor', + ], + 'amet' => 'sit', + 'non' => [ + 'diam', + 'elit', + 'in', + ], + ], + ]; + + yield [ + [ + 'lorem' => [ + 'diam' => 'non', + 'elit' => 'non', + 'in' => 'non', + ], + 'dolor1' => 'ipsum', + 'dolor2' => 'ipsum', + 'sit' => 'amet', + ], + [ + 'lorem' => [ + 'non' => [ + 'diam', + 'elit', + 'in', + ], + ], + 'ipsum' => [ + 'dolor1', + 'dolor2', + ], + 'amet' => 'sit', + ], + ]; + } + + public function provideFirstElement(): ?Generator + { + yield [ + 'An empty array (first level only)', + null, + [], + ]; + + yield [ + 'An empty array', + null, + [], + false, + ]; + + yield [ + 'Multidimensional array (first level only)', + [ + [ + 'abc', + 2, + 'def', + ], + 4, + ], + [ + [ + [ + 'abc', + 2, + 'def', + ], + 4, + ], + '---', + [ + 'ghi', + 7, + [ + 'jkl', + '...', + 10, + ], + ], + ], + ]; + + yield [ + 'Multidimensional array', + 'abc', + [ + [ + [ + 'abc', + 2, + 'def', + ], + 4, + ], + '---', + [ + 'ghi', + 7, + [ + 'jkl', + '...', + 10, + ], + ], + ], + false, + ]; + } + + public function provideIsFirstElement(): ?Generator + { + yield [ + 'An empty array (first level only)', + false, + [], + '', + ]; + + yield [ + 'An empty array', + false, + [], + '', + false, + ]; + + yield [ + 'Non-existing integer in array with integers (first level only)', + false, + [ + 1, + 2, + 3, + ], + 4, + ]; + + yield [ + 'Existing integer in array with integers (first level only)', + true, + [ + 1, + 2, + 3, + ], + 1, + ]; + + yield [ + 'Existing integer in array with integers', + true, + [ + 1, + 2, + 3, + ], + 1, + false, + ]; + + yield [ + 'Non-existing integer in multidimensional array with integers (first level only)', + false, + [ + [ + [ + 1, + 2, + 3, + ], + 4, + ], + 5, + [ + 6, + 7, + [ + 8, + 9, + 10, + ], + ], + ], + 9, + ]; + + yield [ + 'Non-existing integer in multidimensional array with integers', + false, + [ + [ + [ + 1, + 2, + 3, + ], + 4, + ], + 5, + [ + 6, + 7, + [ + 8, + 9, + 10, + ], + ], + ], + 9, + false, + ]; + + yield [ + 'Existing integer in multidimensional array with integers, but first level only checked', + false, + [ + [ + [ + 1, + 2, + 3, + ], + 4, + ], + 5, + [ + 6, + 7, + [ + 8, + 9, + 10, + ], + ], + ], + 1, + ]; + + yield [ + 'Existing integer in multidimensional array with integers', + true, + [ + [ + [ + 1, + 2, + 3, + ], + 4, + ], + 5, + [ + 6, + 7, + [ + 8, + 9, + 10, + ], + ], + ], + 1, + false, + ]; + + yield [ + 'Non-existing element in multidimensional array (first level only)', + false, + [ + [ + [ + 'abc', + 2, + 'def', + ], + 4, + ], + '---', + [ + 'ghi', + 7, + [ + 'jkl', + '...', + 10, + ], + ], + ], + 9, + ]; + + yield [ + 'Existing element in multidimensional array, but first level only checked', + false, + [ + [ + [ + 'abc', + 2, + 'def', + ], + 4, + ], + '---', + [ + 'ghi', + 7, + [ + 'jkl', + '...', + 10, + ], + ], + ], + 'abc', + ]; + + yield [ + 'Existing element in multidimensional array', + true, + [ + [ + [ + 'abc', + 2, + 'def', + ], + 4, + ], + '---', + [ + 'ghi', + 7, + [ + 'jkl', + '...', + 10, + ], + ], + ], + 'abc', + false, + ]; + } + + public function provideIsLastElement(): ?Generator + { + yield [ + 'An empty array (first level only)', + false, + [], + '', + ]; + + yield [ + 'An empty array', + false, + [], + '', + false, + ]; + + yield [ + 'Non-existing integer in array with integers (first level only)', + false, + [ + 1, + 2, + 3, + ], + 4, + ]; + + yield [ + 'Existing integer in array with integers (first level only)', + true, + [ + 1, + 2, + 3, + ], + 3, + ]; + + yield [ + 'Existing integer in array with integers', + true, + [ + 1, + 2, + 3, + ], + 3, + false, + ]; + + yield [ + 'Non-existing integer in multidimensional array with integers (first level only)', + false, + [ + [ + [ + 1, + 2, + 3, + ], + 4, + ], + 5, + [ + 6, + 7, + [ + 8, + 9, + 10, + ], + ], + ], + 11, + ]; + + yield [ + 'Non-existing integer in multidimensional array with integers', + false, + [ + [ + [ + 1, + 2, + 3, + ], + 4, + ], + 5, + [ + 6, + 7, + [ + 8, + 9, + 10, + ], + ], + ], + 11, + false, + ]; + + yield [ + 'Existing integer in multidimensional array with integers, but first level only checked', + false, + [ + [ + [ + 1, + 2, + 3, + ], + 4, + ], + 5, + [ + 6, + 7, + [ + 8, + 9, + 10, + ], + ], + ], + 10, + ]; + + yield [ + 'Existing integer in multidimensional array with integers', + true, + [ + [ + [ + 1, + 2, + 3, + ], + 4, + ], + 5, + [ + 6, + 7, + [ + 8, + 9, + 10, + ], + ], + ], + 10, + false, + ]; + + yield [ + 'Non-existing element in multidimensional array (first level only)', + false, + [ + [ + [ + 'abc', + 2, + 'def', + ], + 4, + ], + '---', + [ + 'ghi', + 7, + [ + 'jkl', + '...', + 10, + ], + ], + ], + 9, + ]; + + yield [ + 'Existing element in multidimensional array, but first level only checked', + false, + [ + [ + [ + 'abc', + 2, + 'def', + ], + 4, + ], + '---', + [ + 'ghi', + 7, + [ + 10, + '...', + 'jkl', + ], + ], + ], + 'jkl', + ]; + + yield [ + 'Existing element in multidimensional array', + true, + [ + [ + [ + 'abc', + 2, + 'def', + ], + 4, + ], + '---', + [ + 'ghi', + 7, + [ + 10, + '...', + 'jkl', + ], + ], + ], + 'jkl', + false, + ]; + } + + public function provideLastElement(): ?Generator + { + yield [ + 'An empty array (first level only)', + null, + [], + ]; + + yield [ + 'An empty array', + null, + [], + false, + ]; + + yield [ + 'One-dimensional array (first level only)', + 3, + [ + 1, + 2, + 3, + ], + ]; + + yield [ + 'One-dimensional array (first level only)', + 3, + [ + 1, + 2, + 3, + ], + false, + ]; + + yield [ + 'Multidimensional array (first level only)', + [ + 'ghi', + 7, + [ + 'jkl', + '...', + 10, + ], + ], + [ + [ + [ + 'abc', + 2, + 'def', + ], + 4, + ], + '---', + [ + 'ghi', + 7, + [ + 'jkl', + '...', + 10, + ], + ], + ], + ]; + + yield [ + 'Multidimensional array', + 10, + [ + [ + [ + 'abc', + 2, + 'def', + ], + 4, + ], + '---', + [ + 'ghi', + 7, + [ + 'jkl', + '...', + 10, + ], + ], + ], + false, + ]; + } + + public function provideLastRow(): ?Generator + { + yield [ + 'An empty array', + null, + [], + ]; + + yield [ + 'One-dimensional array', + [], + [ + 'a', + 'b', + 1, + 2, + ], + ]; + + yield [ + 'Multidimensional array with scalar as last element', + [], + [ + 'a', + [ + 'b', + 'c', + ], + [ + 'e', + 'f', + ], + 1, + 2, + ], + ]; + + yield [ + 'Multidimensional array with an empty array as last element', + [], + [ + 'a', + [ + 'b', + 'c', + ], + 1, + 2, + [], + ], + ]; + + yield [ + 'Multidimensional array', + [ + 'e', + 'f', + ], + [ + 'a', + [ + 'b', + 'c', + ], + 1, + 2, + [ + 'e', + 'f', + ], + ], + ]; + } + + /** + * Provides simple array to set/replace values with keys + * + * @return Generator + */ + public function provideSimpleArrayToSetKeysAsValues() + { + yield [ + [ + 1, + 2, + 3, + 4, + ], + [ + 1 => 0, + 2 => 1, + 3 => 2, + 4 => 3, + ], + ]; + + yield [ + [ + 'Lorem', + 'ipsum', + 'dolor', + 'sit', + 'amet', + ], + [ + 'Lorem' => 0, + 'ipsum' => 1, + 'dolor' => 2, + 'sit' => 3, + 'amet' => 4, + ], + ]; + } + + /** + * Provides patterns of keys or paths that matched will stop the process and the expected array for the + * getLastElementsPaths() method + * + * @return Generator + */ + public function provideStopIfMatchedByForGetLastElementsPaths(): ?Generator + { + // Special exception: do not use, stop recursive on the "diam" key + yield [ + ['diam'], + '.', + [ + 'ipsum.quis.vestibulum.porta-1.0' => 'turpis', + 'ipsum.quis.vestibulum.porta-1.1' => 'urna', + 'ipsum.quis.vestibulum.porta-2.tortor.in.0' => 'dui', + 'ipsum.quis.vestibulum.porta-2.tortor.in.dolor.0' => 'aliquam', + 'ipsum.quis.vestibulum.porta-3.0' => 1, + 'ipsum.quis.vestibulum.porta-3.1' => 2, + 'ipsum.quis.vestibulum.porta-3.2' => 3, + 'primis.0.0' => 'in', + 'primis.0.1' => 'faucibus', + 'primis.0.2' => 'orci', + 'primis.1.0' => 'luctus', + 'primis.1.1' => 'et', + 'primis.1.2' => 'ultrices', + ], + ]; + /* - * Negative cases + * Stop building of paths on these keys: + * - "tortor" + * - "primis" */ + yield [ + [ + 'tortor', + 'primis', + ], + ' . ', + [ + 'ipsum . quis . vestibulum . porta-1 . 0' => 'turpis', + 'ipsum . quis . vestibulum . porta-1 . 1' => 'urna', + 'ipsum . quis . vestibulum . porta-2 . tortor' => [ + 'in' => [ + 'dui', + 'dolor' => [ + 'aliquam', + ], + ], + ], + 'ipsum . quis . vestibulum . porta-3 . 0' => 1, + 'ipsum . quis . vestibulum . porta-3 . 1' => 2, + 'ipsum . quis . vestibulum . porta-3 . 2' => 3, + 'primis' => [ + [ + 'in', + 'faucibus', + 'orci', + ], + [ + 'luctus', + 'et', + 'ultrices', + ], + ], + ], + ]; + + // Stop building of paths on more sophisticated keys + yield [ + [ + 'porta\-\d+', + '^\d+$', + ], + ' > ', + [ + 'ipsum > quis > vestibulum > porta-1' => [ + 'turpis', + 'urna', + ], + 'ipsum > quis > vestibulum > porta-2' => [ + 'tortor' => [ + 'in' => [ + 'dui', + 'dolor' => [ + 'aliquam', + ], + ], + ], + ], + 'ipsum > quis > vestibulum > porta-3' => [ + 1, + 2, + 3, + ], + 'primis > 0' => [ + 'in', + 'faucibus', + 'orci', + ], + 'primis > 1' => [ + 'luctus', + 'et', + 'ultrices', + ], + ], + ]; + + /* + * Stop building of paths on these: + * - keys + * and + * - paths (verify paths too) + */ + yield [ + [ + 'porta-1', + 'porta-2 > tortor > in', + ], + ' > ', + [ + 'ipsum > quis > vestibulum > porta-1' => [ + 'turpis', + 'urna', + ], + 'ipsum > quis > vestibulum > porta-2 > tortor > in' => [ + 'dui', + 'dolor' => [ + 'aliquam', + ], + ], + 'ipsum > quis > vestibulum > porta-3 > 0' => 1, + 'ipsum > quis > vestibulum > porta-3 > 1' => 2, + 'ipsum > quis > vestibulum > porta-3 > 2' => 3, + 'primis > 0 > 0' => 'in', + 'primis > 0 > 1' => 'faucibus', + 'primis > 0 > 2' => 'orci', + 'primis > 1 > 0' => 'luctus', + 'primis > 1 > 1' => 'et', + 'primis > 1 > 2' => 'ultrices', + ], + ]; + + // Stop building of paths on these paths (verify paths only) + yield [ + [ + 'ipsum > quis > vestibulum > porta-1', + 'ipsum > quis > vestibulum > porta-2 > tortor', + 'primis > 1', + ], + ' > ', + [ + 'ipsum > quis > vestibulum > porta-1' => [ + 'turpis', + 'urna', + ], + 'ipsum > quis > vestibulum > porta-2 > tortor' => [ + 'in' => [ + 'dui', + 'dolor' => [ + 'aliquam', + ], + ], + ], + 'ipsum > quis > vestibulum > porta-3 > 0' => 1, + 'ipsum > quis > vestibulum > porta-3 > 1' => 2, + 'ipsum > quis > vestibulum > porta-3 > 2' => 3, + 'primis > 0 > 0' => 'in', + 'primis > 0 > 1' => 'faucibus', + 'primis > 0 > 2' => 'orci', + 'primis > 1' => [ + 'luctus', + 'et', + 'ultrices', + ], + ], + ]; + + // Stop building of paths if path contains any of these part (verify part of paths only) + yield [ + [ + 'vestibulum > porta-1', + 'tortor > in', + '[a-z]+ > \d+', + ], + ' > ', + [ + 'ipsum > quis > vestibulum > porta-1' => [ + 'turpis', + 'urna', + ], + 'ipsum > quis > vestibulum > porta-2 > tortor > in' => [ + 'dui', + 'dolor' => [ + 'aliquam', + ], + ], + 'ipsum > quis > vestibulum > porta-3 > 0' => 1, + 'ipsum > quis > vestibulum > porta-3 > 1' => 2, + 'ipsum > quis > vestibulum > porta-3 > 2' => 3, + 'primis > 0' => [ + 'in', + 'faucibus', + 'orci', + ], + 'primis > 1' => [ + 'luctus', + 'et', + 'ultrices', + ], + ], + ]; + } + + public function provideValueToIsEmptyArray(): ?Generator + { + yield [ + 'An empty string', + '', + false, + ]; + + yield [ + 'Non-empty string', + 'test', + false, + ]; + + yield [ + 'Null', + null, + false, + ]; + + yield [ + 'An integer equals 0', + 1234, + false, + ]; + + yield [ + 'An integer greater than 0', + 1234, + false, + ]; + + yield [ + 'An empty array', + [], + true, + ]; + + yield [ + 'Non-empty array', + [ + 'test', + ], + false, + ]; + } + + public function provideValueToIsNotEmptyArray(): ?Generator + { + yield [ + 'An empty string', + '', + false, + ]; + + yield [ + 'Non-empty string', + 'test', + false, + ]; + + yield [ + 'Null', + null, + false, + ]; + + yield [ + 'An integer equals 0', + 1234, + false, + ]; + + yield [ + 'An integer greater than 0', + 1234, + false, + ]; + + yield [ + 'An empty array', + [], + false, + ]; + + yield [ + 'Non-empty array', + [ + 'test', + ], + true, + ]; + } + + /** + * Provide values to filter and get non-empty values + * + * @return Generator + */ + public function provideValuesToFilterNonEmpty(): ?Generator + { + $simpleObject = new SimpleToString('1234'); + + yield [ + 'All values are empty', + [ + '', + null, + [], + ], + [], + ]; + + yield [ + '5 values with 2 empty strings', + [ + 'test 1', + '', + 'test 2', + 'test 3', + '', + ], + [ + 0 => 'test 1', + 2 => 'test 2', + 3 => 'test 3', + ], + ]; + + yield [ + '"0" shouldn\'t be treated like an empty value', + [ + 123, + 0, + 456, + ], + [ + 123, + 0, + 456, + ], + ]; + + yield [ + 'Object shouldn\'t be treated like an empty value', + [ + 'test 1', + $simpleObject, + 'test 2', + null, + 'test 3', + ], + [ + 0 => 'test 1', + 1 => $simpleObject, + 2 => 'test 2', + 4 => 'test 3', + ], + ]; + + yield [ + 'Mixed values (non-empty, empty, strings, integers, objects)', + [ + 'test 1', + '', + 123, + null, + 'test 2', + 'test 3', + 0, + $simpleObject, + 456, + [], + $simpleObject, + ], + [ + 0 => 'test 1', + 2 => 123, + 4 => 'test 2', + 5 => 'test 3', + 6 => 0, + 7 => $simpleObject, + 8 => 456, + 10 => $simpleObject, + ], + ]; + } + + /** + * Provide values to filter and get non-empty values concatenated by given separator + * + * @return Generator + */ + public function provideValuesToFilterNonEmptyAsString() + { + yield [ + 'An empty array (no values to filter)', + [], + ' | ', + null, + ]; + + yield [ + 'All values are empty', + [ + '', + null, + [], + ], + ' | ', + '', + ]; + + yield [ + '5 values with 2 empty strings', + [ + 'test 1', + '', + 'test 2', + 'test 3', + '', + ], + ' | ', + 'test 1 | test 2 | test 3', + ]; + + yield [ + 'Numbers with "0" that shouldn\'t be treated like an empty value', + [ + 123, + 0, + 456, + ], + ' <-> ', + '123 <-> 0 <-> 456', + ]; + + yield [ + 'Object shouldn\'t be treated like an empty value', + [ + 'test 1', + new SimpleToString('1234'), + 'test 2', + null, + 'test 3', + ], + ' | ', + 'test 1 | Instance with ID: 1234 | test 2 | test 3', + ]; + + yield [ + 'Mixed values (non-empty, empty, strings, integers, objects)', + [ + 'test 1', + '', + 123, + null, + 'test 2', + 'test 3', + 0, + new SimpleToString('A1XC90Z'), + 456, + [], + new SimpleToString('FF-45-0Z'), + ], + ';', + 'test 1;123;test 2;test 3;0;Instance with ID: A1XC90Z;456;Instance with ID: FF-45-0Z', + ]; + } + + /** + * Provide values to filter and get non-empty values concatenated by default separator + * + * @return Generator + */ + public function provideValuesToFilterNonEmptyAsStringUsingDefaultSeparator() + { + yield [ + 'An empty array (no values to filter)', + [], + null, + ]; + + yield [ + 'All values are empty', + [ + '', + null, + [], + ], + '', + ]; + + yield [ + '5 values with 2 empty strings', + [ + 'test 1', + '', + 'test 2', + 'test 3', + '', + ], + 'test 1, test 2, test 3', + ]; + + yield [ + 'Numbers with "0" that shouldn\'t be treated like an empty value', + [ + 123, + 0, + 456, + ], + '123, 0, 456', + ]; + + yield [ + 'Object shouldn\'t be treated like an empty value', + [ + 'test 1', + new SimpleToString('1234'), + 'test 2', + null, + 'test 3', + ], + 'test 1, Instance with ID: 1234, test 2, test 3', + ]; + + yield [ + 'Mixed values (non-empty, empty, strings, integers, objects)', + [ + 'test 1', + '', + 123, + null, + 'test 2', + 'test 3', + 0, + new SimpleToString('A1XC90Z'), + 456, + [], + new SimpleToString('FF-45-0Z'), + ], + 'test 1, 123, test 2, test 3, 0, Instance with ID: A1XC90Z, 456, Instance with ID: FF-45-0Z', + ]; + } + + public function testAreAllKeysIntegers() + { + self::assertFalse(Arrays::areAllKeysIntegers([])); + self::assertEquals(1, Arrays::areAllKeysIntegers($this->simpleArray)); + self::assertEquals(2, Arrays::areAllKeysIntegers($this->simpleArray)); + self::assertEquals('', Arrays::areAllKeysIntegers($this->complexArray)); + } + + public function testAreAllKeysMatchedByPattern(): void + { + $pattern = '\d+'; + + // Empty array + self::assertFalse(Arrays::areAllKeysMatchedByPattern([], $pattern)); + + // Simple array with integers as keys only + self::assertTrue(Arrays::areAllKeysMatchedByPattern($this->simpleArray, $pattern)); + + // Complex array with strings and integers as keys + self::assertFalse(Arrays::areAllKeysMatchedByPattern($this->complexArray, $pattern)); + + $array = [ + 'a' => 'b', + 'c' => 'd', + ]; + + // Yet another simple array, but with strings as keys + self::assertFalse(Arrays::areAllKeysMatchedByPattern($array, $pattern)); + + // The same array with another pattern + $pattern = '\w+'; + self::assertTrue(Arrays::areAllKeysMatchedByPattern($array, $pattern)); + + // The same array with mixed keys (strings and integers as keys) + $array[1] = 'x'; + $pattern = '\d+'; + self::assertFalse(Arrays::areAllKeysMatchedByPattern($array, $pattern)); + + // Multidimensional array - negative case + $array['e'] = ['f' => 'g']; + self::assertFalse(Arrays::areAllKeysMatchedByPattern($array, $pattern)); + + // Multidimensional array - positive case + unset($array[1]); + $pattern = '\w+'; + self::assertTrue(Arrays::areAllKeysMatchedByPattern($array, $pattern)); + } + + public function testAreAllValuesEmpty() + { + // Negative cases + self::assertFalse(Arrays::areAllValuesEmpty([])); + self::assertFalse(Arrays::areAllValuesEmpty([], true)); + self::assertFalse(Arrays::areAllValuesEmpty($this->simpleArray)); + self::assertFalse(Arrays::areAllValuesEmpty($this->simpleArray, true)); + + $array = [ + null, + 0, + ]; + self::assertFalse(Arrays::areAllValuesEmpty($array, true)); + + $array = [ + null, + [ + null, + ], + ]; + self::assertFalse(Arrays::areAllValuesEmpty($array, true)); + + // Positive cases + $array = [ + '', + 0, + ]; + self::assertTrue(Arrays::areAllValuesEmpty($array)); + + $array = [ + null, + null, + ]; + self::assertTrue(Arrays::areAllValuesEmpty($array, true)); + } + + public function testAreKeysInArray(): void + { + // Negative cases + self::assertFalse(Arrays::areKeysInArray([], [])); + self::assertFalse(Arrays::areKeysInArray([null], $this->simpleArray)); + self::assertFalse(Arrays::areKeysInArray([''], $this->simpleArray)); + self::assertFalse(Arrays::areKeysInArray(['dolorrr'], $this->simpleArrayWithKeys)); + + $keys1 = [ + 1, + 3, + 9, + ]; + + self::assertFalse(Arrays::areKeysInArray($keys1, $this->simpleArray)); + self::assertFalse(Arrays::areKeysInArray($keys1, $this->complexArray)); + self::assertFalse(Arrays::areKeysInArray($keys1, $this->complexArray, false)); + + // Positive cases + $keys12 = [ + 2, + 'mollis', + ]; + + $keys13 = [ + 1, + 3, + ]; + + $keys14 = [ + 'dolor', + 'amet', + ]; + + $keys15 = [ + 'dolor', + 'amet', + ]; + + $keys16 = [ + 'a' => 'lorem', + 11 => 'amet', + ]; + + $keys17 = [ + 'a' => 'lorem', + 11 => 'amet', + 'c' => 'sit__', + ]; + + self::assertTrue(Arrays::areKeysInArray([1], $this->simpleArray)); + self::assertTrue(Arrays::areKeysInArray($keys12, $this->simpleArray, false)); + self::assertTrue(Arrays::areKeysInArray($keys12, $this->complexArray)); + self::assertTrue(Arrays::areKeysInArray($keys13, $this->simpleArray)); + self::assertTrue(Arrays::areKeysInArray($keys14, $this->simpleArrayWithKeys)); + self::assertTrue(Arrays::areKeysInArray($keys15, $this->simpleArrayWithKeys)); + + self::assertTrue(Arrays::areKeysInArray(['a' => 'dolor'], $this->simpleArrayWithKeys)); + self::assertTrue(Arrays::areKeysInArray(['a' => 'dolor'], $this->simpleArrayWithKeys, false)); + + self::assertTrue(Arrays::areKeysInArray($keys16, $this->complexArray)); + self::assertTrue(Arrays::areKeysInArray($keys17, $this->complexArray, false)); + } + + public function testArray2JavaScript(): void + { + // Negative cases self::assertNull(Arrays::array2JavaScript([])); - /* - * Positive cases - */ + // Positive cases self::assertEquals('new Array(\'Lorem\', \'ipsum\', \'dolor\', \'sit\', \'amet\');', Arrays::array2JavaScript($this->simpleArray)); self::assertEquals('var letsTest = new Array(\'Lorem\', \'ipsum\', \'dolor\', \'sit\', \'amet\');', Arrays::array2JavaScript($this->simpleArray, 'letsTest')); @@ -326,1038 +2410,29 @@ letsTest[2] = value_2;'; self::assertEquals($effect, Arrays::array2JavaScript($this->twoDimensionsArray, 'letsTest', true)); } - /** - * @param string $description Description of test case - * @param array|null $expected Expected new array (with quoted elements) - * @param array $array The array to check for string values - * - * @dataProvider provideArrayToQuoteStrings - */ - public function testQuoteStrings($description, $expected, array $array) + public function testConstructor() { - self::assertSame($expected, Arrays::quoteStrings($array), $description); - } - - public function testRemoveMarginalElement() - { - $array = $this->simpleArray; - $string = 'Lorem ipsum'; - - /* - * Removing first element - */ - self::assertSame([ - 0 => 'Lorem', - 1 => 'ipsum', - 2 => 'dolor', - 3 => 'sit', - ], Arrays::removeMarginalElement($array)); - self::assertEquals('Lorem ipsu', Arrays::removeMarginalElement($string)); - - /* - * Removing last element - */ - self::assertSame([ - 1 => 'ipsum', - 2 => 'dolor', - 3 => 'sit', - 4 => 'amet', - ], Arrays::removeMarginalElement($array, false)); - self::assertEquals('orem ipsum', Arrays::removeMarginalElement($string, false)); - } - - public function testRemoveElements() - { - $array1 = $this->simpleArray; - $array2 = $this->simpleArray; - - Arrays::removeElements($array1, 'ipsum'); - self::assertSame([ - 1 => 'ipsum', - 2 => 'dolor', - 3 => 'sit', - 4 => 'amet', - ], $array1); - - Arrays::removeElements($array2, 'sit', false); - self::assertSame([ - 0 => 'Lorem', - 1 => 'ipsum', - 2 => 'dolor', - 3 => 'sit', - ], $array2); - - Arrays::removeElements($this->complexArray['lorem'], 'sit', false); - self::assertSame(['ipsum' => ['dolor' => 'sit']], $this->complexArray['lorem']); - } - - public function testRemoveElement() - { - self::assertFalse(Arrays::removeElement($this->simpleArray, 'eeee')); - self::assertTrue(is_array(Arrays::removeElement($this->simpleArray, 'Lorem'))); - - Arrays::removeElement($this->simpleArray, 'amet'); - self::assertFalse(isset($this->simpleArray['amet'])); - } - - public function testSetKeysAsValuesEmptyArray() - { - self::assertNull(Arrays::setKeysAsValues([])); - } - - public function testSetKeysAsValuesSameKeysValues() - { - $array = [ - 0, - 1, - 2, - 3, - ]; - - self::assertEquals($array, Arrays::setKeysAsValues($array)); + static::assertHasNoConstructor(Arrays::class); } /** - * @param array $array The array to change values with keys - * @param array $replaced The array with replaced values with keys + * @param array $array + * @param bool $expected * - * @dataProvider provideSimpleArrayToSetKeysAsValues + * @dataProvider provideArrayToVerifyIfContainsEmptyStringsOnly */ - public function testSetKeysAsValuesSimpleArray($array, $replaced) + public function testContainsEmptyStringsOnly(array $array, bool $expected): void { - self::assertEquals($replaced, Arrays::setKeysAsValues($array)); - } - - public function testSetKeysAsValuesTwoDimensionsArray() - { - $replaced = [ - [ - 'lorem' => 0, - 'ipsum' => 1, - 'dolor' => 2, - 'sit' => 3, - 'amet' => 4, - ], - [ - 'consectetur' => 0, - 'adipiscing' => 1, - 'elit' => 2, - ], - [ - 'donec' => 0, - 'sagittis' => 1, - 'fringilla' => 2, - 'eleifend' => 3, - ], - ]; - - self::assertEquals($replaced, Arrays::setKeysAsValues($this->twoDimensionsArray)); - } - - /** - * @param array $array The array to change values with keys - * @param array $replaced The array with replaced values with keys - * - * @dataProvider provideArrayWithDuplicatedValuesToSetKeysAsValues - */ - public function testSetKeysAsValuesDuplicatedValues($array, $replaced) - { - self::assertEquals($replaced, Arrays::setKeysAsValues($array, false)); - } - - public function testGetNonArrayElementsCount() - { - /* - * Negative cases - */ - self::assertNull(Arrays::getNonArrayElementsCount([])); - - /* - * Positive cases - */ - self::assertEquals(5, Arrays::getNonArrayElementsCount($this->simpleArray)); - self::assertEquals(3, Arrays::getNonArrayElementsCount($this->simpleArrayWithKeys)); - self::assertEquals(12, Arrays::getNonArrayElementsCount($this->twoDimensionsArray)); - } - - public function testString2array() - { - /* - * Negative cases - */ - self::assertNull(Arrays::string2array('')); - self::assertNull(Arrays::string2array(null)); - - /* - * Positive cases - */ - $array = [ - 'light' => '#fff', - 'dark' => '#000', - ]; - - self::assertEquals($array, Arrays::string2array('light:#fff|dark:#000')); - self::assertEquals($array, Arrays::string2array('light: #fff | dark: #000')); - - $array = [ - 'red' => '#f00', - 'green' => '#0f0', - 'blue' => '#00f', - ]; - - self::assertEquals($array, Arrays::string2array('red:#f00|green:#0f0|blue:#00f')); - self::assertEquals($array, Arrays::string2array('red: #f00 | green: #0f0 | blue: #00f')); - self::assertEquals($array, Arrays::string2array('red : #f00 | green : #0f0 | blue : #00f')); - } - - public function testAreKeysInArray() - { - /* - * Negative cases - */ - self::assertFalse(Arrays::areKeysInArray([], [])); - self::assertFalse(Arrays::areKeysInArray([null], $this->simpleArray)); - self::assertFalse(Arrays::areKeysInArray([''], $this->simpleArray)); - self::assertFalse(Arrays::areKeysInArray(['dolorrr'], $this->simpleArrayWithKeys)); - - $keys1 = [ - 1, - 3, - 9, - ]; - - self::assertFalse(Arrays::areKeysInArray($keys1, $this->simpleArray)); - self::assertFalse(Arrays::areKeysInArray($keys1, $this->complexArray)); - self::assertFalse(Arrays::areKeysInArray($keys1, $this->complexArray, false)); - - /* - * Positive cases - */ - $keys12 = [ - 2, - 'mollis', - ]; - - $keys13 = [ - 1, - 3, - ]; - - $keys14 = [ - 'dolor', - 'amet', - ]; - - $keys15 = [ - 'dolor', - 'amet', - ]; - - $keys16 = [ - 'a' => 'lorem', - 11 => 'amet', - ]; - - $keys17 = [ - 'a' => 'lorem', - 11 => 'amet', - 'c' => 'sit__', - ]; - - self::assertTrue(Arrays::areKeysInArray([1], $this->simpleArray)); - self::assertTrue(Arrays::areKeysInArray($keys12, $this->simpleArray, false)); - self::assertTrue(Arrays::areKeysInArray($keys12, $this->complexArray)); - self::assertTrue(Arrays::areKeysInArray($keys13, $this->simpleArray)); - self::assertTrue(Arrays::areKeysInArray($keys14, $this->simpleArrayWithKeys)); - self::assertTrue(Arrays::areKeysInArray($keys15, $this->simpleArrayWithKeys)); - - self::assertTrue(Arrays::areKeysInArray(['a' => 'dolor'], $this->simpleArrayWithKeys)); - self::assertTrue(Arrays::areKeysInArray(['a' => 'dolor'], $this->simpleArrayWithKeys, false)); - - self::assertTrue(Arrays::areKeysInArray($keys16, $this->complexArray)); - self::assertTrue(Arrays::areKeysInArray($keys17, $this->complexArray, false)); - } - - public function testGetLastElementsPathsUsingEmptyArray() - { - self::assertNull(Arrays::getLastElementsPaths([])); - } - - public function testGetLastElementsPathsUsingDefaults() - { - /* - * Using default separator and other default arguments - */ - $expected = [ - 'lorem.ipsum.dolor' => 'sit', - 'lorem.ipsum.diam.non' => 'egestas', - 'consectetur' => 'adipiscing', - 'mollis' => 1234, - 2 => [], - 'sit.nullam' => 'donec', - 'sit.aliquet.vitae.ligula' => 'quis', - 'sit.0' => 'elit', - 'amet.0' => 'iaculis', - 'amet.1' => 'primis', - ]; - - self::assertEquals($expected, Arrays::getLastElementsPaths($this->complexArray)); - } - - public function testGetLastElementsPathsUsingCustomSeparator() - { - /* - * Using custom separator - */ - $separator = ' -> '; - $expected = [ - sprintf('lorem%sipsum%sdolor', $separator, $separator) => 'sit', - sprintf('lorem%sipsum%sdiam%snon', $separator, $separator, $separator) => 'egestas', - 'consectetur' => 'adipiscing', - 'mollis' => 1234, - 2 => [], - sprintf('sit%snullam', $separator) => 'donec', - sprintf('sit%saliquet%svitae%sligula', $separator, $separator, $separator) => 'quis', - sprintf('sit%s0', $separator) => 'elit', - sprintf('amet%s0', $separator) => 'iaculis', - sprintf('amet%s1', $separator) => 'primis', - ]; - - self::assertEquals($expected, Arrays::getLastElementsPaths($this->complexArray, $separator)); - } - - /** - * @param string|array $stopIfMatchedBy Patterns of keys or paths that matched will stop the process of path - * building and including children of those keys or paths (recursive will - * not be used for keys in lower level of given array) - * @param string $separator Separator used in resultant strings. Default: ".". - * @param array $expected Expected array - * - * @dataProvider provideStopIfMatchedByForGetLastElementsPaths - */ - public function testGetLastElementsPathsUsingStopIfMatchedBy($stopIfMatchedBy, $separator, array $expected) - { - self::assertEquals($expected, Arrays::getLastElementsPaths($this->superComplexArray, $separator, '', $stopIfMatchedBy)); - } - - public function testAreAllKeysMatchedByPattern() - { - $pattern = '\d+'; - - /* - * Empty array - */ - self::assertFalse(Arrays::areAllKeysMatchedByPattern([], $pattern)); - - /* - * Simple array with integers as keys only - */ - self::assertTrue(Arrays::areAllKeysMatchedByPattern($this->simpleArray, $pattern)); - - /* - * Complex array with strings and integers as keys - */ - self::assertFalse(Arrays::areAllKeysMatchedByPattern($this->complexArray, $pattern)); - - $array = [ - 'a' => 'b', - 'c' => 'd', - ]; - - /* - * Yet another simple array, but with strings as keys - */ - self::assertFalse(Arrays::areAllKeysMatchedByPattern($array, $pattern)); - - /* - * The same array with another pattern - */ - $pattern = '\w+'; - self::assertTrue(Arrays::areAllKeysMatchedByPattern($array, $pattern)); - - /* - * The same array with mixed keys (strings and integers as keys) - */ - $array[1] = 'x'; - $pattern = '\d+'; - self::assertFalse(Arrays::areAllKeysMatchedByPattern($array, $pattern)); - - /* - * Multidimensional array - negative case - */ - $array['e'] = ['f' => 'g']; - self::assertFalse(Arrays::areAllKeysMatchedByPattern($array, $pattern)); - - /* - * Multidimensional array - positive case - */ - unset($array[1]); - $pattern = '\w+'; - self::assertTrue(Arrays::areAllKeysMatchedByPattern($array, $pattern)); - } - - public function testAreAllKeysIntegers() - { - self::assertFalse(Arrays::areAllKeysIntegers([])); - self::assertEquals(1, Arrays::areAllKeysIntegers($this->simpleArray)); - self::assertEquals(2, Arrays::areAllKeysIntegers($this->simpleArray)); - self::assertEquals('', Arrays::areAllKeysIntegers($this->complexArray)); - } - - public function testIssetRecursive() - { - /* - * Negative cases - */ - self::assertFalse(Arrays::issetRecursive([], [])); - - /* - * Positive cases - */ - $unExistingKeys = [ - 'a', - 'b', - 3, - ]; - - $existingKeys = [ - 'simpleArray' => [ - 1, - 3, - 4, - ], - 'simpleArrayWithKeys' => [ - 'dolor', - 'amet', - ], - 'twoDimensionsArray' => [ - 2, - 3, - ], - 'complexArray' => [ - 'sit', - 'aliquet', - 'vitae', - ], - ]; - - self::assertFalse(Arrays::issetRecursive($this->simpleArray, $unExistingKeys)); - self::assertTrue(Arrays::issetRecursive($this->simpleArray, $existingKeys['simpleArray'])); - - self::assertFalse(Arrays::issetRecursive($this->simpleArrayWithKeys, $unExistingKeys)); - self::assertTrue(Arrays::issetRecursive($this->simpleArrayWithKeys, $existingKeys['simpleArrayWithKeys'])); - - self::assertFalse(Arrays::issetRecursive($this->twoDimensionsArray, $unExistingKeys)); - self::assertTrue(Arrays::issetRecursive($this->twoDimensionsArray, $existingKeys['twoDimensionsArray'])); - - self::assertFalse(Arrays::issetRecursive($this->complexArray, $unExistingKeys)); - self::assertTrue(Arrays::issetRecursive($this->complexArray, $existingKeys['complexArray'])); - } - - public function testGetValueByKeysPath() - { - /* - * Negative cases - */ - self::assertNull(Arrays::getValueByKeysPath([], [])); - - /* - * Positive cases - */ - self::assertNull(Arrays::getValueByKeysPath($this->simpleArray, [])); - self::assertEquals('ipsum', Arrays::getValueByKeysPath($this->simpleArray, [1])); - self::assertEquals('sit', Arrays::getValueByKeysPath($this->simpleArray, [3])); - self::assertEquals('sit', Arrays::getValueByKeysPath($this->simpleArrayWithKeys, ['dolor'])); - - $keys = [ - 'twoDimensionsArray' => [ - 2, - 2, - ], - 'complexArray' => [ - [ - 'lorem', - 'ipsum', - 'diam', - ], - [ - 'sit', - 'aliquet', - ], - ], - ]; - - $value = [ - [ - 'non' => 'egestas', - ], - [ - 'vitae' => [ - 'ligula' => 'quis', - ], - ], - ]; - - self::assertEquals('fringilla', Arrays::getValueByKeysPath($this->twoDimensionsArray, $keys['twoDimensionsArray'])); - self::assertEquals($value[0], Arrays::getValueByKeysPath($this->complexArray, $keys['complexArray'][0])); - self::assertEquals($value[1], Arrays::getValueByKeysPath($this->complexArray, $keys['complexArray'][1])); - } - - public function testGetAllValuesOfKeyEmptyValue() - { - self::assertNull(Arrays::getAllValuesOfKey([], '')); - self::assertNull(Arrays::getAllValuesOfKey([], 'test')); - } - - public function testGetAllValuesOfKeyNotExistingKey() - { - self::assertNull(Arrays::getAllValuesOfKey([], 'ipsum')); - self::assertEquals([], Arrays::getAllValuesOfKey($this->simpleArray, 'ipsum')); - } - - public function testGetAllValuesOfKey() - { - /* - * Positive case - 1-dimension array - */ - self::assertEquals(['ipsum'], Arrays::getAllValuesOfKey($this->simpleArray, 1)); - - /* - * Positive case - 2-dimensions array - */ - $effect = [ - [ - 'lorem', - 'ipsum', - 'dolor', - 'sit', - 'amet', - ], - 1 => 'consectetur', - 2 => 'donec', - ]; - - self::assertEquals($effect, Arrays::getAllValuesOfKey($this->twoDimensionsArray, 0)); - - /* - * Positive case - multi-dimensions array - */ - self::assertEquals(['primis'], Arrays::getAllValuesOfKey($this->complexArray, 1)); - - /* - * Positive case - multi-dimensions array - */ - $effect = [ - 0 => [ - 'in' => [ - 'dui', - 'dolor' => [ - 'aliquam', - ], - ], - ], - ]; - - self::assertEquals($effect, Arrays::getAllValuesOfKey($this->superComplexArray, 'tortor')); - } - - public function testKsortRecursive() - { - /* - * Negative cases - */ - $array = []; - self::assertNull(Arrays::ksortRecursive($array)); - - /* - * Positive cases - */ - self::assertEquals($this->simpleArray, Arrays::ksortRecursive($this->simpleArray)); - self::assertEquals($this->simpleArray, Arrays::ksortRecursive($this->simpleArray, SORT_NUMERIC)); - self::assertEquals($this->twoDimensionsArray, Arrays::ksortRecursive($this->twoDimensionsArray)); - - /* - * Positive case - multi-dimensions array - */ - $effect = [ - 'amet' => [ - 'iaculis', - 'primis', - ], - 'consectetur' => 'adipiscing', - 'lorem' => [ - 'ipsum' => [ - 'dolor' => 'sit', - 'diam' => [ - 'non' => 'egestas', - ], - ], - ], - 'mollis' => 1234, - 'sit' => [ - 'nullam' => 'donec', - 'aliquet' => [ - 'vitae' => [ - 'ligula' => 'quis', - ], - ], - 'elit', - ], - 2 => [], - ]; - - self::assertEquals($effect, Arrays::ksortRecursive($this->complexArray)); - - /* - * Positive case - multi-dimensions array - with options of ksort() function - */ - $effect = [ - 2 => [], - 'amet' => [ - 'iaculis', - 'primis', - ], - 'consectetur' => 'adipiscing', - 'lorem' => [ - 'ipsum' => [ - 'dolor' => 'sit', - 'diam' => [ - 'non' => 'egestas', - ], - ], - ], - 'mollis' => 1234, - 'sit' => [ - 'nullam' => 'donec', - 'aliquet' => [ - 'vitae' => [ - 'ligula' => 'quis', - ], - ], - 'elit', - ], - ]; - - self::assertEquals($effect, Arrays::ksortRecursive($this->complexArray, SORT_STRING & SORT_NATURAL)); - } - - public function testSetPositions() - { - /* - * Negative cases - */ - self::assertNull(Arrays::setPositions([])); - - /* - * Positive case - 1-dimension array - */ - $array = [ - 'abc', - ]; - - self::assertEquals($array, Arrays::setPositions($array)); - - /* - * Positive case - 2-dimensions array - */ - $effect = $this->twoDimensionsArray; - $effect[0][Arrays::POSITION_KEY_NAME] = 1; - $effect[1][Arrays::POSITION_KEY_NAME] = 2; - $effect[2][Arrays::POSITION_KEY_NAME] = 3; - - self::assertEquals($effect, Arrays::setPositions($this->twoDimensionsArray)); - - /* - * Positive case - multi-level array - */ - $array = [ - 'lorem', - 'ipsum' => [ - 'dolor', - 'sit', - ], - 'amet' => [ - 'consectetur', - 'adipiscing' => [ - 'elit' => [ - 'cras', - 'quis', - 'ligula', - ], - ], - ], - ]; - - $effect = [ - 'lorem', - 'ipsum' => [ - 'dolor', - 'sit', - Arrays::POSITION_KEY_NAME => 1, - ], - 'amet' => [ - 'consectetur', - 'adipiscing' => [ - 'elit' => [ - 'cras', - 'quis', - 'ligula', - Arrays::POSITION_KEY_NAME => 1, - ], - Arrays::POSITION_KEY_NAME => 1, - ], - Arrays::POSITION_KEY_NAME => 2, - ], - ]; - - self::assertEquals($effect, Arrays::setPositions($array)); - - /* - * Positive case - non-default name of key with position value & 2-dimensions array - */ - $keyName = 'level'; - $effect = $this->twoDimensionsArray; - - $effect[0][$keyName] = 1; - $effect[1][$keyName] = 2; - $effect[2][$keyName] = 3; - - self::assertEquals($effect, Arrays::setPositions($this->twoDimensionsArray, $keyName)); - - /* - * Positive case - non-default start value of position & 2-dimensions array - */ - $startPosition = 5; - $effect = $this->twoDimensionsArray; - - $effect[Arrays::POSITION_KEY_NAME] = $startPosition; - $effect[0][Arrays::POSITION_KEY_NAME] = 1; - $effect[1][Arrays::POSITION_KEY_NAME] = 2; - $effect[2][Arrays::POSITION_KEY_NAME] = 3; - - self::assertEquals($effect, Arrays::setPositions($this->twoDimensionsArray, Arrays::POSITION_KEY_NAME, $startPosition)); - } - - public function testTrimRecursive() - { - /* - * Negative cases - */ - self::assertSame([], Arrays::trimRecursive([])); - - /* - * Positive cases - */ - self::assertEquals(['a'], Arrays::trimRecursive([' a '])); - self::assertEquals([ - 'a', - 'b', - ], Arrays::trimRecursive([ - ' a ', - 'b ', - ])); - - $array = [ - 'abc ', - [ - 'def', - 'ghi ', - ], - ]; - - $result = [ - 'abc', - [ - 'def', - 'ghi', - ], - ]; - - self::assertEquals($result, Arrays::trimRecursive($array)); - - $array = [ - 'abc ', - [ - ' def ', - 'ghi ', - 'oo' => [ - ' ee ', - 'g' => 'h hhh ', - ], - ], - ]; - - $result = [ - 'abc', - [ - 'def', - 'ghi', - 'oo' => [ - 'ee', - 'g' => 'h hhh', - ], - ], - ]; - - self::assertEquals($result, Arrays::trimRecursive($array)); - } - - public function testSortByCustomKeysOrder() - { - /* - * Negative cases - */ - self::assertNull(Arrays::sortByCustomKeysOrder([], [])); - - /* - * Positive cases - */ - self::assertEquals([0], Arrays::sortByCustomKeysOrder([0], [])); - self::assertEquals($this->simpleArray, Arrays::sortByCustomKeysOrder($this->simpleArray, [])); - - $keysOrder = [ - 'amet', - 'dolor', - 'Lorem', - ]; - - $sorted = [ - 'dolor' => 'sit', - 'amet' => 'consectetur', - 'Lorem' => 'ipsum', - ]; - - self::assertEquals($sorted, Arrays::sortByCustomKeysOrder($this->simpleArrayWithKeys, $keysOrder)); - - $array = [ - 'Lorem', - 'ipsum', - 'dolor', - 'sit', - 'amet', - ]; - - $keysOrder = [ - 0, - 3, - 1, - 2, - ]; - - $sorted = [ - 0 => 'Lorem', - 3 => 'sit', - 1 => 'ipsum', - 2 => 'dolor', - 4 => 'amet', - ]; - - self::assertEquals($sorted, Arrays::sortByCustomKeysOrder($array, $keysOrder)); - - $array = [ - 'Lorem', - 'ipsum', - 'dolor', - 'sit', - 'amet', - ]; - - $keysOrder = [ - 0, - 3, - ]; - - $sorted = [ - 0 => 'Lorem', - 3 => 'sit', - 1 => 'ipsum', - 2 => 'dolor', - 4 => 'amet', - ]; - - self::assertEquals($sorted, Arrays::sortByCustomKeysOrder($array, $keysOrder)); - } - - public function testImplodeSmart() - { - $separator = '/'; - - /* - * Empty array - */ - self::assertNull(Arrays::implodeSmart([], $separator)); - - /* - * Simple, one-dimension array - */ - self::assertEquals(implode($separator, $this->simpleArray), Arrays::implodeSmart($this->simpleArray, $separator)); - - /* - * An array with elements that contain separator - */ - $array = [ - 'lorem' . $separator, - 'ipsum', - $separator . 'dolor', - ]; - - self::assertEquals(implode($separator, [ - 'lorem', - 'ipsum', - 'dolor', - ]), Arrays::implodeSmart($array, $separator)); - - /* - * Complex array - */ - self::assertEquals(implode($separator, [ - 'donec', - 'quis', - 'elit', - ]), Arrays::implodeSmart($this->complexArray['sit'], $separator)); - } - - public function testGetNextElement() - { - /* - * Negative cases - */ - self::assertNull(Arrays::getNextElement($this->simpleArray, 'amet')); - self::assertNull(Arrays::getNextElement($this->simpleArray, 'xyz')); - self::assertNull(Arrays::getNextElement($this->simpleArray, 0)); - self::assertNull(Arrays::getNextElement([], '')); - self::assertNull(Arrays::getNextElement([], null)); - - /* - * Positive cases - */ - self::assertEquals('ipsum', Arrays::getNextElement($this->simpleArray, 'Lorem')); - self::assertEquals('sit', Arrays::getNextElement($this->simpleArray, 'dolor')); - } - - public function testGetPreviousElement() - { - /* - * Negative cases - */ - self::assertNull(Arrays::getPreviousElement($this->simpleArray, 'Lorem')); - self::assertNull(Arrays::getPreviousElement($this->simpleArray, 'xyz')); - self::assertNull(Arrays::getPreviousElement($this->simpleArray, 0)); - self::assertNull(Arrays::getPreviousElement([], '')); - self::assertNull(Arrays::getPreviousElement([], null)); - - /* - * Positive cases - */ - self::assertEquals('ipsum', Arrays::getPreviousElement($this->simpleArray, 'dolor')); - self::assertEquals('sit', Arrays::getPreviousElement($this->simpleArray, 'amet')); - } - - public function testGetIndexOf() - { - /* - * Negative cases - */ - self::assertFalse(Arrays::getIndexOf([], 'a')); - self::assertNull(Arrays::getIndexOf($this->simpleArray, 'loremmm')); - - /* - * Positive cases - */ - self::assertEquals(1, Arrays::getIndexOf($this->simpleArray, 'ipsum')); - self::assertEquals('dolor', Arrays::getIndexOf($this->simpleArrayWithKeys, 'sit')); - self::assertEquals('mollis', Arrays::getIndexOf($this->complexArray, 1234)); - } - - public function testIncrementIndexes() - { - /* - * Negative cases - */ - self::assertNull(Arrays::incrementIndexes([])); - - /* - * Positive cases - */ - $array = [ - 1 => 'Lorem', - 2 => 'ipsum', - 3 => 'dolor', - 4 => 'sit', - 5 => 'amet', - ]; - self::assertEquals($array, Arrays::incrementIndexes($this->simpleArray)); - - $array = [ - 0 => 'Lorem', - 2 => 'ipsum', - 3 => 'dolor', - 4 => 'sit', - 5 => 'amet', - ]; - self::assertEquals($array, Arrays::incrementIndexes($this->simpleArray, 1)); - - $array = [ - 0 => 'Lorem', - 1 => 'ipsum', - 3 => 'dolor', - 4 => 'sit', - 5 => 'amet', - ]; - self::assertEquals($array, Arrays::incrementIndexes($this->simpleArray, 2)); - } - - public function testAreAllValuesEmpty() - { - /* - * Negative cases - */ - self::assertFalse(Arrays::areAllValuesEmpty([])); - self::assertFalse(Arrays::areAllValuesEmpty([], true)); - self::assertFalse(Arrays::areAllValuesEmpty($this->simpleArray)); - self::assertFalse(Arrays::areAllValuesEmpty($this->simpleArray, true)); - - $array = [ - null, - 0, - ]; - self::assertFalse(Arrays::areAllValuesEmpty($array, true)); - - $array = [ - null, - [ - null, - ], - ]; - self::assertFalse(Arrays::areAllValuesEmpty($array, true)); - - /* - * Positive cases - */ - $array = [ - '', - 0, - ]; - self::assertTrue(Arrays::areAllValuesEmpty($array)); - - $array = [ - null, - null, - ]; - self::assertTrue(Arrays::areAllValuesEmpty($array, true)); + static::assertSame($expected, Arrays::containsEmptyStringsOnly($array)); } public function testDiffRecursive() { - /* - * Negative cases - */ + // Negative cases self::assertEquals([], Arrays::arrayDiffRecursive([], [])); self::assertEquals([], Arrays::arrayDiffRecursive([], [], true)); - /* - * Positive cases - full comparison (keys and values) - */ + // Positive cases - full comparison (keys and values) self::assertEquals(['a'], Arrays::arrayDiffRecursive(['a'], [])); self::assertEquals([], Arrays::arrayDiffRecursive([], ['a'])); self::assertEquals([], Arrays::arrayDiffRecursive($this->simpleArray, $this->simpleArray)); @@ -1410,9 +2485,7 @@ letsTest[2] = value_2;'; self::assertEquals($this->twoDimensionsArray[1], Arrays::arrayDiffRecursive($this->twoDimensionsArray[1], $this->twoDimensionsArray)); - /* - * Positive cases - simple comparison (values only) - */ + // Positive cases - simple comparison (values only) self::assertEquals(['a'], Arrays::arrayDiffRecursive(['a'], [], true)); $array = [ @@ -1509,47 +2582,454 @@ letsTest[2] = value_2;'; self::assertEquals($diff, Arrays::arrayDiffRecursive($array, $this->twoDimensionsArray, true)); } + public function testGetAllValuesOfKey() + { + // Positive case - 1-dimension array + self::assertEquals(['ipsum'], Arrays::getAllValuesOfKey($this->simpleArray, 1)); + + // Positive case - 2-dimensions array + $effect = [ + [ + 'lorem', + 'ipsum', + 'dolor', + 'sit', + 'amet', + ], + 1 => 'consectetur', + 2 => 'donec', + ]; + + self::assertEquals($effect, Arrays::getAllValuesOfKey($this->twoDimensionsArray, 0)); + + // Positive case - multi-dimensions array + self::assertEquals(['primis'], Arrays::getAllValuesOfKey($this->complexArray, 1)); + + // Positive case - multi-dimensions array + $effect = [ + 0 => [ + 'in' => [ + 'dui', + 'dolor' => [ + 'aliquam', + ], + ], + ], + ]; + + self::assertEquals($effect, Arrays::getAllValuesOfKey($this->superComplexArray, 'tortor')); + } + + public function testGetAllValuesOfKeyEmptyValue() + { + self::assertNull(Arrays::getAllValuesOfKey([], '')); + self::assertNull(Arrays::getAllValuesOfKey([], 'test')); + } + + public function testGetAllValuesOfKeyNotExistingKey() + { + self::assertNull(Arrays::getAllValuesOfKey([], 'ipsum')); + self::assertEquals([], Arrays::getAllValuesOfKey($this->simpleArray, 'ipsum')); + } + public function testGetDimensionsCount() { - /* - * Basic cases - */ + // Basic cases self::assertEquals(0, Arrays::getDimensionsCount([])); self::assertEquals(1, Arrays::getDimensionsCount([''])); - /* - * Simple cases - */ + // Simple cases self::assertEquals(1, Arrays::getDimensionsCount($this->simpleArray)); self::assertEquals(1, Arrays::getDimensionsCount($this->simpleArrayWithKeys)); - /* - * Complex cases - */ + // Complex cases self::assertEquals(2, Arrays::getDimensionsCount($this->twoDimensionsArray)); self::assertEquals(4, Arrays::getDimensionsCount($this->complexArray)); } - public function testIsMultiDimensional() + public function testGetElementsFromLevel(): void { - /* - * Negative cases - */ - self::assertNull(Arrays::isMultiDimensional([])); + $array = [ + // Level 1: + 'ab', + [ + // Level 2: + 'cd', + 'ef', + ], - /* - * Positive cases - */ - self::assertFalse(Arrays::isMultiDimensional($this->simpleArray)); - self::assertFalse(Arrays::isMultiDimensional($this->simpleArrayWithKeys)); + // Level 1: + [ + // Level 2: + 'gh', + [ + // Level 3: + 'ij', + 'kl', + ], + ], - self::assertTrue(Arrays::isMultiDimensional($this->twoDimensionsArray)); - self::assertTrue(Arrays::isMultiDimensional($this->complexArray)); + // Level 1: + [ + // Level 2: + [ + // Level 3: + 'mn', + 'op', + ], + ], + ]; + + $expectedLevel1 = [ + 'ab', + [ + 'cd', + 'ef', + ], + [ + 'gh', + [ + 'ij', + 'kl', + ], + ], + [ + [ + 'mn', + 'op', + ], + ], + ]; + + $expectedLevel2 = [ + 'cd', + 'ef', + 'gh', + [ + 'ij', + 'kl', + ], + [ + 'mn', + 'op', + ], + ]; + + $expectedLevel3 = [ + 'ij', + 'kl', + 'mn', + 'op', + ]; + + self::assertSame($expectedLevel1, Arrays::getElementsFromLevel($array, 1)); + self::assertSame($expectedLevel2, Arrays::getElementsFromLevel($array, 2)); + self::assertSame($expectedLevel3, Arrays::getElementsFromLevel($array, 3)); } - public function testGetNonEmptyValuesUsingEmptyArray() + public function testGetElementsFromLevelIfArrayHasOneLevelOnly(): void { - self::assertNull(Arrays::getNonEmptyValues([])); + $array = [ + // Level 1: + 'ab', + 'cd', + 'ef', + ]; + + self::assertSame($array, Arrays::getElementsFromLevel($array, 1)); + } + + public function testGetElementsFromLevelIfArrayIsEmpty(): void + { + self::assertNull(Arrays::getElementsFromLevel([], -1)); + self::assertNull(Arrays::getElementsFromLevel([], 0)); + self::assertNull(Arrays::getElementsFromLevel([], 1)); + } + + public function testGetElementsFromLevelIfGivenKeyDoesNotExist(): void + { + $array = [ + 'test1' => [1, 2, 3], + 'test2' => [4, 5, 6], + 'test3' => [ + 'xy', + 'test4' => [7, 8, 9], + 'test5' => [ + 'test6' => [10, 11, 12], + ], + ], + ]; + + self::assertSame([], Arrays::getElementsFromLevel($array, 2, 'X')); + } + + public function testGetElementsFromLevelIfGivenLevelIsNotPositiveValue(): void + { + self::assertNull(Arrays::getElementsFromLevel([1, 2, 3], -1)); + self::assertNull(Arrays::getElementsFromLevel([1, 2, 3], 0)); + } + + public function testGetElementsFromLevelIfThereIsNoGivenLevel(): void + { + self::assertSame([], Arrays::getElementsFromLevel([1, 2, 3], 9999)); + } + + public function testGetElementsFromLevelWithGivenKey(): void + { + $array = [ + // Level 1: + [ + 'a', + 'b', + + // Level 2: + 'c' => [ + 1, + 2, + + // Level 3: + 'c' => [ + 4, + 5, + ], + ], + ], + + // Level 1: + [ + 'd', + 'e', + + // Level 2: + 'c' => [ + 6, + 7, + + // Level 3: + 'c' => [ + 8, + 9, + ], + ], + ], + ]; + + $expected2 = [ + [ + 1, + 2, + 'c' => [ + 4, + 5, + ], + ], + [ + 6, + 7, + 'c' => [ + 8, + 9, + ], + ], + ]; + + $expected3 = [ + [ + 4, + 5, + ], + [ + 8, + 9, + ], + ]; + + self::assertSame([], Arrays::getElementsFromLevel($array, 1, 'c')); + self::assertSame($expected2, Arrays::getElementsFromLevel($array, 2, 'c')); + self::assertSame($expected3, Arrays::getElementsFromLevel($array, 3, 'c')); + self::assertSame([], Arrays::getElementsFromLevel($array, 4, 'c')); + } + + /** + * @param string $description + * @param $expected + * @param array $array + * @param null|bool $firstLevelOnly + * + * @dataProvider provideFirstElement + */ + public function testGetFirstElement( + string $description, + $expected, + array $array, + ?bool $firstLevelOnly = null + ): void { + if (null === $firstLevelOnly) { + static::assertSame($expected, Arrays::getFirstElement($array), $description); + + return; + } + + static::assertSame($expected, Arrays::getFirstElement($array, $firstLevelOnly), $description); + } + + public function testGetFirstKey() + { + // Negative cases + self::assertNull(Arrays::getFirstKey([])); + + // Positive cases + self::assertEquals(0, Arrays::getFirstKey($this->simpleArray)); + self::assertEquals('lorem', Arrays::getFirstKey($this->complexArray)); + } + + public function testGetIndexOf() + { + // Negative cases + self::assertFalse(Arrays::getIndexOf([], 'a')); + self::assertNull(Arrays::getIndexOf($this->simpleArray, 'loremmm')); + + // Positive cases + self::assertEquals(1, Arrays::getIndexOf($this->simpleArray, 'ipsum')); + self::assertEquals('dolor', Arrays::getIndexOf($this->simpleArrayWithKeys, 'sit')); + self::assertEquals('mollis', Arrays::getIndexOf($this->complexArray, 1234)); + } + + /** + * @param string $description + * @param $expected + * @param array $array + * @param null|bool $firstLevelOnly + * + * @dataProvider provideLastElement + */ + public function testGetLastElement( + string $description, + $expected, + array $array, + ?bool $firstLevelOnly = null + ): void { + if (null === $firstLevelOnly) { + static::assertSame($expected, Arrays::getLastElement($array), $description); + + return; + } + + static::assertSame($expected, Arrays::getLastElement($array, $firstLevelOnly), $description); + } + + public function testGetLastElementBreadCrumb() + { + self::assertNull(Arrays::getLastElementBreadCrumb([])); + self::assertEquals('4/amet', Arrays::getLastElementBreadCrumb($this->simpleArray)); + self::assertEquals('2/3/eleifend', Arrays::getLastElementBreadCrumb($this->twoDimensionsArray)); + self::assertEquals('amet/1/primis', Arrays::getLastElementBreadCrumb($this->complexArray)); + } + + public function testGetLastElementsPathsUsingCustomSeparator(): void + { + // Using custom separator + $separator = ' -> '; + $expected = [ + sprintf('lorem%sipsum%sdolor', $separator, $separator) => 'sit', + sprintf('lorem%sipsum%sdiam%snon', $separator, $separator, $separator) => 'egestas', + 'consectetur' => 'adipiscing', + 'mollis' => 1234, + 2 => [], + sprintf('sit%snullam', $separator) => 'donec', + sprintf('sit%saliquet%svitae%sligula', $separator, $separator, $separator) => 'quis', + sprintf('sit%s0', $separator) => 'elit', + sprintf('amet%s0', $separator) => 'iaculis', + sprintf('amet%s1', $separator) => 'primis', + ]; + + self::assertEquals($expected, Arrays::getLastElementsPaths($this->complexArray, $separator)); + } + + public function testGetLastElementsPathsUsingDefaults(): void + { + // Using default separator and other default arguments + $expected = [ + 'lorem.ipsum.dolor' => 'sit', + 'lorem.ipsum.diam.non' => 'egestas', + 'consectetur' => 'adipiscing', + 'mollis' => 1234, + 2 => [], + 'sit.nullam' => 'donec', + 'sit.aliquet.vitae.ligula' => 'quis', + 'sit.0' => 'elit', + 'amet.0' => 'iaculis', + 'amet.1' => 'primis', + ]; + + self::assertEquals($expected, Arrays::getLastElementsPaths($this->complexArray)); + } + + public function testGetLastElementsPathsUsingEmptyArray(): void + { + self::assertNull(Arrays::getLastElementsPaths([])); + } + + /** + * @param array $stopIfMatchedBy Patterns of keys or paths that matched will stop the process of path building and + * including children of those keys or paths (recursive will not be used for keys in + * lower level of given array) + * @param string $separator Separator used in resultant strings. Default: ".". + * @param array $expected Expected array + * + * @dataProvider provideStopIfMatchedByForGetLastElementsPaths + */ + public function testGetLastElementsPathsUsingStopIfMatchedBy( + array $stopIfMatchedBy, + string $separator, + array $expected + ): void { + $paths = Arrays::getLastElementsPaths($this->superComplexArray, $separator, '', $stopIfMatchedBy); + self::assertEquals($expected, $paths); + } + + public function testGetLastKey() + { + self::assertNull(Arrays::getLastKey([])); + self::assertEquals(4, Arrays::getLastKey($this->simpleArray)); + self::assertEquals('amet', Arrays::getLastKey($this->complexArray)); + } + + /** + * @param string $description + * @param null|array $expected + * @param array $array + * + * @dataProvider provideLastRow + */ + public function testGetLastRow(string $description, ?array $expected, array $array): void + { + static::assertSame($expected, Arrays::getLastRow($array), $description); + } + + public function testGetNextElement() + { + // Negative cases + self::assertNull(Arrays::getNextElement($this->simpleArray, 'amet')); + self::assertNull(Arrays::getNextElement($this->simpleArray, 'xyz')); + self::assertNull(Arrays::getNextElement($this->simpleArray, 0)); + self::assertNull(Arrays::getNextElement([], '')); + self::assertNull(Arrays::getNextElement([], null)); + + // Positive cases + self::assertEquals('ipsum', Arrays::getNextElement($this->simpleArray, 'Lorem')); + self::assertEquals('sit', Arrays::getNextElement($this->simpleArray, 'dolor')); + } + + public function testGetNonArrayElementsCount(): void + { + // Negative cases + self::assertNull(Arrays::getNonArrayElementsCount([])); + + // Positive cases + self::assertEquals(5, Arrays::getNonArrayElementsCount($this->simpleArray)); + self::assertEquals(3, Arrays::getNonArrayElementsCount($this->simpleArrayWithKeys)); + self::assertEquals(12, Arrays::getNonArrayElementsCount($this->twoDimensionsArray)); } /** @@ -1564,18 +3044,6 @@ letsTest[2] = value_2;'; self::assertSame($expected, Arrays::getNonEmptyValues($values), $description); } - /** - * @param string $description Description of test case - * @param array $values The values to filter - * @param string $expected Expected non-empty values (as string) - * - * @dataProvider provideValuesToFilterNonEmptyAsStringUsingDefaultSeparator - */ - public function testGetNonEmptyValuesAsStringUsingDefaultSeparator($description, array $values, $expected) - { - self::assertSame($expected, Arrays::getNonEmptyValuesAsString($values), $description); - } - /** * @param string $description Description of test case * @param array $values The values to filter @@ -1590,1100 +3058,850 @@ letsTest[2] = value_2;'; } /** - * Provides simple array to set/replace values with keys + * @param string $description Description of test case + * @param array $values The values to filter + * @param string $expected Expected non-empty values (as string) * - * @return \Generator + * @dataProvider provideValuesToFilterNonEmptyAsStringUsingDefaultSeparator */ - public function provideSimpleArrayToSetKeysAsValues() + public function testGetNonEmptyValuesAsStringUsingDefaultSeparator($description, array $values, $expected) { - yield[ - [ - 1, + self::assertSame($expected, Arrays::getNonEmptyValuesAsString($values), $description); + } + + public function testGetNonEmptyValuesUsingEmptyArray() + { + self::assertNull(Arrays::getNonEmptyValues([])); + } + + public function testGetPreviousElement() + { + // Negative cases + self::assertNull(Arrays::getPreviousElement($this->simpleArray, 'Lorem')); + self::assertNull(Arrays::getPreviousElement($this->simpleArray, 'xyz')); + self::assertNull(Arrays::getPreviousElement($this->simpleArray, 0)); + self::assertNull(Arrays::getPreviousElement([], '')); + self::assertNull(Arrays::getPreviousElement([], null)); + + // Positive cases + self::assertEquals('ipsum', Arrays::getPreviousElement($this->simpleArray, 'dolor')); + self::assertEquals('sit', Arrays::getPreviousElement($this->simpleArray, 'amet')); + } + + public function testGetValueByKeysPath() + { + // Negative cases + self::assertNull(Arrays::getValueByKeysPath([], [])); + + // Positive cases + self::assertNull(Arrays::getValueByKeysPath($this->simpleArray, [])); + self::assertEquals('ipsum', Arrays::getValueByKeysPath($this->simpleArray, [1])); + self::assertEquals('sit', Arrays::getValueByKeysPath($this->simpleArray, [3])); + self::assertEquals('sit', Arrays::getValueByKeysPath($this->simpleArrayWithKeys, ['dolor'])); + + $keys = [ + 'twoDimensionsArray' => [ 2, + 2, + ], + 'complexArray' => [ + [ + 'lorem', + 'ipsum', + 'diam', + ], + [ + 'sit', + 'aliquet', + ], + ], + ]; + + $value = [ + [ + 'non' => 'egestas', + ], + [ + 'vitae' => [ + 'ligula' => 'quis', + ], + ], + ]; + + self::assertEquals('fringilla', Arrays::getValueByKeysPath($this->twoDimensionsArray, $keys['twoDimensionsArray'])); + self::assertEquals($value[0], Arrays::getValueByKeysPath($this->complexArray, $keys['complexArray'][0])); + self::assertEquals($value[1], Arrays::getValueByKeysPath($this->complexArray, $keys['complexArray'][1])); + } + + public function testImplodeSmart() + { + $separator = '/'; + + // Empty array + self::assertNull(Arrays::implodeSmart([], $separator)); + + // Simple, One-dimensional array + self::assertEquals(implode($separator, $this->simpleArray), Arrays::implodeSmart($this->simpleArray, $separator)); + + // An array with elements that contain separator + $array = [ + 'lorem'.$separator, + 'ipsum', + $separator.'dolor', + ]; + + self::assertEquals(implode($separator, [ + 'lorem', + 'ipsum', + 'dolor', + ]), Arrays::implodeSmart($array, $separator)); + + // Complex array + self::assertEquals(implode($separator, [ + 'donec', + 'quis', + 'elit', + ]), Arrays::implodeSmart($this->complexArray['sit'], $separator)); + } + + public function testIncrementIndexes() + { + // Negative cases + self::assertNull(Arrays::incrementIndexes([])); + + // Positive cases + $array = [ + 1 => 'Lorem', + 2 => 'ipsum', + 3 => 'dolor', + 4 => 'sit', + 5 => 'amet', + ]; + self::assertEquals($array, Arrays::incrementIndexes($this->simpleArray)); + + $array = [ + 0 => 'Lorem', + 2 => 'ipsum', + 3 => 'dolor', + 4 => 'sit', + 5 => 'amet', + ]; + self::assertEquals($array, Arrays::incrementIndexes($this->simpleArray, 1)); + + $array = [ + 0 => 'Lorem', + 1 => 'ipsum', + 3 => 'dolor', + 4 => 'sit', + 5 => 'amet', + ]; + self::assertEquals($array, Arrays::incrementIndexes($this->simpleArray, 2)); + } + + /** + * @param string $description Description of test case + * @param mixed $value The value to verify + * @param bool $expected Expected information + * + * @dataProvider provideValueToIsEmptyArray + */ + public function testIsEmptyArray(string $description, $value, bool $expected): void + { + self::assertSame($expected, Arrays::isEmptyArray($value), $description); + } + + /** + * @param string $description + * @param bool $expected + * @param array $array + * @param $element + * @param bool $firstLevelOnly + * + * @dataProvider provideIsFirstElement + */ + public function testIsFirstElement( + string $description, + bool $expected, + array $array, + $element, + ?bool $firstLevelOnly = null + ): void { + if (null === $firstLevelOnly) { + static::assertSame($expected, Arrays::isFirstElement($array, $element), $description); + + return; + } + + static::assertSame($expected, Arrays::isFirstElement($array, $element, $firstLevelOnly), $description); + } + + /** + * @param string $description + * @param bool $expected + * @param array $array + * @param $element + * @param null|bool $firstLevelOnly + * + * @dataProvider provideIsLastElement + */ + public function testIsLastElement( + string $description, + bool $expected, + array $array, + $element, + ?bool $firstLevelOnly = null + ): void { + if (null === $firstLevelOnly) { + static::assertSame($expected, Arrays::isLastElement($array, $element), $description); + + return; + } + + static::assertSame($expected, Arrays::isLastElement($array, $element, $firstLevelOnly), $description); + } + + public function testIsMultiDimensional() + { + // Negative cases + self::assertNull(Arrays::isMultiDimensional([])); + + // Positive cases + self::assertFalse(Arrays::isMultiDimensional($this->simpleArray)); + self::assertFalse(Arrays::isMultiDimensional($this->simpleArrayWithKeys)); + + self::assertTrue(Arrays::isMultiDimensional($this->twoDimensionsArray)); + self::assertTrue(Arrays::isMultiDimensional($this->complexArray)); + } + + /** + * @param string $description Description of test case + * @param mixed $value The value to verify + * @param bool $expected Expected information + * + * @dataProvider provideValueToIsNotEmptyArray + */ + public function testIsNotEmptyArray(string $description, $value, bool $expected): void + { + self::assertSame($expected, Arrays::isNotEmptyArray($value), $description); + } + + public function testIssetRecursive() + { + // Negative cases + self::assertFalse(Arrays::issetRecursive([], [])); + + // Positive cases + $unExistingKeys = [ + 'a', + 'b', + 3, + ]; + + $existingKeys = [ + 'simpleArray' => [ + 1, 3, 4, ], - [ - 1 => 0, - 2 => 1, - 3 => 2, - 4 => 3, - ], - ]; - - yield[ - [ - 'Lorem', - 'ipsum', + 'simpleArrayWithKeys' => [ 'dolor', - 'sit', 'amet', ], - [ - 'Lorem' => 0, - 'ipsum' => 1, - 'dolor' => 2, - 'sit' => 3, - 'amet' => 4, + 'twoDimensionsArray' => [ + 2, + 3, + ], + 'complexArray' => [ + 'sit', + 'aliquet', + 'vitae', ], ]; + + self::assertFalse(Arrays::issetRecursive($this->simpleArray, $unExistingKeys)); + self::assertTrue(Arrays::issetRecursive($this->simpleArray, $existingKeys['simpleArray'])); + + self::assertFalse(Arrays::issetRecursive($this->simpleArrayWithKeys, $unExistingKeys)); + self::assertTrue(Arrays::issetRecursive($this->simpleArrayWithKeys, $existingKeys['simpleArrayWithKeys'])); + + self::assertFalse(Arrays::issetRecursive($this->twoDimensionsArray, $unExistingKeys)); + self::assertTrue(Arrays::issetRecursive($this->twoDimensionsArray, $existingKeys['twoDimensionsArray'])); + + self::assertFalse(Arrays::issetRecursive($this->complexArray, $unExistingKeys)); + self::assertTrue(Arrays::issetRecursive($this->complexArray, $existingKeys['complexArray'])); } - /** - * Provides an array with duplicated values to set/replace values with keys - * - * @return \Generator - */ - public function provideArrayWithDuplicatedValuesToSetKeysAsValues() + public function testKsortRecursive() { - yield[ - [ - 'lorem' => 'ipsum', - 'dolor' => 'ipsum', - 'sit' => 'amet', - 'diam' => 'non', - 'elit' => 'non', - 'in' => 'non', - ], - [ - 'ipsum' => [ - 'lorem', - 'dolor', - ], - 'amet' => 'sit', - 'non' => [ - 'diam', - 'elit', - 'in', - ], - ], - ]; + // Negative cases + $array = []; + self::assertNull(Arrays::ksortRecursive($array)); - yield[ - [ - 'lorem' => [ - 'diam' => 'non', - 'elit' => 'non', - 'in' => 'non', - ], - 'dolor1' => 'ipsum', - 'dolor2' => 'ipsum', - 'sit' => 'amet', - ], - [ - 'lorem' => [ - 'non' => [ - 'diam', - 'elit', - 'in', - ], - ], - 'ipsum' => [ - 'dolor1', - 'dolor2', - ], - 'amet' => 'sit', - ], - ]; - } + // Positive cases + self::assertEquals($this->simpleArray, Arrays::ksortRecursive($this->simpleArray)); + self::assertEquals($this->simpleArray, Arrays::ksortRecursive($this->simpleArray, SORT_NUMERIC)); + self::assertEquals($this->twoDimensionsArray, Arrays::ksortRecursive($this->twoDimensionsArray)); - /** - * Provides patterns of keys or paths that matched will stop the process and the expected array for the - * getLastElementsPaths() method - * - * @return \Generator - */ - public function provideStopIfMatchedByForGetLastElementsPaths() - { - /* - * Special exception: do not use, stop recursive on the "diam" key - */ - yield[ - 'diam', - '.', - [ - 'ipsum.quis.vestibulum.porta-1.0' => 'turpis', - 'ipsum.quis.vestibulum.porta-1.1' => 'urna', - 'ipsum.quis.vestibulum.porta-2.tortor.in.0' => 'dui', - 'ipsum.quis.vestibulum.porta-2.tortor.in.dolor.0' => 'aliquam', - 'ipsum.quis.vestibulum.porta-3.0' => 1, - 'ipsum.quis.vestibulum.porta-3.1' => 2, - 'ipsum.quis.vestibulum.porta-3.2' => 3, - 'primis.0.0' => 'in', - 'primis.0.1' => 'faucibus', - 'primis.0.2' => 'orci', - 'primis.1.0' => 'luctus', - 'primis.1.1' => 'et', - 'primis.1.2' => 'ultrices', - ], - ]; - - /* - * Stop building of paths on these keys: - * - "tortor" - * - "primis" - */ - yield[ - [ - 'tortor', + // Positive case - multi-dimensions array + $effect = [ + 'amet' => [ + 'iaculis', 'primis', ], - ' . ', - [ - 'ipsum . quis . vestibulum . porta-1 . 0' => 'turpis', - 'ipsum . quis . vestibulum . porta-1 . 1' => 'urna', - 'ipsum . quis . vestibulum . porta-2 . tortor' => [ - 'in' => [ - 'dui', - 'dolor' => [ - 'aliquam', - ], + 'consectetur' => 'adipiscing', + 'lorem' => [ + 'ipsum' => [ + 'dolor' => 'sit', + 'diam' => [ + 'non' => 'egestas', ], ], - 'ipsum . quis . vestibulum . porta-3 . 0' => 1, - 'ipsum . quis . vestibulum . porta-3 . 1' => 2, - 'ipsum . quis . vestibulum . porta-3 . 2' => 3, - 'primis' => [ - [ - 'in', - 'faucibus', - 'orci', - ], - [ - 'luctus', - 'et', - 'ultrices', + ], + 'mollis' => 1234, + 'sit' => [ + 'nullam' => 'donec', + 'aliquet' => [ + 'vitae' => [ + 'ligula' => 'quis', ], ], + 'elit', + ], + 2 => [], + ]; + + self::assertEquals($effect, Arrays::ksortRecursive($this->complexArray)); + + // Positive case - multi-dimensions array - with options of ksort() function + $effect = [ + 2 => [], + 'amet' => [ + 'iaculis', + 'primis', + ], + 'consectetur' => 'adipiscing', + 'lorem' => [ + 'ipsum' => [ + 'dolor' => 'sit', + 'diam' => [ + 'non' => 'egestas', + ], + ], + ], + 'mollis' => 1234, + 'sit' => [ + 'nullam' => 'donec', + 'aliquet' => [ + 'vitae' => [ + 'ligula' => 'quis', + ], + ], + 'elit', ], ]; - /* - * Stop building of paths on more sophisticated keys - */ - yield[ - [ - 'porta\-\d+', - '^\d+$', - ], - ' > ', - [ - 'ipsum > quis > vestibulum > porta-1' => [ - 'turpis', - 'urna', - ], - 'ipsum > quis > vestibulum > porta-2' => [ - 'tortor' => [ - 'in' => [ - 'dui', - 'dolor' => [ - 'aliquam', - ], - ], - ], - ], - 'ipsum > quis > vestibulum > porta-3' => [ - 1, - 2, - 3, - ], - 'primis > 0' => [ - 'in', - 'faucibus', - 'orci', - ], - 'primis > 1' => [ - 'luctus', - 'et', - 'ultrices', - ], - ], - ]; + self::assertEquals($effect, Arrays::ksortRecursive($this->complexArray, SORT_STRING & SORT_NATURAL)); + } - /* - * Stop building of paths on these: - * - keys - * and - * - paths (verify paths too) - */ - yield[ - [ - 'porta-1', - 'porta-2 > tortor > in', - ], - ' > ', - [ - 'ipsum > quis > vestibulum > porta-1' => [ - 'turpis', - 'urna', - ], - 'ipsum > quis > vestibulum > porta-2 > tortor > in' => [ - 'dui', - 'dolor' => [ - 'aliquam', - ], - ], - 'ipsum > quis > vestibulum > porta-3 > 0' => 1, - 'ipsum > quis > vestibulum > porta-3 > 1' => 2, - 'ipsum > quis > vestibulum > porta-3 > 2' => 3, - 'primis > 0 > 0' => 'in', - 'primis > 0 > 1' => 'faucibus', - 'primis > 0 > 2' => 'orci', - 'primis > 1 > 0' => 'luctus', - 'primis > 1 > 1' => 'et', - 'primis > 1 > 2' => 'ultrices', - ], - ]; - - /* - * Stop building of paths on these paths (verify paths only) - */ - yield[ - [ - 'ipsum > quis > vestibulum > porta-1', - 'ipsum > quis > vestibulum > porta-2 > tortor', - 'primis > 1', - ], - ' > ', - [ - 'ipsum > quis > vestibulum > porta-1' => [ - 'turpis', - 'urna', - ], - 'ipsum > quis > vestibulum > porta-2 > tortor' => [ - 'in' => [ - 'dui', - 'dolor' => [ - 'aliquam', - ], - ], - ], - 'ipsum > quis > vestibulum > porta-3 > 0' => 1, - 'ipsum > quis > vestibulum > porta-3 > 1' => 2, - 'ipsum > quis > vestibulum > porta-3 > 2' => 3, - 'primis > 0 > 0' => 'in', - 'primis > 0 > 1' => 'faucibus', - 'primis > 0 > 2' => 'orci', - 'primis > 1' => [ - 'luctus', - 'et', - 'ultrices', - ], - ], - ]; - - /* - * Stop building of paths if path contains any of these part (verify part of paths only) - */ - yield[ - [ - 'vestibulum > porta-1', - 'tortor > in', - '[a-z]+ > \d+', - ], - ' > ', - [ - 'ipsum > quis > vestibulum > porta-1' => [ - 'turpis', - 'urna', - ], - 'ipsum > quis > vestibulum > porta-2 > tortor > in' => [ - 'dui', - 'dolor' => [ - 'aliquam', - ], - ], - 'ipsum > quis > vestibulum > porta-3 > 0' => 1, - 'ipsum > quis > vestibulum > porta-3 > 1' => 2, - 'ipsum > quis > vestibulum > porta-3 > 2' => 3, - 'primis > 0' => [ - 'in', - 'faucibus', - 'orci', - ], - 'primis > 1' => [ - 'luctus', - 'et', - 'ultrices', - ], - ], - ]; + public function testMakeArray(): void + { + self::assertSame($this->simpleArray, Arrays::makeArray($this->simpleArray)); + self::assertSame(['test'], Arrays::makeArray('test')); } /** - * Provide values to filter and get non-empty values + * @param string $description Description of test case + * @param null|array $expected Expected new array (with quoted elements) + * @param array $array The array to check for string values * - * @return \Generator + * @dataProvider provideArrayToQuoteStrings */ - public function provideValuesToFilterNonEmpty() + public function testQuoteStrings(string $description, ?array $expected, array $array): void { - $simpleObject = new SimpleToString('1234'); + self::assertSame($expected, Arrays::quoteStrings($array), $description); + } - yield[ - 'All values are empty', - [ - '', - null, - [], - ], - [], - ]; + public function testRemoveElement() + { + self::assertFalse(Arrays::removeElement($this->simpleArray, 'eeee')); + self::assertTrue(is_array(Arrays::removeElement($this->simpleArray, 'Lorem'))); - yield[ - '5 values with 2 empty strings', - [ - 'test 1', - '', - 'test 2', - 'test 3', - '', - ], - [ - 0 => 'test 1', - 2 => 'test 2', - 3 => 'test 3', - ], - ]; + Arrays::removeElement($this->simpleArray, 'amet'); + self::assertFalse(isset($this->simpleArray['amet'])); + } - yield[ - '"0" shouldn\'t be treated like an empty value', - [ - 123, - 0, - 456, - ], - [ - 123, - 0, - 456, - ], - ]; + public function testRemoveElements(): void + { + $array1 = $this->simpleArray; + $array2 = $this->simpleArray; - yield[ - 'Object shouldn\'t be treated like an empty value', - [ - 'test 1', - $simpleObject, - 'test 2', - null, - 'test 3', - ], - [ - 0 => 'test 1', - 1 => $simpleObject, - 2 => 'test 2', - 4 => 'test 3', - ], - ]; + Arrays::removeElements($array1, 'ipsum'); + self::assertSame([ + 1 => 'ipsum', + 2 => 'dolor', + 3 => 'sit', + 4 => 'amet', + ], $array1); - yield[ - 'Mixed values (non-empty, empty, strings, integers, objects)', - [ - 'test 1', - '', - 123, - null, - 'test 2', - 'test 3', - 0, - $simpleObject, - 456, - [], - $simpleObject, - ], - [ - 0 => 'test 1', - 2 => 123, - 4 => 'test 2', - 5 => 'test 3', - 6 => 0, - 7 => $simpleObject, - 8 => 456, - 10 => $simpleObject, - ], - ]; + Arrays::removeElements($array2, 'sit', false); + self::assertSame([ + 0 => 'Lorem', + 1 => 'ipsum', + 2 => 'dolor', + 3 => 'sit', + ], $array2); + + Arrays::removeElements($this->complexArray['lorem'], 'sit', false); + self::assertSame(['ipsum' => ['dolor' => 'sit']], $this->complexArray['lorem']); } /** - * Provide values to filter and get non-empty values concatenated by default separator + * @param string $description Description of test case + * @param array $array The array which should be shortened + * @param bool $last If is set to true, last element is removed (default behaviour). Otherwise - first. + * @param null|array $expected Expected result * - * @return \Generator + * @dataProvider provideArrayToRemoveMarginalElement */ - public function provideValuesToFilterNonEmptyAsStringUsingDefaultSeparator() + public function testRemoveMarginalElement(string $description, array $array, bool $last, ?array $expected): void { - yield[ - 'An empty array (no values to filter)', - [], - null, - ]; - - yield[ - 'All values are empty', - [ - '', - null, - [], - ], - '', - ]; - - yield[ - '5 values with 2 empty strings', - [ - 'test 1', - '', - 'test 2', - 'test 3', - '', - ], - 'test 1, test 2, test 3', - ]; - - yield[ - 'Numbers with "0" that shouldn\'t be treated like an empty value', - [ - 123, - 0, - 456, - ], - '123, 0, 456', - ]; - - yield[ - 'Object shouldn\'t be treated like an empty value', - [ - 'test 1', - new SimpleToString('1234'), - 'test 2', - null, - 'test 3', - ], - 'test 1, Instance with ID: 1234, test 2, test 3', - ]; - - yield[ - 'Mixed values (non-empty, empty, strings, integers, objects)', - [ - 'test 1', - '', - 123, - null, - 'test 2', - 'test 3', - 0, - new SimpleToString('A1XC90Z'), - 456, - [], - new SimpleToString('FF-45-0Z'), - ], - 'test 1, 123, test 2, test 3, 0, Instance with ID: A1XC90Z, 456, Instance with ID: FF-45-0Z', - ]; + self::assertSame($expected, Arrays::removeMarginalElement($array, $last), $description); } /** - * Provide values to filter and get non-empty values concatenated by given separator + * @param string $description Description of test case + * @param array $array Array which keys should be replaced + * @param string $oldKeyPattern Regular expression of the old key + * @param string $newKey Name of the new key + * @param array $expected Expected result * - * @return \Generator + * @dataProvider provideArrayToReplaceKeys */ - public function provideValuesToFilterNonEmptyAsString() - { - yield[ - 'An empty array (no values to filter)', - [], - ' | ', - null, - ]; - - yield[ - 'All values are empty', - [ - '', - null, - [], - ], - ' | ', - '', - ]; - - yield[ - '5 values with 2 empty strings', - [ - 'test 1', - '', - 'test 2', - 'test 3', - '', - ], - ' | ', - 'test 1 | test 2 | test 3', - ]; - - yield[ - 'Numbers with "0" that shouldn\'t be treated like an empty value', - [ - 123, - 0, - 456, - ], - ' <-> ', - '123 <-> 0 <-> 456', - ]; - - yield[ - 'Object shouldn\'t be treated like an empty value', - [ - 'test 1', - new SimpleToString('1234'), - 'test 2', - null, - 'test 3', - ], - ' | ', - 'test 1 | Instance with ID: 1234 | test 2 | test 3', - ]; - - yield[ - 'Mixed values (non-empty, empty, strings, integers, objects)', - [ - 'test 1', - '', - 123, - null, - 'test 2', - 'test 3', - 0, - new SimpleToString('A1XC90Z'), - 456, - [], - new SimpleToString('FF-45-0Z'), - ], - ';', - 'test 1;123;test 2;test 3;0;Instance with ID: A1XC90Z;456;Instance with ID: FF-45-0Z', - ]; + public function testReplaceKeys( + string $description, + array $array, + string $oldKeyPattern, + string $newKey, + ?array $expected + ): void { + self::assertSame($expected, Arrays::replaceKeys($array, $oldKeyPattern, $newKey), $description); } - public function provideArrayValuesKeysConverted2string() + /** + * @param array $array The array to change values with keys + * @param array $replaced The array with replaced values with keys + * + * @dataProvider provideArrayWithDuplicatedValuesToSetKeysAsValues + */ + public function testSetKeysAsValuesDuplicatedValues($array, $replaced) { - yield[ - 'An empty array', - null, - [], - ]; - - yield[ - 'Empty string and null as value', - 'test_1=,test_2=,test_3=3', - [ - 'test_1' => null, - 'test_2' => '', - 'test_3' => '3', - ], - ]; - - yield[ - 'Empty string and null as value (with custom separators)', - 'test_1="" test_2="" test_3="3"', - [ - 'test_1' => null, - 'test_2' => '', - 'test_3' => '3', - ], - ' ', - '=', - '"', - ]; - - yield[ - 'Empty string as key', - '1=test_1,=test_2,3=test_3', - [ - 1 => 'test_1', - '' => 'test_2', - '3' => 'test_3', - ], - ]; - - yield[ - 'Empty string as key (with custom separators)', - '1 => "test_1"; => "test_2"; 3 => "test_3"', - [ - 1 => 'test_1', - '' => 'test_2', - '3' => 'test_3', - ], - '; ', - ' => ', - '"', - ]; - - yield[ - 'Mixed types of keys and values', - 'test_1=test test,test_2=2,test_3=3.45', - [ - 'test_1' => 'test test', - 'test_2' => 2, - 'test_3' => 3.45, - ], - ]; - - yield[ - 'Mixed types of keys and values (with custom separators)', - 'test_1 --> *test test* | test_2 --> *2* | test_3 --> *3.45*', - [ - 'test_1' => 'test test', - 'test_2' => 2, - 'test_3' => 3.45, - ], - ' | ', - ' --> ', - '*', - ]; + self::assertEquals($replaced, Arrays::setKeysAsValues($array, false)); } - public function provideArrayValues2csv() + public function testSetKeysAsValuesEmptyArray() { - yield[ - 'An empty array', - null, - [], - ]; - - yield[ - 'Empty string, and empty array and null as row', - "1,2,3\n5,6,", - [ - 'test_1' => '', - 'test_2' => [], - 'test_3' => null, - 'test_4' => [ - 'aa' => 1, - 'bb' => 2, - 'cc' => 3, - ], - [ - 'dd' => 5, - 'ee' => 6, - 'ff' => '', - ], - ], - ]; - - yield[ - 'Empty string, and empty array and null as row (with custom separator)', - "1, 2, 3\n5, 6, ", - [ - 'test_1' => '', - 'test_2' => [], - 'test_3' => null, - 'test_4' => [ - 'aa' => 1, - 'bb' => 2, - 'cc' => 3, - ], - [ - 'dd' => 5, - 'ee' => 6, - 'ff' => '', - ], - ], - ', ', - ]; - - yield[ - 'Empty string as key, non-array as value', - "1,2,3\n5,6,", - [ - '' => 'test_1', - 1 => 'test_2', - '3' => [ - 'aa' => 1, - 'bb' => 2, - 'cc' => 3, - ], - [ - 'dd' => 5, - 'ee' => 6, - 'ff' => '', - ], - ], - ]; - - yield[ - 'Empty string as key, non-array as value (with custom separator)', - "1 | 2 | 3\n5 | 6 | ", - [ - '' => 'test_1', - 1 => 'test_2', - '3' => [ - 'aa' => 1, - 'bb' => 2, - 'cc' => 3, - ], - [ - 'dd' => 5, - 'ee' => 6, - 'ff' => '', - ], - ], - ' | ', - ]; - - yield[ - 'Invalid structure, not like database table', - "1,2,3\n5,6\n7,8,9,10", - [ - [ - 'aa' => 1, - 'bb' => 2, - 'cc' => 3, - ], - [ - 'dd' => 5, - 'ee' => 6, - ], - [ - 7, - 8, - 9, - 10, - ], - ], - ]; - - yield[ - 'Invalid structure, not like database table (with custom separator)', - "1 <-> 2 <-> 3\n5 <-> 6\n7 <-> 8 <-> 9 <-> 10", - [ - [ - 'aa' => 1, - 'bb' => 2, - 'cc' => 3, - ], - [ - 'dd' => 5, - 'ee' => 6, - ], - [ - 7, - 8, - 9, - 10, - ], - ], - ' <-> ', - ]; - - yield[ - 'Mixed types of keys and values', - "1,2,3.45\n5,6,\n7,8,9,,10", - [ - [ - 'aa' => 1, - 'bb' => 2, - 'cc' => 3.45, - ], - [ - 'dd' => 5, - 'ee' => 6, - null, - ], - [ - 7, - 8, - 'qq' => 9, - '', - 10, - ], - ], - ]; - - yield[ - 'Mixed types of keys and values (with custom separator)', - "1 // 2 // 3.45\n5 // 6 // \n7 // 8 // 9 // // 10", - [ - [ - 'aa' => 1, - 'bb' => 2, - 'cc' => 3.45, - ], - [ - 'dd' => 5, - 'ee' => 6, - null, - ], - [ - 7, - 8, - 'qq' => 9, - '', - 10, - ], - ], - ' // ', - ]; + self::assertNull(Arrays::setKeysAsValues([])); } - public function provideArrayValues2string() + public function testSetKeysAsValuesSameKeysValues() { - yield[ - 'An empty array', - null, - [], - ]; - - yield[ - 'Simple array', - 'Test 1,Test 2,Test 3', - [ - 1 => 'Test 1', - 2 => 'Test 2', - 3 => 'Test 3', - ], - ]; - - yield[ - 'Simple array (with custom separator)', - 'Test 1.Test 2.Test 3', - [ - 1 => 'Test 1', - 2 => 'Test 2', - 3 => 'Test 3', - ], - '', - '.', - ]; - - yield[ - 'Simple array (concrete column)', - 'Test 2', - [ - 1 => 'Test 1', - 2 => 'Test 2', - 3 => 'Test 3', - ], - 2, - ]; - - yield[ - 'Simple array (concrete column with custom separator)', - 'Test 2', - [ - 1 => 'Test 1', - 2 => 'Test 2', - 3 => 'Test 3', - ], - 2, - '.', - ]; - - yield[ - 'Complex array', - '1,2,3,test 1,test 2,test 3,,test 4,,bbb,3.45', - [ - [ - 1, - 2, - 3, - ], - [ - 'test 1', - 'test 2', - [ - 'test 3', - '', - 'test 4', - ], - ], - [], - [ - 'a' => '', - 'b' => 'bbb', - [], - 'c' => 3.45, - ], - ], - ]; - - yield[ - '1st complex array (concrete column)', - '2,test 2,', - [ - [ - 1, - 2, - 3, - ], - [ - 'test 1', - 'test 2', - [ - 'test 3', - '', - 'test 4', - ], - ], - [], - [ - 'a' => '', - 'b' => 'bbb', - [], - 'c' => 3.45, - ], - ], + $array = [ + 0, 1, + 2, + 3, ]; - yield[ - '2nd complex array (concrete column)', - 'bb,1234,0xb', - [ - [ - 1, - 2, - 3, - ], - [ - 'a' => 'aa', - 'b' => 'bb', - 'c' => 'cc', - ], - [ - 'a', - 'b', - 'c', - ], - [ - 'a' => '', - 'b' => 1234, - ], - [ - 'c' => 5678, - 'b' => '0xb', - ], - ], - 'b', - ]; - - yield[ - '3rd complex array (concrete column with custom separator)', - 'bb - 1234 - 3xb - bbb', - [ - [ - 1, - 2, - 3, - ], - [ - 'a' => 'aa', - 'b' => 'bb', - 'c' => 'cc', - ], - [ - 'a', - 'b' => [], - 'c', - ], - [ - 'a' => '', - 'b' => 1234, - ], - [ - 'c' => 5678, - 'b' => [ - 'b1' => '0xb', - 'b2' => '1xb', - 'b' => '3xb', - ], - [ - 1, - 2, - 'a' => 'aaa', - 'b' => 'bbb', - ], - ], - ], - 'b', - ' - ', - ]; + self::assertEquals($array, Arrays::setKeysAsValues($array)); } - public function provideArrayToQuoteStrings() + /** + * @param array $array The array to change values with keys + * @param array $replaced The array with replaced values with keys + * + * @dataProvider provideSimpleArrayToSetKeysAsValues + */ + public function testSetKeysAsValuesSimpleArray($array, $replaced) { - yield[ - 'An empty array', - null, - [], - ]; + self::assertEquals($replaced, Arrays::setKeysAsValues($array)); + } - yield[ - 'Simple array', + public function testSetKeysAsValuesTwoDimensionsArray() + { + $replaced = [ [ - 1, - 2, - 3, - '\'1\'', - '\'2\'', + 'lorem' => 0, + 'ipsum' => 1, + 'dolor' => 2, + 'sit' => 3, + 'amet' => 4, ], [ - 1, - 2, - 3, - '1', - '2', + 'consectetur' => 0, + 'adipiscing' => 1, + 'elit' => 2, + ], + [ + 'donec' => 0, + 'sagittis' => 1, + 'fringilla' => 2, + 'eleifend' => 3, ], ]; - yield[ - 'Complex array', - [ - 123, - '\'456\'', - [ - 'x' => [ - 0, - '\'0\'', - 1 => '\'1\'', - 2 => 2, - ], - '\'y\'', - ], - 444 => '\'\'', - [ - [ - [ - '\'test\'', - ], - ], - ], + self::assertEquals($replaced, Arrays::setKeysAsValues($this->twoDimensionsArray)); + } + + public function testSetPositions() + { + // Negative cases + self::assertNull(Arrays::setPositions([])); + + // Positive case - 1-dimension array + $array = [ + 'abc', + ]; + + self::assertEquals($array, Arrays::setPositions($array)); + + // Positive case - 2-dimensions array + $effect = $this->twoDimensionsArray; + $effect[0][Arrays::POSITION_KEY_NAME] = 1; + $effect[1][Arrays::POSITION_KEY_NAME] = 2; + $effect[2][Arrays::POSITION_KEY_NAME] = 3; + + self::assertEquals($effect, Arrays::setPositions($this->twoDimensionsArray)); + + // Positive case - multidimensional array + $array = [ + 'lorem', + 'ipsum' => [ + 'dolor', + 'sit', ], - [ - 123, - '456', - [ - 'x' => [ - 0, - '0', - 1 => '1', - 2 => 2, - ], - 'y', - ], - 444 => '', - [ - [ - [ - 'test', - ], + 'amet' => [ + 'consectetur', + 'adipiscing' => [ + 'elit' => [ + 'cras', + 'quis', + 'ligula', ], ], ], ]; + + $effect = [ + 'lorem', + 'ipsum' => [ + 'dolor', + 'sit', + Arrays::POSITION_KEY_NAME => 1, + ], + 'amet' => [ + 'consectetur', + 'adipiscing' => [ + 'elit' => [ + 'cras', + 'quis', + 'ligula', + Arrays::POSITION_KEY_NAME => 1, + ], + Arrays::POSITION_KEY_NAME => 1, + ], + Arrays::POSITION_KEY_NAME => 2, + ], + ]; + + self::assertEquals($effect, Arrays::setPositions($array)); + + // Positive case - non-default name of key with position value & 2-dimensions array + $keyName = 'level'; + $effect = $this->twoDimensionsArray; + + $effect[0][$keyName] = 1; + $effect[1][$keyName] = 2; + $effect[2][$keyName] = 3; + + self::assertEquals($effect, Arrays::setPositions($this->twoDimensionsArray, $keyName)); + + // Positive case - non-default start value of position & 2-dimensions array + $startPosition = 5; + $effect = $this->twoDimensionsArray; + + $effect[Arrays::POSITION_KEY_NAME] = $startPosition; + $effect[0][Arrays::POSITION_KEY_NAME] = 1; + $effect[1][Arrays::POSITION_KEY_NAME] = 2; + $effect[2][Arrays::POSITION_KEY_NAME] = 3; + + self::assertEquals($effect, Arrays::setPositions($this->twoDimensionsArray, Arrays::POSITION_KEY_NAME, $startPosition)); + } + + public function testSortByCustomKeysOrder() + { + // Negative cases + self::assertNull(Arrays::sortByCustomKeysOrder([], [])); + + // Positive cases + self::assertEquals([0], Arrays::sortByCustomKeysOrder([0], [])); + self::assertEquals($this->simpleArray, Arrays::sortByCustomKeysOrder($this->simpleArray, [])); + + $keysOrder = [ + 'amet', + 'dolor', + 'Lorem', + ]; + + $sorted = [ + 'dolor' => 'sit', + 'amet' => 'consectetur', + 'Lorem' => 'ipsum', + ]; + + self::assertEquals($sorted, Arrays::sortByCustomKeysOrder($this->simpleArrayWithKeys, $keysOrder)); + + $array = [ + 'Lorem', + 'ipsum', + 'dolor', + 'sit', + 'amet', + ]; + + $keysOrder = [ + 0, + 3, + 1, + 2, + ]; + + $sorted = [ + 0 => 'Lorem', + 3 => 'sit', + 1 => 'ipsum', + 2 => 'dolor', + 4 => 'amet', + ]; + + self::assertEquals($sorted, Arrays::sortByCustomKeysOrder($array, $keysOrder)); + + $array = [ + 'Lorem', + 'ipsum', + 'dolor', + 'sit', + 'amet', + ]; + + $keysOrder = [ + 0, + 3, + ]; + + $sorted = [ + 0 => 'Lorem', + 3 => 'sit', + 1 => 'ipsum', + 2 => 'dolor', + 4 => 'amet', + ]; + + self::assertEquals($sorted, Arrays::sortByCustomKeysOrder($array, $keysOrder)); + } + + public function testString2array(): void + { + // Negative cases + self::assertNull(Arrays::string2array('')); + + // Positive cases + $array = [ + 'light' => '#fff', + 'dark' => '#000', + ]; + + self::assertEquals($array, Arrays::string2array('light:#fff|dark:#000')); + self::assertEquals($array, Arrays::string2array('light: #fff | dark: #000')); + + $array = [ + 'red' => '#f00', + 'green' => '#0f0', + 'blue' => '#00f', + ]; + + self::assertEquals($array, Arrays::string2array('red:#f00|green:#0f0|blue:#00f')); + self::assertEquals($array, Arrays::string2array('red: #f00 | green: #0f0 | blue: #00f')); + self::assertEquals($array, Arrays::string2array('red : #f00 | green : #0f0 | blue : #00f')); + } + + public function testTrimRecursive() + { + // Negative cases + self::assertSame([], Arrays::trimRecursive([])); + + // Positive cases + self::assertEquals(['a'], Arrays::trimRecursive([' a '])); + self::assertEquals([ + 'a', + 'b', + ], Arrays::trimRecursive([ + ' a ', + 'b ', + ])); + + $array = [ + 'abc ', + [ + 'def', + 'ghi ', + ], + ]; + + $result = [ + 'abc', + [ + 'def', + 'ghi', + ], + ]; + + self::assertEquals($result, Arrays::trimRecursive($array)); + + $array = [ + 'abc ', + [ + ' def ', + 'ghi ', + 'oo' => [ + ' ee ', + 'g' => 'h hhh ', + ], + ], + ]; + + $result = [ + 'abc', + [ + 'def', + 'ghi', + 'oo' => [ + 'ee', + 'g' => 'h hhh', + ], + ], + ]; + + self::assertEquals($result, Arrays::trimRecursive($array)); + } + + /** + * @param string $description Description of test + * @param string $expected Expected array converted to csv string + * @param array $array Data to be converted. It have to be an array that represents database table. + * @param string $separator (optional) Separator used between values. Default: ",". + * + * @dataProvider provideArrayValues2csv + */ + public function testValues2csv(string $description, ?string $expected, array $array, string $separator = ','): void + { + // Required to avoid failure: + // + // Failed asserting that two strings are identical + // 1,2,3.45 - expected + // 1,2,3,45 - actual + Locale::setLocale(LC_ALL, 'en', 'US'); + + static::assertSame($expected, Arrays::values2csv($array, $separator), $description); + } + + /** + * @param string $description Description of test + * @param string $expected Expected array converted to string + * @param array $array Data to be converted + * @param string $arrayColumnKey (optional) Column name. Default: "". + * @param string $separator (optional) Separator used between values. Default: ",". + * + * @dataProvider provideArrayValues2string + */ + public function testValues2string($description, $expected, array $array, $arrayColumnKey = '', $separator = ',') + { + // Required to avoid failure: + // + // Failed asserting that two strings are identical + // 1,2,3,test 1,test 2,test 3,,test 4,,bbb,3.45 - expected + // 1,2,3,test 1,test 2,test 3,,test 4,,bbb,3,45 - actual + Locale::setLocale(LC_ALL, 'en', 'US'); + + self::assertSame($expected, Arrays::values2string($array, $arrayColumnKey, $separator), $description); + } + + /** + * @param string $description Description of test + * @param string $expected Expected array converted to string + * @param array $array Data to be converted + * @param string $separator (optional) Separator used between name-value pairs. Default: ",". + * @param string $valuesKeysSeparator (optional) Separator used between name and value. Default: "=". + * @param string $valuesWrapper (optional) Wrapper used to wrap values, e.g. double-quote: key="value". + * Default: "". + * + * @dataProvider provideArrayValuesKeysConverted2string + */ + public function testValuesKeys2string( + $description, + $expected, + array $array, + $separator = ',', + $valuesKeysSeparator = '=', + $valuesWrapper = '' + ) { + // Required to avoid failure: + // + // Failed asserting that two strings are identical + // test_1=test test,test_2=2,test_3=3.45 - expected + // test_1=test test,test_2=2,test_3=3,45 - actual + Locale::setLocale(LC_ALL, 'en', 'US'); + + self::assertSame( + $expected, + Arrays::valuesKeys2string($array, $separator, $valuesKeysSeparator, $valuesWrapper), + $description + ); + + self::assertSame( + '0=Lorem,1=ipsum,2=dolor,3=sit,4=amet', + Arrays::valuesKeys2string($this->simpleArray), + 'Simple array' + ); + + self::assertSame( + '0=Lorem;1=ipsum;2=dolor;3=sit;4=amet', + Arrays::valuesKeys2string($this->simpleArray, ';'), + 'Simple array (with custom separator)' + ); + + self::assertSame( + '0=Lorem 1=ipsum 2=dolor 3=sit 4=amet', + Arrays::valuesKeys2string($this->simpleArray, ' '), + 'Simple array (with custom separator)' + ); + + self::assertSame( + '0="Lorem" 1="ipsum" 2="dolor" 3="sit" 4="amet"', + Arrays::valuesKeys2string($this->simpleArray, ' ', '=', '"'), + 'Simple array (with custom separators)' + ); + + self::assertSame( + '0="Lorem", 1="ipsum", 2="dolor", 3="sit", 4="amet"', + Arrays::valuesKeys2string($this->simpleArray, ', ', '=', '"'), + 'Simple array (with custom separators)' + ); } /** * {@inheritdoc} */ - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -2698,7 +3916,7 @@ letsTest[2] = value_2;'; $this->simpleArrayWithKeys = [ 'Lorem' => 'ipsum', 'dolor' => 'sit', - 'amet' => 'consectetur', + 'amet' => 'consectetur', ]; $this->twoDimensionsArray = [ @@ -2723,19 +3941,19 @@ letsTest[2] = value_2;'; ]; $this->complexArray = [ - 'lorem' => [ + 'lorem' => [ 'ipsum' => [ 'dolor' => 'sit', - 'diam' => [ + 'diam' => [ 'non' => 'egestas', ], ], ], 'consectetur' => 'adipiscing', - 'mollis' => 1234, - 2 => [], - 'sit' => [ - 'nullam' => 'donec', + 'mollis' => 1234, + 2 => [], + 'sit' => [ + 'nullam' => 'donec', 'aliquet' => [ 'vitae' => [ 'ligula' => 'quis', @@ -2743,14 +3961,14 @@ letsTest[2] = value_2;'; ], 'elit', ], - 'amet' => [ + 'amet' => [ 'iaculis', 'primis', ], ]; $this->superComplexArray = [ - 'ipsum' => [ + 'ipsum' => [ 'quis' => [ 'vestibulum' => [ 'porta-1' => [ @@ -2793,7 +4011,7 @@ letsTest[2] = value_2;'; /** * {@inheritdoc} */ - protected function tearDown() + protected function tearDown(): void { parent::tearDown(); diff --git a/tests/Utilities/Bootstrap4CssSelectorTest.php b/tests/Utilities/Bootstrap4CssSelectorTest.php index ee6787d..98fe708 100644 --- a/tests/Utilities/Bootstrap4CssSelectorTest.php +++ b/tests/Utilities/Bootstrap4CssSelectorTest.php @@ -8,6 +8,7 @@ namespace Meritoo\Test\Common\Utilities; +use Generator; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Utilities\Bootstrap4CssSelector; @@ -16,27 +17,147 @@ use Meritoo\Common\Utilities\Bootstrap4CssSelector; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\Bootstrap4CssSelector */ class Bootstrap4CssSelectorTest extends BaseTestCase { + /** + * Provides name of form and expected selector + * + * @return Generator + */ + public function provideFormNameAndSelector() + { + yield [ + 'test', + 'form[name="test"] .form-group', + ]; + + yield [ + 'test-123-test-456', + 'form[name="test-123-test-456"] .form-group', + ]; + + yield [ + 'test_something_098_different', + 'form[name="test_something_098_different"] .form-group', + ]; + } + + /** + * Provides name of form, name of field and expected selector + * + * @return Generator + */ + public function provideFormNameFieldNameAndSelector() + { + yield [ + 'test', + 'test', + 'form[name="test"] label[for="test"] .invalid-feedback .form-error-message', + ]; + + yield [ + 'test-123-test-456', + 'great-000-field', + 'form[name="test-123-test-456"] label[for="great-000-field"] .invalid-feedback .form-error-message', + ]; + + yield [ + 'test_something_098_different', + 'this-is-the-123789-field', + 'form[name="test_something_098_different"] label[for="this-is-the-123789-field"] .invalid-feedback .form-error-message', + ]; + } + + /** + * Provides name of form, index/position of the field-set and expected selector + * + * @return Generator + */ + public function provideFormNameFieldSetIndexAndSelector() + { + yield [ + 'test', + 0, + 'form[name="test"] fieldset:nth-of-type(0) legend.col-form-label .invalid-feedback .form-error-message', + ]; + + yield [ + 'test-123-test-456', + 1, + 'form[name="test-123-test-456"] fieldset:nth-of-type(1) legend.col-form-label .invalid-feedback .form-error-message', + ]; + + yield [ + 'test_something_098_different', + 1245, + 'form[name="test_something_098_different"] fieldset:nth-of-type(1245) legend.col-form-label .invalid-feedback .form-error-message', + ]; + } + public function testConstructor() { static::assertHasNoConstructor(Bootstrap4CssSelector::class); } + public function testGetFieldErrorContainerSelector() + { + static::assertSame('.invalid-feedback .form-error-message', Bootstrap4CssSelector::getFieldErrorContainerSelector()); + } + + /** + * @param string $formName Name of form (value of the "name" attribute) + * @param string $fieldName Name of field (value of the "name" attribute) + * @param string $expected Expected selector + * + * @dataProvider provideFormNameFieldNameAndSelector + */ + public function testGetFieldErrorSelector($formName, $fieldName, $expected) + { + static::assertSame($expected, Bootstrap4CssSelector::getFieldErrorSelector($formName, $fieldName)); + } + + /** + * @param string $emptyValue Name of field (value of the "name" attribute) + * @dataProvider provideEmptyScalarValue + */ + public function testGetFieldErrorSelectorUsingEmptyFieldName($emptyValue) + { + $formName = 'test'; + static::assertSame('', Bootstrap4CssSelector::getFieldErrorSelector($formName, $emptyValue)); + } + /** * @param string $emptyValue Name of form (value of the "name" attribute) * @dataProvider provideEmptyScalarValue */ - public function testGetRadioButtonErrorSelectorUsingEmptyFormName($emptyValue) + public function testGetFieldErrorSelectorUsingEmptyFormName($emptyValue) { - $fieldSetIndex = 1; - static::assertSame('', Bootstrap4CssSelector::getRadioButtonErrorSelector($emptyValue, $fieldSetIndex)); + $fieldName = 'test'; + static::assertSame('', Bootstrap4CssSelector::getFieldErrorSelector($emptyValue, $fieldName)); } - public function testGetRadioButtonErrorSelectorUsingNegativeFieldSetIndex() + /** + * @param string $formName Name of form (value of the "name" attribute) + * @param string $expected Expected selector + * + * @dataProvider provideFormNameAndSelector + */ + public function testGetFieldGroupSelector($formName, $expected) { - static::assertSame('', Bootstrap4CssSelector::getRadioButtonErrorSelector('test-test', -1)); + static::assertSame($expected, Bootstrap4CssSelector::getFieldGroupSelector($formName)); + } + + /** + * @param string $emptyValue Name of form (value of the "name" attribute) + * @dataProvider provideEmptyScalarValue + */ + public function testGetFieldGroupSelectorUsingEmptyFormName($emptyValue) + { + static::assertSame('', Bootstrap4CssSelector::getFieldGroupSelector($emptyValue)); } /** @@ -55,131 +176,14 @@ class Bootstrap4CssSelectorTest extends BaseTestCase * @param string $emptyValue Name of form (value of the "name" attribute) * @dataProvider provideEmptyScalarValue */ - public function testGetFieldErrorSelectorUsingEmptyFormName($emptyValue) + public function testGetRadioButtonErrorSelectorUsingEmptyFormName($emptyValue) { - $fieldName = 'test'; - static::assertSame('', Bootstrap4CssSelector::getFieldErrorSelector($emptyValue, $fieldName)); + $fieldSetIndex = 1; + static::assertSame('', Bootstrap4CssSelector::getRadioButtonErrorSelector($emptyValue, $fieldSetIndex)); } - /** - * @param string $emptyValue Name of field (value of the "name" attribute) - * @dataProvider provideEmptyScalarValue - */ - public function testGetFieldErrorSelectorUsingEmptyFieldName($emptyValue) + public function testGetRadioButtonErrorSelectorUsingNegativeFieldSetIndex() { - $formName = 'test'; - static::assertSame('', Bootstrap4CssSelector::getFieldErrorSelector($formName, $emptyValue)); - } - - /** - * @param string $formName Name of form (value of the "name" attribute) - * @param string $fieldName Name of field (value of the "name" attribute) - * @param string $expected Expected selector - * - * @dataProvider provideFormNameFieldNameAndSelector - */ - public function testGetFieldErrorSelector($formName, $fieldName, $expected) - { - static::assertSame($expected, Bootstrap4CssSelector::getFieldErrorSelector($formName, $fieldName)); - } - - /** - * @param string $emptyValue Name of form (value of the "name" attribute) - * @dataProvider provideEmptyScalarValue - */ - public function testGetFieldGroupSelectorUsingEmptyFormName($emptyValue) - { - static::assertSame('', Bootstrap4CssSelector::getFieldGroupSelector($emptyValue)); - } - - /** - * @param string $formName Name of form (value of the "name" attribute) - * @param string $expected Expected selector - * - * @dataProvider provideFormNameAndSelector - */ - public function testGetFieldGroupSelector($formName, $expected) - { - static::assertSame($expected, Bootstrap4CssSelector::getFieldGroupSelector($formName)); - } - - public function testGetFieldErrorContainerSelector() - { - static::assertSame('.invalid-feedback .form-error-message', Bootstrap4CssSelector::getFieldErrorContainerSelector()); - } - - /** - * Provides name of form, index/position of the field-set and expected selector - * - * @return \Generator - */ - public function provideFormNameFieldSetIndexAndSelector() - { - yield[ - 'test', - 0, - 'form[name="test"] fieldset:nth-of-type(0) legend.col-form-label .invalid-feedback .form-error-message', - ]; - - yield[ - 'test-123-test-456', - 1, - 'form[name="test-123-test-456"] fieldset:nth-of-type(1) legend.col-form-label .invalid-feedback .form-error-message', - ]; - - yield[ - 'test_something_098_different', - 1245, - 'form[name="test_something_098_different"] fieldset:nth-of-type(1245) legend.col-form-label .invalid-feedback .form-error-message', - ]; - } - - /** - * Provides name of form, name of field and expected selector - * - * @return \Generator - */ - public function provideFormNameFieldNameAndSelector() - { - yield[ - 'test', - 'test', - 'form[name="test"] label[for="test"] .invalid-feedback .form-error-message', - ]; - - yield[ - 'test-123-test-456', - 'great-000-field', - 'form[name="test-123-test-456"] label[for="great-000-field"] .invalid-feedback .form-error-message', - ]; - - yield[ - 'test_something_098_different', - 'this-is-the-123789-field', - 'form[name="test_something_098_different"] label[for="this-is-the-123789-field"] .invalid-feedback .form-error-message', - ]; - } - - /** - * Provides name of form and expected selector - * - * @return \Generator - */ - public function provideFormNameAndSelector() - { - yield[ - 'test', - 'form[name="test"] .form-group', - ]; - - yield[ - 'test-123-test-456', - 'form[name="test-123-test-456"] .form-group', - ]; - - yield[ - 'test_something_098_different', - 'form[name="test_something_098_different"] .form-group', - ]; + static::assertSame('', Bootstrap4CssSelector::getRadioButtonErrorSelector('test-test', -1)); } } diff --git a/tests/Utilities/BundleTest.php b/tests/Utilities/BundleTest.php index 92dd655..13a74ac 100644 --- a/tests/Utilities/BundleTest.php +++ b/tests/Utilities/BundleTest.php @@ -18,14 +18,176 @@ use Meritoo\Common\Utilities\Bundle; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\Bundle */ class BundleTest extends BaseTestCase { + /** + * Provides empty path of the view / template and/or name of bundle + * + * @return Generator + */ + public function provideEmptyViewPathAndBundle() + { + yield [ + '', + '', + ]; + + yield [ + 'test', + '', + ]; + + yield [ + '', + 'test', + ]; + } + + /** + * Provides full and short name of bundle + * + * @return Generator + */ + public function provideFullAndShortBundleName() + { + yield [ + 'MyExtraBundle', + 'MyExtra', + ]; + + yield [ + 'MySuperExtraGorgeousBundle', + 'MySuperExtraGorgeous', + ]; + } + + /** + * Provides incorrect name of bundle + * + * @return Generator + */ + public function provideIncorrectBundleName() + { + yield [ + 'myExtra', + ]; + + yield [ + 'MyExtra', + ]; + + yield [ + 'MySuperExtraGorgeous', + ]; + } + + /** + * Provides path of the view / template and name of bundle + * + * @return Generator + */ + public function provideViewPathAndBundle() + { + yield [ + 'User', + 'MyExtraBundle', + '@MyExtra/User.html.twig', + ]; + + yield [ + 'User:Active', + 'MyExtraBundle', + '@MyExtra/User/Active.html.twig', + ]; + + yield [ + 'User:Active', + 'MySuperExtraGorgeousBundle', + '@MySuperExtraGorgeous/User/Active.html.twig', + ]; + } + + /** + * Provides path of the view / template, name of bundle and extension of the view / template + * + * @return Generator + */ + public function provideViewPathAndBundleAndExtension() + { + yield [ + 'User:Active', + 'MyExtraBundle', + '', + null, + ]; + + yield [ + 'User:Active', + 'MyExtraBundle', + 'js.twig', + '@MyExtra/User/Active.js.twig', + ]; + } + + /** + * Provides path of the view / template and incorrect name of bundle + * + * @return Generator + */ + public function provideViewPathAndIncorrectBundleName() + { + yield [ + 'User:Active', + 'myExtra', + ]; + + yield [ + 'User:Active', + 'MyExtra', + ]; + + yield [ + 'User:Active', + 'MySuperExtraGorgeous', + ]; + } + public function testConstructor() { static::assertHasNoConstructor(Bundle::class); } + /** + * @param string $viewPath Path of the view / template, e.g. "MyDirectory/my-template" + * @param string $bundleName Full name of the bundle, e.g. "MyExtraBundle" + * @param string $extension (optional) Extension of the view / template + * @param string $expected Expected path to view / template + * + * @throws IncorrectBundleNameException + * @dataProvider provideViewPathAndBundleAndExtension + */ + public function testGetBundleViewPathUsingCustomExtension($viewPath, $bundleName, $extension, $expected) + { + self::assertEquals($expected, Bundle::getBundleViewPath($viewPath, $bundleName, $extension)); + } + + /** + * @param string $viewPath Path of the view / template, e.g. "MyDirectory/my-template" + * @param string $bundleName Full name of the bundle, e.g. "MyExtraBundle" + * @param string $expected Expected path to view / template + * + * @throws IncorrectBundleNameException + * @dataProvider provideViewPathAndBundle + */ + public function testGetBundleViewPathUsingDefaultExtension($viewPath, $bundleName, $expected) + { + self::assertEquals($expected, Bundle::getBundleViewPath($viewPath, $bundleName)); + } + /** * @param string $viewPath Path of the view / template, e.g. "MyDirectory/my-template" * @param string $bundleName Full name of the bundle, e.g. "MyExtraBundle" @@ -47,65 +209,14 @@ class BundleTest extends BaseTestCase public function testGetBundleViewPathUsingIncorrectBundleName($viewPath, $bundleName) { $template = 'Name of bundle \'%s\' is incorrect. It should start with big letter and end with "Bundle". Is' - . ' there everything ok?'; + .' there everything ok?'; - $message = sprintf($template, $bundleName); - $this->setExpectedException(IncorrectBundleNameException::class, $message); + $this->expectException(IncorrectBundleNameException::class); + $this->expectExceptionMessage(sprintf($template, $bundleName)); Bundle::getBundleViewPath($viewPath, $bundleName); } - /** - * @param string $viewPath Path of the view / template, e.g. "MyDirectory/my-template" - * @param string $bundleName Full name of the bundle, e.g. "MyExtraBundle" - * @param string $expected Expected path to view / template - * - * @throws IncorrectBundleNameException - * @dataProvider provideViewPathAndBundle - */ - public function testGetBundleViewPathUsingDefaultExtension($viewPath, $bundleName, $expected) - { - self::assertEquals($expected, Bundle::getBundleViewPath($viewPath, $bundleName)); - } - - /** - * @param string $viewPath Path of the view / template, e.g. "MyDirectory/my-template" - * @param string $bundleName Full name of the bundle, e.g. "MyExtraBundle" - * @param string $extension (optional) Extension of the view / template - * @param string $expected Expected path to view / template - * - * @throws IncorrectBundleNameException - * @dataProvider provideViewPathAndBundleAndExtension - */ - public function testGetBundleViewPathUsingCustomExtension($viewPath, $bundleName, $extension, $expected) - { - self::assertEquals($expected, Bundle::getBundleViewPath($viewPath, $bundleName, $extension)); - } - - /** - * @param mixed $emptyValue Empty value, e.g. "" - * - * @throws IncorrectBundleNameException - * @dataProvider provideEmptyValue - */ - public function testGetShortBundleNameUsingEmptyValue($emptyValue) - { - $this->setExpectedException(IncorrectBundleNameException::class); - Bundle::getShortBundleName($emptyValue); - } - - /** - * @param string $bundleName Full name of the bundle, e.g. "MyExtraBundle" - * - * @throws IncorrectBundleNameException - * @dataProvider provideIncorrectBundleName - */ - public function testGetShortBundleNameUsingIncorrectBundleName($bundleName) - { - $this->setExpectedException(IncorrectBundleNameException::class); - Bundle::getShortBundleName($bundleName); - } - /** * @param string $fullBundleName Full name of the bundle, e.g. "MyExtraBundle" * @param string $shortBundleName Short name of bundle (without "Bundle") @@ -118,135 +229,21 @@ class BundleTest extends BaseTestCase self::assertEquals($shortBundleName, Bundle::getShortBundleName($fullBundleName)); } - /** - * Provides empty path of the view / template and/or name of bundle - * - * @return Generator - */ - public function provideEmptyViewPathAndBundle() + public function testGetShortBundleNameUsingEmptyValue(): void { - yield[ - '', - '', - ]; - - yield[ - 'test', - '', - ]; - - yield[ - '', - 'test', - ]; + $this->expectException(IncorrectBundleNameException::class); + Bundle::getShortBundleName(''); } /** - * Provides path of the view / template and incorrect name of bundle + * @param string $bundleName Full name of the bundle, e.g. "MyExtraBundle" * - * @return Generator + * @throws IncorrectBundleNameException + * @dataProvider provideIncorrectBundleName */ - public function provideViewPathAndIncorrectBundleName() + public function testGetShortBundleNameUsingIncorrectBundleName($bundleName) { - yield[ - 'User:Active', - 'myExtra', - ]; - - yield[ - 'User:Active', - 'MyExtra', - ]; - - yield[ - 'User:Active', - 'MySuperExtraGorgeous', - ]; - } - - /** - * Provides path of the view / template and name of bundle - * - * @return Generator - */ - public function provideViewPathAndBundle() - { - yield[ - 'User', - 'MyExtraBundle', - '@MyExtra/User.html.twig', - ]; - - yield[ - 'User:Active', - 'MyExtraBundle', - '@MyExtra/User/Active.html.twig', - ]; - - yield[ - 'User:Active', - 'MySuperExtraGorgeousBundle', - '@MySuperExtraGorgeous/User/Active.html.twig', - ]; - } - - /** - * Provides path of the view / template, name of bundle and extension of the view / template - * - * @return Generator - */ - public function provideViewPathAndBundleAndExtension() - { - yield[ - 'User:Active', - 'MyExtraBundle', - '', - null, - ]; - - yield[ - 'User:Active', - 'MyExtraBundle', - 'js.twig', - '@MyExtra/User/Active.js.twig', - ]; - } - - /** - * Provides incorrect name of bundle - * - * @return Generator - */ - public function provideIncorrectBundleName() - { - yield[ - 'myExtra', - ]; - - yield[ - 'MyExtra', - ]; - - yield[ - 'MySuperExtraGorgeous', - ]; - } - - /** - * Provides full and short name of bundle - * - * @return Generator - */ - public function provideFullAndShortBundleName() - { - yield[ - 'MyExtraBundle', - 'MyExtra', - ]; - - yield[ - 'MySuperExtraGorgeousBundle', - 'MySuperExtraGorgeous', - ]; + $this->expectException(IncorrectBundleNameException::class); + Bundle::getShortBundleName($bundleName); } } diff --git a/tests/Utilities/ComposerTest.php b/tests/Utilities/ComposerTest.php index 10e4177..e1bc69f 100644 --- a/tests/Utilities/ComposerTest.php +++ b/tests/Utilities/ComposerTest.php @@ -17,6 +17,9 @@ use Meritoo\Common\Utilities\Composer; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\Composer */ class ComposerTest extends BaseTestCase { @@ -27,64 +30,56 @@ class ComposerTest extends BaseTestCase */ private $composerJsonPath; + /** + * Provides names and values of existing nodes + * + * @return Generator + */ + public function getExistingNode(): Generator + { + yield [ + 'name', + 'test/test', + ]; + + yield [ + 'version', + '1.0.2', + ]; + } + public function testConstructor() { static::assertHasNoConstructor(Composer::class); } - /** - * @param string $composerJsonPath Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testGetValueNotExistingComposerJson($composerJsonPath) - { - self::assertNull(Composer::getValue($composerJsonPath, '')); - self::assertNull(Composer::getValue('not/existing/composer.json', '')); - } - - /** - * @param string $nodeName Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testGetValueNotExistingNode($nodeName) - { - self::assertNull(Composer::getValue($this->composerJsonPath, $nodeName)); - self::assertNull(Composer::getValue($this->composerJsonPath, 'not_existing_node')); - } - /** * @param string $nodeName Name of existing node * @param string $nodeValue Value of existing node * * @dataProvider getExistingNode */ - public function testGetValueExistingNode($nodeName, $nodeValue) + public function testGetValueExistingNode(string $nodeName, string $nodeValue): void { self::assertEquals($nodeValue, Composer::getValue($this->composerJsonPath, $nodeName)); } - /** - * Provides names and values of existing nodes - * - * @return Generator - */ - public function getExistingNode() + public function testGetValueNotExistingComposerJson(): void { - yield[ - 'name', - 'test/test', - ]; + self::assertNull(Composer::getValue('', '')); + self::assertNull(Composer::getValue('not/existing/composer.json', '')); + } - yield[ - 'version', - '1.0.2', - ]; + public function testGetValueNotExistingNode(): void + { + self::assertNull(Composer::getValue($this->composerJsonPath, '')); + self::assertNull(Composer::getValue($this->composerJsonPath, 'not_existing_node')); } /** * {@inheritdoc} */ - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/tests/Utilities/CssSelectorTest.php b/tests/Utilities/CssSelectorTest.php index 0ab9535..6f4e3f3 100644 --- a/tests/Utilities/CssSelectorTest.php +++ b/tests/Utilities/CssSelectorTest.php @@ -8,6 +8,7 @@ namespace Meritoo\Test\Common\Utilities; +use Generator; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Utilities\CssSelector; @@ -16,128 +17,154 @@ use Meritoo\Common\Utilities\CssSelector; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\CssSelector */ class CssSelectorTest extends BaseTestCase { + /** + * Provides name of form and selector of the form + * + * @return Generator + */ + public function provideFormNameAndSelector() + { + yield [ + 'test', + 'form[name="test"]', + ]; + + yield [ + 'test-123-test-456', + 'form[name="test-123-test-456"]', + ]; + + yield [ + 'test_something_098_different', + 'form[name="test_something_098_different"]', + ]; + } + + /** + * Provides name of form, ID of field and expected selector of label + * + * @return Generator + */ + public function provideFormNameFieldIdAndLabelSelector() + { + yield [ + 'test', + 'test', + 'form[name="test"] label[for="test"]', + ]; + + yield [ + 'test-123-test-456', + 'great-000-field', + 'form[name="test-123-test-456"] label[for="great-000-field"]', + ]; + + yield [ + 'test_something_098_different', + 'this-is-the-123789-field', + 'form[name="test_something_098_different"] label[for="this-is-the-123789-field"]', + ]; + } + + /** + * Provides name of form, ID of field and expected selector + * + * @return Generator + */ + public function provideFormNameFieldIdAndSelector() + { + yield [ + 'test', + 'test', + 'form[name="test"] input#test', + ]; + + yield [ + 'test-123-test-456', + 'great-000-field', + 'form[name="test-123-test-456"] input#great-000-field', + ]; + + yield [ + 'test_something_098_different', + 'this-is-the-123789-field', + 'form[name="test_something_098_different"] input#this-is-the-123789-field', + ]; + } + + /** + * Provides name of form, name of field and expected selector + * + * @return Generator + */ + public function provideFormNameFieldNameAndSelector() + { + yield [ + 'test', + 'test', + 'form[name="test"] input[name="test"]', + ]; + + yield [ + 'test-123-test-456', + 'great-000-field', + 'form[name="test-123-test-456"] input[name="great-000-field"]', + ]; + + yield [ + 'test_something_098_different', + 'this-is-the-123789-field', + 'form[name="test_something_098_different"] input[name="this-is-the-123789-field"]', + ]; + } + + /** + * Provides name of form, index/position of the field-set and expected selector + * + * @return Generator + */ + public function provideFormNameFieldSetIndexAndSelector() + { + yield [ + 'test', + 0, + 'form[name="test"] fieldset:nth-of-type(0)', + ]; + + yield [ + 'test-123-test-456', + 1, + 'form[name="test-123-test-456"] fieldset:nth-of-type(1)', + ]; + + yield [ + 'test_something_098_different', + 1245, + 'form[name="test_something_098_different"] fieldset:nth-of-type(1245)', + ]; + } + public function testConstructor() { static::assertHasNoConstructor(CssSelector::class); } /** - * @param string $emptyValue Name of form (value of the "name" attribute) - * @dataProvider provideEmptyScalarValue - */ - public function testGetFormByNameSelectorUsingEmptyName($emptyValue) - { - static::assertSame('', CssSelector::getFormByNameSelector($emptyValue)); - } - - /** - * @param string $formName Name of form (value of the "name" attribute) - * @param string $expected Expected selector + * @param string $formName Name of form (value of the "name" attribute) + * @param int $fieldSetIndex Index/Position of the field-set + * @param string $expected Expected selector * - * @dataProvider provideFormNameAndSelector + * @dataProvider provideFormNameFieldSetIndexAndSelector */ - public function testGetFormByNameSelector($formName, $expected) + public function testGetFieldSetByIndexSelector($formName, $fieldSetIndex, $expected) { - static::assertSame($expected, CssSelector::getFormByNameSelector($formName)); - } - - /** - * @param string $emptyValue Name of form (value of the "name" attribute) - * @dataProvider provideEmptyScalarValue - */ - public function testGetInputByNameSelectorUsingEmptyFormName($emptyValue) - { - $fieldName = 'test-test'; - static::assertSame('', CssSelector::getInputByNameSelector($emptyValue, $fieldName)); - } - - /** - * @param string $emptyValue Name of field (value of the "name" attribute) - * @dataProvider provideEmptyScalarValue - */ - public function testGetInputByNameSelectorUsingEmptyFieldName($emptyValue) - { - $formName = 'test-test'; - static::assertSame('', CssSelector::getInputByNameSelector($formName, $emptyValue)); - } - - /** - * @param string $formName Name of form (value of the "name" attribute) - * @param string $fieldName Name of field (value of the "name" attribute) - * @param string $expected Expected selector - * - * @dataProvider provideFormNameFieldNameAndSelector - */ - public function testGetInputByNameSelector($formName, $fieldName, $expected) - { - static::assertSame($expected, CssSelector::getInputByNameSelector($formName, $fieldName)); - } - - /** - * @param string $emptyValue Name of form (value of the "name" attribute) - * @dataProvider provideEmptyScalarValue - */ - public function testGetInputByIdSelectorUsingEmptyFormName($emptyValue) - { - $fieldId = 'test-test'; - static::assertSame('', CssSelector::getInputByIdSelector($emptyValue, $fieldId)); - } - - /** - * @param string $emptyValue ID of field (value of the "id" attribute) - * @dataProvider provideEmptyScalarValue - */ - public function testGetInputByIdSelectorUsingEmptyFieldName($emptyValue) - { - $formName = 'test-test'; - static::assertSame('', CssSelector::getInputByIdSelector($formName, $emptyValue)); - } - - /** - * @param string $formName Name of form (value of the "name" attribute) - * @param string $fieldId ID of field (value of the "id" attribute) - * @param string $expected Expected selector - * - * @dataProvider provideFormNameFieldIdAndSelector - */ - public function testGetInputByIdSelector($formName, $fieldId, $expected) - { - static::assertSame($expected, CssSelector::getInputByIdSelector($formName, $fieldId)); - } - - /** - * @param string $emptyValue Name of form (value of the "name" attribute) - * @dataProvider provideEmptyScalarValue - */ - public function testGetLabelSelectorUsingEmptyFormName($emptyValue) - { - $fieldId = 'test-test'; - static::assertSame('', CssSelector::getLabelSelector($emptyValue, $fieldId)); - } - - /** - * @param string $emptyValue ID of field (value of the "id" attribute) - * @dataProvider provideEmptyScalarValue - */ - public function testGetLabelSelectorUsingEmptyFieldId($emptyValue) - { - $formName = 'test-test'; - static::assertSame('', CssSelector::getLabelSelector($formName, $emptyValue)); - } - - /** - * @param string $formName Name of form (value of the "name" attribute) - * @param string $fieldId ID of field (value of the "id" attribute) - * @param string $expected Expected selector - * - * @dataProvider provideFormNameFieldIdAndLabelSelector - */ - public function testGetLabelSelector($formName, $fieldId, $expected) - { - static::assertSame($expected, CssSelector::getLabelSelector($formName, $fieldId)); + static::assertSame($expected, CssSelector::getFieldSetByIndexSelector($formName, $fieldSetIndex)); } /** @@ -156,141 +183,118 @@ class CssSelectorTest extends BaseTestCase } /** - * @param string $formName Name of form (value of the "name" attribute) - * @param int $fieldSetIndex Index/Position of the field-set - * @param string $expected Expected selector + * @param string $formName Name of form (value of the "name" attribute) + * @param string $expected Expected selector * - * @dataProvider provideFormNameFieldSetIndexAndSelector + * @dataProvider provideFormNameAndSelector */ - public function testGetFieldSetByIndexSelector($formName, $fieldSetIndex, $expected) + public function testGetFormByNameSelector($formName, $expected) { - static::assertSame($expected, CssSelector::getFieldSetByIndexSelector($formName, $fieldSetIndex)); + static::assertSame($expected, CssSelector::getFormByNameSelector($formName)); } /** - * Provides name of form and selector of the form - * - * @return \Generator + * @param string $emptyValue Name of form (value of the "name" attribute) + * @dataProvider provideEmptyScalarValue */ - public function provideFormNameAndSelector() + public function testGetFormByNameSelectorUsingEmptyName($emptyValue) { - yield[ - 'test', - 'form[name="test"]', - ]; - - yield[ - 'test-123-test-456', - 'form[name="test-123-test-456"]', - ]; - - yield[ - 'test_something_098_different', - 'form[name="test_something_098_different"]', - ]; + static::assertSame('', CssSelector::getFormByNameSelector($emptyValue)); } /** - * Provides name of form, name of field and expected selector + * @param string $formName Name of form (value of the "name" attribute) + * @param string $fieldId ID of field (value of the "id" attribute) + * @param string $expected Expected selector * - * @return \Generator + * @dataProvider provideFormNameFieldIdAndSelector */ - public function provideFormNameFieldNameAndSelector() + public function testGetInputByIdSelector($formName, $fieldId, $expected) { - yield[ - 'test', - 'test', - 'form[name="test"] input[name="test"]', - ]; - - yield[ - 'test-123-test-456', - 'great-000-field', - 'form[name="test-123-test-456"] input[name="great-000-field"]', - ]; - - yield[ - 'test_something_098_different', - 'this-is-the-123789-field', - 'form[name="test_something_098_different"] input[name="this-is-the-123789-field"]', - ]; + static::assertSame($expected, CssSelector::getInputByIdSelector($formName, $fieldId)); } /** - * Provides name of form, ID of field and expected selector of label - * - * @return \Generator + * @param string $emptyValue ID of field (value of the "id" attribute) + * @dataProvider provideEmptyScalarValue */ - public function provideFormNameFieldIdAndLabelSelector() + public function testGetInputByIdSelectorUsingEmptyFieldName($emptyValue) { - yield[ - 'test', - 'test', - 'form[name="test"] label[for="test"]', - ]; - - yield[ - 'test-123-test-456', - 'great-000-field', - 'form[name="test-123-test-456"] label[for="great-000-field"]', - ]; - - yield[ - 'test_something_098_different', - 'this-is-the-123789-field', - 'form[name="test_something_098_different"] label[for="this-is-the-123789-field"]', - ]; + $formName = 'test-test'; + static::assertSame('', CssSelector::getInputByIdSelector($formName, $emptyValue)); } /** - * Provides name of form, index/position of the field-set and expected selector - * - * @return \Generator + * @param string $emptyValue Name of form (value of the "name" attribute) + * @dataProvider provideEmptyScalarValue */ - public function provideFormNameFieldSetIndexAndSelector() + public function testGetInputByIdSelectorUsingEmptyFormName($emptyValue) { - yield[ - 'test', - 0, - 'form[name="test"] fieldset:nth-of-type(0)', - ]; - - yield[ - 'test-123-test-456', - 1, - 'form[name="test-123-test-456"] fieldset:nth-of-type(1)', - ]; - - yield[ - 'test_something_098_different', - 1245, - 'form[name="test_something_098_different"] fieldset:nth-of-type(1245)', - ]; + $fieldId = 'test-test'; + static::assertSame('', CssSelector::getInputByIdSelector($emptyValue, $fieldId)); } /** - * Provides name of form, ID of field and expected selector + * @param string $formName Name of form (value of the "name" attribute) + * @param string $fieldName Name of field (value of the "name" attribute) + * @param string $expected Expected selector * - * @return \Generator + * @dataProvider provideFormNameFieldNameAndSelector */ - public function provideFormNameFieldIdAndSelector() + public function testGetInputByNameSelector($formName, $fieldName, $expected) { - yield[ - 'test', - 'test', - 'form[name="test"] input#test', - ]; + static::assertSame($expected, CssSelector::getInputByNameSelector($formName, $fieldName)); + } - yield[ - 'test-123-test-456', - 'great-000-field', - 'form[name="test-123-test-456"] input#great-000-field', - ]; + /** + * @param string $emptyValue Name of field (value of the "name" attribute) + * @dataProvider provideEmptyScalarValue + */ + public function testGetInputByNameSelectorUsingEmptyFieldName($emptyValue) + { + $formName = 'test-test'; + static::assertSame('', CssSelector::getInputByNameSelector($formName, $emptyValue)); + } - yield[ - 'test_something_098_different', - 'this-is-the-123789-field', - 'form[name="test_something_098_different"] input#this-is-the-123789-field', - ]; + /** + * @param string $emptyValue Name of form (value of the "name" attribute) + * @dataProvider provideEmptyScalarValue + */ + public function testGetInputByNameSelectorUsingEmptyFormName($emptyValue) + { + $fieldName = 'test-test'; + static::assertSame('', CssSelector::getInputByNameSelector($emptyValue, $fieldName)); + } + + /** + * @param string $formName Name of form (value of the "name" attribute) + * @param string $fieldId ID of field (value of the "id" attribute) + * @param string $expected Expected selector + * + * @dataProvider provideFormNameFieldIdAndLabelSelector + */ + public function testGetLabelSelector($formName, $fieldId, $expected) + { + static::assertSame($expected, CssSelector::getLabelSelector($formName, $fieldId)); + } + + /** + * @param string $emptyValue ID of field (value of the "id" attribute) + * @dataProvider provideEmptyScalarValue + */ + public function testGetLabelSelectorUsingEmptyFieldId($emptyValue) + { + $formName = 'test-test'; + static::assertSame('', CssSelector::getLabelSelector($formName, $emptyValue)); + } + + /** + * @param string $emptyValue Name of form (value of the "name" attribute) + * @dataProvider provideEmptyScalarValue + */ + public function testGetLabelSelectorUsingEmptyFormName($emptyValue) + { + $fieldId = 'test-test'; + static::assertSame('', CssSelector::getLabelSelector($emptyValue, $fieldId)); } } diff --git a/tests/Utilities/DateTest.php b/tests/Utilities/DateTest.php index 5fca3fe..5821e88 100644 --- a/tests/Utilities/DateTest.php +++ b/tests/Utilities/DateTest.php @@ -15,162 +15,390 @@ use Meritoo\Common\Exception\Type\UnknownDatePartTypeException; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\DatePeriod; use Meritoo\Common\Utilities\Date; +use Meritoo\Common\Utilities\Locale; /** * Test case of the Date methods (only static functions) * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\Date */ class DateTest extends BaseTestCase { - public function testConstructor() + /** + * Provides correct period + * + * @return Generator + */ + public function provideCorrectPeriod() + { + yield [ + DatePeriod::LAST_WEEK, + new DatePeriod( + (new DateTime('this week'))->sub(new DateInterval('P7D'))->setTime(0, 0, 0), + (new DateTime('this week'))->sub(new DateInterval('P1D'))->setTime(23, 59, 59) + ), + ]; + + yield [ + DatePeriod::THIS_WEEK, + new DatePeriod( + (new DateTime('this week'))->setTime(0, 0, 0), + (new DateTime('this week'))->add(new DateInterval('P6D'))->setTime(23, 59, 59) + ), + ]; + + yield [ + DatePeriod::NEXT_WEEK, + new DatePeriod( + (new DateTime('this week'))->add(new DateInterval('P7D'))->setTime(0, 0, 0), + (new DateTime('this week'))->add(new DateInterval('P7D')) + ->add(new DateInterval('P6D')) + ->setTime(23, 59, 59) + ), + ]; + + yield [ + DatePeriod::LAST_MONTH, + new DatePeriod( + (new DateTime('first day of last month'))->setTime(0, 0, 0), + (new DateTime('last day of last month'))->setTime(23, 59, 59) + ), + ]; + + yield [ + DatePeriod::THIS_MONTH, + new DatePeriod( + Date::getDatesForPeriod(DatePeriod::LAST_MONTH) + ->getEndDate() + ->add(new DateInterval('P1D')) + ->setTime(0, 0, 0), + Date::getDatesForPeriod(DatePeriod::NEXT_MONTH) + ->getStartDate() + ->sub(new DateInterval('P1D')) + ->setTime(23, 59, 59) + ), + ]; + + yield [ + DatePeriod::NEXT_MONTH, + new DatePeriod( + (new DateTime('first day of next month'))->setTime(0, 0, 0), + (new DateTime('last day of next month'))->setTime(23, 59, 59) + ), + ]; + + $lastYearStart = (new DateTime())->modify('-1 year'); + $lastYearEnd = (new DateTime())->modify('-1 year'); + $year = $lastYearStart->format('Y'); + + yield [ + DatePeriod::LAST_YEAR, + new DatePeriod( + $lastYearStart->setDate($year, 1, 1)->setTime(0, 0, 0), + $lastYearEnd->setDate($year, 12, 31)->setTime(23, 59, 59) + ), + ]; + + $year = (new DateTime())->format('Y'); + + yield [ + DatePeriod::THIS_YEAR, + new DatePeriod( + (new DateTime())->setDate($year, 1, 1)->setTime(0, 0, 0), + (new DateTime())->setDate($year, 12, 31)->setTime(23, 59, 59) + ), + ]; + + $nextYearStart = (new DateTime())->modify('1 year'); + $nextYearEnd = (new DateTime())->modify('1 year'); + $year = $nextYearStart->format('Y'); + + yield [ + DatePeriod::NEXT_YEAR, + new DatePeriod( + $nextYearStart->setDate($year, 1, 1)->setTime(0, 0, 0), + $nextYearEnd->setDate($year, 12, 31)->setTime(23, 59, 59) + ), + ]; + } + + /** + * Provides data for the random date + * + * @return Generator + */ + public function provideDataOfRandomDate() + { + yield [ + new DateTime('2000-01-01'), + 1, + 100, + ]; + + yield [ + new DateTime('2000-12-01'), + 1, + 100, + ]; + yield [ + new DateTime('2000-01-01'), + '1', + '100', + ]; + + yield [ + new DateTime('2000-12-01'), + '1', + '100', + ]; + + yield [ + new DateTime('2000-01-01'), + 10, + 50, + ]; + + yield [ + new DateTime('2000-12-01'), + 10, + 50, + ]; + } + + /** + * Provides data for the random date with incorrect end of random partition + * + * @return Generator + */ + public function provideDataOfRandomDateIncorrectEnd() + { + yield [ + new DateTime('2000-01-01'), + 100, + 1, + ]; + } + + /** + * Provide empty dates for date difference + * + * @return Generator + */ + public function provideEmptyDatesForDateDifference() + { + yield [ + null, + null, + ]; + + yield [ + '', + '', + ]; + + yield [ + null, + new DateTime(), + ]; + + yield [ + new DateTime(), + null, + ]; + } + + /** + * Provides incorrect invalidCount of DateTime + * + * @return Generator + */ + public function provideIncorrectDateTimeValue() + { + // Incorrect one-character values + yield ['a']; + yield ['m']; + + // Incorrect strings + yield ['ss']; + yield ['sss']; + yield ['mm']; + yield ['yy']; + yield ['yyyy']; + + // Incorrect integer values + yield [1]; + yield [10]; + yield [15]; + yield [100]; + yield [1000]; + + // Incorrect string / numeric values + yield ['1']; + yield ['10']; + yield ['15']; + yield ['100']; + yield ['1000']; + + // Incorrect dates + yield ['0-0-0']; + yield ['20-01-01']; + yield ['2015-0-0']; + yield ['2015-00-00']; + yield ['2015-16-01']; + } + + /** + * Provides incorrect period + * + * @return Generator + */ + public function provideIncorrectPeriod() + { + yield [-1]; + yield [0]; + yield [10]; + } + + /** + * Provides incorrect values of year, month and day + * + * @return Generator + */ + public function provideIncorrectYearMonthDay(): Generator + { + yield [ + 0, + 0, + 0, + ]; + + yield [ + -1, + -1, + -1, + ]; + + yield [ + 5000, + 50, + 50, + ]; + + yield [ + 2000, + 13, + 01, + ]; + + yield [ + 2000, + 01, + 40, + ]; + } + + /** + * Provides invalid format of date + * + * @return Generator + */ + public function provideInvalidDateFormats() + { + yield [0]; + yield [9]; + yield ['[]']; + yield ['invalid']; + yield ['Q']; + yield [',']; + yield ['.']; + yield ['aa###']; + yield ['Y/m/d H:i:invalid']; + } + + /** + * Provides values of year, month and day + * + * @return Generator + */ + public function provideYearMonthDay() + { + yield [ + 2000, + 01, + 01, + ]; + + yield [ + 2000, + 1, + 1, + ]; + + yield [ + 2000, + 2, + 2, + ]; + + yield [ + 2000, + 6, + 1, + ]; + + yield [ + 2000, + 12, + 01, + ]; + + yield [ + 2000, + 12, + 1, + ]; + + yield [ + 2000, + 12, + 31, + ]; + } + + public function testConstructor(): void { static::assertHasNoConstructor(Date::class); } - /** - * @param mixed $value Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testGetDateTimeEmptyValue($value) + public function testGenerateRandomTimeCustomFormat(): void { - self::assertFalse(Date::getDateTime($value)); + self::assertMatchesRegularExpression('/^0[1-9]{1}|1[0-2]{1}$/', Date::generateRandomTime('h')); // 01 through 12 + self::assertMatchesRegularExpression('/^[0-5]?[0-9]$/', Date::generateRandomTime('i')); // 00 through 59 + self::assertMatchesRegularExpression('/^[0-5]?[0-9]$/', Date::generateRandomTime('s')); // 00 through 59 + + self::assertMatchesRegularExpression('/^\d{2}:\d{2}$/', Date::generateRandomTime('H:i')); + self::assertMatchesRegularExpression('/^[1-9]|1[0-2]:\d{2}$/', Date::generateRandomTime('g:i')); } - /** - * @param mixed $value Incorrect source of DateTime - * @dataProvider provideIncorrectDateTimeValue - */ - public function testGetDateTimeIncorrectValue($value) + public function testGenerateRandomTimeDefaultFormat(): void { - self::assertFalse(Date::getDateTime($value)); - } - - /** - * @param bool $value The value which maybe is a date - * @dataProvider provideBooleanValue - */ - public function testGetDateTimeBoolean($value) - { - self::assertFalse(Date::getDateTime($value)); - } - - /** - * @param string $relativeFormat Relative / compound format of DateTime - * @dataProvider provideDateTimeRelativeFormat - */ - public function testGetDateTimeRelativeFormats($relativeFormat) - { - /* - * Values based on relative / compound formats, but... without explicitly declaring them as compound - * (2nd argument set to false by default) - * - * http://php.net/manual/en/datetime.formats.compound.php - */ - self::assertFalse(Date::getDateTime($relativeFormat)); - - /* - * Values based on relative / compound formats - * http://php.net/manual/en/datetime.formats.compound.php - */ - self::assertInstanceOf(DateTime::class, Date::getDateTime($relativeFormat, true)); - } - - /** - * @param DateTime $dateTime Instance of DateTime class - * @dataProvider provideDateTimeInstance - */ - public function testGetDateTimeInstanceDateTime(DateTime $dateTime) - { - self::assertInstanceOf(DateTime::class, Date::getDateTime($dateTime)); - } - - public function testGetDateTimeConcreteDates() - { - /* - * Using the standard date format provided by the tested method - */ - self::assertInstanceOf(DateTime::class, Date::getDateTime('2015-03-20')); - - /* - * Using custom date format - */ - self::assertInstanceOf(DateTime::class, Date::getDateTime('2015-03-20 11:30', false, 'Y-m-d H:i')); - self::assertInstanceOf(DateTime::class, Date::getDateTime('20.03.2015', false, 'd.m.Y')); + self::assertMatchesRegularExpression('/\d{2}:\d{2}:\d{2}/', Date::generateRandomTime()); } /** * @param mixed $value Empty value, e.g. "" * @dataProvider provideEmptyValue */ - public function testIsValidDateEmptyDates($value) - { - self::assertFalse(Date::isValidDate($value)); - } - - /** - * @param mixed $value Incorrect source of DateTime - * @dataProvider provideIncorrectDateTimeValue - */ - public function testIsValidDateIncorrectDates($value) - { - self::assertFalse(Date::isValidDate($value)); - } - - public function testIsValidDateValidDates() - { - self::assertTrue(Date::isValidDate('2017-01-01')); - self::assertTrue(Date::isValidDate('2017-01-01 10:30', true)); - self::assertTrue(Date::isValidDate('2017-01-01 14:00', true)); - - self::assertTrue(Date::isValidDate(new DateTime())); - self::assertTrue(Date::isValidDate(new DateTime('now'))); - self::assertTrue(Date::isValidDate(new DateTime('tomorrow'))); - self::assertTrue(Date::isValidDate(new DateTime('m'))); - } - - /** - * @param mixed $value Empty source of date format - * @dataProvider provideEmptyValue - */ - public function testIsValidDateFormatEmptyFormats($value) - { - self::assertFalse(Date::isValidDateFormat($value)); - } - - /** - * @param mixed $format Invalid format of date - * @dataProvider provideInvalidDateFormats - */ - public function testIsValidDateFormatInvalidFormats($format) - { - self::assertFalse(Date::isValidDateFormat($format)); - } - - public function testIsValidDateFormatValidFormats() - { - self::assertTrue(Date::isValidDateFormat('Y')); - self::assertTrue(Date::isValidDateFormat('yy')); - self::assertTrue(Date::isValidDateFormat('M')); - self::assertTrue(Date::isValidDateFormat('i')); - self::assertTrue(Date::isValidDateFormat('l')); - self::assertTrue(Date::isValidDateFormat('l, d F')); - self::assertTrue(Date::isValidDateFormat('Y-m-d')); - self::assertTrue(Date::isValidDateFormat('H:i:s')); - self::assertTrue(Date::isValidDateFormat('Y/m/d H:i:s')); - } - - /** - * @param mixed $value Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testGenerateRandomTimeEmptyFormat($value) + public function testGenerateRandomTimeEmptyFormat($value): void { self::assertNull(Date::generateRandomTime($value)); } - public function testGenerateRandomTimeIncorrectFormat() + public function testGenerateRandomTimeIncorrectFormat(): void { self::assertNull(Date::generateRandomTime(',')); self::assertNull(Date::generateRandomTime(';')); @@ -178,28 +406,19 @@ class DateTest extends BaseTestCase self::assertNull(Date::generateRandomTime('?')); } - public function testGenerateRandomTimeDefaultFormat() + public function testGetCurrentDayOfWeek(): void { - self::assertRegExp('/\d{2}:\d{2}:\d{2}/', Date::generateRandomTime()); + self::assertMatchesRegularExpression('/^[0-6]{1}$/', (string) Date::getCurrentDayOfWeek()); } - public function testGenerateRandomTimeCustomFormat() + public function testGetCurrentDayOfWeekName(): void { - self::assertRegExp('/^0[1-9]{1}|1[0-2]{1}$/', Date::generateRandomTime('h')); // 01 through 12 - self::assertRegExp('/^[0-5]?[0-9]$/', Date::generateRandomTime('i')); // 00 through 59 - self::assertRegExp('/^[0-5]?[0-9]$/', Date::generateRandomTime('s')); // 00 through 59 + // Required to avoid failure: + // + // Failed asserting that 'giovedì' matches PCRE pattern + // "/^Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday$/" + Locale::setLocale(LC_ALL, 'en', 'US'); - self::assertRegExp('/^\d{2}:\d{2}$/', Date::generateRandomTime('H:i')); - self::assertRegExp('/^[1-9]|1[0-2]:\d{2}$/', Date::generateRandomTime('g:i')); - } - - public function testGetCurrentDayOfWeek() - { - self::assertRegExp('/^[0-6]{1}$/', (string)Date::getCurrentDayOfWeek()); - } - - public function testGetCurrentDayOfWeekName() - { $days = [ 'Monday', 'Tuesday', @@ -212,247 +431,30 @@ class DateTest extends BaseTestCase $pattern = sprintf('/^%s$/', implode('|', $days)); - self::assertRegExp($pattern, Date::getCurrentDayOfWeekName()); + self::assertMatchesRegularExpression($pattern, Date::getCurrentDayOfWeekName()); } /** - * @param int $year The year value - * @param int $month The month value - * @param int $day The day value - * - * @dataProvider provideIncorrectYearMonthDay - */ - public function testGetDayOfWeekIncorrectValues($year, $month, $day) - { - $this->setExpectedException(UnknownDatePartTypeException::class); - self::assertEmpty(Date::getDayOfWeek($year, $month, $day)); - } - - /** - * @param int $year The year value - * @param int $month The month value - * @param int $day The day value - * - * @dataProvider provideYearMonthDay - */ - public function testGetDayOfWeek($year, $month, $day) - { - self::assertRegExp('/^[0-6]{1}$/', (string)Date::getDayOfWeek($year, $month, $day)); - } - - /** - * @param string|DateTime $dateStart The start date - * @param string|DateTime $dateEnd The end date + * @param DateTime|string $dateStart The start date + * @param DateTime|string $dateEnd The end date * * @dataProvider provideEmptyDatesForDateDifference */ - public function testGetDateDifferenceEmptyDates($dateStart, $dateEnd) + public function testGetDateDifferenceEmptyDates($dateStart, $dateEnd): void { self::assertNull(Date::getDateDifference($dateStart, $dateEnd)); } - public function testGetDateDifferenceInvalidDates() - { - self::assertNull(Date::getDateDifference('2017-01-40', '2017-13-01')); - self::assertNull(Date::getDateDifference('xyz', 'lorem')); - } - - public function testGetDateDifferenceOneDay() - { - /* - * Difference of 1 day - */ - $dateStart = '2017-01-01'; - $dateEnd = '2017-01-02'; - - $effect = [ - Date::DATE_DIFFERENCE_UNIT_YEARS => 0, - Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, - Date::DATE_DIFFERENCE_UNIT_DAYS => 1, - Date::DATE_DIFFERENCE_UNIT_HOURS => 0, - Date::DATE_DIFFERENCE_UNIT_MINUTES => 0, - ]; - - self::assertEquals($effect, Date::getDateDifference($dateStart, $dateEnd)); - self::assertEquals($effect, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd))); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_YEARS)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_YEARS)); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MONTHS)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MONTHS)); - - self::assertEquals(1, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_DAYS)); - self::assertEquals(1, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_DAYS)); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_HOURS)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_HOURS)); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MINUTES)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MINUTES)); - - /* - * Difference of 1 day (using the relative date format) - */ - $effect = [ - Date::DATE_DIFFERENCE_UNIT_YEARS => 0, - Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, - Date::DATE_DIFFERENCE_UNIT_DAYS => 1, - Date::DATE_DIFFERENCE_UNIT_HOURS => 0, - Date::DATE_DIFFERENCE_UNIT_MINUTES => 0, - ]; - - self::assertEquals($effect, Date::getDateDifference(new DateTime('yesterday'), new DateTime('midnight'))); - self::assertEquals(0, Date::getDateDifference(new DateTime('yesterday'), new DateTime('midnight'), Date::DATE_DIFFERENCE_UNIT_MONTHS)); - self::assertEquals(1, Date::getDateDifference(new DateTime('yesterday'), new DateTime('midnight'), Date::DATE_DIFFERENCE_UNIT_DAYS)); - self::assertEquals(0, Date::getDateDifference(new DateTime('yesterday'), new DateTime('midnight'), Date::DATE_DIFFERENCE_UNIT_HOURS)); - self::assertEquals(0, Date::getDateDifference(new DateTime('yesterday'), new DateTime('midnight'), Date::DATE_DIFFERENCE_UNIT_MINUTES)); - } - - public function testGetDateDifferenceOneDayTwoHours() - { - /* - * Difference of 1 day, 2 hours and 15 minutes - */ - $dateStart = '2017-01-01 12:00'; - $dateEnd = '2017-01-02 14:15'; - - $effect = [ - Date::DATE_DIFFERENCE_UNIT_YEARS => 0, - Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, - Date::DATE_DIFFERENCE_UNIT_DAYS => 1, - Date::DATE_DIFFERENCE_UNIT_HOURS => 2, - Date::DATE_DIFFERENCE_UNIT_MINUTES => 15, - ]; - - self::assertEquals($effect, Date::getDateDifference($dateStart, $dateEnd)); - self::assertEquals($effect, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd))); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_YEARS)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_YEARS)); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MONTHS)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MONTHS)); - - self::assertEquals(1, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_DAYS)); - self::assertEquals(1, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_DAYS)); - - self::assertEquals(2, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_HOURS)); - self::assertEquals(2, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_HOURS)); - - self::assertEquals(15, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MINUTES)); - self::assertEquals(15, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MINUTES)); - } - - public function testGetDateDifferenceOneMonthFortyOneDays() - { - /* - * Difference of 1 month, 41 days, 4 hours and 30 minutes - */ - $dateStart = '2017-01-01 12:00'; - $dateEnd = '2017-02-11 16:30'; - - $effect = [ - Date::DATE_DIFFERENCE_UNIT_YEARS => 0, - Date::DATE_DIFFERENCE_UNIT_MONTHS => 1, - Date::DATE_DIFFERENCE_UNIT_DAYS => 41, - Date::DATE_DIFFERENCE_UNIT_HOURS => 4, - Date::DATE_DIFFERENCE_UNIT_MINUTES => 30, - ]; - - self::assertEquals($effect, Date::getDateDifference($dateStart, $dateEnd)); - self::assertEquals($effect, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd))); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_YEARS)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_YEARS)); - - self::assertEquals(1, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MONTHS)); - self::assertEquals(1, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MONTHS)); - - self::assertEquals(41, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_DAYS)); - self::assertEquals(41, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_DAYS)); - - self::assertEquals(4, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_HOURS)); - self::assertEquals(4, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_HOURS)); - - self::assertEquals(30, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MINUTES)); - self::assertEquals(30, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MINUTES)); - } - - public function testGetDateDifferenceNewYear() - { - $dateStart = '2017-12-31 23:59'; - $dateEnd = '2018-01-01 00:00'; - - $effect = [ - Date::DATE_DIFFERENCE_UNIT_YEARS => 0, - Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, - Date::DATE_DIFFERENCE_UNIT_DAYS => 0, - Date::DATE_DIFFERENCE_UNIT_HOURS => 0, - Date::DATE_DIFFERENCE_UNIT_MINUTES => 1, - ]; - - self::assertEquals($effect, Date::getDateDifference($dateStart, $dateEnd)); - self::assertEquals($effect, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd))); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_YEARS)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_YEARS)); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MONTHS)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MONTHS)); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_DAYS)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_DAYS)); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_HOURS)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_HOURS)); - - self::assertEquals(1, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MINUTES)); - self::assertEquals(1, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MINUTES)); - } - - public function testGetDateDifferenceLessThan24Hours() - { - $dateStart = '2017-01-01 16:00'; - $dateEnd = '2017-01-02 10:00'; - - $effect = [ - Date::DATE_DIFFERENCE_UNIT_YEARS => 0, - Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, - Date::DATE_DIFFERENCE_UNIT_DAYS => 0, - Date::DATE_DIFFERENCE_UNIT_HOURS => 18, - Date::DATE_DIFFERENCE_UNIT_MINUTES => 0, - ]; - - self::assertEquals($effect, Date::getDateDifference($dateStart, $dateEnd)); - self::assertEquals($effect, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd))); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_YEARS)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_YEARS)); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MONTHS)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MONTHS)); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_DAYS)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_DAYS)); - - self::assertEquals(18, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_HOURS)); - self::assertEquals(18, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_HOURS)); - - self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MINUTES)); - self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MINUTES)); - } - - public function testGetDateDifferenceEqual24Hours() + public function testGetDateDifferenceEqual24Hours(): void { $dateStart = '2017-01-01 00:00'; $dateEnd = '2017-01-02 00:00'; $effect = [ - Date::DATE_DIFFERENCE_UNIT_YEARS => 0, - Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, - Date::DATE_DIFFERENCE_UNIT_DAYS => 1, - Date::DATE_DIFFERENCE_UNIT_HOURS => 0, + Date::DATE_DIFFERENCE_UNIT_YEARS => 0, + Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, + Date::DATE_DIFFERENCE_UNIT_DAYS => 1, + Date::DATE_DIFFERENCE_UNIT_HOURS => 0, Date::DATE_DIFFERENCE_UNIT_MINUTES => 0, ]; @@ -475,16 +477,22 @@ class DateTest extends BaseTestCase self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MINUTES)); } - public function testGetDateDifferenceInvertedDates() + public function testGetDateDifferenceInvalidDates(): void + { + self::assertNull(Date::getDateDifference('2017-01-40', '2017-13-01')); + self::assertNull(Date::getDateDifference('xyz', 'lorem')); + } + + public function testGetDateDifferenceInvertedDates(): void { $dateStart = '2017-01-02 10:00'; $dateEnd = '2017-01-01 16:00'; $effect = [ - Date::DATE_DIFFERENCE_UNIT_YEARS => 0, - Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, - Date::DATE_DIFFERENCE_UNIT_DAYS => -1, - Date::DATE_DIFFERENCE_UNIT_HOURS => 6, + Date::DATE_DIFFERENCE_UNIT_YEARS => 0, + Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, + Date::DATE_DIFFERENCE_UNIT_DAYS => -1, + Date::DATE_DIFFERENCE_UNIT_HOURS => 6, Date::DATE_DIFFERENCE_UNIT_MINUTES => 0, ]; @@ -507,19 +515,81 @@ class DateTest extends BaseTestCase self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MINUTES)); } - public function testGetDateDifferenceNoDifference() + public function testGetDateDifferenceLessThan24Hours(): void { - /* - * No difference - */ + $dateStart = '2017-01-01 16:00'; + $dateEnd = '2017-01-02 10:00'; + + $effect = [ + Date::DATE_DIFFERENCE_UNIT_YEARS => 0, + Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, + Date::DATE_DIFFERENCE_UNIT_DAYS => 0, + Date::DATE_DIFFERENCE_UNIT_HOURS => 18, + Date::DATE_DIFFERENCE_UNIT_MINUTES => 0, + ]; + + self::assertEquals($effect, Date::getDateDifference($dateStart, $dateEnd)); + self::assertEquals($effect, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd))); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_YEARS)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_YEARS)); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MONTHS)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MONTHS)); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_DAYS)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_DAYS)); + + self::assertEquals(18, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_HOURS)); + self::assertEquals(18, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_HOURS)); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MINUTES)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MINUTES)); + } + + public function testGetDateDifferenceNewYear(): void + { + $dateStart = '2017-12-31 23:59'; + $dateEnd = '2018-01-01 00:00'; + + $effect = [ + Date::DATE_DIFFERENCE_UNIT_YEARS => 0, + Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, + Date::DATE_DIFFERENCE_UNIT_DAYS => 0, + Date::DATE_DIFFERENCE_UNIT_HOURS => 0, + Date::DATE_DIFFERENCE_UNIT_MINUTES => 1, + ]; + + self::assertEquals($effect, Date::getDateDifference($dateStart, $dateEnd)); + self::assertEquals($effect, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd))); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_YEARS)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_YEARS)); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MONTHS)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MONTHS)); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_DAYS)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_DAYS)); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_HOURS)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_HOURS)); + + self::assertEquals(1, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MINUTES)); + self::assertEquals(1, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MINUTES)); + } + + public function testGetDateDifferenceNoDifference(): void + { + // No difference $dateStart = '2017-01-01 12:00'; $dateEnd = $dateStart; $effect = [ - Date::DATE_DIFFERENCE_UNIT_YEARS => 0, - Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, - Date::DATE_DIFFERENCE_UNIT_DAYS => 0, - Date::DATE_DIFFERENCE_UNIT_HOURS => 0, + Date::DATE_DIFFERENCE_UNIT_YEARS => 0, + Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, + Date::DATE_DIFFERENCE_UNIT_DAYS => 0, + Date::DATE_DIFFERENCE_UNIT_HOURS => 0, Date::DATE_DIFFERENCE_UNIT_MINUTES => 0, ]; @@ -542,41 +612,197 @@ class DateTest extends BaseTestCase self::assertEquals(0, Date::getDateDifference(new DateTime(), new DateTime(), Date::DATE_DIFFERENCE_UNIT_MINUTES)); } - /** - * @param mixed $invalidCount Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testGetDatesCollectionInvalidCount($invalidCount) + public function testGetDateDifferenceOneDay(): void { - self::assertEquals([], Date::getDatesCollection(new DateTime(), $invalidCount)); - self::assertEquals([], Date::getDatesCollection(new DateTime(), -1)); + // Difference of 1 day + $dateStart = '2017-01-01'; + $dateEnd = '2017-01-02'; + + $effect = [ + Date::DATE_DIFFERENCE_UNIT_YEARS => 0, + Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, + Date::DATE_DIFFERENCE_UNIT_DAYS => 1, + Date::DATE_DIFFERENCE_UNIT_HOURS => 0, + Date::DATE_DIFFERENCE_UNIT_MINUTES => 0, + ]; + + self::assertEquals($effect, Date::getDateDifference($dateStart, $dateEnd)); + self::assertEquals($effect, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd))); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_YEARS)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_YEARS)); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MONTHS)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MONTHS)); + + self::assertEquals(1, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_DAYS)); + self::assertEquals(1, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_DAYS)); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_HOURS)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_HOURS)); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MINUTES)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MINUTES)); + + // Difference of 1 day (using the relative date format) + $effect = [ + Date::DATE_DIFFERENCE_UNIT_YEARS => 0, + Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, + Date::DATE_DIFFERENCE_UNIT_DAYS => 1, + Date::DATE_DIFFERENCE_UNIT_HOURS => 0, + Date::DATE_DIFFERENCE_UNIT_MINUTES => 0, + ]; + + self::assertEquals($effect, Date::getDateDifference(new DateTime('yesterday'), new DateTime('midnight'))); + self::assertEquals(0, Date::getDateDifference(new DateTime('yesterday'), new DateTime('midnight'), Date::DATE_DIFFERENCE_UNIT_MONTHS)); + self::assertEquals(1, Date::getDateDifference(new DateTime('yesterday'), new DateTime('midnight'), Date::DATE_DIFFERENCE_UNIT_DAYS)); + self::assertEquals(0, Date::getDateDifference(new DateTime('yesterday'), new DateTime('midnight'), Date::DATE_DIFFERENCE_UNIT_HOURS)); + self::assertEquals(0, Date::getDateDifference(new DateTime('yesterday'), new DateTime('midnight'), Date::DATE_DIFFERENCE_UNIT_MINUTES)); + } + + public function testGetDateDifferenceOneDayTwoHours(): void + { + // Difference of 1 day, 2 hours and 15 minutes + $dateStart = '2017-01-01 12:00'; + $dateEnd = '2017-01-02 14:15'; + + $effect = [ + Date::DATE_DIFFERENCE_UNIT_YEARS => 0, + Date::DATE_DIFFERENCE_UNIT_MONTHS => 0, + Date::DATE_DIFFERENCE_UNIT_DAYS => 1, + Date::DATE_DIFFERENCE_UNIT_HOURS => 2, + Date::DATE_DIFFERENCE_UNIT_MINUTES => 15, + ]; + + self::assertEquals($effect, Date::getDateDifference($dateStart, $dateEnd)); + self::assertEquals($effect, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd))); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_YEARS)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_YEARS)); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MONTHS)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MONTHS)); + + self::assertEquals(1, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_DAYS)); + self::assertEquals(1, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_DAYS)); + + self::assertEquals(2, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_HOURS)); + self::assertEquals(2, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_HOURS)); + + self::assertEquals(15, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MINUTES)); + self::assertEquals(15, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MINUTES)); + } + + public function testGetDateDifferenceOneMonthFortyOneDays(): void + { + // Difference of 1 month, 41 days, 4 hours and 30 minutes + $dateStart = '2017-01-01 12:00'; + $dateEnd = '2017-02-11 16:30'; + + $effect = [ + Date::DATE_DIFFERENCE_UNIT_YEARS => 0, + Date::DATE_DIFFERENCE_UNIT_MONTHS => 1, + Date::DATE_DIFFERENCE_UNIT_DAYS => 41, + Date::DATE_DIFFERENCE_UNIT_HOURS => 4, + Date::DATE_DIFFERENCE_UNIT_MINUTES => 30, + ]; + + self::assertEquals($effect, Date::getDateDifference($dateStart, $dateEnd)); + self::assertEquals($effect, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd))); + + self::assertEquals(0, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_YEARS)); + self::assertEquals(0, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_YEARS)); + + self::assertEquals(1, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MONTHS)); + self::assertEquals(1, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MONTHS)); + + self::assertEquals(41, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_DAYS)); + self::assertEquals(41, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_DAYS)); + + self::assertEquals(4, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_HOURS)); + self::assertEquals(4, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_HOURS)); + + self::assertEquals(30, Date::getDateDifference($dateStart, $dateEnd, Date::DATE_DIFFERENCE_UNIT_MINUTES)); + self::assertEquals(30, Date::getDateDifference(new DateTime($dateStart), new DateTime($dateEnd), Date::DATE_DIFFERENCE_UNIT_MINUTES)); } /** - * @param mixed $invalidInterval Empty value, e.g. "" - * @dataProvider provideEmptyValue + * @param bool $value The value which maybe is a date + * @dataProvider provideBooleanValue */ - public function testGetDatesCollectionInvalidInterval($invalidInterval) + public function testGetDateTimeBoolean($value): void { - self::assertEquals([], Date::getDatesCollection(new DateTime(), 2, $invalidInterval)); - self::assertEquals([], Date::getDatesCollection(new DateTime(), 2, 'lorem')); - self::assertEquals([], Date::getDatesCollection(new DateTime(), 2, '%d')); + self::assertFalse(Date::getDateTime($value)); } - public function testGetDatesCollection() + public function testGetDateTimeConcreteDates(): void + { + // Using the standard date format provided by the tested method + self::assertInstanceOf(DateTime::class, Date::getDateTime('2015-03-20')); + + // Using custom date format + self::assertInstanceOf(DateTime::class, Date::getDateTime('2015-03-20 11:30', false, 'Y-m-d H:i')); + self::assertInstanceOf(DateTime::class, Date::getDateTime('20.03.2015', false, 'd.m.Y')); + } + + /** + * @param mixed $value Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public function testGetDateTimeEmptyValue($value): void + { + self::assertFalse(Date::getDateTime($value)); + } + + /** + * @param mixed $value Incorrect source of DateTime + * @dataProvider provideIncorrectDateTimeValue + */ + public function testGetDateTimeIncorrectValue($value): void + { + self::assertFalse(Date::getDateTime($value)); + } + + /** + * @param DateTime $dateTime Instance of DateTime class + * @dataProvider provideDateTimeInstance + */ + public function testGetDateTimeInstanceDateTime(DateTime $dateTime): void + { + self::assertInstanceOf(DateTime::class, Date::getDateTime($dateTime)); + } + + /** + * @param string $relativeFormat Relative / compound format of DateTime + * @dataProvider provideDateTimeRelativeFormat + */ + public function testGetDateTimeRelativeFormats($relativeFormat): void { /* - * 1 date only + * Values based on relative / compound formats, but... without explicitly declaring them as compound + * (2nd argument set to false by default) + * + * http://php.net/manual/en/datetime.formats.compound.php */ + self::assertFalse(Date::getDateTime($relativeFormat)); + + /* + * Values based on relative / compound formats + * http://php.net/manual/en/datetime.formats.compound.php + */ + self::assertInstanceOf(DateTime::class, Date::getDateTime($relativeFormat, true)); + } + + public function testGetDatesCollection(): void + { + // 1 date only $effect = [ 1 => new DateTime('2017-01-02'), ]; self::assertEquals($effect, Date::getDatesCollection(new DateTime('2017-01-01'), 1)); - /* - * 3 dates with default date interval (days) - */ + // 3 dates with default date interval (days) $effect = [ 1 => new DateTime('2017-01-02'), 2 => new DateTime('2017-01-03'), @@ -585,9 +811,7 @@ class DateTest extends BaseTestCase self::assertEquals($effect, Date::getDatesCollection(new DateTime('2017-01-01'), 3)); - /* - * 3 dates with custom date interval (hours) - */ + // 3 dates with custom date interval (hours) $effect = [ 1 => new DateTime('2017-01-01 10:30'), 2 => new DateTime('2017-01-01 11:30'), @@ -596,9 +820,7 @@ class DateTest extends BaseTestCase self::assertEquals($effect, Date::getDatesCollection(new DateTime('2017-01-01 09:30'), 3, 'PT%dH')); - /* - * 3 dates with custom date interval (months) - */ + // 3 dates with custom date interval (months) $effect = [ 1 => new DateTime('2017-02-01'), 2 => new DateTime('2017-03-01'), @@ -608,7 +830,116 @@ class DateTest extends BaseTestCase self::assertEquals($effect, Date::getDatesCollection(new DateTime('2017-01-01'), 3, 'P%dM')); } - public function testGetRandomDateUsingDefaults() + /** + * @param mixed $invalidCount Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public function testGetDatesCollectionInvalidCount($invalidCount): void + { + self::assertEquals([], Date::getDatesCollection(new DateTime(), $invalidCount)); + self::assertEquals([], Date::getDatesCollection(new DateTime(), -1)); + } + + /** + * @param mixed $invalidInterval Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public function testGetDatesCollectionInvalidInterval($invalidInterval): void + { + self::assertEquals([], Date::getDatesCollection(new DateTime(), 2, $invalidInterval)); + self::assertEquals([], Date::getDatesCollection(new DateTime(), 2, 'lorem')); + self::assertEquals([], Date::getDatesCollection(new DateTime(), 2, '%d')); + } + + /** + * @param int $period The period, type of period. One of DatePeriod class constants, e.g. + * DatePeriod::LAST_WEEK. + * @param DatePeriod $expected Expected start and end date for given period + * + * @dataProvider provideCorrectPeriod + */ + public function testGetDatesForPeriod($period, DatePeriod $expected): void + { + self::assertEquals($expected, Date::getDatesForPeriod($period)); + } + + public function testGetDatesForPeriodUsingEmptyString(): void + { + self::assertNull(Date::getDatesForPeriod('')); + } + + /** + * @param int $period Incorrect period to verify + * @dataProvider provideIncorrectPeriod + */ + public function testGetDatesForPeriodUsingIncorrectPeriod($period): void + { + self::assertNull(Date::getDatesForPeriod($period)); + } + + /** + * @param int $year The year value + * @param int $month The month value + * @param int $day The day value + * + * @dataProvider provideYearMonthDay + */ + public function testGetDayOfWeek(int $year, int $month, int $day): void + { + self::assertMatchesRegularExpression('/^[0-6]{1}$/', (string) Date::getDayOfWeek($year, $month, $day)); + } + + /** + * @param int $year The year value + * @param int $month The month value + * @param int $day The day value + * + * @dataProvider provideIncorrectYearMonthDay + */ + public function testGetDayOfWeekIncorrectValues(int $year, int $month, int $day): void + { + $this->expectException(UnknownDatePartTypeException::class); + self::assertEmpty(Date::getDayOfWeek($year, $month, $day)); + } + + /** + * @param DateTime $startDate The start date. Start of the random date. + * @param int $start Start of random partition + * @param int $end End of random partition + * + * @dataProvider provideDataOfRandomDate + */ + public function testGetRandomDate(DateTime $startDate, $start, $end): void + { + $randomDate = Date::getRandomDate($startDate, $start, $end); + + $minDate = clone $startDate; + $maxDate = clone $startDate; + + $intervalMinDate = $minDate->add(new DateInterval(sprintf('P%dD', $start))); + $intervalMaxDate = $maxDate->add(new DateInterval(sprintf('P%dD', $end))); + + self::assertTrue($randomDate >= $intervalMinDate && $randomDate <= $intervalMaxDate); + } + + /** + * @param DateTime $startDate The start date. Start of the random date. + * @param int $start Start of random partition + * @param int $end End of random partition + * + * @dataProvider provideDataOfRandomDateIncorrectEnd + */ + public function testGetRandomDateIncorrectEnd(DateTime $startDate, $start, $end): void + { + $randomDate = Date::getRandomDate($startDate, $start, $end); + + $cloned = clone $startDate; + $intervalDate = $cloned->add(new DateInterval(sprintf('P%dD', $start))); + + self::assertTrue($randomDate >= $intervalDate && $randomDate <= $intervalDate); + } + + public function testGetRandomDateUsingDefaults(): void { $startDate = new DateTime(); $start = 1; @@ -625,432 +956,63 @@ class DateTest extends BaseTestCase } /** - * @param DateTime $startDate The start date. Start of the random date. - * @param int $start Start of random partition - * @param int $end End of random partition - * - * @dataProvider provideDataOfRandomDateIncorrectEnd - */ - public function testGetRandomDateIncorrectEnd(DateTime $startDate, $start, $end) - { - $randomDate = Date::getRandomDate($startDate, $start, $end); - - $cloned = clone $startDate; - $intervalDate = $cloned->add(new DateInterval(sprintf('P%dD', $start))); - - self::assertTrue($randomDate >= $intervalDate && $randomDate <= $intervalDate); - } - - /** - * @param DateTime $startDate The start date. Start of the random date. - * @param int $start Start of random partition - * @param int $end End of random partition - * - * @dataProvider provideDataOfRandomDate - */ - public function testGetRandomDate(DateTime $startDate, $start, $end) - { - $randomDate = Date::getRandomDate($startDate, $start, $end); - - $minDate = clone $startDate; - $maxDate = clone $startDate; - - $intervalMinDate = $minDate->add(new DateInterval(sprintf('P%dD', $start))); - $intervalMaxDate = $maxDate->add(new DateInterval(sprintf('P%dD', $end))); - - self::assertTrue($randomDate >= $intervalMinDate && $randomDate <= $intervalMaxDate); - } - - /** - * @param mixed $period Empty value, e.g. "" + * @param mixed $value Empty value, e.g. "" * @dataProvider provideEmptyValue */ - public function testGetDatesForPeriodUsingEmptyPeriod($period) + public function testIsValidDateEmptyDates($value): void { - self::assertNull(Date::getDatesForPeriod($period)); + self::assertFalse(Date::isValidDate($value)); } /** - * @param int $period Incorrect period to verify - * @dataProvider provideIncorrectPeriod + * @param mixed $value Empty source of date format + * @dataProvider provideEmptyValue */ - public function testGetDatesForPeriodUsingIncorrectPeriod($period) + public function testIsValidDateFormatEmptyFormats($value): void { - self::assertNull(Date::getDatesForPeriod($period)); + self::assertFalse(Date::isValidDateFormat($value)); } /** - * @param int $period The period, type of period. One of DatePeriod class constants, e.g. - * DatePeriod::LAST_WEEK. - * @param DatePeriod $expected Expected start and end date for given period - * - * @dataProvider provideCorrectPeriod + * @param mixed $format Invalid format of date + * @dataProvider provideInvalidDateFormats */ - public function testGetDatesForPeriod($period, DatePeriod $expected) + public function testIsValidDateFormatInvalidFormats($format): void { - self::assertEquals($expected, Date::getDatesForPeriod($period)); + self::assertFalse(Date::isValidDateFormat($format)); + } + + public function testIsValidDateFormatValidFormats(): void + { + self::assertTrue(Date::isValidDateFormat('Y')); + self::assertTrue(Date::isValidDateFormat('yy')); + self::assertTrue(Date::isValidDateFormat('M')); + self::assertTrue(Date::isValidDateFormat('i')); + self::assertTrue(Date::isValidDateFormat('l')); + self::assertTrue(Date::isValidDateFormat('l, d F')); + self::assertTrue(Date::isValidDateFormat('Y-m-d')); + self::assertTrue(Date::isValidDateFormat('H:i:s')); + self::assertTrue(Date::isValidDateFormat('Y/m/d H:i:s')); } /** - * Provides incorrect invalidCount of DateTime - * - * @return Generator + * @param mixed $value Incorrect source of DateTime + * @dataProvider provideIncorrectDateTimeValue */ - public function provideIncorrectDateTimeValue() + public function testIsValidDateIncorrectDates($value): void { - /* - * Incorrect one-character values - */ - yield['a']; - yield['m']; - - /* - * Incorrect strings - */ - yield['ss']; - yield['sss']; - yield['mm']; - yield['yy']; - yield['yyyy']; - - /* - * Incorrect integer values - */ - yield[1]; - yield[10]; - yield[15]; - yield[100]; - yield[1000]; - - /* - * Incorrect string / numeric values - */ - yield['1']; - yield['10']; - yield['15']; - yield['100']; - yield['1000']; - - /* - * Incorrect dates - */ - yield['0-0-0']; - yield['20-01-01']; - yield['2015-0-0']; - yield['2015-00-00']; - yield['2015-16-01']; + self::assertFalse(Date::isValidDate($value)); } - /** - * Provides invalid format of date - * - * @return Generator - */ - public function provideInvalidDateFormats() + public function testIsValidDateValidDates(): void { - yield[0]; - yield[9]; - yield['[]']; - yield['invalid']; - yield['Q']; - yield[',']; - yield['.']; - yield['aa###']; - yield['Y/m/d H:i:invalid']; - } + self::assertTrue(Date::isValidDate('2017-01-01')); + self::assertTrue(Date::isValidDate('2017-01-01 10:30', true)); + self::assertTrue(Date::isValidDate('2017-01-01 14:00', true)); - /** - * Provide empty dates for date difference - * - * @return Generator - */ - public function provideEmptyDatesForDateDifference() - { - yield[ - null, - null, - ]; - - yield[ - '', - '', - ]; - - yield[ - null, - new DateTime(), - ]; - - yield[ - new DateTime(), - null, - ]; - } - - /** - * Provides incorrect values of year, month and day - * - * @return Generator - */ - public function provideIncorrectYearMonthDay() - { - yield[ - null, - null, - null, - ]; - - yield[ - '', - '', - '', - ]; - - yield[ - 0, - 0, - 0, - ]; - - yield[ - -1, - -1, - -1, - ]; - - yield[ - 5000, - 50, - 50, - ]; - - yield[ - 2000, - 13, - 01, - ]; - - yield[ - 2000, - 01, - 40, - ]; - } - - /** - * Provides values of year, month and day - * - * @return Generator - */ - public function provideYearMonthDay() - { - yield[ - 2000, - 01, - 01, - ]; - - yield[ - 2000, - 1, - 1, - ]; - - yield[ - 2000, - 2, - 2, - ]; - - yield[ - 2000, - 6, - 1, - ]; - - yield[ - 2000, - 12, - 01, - ]; - - yield[ - 2000, - 12, - 1, - ]; - - yield[ - 2000, - 12, - 31, - ]; - } - - /** - * Provides data for the random date with incorrect end of random partition - * - * @return Generator - */ - public function provideDataOfRandomDateIncorrectEnd() - { - yield[ - new DateTime('2000-01-01'), - 100, - 1, - ]; - } - - /** - * Provides data for the random date - * - * @return Generator - */ - public function provideDataOfRandomDate() - { - yield[ - new DateTime('2000-01-01'), - 1, - 100, - ]; - - yield[ - new DateTime('2000-12-01'), - 1, - 100, - ]; - yield[ - new DateTime('2000-01-01'), - '1', - '100', - ]; - - yield[ - new DateTime('2000-12-01'), - '1', - '100', - ]; - - yield[ - new DateTime('2000-01-01'), - 10, - 50, - ]; - - yield[ - new DateTime('2000-12-01'), - 10, - 50, - ]; - } - - /** - * Provides incorrect period - * - * @return Generator - */ - public function provideIncorrectPeriod() - { - yield[-1]; - yield[0]; - yield[10]; - } - - /** - * Provides correct period - * - * @return Generator - */ - public function provideCorrectPeriod() - { - yield[ - DatePeriod::LAST_WEEK, - new DatePeriod( - (new DateTime('this week'))->sub(new DateInterval('P7D'))->setTime(0, 0, 0), - (new DateTime('this week'))->sub(new DateInterval('P1D'))->setTime(23, 59, 59) - ), - ]; - - yield[ - DatePeriod::THIS_WEEK, - new DatePeriod( - (new DateTime('this week'))->setTime(0, 0, 0), - (new DateTime('this week'))->add(new DateInterval('P6D'))->setTime(23, 59, 59) - ), - ]; - - yield[ - DatePeriod::NEXT_WEEK, - new DatePeriod( - (new DateTime('this week'))->add(new DateInterval('P7D'))->setTime(0, 0, 0), - (new DateTime('this week'))->add(new DateInterval('P7D')) - ->add(new DateInterval('P6D')) - ->setTime(23, 59, 59) - ), - ]; - - yield[ - DatePeriod::LAST_MONTH, - new DatePeriod( - (new DateTime('first day of last month'))->setTime(0, 0, 0), - (new DateTime('last day of last month'))->setTime(23, 59, 59) - ), - ]; - - yield[ - DatePeriod::THIS_MONTH, - new DatePeriod( - Date::getDatesForPeriod(DatePeriod::LAST_MONTH) - ->getEndDate() - ->add(new DateInterval('P1D')) - ->setTime(0, 0, 0), - Date::getDatesForPeriod(DatePeriod::NEXT_MONTH) - ->getStartDate() - ->sub(new DateInterval('P1D')) - ->setTime(23, 59, 59) - ), - ]; - - yield[ - DatePeriod::NEXT_MONTH, - new DatePeriod( - (new DateTime('first day of next month'))->setTime(0, 0, 0), - (new DateTime('last day of next month'))->setTime(23, 59, 59) - ), - ]; - - $lastYearStart = (new DateTime())->modify('-1 year'); - $lastYearEnd = (new DateTime())->modify('-1 year'); - $year = $lastYearStart->format('Y'); - - yield[ - DatePeriod::LAST_YEAR, - new DatePeriod( - $lastYearStart->setDate($year, 1, 1)->setTime(0, 0, 0), - $lastYearEnd->setDate($year, 12, 31)->setTime(23, 59, 59) - ), - ]; - - $year = (new DateTime())->format('Y'); - - yield[ - DatePeriod::THIS_YEAR, - new DatePeriod( - (new DateTime())->setDate($year, 1, 1)->setTime(0, 0, 0), - (new DateTime())->setDate($year, 12, 31)->setTime(23, 59, 59) - ), - ]; - - $nextYearStart = (new DateTime())->modify('1 year'); - $nextYearEnd = (new DateTime())->modify('1 year'); - $year = $nextYearStart->format('Y'); - - yield[ - DatePeriod::NEXT_YEAR, - new DatePeriod( - $nextYearStart->setDate($year, 1, 1)->setTime(0, 0, 0), - $nextYearEnd->setDate($year, 12, 31)->setTime(23, 59, 59) - ), - ]; + self::assertTrue(Date::isValidDate(new DateTime())); + self::assertTrue(Date::isValidDate(new DateTime('now'))); + self::assertTrue(Date::isValidDate(new DateTime('tomorrow'))); + self::assertTrue(Date::isValidDate(new DateTime('m'))); } } diff --git a/tests/Utilities/GeneratorUtilityTest.php b/tests/Utilities/GeneratorUtilityTest.php index 7642c87..18eb8a0 100644 --- a/tests/Utilities/GeneratorUtilityTest.php +++ b/tests/Utilities/GeneratorUtilityTest.php @@ -16,6 +16,9 @@ use Meritoo\Common\Utilities\GeneratorUtility; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\GeneratorUtility */ class GeneratorUtilityTest extends BaseTestCase { @@ -26,9 +29,7 @@ class GeneratorUtilityTest extends BaseTestCase public function testGetGeneratorElements() { - /* - * Generator that provides boolean value - */ + // Generator that provides boolean value $elements = [ [false], [true], @@ -46,15 +47,11 @@ class GeneratorUtilityTest extends BaseTestCase [[]], ]; - /* - * Generator that provides an empty value - */ + // Generator that provides an empty value $generator = $this->provideEmptyValue(); self::assertEquals($elements, GeneratorUtility::getGeneratorElements($generator)); - /* - * Generator that provides instance of DateTime class - */ + // Generator that provides instance of DateTime class $generator = $this->provideDateTimeInstance(); self::assertCount(4, GeneratorUtility::getGeneratorElements($generator)); } diff --git a/tests/Utilities/LocaleTest.php b/tests/Utilities/LocaleTest.php index 6a2c9ea..0f0d16e 100644 --- a/tests/Utilities/LocaleTest.php +++ b/tests/Utilities/LocaleTest.php @@ -18,9 +18,140 @@ use ReflectionException; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\Locale */ class LocaleTest extends BaseTestCase { + /** + * Provides category + * + * @return Generator + */ + public function provideCategoryLanguageCodeAndExpectedLocale() + { + yield [ + LC_ALL, + 'fr', + '', + 'fr_FR.UTF-8', + ]; + + yield [ + LC_COLLATE, + 'fr', + 'FR', + 'fr_FR.UTF-8', + ]; + + yield [ + LC_CTYPE, + 'en', + 'US', + 'en_US.UTF-8', + ]; + + yield [ + LC_NUMERIC, + 'en', + 'GB', + 'en_GB.UTF-8', + ]; + + yield [ + LC_MONETARY, + 'es', + '', + 'es_ES.UTF-8', + ]; + + yield [ + LC_MONETARY, + 'es', + 'ES', + 'es_ES.UTF-8', + ]; + + yield [ + LC_TIME, + 'it', + '', + 'it_IT.UTF-8', + ]; + + yield [ + LC_TIME, + 'it', + 'IT', + 'it_IT.UTF-8', + ]; + + yield [ + LC_TIME, + 'it', + 'it', + 'it_IT.UTF-8', + ]; + } + + /** + * Provides language, encoding and country code + * + * @return Generator + */ + public function provideLanguageEncodingAndCountryCode() + { + yield [ + 'fr', + '', + '', + 'fr_FR', + ]; + + yield [ + 'fr', + '', + 'UTF-8', + 'fr_FR.UTF-8', + ]; + + yield [ + 'fr', + 'FR', + '', + 'fr_FR', + ]; + + yield [ + 'fr', + 'FR', + 'UTF-8', + 'fr_FR.UTF-8', + ]; + + yield [ + 'en', + 'US', + '', + 'en_US', + ]; + + yield [ + 'en', + 'US', + 'UTF-8', + 'en_US.UTF-8', + ]; + + yield [ + 'en', + 'US', + 'ISO-8859-1', + 'en_US.ISO-8859-1', + ]; + } + /** * @throws ReflectionException */ @@ -29,56 +160,6 @@ class LocaleTest extends BaseTestCase static::assertHasNoConstructor(Locale::class); } - /** - * @param mixed $languageCode Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testGetLongFormEmptyLanguageCode($languageCode) - { - self::assertEquals('', Locale::getLongForm($languageCode)); - } - - /** - * @param string $languageCode Language code, in ISO 639-1 format. Short form of the locale, e.g. "fr". - * @param string $countryCode Country code, in ISO 3166-1 alpha-2 format, e.g. "FR" - * @param string $encoding Encoding of the final locale - * @param string $expected Expected long form of the locale - * - * @dataProvider provideLanguageEncodingAndCountryCode - */ - public function testGetLongForm($languageCode, $countryCode, $encoding, $expected) - { - self::assertEquals($expected, Locale::getLongForm($languageCode, $countryCode, $encoding)); - } - - /** - * @param mixed $emptyValue Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testSetLocaleEmptyCategoryAndLanguageCode($emptyValue) - { - self::assertFalse(Locale::setLocale($emptyValue, $emptyValue)); - } - - public function testSetLocaleIncorrectCategory() - { - self::assertFalse(Locale::setLocale(-1, 'en')); - } - - /** - * @param int $category Named constant specifying the category of the functions affected by the locale - * setting. It's the same constant as required by setlocale() function. - * @param string $languageCode Language code, in ISO 639-1 format. Short form of the locale, e.g. "fr". - * @param string $countryCode Country code, in ISO 3166-1 alpha-2 format, e.g. "FR" - * @param string $expectedLocale Expected locale - * - * @dataProvider provideCategoryLanguageCodeAndExpectedLocale - */ - public function testSetLocale($category, $languageCode, $countryCode, $expectedLocale) - { - self::assertEquals($expectedLocale, Locale::setLocale($category, $languageCode, $countryCode)); - } - /** * @param int $category Named constant specifying the category of the functions affected by the locale * setting. It's the same constant as required by setlocale() function. @@ -95,130 +176,52 @@ class LocaleTest extends BaseTestCase } /** - * Provides language, encoding and country code + * @param string $languageCode Language code, in ISO 639-1 format. Short form of the locale, e.g. "fr". + * @param string $countryCode Country code, in ISO 3166-1 alpha-2 format, e.g. "FR" + * @param string $encoding Encoding of the final locale + * @param string $expected Expected long form of the locale * - * @return Generator + * @dataProvider provideLanguageEncodingAndCountryCode */ - public function provideLanguageEncodingAndCountryCode() + public function testGetLongForm($languageCode, $countryCode, $encoding, $expected) { - yield[ - 'fr', - '', - '', - 'fr_FR', - ]; - - yield[ - 'fr', - '', - 'UTF-8', - 'fr_FR.UTF-8', - ]; - - yield[ - 'fr', - 'FR', - '', - 'fr_FR', - ]; - - yield[ - 'fr', - 'FR', - 'UTF-8', - 'fr_FR.UTF-8', - ]; - - yield[ - 'en', - 'US', - '', - 'en_US', - ]; - - yield[ - 'en', - 'US', - 'UTF-8', - 'en_US.UTF-8', - ]; - - yield[ - 'en', - 'US', - 'ISO-8859-1', - 'en_US.ISO-8859-1', - ]; + self::assertEquals($expected, Locale::getLongForm($languageCode, $countryCode, $encoding)); } /** - * Provides category - * - * @return Generator + * @param mixed $languageCode Empty value, e.g. "" + * @dataProvider provideEmptyValue */ - public function provideCategoryLanguageCodeAndExpectedLocale() + public function testGetLongFormEmptyLanguageCode($languageCode) { - yield[ - LC_ALL, - 'fr', - '', - 'fr_FR.UTF-8', - ]; + self::assertEquals('', Locale::getLongForm($languageCode)); + } - yield[ - LC_COLLATE, - 'fr', - 'FR', - 'fr_FR.UTF-8', - ]; + /** + * @param int $category Named constant specifying the category of the functions affected by the locale + * setting. It's the same constant as required by setlocale() function. + * @param string $languageCode Language code, in ISO 639-1 format. Short form of the locale, e.g. "fr". + * @param string $countryCode Country code, in ISO 3166-1 alpha-2 format, e.g. "FR" + * @param string $expectedLocale Expected locale + * + * @dataProvider provideCategoryLanguageCodeAndExpectedLocale + */ + public function testSetLocale($category, $languageCode, $countryCode, $expectedLocale) + { + self::assertEquals($expectedLocale, Locale::setLocale($category, $languageCode, $countryCode)); + } - yield[ - LC_CTYPE, - 'en', - 'US', - 'en_US.UTF-8', - ]; + /** + * @param mixed $emptyValue Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public function testSetLocaleEmptyCategoryAndLanguageCode($emptyValue) + { + self::assertFalse(Locale::setLocale($emptyValue, $emptyValue)); + } - yield[ - LC_NUMERIC, - 'en', - 'GB', - 'en_GB.UTF-8', - ]; - - yield[ - LC_MONETARY, - 'es', - '', - 'es_ES.UTF-8', - ]; - - yield[ - LC_MONETARY, - 'es', - 'ES', - 'es_ES.UTF-8', - ]; - - yield[ - LC_TIME, - 'it', - '', - 'it_IT.UTF-8', - ]; - - yield[ - LC_TIME, - 'it', - 'IT', - 'it_IT.UTF-8', - ]; - - yield[ - LC_TIME, - 'it', - 'it', - 'it_IT.UTF-8', - ]; + public function testSetLocaleIncorrectCategory() + { + self::assertFalse(Locale::setLocale(-1, 'en')); } } diff --git a/tests/Utilities/MimeTypesTest.php b/tests/Utilities/MimeTypesTest.php index ee0e85e..829495f 100644 --- a/tests/Utilities/MimeTypesTest.php +++ b/tests/Utilities/MimeTypesTest.php @@ -17,14 +17,311 @@ use Meritoo\Common\Utilities\MimeTypes; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\MimeTypes */ class MimeTypesTest extends BaseTestCase { + /** + * Provides real file path to get information if the file is an image + * + * @return Generator + */ + public function provideExistingFilePathToCheckIsImagePath() + { + yield [ + $this->getFilePathForTesting('minion.jpg'), + true, + ]; + + yield [ + $this->getFilePathForTesting('lorem-ipsum.txt'), + false, + ]; + } + + /** + * Provides real file path to get mime type + * + * @return Generator + */ + public function provideFilePathToGetMimeTypeOfRealFile() + { + yield [ + $this->getFilePathForTesting('minion.jpg'), + 'image/jpeg', + ]; + + yield [ + $this->getFilePathForTesting('lorem-ipsum.txt'), + 'text/plain', + ]; + } + + /** + * Provides mime type of image + * + * @return Generator + */ + public function provideImageMimeType() + { + yield ['image/bmp']; + yield ['image/jpeg']; + yield ['image/png']; + yield ['image/tiff']; + yield ['image/vnd.microsoft.icon']; + yield ['image/x-rgb']; + } + + /** + * Provides existing mime type used to get multiple, more than one extension + * + * @return Generator + */ + public function provideMimeTypeToGetMultipleExtension() + { + yield [ + 'application/postscript', + [ + 'ai', + 'eps', + 'ps', + ], + ]; + + yield [ + 'audio/midi', + [ + 'mid', + 'midi', + 'kar', + 'rmi', + ], + ]; + + yield [ + 'image/jpeg', + [ + 'jpeg', + 'jpe', + 'jpg', + ], + ]; + + yield [ + 'text/html', + [ + 'html', + 'htm', + ], + ]; + + yield [ + 'text/plain', + [ + 'txt', + 'text', + 'conf', + 'def', + 'list', + 'log', + 'in', + ], + ]; + + yield [ + 'video/mp4', + [ + 'mp4', + 'mp4v', + 'mpg4', + 'm4v', + ], + ]; + } + + /** + * Provides existing mime type used to get single, one extension + * + * @return Generator + */ + public function provideMimeTypeToGetSingleExtension() + { + yield [ + 'application/x-7z-compressed', + '7z', + ]; + + yield [ + 'application/json', + 'json', + ]; + + yield [ + 'application/zip', + 'zip', + ]; + } + + /** + * Provides mime types used to get extensions + * + * @return Generator + */ + public function provideMimesTypesToGetExtensions() + { + yield [ + [ + 'application/x-7z-compressed', + 'application/json', + ], + [ + 'application/x-7z-compressed' => '7z', + 'application/json' => 'json', + ], + ]; + + yield [ + [ + 'application/mathematica', + 'application/xml', + 'audio/mp4', + 'video/mp4', + ], + [ + 'application/mathematica' => [ + 'ma', + 'nb', + 'mb', + ], + 'application/xml' => [ + 'xml', + 'xsl', + ], + 'audio/mp4' => 'mp4a', + 'video/mp4' => [ + 'mp4', + 'mp4v', + 'mpg4', + 'm4v', + ], + ], + ]; + } + + /** + * Provides mime types used to get extensions as upper case + * + * @return Generator + */ + public function provideMimesTypesToGetExtensionsUpperCase() + { + yield [ + [ + 'application/x-7z-compressed', + 'application/json', + ], + [ + 'application/x-7z-compressed' => '7Z', + 'application/json' => 'JSON', + ], + ]; + + yield [ + [ + 'application/xml', + 'audio/mp4', + 'text/html', + 'video/mp4', + ], + [ + 'application/xml' => [ + 'XML', + 'XSL', + ], + 'audio/mp4' => 'MP4A', + 'text/html' => [ + 'HTML', + 'HTM', + ], + 'video/mp4' => [ + 'MP4', + 'MP4V', + 'MPG4', + 'M4V', + ], + ], + ]; + } + + /** + * Provides mime type of non-image + * + * @return Generator + */ + public function provideNonImageMimeType() + { + yield ['application/rtf']; + yield ['audio/mp4']; + yield ['text/plain']; + yield ['text/html']; + } + + /** + * Provides not existing mime type + * + * @return Generator + */ + public function provideNotExistingMimeType() + { + yield ['lorem/ipsum']; + yield ['dolor']; + yield ['x/y/z']; + } + + /** + * Provides not existing mime types + * + * @return Generator + */ + public function provideNotExistingMimeTypes() + { + yield [ + [], + ]; + + yield [ + [ + '', + null, + false, + 0, + ], + ]; + + yield [ + [ + 'lorem/ipsum', + 'dolor/sit', + ], + ]; + } + public function testConstructor() { static::assertHasNoConstructor(MimeTypes::class); } + /** + * @param bool $mimeType The mime type, e.g. "video/mpeg" + * @dataProvider provideBooleanValue + */ + public function testGetExtensionBooleanMimeType($mimeType) + { + self::assertEquals('', MimeTypes::getExtension($mimeType)); + } + /** * @param mixed $mimeType Empty value, e.g. "" * @dataProvider provideEmptyValue @@ -35,12 +332,14 @@ class MimeTypesTest extends BaseTestCase } /** - * @param bool $mimeType The mime type, e.g. "video/mpeg" - * @dataProvider provideBooleanValue + * @param string $mimeType The mime type, e.g. "video/mpeg" + * @param array $extensions Expected extensions + * + * @dataProvider provideMimeTypeToGetMultipleExtension */ - public function testGetExtensionBooleanMimeType($mimeType) + public function testGetExtensionMultiple($mimeType, $extensions) { - self::assertEquals('', MimeTypes::getExtension($mimeType)); + self::assertEquals($extensions, MimeTypes::getExtension($mimeType)); } /** @@ -64,14 +363,14 @@ class MimeTypesTest extends BaseTestCase } /** - * @param string $mimeType The mime type, e.g. "video/mpeg" - * @param array $extensions Expected extensions + * @param array $mimesTypes The mimes types, e.g. ['video/mpeg', 'image/jpeg'] + * @param array $extensions Expected extensions * - * @dataProvider provideMimeTypeToGetMultipleExtension + * @dataProvider provideMimesTypesToGetExtensions */ - public function testGetExtensionMultiple($mimeType, $extensions) + public function testGetExtensions($mimesTypes, $extensions) { - self::assertEquals($extensions, MimeTypes::getExtension($mimeType)); + self::assertEquals($extensions, MimeTypes::getExtensions($mimesTypes)); } /** @@ -83,17 +382,6 @@ class MimeTypesTest extends BaseTestCase self::assertEquals([], MimeTypes::getExtensions($mimesTypes)); } - /** - * @param array $mimesTypes The mimes types, e.g. ['video/mpeg', 'image/jpeg'] - * @param array $extensions Expected extensions - * - * @dataProvider provideMimesTypesToGetExtensions - */ - public function testGetExtensions($mimesTypes, $extensions) - { - self::assertEquals($extensions, MimeTypes::getExtensions($mimesTypes)); - } - /** * @param array $mimesTypes The mimes types, e.g. ['video/mpeg', 'image/jpeg'] * @param array $extensions Expected extensions @@ -135,12 +423,12 @@ class MimeTypesTest extends BaseTestCase } /** - * @param string $mimeType Not existing mime type, e.g. "lorem/ipsum" - * @dataProvider provideNotExistingMimeType + * @param string $mimeType Mime type of image, e.g. "image/jpeg" + * @dataProvider provideImageMimeType */ - public function testIsImageNotExistingMimeType($mimeType) + public function testIsImageImageMimeType($mimeType) { - self::assertFalse(MimeTypes::isImage($mimeType)); + self::assertTrue(MimeTypes::isImage($mimeType)); } /** @@ -152,6 +440,15 @@ class MimeTypesTest extends BaseTestCase self::assertFalse(MimeTypes::isImage($mimeType)); } + /** + * @param string $mimeType Not existing mime type, e.g. "lorem/ipsum" + * @dataProvider provideNotExistingMimeType + */ + public function testIsImageNotExistingMimeType($mimeType) + { + self::assertFalse(MimeTypes::isImage($mimeType)); + } + /** * @param mixed $path Empty value, e.g. "" * @dataProvider provideEmptyValue @@ -161,15 +458,6 @@ class MimeTypesTest extends BaseTestCase self::assertFalse(MimeTypes::isImagePath($path)); } - /** - * @param mixed $path Path of not existing file, e.g. "lorem/ipsum.jpg" - * @dataProvider provideNotExistingFilePath - */ - public function testIsImagePathNotExistingPath($path) - { - self::assertFalse(MimeTypes::isImagePath($path)); - } - /** * @param string $path Path of the file to check * @param bool $isImage Expected information if the file is an image @@ -182,296 +470,11 @@ class MimeTypesTest extends BaseTestCase } /** - * @param string $mimeType Mime type of image, e.g. "image/jpeg" - * @dataProvider provideImageMimeType + * @param mixed $path Path of not existing file, e.g. "lorem/ipsum.jpg" + * @dataProvider provideNotExistingFilePath */ - public function testIsImageImageMimeType($mimeType) + public function testIsImagePathNotExistingPath($path) { - self::assertTrue(MimeTypes::isImage($mimeType)); - } - - /** - * Provides not existing mime type - * - * @return Generator - */ - public function provideNotExistingMimeType() - { - yield['lorem/ipsum']; - yield['dolor']; - yield['x/y/z']; - } - - /** - * Provides mime type of non-image - * - * @return Generator - */ - public function provideNonImageMimeType() - { - yield['application/rtf']; - yield['audio/mp4']; - yield['text/plain']; - yield['text/html']; - } - - /** - * Provides mime type of image - * - * @return Generator - */ - public function provideImageMimeType() - { - yield['image/bmp']; - yield['image/jpeg']; - yield['image/png']; - yield['image/tiff']; - yield['image/vnd.microsoft.icon']; - yield['image/x-rgb']; - } - - /** - * Provides existing mime type used to get single, one extension - * - * @return Generator - */ - public function provideMimeTypeToGetSingleExtension() - { - yield[ - 'application/x-7z-compressed', - '7z', - ]; - - yield[ - 'application/json', - 'json', - ]; - - yield[ - 'application/zip', - 'zip', - ]; - } - - /** - * Provides existing mime type used to get multiple, more than one extension - * - * @return Generator - */ - public function provideMimeTypeToGetMultipleExtension() - { - yield[ - 'application/postscript', - [ - 'ai', - 'eps', - 'ps', - ], - ]; - - yield[ - 'audio/midi', - [ - 'mid', - 'midi', - 'kar', - 'rmi', - ], - ]; - - yield[ - 'image/jpeg', - [ - 'jpeg', - 'jpe', - 'jpg', - ], - ]; - - yield[ - 'text/html', - [ - 'html', - 'htm', - ], - ]; - - yield[ - 'text/plain', - [ - 'txt', - 'text', - 'conf', - 'def', - 'list', - 'log', - 'in', - ], - ]; - - yield[ - 'video/mp4', - [ - 'mp4', - 'mp4v', - 'mpg4', - 'm4v', - ], - ]; - } - - /** - * Provides not existing mime types - * - * @return Generator - */ - public function provideNotExistingMimeTypes() - { - yield[ - [], - ]; - - yield[ - [ - '', - null, - false, - 0, - ], - ]; - - yield[ - [ - 'lorem/ipsum', - 'dolor/sit', - ], - ]; - } - - /** - * Provides mime types used to get extensions - * - * @return Generator - */ - public function provideMimesTypesToGetExtensions() - { - yield[ - [ - 'application/x-7z-compressed', - 'application/json', - ], - [ - 'application/x-7z-compressed' => '7z', - 'application/json' => 'json', - ], - ]; - - yield[ - [ - 'application/mathematica', - 'application/xml', - 'audio/mp4', - 'video/mp4', - ], - [ - 'application/mathematica' => [ - 'ma', - 'nb', - 'mb', - ], - 'application/xml' => [ - 'xml', - 'xsl', - ], - 'audio/mp4' => 'mp4a', - 'video/mp4' => [ - 'mp4', - 'mp4v', - 'mpg4', - 'm4v', - ], - ], - ]; - } - - /** - * Provides mime types used to get extensions as upper case - * - * @return Generator - */ - public function provideMimesTypesToGetExtensionsUpperCase() - { - yield[ - [ - 'application/x-7z-compressed', - 'application/json', - ], - [ - 'application/x-7z-compressed' => '7Z', - 'application/json' => 'JSON', - ], - ]; - - yield[ - [ - 'application/xml', - 'audio/mp4', - 'text/html', - 'video/mp4', - ], - [ - 'application/xml' => [ - 'XML', - 'XSL', - ], - 'audio/mp4' => 'MP4A', - 'text/html' => [ - 'HTML', - 'HTM', - ], - 'video/mp4' => [ - 'MP4', - 'MP4V', - 'MPG4', - 'M4V', - ], - ], - ]; - } - - /** - * Provides real file path to get mime type - * - * @return Generator - */ - public function provideFilePathToGetMimeTypeOfRealFile() - { - yield[ - $this->getFilePathForTesting('minion.jpg'), - 'image/jpeg', - ]; - - yield[ - $this->getFilePathForTesting('lorem-ipsum.txt'), - 'text/plain', - ]; - } - - /** - * Provides real file path to get information if the file is an image - * - * @return Generator - */ - public function provideExistingFilePathToCheckIsImagePath() - { - yield[ - $this->getFilePathForTesting('minion.jpg'), - true, - ]; - - yield[ - $this->getFilePathForTesting('lorem-ipsum.txt'), - false, - ]; + self::assertFalse(MimeTypes::isImagePath($path)); } } diff --git a/tests/Utilities/MiscellaneousTest.php b/tests/Utilities/MiscellaneousTest.php index 70c36c7..7705cb0 100644 --- a/tests/Utilities/MiscellaneousTest.php +++ b/tests/Utilities/MiscellaneousTest.php @@ -21,6 +21,9 @@ use stdClass; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\Miscellaneous */ class MiscellaneousTest extends BaseTestCase { @@ -29,25 +32,978 @@ class MiscellaneousTest extends BaseTestCase private $stringDotSeparated; private $stringWithoutSpaces; - public function testConstructor() + public function provideDataToReplaceWithQuoteStrings() { - static::assertHasNoConstructor(Miscellaneous::class); + yield [ + 'An empty string as subject', + '', + 'test', + 'another test', + '', + ]; + + yield [ + 'An empty string to search', + 'test', + '', + 'another test', + 'test', + ]; + + yield [ + 'An empty string as replacement', + 'test', + 'another test', + '', + 'test', + ]; + + yield [ + 'Replace 1 not existing word in 1 sentence (nothing to replace)', + 'Lorem ipsum dolor sit amet', + 'plum', + 'commodo', + 'Lorem ipsum dolor sit amet', + ]; + + yield [ + 'Replace 1 word in 1 sentence', + 'Lorem ipsum dolor sit amet', + 'ipsum', + 'commodo', + 'Lorem \'commodo\' dolor sit amet', + ]; + + yield [ + 'Replace 1 word in 2 sentences', + [ + 'Lorem ipsum dolor sit amet', + 'Maecenas sed diam eget risus varius blandit sit amet', + ], + 'amet', + 'commodo', + [ + 'Lorem ipsum dolor sit \'commodo\'', + 'Maecenas sed diam eget risus varius blandit sit \'commodo\'', + ], + ]; + + yield [ + '1 pattern (word -> "")', + 'Lorem ipsum dolor sit amet', + '|ipsum|', + '', + 'Lorem \'\' dolor sit amet', + ]; + + yield [ + '1 pattern (word -> word)', + 'Lorem ipsum dolor sit amet', + '|ipsum|', + 'commodo', + 'Lorem \'commodo\' dolor sit amet', + ]; + + yield [ + '2 patterns (word -> word)', + 'Lorem ipsum dolor sit amet', + [ + '|ipsum|', + '|amet|', + ], + [ + 'commodo', + 'egestas', + ], + 'Lorem \'commodo\' dolor sit \'egestas\'', + ]; } - public function testGetDirectoryContent() + /** + * Provides empty value used to fill missing zeros + * + * @return Generator + */ + public function provideEmptyValueToFillMissingZeros() { - $directoryPath = __DIR__ . '/../'; - $filePath = __FILE__; + yield ['']; + yield [' ']; + yield [null]; + yield [false]; + yield [[]]; + } - self::assertNull(Miscellaneous::getDirectoryContent(null)); - self::assertNull(Miscellaneous::getDirectoryContent('')); + public function provideEmptyValuesToReplace() + { + yield [ + 'An empty string as subject', + '', + 'test', + 'another test', + '', + ]; - self::assertGreaterThanOrEqual(0, count(Miscellaneous::getDirectoryContent($directoryPath))); - self::assertGreaterThanOrEqual(0, count(Miscellaneous::getDirectoryContent($directoryPath, true))); - self::assertGreaterThanOrEqual(0, count(Miscellaneous::getDirectoryContent($directoryPath, true, 5))); + yield [ + 'An empty array as subject', + [], + 'test', + 'another test', + [], + ]; - self::assertGreaterThanOrEqual(0, count(Miscellaneous::getDirectoryContent($filePath))); - self::assertGreaterThanOrEqual(0, count(Miscellaneous::getDirectoryContent($filePath, true))); + yield [ + 'Null as subject', + null, + 'test', + 'another test', + null, + ]; + + yield [ + 'An empty string to search', + 'test', + '', + 'another test', + 'test', + ]; + + yield [ + 'An empty array to search', + 'test', + [], + 'another test', + 'test', + ]; + + yield [ + 'Null to search', + 'test', + null, + 'another test', + 'test', + ]; + + yield [ + 'An empty string as replacement', + 'test', + 'another test', + '', + 'test', + ]; + + yield [ + 'An empty array as replacement', + 'test', + 'another test', + [], + 'test', + ]; + + yield [ + 'Null as replacement', + 'test', + 'another test', + null, + 'test', + ]; + } + + /** + * Provides names of files + * + * @return Generator + */ + public function provideFileNames() + { + yield [ + 'Lorem.ipsum-dolor.sit.JPG', + 'Lorem.ipsum-dolor.sit', + ]; + + yield [ + 'lets-test.doc', + 'lets-test', + ]; + + yield [ + 'something/else.txt', + 'something/else', + ]; + + yield [ + 'public/js/user.js', + 'public/js/user', + ]; + } + + public function provideFilePath(): ?Generator + { + yield [ + 'Path with file', + 'lorem/ipsum-dolor/sit.amet.JPG', + 'sit.amet.JPG', + ]; + + yield [ + 'Path with complicated name of file', + 'lorem/ipsum-dolor/this-1_2 3 & my! 4+file.jpg', + 'this-1_2 3 & my! 4+file.jpg', + ]; + + yield [ + 'Path without file', + 'lorem/ipsum-dolor/sit-amet', + '', + ]; + + yield [ + 'Path with a dot "." in name of directory', + 'lorem/ipsum.dolor/sit.amet.JPG', + 'sit.amet.JPG', + ]; + + yield [ + 'Relative path', + 'lorem/ipsum/../dolor/sit.amet.JPG', + 'sit.amet.JPG', + ]; + } + + public function provideGreatestCommonDivisor(): ?Generator + { + yield [ + 0, + 0, + 0, + ]; + + yield [ + 1, + 1, + 1, + ]; + + yield [ + 5, + 3, + 1, + ]; + + yield [ + 6, + 3, + 3, + ]; + + yield [ + 12, + 9, + 3, + ]; + + yield [ + 20, + 12, + 4, + ]; + + yield [ + 120, + 80, + 40, + ]; + } + + public function provideLastElementOfString(): ?Generator + { + yield [ + 'An empty string', + '', + '', + null, + ]; + + yield [ + 'One-character string', + 'a', + ',', + null, + ]; + + yield [ + 'String without given separator', + 'abc', + ',', + null, + ]; + + yield [ + 'Simple, short string', + 'a, b, c', + ',', + ' c', + ]; + + yield [ + 'A sentence', + 'Lorem ipsum - dolor sit - amet, consectetur adipiscing - elit.', + '-', + ' elit.', + ]; + + yield [ + 'A class namespace', + 'This\\Is\\My\\Class\\For\\Testing', + '\\', + 'Testing', + ]; + } + + /** + * Provides number used to fill missing zeros + * + * @return Generator + */ + public function provideNumberToFillMissingZeros() + { + yield [ + 0, + 0, + true, + '0', + ]; + + yield [ + 0, + 0, + false, + '0', + ]; + + yield [ + 1, + 0, + true, + '1', + ]; + + yield [ + 1, + 0, + false, + '1', + ]; + + yield [ + 1, + 1, + true, + '1', + ]; + + yield [ + 1, + 1, + false, + '1', + ]; + + yield [ + 123, + 5, + true, + '00123', + ]; + + yield [ + 123, + 5, + false, + '12300', + ]; + } + + /** + * Provides path used to remove the ending directory's separator + * + * @return Generator + */ + public function providePathsToRemoveEndingDirectorySeparator() + { + yield [ + 'lorem/ipsum/dolor/', + '/', + 'lorem/ipsum/dolor', + ]; + + yield [ + 'lorem/ipsum/dolor', + '/', + 'lorem/ipsum/dolor', + ]; + + yield [ + 'lorem\ipsum\dolor\\', + '\\', + 'lorem\ipsum\dolor', + ]; + + yield [ + 'lorem\ipsum\dolor', + '\\', + 'lorem\ipsum\dolor', + ]; + + yield [ + 'lorem;ipsum;dolor;', + ';', + 'lorem;ipsum;dolor', + ]; + + yield [ + 'lorem;ipsum;dolor', + ';', + 'lorem;ipsum;dolor', + ]; + } + + /** + * Provides path used to remove the starting / beginning directory's separator + * + * @return Generator + */ + public function providePathsToRemoveStartingDirectorySeparator() + { + yield [ + '/lorem/ipsum/dolor', + '/', + 'lorem/ipsum/dolor', + ]; + + yield [ + 'lorem/ipsum/dolor', + '/', + 'lorem/ipsum/dolor', + ]; + + yield [ + '\\lorem\ipsum\dolor', + '\\', + 'lorem\ipsum\dolor', + ]; + + yield [ + 'lorem\ipsum\dolor', + '\\', + 'lorem\ipsum\dolor', + ]; + + yield [ + ';lorem;ipsum;dolor', + ';', + 'lorem;ipsum;dolor', + ]; + + yield [ + 'lorem;ipsum;dolor', + ';', + 'lorem;ipsum;dolor', + ]; + } + + public function provideRegexToReplace() + { + yield [ + 'Different count of strings to search and replace - 1st part', + 'Lorem ipsum dolor sit amet', + [ + '|ipsum|', + ], + 'commodo', + 'Lorem ipsum dolor sit amet', + ]; + + yield [ + 'Different count of strings to search and replace - 2nd part', + 'Lorem ipsum dolor sit amet', + '|ipsum|', + [ + 'commodo', + ], + 'Lorem ipsum dolor sit amet', + ]; + + yield [ + '1 pattern (word -> "")', + 'Lorem ipsum dolor sit amet', + '|ipsum|', + '', + 'Lorem dolor sit amet', + ]; + + yield [ + '1 pattern (word -> word)', + 'Lorem ipsum dolor sit amet', + '|ipsum|', + 'commodo', + 'Lorem commodo dolor sit amet', + ]; + + yield [ + '2 patterns (word -> word)', + 'Lorem ipsum dolor sit amet', + [ + '|ipsum|', + '|amet|', + ], + [ + 'commodo', + 'egestas', + ], + 'Lorem commodo dolor sit egestas', + ]; + + yield [ + '1 word in 2 sentences', + [ + 'Lorem ipsum dolor sit amet', + 'Maecenas sed diam eget risus varius blandit sit amet', + ], + '|amet|', + 'commodo', + [ + 'Lorem ipsum dolor sit commodo', + 'Maecenas sed diam eget risus varius blandit sit commodo', + ], + ]; + + yield [ + '2 words in 2 sentences', + [ + 'Lorem ipsum dolor sit amet', + 'Maecenas sed diam eget risus varius blandit sit amet', + ], + [ + '|ipsum|', + '|amet|', + ], + [ + 'commodo', + 'egestas', + ], + [ + 'Lorem commodo dolor sit egestas', + 'Maecenas sed diam eget risus varius blandit sit egestas', + ], + ]; + } + + public function provideStringElements(): ?Generator + { + yield [ + 'An empty string', + '', + '', + [], + ]; + + yield [ + 'One-character string', + 'a', + ',', + [], + ]; + + yield [ + 'String without given separator', + 'abc', + ',', + [], + ]; + + yield [ + 'Simple, short string', + 'a, b, c', + ',', + [ + 'a', + ' b', + ' c', + ], + ]; + + yield [ + 'A sentence', + 'Lorem ipsum - dolor sit - amet, consectetur adipiscing - elit.', + '-', + [ + 'Lorem ipsum ', + ' dolor sit ', + ' amet, consectetur adipiscing ', + ' elit.', + ], + ]; + + yield [ + 'A class namespace', + 'This\\Is\\My\\Class\\For\\Testing', + '\\', + [ + 'This', + 'Is', + 'My', + 'Class', + 'For', + 'Testing', + ], + ]; + } + + /** + * Provides string to convert to camel case + * + * @return Generator + */ + public function provideStringToCamelCase() + { + yield [ + 'lorem ipsum', + ' ', + 'loremIpsum', + ]; + + yield [ + 'Lorem ipSum Dolor', + ' ', + 'loremIpsumDolor', + ]; + + yield [ + 'abc;def;ghi', + ';', + 'abcDefGhi', + ]; + } + + /** + * Provides string to convert characters to latin characters and lower cased and human-readable + * + * @return Generator + */ + public function provideStringToLatinLowerCaseHuman() + { + yield [ + 'asuo', + 'ąśüö', + ]; + + yield [ + 'eoaslzzcn', + 'ęóąśłżźćń', + ]; + + yield [ + 'loremipsum', + 'loremipsum', + ]; + + yield [ + 'lorem-ipsum', + 'lorem ipsum', + ]; + + yield [ + 'lorem-ipsum', + 'lorem;ipsum', + ]; + + yield [ + 'lorem1ipsum2', + 'lorem1ipsum2', + ]; + + yield [ + 'lorem_ipsum', + 'lorem ipsum', + '_', + ]; + + yield [ + 'lorem-ipsum', + 'lorem-ipsum', + ]; + + yield [ + 'lorem ipsum', + 'Lorem!Ipsum', + ' ', + ]; + + yield [ + 'lorem ipsum', + 'Lorem.Ipsum', + ' ', + ]; + + yield [ + 'lorem|ipsum', + 'Lorem.Ipsum', + '|', + ]; + + yield [ + 'lorem-ipsum', + 'LoremIpsum', + ]; + + yield [ + 'lorem.ipsum', + 'Lorem Ipsum', + '.', + ]; + + yield [ + 'lorem.ipsum', + 'Lorem=Ipsum', + '.', + ]; + + yield [ + 'lorem-ipsum-d', + 'LoremIpsumD', + ]; + + yield [ + 'lorem.ipsum.d', + 'LoremIpsumD', + '.', + ]; + } + + /** + * Provides string to convert characters to latin characters and not lower cased and not human-readable + * + * @return Generator + */ + public function provideStringToLatinNotLowerCaseHuman(): ?Generator + { + yield [ + 'asuo', + 'ąśüö', + ]; + + yield [ + 'eoaslzzcn', + 'ęóąśłżźćń', + ]; + + yield [ + 'loremipsum', + 'loremipsum', + ]; + + yield [ + 'LoremIpsum', + 'LoremIpsum', + ]; + + yield [ + 'Lorem.Ipsum', + 'Lorem Ipsum', + '.', + ]; + + yield [ + 'Lorem.Ipsum', + 'Lorem=Ipsum', + '.', + ]; + + yield [ + 'LoremIpsumD', + 'LoremIpsumD', + ]; + + yield [ + 'LoremIpsumD', + 'LoremIpsumD', + '.', + ]; + + yield [ + 'lorem-ipsum', + 'lorem ipsum', + ]; + + yield [ + 'lorem-ipsum', + 'lorem;ipsum', + ]; + + yield [ + 'lorem1ipsum2', + 'lorem1ipsum2', + ]; + + yield [ + 'lorem_ipsum', + 'lorem ipsum', + '_', + ]; + + yield [ + 'LoremIpsum', + 'LoremIpsum', + ]; + + yield [ + 'Lorem Ipsum', + 'Lorem!Ipsum', + ' ', + ]; + + yield [ + 'Lorem Ipsum', + 'Lorem.Ipsum', + ' ', + ]; + + yield [ + 'Lorem|Ipsum', + 'Lorem.Ipsum', + '|', + ]; + } + + public function provideStringToRemoveMarginalCharacter(): ?Generator + { + yield [ + 'An empty string - remove last character', + '', + true, + null, + ]; + + yield [ + 'An empty string - remove first character', + '', + false, + null, + ]; + + yield [ + 'Simple, two words - remove last character', + 'Lorem ipsum', + true, + 'Lorem ipsu', + ]; + + yield [ + 'Simple, two words - remove first character', + 'Lorem ipsum', + false, + 'orem ipsum', + ]; + + yield [ + 'Two sentences - remove last character', + 'Etiam ullamcorper. Suspendisse a pellentesque dui, non felis.', + true, + 'Etiam ullamcorper. Suspendisse a pellentesque dui, non felis', + ]; + + yield [ + 'Two sentences - remove first character', + 'Etiam ullamcorper. Suspendisse a pellentesque dui, non felis.', + false, + 'tiam ullamcorper. Suspendisse a pellentesque dui, non felis.', + ]; + } + + public function provideStringsToReplace() + { + yield [ + 'Different count of strings to search and replace - 1st part', + 'Lorem ipsum dolor sit amet', + [ + 'ipsum', + ], + 'commodo', + 'Lorem ipsum dolor sit amet', + ]; + + yield [ + 'Different count of strings to search and replace - 2nd part', + 'Lorem ipsum dolor sit amet', + 'ipsum', + [ + 'commodo', + ], + 'Lorem commodo dolor sit amet', + ]; + + yield [ + 'Replace 1 not existing word in 1 sentence (nothing to replace)', + 'Lorem ipsum dolor sit amet', + 'plum', + 'commodo', + 'Lorem ipsum dolor sit amet', + ]; + + yield [ + 'Replace 1 word in 1 sentence', + 'Lorem ipsum dolor sit amet', + 'ipsum', + 'commodo', + 'Lorem commodo dolor sit amet', + ]; + + yield [ + 'Replace 1 not existing word in 2 sentences (nothing to replace)', + [ + 'Lorem ipsum dolor sit amet', + 'Maecenas sed diam eget risus varius blandit sit amet', + ], + 'plum', + 'commodo', + [ + 'Lorem ipsum dolor sit amet', + 'Maecenas sed diam eget risus varius blandit sit amet', + ], + ]; + + yield [ + 'Replace 1 word in 2 sentences', + [ + 'Lorem ipsum dolor sit amet', + 'Maecenas sed diam eget risus varius blandit sit amet', + ], + 'amet', + 'commodo', + [ + 'Lorem ipsum dolor sit commodo', + 'Maecenas sed diam eget risus varius blandit sit commodo', + ], + ]; + } + + public function testBreakLongText() + { + self::assertEquals('Lorem ipsum dolor sit
amet, consectetur
adipiscing
elit', Miscellaneous::breakLongText($this->stringCommaSeparated, 20)); + self::assertEquals('Lorem ipsum dolor sit---amet, consectetur---adipiscing---elit', Miscellaneous::breakLongText($this->stringCommaSeparated, 20, '---')); + self::assertEquals('LoremIpsum
DolorSitAm
etConsecte
turAdipisc
ingElit', Miscellaneous::breakLongText($this->stringWithoutSpaces, 10)); + } + + /** + * @param int $first + * @param int $second + * @param int $expected + * + * @dataProvider provideGreatestCommonDivisor + */ + public function testCalculateGreatestCommonDivisor(int $first, int $second, int $expected): void + { + static::assertSame($expected, Miscellaneous::calculateGreatestCommonDivisor($first, $second)); } public function testCheckboxValue2Boolean() @@ -64,435 +1020,29 @@ class MiscellaneousTest extends BaseTestCase self::assertEquals(0, Miscellaneous::checkboxValue2Integer(null)); } - public function testGetFileExtension() + public function testConcatenatePathsInNixOs() { - $fileName = 'Lorem.ipsum-dolor.sit.JPG'; - self::assertEquals('JPG', Miscellaneous::getFileExtension($fileName)); - self::assertEquals('jpg', Miscellaneous::getFileExtension($fileName, true)); - } - - /** - * @param string $fileName Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testGetFileNameWithoutExtensionEmptyValue($fileName) - { - self::assertEquals('', Miscellaneous::getFileNameWithoutExtension($fileName)); - } - - /** - * @param string $fileName The file name with extension - * @param string $withoutExtension The file name without extension - * - * @dataProvider provideFileNames - */ - public function testGetFileNameWithoutExtension($fileName, $withoutExtension) - { - self::assertEquals($withoutExtension, Miscellaneous::getFileNameWithoutExtension($fileName)); - } - - public function testGetFileNameFromPath() - { - /* - * Path with file - */ - self::assertEquals('sit.amet.JPG', Miscellaneous::getFileNameFromPath('lorem/ipsum-dolor/sit.amet.JPG')); - - /* - * Path without file - */ - self::assertEquals('', Miscellaneous::getFileNameFromPath('lorem/ipsum-dolor/sit-amet')); - - /* - * Path with a dot "." in name of directory - */ - self::assertEquals('sit.amet.JPG', Miscellaneous::getFileNameFromPath('lorem/ipsum.dolor/sit.amet.JPG')); - - /* - * Relative path - */ - self::assertEquals('sit.amet.JPG', Miscellaneous::getFileNameFromPath('lorem/ipsum/../dolor/sit.amet.JPG')); - } - - public function testGetUniqueFileName() - { - $originalFileName = 'Lorem.ipsum-dolor.sit.JPG'; - $pattern = '|^lorem\-ipsum\-dolor\-sit\-[a-z0-9.-]+\.jpg$|'; - - /* - * With object ID - */ - $uniqueFileName1 = Miscellaneous::getUniqueFileName($originalFileName, 123); - - /* - * Without object ID - */ - $uniqueFileName2 = Miscellaneous::getUniqueFileName($originalFileName); - - $isCorrect1 = (bool)preg_match($pattern, $uniqueFileName1); - $isCorrect2 = (bool)preg_match($pattern, $uniqueFileName2); - - self::assertTrue($isCorrect1); - self::assertTrue($isCorrect2); - } - - public function testValue2NonNegativeInteger() - { - self::assertEquals(2, Miscellaneous::value2NonNegativeInteger('2')); - self::assertEquals(0, Miscellaneous::value2NonNegativeInteger('a')); - self::assertEquals('-', Miscellaneous::value2NonNegativeInteger('-4', '-')); - } - - public function testIsPhpModuleLoaded() - { - $loadedExtensions = get_loaded_extensions(); - $firstExtension = $loadedExtensions[0]; - - self::assertTrue(Miscellaneous::isPhpModuleLoaded($firstExtension)); - self::assertFalse(Miscellaneous::isPhpModuleLoaded('xyz123')); - } - - /** - * @param mixed $string Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testToLatinEmptyValue($string) - { - self::assertEquals('', Miscellaneous::toLatin($string)); - } - - /** - * @param string $expected Expected/converted string - * @param string $string String to convert - * @param string $replacementChar (optional) Replacement character for all non-latin characters - * - * @dataProvider provideStringToLatinNotLowerCaseHuman - */ - public function testToLatinNotLowerCaseHuman($expected, $string, $replacementChar = '-') - { - self::assertEquals($expected, Miscellaneous::toLatin($string, false, $replacementChar)); - } - - /** - * @param string $expected Expected/converted string - * @param string $string String to convert - * @param string $replacementChar (optional) Replacement character for all non-latin characters - * - * @dataProvider provideStringToLatinLowerCaseHuman - */ - public function testToLatinLowerCaseHuman($expected, $string, $replacementChar = '-') - { - self::assertEquals($expected, Miscellaneous::toLatin($string, true, $replacementChar)); - } - - public function testGetUniqueString() - { - $prefix = ''; - $hashed = false; - self::assertEquals(23, strlen(Miscellaneous::getUniqueString($prefix, $hashed))); - - $prefix = 'xyz'; - $hashed = false; - self::assertEquals(26, strlen(Miscellaneous::getUniqueString($prefix, $hashed))); - - $hashed = true; - self::assertEquals(40, strlen(Miscellaneous::getUniqueString($prefix, $hashed))); - } - - /** - * @param string|array $search An empty value to find - * @dataProvider provideEmptyValue - */ - public function testReplaceEmptyValue($search) - { - $replacement1 = ''; - $replacement2 = []; - - $replacement3 = [ - 'commodo', - 'interdum', + // For *nix operating system + $paths1 = [ + 'first/directory', + 'second/one', + 'and/the/third', ]; - self::assertEquals($this->stringSmall, Miscellaneous::replace($this->stringSmall, $search, $replacement1)); - self::assertEquals($this->stringSmall, Miscellaneous::replace($this->stringSmall, $search, $replacement2)); - self::assertEquals($this->stringSmall, Miscellaneous::replace($this->stringSmall, $search, $replacement3)); - - self::assertEquals($this->stringSmall, Miscellaneous::replace($this->stringSmall, $search, $replacement1, true)); - self::assertEquals($this->stringSmall, Miscellaneous::replace($this->stringSmall, $search, $replacement2, true)); - self::assertEquals($this->stringSmall, Miscellaneous::replace($this->stringSmall, $search, $replacement3, true)); + self::assertEquals('/'.implode('/', $paths1), Miscellaneous::concatenatePaths($paths1)); + self::assertEquals('/'.implode('/', $paths1), Miscellaneous::concatenatePaths($paths1[0], $paths1[1], $paths1[2])); } - /** - * @param string $description Description of test - * @param string|array $subject The string or an array of strings to search and replace - * @param string|array $search String or pattern or array of patterns to find. It may be: string, an array - * of strings or an array of patterns. - * @param string|array $replacement The string or an array of strings to replace. It may be: string or an array - * of strings. - * @param mixed $result Result of replacing - * - * @dataProvider provideEmptyValuesToReplace - */ - public function testReplaceUsingEmptyValues($description, $subject, $search, $replacement, $result) + public function testConcatenatePathsInWindowsOs() { - static::assertSame($result, Miscellaneous::replace($subject, $search, $replacement), $description); - } + // For Windows operating system + $paths2 = [ + 'C:\first\directory', + 'second\one', + 'and\the\third', + ]; - /** - * @param string $description Description of test - * @param string $subject The string or an array of strings to search and replace - * @param string $search String or pattern or array of patterns to find. It may be: string, an array of - * strings or an array of patterns. - * @param string $replacement The string or an array of strings to replace. It may be: string or an array of - * strings. - * @param mixed $result Result of replacing - * - * @dataProvider provideStringsToReplace - */ - public function testReplaceUsingStrings($description, $subject, $search, $replacement, $result) - { - static::assertSame($result, Miscellaneous::replace($subject, $search, $replacement), $description); - } - - /** - * @param string $description Description of test - * @param string $subject The string or an array of strings to search and replace - * @param string $search String or pattern or array of patterns to find. It may be: string, an array of - * strings or an array of patterns. - * @param string $replacement The string or an array of strings to replace. It may be: string or an array of - * strings. - * @param mixed $result Result of replacing - * - * @dataProvider provideRegexToReplace - */ - public function testReplaceUsingRegex($description, $subject, $search, $replacement, $result) - { - static::assertSame($result, Miscellaneous::replace($subject, $search, $replacement), $description); - } - - /** - * @param string $description Description of test - * @param string $subject The string or an array of strings to search and replace - * @param string $search String or pattern or array of patterns to find. It may be: string, an array of - * strings or an array of patterns. - * @param string $replacement The string or an array of strings to replace. It may be: string or an array of - * strings. - * @param mixed $result Result of replacing - * - * @dataProvider provideDataToReplaceWithQuoteStrings - */ - public function testReplaceWithQuoteStrings($description, $subject, $search, $replacement, $result) - { - static::assertSame($result, Miscellaneous::replace($subject, $search, $replacement, true), $description); - } - - public function testUppercaseFirst() - { - self::assertEquals('', Miscellaneous::uppercaseFirst('')); - self::assertEquals('', Miscellaneous::uppercaseFirst(null)); - self::assertEquals('', Miscellaneous::uppercaseFirst(false)); - - $text = 'lorEM ipsum dolor sit Amet'; - self::assertEquals('LorEM ipsum dolor sit Amet', Miscellaneous::uppercaseFirst($text)); - - $restLowercase = true; - self::assertEquals('Lorem ipsum dolor sit amet', Miscellaneous::uppercaseFirst($text, $restLowercase)); - - $restLowercase = false; - self::assertEquals('LOREM IPSUM DOLOR SIT AMET', Miscellaneous::uppercaseFirst($text, $restLowercase)); - } - - public function testLowercaseFirst() - { - self::assertEquals('', Miscellaneous::lowercaseFirst('')); - self::assertEquals('', Miscellaneous::lowercaseFirst(null)); - self::assertEquals('', Miscellaneous::lowercaseFirst(false)); - - $text = 'LorEM ipsum dolor sit Amet'; - self::assertEquals('lorEM ipsum dolor sit Amet', Miscellaneous::lowercaseFirst($text)); - - $restLowercase = true; - self::assertEquals('lorem ipsum dolor sit amet', Miscellaneous::lowercaseFirst($text, $restLowercase)); - - $restLowercase = false; - self::assertEquals('lOREM IPSUM DOLOR SIT AMET', Miscellaneous::lowercaseFirst($text, $restLowercase)); - } - - public function testGetNewFileName() - { - self::assertEquals('test.jpg', Miscellaneous::getNewFileName('test.jpg', '', '')); - self::assertEquals('my-test.jpg', Miscellaneous::getNewFileName('test.jpg', 'my-', '')); - self::assertEquals('test-file.jpg', Miscellaneous::getNewFileName('test.jpg', '', '-file')); - self::assertEquals('my-test-file.jpg', Miscellaneous::getNewFileName('test.jpg', 'my-', '-file')); - } - - public function testGetOperatingSystemNameServer() - { - /* - * While running Docker OS is a Linux - */ - self::assertEquals('Linux', Miscellaneous::getOperatingSystemNameServer()); - } - - public function testSubstringToWord() - { - $suffix = '...'; - - self::assertEquals('Lorem ipsum' . $suffix, Miscellaneous::substringToWord($this->stringCommaSeparated, 20)); - self::assertEquals('Lorem ipsum dolor sit' . $suffix, Miscellaneous::substringToWord($this->stringCommaSeparated, 25)); - - self::assertEquals('Lorem ipsum dolor', Miscellaneous::substringToWord($this->stringCommaSeparated, 20, '')); - self::assertEquals('Lorem ipsum dolor sit amet, consectetur', Miscellaneous::substringToWord($this->stringCommaSeparated, 40, '')); - } - - public function testBreakLongText() - { - self::assertEquals('Lorem ipsum dolor sit
amet, consectetur
adipiscing
elit', Miscellaneous::breakLongText($this->stringCommaSeparated, 20)); - self::assertEquals('Lorem ipsum dolor sit---amet, consectetur---adipiscing---elit', Miscellaneous::breakLongText($this->stringCommaSeparated, 20, '---')); - self::assertEquals('LoremIpsum
DolorSitAm
etConsecte
turAdipisc
ingElit', Miscellaneous::breakLongText($this->stringWithoutSpaces, 10)); - } - - public function testRemoveDirectoryUsingNotExistingDirectory() - { - self::assertNull(Miscellaneous::removeDirectory('/abc/def/ghi')); - } - - public function testRemoveDirectoryUsingNoDirectory() - { - $directoryPath = sys_get_temp_dir() . '/ipsum.txt'; - touch($directoryPath); - self::assertTrue(Miscellaneous::removeDirectory($directoryPath)); - } - - public function testRemoveDirectoryUsingSimpleDirectory() - { - $directoryPath = sys_get_temp_dir() . '/lorem/ipsum'; - mkdir($directoryPath, 0777, true); - self::assertTrue(Miscellaneous::removeDirectory($directoryPath)); - } - - public function testRemoveDirectoryUsingComplexDirectory() - { - $directory1Path = sys_get_temp_dir() . '/lorem/ipsum'; - $directory2Path = sys_get_temp_dir() . '/lorem/dolor/sit'; - - mkdir($directory1Path, 0777, true); - mkdir($directory2Path, 0777, true); - - self::assertTrue(Miscellaneous::removeDirectory(sys_get_temp_dir() . '/lorem')); - } - - /** - * @param string $text Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testRemoveStartingDirectorySeparatorEmptyValue($text) - { - self::assertEquals('', Miscellaneous::removeStartingDirectorySeparator($text)); - } - - /** - * @param string $text Text that may contain a directory's separator at the start / beginning - * @param string $separator The directory's separator, e.g. "/" - * @param string $expected Text without the starting / beginning directory's separator - * - * @dataProvider providePathsToRemoveStartingDirectorySeparator - */ - public function testRemoveStartingDirectorySeparator($text, $separator, $expected) - { - self::assertEquals($expected, Miscellaneous::removeStartingDirectorySeparator($text, $separator)); - } - - /** - * @param string $text Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testRemoveEndingDirectorySeparatorEmptyValue($text) - { - self::assertEquals('', Miscellaneous::removeEndingDirectorySeparator($text)); - } - - /** - * @param string $text Text that may contain a directory's separator at the end - * @param string $separator (optional) The directory's separator, e.g. "/" - * @param string $expected Text without the ending directory's separator - * - * @dataProvider providePathsToRemoveEndingDirectorySeparator - */ - public function testRemoveEndingDirectorySeparator($text, $separator, $expected) - { - self::assertEquals($expected, Miscellaneous::removeEndingDirectorySeparator($text, $separator)); - } - - public function testIsDecimal() - { - self::assertTrue(Miscellaneous::isDecimal(1.2)); - self::assertTrue(Miscellaneous::isDecimal('1.2')); - self::assertFalse(Miscellaneous::isDecimal('a')); - self::assertFalse(Miscellaneous::isDecimal(1)); - } - - public function testIsFilePath() - { - $filePath = __FILE__; - $directoryPath = dirname($filePath); - - self::assertTrue(Miscellaneous::isFilePath($filePath)); - self::assertFalse(Miscellaneous::isFilePath($directoryPath)); - } - - /** - * @param string $string The string to convert e.g. this-is-eXamplE (return: thisIsExample) - * @dataProvider provideEmptyValue - */ - public function testGetCamelCaseEmptyValue($string) - { - self::assertEquals('', Miscellaneous::getCamelCase($string)); - } - - /** - * @param string $string The string to convert e.g. this-is-eXamplE (return: thisIsExample) - * @param string $separator (optional) Separator used to find parts of the string, e.g. '-' or ',' - * @param string $expected String in camel case - * - * @dataProvider provideStringToCamelCase - */ - public function testGetCamelCase($string, $separator, $expected) - { - self::assertEquals($expected, Miscellaneous::getCamelCase($string, $separator)); - } - - public function testQuoteValue() - { - self::assertEquals(123, Miscellaneous::quoteValue(123)); - self::assertEquals('\'lorem ipsum\'', Miscellaneous::quoteValue('lorem ipsum')); - self::assertEquals('"lorem ipsum"', Miscellaneous::quoteValue('lorem ipsum', false)); - } - - public function testGetHumanReadableSize() - { - Locale::setLocale(LC_ALL, 'en', 'US'); - - self::assertEquals('400 B', Miscellaneous::getHumanReadableSize(400)); - self::assertEquals('1 KB', Miscellaneous::getHumanReadableSize(1024)); - self::assertEquals('1 MB', Miscellaneous::getHumanReadableSize(1024 * 1024)); - self::assertEquals('1.75 MB', Miscellaneous::getHumanReadableSize(1024 * 1024 * 1.75)); - } - - public function testGetLastElementOfString() - { - self::assertEquals('elit', Miscellaneous::getLastElementOfString($this->stringCommaSeparated, ' ')); - self::assertEquals('consectetur adipiscing elit', Miscellaneous::getLastElementOfString($this->stringCommaSeparated, ',')); - self::assertEquals(null, Miscellaneous::getLastElementOfString($this->stringCommaSeparated, ';')); - self::assertEquals(null, Miscellaneous::getLastElementOfString($this->stringCommaSeparated, '.')); - } - - public function testTrimSmart() - { - self::assertNull(Miscellaneous::trimSmart(null)); - self::assertEquals(' ', Miscellaneous::trimSmart(' ')); - self::assertEquals('lorem ipsum', Miscellaneous::trimSmart(' lorem ipsum')); - self::assertEquals('lorem ipsum', Miscellaneous::trimSmart(' lorem ipsum ')); + self::assertEquals(implode('\\', $paths2), Miscellaneous::concatenatePaths($paths2)); } /** @@ -517,61 +1067,214 @@ class MiscellaneousTest extends BaseTestCase unset($paths[2]); $imploded = implode('/', $paths); - self::assertEquals('/' . $imploded, $concatenated); + self::assertEquals('/'.$imploded, $concatenated); } - public function testConcatenatePathsInNixOs() + public function testConstructor() { - /* - * For *nix operating system - */ - $paths1 = [ - 'first/directory', - 'second/one', - 'and/the/third', - ]; - - self::assertEquals('/' . implode('/', $paths1), Miscellaneous::concatenatePaths($paths1)); - self::assertEquals('/' . implode('/', $paths1), Miscellaneous::concatenatePaths($paths1[0], $paths1[1], $paths1[2])); + static::assertHasNoConstructor(Miscellaneous::class); } - public function testConcatenatePathsInWindowsOs() + /** + * @param mixed $number Number for who the "0" characters should be inserted + * @param int $length Wanted length of final number + * @param bool $before If false, 0 characters will be inserted after given number + * @param string $expected String with added missing the "0" characters + * + * @dataProvider provideNumberToFillMissingZeros + */ + public function testFillMissingZeros($number, $length, $before, $expected): void { - /* - * For Windows operating system - */ - $paths2 = [ - 'C:\first\directory', - 'second\one', - 'and\the\third', - ]; - - self::assertEquals(implode('\\', $paths2), Miscellaneous::concatenatePaths($paths2)); + self::assertSame($expected, Miscellaneous::fillMissingZeros($number, $length, $before)); } - public function testIncludeFileExtension() + /** + * @param mixed $number Number for who the "0" characters should be inserted + * @dataProvider provideEmptyValueToFillMissingZeros + */ + public function testFillMissingZerosEmptyValue($number) { - $fileName = 'lorem-ipsum.jpg'; - - self::assertEquals($fileName, Miscellaneous::includeFileExtension($fileName, 'jpg')); - self::assertEquals(sprintf('%s.%s', $fileName, 'txt'), Miscellaneous::includeFileExtension($fileName, 'txt')); + self::assertEquals('', Miscellaneous::fillMissingZeros($number, 1)); } - public function testGetStringElements() + /** + * @param string $string The string to convert e.g. this-is-eXamplE (return: thisIsExample) + * @param string $separator (optional) Separator used to find parts of the string, e.g. '-' or ',' + * @param string $expected String in camel case + * + * @dataProvider provideStringToCamelCase + */ + public function testGetCamelCase($string, $separator, $expected) { - $elements = [ - 'Lorem ipsum dolor sit amet', - ' consectetur adipiscing elit', - ]; - - self::assertEquals($elements, Miscellaneous::getStringElements($this->stringCommaSeparated, ',')); - self::assertEquals([], Miscellaneous::getStringElements($this->stringCommaSeparated, ';')); + self::assertEquals($expected, Miscellaneous::getCamelCase($string, $separator)); } - public function testGetStringWithoutLastElement() + /** + * @param string $string The string to convert e.g. this-is-eXamplE (return: thisIsExample) + * @dataProvider provideEmptyValue + */ + public function testGetCamelCaseEmptyValue($string) { - self::assertEquals('Lorem ipsum dolor sit', Miscellaneous::getStringWithoutLastElement($this->stringSmall, ' ')); - self::assertEquals('', Miscellaneous::getStringWithoutLastElement($this->stringSmall, ';')); + self::assertEquals('', Miscellaneous::getCamelCase($string)); + } + + public function testGetDirectoryContent() + { + $directoryPath = __DIR__.'/../'; + $filePath = __FILE__; + + self::assertNull(Miscellaneous::getDirectoryContent(null)); + self::assertNull(Miscellaneous::getDirectoryContent('')); + + self::assertGreaterThanOrEqual(0, count(Miscellaneous::getDirectoryContent($directoryPath))); + self::assertGreaterThanOrEqual(0, count(Miscellaneous::getDirectoryContent($directoryPath, true))); + self::assertGreaterThanOrEqual(0, count(Miscellaneous::getDirectoryContent($directoryPath, true, 5))); + + self::assertGreaterThanOrEqual(0, count(Miscellaneous::getDirectoryContent($filePath))); + self::assertGreaterThanOrEqual(0, count(Miscellaneous::getDirectoryContent($filePath, true))); + } + + public function testGetFileExtension() + { + $fileName = 'Lorem.ipsum-dolor.sit.JPG'; + self::assertEquals('JPG', Miscellaneous::getFileExtension($fileName)); + self::assertEquals('jpg', Miscellaneous::getFileExtension($fileName, true)); + } + + /** + * @param string $description Description of test + * @param string $path A path that contains file name + * @param string $expected Expected file name + * + * @dataProvider provideFilePath + */ + public function testGetFileNameFromPath(string $description, string $path, string $expected): void + { + static::assertEquals($expected, Miscellaneous::getFileNameFromPath($path), $description); + } + + /** + * @param string $fileName The file name with extension + * @param string $withoutExtension The file name without extension + * + * @dataProvider provideFileNames + */ + public function testGetFileNameWithoutExtension($fileName, $withoutExtension) + { + self::assertEquals($withoutExtension, Miscellaneous::getFileNameWithoutExtension($fileName)); + } + + /** + * @param string $fileName Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public function testGetFileNameWithoutExtensionEmptyValue($fileName) + { + self::assertEquals('', Miscellaneous::getFileNameWithoutExtension($fileName)); + } + + public function testGetHumanReadableSize() + { + Locale::setLocale(LC_ALL, 'en', 'US'); + + self::assertEquals('400 B', Miscellaneous::getHumanReadableSize(400)); + self::assertEquals('1 KB', Miscellaneous::getHumanReadableSize(1024)); + self::assertEquals('1 MB', Miscellaneous::getHumanReadableSize(1024 * 1024)); + self::assertEquals('1.75 MB', Miscellaneous::getHumanReadableSize(1024 * 1024 * 1.75)); + } + + public function testGetInvertedColor() + { + // Simple cases + self::assertEquals('000000', Miscellaneous::getInvertedColor('fff')); + self::assertEquals('ffffff', Miscellaneous::getInvertedColor('000')); + self::assertEquals('000000', Miscellaneous::getInvertedColor('ffffff')); + self::assertEquals('ffffff', Miscellaneous::getInvertedColor('000000')); + self::assertEquals('#000000', Miscellaneous::getInvertedColor('#ffffff')); + self::assertEquals('#ffffff', Miscellaneous::getInvertedColor('#000000')); + + // Advanced cases - part 1 + self::assertEquals('ffffee', Miscellaneous::getInvertedColor('001')); + self::assertEquals('ffeeff', Miscellaneous::getInvertedColor('010')); + self::assertEquals('eeffff', Miscellaneous::getInvertedColor('100')); + self::assertEquals('333333', Miscellaneous::getInvertedColor('ccc')); + self::assertEquals('333333', Miscellaneous::getInvertedColor('CCC')); + + // Advanced cases - part 2 + self::assertEquals('3e3e3e', Miscellaneous::getInvertedColor('c1c1c1')); + self::assertEquals('3e3e3e', Miscellaneous::getInvertedColor('C1C1C1')); + self::assertEquals('#dd5a01', Miscellaneous::getInvertedColor('#22a5fe')); + self::assertEquals('#22dbb3', Miscellaneous::getInvertedColor('#dd244c')); + self::assertEquals('#464646', Miscellaneous::getInvertedColor('#b9b9b9')); + self::assertEquals('#080808', Miscellaneous::getInvertedColor('#f7f7f7')); + + // Advanced cases - verification + self::assertEquals('000011', Miscellaneous::getInvertedColor('ffffee')); + self::assertEquals('cccccc', Miscellaneous::getInvertedColor('333333')); + self::assertEquals('#22a5fe', Miscellaneous::getInvertedColor('#dd5a01')); + self::assertEquals('#22a5fe', Miscellaneous::getInvertedColor('#DD5A01')); + self::assertEquals('#f7f7f7', Miscellaneous::getInvertedColor('#080808')); + } + + public function testGetInvertedColorWithIncorrectLength() + { + $this->expectException(IncorrectColorHexLengthException::class); + + Miscellaneous::getInvertedColor(null); + Miscellaneous::getInvertedColor(''); + Miscellaneous::getInvertedColor(1); + Miscellaneous::getInvertedColor(12); + Miscellaneous::getInvertedColor(1234567); + Miscellaneous::getInvertedColor('1'); + Miscellaneous::getInvertedColor('12'); + Miscellaneous::getInvertedColor('1234567'); + } + + public function testGetInvertedColorWithInvalidValue() + { + $this->expectException(InvalidColorHexValueException::class); + + Miscellaneous::getInvertedColor('0011zz'); + Miscellaneous::getInvertedColor('001#zz'); + Miscellaneous::getInvertedColor('001!zz'); + Miscellaneous::getInvertedColor('001-zz'); + Miscellaneous::getInvertedColor('00ppqq'); + } + + /** + * @param string $description + * @param string $string + * @param string $separator + * @param string|null $expected + * + * @dataProvider provideLastElementOfString + */ + public function testGetLastElementOfString( + string $description, + string $string, + string $separator, + ?string $expected + ): void { + self::assertEquals($expected, Miscellaneous::getLastElementOfString($string, $separator), $description); + } + + public function testGetNewFileName() + { + self::assertEquals('test.jpg', Miscellaneous::getNewFileName('test.jpg', '', '')); + self::assertEquals('my-test.jpg', Miscellaneous::getNewFileName('test.jpg', 'my-', '')); + self::assertEquals('test-file.jpg', Miscellaneous::getNewFileName('test.jpg', '', '-file')); + self::assertEquals('my-test-file.jpg', Miscellaneous::getNewFileName('test.jpg', 'my-', '-file')); + } + + public function testGetOperatingSystemNameServer() + { + // While running Docker OS is a Linux + self::assertEquals('Linux', Miscellaneous::getOperatingSystemNameServer()); + } + + public function testGetProjectRootPath(): void + { + self::assertNotEmpty(Miscellaneous::getProjectRootPath()); } public function testGetSafelyGlobalVariable() @@ -586,24 +1289,27 @@ class MiscellaneousTest extends BaseTestCase self::assertEquals(123, Miscellaneous::getSafelyGlobalVariable(INPUT_GET, 'lorem')); } - public function testIsBetween() - { - /* - * Negative cases - */ - self::assertFalse(Miscellaneous::isBetween(0, 0, 0)); - self::assertFalse(Miscellaneous::isBetween('0', '0', '0')); - self::assertFalse(Miscellaneous::isBetween(0, 0, 1)); - self::assertFalse(Miscellaneous::isBetween(-1, -1, -1)); - self::assertFalse(Miscellaneous::isBetween(1.2, 0.1, 1.1)); + /** + * @param string $description + * @param string $string + * @param string $separator + * @param array $expected + * + * @dataProvider provideStringElements + */ + public function testGetStringElements( + string $description, + string $string, + string $separator, + array $expected + ): void { + self::assertEquals($expected, Miscellaneous::getStringElements($string, $separator), $description); + } - /* - * Positive cases - */ - self::assertTrue(Miscellaneous::isBetween(1, 0, 2)); - self::assertTrue(Miscellaneous::isBetween('1', '0', '2')); - self::assertTrue(Miscellaneous::isBetween(-1, -2, 2)); - self::assertTrue(Miscellaneous::isBetween(1.1, 0.1, 1.2)); + public function testGetStringWithoutLastElement() + { + self::assertEquals('Lorem ipsum dolor sit', Miscellaneous::getStringWithoutLastElement($this->stringSmall, ' ')); + self::assertEquals('', Miscellaneous::getStringWithoutLastElement($this->stringSmall, ';')); } public function testGetType() @@ -617,11 +1323,41 @@ class MiscellaneousTest extends BaseTestCase self::assertEquals(__CLASS__, Miscellaneous::getType(new self())); } + public function testGetUniqueFileName() + { + $originalFileName = 'Lorem.ipsum-dolor.sit.JPG'; + $pattern = '|^lorem\-ipsum\-dolor\-sit\-[a-z0-9.-]+\.jpg$|'; + + // With object ID + $uniqueFileName1 = Miscellaneous::getUniqueFileName($originalFileName, 123); + + // Without object ID + $uniqueFileName2 = Miscellaneous::getUniqueFileName($originalFileName); + + $isCorrect1 = (bool) preg_match($pattern, $uniqueFileName1); + $isCorrect2 = (bool) preg_match($pattern, $uniqueFileName2); + + self::assertTrue($isCorrect1); + self::assertTrue($isCorrect2); + } + + public function testGetUniqueString() + { + $prefix = ''; + $hashed = false; + self::assertEquals(23, strlen(Miscellaneous::getUniqueString($prefix, $hashed))); + + $prefix = 'xyz'; + $hashed = false; + self::assertEquals(26, strlen(Miscellaneous::getUniqueString($prefix, $hashed))); + + $hashed = true; + self::assertEquals(40, strlen(Miscellaneous::getUniqueString($prefix, $hashed))); + } + public function testGetValidColorComponent() { - /* - * Negative cases - */ + // Negative cases self::assertEquals(0, Miscellaneous::getValidColorComponent(null)); self::assertEquals(0, Miscellaneous::getValidColorComponent('')); self::assertEquals(0, Miscellaneous::getValidColorComponent('0')); @@ -629,18 +1365,14 @@ class MiscellaneousTest extends BaseTestCase self::assertEquals(0, Miscellaneous::getValidColorComponent(256)); self::assertEquals(0, Miscellaneous::getValidColorComponent(256, false)); - /* - * Positive cases - part 1 - */ + // Positive cases - part 1 self::assertEquals(1, Miscellaneous::getValidColorComponent(1)); self::assertEquals('0a', Miscellaneous::getValidColorComponent(10)); self::assertEquals('0f', Miscellaneous::getValidColorComponent(15)); self::assertEquals(64, Miscellaneous::getValidColorComponent(100)); self::assertEquals('ff', Miscellaneous::getValidColorComponent(255)); - /* - * Positive cases - part 2 - */ + // Positive cases - part 2 self::assertEquals(1, Miscellaneous::getValidColorComponent(1, false)); self::assertEquals(10, Miscellaneous::getValidColorComponent(10, false)); self::assertEquals(15, Miscellaneous::getValidColorComponent(15, false)); @@ -648,829 +1380,344 @@ class MiscellaneousTest extends BaseTestCase self::assertEquals(255, Miscellaneous::getValidColorComponent(255, false)); } - public function testGetInvertedColorWithIncorrectLength() + public function testIncludeFileExtension() { - $this->setExpectedException(IncorrectColorHexLengthException::class); + $fileName = 'lorem-ipsum.jpg'; - Miscellaneous::getInvertedColor(null); - Miscellaneous::getInvertedColor(''); - Miscellaneous::getInvertedColor(1); - Miscellaneous::getInvertedColor(12); - Miscellaneous::getInvertedColor(1234567); - Miscellaneous::getInvertedColor('1'); - Miscellaneous::getInvertedColor('12'); - Miscellaneous::getInvertedColor('1234567'); + self::assertEquals($fileName, Miscellaneous::includeFileExtension($fileName, 'jpg')); + self::assertEquals(sprintf('%s.%s', $fileName, 'txt'), Miscellaneous::includeFileExtension($fileName, 'txt')); } - public function testGetInvertedColorWithInvalidValue() + public function testIsBetween() { - $this->setExpectedException(InvalidColorHexValueException::class); + // Negative cases + self::assertFalse(Miscellaneous::isBetween(0, 0, 0)); + self::assertFalse(Miscellaneous::isBetween('0', '0', '0')); + self::assertFalse(Miscellaneous::isBetween(0, 0, 1)); + self::assertFalse(Miscellaneous::isBetween(-1, -1, -1)); + self::assertFalse(Miscellaneous::isBetween(1.2, 0.1, 1.1)); - Miscellaneous::getInvertedColor('0011zz'); - Miscellaneous::getInvertedColor('001#zz'); - Miscellaneous::getInvertedColor('001!zz'); - Miscellaneous::getInvertedColor('001-zz'); - Miscellaneous::getInvertedColor('00ppqq'); + // Positive cases + self::assertTrue(Miscellaneous::isBetween(1, 0, 2)); + self::assertTrue(Miscellaneous::isBetween('1', '0', '2')); + self::assertTrue(Miscellaneous::isBetween(-1, -2, 2)); + self::assertTrue(Miscellaneous::isBetween(1.1, 0.1, 1.2)); } - public function testGetInvertedColor() + public function testIsDecimal() { - /* - * Simple cases - */ - self::assertEquals('000000', Miscellaneous::getInvertedColor('fff')); - self::assertEquals('ffffff', Miscellaneous::getInvertedColor('000')); - self::assertEquals('000000', Miscellaneous::getInvertedColor('ffffff')); - self::assertEquals('ffffff', Miscellaneous::getInvertedColor('000000')); - self::assertEquals('#000000', Miscellaneous::getInvertedColor('#ffffff')); - self::assertEquals('#ffffff', Miscellaneous::getInvertedColor('#000000')); + self::assertTrue(Miscellaneous::isDecimal(1.2)); + self::assertTrue(Miscellaneous::isDecimal('1.2')); + self::assertFalse(Miscellaneous::isDecimal('a')); + self::assertFalse(Miscellaneous::isDecimal(1)); + } - /* - * Advanced cases - part 1 - */ - self::assertEquals('ffffee', Miscellaneous::getInvertedColor('001')); - self::assertEquals('ffeeff', Miscellaneous::getInvertedColor('010')); - self::assertEquals('eeffff', Miscellaneous::getInvertedColor('100')); - self::assertEquals('333333', Miscellaneous::getInvertedColor('ccc')); - self::assertEquals('333333', Miscellaneous::getInvertedColor('CCC')); + public function testIsFilePath() + { + $filePath = __FILE__; + $directoryPath = dirname($filePath); - /* - * Advanced cases - part 2 - */ - self::assertEquals('3e3e3e', Miscellaneous::getInvertedColor('c1c1c1')); - self::assertEquals('3e3e3e', Miscellaneous::getInvertedColor('C1C1C1')); - self::assertEquals('#dd5a01', Miscellaneous::getInvertedColor('#22a5fe')); - self::assertEquals('#22dbb3', Miscellaneous::getInvertedColor('#dd244c')); - self::assertEquals('#464646', Miscellaneous::getInvertedColor('#b9b9b9')); - self::assertEquals('#080808', Miscellaneous::getInvertedColor('#f7f7f7')); + self::assertTrue(Miscellaneous::isFilePath($filePath)); + self::assertFalse(Miscellaneous::isFilePath($directoryPath)); + } - /* - * Advanced cases - verification - */ - self::assertEquals('000011', Miscellaneous::getInvertedColor('ffffee')); - self::assertEquals('cccccc', Miscellaneous::getInvertedColor('333333')); - self::assertEquals('#22a5fe', Miscellaneous::getInvertedColor('#dd5a01')); - self::assertEquals('#22a5fe', Miscellaneous::getInvertedColor('#DD5A01')); - self::assertEquals('#f7f7f7', Miscellaneous::getInvertedColor('#080808')); + public function testIsPhpModuleLoaded() + { + $loadedExtensions = get_loaded_extensions(); + $firstExtension = $loadedExtensions[0]; + + self::assertTrue(Miscellaneous::isPhpModuleLoaded($firstExtension)); + self::assertFalse(Miscellaneous::isPhpModuleLoaded('xyz123')); + } + + public function testLowercaseFirst() + { + self::assertEquals('', Miscellaneous::lowercaseFirst('')); + self::assertEquals('', Miscellaneous::lowercaseFirst(null)); + self::assertEquals('', Miscellaneous::lowercaseFirst(false)); + + $text = 'LorEM ipsum dolor sit Amet'; + self::assertEquals('lorEM ipsum dolor sit Amet', Miscellaneous::lowercaseFirst($text)); + + $restLowercase = true; + self::assertEquals('lorem ipsum dolor sit amet', Miscellaneous::lowercaseFirst($text, $restLowercase)); + + $restLowercase = false; + self::assertEquals('lOREM IPSUM DOLOR SIT AMET', Miscellaneous::lowercaseFirst($text, $restLowercase)); + } + + public function testQuoteValue() + { + self::assertEquals(123, Miscellaneous::quoteValue(123)); + self::assertEquals('\'lorem ipsum\'', Miscellaneous::quoteValue('lorem ipsum')); + self::assertEquals('"lorem ipsum"', Miscellaneous::quoteValue('lorem ipsum', false)); + } + + public function testRemoveDirectoryUsingComplexDirectory() + { + $directory1Path = sys_get_temp_dir().'/lorem/ipsum'; + $directory2Path = sys_get_temp_dir().'/lorem/dolor/sit'; + + // Directory does not exist? Let's create it + // Required to avoid test failure + if (!file_exists($directory1Path)) { + mkdir($directory1Path, 0777, true); + } + + // Directory does not exist? Let's create it + // Required to avoid test failure + if (!file_exists($directory2Path)) { + mkdir($directory2Path, 0777, true); + } + + self::assertTrue(Miscellaneous::removeDirectory(sys_get_temp_dir().'/lorem')); + } + + public function testRemoveDirectoryUsingNoDirectory() + { + $directoryPath = sys_get_temp_dir().'/ipsum.txt'; + touch($directoryPath); + self::assertTrue(Miscellaneous::removeDirectory($directoryPath)); + } + + public function testRemoveDirectoryUsingNotExistingDirectory() + { + self::assertNull(Miscellaneous::removeDirectory('/abc/def/ghi')); + } + + public function testRemoveDirectoryUsingSimpleDirectory() + { + $directoryPath = sys_get_temp_dir().'/lorem/ipsum'; + mkdir($directoryPath, 0777, true); + self::assertTrue(Miscellaneous::removeDirectory($directoryPath)); } /** - * @param mixed $number Number for who the "0" characters should be inserted - * @dataProvider provideEmptyValueToFillMissingZeros - */ - public function testFillMissingZerosEmptyValue($number) - { - self::assertEquals('', Miscellaneous::fillMissingZeros($number, 1)); - } - - /** - * @param mixed $number Number for who the "0" characters should be inserted - * @param int $length Wanted length of final number - * @param bool $before If false, 0 characters will be inserted after given number - * @param string $expected String with added missing the "0" characters + * @param string $text Text that may contain a directory's separator at the end + * @param string $separator (optional) The directory's separator, e.g. "/" + * @param string $expected Text without the ending directory's separator * - * @dataProvider provideNumberToFillMissingZeros + * @dataProvider providePathsToRemoveEndingDirectorySeparator */ - public function testFillMissingZeros($number, $length, $before, $expected) + public function testRemoveEndingDirectorySeparator($text, $separator, $expected) { - self::assertSame($expected, Miscellaneous::fillMissingZeros($number, $length, $before)); - } - - public function testGetProjectRootPath() - { - self::assertNotEmpty(Miscellaneous::getProjectRootPath()); + self::assertEquals($expected, Miscellaneous::removeEndingDirectorySeparator($text, $separator)); } /** - * Provides string to convert characters to latin characters and not lower cased and not human-readable - * - * @return Generator + * @param string $text Empty value, e.g. "" + * @dataProvider provideEmptyValue */ - public function provideStringToLatinNotLowerCaseHuman() + public function testRemoveEndingDirectorySeparatorEmptyValue($text) { - yield[ - 'asuo', - 'ąśüö', - ]; - - yield[ - 'eoaslzzcn', - 'ęóąśłżźćń', - ]; - - yield[ - 'loremipsum', - 'loremipsum', - ]; - - yield[ - 'LoremIpsum', - 'LoremIpsum', - ]; - - yield[ - 'Lorem.Ipsum', - 'Lorem Ipsum', - '.', - ]; - - yield[ - 'Lorem.Ipsum', - 'Lorem=Ipsum', - '.', - ]; - - yield[ - 'LoremIpsumD', - 'LoremIpsumD', - ]; - - yield[ - 'LoremIpsumD', - 'LoremIpsumD', - '.', - ]; - - yield[ - 'lorem-ipsum', - 'lorem ipsum', - ]; - - yield[ - 'lorem-ipsum', - 'lorem;ipsum', - ]; - - yield[ - 'lorem1ipsum2', - 'lorem1ipsum2', - ]; - - yield[ - 'lorem_ipsum', - 'lorem ipsum', - '_', - ]; - - yield[ - 'LoremIpsum', - 'LoremIpsum', - ]; - - yield[ - 'Lorem Ipsum', - 'Lorem!Ipsum', - ' ', - ]; - - yield[ - 'Lorem Ipsum', - 'Lorem.Ipsum', - ' ', - ]; - - yield[ - 'Lorem|Ipsum', - 'Lorem.Ipsum', - '|', - ]; + self::assertEquals('', Miscellaneous::removeEndingDirectorySeparator($text)); } /** - * Provides string to convert characters to latin characters and lower cased and human-readable + * @param string $description Description of test case + * @param string $string The string which should be shortened + * @param bool $last (optional) If is set to true, last element is removed (default behaviour). + * Otherwise - first. + * @param null|string $expected Expected result * - * @return Generator + * @dataProvider provideStringToRemoveMarginalCharacter */ - public function provideStringToLatinLowerCaseHuman() - { - yield[ - 'asuo', - 'ąśüö', - ]; - - yield[ - 'eoaslzzcn', - 'ęóąśłżźćń', - ]; - - yield[ - 'loremipsum', - 'loremipsum', - ]; - - yield[ - 'lorem-ipsum', - 'lorem ipsum', - ]; - - yield[ - 'lorem-ipsum', - 'lorem;ipsum', - ]; - - yield[ - 'lorem1ipsum2', - 'lorem1ipsum2', - ]; - - yield[ - 'lorem_ipsum', - 'lorem ipsum', - '_', - ]; - - yield[ - 'lorem-ipsum', - 'lorem-ipsum', - ]; - - yield[ - 'lorem ipsum', - 'Lorem!Ipsum', - ' ', - ]; - - yield[ - 'lorem ipsum', - 'Lorem.Ipsum', - ' ', - ]; - - yield[ - 'lorem|ipsum', - 'Lorem.Ipsum', - '|', - ]; - - yield[ - 'lorem-ipsum', - 'LoremIpsum', - ]; - - yield[ - 'lorem.ipsum', - 'Lorem Ipsum', - '.', - ]; - - yield[ - 'lorem.ipsum', - 'Lorem=Ipsum', - '.', - ]; - - yield[ - 'lorem-ipsum-d', - 'LoremIpsumD', - ]; - - yield[ - 'lorem.ipsum.d', - 'LoremIpsumD', - '.', - ]; + public function testRemoveMarginalCharacter( + string $description, + string $string, + bool $last, + ?string $expected + ): void { + self::assertEquals($expected, Miscellaneous::removeMarginalCharacter($string, $last), $description); } /** - * Provides names of files + * @param string $text Text that may contain a directory's separator at the start / beginning + * @param string $separator The directory's separator, e.g. "/" + * @param string $expected Text without the starting / beginning directory's separator * - * @return Generator + * @dataProvider providePathsToRemoveStartingDirectorySeparator */ - public function provideFileNames() + public function testRemoveStartingDirectorySeparator($text, $separator, $expected) { - yield[ - 'Lorem.ipsum-dolor.sit.JPG', - 'Lorem.ipsum-dolor.sit', - ]; - - yield[ - 'lets-test.doc', - 'lets-test', - ]; - - yield[ - 'something/else.txt', - 'something/else', - ]; - - yield[ - 'public/js/user.js', - 'public/js/user', - ]; + self::assertEquals($expected, Miscellaneous::removeStartingDirectorySeparator($text, $separator)); } /** - * Provides string to convert to camel case - * - * @return Generator + * @param string $text Empty value, e.g. "" + * @dataProvider provideEmptyValue */ - public function provideStringToCamelCase() + public function testRemoveStartingDirectorySeparatorEmptyValue($text) { - yield[ - 'lorem ipsum', - ' ', - 'loremIpsum', - ]; - - yield[ - 'Lorem ipSum Dolor', - ' ', - 'loremIpsumDolor', - ]; - - yield[ - 'abc;def;ghi', - ';', - 'abcDefGhi', - ]; + self::assertEquals('', Miscellaneous::removeStartingDirectorySeparator($text)); } /** - * Provides path used to remove the starting / beginning directory's separator - * - * @return Generator + * @param array|string $search An empty value to find + * @dataProvider provideEmptyValue */ - public function providePathsToRemoveStartingDirectorySeparator() + public function testReplaceEmptyValue($search) { - yield[ - '/lorem/ipsum/dolor', - '/', - 'lorem/ipsum/dolor', + $replacement1 = ''; + $replacement2 = []; + + $replacement3 = [ + 'commodo', + 'interdum', ]; - yield[ - 'lorem/ipsum/dolor', - '/', - 'lorem/ipsum/dolor', - ]; + self::assertEquals($this->stringSmall, Miscellaneous::replace($this->stringSmall, $search, $replacement1)); + self::assertEquals($this->stringSmall, Miscellaneous::replace($this->stringSmall, $search, $replacement2)); + self::assertEquals($this->stringSmall, Miscellaneous::replace($this->stringSmall, $search, $replacement3)); - yield[ - '\\lorem\ipsum\dolor', - '\\', - 'lorem\ipsum\dolor', - ]; - - yield[ - 'lorem\ipsum\dolor', - '\\', - 'lorem\ipsum\dolor', - ]; - - yield[ - ';lorem;ipsum;dolor', - ';', - 'lorem;ipsum;dolor', - ]; - - yield[ - 'lorem;ipsum;dolor', - ';', - 'lorem;ipsum;dolor', - ]; + self::assertEquals($this->stringSmall, Miscellaneous::replace($this->stringSmall, $search, $replacement1, true)); + self::assertEquals($this->stringSmall, Miscellaneous::replace($this->stringSmall, $search, $replacement2, true)); + self::assertEquals($this->stringSmall, Miscellaneous::replace($this->stringSmall, $search, $replacement3, true)); } /** - * Provides path used to remove the ending directory's separator + * @param string $description Description of test + * @param array|string $subject The string or an array of strings to search and replace + * @param array|string $search String or pattern or array of patterns to find. It may be: string, an array + * of strings or an array of patterns. + * @param array|string $replacement The string or an array of strings to replace. It may be: string or an array + * of strings. + * @param mixed $result Result of replacing * - * @return Generator + * @dataProvider provideEmptyValuesToReplace */ - public function providePathsToRemoveEndingDirectorySeparator() + public function testReplaceUsingEmptyValues($description, $subject, $search, $replacement, $result) { - yield[ - 'lorem/ipsum/dolor/', - '/', - 'lorem/ipsum/dolor', - ]; - - yield[ - 'lorem/ipsum/dolor', - '/', - 'lorem/ipsum/dolor', - ]; - - yield[ - 'lorem\ipsum\dolor\\', - '\\', - 'lorem\ipsum\dolor', - ]; - - yield[ - 'lorem\ipsum\dolor', - '\\', - 'lorem\ipsum\dolor', - ]; - - yield[ - 'lorem;ipsum;dolor;', - ';', - 'lorem;ipsum;dolor', - ]; - - yield[ - 'lorem;ipsum;dolor', - ';', - 'lorem;ipsum;dolor', - ]; + static::assertSame($result, Miscellaneous::replace($subject, $search, $replacement), $description); } /** - * Provides empty value used to fill missing zeros + * @param string $description Description of test + * @param string $subject The string or an array of strings to search and replace + * @param string $search String or pattern or array of patterns to find. It may be: string, an array of + * strings or an array of patterns. + * @param string $replacement The string or an array of strings to replace. It may be: string or an array of + * strings. + * @param mixed $result Result of replacing * - * @return Generator + * @dataProvider provideRegexToReplace */ - public function provideEmptyValueToFillMissingZeros() + public function testReplaceUsingRegex($description, $subject, $search, $replacement, $result) { - yield['']; - yield[' ']; - yield[null]; - yield[false]; - yield[[]]; + static::assertSame($result, Miscellaneous::replace($subject, $search, $replacement), $description); } /** - * Provides number used to fill missing zeros + * @param string $description Description of test + * @param string $subject The string or an array of strings to search and replace + * @param string $search String or pattern or array of patterns to find. It may be: string, an array of + * strings or an array of patterns. + * @param string $replacement The string or an array of strings to replace. It may be: string or an array of + * strings. + * @param mixed $result Result of replacing * - * @return Generator + * @dataProvider provideStringsToReplace */ - public function provideNumberToFillMissingZeros() + public function testReplaceUsingStrings($description, $subject, $search, $replacement, $result) { - yield[ - 0, - 0, - true, - '0', - ]; - - yield[ - 0, - 0, - false, - '0', - ]; - - yield[ - 1, - 0, - true, - '1', - ]; - - yield[ - 1, - 0, - false, - '1', - ]; - - yield[ - 1, - 1, - true, - '1', - ]; - - yield[ - 1, - 1, - false, - '1', - ]; - - yield[ - 123, - 5, - true, - '00123', - ]; - - yield[ - 123, - 5, - false, - '12300', - ]; + static::assertSame($result, Miscellaneous::replace($subject, $search, $replacement), $description); } - public function provideEmptyValuesToReplace() + /** + * @param string $description Description of test + * @param string $subject The string or an array of strings to search and replace + * @param string $search String or pattern or array of patterns to find. It may be: string, an array of + * strings or an array of patterns. + * @param string $replacement The string or an array of strings to replace. It may be: string or an array of + * strings. + * @param mixed $result Result of replacing + * + * @dataProvider provideDataToReplaceWithQuoteStrings + */ + public function testReplaceWithQuoteStrings($description, $subject, $search, $replacement, $result) { - yield[ - 'An empty string as subject', - '', - 'test', - 'another test', - '', - ]; - - yield[ - 'An empty array as subject', - [], - 'test', - 'another test', - [], - ]; - - yield[ - 'Null as subject', - null, - 'test', - 'another test', - null, - ]; - - yield[ - 'An empty string to search', - 'test', - '', - 'another test', - 'test', - ]; - - yield[ - 'An empty array to search', - 'test', - [], - 'another test', - 'test', - ]; - - yield[ - 'Null to search', - 'test', - null, - 'another test', - 'test', - ]; - - yield[ - 'An empty string as replacement', - 'test', - 'another test', - '', - 'test', - ]; - - yield[ - 'An empty array as replacement', - 'test', - 'another test', - [], - 'test', - ]; - - yield[ - 'Null as replacement', - 'test', - 'another test', - null, - 'test', - ]; + static::assertSame($result, Miscellaneous::replace($subject, $search, $replacement, true), $description); } - public function provideStringsToReplace() + public function testSubstringToWord() { - yield[ - 'Different count of strings to search and replace - 1st part', - 'Lorem ipsum dolor sit amet', - [ - 'ipsum', - ], - 'commodo', - 'Lorem ipsum dolor sit amet', - ]; + $suffix = '...'; - yield[ - 'Different count of strings to search and replace - 2nd part', - 'Lorem ipsum dolor sit amet', - 'ipsum', - [ - 'commodo', - ], - 'Lorem commodo dolor sit amet', - ]; + self::assertEquals('Lorem ipsum'.$suffix, Miscellaneous::substringToWord($this->stringCommaSeparated, 20)); + self::assertEquals('Lorem ipsum dolor sit'.$suffix, Miscellaneous::substringToWord($this->stringCommaSeparated, 25)); - yield[ - 'Replace 1 not existing word in 1 sentence (nothing to replace)', - 'Lorem ipsum dolor sit amet', - 'plum', - 'commodo', - 'Lorem ipsum dolor sit amet', - ]; - - yield[ - 'Replace 1 word in 1 sentence', - 'Lorem ipsum dolor sit amet', - 'ipsum', - 'commodo', - 'Lorem commodo dolor sit amet', - ]; - - yield[ - 'Replace 1 not existing word in 2 sentences (nothing to replace)', - [ - 'Lorem ipsum dolor sit amet', - 'Maecenas sed diam eget risus varius blandit sit amet', - ], - 'plum', - 'commodo', - [ - 'Lorem ipsum dolor sit amet', - 'Maecenas sed diam eget risus varius blandit sit amet', - ], - ]; - - yield[ - 'Replace 1 word in 2 sentences', - [ - 'Lorem ipsum dolor sit amet', - 'Maecenas sed diam eget risus varius blandit sit amet', - ], - 'amet', - 'commodo', - [ - 'Lorem ipsum dolor sit commodo', - 'Maecenas sed diam eget risus varius blandit sit commodo', - ], - ]; + self::assertEquals('Lorem ipsum dolor', Miscellaneous::substringToWord($this->stringCommaSeparated, 20, '')); + self::assertEquals('Lorem ipsum dolor sit amet, consectetur', Miscellaneous::substringToWord($this->stringCommaSeparated, 40, '')); } - public function provideRegexToReplace() + /** + * @param mixed $string Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public function testToLatinEmptyValue($string) { - yield[ - 'Different count of strings to search and replace - 1st part', - 'Lorem ipsum dolor sit amet', - [ - '|ipsum|', - ], - 'commodo', - 'Lorem ipsum dolor sit amet', - ]; - - yield[ - 'Different count of strings to search and replace - 2nd part', - 'Lorem ipsum dolor sit amet', - '|ipsum|', - [ - 'commodo', - ], - 'Lorem ipsum dolor sit amet', - ]; - - yield[ - '1 pattern (word -> "")', - 'Lorem ipsum dolor sit amet', - '|ipsum|', - '', - 'Lorem dolor sit amet', - ]; - - yield[ - '1 pattern (word -> word)', - 'Lorem ipsum dolor sit amet', - '|ipsum|', - 'commodo', - 'Lorem commodo dolor sit amet', - ]; - - yield[ - '2 patterns (word -> word)', - 'Lorem ipsum dolor sit amet', - [ - '|ipsum|', - '|amet|', - ], - [ - 'commodo', - 'egestas', - ], - 'Lorem commodo dolor sit egestas', - ]; - - yield[ - '1 word in 2 sentences', - [ - 'Lorem ipsum dolor sit amet', - 'Maecenas sed diam eget risus varius blandit sit amet', - ], - '|amet|', - 'commodo', - [ - 'Lorem ipsum dolor sit commodo', - 'Maecenas sed diam eget risus varius blandit sit commodo', - ], - ]; - - yield[ - '2 words in 2 sentences', - [ - 'Lorem ipsum dolor sit amet', - 'Maecenas sed diam eget risus varius blandit sit amet', - ], - [ - '|ipsum|', - '|amet|', - ], - [ - 'commodo', - 'egestas', - ], - [ - 'Lorem commodo dolor sit egestas', - 'Maecenas sed diam eget risus varius blandit sit egestas', - ], - ]; + self::assertEquals('', Miscellaneous::toLatin($string)); } - public function provideDataToReplaceWithQuoteStrings() + /** + * @param string $expected Expected/converted string + * @param string $string String to convert + * @param string $replacementChar (optional) Replacement character for all non-latin characters + * + * @dataProvider provideStringToLatinLowerCaseHuman + */ + public function testToLatinLowerCaseHuman($expected, $string, $replacementChar = '-') { - yield[ - 'An empty string as subject', - '', - 'test', - 'another test', - '', - ]; + self::assertEquals($expected, Miscellaneous::toLatin($string, true, $replacementChar)); + } - yield[ - 'An empty string to search', - 'test', - '', - 'another test', - 'test', - ]; + /** + * @param string $expected Expected/converted string + * @param string $string String to convert + * @param string $replacementChar (optional) Replacement character for all non-latin characters + * + * @dataProvider provideStringToLatinNotLowerCaseHuman + */ + public function testToLatinNotLowerCaseHuman($expected, $string, $replacementChar = '-') + { + self::assertEquals($expected, Miscellaneous::toLatin($string, false, $replacementChar)); + } - yield[ - 'An empty string as replacement', - 'test', - 'another test', - '', - 'test', - ]; + public function testTrimSmart() + { + self::assertNull(Miscellaneous::trimSmart(null)); + self::assertEquals(' ', Miscellaneous::trimSmart(' ')); + self::assertEquals('lorem ipsum', Miscellaneous::trimSmart(' lorem ipsum')); + self::assertEquals('lorem ipsum', Miscellaneous::trimSmart(' lorem ipsum ')); + } - yield[ - 'Replace 1 not existing word in 1 sentence (nothing to replace)', - 'Lorem ipsum dolor sit amet', - 'plum', - 'commodo', - 'Lorem ipsum dolor sit amet', - ]; + public function testUppercaseFirst() + { + self::assertEquals('', Miscellaneous::uppercaseFirst('')); + self::assertEquals('', Miscellaneous::uppercaseFirst(null)); + self::assertEquals('', Miscellaneous::uppercaseFirst(false)); - yield[ - 'Replace 1 word in 1 sentence', - 'Lorem ipsum dolor sit amet', - 'ipsum', - 'commodo', - 'Lorem \'commodo\' dolor sit amet', - ]; + $text = 'lorEM ipsum dolor sit Amet'; + self::assertEquals('LorEM ipsum dolor sit Amet', Miscellaneous::uppercaseFirst($text)); - yield[ - 'Replace 1 word in 2 sentences', - [ - 'Lorem ipsum dolor sit amet', - 'Maecenas sed diam eget risus varius blandit sit amet', - ], - 'amet', - 'commodo', - [ - 'Lorem ipsum dolor sit \'commodo\'', - 'Maecenas sed diam eget risus varius blandit sit \'commodo\'', - ], - ]; + $restLowercase = true; + self::assertEquals('Lorem ipsum dolor sit amet', Miscellaneous::uppercaseFirst($text, $restLowercase)); - yield[ - '1 pattern (word -> "")', - 'Lorem ipsum dolor sit amet', - '|ipsum|', - '', - 'Lorem \'\' dolor sit amet', - ]; + $restLowercase = false; + self::assertEquals('LOREM IPSUM DOLOR SIT AMET', Miscellaneous::uppercaseFirst($text, $restLowercase)); + } - yield[ - '1 pattern (word -> word)', - 'Lorem ipsum dolor sit amet', - '|ipsum|', - 'commodo', - 'Lorem \'commodo\' dolor sit amet', - ]; - - yield[ - '2 patterns (word -> word)', - 'Lorem ipsum dolor sit amet', - [ - '|ipsum|', - '|amet|', - ], - [ - 'commodo', - 'egestas', - ], - 'Lorem \'commodo\' dolor sit \'egestas\'', - ]; + public function testValue2NonNegativeInteger() + { + self::assertEquals(2, Miscellaneous::value2NonNegativeInteger('2')); + self::assertEquals(0, Miscellaneous::value2NonNegativeInteger('a')); + self::assertEquals('-', Miscellaneous::value2NonNegativeInteger('-4', '-')); } /** * {@inheritdoc} */ - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -1483,7 +1730,7 @@ class MiscellaneousTest extends BaseTestCase /** * {@inheritdoc} */ - protected function tearDown() + protected function tearDown(): void { parent::tearDown(); unset($this->stringSmall, $this->stringCommaSeparated, $this->stringDotSeparated, $this->stringWithoutSpaces); diff --git a/tests/Utilities/QueryBuilderUtilityTest.php b/tests/Utilities/QueryBuilderUtilityTest.php index 0451ef8..2b59e6a 100644 --- a/tests/Utilities/QueryBuilderUtilityTest.php +++ b/tests/Utilities/QueryBuilderUtilityTest.php @@ -17,237 +17,19 @@ use Doctrine\ORM\QueryBuilder; use Generator; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Utilities\QueryBuilderUtility; +use stdClass; /** * Test case of the useful methods for query builder (the Doctrine's QueryBuilder class) * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\QueryBuilderUtility */ class QueryBuilderUtilityTest extends BaseTestCase { - public function testConstructor() - { - static::assertHasNoConstructor(QueryBuilderUtility::class); - } - - /** - * @param QueryBuilder $queryBuilder The query builder to retrieve root alias - * @param null|string $rootAlias Expected root alias of given query builder - * - * @dataProvider provideQueryBuilderAndRootAlias - */ - public function testGetRootAlias(QueryBuilder $queryBuilder, $rootAlias) - { - static::assertSame($rootAlias, QueryBuilderUtility::getRootAlias($queryBuilder)); - } - - /** - * @param QueryBuilder $queryBuilder The query builder to verify - * @param string $propertyName Name of property that maybe is joined - * @param null|string $propertyAlias Expected alias of given property joined in given query builder - * - * @dataProvider provideQueryBuilderAndPropertyAlias - */ - public function testGetJoinedPropertyAlias(QueryBuilder $queryBuilder, $propertyName, $propertyAlias) - { - static::assertSame($propertyAlias, QueryBuilderUtility::getJoinedPropertyAlias($queryBuilder, $propertyName)); - } - - public function testSetCriteriaWithoutCriteria() - { - $entityManager = $this->getMock(EntityManagerInterface::class); - $queryBuilder = new QueryBuilder($entityManager); - $newQueryBuilder = QueryBuilderUtility::setCriteria($queryBuilder); - - static::assertSame($queryBuilder, $newQueryBuilder); - static::assertCount(0, $newQueryBuilder->getParameters()); - static::assertNull($newQueryBuilder->getDQLPart('where')); - } - - public function testSetCriteriaWithoutAlias() - { - $criteria = [ - 'lorem' => 11, - 'ipsum' => 22, - ]; - - $entityManager = $this->getMock(EntityManagerInterface::class); - $queryBuilder = new QueryBuilder($entityManager); - $newQueryBuilder = QueryBuilderUtility::setCriteria($queryBuilder, $criteria); - - static::assertSame($queryBuilder, $newQueryBuilder); - static::assertCount(count($criteria), $newQueryBuilder->getParameters()); - static::assertNotNull($newQueryBuilder->getDQLPart('where')); - } - - /** - * @param QueryBuilder $queryBuilder The query builder - * @param array $criteria The criteria used in WHERE clause - * - * @dataProvider provideQueryBuilderAndCriteria - */ - public function testSetCriteria(QueryBuilder $queryBuilder, array $criteria) - { - $newQueryBuilder = QueryBuilderUtility::setCriteria($queryBuilder, $criteria); - $criteriaCount = count($criteria); - $nullsCount = 0; - - /* - * I have to verify count/amount of NULLs and decrease $criteriaCount, because for null parameter is not added - */ - array_walk($criteria, function ($value) use (&$nullsCount) { - if (null === $value) { - ++$nullsCount; - } - }); - - static::assertSame($queryBuilder, $newQueryBuilder); - static::assertCount($criteriaCount - $nullsCount, $newQueryBuilder->getParameters()); - static::assertNotNull($newQueryBuilder->getDQLPart('where')); - } - - public function testDeleteEntitiesWithoutFlush() - { - $methods = [ - 'remove', - 'flush', - ]; - - $entityManager = $this->getMock(EntityManager::class, $methods, [], '', false); - $entities1 = []; - - $entities2 = [ - new \stdClass(), - ]; - - static::assertFalse(QueryBuilderUtility::deleteEntities($entityManager, $entities1, false)); - static::assertTrue(QueryBuilderUtility::deleteEntities($entityManager, $entities2, false)); - } - - public function testDeleteEntities() - { - $methods = [ - 'remove', - 'flush', - ]; - - $entityManager = $this->getMock(EntityManager::class, $methods, [], '', false); - $entities1 = []; - - $entities2 = [ - new \stdClass(), - ]; - - static::assertFalse(QueryBuilderUtility::deleteEntities($entityManager, $entities1)); - static::assertTrue(QueryBuilderUtility::deleteEntities($entityManager, $entities2)); - } - - /** - * @param QueryBuilder $queryBuilder The query builder - * @param array|ArrayCollection $parameters Parameters to add. Collection of Doctrine\ORM\Query\Parameter - * instances or an array with key-value pairs. - * - * @dataProvider provideQueryBuilderAndParameters - */ - public function testAddParameters(QueryBuilder $queryBuilder, $parameters) - { - $newQueryBuilder = QueryBuilderUtility::addParameters($queryBuilder, $parameters); - - static::assertSame($queryBuilder, $newQueryBuilder); - static::assertCount(count($parameters), $newQueryBuilder->getParameters()); - } - - /** - * Provides query builder to retrieve root alias and expected root alias - * - * @return Generator - */ - public function provideQueryBuilderAndRootAlias() - { - $entityManager = $this->getMock(EntityManagerInterface::class); - - yield[ - new QueryBuilder($entityManager), - null, - ]; - - yield[ - (new QueryBuilder($entityManager))->from('lorem_ipsum', 'lm'), - 'lm', - ]; - - yield[ - (new QueryBuilder($entityManager)) - ->from('lorem', 'l') - ->leftJoin('l.ipsum', 'i'), - 'l', - ]; - } - - /** - * Provides query builder, name of property and expected alias of given property - * - * @return Generator - */ - public function provideQueryBuilderAndPropertyAlias() - { - $entityManager = $this->getMock(EntityManagerInterface::class); - - yield[ - new QueryBuilder($entityManager), - '', - null, - ]; - - yield[ - new QueryBuilder($entityManager), - 'lorem', - null, - ]; - - yield[ - (new QueryBuilder($entityManager))->from('lorem_ipsum', 'lm'), - 'lm', - null, - ]; - - yield[ - (new QueryBuilder($entityManager)) - ->from('lorem', 'l') - ->leftJoin('l.ipsum', 'i'), - 'ipsum', - 'i', - ]; - - yield[ - (new QueryBuilder($entityManager)) - ->from('lorem', 'l') - ->leftJoin('l.ipsum', 'i') - ->innerJoin('i.dolor', 'd'), - 'ipsum1', - null, - ]; - - yield[ - (new QueryBuilder($entityManager)) - ->from('lorem', 'l') - ->leftJoin('l.ipsum', 'i') - ->innerJoin('i.dolor', 'd'), - 'ipsum', - 'i', - ]; - - yield[ - (new QueryBuilder($entityManager)) - ->from('lorem', 'l') - ->leftJoin('l.ipsum', 'i') - ->innerJoin('i.dolor', 'd'), - 'dolor', - 'd', - ]; - } - /** * Provides query builder and criteria used in WHERE clause * @@ -255,7 +37,11 @@ class QueryBuilderUtilityTest extends BaseTestCase */ public function provideQueryBuilderAndCriteria() { - $entityManager = $this->getMock(EntityManager::class, ['getExpressionBuilder'], [], '', false); + $entityManager = $this + ->getMockBuilder(EntityManager::class) + ->disableOriginalConstructor() + ->setMethods(['getExpressionBuilder']) + ->getMock(); $entityManager ->expects(static::any()) @@ -263,7 +49,7 @@ class QueryBuilderUtilityTest extends BaseTestCase ->willReturn(new Expr()) ; - yield[ + yield [ (new QueryBuilder($entityManager))->from('lorem_ipsum', 'lm'), [ 'lorem' => 11, @@ -272,7 +58,7 @@ class QueryBuilderUtilityTest extends BaseTestCase ], ]; - yield[ + yield [ (new QueryBuilder($entityManager))->from('lorem_ipsum', 'lm'), [ 'lorem' => [ @@ -295,19 +81,19 @@ class QueryBuilderUtilityTest extends BaseTestCase */ public function provideQueryBuilderAndParameters() { - $entityManager = $this->getMock(EntityManagerInterface::class); + $entityManager = $this->createMock(EntityManagerInterface::class); - yield[ + yield [ new QueryBuilder($entityManager), [], ]; - yield[ + yield [ new QueryBuilder($entityManager), new ArrayCollection(), ]; - yield[ + yield [ new QueryBuilder($entityManager), [ 'lorem' => 11, @@ -315,7 +101,7 @@ class QueryBuilderUtilityTest extends BaseTestCase ], ]; - yield[ + yield [ new QueryBuilder($entityManager), new ArrayCollection([ 'lorem' => 11, @@ -323,7 +109,7 @@ class QueryBuilderUtilityTest extends BaseTestCase ]), ]; - yield[ + yield [ new QueryBuilder($entityManager), [ new Parameter('lorem', 11), @@ -331,7 +117,7 @@ class QueryBuilderUtilityTest extends BaseTestCase ], ]; - yield[ + yield [ new QueryBuilder($entityManager), new ArrayCollection([ new Parameter('lorem', 11), @@ -339,4 +125,234 @@ class QueryBuilderUtilityTest extends BaseTestCase ]), ]; } + + /** + * Provides query builder, name of property and expected alias of given property + * + * @return Generator + */ + public function provideQueryBuilderAndPropertyAlias() + { + $entityManager = $this->createMock(EntityManagerInterface::class); + + yield [ + new QueryBuilder($entityManager), + '', + null, + ]; + + yield [ + new QueryBuilder($entityManager), + 'lorem', + null, + ]; + + yield [ + (new QueryBuilder($entityManager))->from('lorem_ipsum', 'lm'), + 'lm', + null, + ]; + + yield [ + (new QueryBuilder($entityManager)) + ->from('lorem', 'l') + ->leftJoin('l.ipsum', 'i'), + 'ipsum', + 'i', + ]; + + yield [ + (new QueryBuilder($entityManager)) + ->from('lorem', 'l') + ->leftJoin('l.ipsum', 'i') + ->innerJoin('i.dolor', 'd'), + 'ipsum1', + null, + ]; + + yield [ + (new QueryBuilder($entityManager)) + ->from('lorem', 'l') + ->leftJoin('l.ipsum', 'i') + ->innerJoin('i.dolor', 'd'), + 'ipsum', + 'i', + ]; + + yield [ + (new QueryBuilder($entityManager)) + ->from('lorem', 'l') + ->leftJoin('l.ipsum', 'i') + ->innerJoin('i.dolor', 'd'), + 'dolor', + 'd', + ]; + } + + /** + * Provides query builder to retrieve root alias and expected root alias + * + * @return Generator + */ + public function provideQueryBuilderAndRootAlias() + { + $entityManager = $this->createMock(EntityManagerInterface::class); + + yield [ + new QueryBuilder($entityManager), + null, + ]; + + yield [ + (new QueryBuilder($entityManager))->from('lorem_ipsum', 'lm'), + 'lm', + ]; + + yield [ + (new QueryBuilder($entityManager)) + ->from('lorem', 'l') + ->leftJoin('l.ipsum', 'i'), + 'l', + ]; + } + + /** + * @param QueryBuilder $queryBuilder The query builder + * @param array|ArrayCollection $parameters Parameters to add. Collection of Doctrine\ORM\Query\Parameter + * instances or an array with key-value pairs. + * + * @dataProvider provideQueryBuilderAndParameters + */ + public function testAddParameters(QueryBuilder $queryBuilder, $parameters) + { + $newQueryBuilder = QueryBuilderUtility::addParameters($queryBuilder, $parameters); + + static::assertSame($queryBuilder, $newQueryBuilder); + static::assertCount(count($parameters), $newQueryBuilder->getParameters()); + } + + public function testConstructor() + { + static::assertHasNoConstructor(QueryBuilderUtility::class); + } + + public function testDeleteEntities() + { + $methods = [ + 'remove', + 'flush', + ]; + + $entityManager = $this + ->getMockBuilder(EntityManager::class) + ->disableOriginalConstructor() + ->setMethods($methods) + ->getMock(); + + $entities1 = []; + + $entities2 = [ + new stdClass(), + ]; + + static::assertFalse(QueryBuilderUtility::deleteEntities($entityManager, $entities1)); + static::assertTrue(QueryBuilderUtility::deleteEntities($entityManager, $entities2)); + } + + public function testDeleteEntitiesWithoutFlush() + { + $methods = [ + 'remove', + 'flush', + ]; + + $entityManager = $this + ->getMockBuilder(EntityManager::class) + ->disableOriginalConstructor() + ->setMethods($methods) + ->getMock(); + + $entities1 = []; + + $entities2 = [ + new stdClass(), + ]; + + static::assertFalse(QueryBuilderUtility::deleteEntities($entityManager, $entities1, false)); + static::assertTrue(QueryBuilderUtility::deleteEntities($entityManager, $entities2, false)); + } + + /** + * @param QueryBuilder $queryBuilder The query builder to verify + * @param string $propertyName Name of property that maybe is joined + * @param null|string $propertyAlias Expected alias of given property joined in given query builder + * + * @dataProvider provideQueryBuilderAndPropertyAlias + */ + public function testGetJoinedPropertyAlias(QueryBuilder $queryBuilder, $propertyName, $propertyAlias) + { + static::assertSame($propertyAlias, QueryBuilderUtility::getJoinedPropertyAlias($queryBuilder, $propertyName)); + } + + /** + * @param QueryBuilder $queryBuilder The query builder to retrieve root alias + * @param null|string $rootAlias Expected root alias of given query builder + * + * @dataProvider provideQueryBuilderAndRootAlias + */ + public function testGetRootAlias(QueryBuilder $queryBuilder, $rootAlias) + { + static::assertSame($rootAlias, QueryBuilderUtility::getRootAlias($queryBuilder)); + } + + /** + * @param QueryBuilder $queryBuilder The query builder + * @param array $criteria The criteria used in WHERE clause + * + * @dataProvider provideQueryBuilderAndCriteria + */ + public function testSetCriteria(QueryBuilder $queryBuilder, array $criteria) + { + $newQueryBuilder = QueryBuilderUtility::setCriteria($queryBuilder, $criteria); + $criteriaCount = count($criteria); + $nullsCount = 0; + + // I have to verify count/amount of NULLs and decrease $criteriaCount, because for null parameter is not added + array_walk($criteria, function ($value) use (&$nullsCount) { + if (null === $value) { + ++$nullsCount; + } + }); + + static::assertSame($queryBuilder, $newQueryBuilder); + static::assertCount($criteriaCount - $nullsCount, $newQueryBuilder->getParameters()); + static::assertNotNull($newQueryBuilder->getDQLPart('where')); + } + + public function testSetCriteriaWithoutAlias() + { + $criteria = [ + 'lorem' => 11, + 'ipsum' => 22, + ]; + + $entityManager = $this->createMock(EntityManagerInterface::class); + $queryBuilder = new QueryBuilder($entityManager); + $newQueryBuilder = QueryBuilderUtility::setCriteria($queryBuilder, $criteria); + + static::assertSame($queryBuilder, $newQueryBuilder); + static::assertCount(count($criteria), $newQueryBuilder->getParameters()); + static::assertNotNull($newQueryBuilder->getDQLPart('where')); + } + + public function testSetCriteriaWithoutCriteria() + { + $entityManager = $this->createMock(EntityManagerInterface::class); + $queryBuilder = new QueryBuilder($entityManager); + $newQueryBuilder = QueryBuilderUtility::setCriteria($queryBuilder); + + static::assertSame($queryBuilder, $newQueryBuilder); + static::assertCount(0, $newQueryBuilder->getParameters()); + static::assertNull($newQueryBuilder->getDQLPart('where')); + } } diff --git a/tests/Utilities/Reflection/A.php b/tests/Utilities/Reflection/A.php index f20ca1a..b5fcbcb 100644 --- a/tests/Utilities/Reflection/A.php +++ b/tests/Utilities/Reflection/A.php @@ -14,6 +14,9 @@ namespace Meritoo\Test\Common\Utilities\Reflection; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @coversNothing */ class A { @@ -21,13 +24,13 @@ class A private $count = 1; - protected function lorem() - { - return 'ipsum'; - } - protected function getCount() { return $this->count; } + + protected function lorem() + { + return 'ipsum'; + } } diff --git a/tests/Utilities/Reflection/B.php b/tests/Utilities/Reflection/B.php index 101cdce..aa676be 100644 --- a/tests/Utilities/Reflection/B.php +++ b/tests/Utilities/Reflection/B.php @@ -14,6 +14,9 @@ namespace Meritoo\Test\Common\Utilities\Reflection; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @coversNothing */ class B extends A implements I { diff --git a/tests/Utilities/Reflection/C.php b/tests/Utilities/Reflection/C.php index 3c4d598..cf8b860 100644 --- a/tests/Utilities/Reflection/C.php +++ b/tests/Utilities/Reflection/C.php @@ -14,16 +14,19 @@ namespace Meritoo\Test\Common\Utilities\Reflection; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @coversNothing */ class C extends B { - public function getPositive() - { - return true; - } - public function getNegative() { return false; } + + public function getPositive() + { + return true; + } } diff --git a/tests/Utilities/Reflection/D.php b/tests/Utilities/Reflection/D.php index 06f6550..2467723 100644 --- a/tests/Utilities/Reflection/D.php +++ b/tests/Utilities/Reflection/D.php @@ -14,6 +14,9 @@ namespace Meritoo\Test\Common\Utilities\Reflection; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @coversNothing */ class D { diff --git a/tests/Utilities/Reflection/E.php b/tests/Utilities/Reflection/E.php index 7a72efc..96afb85 100644 --- a/tests/Utilities/Reflection/E.php +++ b/tests/Utilities/Reflection/E.php @@ -14,6 +14,8 @@ namespace Meritoo\Test\Common\Utilities\Reflection; * * @author Meritoo * @copyright Meritoo + * + * @internal */ trait E { diff --git a/tests/Utilities/Reflection/F.php b/tests/Utilities/Reflection/F.php index 7206544..03a0c3c 100644 --- a/tests/Utilities/Reflection/F.php +++ b/tests/Utilities/Reflection/F.php @@ -14,6 +14,9 @@ namespace Meritoo\Test\Common\Utilities\Reflection; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @coversNothing */ class F { @@ -21,7 +24,7 @@ class F private $accountBalance; private $city; private $country; - private $gInstance; + private $g; public function __construct($accountBalance, $city, $country, $username, $firstName = 'John', $lastName = 'Scott') { @@ -29,11 +32,9 @@ class F $this->city = $city; $this->country = $country; $this->username = $username; - $this->gInstance = new G($firstName, $lastName); + $this->g = new G($firstName, $lastName); - /* - * Called to avoid "Unused private method getAccountBalance" warning only - */ + // Called to avoid "Unused private method getAccountBalance" warning only $this->getAccountBalance(); } diff --git a/tests/Utilities/Reflection/G.php b/tests/Utilities/Reflection/G.php index d7f2c54..34fdcf8 100644 --- a/tests/Utilities/Reflection/G.php +++ b/tests/Utilities/Reflection/G.php @@ -14,6 +14,9 @@ namespace Meritoo\Test\Common\Utilities\Reflection; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @coversNothing */ class G { diff --git a/tests/Utilities/Reflection/H.php b/tests/Utilities/Reflection/H.php index a5ce14d..a47725b 100644 --- a/tests/Utilities/Reflection/H.php +++ b/tests/Utilities/Reflection/H.php @@ -14,14 +14,17 @@ namespace Meritoo\Test\Common\Utilities\Reflection; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @coversNothing */ class H { - const DOLOR = 'sit'; + public const DOLOR = 'sit'; - const LOREM = 'ipsum'; + public const LOREM = 'ipsum'; - const MAX_USERS = 5; + public const MAX_USERS = 5; - const MIN_USERS = 2; + public const MIN_USERS = 2; } diff --git a/tests/Utilities/Reflection/I.php b/tests/Utilities/Reflection/I.php index 020b149..50652a2 100644 --- a/tests/Utilities/Reflection/I.php +++ b/tests/Utilities/Reflection/I.php @@ -14,6 +14,8 @@ namespace Meritoo\Test\Common\Utilities\Reflection; * * @author Meritoo * @copyright Meritoo + * + * @internal */ interface I { diff --git a/src/Traits/Collection/IteratorAggregateTrait.php b/tests/Utilities/Reflection/J.php similarity index 52% rename from src/Traits/Collection/IteratorAggregateTrait.php rename to tests/Utilities/Reflection/J.php index 9c814b1..3affeb4 100644 --- a/src/Traits/Collection/IteratorAggregateTrait.php +++ b/tests/Utilities/Reflection/J.php @@ -6,23 +6,27 @@ * file that was distributed with this source code. */ -namespace Meritoo\Common\Traits\Collection; - -use ArrayIterator; +namespace Meritoo\Test\Common\Utilities\Reflection; /** - * Trait for the Collection required by IteratorAggregate interface + * The J class. + * Used for testing the Reflection class. * * @author Meritoo * @copyright Meritoo + * + * @internal + * @coversNothing */ -trait IteratorAggregateTrait +class J { + private $f; + /** - * {@inheritdoc} + * Class constructor */ - public function getIterator() + public function __construct() { - return new ArrayIterator($this->elements); + $this->f = new F(1000, 'New York', 'USA', 'john.scott'); } } diff --git a/tests/Utilities/Reflection/ObjectsCollection.php b/tests/Utilities/Reflection/ObjectsCollection.php new file mode 100644 index 0000000..d51ea05 --- /dev/null +++ b/tests/Utilities/Reflection/ObjectsCollection.php @@ -0,0 +1,34 @@ + + * @copyright Meritoo + * + * @internal + * @coversNothing + */ +class ObjectsCollection extends BaseCollection +{ + protected function isValidType($element): bool + { + return $element instanceof A + || $element instanceof B + || $element instanceof C + || $element instanceof D + || $element instanceof F; + } +} diff --git a/tests/Utilities/ReflectionTest.php b/tests/Utilities/ReflectionTest.php index 2507d7e..6f16f0a 100644 --- a/tests/Utilities/ReflectionTest.php +++ b/tests/Utilities/ReflectionTest.php @@ -10,7 +10,8 @@ namespace Meritoo\Test\Common\Utilities; use DateTime; use Generator; -use Meritoo\Common\Collection\Collection; +use Meritoo\Common\Collection\BaseCollection; +use Meritoo\Common\Collection\Templates; use Meritoo\Common\Exception\Reflection\CannotResolveClassNameException; use Meritoo\Common\Exception\Reflection\MissingChildClassesException; use Meritoo\Common\Exception\Reflection\NotExistingPropertyException; @@ -26,128 +27,204 @@ use Meritoo\Test\Common\Utilities\Reflection\F; use Meritoo\Test\Common\Utilities\Reflection\G; use Meritoo\Test\Common\Utilities\Reflection\H; use Meritoo\Test\Common\Utilities\Reflection\I; +use Meritoo\Test\Common\Utilities\Reflection\J; +use Meritoo\Test\Common\Utilities\Reflection\ObjectsCollection; use ReflectionProperty; +use stdClass; /** * Test case of the useful reflection methods * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\Reflection */ class ReflectionTest extends BaseTestCase { - public function testConstructor() + public function provideClassToGetConstants(): ?Generator + { + yield [ + new stdClass(), + [], + ]; + + yield [ + stdClass::class, + [], + ]; + + yield [ + H::class, + [ + 'DOLOR' => 'sit', + 'LOREM' => 'ipsum', + 'MAX_USERS' => 5, + 'MIN_USERS' => 2, + ], + ]; + } + + public function provideInvalidClassAndTrait(): ?Generator + { + yield [ + '', + '', + ]; + + yield [ + null, + null, + ]; + + yield [ + 0, + 0, + ]; + } + + public function provideObjectAndNotExistingProperties(): ?Generator + { + yield [ + new stdClass(), + [ + 'test' => 1, + ], + ]; + + yield [ + new A(), + [ + 'test' => 2, + ], + ]; + + yield [ + new B(), + [ + 'firstName' => '', + ], + ]; + } + + public function provideObjectAndNotExistingProperty(): ?Generator + { + yield [ + new stdClass(), + 'test', + ]; + + yield [ + new A(), + 'test', + ]; + + yield [ + new B(), + 'firstName', + ]; + } + + public function provideObjectAndPropertiesValues(): ?Generator + { + yield [ + new A(), + [ + 'count' => 123, + ], + ]; + + yield [ + new B(), + [ + 'name' => 'test test', + ], + ]; + + yield [ + new G(), + [ + 'firstName' => 'Jane', + ], + ]; + + yield [ + new G(), + [ + 'lastName' => 'Smith', + ], + ]; + + yield [ + new G(), + [ + 'firstName' => 'Jane', + 'lastName' => 'Brown', + ], + ]; + + yield [ + new F( + 123, + 'New York', + 'USA', + 'UnKnown' + ), + [ + 'g' => new G(), + ], + ]; + + yield [ + new F( + 123, + 'New York', + 'USA', + 'UnKnown', + 'Mary', + 'Brown' + ), + [ + 'country' => 'Canada', + 'accountBalance' => 456, + ], + ]; + } + + public function provideObjectPropertyAndValue(): ?Generator + { + yield [ + new A(), + 'count', + 123, + ]; + + yield [ + new B(), + 'name', + 'test test', + ]; + + yield [ + new G(), + 'firstName', + 'Jane', + ]; + + yield [ + new G(), + 'lastName', + 'Smith', + ]; + } + + public function testConstructor(): void { static::assertHasNoConstructor(Reflection::class); } - /** - * @param mixed $invalidClass Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testGetClassNameInvalidClass($invalidClass) - { - self::assertNull(Reflection::getClassName($invalidClass)); - self::assertNull(Reflection::getClassName(123)); - } - - public function testGetClassNameNotExistingClass() - { - /* - * Not existing class - */ - self::assertEquals('', Reflection::getClassName('xyz')); - self::assertEquals('', Reflection::getClassName('xyz', true)); - } - - public function testGetClassNameExistingClass() - { - /* - * Existing class - */ - self::assertEquals(self::class, Reflection::getClassName(self::class)); - self::assertEquals('ReflectionTest', Reflection::getClassName(self::class, true)); - self::assertEquals(DateTime::class, Reflection::getClassName(new DateTime())); - self::assertEquals(DateTime::class, Reflection::getClassName(new DateTime(), true)); - - self::assertEquals(DateTime::class, Reflection::getClassName([ - new DateTime(), - new DateTime('yesterday'), - ])); - } - - public function testGetClassNameDuplicatedName() - { - /* - * Class with namespace containing name of class (duplicated string) - */ - if (class_exists('Symfony\Bundle\SecurityBundle\SecurityBundle')) { - self::assertEquals( - 'Symfony\Bundle\SecurityBundle\SecurityBundle', - Reflection::getClassName('Symfony\Bundle\SecurityBundle\SecurityBundle') - ); - - self::assertEquals( - 'SecurityBundle', - Reflection::getClassName('Symfony\Bundle\SecurityBundle\SecurityBundle', true) - ); - } - } - - public function testGetClassNamespaceNotExistingClass() - { - /* - * Not existing class - */ - self::assertEquals('', Reflection::getClassNamespace('xyz')); - } - - public function testGetClassNamespaceExistingClass() - { - /* - * Existing class - */ - self::assertEquals('Meritoo\Test\Common\Utilities', Reflection::getClassNamespace(self::class)); - self::assertEquals(DateTime::class, Reflection::getClassNamespace(new DateTime())); - - self::assertEquals(DateTime::class, Reflection::getClassNamespace([ - new DateTime(), - new DateTime('yesterday'), - ])); - } - - public function testGetClassNamespaceDuplicatedName() - { - /* - * Class with namespace containing name of class (duplicated string) - */ - if (class_exists('Symfony\Bundle\SecurityBundle\SecurityBundle')) { - self::assertEquals( - 'Symfony\Bundle\SecurityBundle', - Reflection::getClassNamespace('Symfony\Bundle\SecurityBundle\SecurityBundle') - ); - } - } - - /** - * @param mixed $invalidClass Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testGetChildClassesInvalidClass($invalidClass) - { - $this->setExpectedException(CannotResolveClassNameException::class); - - self::assertNull(Reflection::getChildClasses($invalidClass)); - self::assertNull(Reflection::getChildClasses(123)); - } - - public function testGetChildClassesNotExistingClass() - { - $this->setExpectedException(CannotResolveClassNameException::class); - self::assertEquals('', Reflection::getChildClasses('xyz')); - } - - public function testGetChildClassesExistingClass() + public function testGetChildClassesExistingClass(): void { /* * Attention. I have to create instances of these classes to load them and be available while using @@ -172,26 +249,132 @@ class ReflectionTest extends BaseTestCase self::assertEquals($effect, Reflection::getChildClasses(A::class)); } - public function testGetOneChildClassWithMissingChildClasses() + /** + * @param mixed $invalidClass Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public function testGetChildClassesInvalidClass($invalidClass): void { - $this->setExpectedException(MissingChildClassesException::class); - self::assertEquals('LoremIpsum', Reflection::getOneChildClass(C::class)); + $this->expectException(CannotResolveClassNameException::class); + + self::assertNull(Reflection::getChildClasses($invalidClass)); + self::assertNull(Reflection::getChildClasses(123)); } - public function testGetOneChildClassWithTooManyChildClasses() + public function testGetChildClassesNotExistingClass(): void { - $this->setExpectedException(TooManyChildClassesException::class); - - self::assertEquals(B::class, Reflection::getOneChildClass(A::class)); - self::assertEquals(C::class, Reflection::getOneChildClass(A::class)); + $this->expectException(CannotResolveClassNameException::class); + self::assertEquals('', Reflection::getChildClasses('xyz')); } - public function testGetOneChildClass() + public function testGetClassNameExistingClass(): void { - self::assertEquals(C::class, Reflection::getOneChildClass(B::class)); + // Existing class + self::assertEquals(self::class, Reflection::getClassName(self::class)); + self::assertEquals('ReflectionTest', Reflection::getClassName(self::class, true)); + self::assertEquals(DateTime::class, Reflection::getClassName(new DateTime())); + self::assertEquals(DateTime::class, Reflection::getClassName(new DateTime(), true)); + + self::assertEquals(DateTime::class, Reflection::getClassName([ + new DateTime(), + new DateTime('yesterday'), + ])); } - public function testGetMethods() + /** + * @param mixed $invalidClass Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public function testGetClassNameInvalidClass($invalidClass): void + { + self::assertNull(Reflection::getClassName($invalidClass)); + self::assertNull(Reflection::getClassName(123)); + } + + public function testGetClassNameNotExistingClass(): void + { + // Not existing class + self::assertEquals('', Reflection::getClassName('xyz')); + self::assertEquals('', Reflection::getClassName('xyz', true)); + } + + public function testGetClassNamespaceExistingClass(): void + { + // Existing class + self::assertEquals('Meritoo\Test\Common\Utilities', Reflection::getClassNamespace(self::class)); + self::assertEquals(DateTime::class, Reflection::getClassNamespace(new DateTime())); + + self::assertEquals(DateTime::class, Reflection::getClassNamespace([ + new DateTime(), + new DateTime('yesterday'), + ])); + } + + public function testGetClassNamespaceNotExistingClass(): void + { + // Not existing class + self::assertEquals('', Reflection::getClassNamespace('xyz')); + } + + /** + * A case when namespace of class contains name of class (name of class is duplicated, occurs twice) + */ + public function testGetClassNamespaceWhileNamespaceContainsClassName(): void + { + self::assertEquals( + 'Meritoo\Common\Collection', + Reflection::getClassNamespace(BaseCollection::class) + ); + } + + /** + * A case when namespace of class contains name of class (iow. name of class occurs twice) + */ + public function testGetClassWhileNamespaceContainsClassName(): void + { + self::assertEquals( + BaseCollection::class, + Reflection::getClassName(BaseCollection::class) + ); + + self::assertEquals( + 'BaseCollection', + Reflection::getClassName(BaseCollection::class, true) + ); + } + + public function testGetConstantValue(): void + { + static::assertSame(H::LOREM, Reflection::getConstantValue(H::class, 'LOREM')); + } + + public function testGetConstantValueUsingClassWithoutConstant(): void + { + static::assertNull(Reflection::getConstantValue(H::class, 'users')); + } + + /** + * @param object|string $class The object or name of object's class + * @param array $expected Expected constants + * + * @dataProvider provideClassToGetConstants + */ + public function testGetConstants($class, array $expected): void + { + static::assertSame($expected, Reflection::getConstants($class)); + } + + public function testGetMaxNumberConstant(): void + { + static::assertSame(5, Reflection::getMaxNumberConstant(H::class)); + } + + public function testGetMaxNumberConstantUsingClassWithoutConstants(): void + { + static::assertNull(Reflection::getMaxNumberConstant(A::class)); + } + + public function testGetMethods(): void { self::assertCount(1, Reflection::getMethods(B::class, true)); self::assertCount(3, Reflection::getMethods(B::class)); @@ -200,50 +383,39 @@ class ReflectionTest extends BaseTestCase self::assertCount(5, Reflection::getMethods(C::class)); } - /** - * @param array|object|string $class An array of objects, namespaces, object or namespace - * @param array|string $trait An array of strings or string - * - * @dataProvider provideInvalidClassAndTrait - */ - public function testUsesTraitInvalidClass($class, $trait) + public function testGetOneChildClass(): void { - $this->setExpectedException(CannotResolveClassNameException::class); - self::assertNull(Reflection::usesTrait($class, $trait)); + // Required to get all classes by get_declared_classes() function and avoid throw of + // Meritoo\Common\Exception\Reflection\MissingChildClassesException exception + new C(); + + self::assertEquals(C::class, Reflection::getOneChildClass(B::class)); } - /** - * @param mixed $trait Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testUsesTraitInvalidTrait($trait) + public function testGetOneChildClassWithMissingChildClasses(): void { - $this->setExpectedException(CannotResolveClassNameException::class); - self::assertNull(Reflection::usesTrait(DateTime::class, $trait)); + $this->expectException(MissingChildClassesException::class); + self::assertEquals('LoremIpsum', Reflection::getOneChildClass(C::class)); } - public function testUsesTraitExistingClass() + public function testGetOneChildClassWithTooManyChildClasses(): void { - self::assertTrue(Reflection::usesTrait(A::class, E::class)); - self::assertFalse(Reflection::usesTrait(B::class, E::class)); - self::assertFalse(Reflection::usesTrait(C::class, E::class)); - self::assertFalse(Reflection::usesTrait(D::class, E::class)); + // Required to get all classes by get_declared_classes() function and avoid failure: + // + // Failed asserting that exception of type "Meritoo\Common\Exception\Reflection\TooManyChildClassesException" + // is thrown + new C(); + + $this->expectException(TooManyChildClassesException::class); + Reflection::getOneChildClass(A::class); } - public function testUsesTraitExistingClassAndVerifyParents() - { - self::assertTrue(Reflection::usesTrait(A::class, E::class, true)); - self::assertTrue(Reflection::usesTrait(B::class, E::class, true)); - self::assertTrue(Reflection::usesTrait(C::class, E::class, true)); - self::assertFalse(Reflection::usesTrait(D::class, E::class, true)); - } - - public function testGetProperties() + public function testGetProperties(): void { self::assertCount(1, Reflection::getProperties(B::class)); } - public function testGetPropertiesUsingFilter() + public function testGetPropertiesUsingFilter(): void { self::assertCount( 1, @@ -261,40 +433,53 @@ class ReflectionTest extends BaseTestCase ); } - public function testGetPropertiesWithParents() + public function testGetPropertiesWithParents(): void { self::assertCount(2, Reflection::getProperties(B::class, null, true)); } - public function testGetPropertyValueOfNotExistingProperty() + public function testGetPropertyUsingClassWithPrivateProperty(): void + { + $property = Reflection::getProperty(A::class, 'count', ReflectionProperty::IS_PRIVATE); + + static::assertInstanceOf(ReflectionProperty::class, $property); + static::assertTrue($property->isPrivate()); + static::assertSame('count', $property->getName()); + } + + public function testGetPropertyUsingClassWithProtectedProperty(): void + { + $property = Reflection::getProperty(B::class, 'name', ReflectionProperty::IS_PROTECTED); + + static::assertInstanceOf(ReflectionProperty::class, $property); + static::assertTrue($property->isProtected()); + static::assertSame('name', $property->getName()); + } + + public function testGetPropertyUsingClassWithoutProperty(): void + { + static::assertNull(Reflection::getProperty(A::class, 'lorem')); + } + + public function testGetPropertyValueFromChain(): void + { + $f = new F(1000, 'New York', 'USA', 'john.scott'); + self::assertEquals('John', Reflection::getPropertyValue($f, 'g.firstName')); + } + + public function testGetPropertyValueFromParentClass(): void + { + $c = new C(); + self::assertEquals(1, Reflection::getPropertyValue($c, 'count', true)); + } + + public function testGetPropertyValueOfNotExistingProperty(): void { self::assertNull(Reflection::getPropertyValue(new D(), 'something')); self::assertNull(Reflection::getPropertyValue(new D(), 'something', true)); } - public function testGetPropertyValueFromChain() - { - $f = new F(1000, 'New York', 'USA', 'john.scott'); - self::assertEquals('John', Reflection::getPropertyValue($f, 'gInstance.firstName')); - } - - public function testGetPropertyValueWithPublicGetter() - { - $country = 'USA'; - $f = new F(1000, 'New York', $country, 'john.scott'); - - self::assertEquals($country, Reflection::getPropertyValue($f, 'country')); - } - - public function testGetPropertyValueWithProtectedGetter() - { - $city = 'New York'; - $f = new F(1000, $city, 'USA', 'john.scott'); - - self::assertEquals($city, Reflection::getPropertyValue($f, 'city')); - } - - public function testGetPropertyValueWithPrivateGetter() + public function testGetPropertyValueWithPrivateGetter(): void { $accountBalance = 1000; $f = new F($accountBalance, 'New York', 'USA', 'john.scott'); @@ -302,7 +487,23 @@ class ReflectionTest extends BaseTestCase self::assertEquals($accountBalance, Reflection::getPropertyValue($f, 'accountBalance')); } - public function testGetPropertyValueWithoutGetter() + public function testGetPropertyValueWithProtectedGetter(): void + { + $city = 'New York'; + $f = new F(1000, $city, 'USA', 'john.scott'); + + self::assertEquals($city, Reflection::getPropertyValue($f, 'city')); + } + + public function testGetPropertyValueWithPublicGetter(): void + { + $country = 'USA'; + $f = new F(1000, 'New York', $country, 'john.scott'); + + self::assertEquals($country, Reflection::getPropertyValue($f, 'country')); + } + + public function testGetPropertyValueWithoutGetter(): void { $username = 'john.scott'; $f = new F(1000, 'New York', 'USA', $username); @@ -310,46 +511,48 @@ class ReflectionTest extends BaseTestCase self::assertEquals($username, Reflection::getPropertyValue($f, 'username')); } - public function testGetPropertyValuesFromEmptySource() + public function testGetPropertyValuesFromChainAndMultipleObjects(): void { - self::assertEquals([], Reflection::getPropertyValues([], 'something')); - self::assertEquals([], Reflection::getPropertyValues(new Collection(), 'something')); - } - - public function testGetPropertyValuesOfNotExistingPropertyFromSingleObject() - { - self::assertEquals([], Reflection::getPropertyValues(new D(), 'something')); - self::assertEquals([], Reflection::getPropertyValues(new D(), 'something', true)); - } - - public function testGetPropertyValuesOfNotExistingPropertyFromMultipleObjects() - { - $objects = [ - new A(), - new A(), - new A(), - new B(), - new B(), - new C(), - new D(), + $expected = [ + 'John', + 'Mary', + 'Peter', ]; - self::assertEquals([], Reflection::getPropertyValues($objects, 'something')); - self::assertEquals([], Reflection::getPropertyValues($objects, 'something', true)); + $objects = [ + new F(1000, 'New York', 'USA', 'john.scott'), + new F(2000, 'London', 'GB', 'john.scott', 'Mary', 'Jane'), + new F(3000, 'Tokyo', 'Japan', 'john.scott', 'Peter', 'Brown'), + ]; - $collection = new Collection($objects); + self::assertEquals($expected, Reflection::getPropertyValues($objects, 'g.firstName')); + self::assertEquals($expected, Reflection::getPropertyValues($objects, 'g.firstName', true)); - self::assertEquals([], Reflection::getPropertyValues($collection, 'something')); - self::assertEquals([], Reflection::getPropertyValues($collection, 'something', true)); + $collection = new ObjectsCollection($objects); + + self::assertEquals($expected, Reflection::getPropertyValues($collection, 'g.firstName')); + self::assertEquals($expected, Reflection::getPropertyValues($collection, 'g.firstName', true)); } - public function testGetPropertyValuesOfExistingPropertyFromSingleObject() + public function testGetPropertyValuesFromChainAndSingleObject(): void { - self::assertEquals(['John'], Reflection::getPropertyValues(new G(), 'firstName')); - self::assertEquals(['John'], Reflection::getPropertyValues(new G(), 'firstName', true)); + $f = new F(1000, 'New York', 'USA', 'john.scott'); + $j = new J(); + + self::assertEquals(['John'], Reflection::getPropertyValues($f, 'g.firstName')); + self::assertEquals(['John'], Reflection::getPropertyValues($f, 'g.firstName', true)); + + self::assertEquals(['John'], Reflection::getPropertyValues($j, 'f.g.firstName')); + self::assertEquals(['John'], Reflection::getPropertyValues($j, 'f.g.firstName', true)); } - public function testGetPropertyValuesOfExistingPropertyFromMultipleObjects() + public function testGetPropertyValuesFromEmptySource(): void + { + self::assertEquals([], Reflection::getPropertyValues([], 'something')); + self::assertEquals([], Reflection::getPropertyValues(new Templates(), 'something')); + } + + public function testGetPropertyValuesOfExistingPropertyFromMultipleObjects(): void { $expected = [ 'New York', @@ -366,184 +569,93 @@ class ReflectionTest extends BaseTestCase self::assertEquals($expected, Reflection::getPropertyValues($objects, 'city')); self::assertEquals($expected, Reflection::getPropertyValues($objects, 'city', true)); - $collection = new Collection($objects); + $collection = new ObjectsCollection($objects); self::assertEquals($expected, Reflection::getPropertyValues($collection, 'city')); self::assertEquals($expected, Reflection::getPropertyValues($collection, 'city', true)); } - public function testGetPropertyValuesFromChainAndSingleObject() + public function testGetPropertyValuesOfExistingPropertyFromSingleObject(): void { - $f = new F(1000, 'New York', 'USA', 'john.scott'); - - self::assertEquals(['John'], Reflection::getPropertyValues($f, 'gInstance.firstName')); - self::assertEquals(['John'], Reflection::getPropertyValues($f, 'gInstance.firstName', true)); + self::assertEquals(['John'], Reflection::getPropertyValues(new G(), 'firstName')); + self::assertEquals(['John'], Reflection::getPropertyValues(new G(), 'firstName', true)); } - public function testGetPropertyValuesFromChainAndMultipleObjects() + public function testGetPropertyValuesOfNotExistingPropertyFromMultipleObjects(): void { - $expected = [ - 'John', - 'Mary', - 'Peter', - ]; - $objects = [ - new F(1000, 'New York', 'USA', 'john.scott'), - new F(2000, 'London', 'GB', 'john.scott', 'Mary', 'Jane'), - new F(3000, 'Tokyo', 'Japan', 'john.scott', 'Peter', 'Brown'), + new A(), + new A(), + new A(), + new B(), + new B(), + new C(), + new D(), ]; - self::assertEquals($expected, Reflection::getPropertyValues($objects, 'gInstance.firstName')); - self::assertEquals($expected, Reflection::getPropertyValues($objects, 'gInstance.firstName', true)); + self::assertEquals([], Reflection::getPropertyValues($objects, 'something')); + self::assertEquals([], Reflection::getPropertyValues($objects, 'something', true)); - $collection = new Collection($objects); + $collection = new ObjectsCollection($objects); - self::assertEquals($expected, Reflection::getPropertyValues($collection, 'gInstance.firstName')); - self::assertEquals($expected, Reflection::getPropertyValues($collection, 'gInstance.firstName', true)); + self::assertEquals([], Reflection::getPropertyValues($collection, 'something')); + self::assertEquals([], Reflection::getPropertyValues($collection, 'something', true)); } - public function testGetMaxNumberConstantUsingClassWithoutConstants() + public function testGetPropertyValuesOfNotExistingPropertyFromSingleObject(): void { - static::assertNull(Reflection::getMaxNumberConstant(A::class)); + self::assertEquals([], Reflection::getPropertyValues(new D(), 'something')); + self::assertEquals([], Reflection::getPropertyValues(new D(), 'something', true)); } - public function testGetMaxNumberConstant() - { - static::assertSame(5, Reflection::getMaxNumberConstant(H::class)); - } - - public function testHasMethodUsingClassWithoutMethod() - { - static::assertFalse(Reflection::hasMethod(A::class, 'getUser')); - } - - public function testHasMethod() - { - static::assertTrue(Reflection::hasMethod(A::class, 'getCount')); - } - - public function testHasPropertyUsingClassWithoutProperty() - { - static::assertFalse(Reflection::hasProperty(A::class, 'users')); - } - - public function testHasProperty() - { - static::assertTrue(Reflection::hasProperty(A::class, 'count')); - } - - public function testHasConstantUsingClassWithoutConstant() - { - static::assertFalse(Reflection::hasConstant(H::class, 'users')); - } - - public function testHasConstant() + public function testHasConstant(): void { static::assertTrue(Reflection::hasConstant(H::class, 'LOREM')); } - public function testGetConstantValueUsingClassWithoutConstant() + public function testHasConstantUsingClassWithoutConstant(): void { - static::assertNull(Reflection::getConstantValue(H::class, 'users')); + static::assertFalse(Reflection::hasConstant(H::class, 'users')); } - public function testGetConstantValue() + public function testHasMethod(): void { - static::assertSame(H::LOREM, Reflection::getConstantValue(H::class, 'LOREM')); + static::assertTrue(Reflection::hasMethod(A::class, 'getCount')); } - public function testIsInterfaceImplementedUsingClassWithoutInterface() + public function testHasMethodUsingClassWithoutMethod(): void { - static::assertFalse(Reflection::isInterfaceImplemented(A::class, I::class)); + static::assertFalse(Reflection::hasMethod(A::class, 'getUser')); } - public function testIsInterfaceImplemented() + public function testHasProperty(): void { - static::assertTrue(Reflection::isInterfaceImplemented(B::class, I::class)); + static::assertTrue(Reflection::hasProperty(A::class, 'count')); } - public function testIsChildOfClassUsingClassWithoutChildClass() + public function testHasPropertyUsingClassWithoutProperty(): void { - static::assertFalse(Reflection::isChildOfClass(A::class, B::class)); + static::assertFalse(Reflection::hasProperty(A::class, 'users')); } - public function testIsChildOfClass() + public function testIsChildOfClass(): void { static::assertTrue(Reflection::isChildOfClass(B::class, A::class)); } - public function testGetPropertyUsingClassWithoutProperty() + public function testIsChildOfClassUsingClassWithoutChildClass(): void { - static::assertNull(Reflection::getProperty(A::class, 'lorem')); + static::assertFalse(Reflection::isChildOfClass(A::class, B::class)); } - public function testGetPropertyUsingClassWithPrivateProperty() + public function testIsInterfaceImplemented(): void { - $property = Reflection::getProperty(A::class, 'count', ReflectionProperty::IS_PRIVATE); - - static::assertInstanceOf(ReflectionProperty::class, $property); - static::assertTrue($property->isPrivate()); - static::assertSame('count', $property->getName()); + static::assertTrue(Reflection::isInterfaceImplemented(B::class, I::class)); } - public function testGetPropertyUsingClassWithProtectedProperty() + public function testIsInterfaceImplementedUsingClassWithoutInterface(): void { - $property = Reflection::getProperty(B::class, 'name', ReflectionProperty::IS_PROTECTED); - - static::assertInstanceOf(ReflectionProperty::class, $property); - static::assertTrue($property->isProtected()); - static::assertSame('name', $property->getName()); - } - - /** - * @param mixed $object Object that should contains given property - * @param string $property Name of the property - * - * @dataProvider provideObjectAndNotExistingProperty - */ - public function testSetPropertyValueUsingNotExistingProperty($object, $property) - { - $this->setExpectedException(NotExistingPropertyException::class); - Reflection::setPropertyValue($object, $property, 'test test test'); - } - - /** - * @param mixed $object Object that should contains given property - * @param string $property Name of the property - * @param mixed $value Value of the property - * - * @dataProvider provideObjectPropertyAndValue - */ - public function testSetPropertyValue($object, $property, $value) - { - $oldValue = Reflection::getPropertyValue($object, $property); - Reflection::setPropertyValue($object, $property, $value); - $newValue = Reflection::getPropertyValue($object, $property); - - static::assertNotSame($oldValue, $value); - static::assertSame($newValue, $value); - } - - public function testSetPropertiesValuesWithoutProperties() - { - $object = new G(); - Reflection::setPropertiesValues($object, []); - - static::assertSame($object->getFirstName(), 'John'); - static::assertSame($object->getLastName(), 'Scott'); - } - - /** - * @param mixed $object Object that should contains given property - * @param array $propertiesValues Key-value pairs, where key - name of the property, value - value of the property - * - * @dataProvider provideObjectAndNotExistingProperties - */ - public function testSetPropertiesValuesUsingNotExistingProperties($object, array $propertiesValues) - { - $this->setExpectedException(NotExistingPropertyException::class); - Reflection::setPropertiesValues($object, $propertiesValues); + static::assertFalse(Reflection::isInterfaceImplemented(A::class, I::class)); } /** @@ -552,7 +664,7 @@ class ReflectionTest extends BaseTestCase * * @dataProvider provideObjectAndPropertiesValues */ - public function testSetPropertiesValues($object, array $propertiesValues) + public function testSetPropertiesValues($object, array $propertiesValues): void { Reflection::setPropertiesValues($object, $propertiesValues); @@ -563,185 +675,90 @@ class ReflectionTest extends BaseTestCase } /** - * Provides invalid class and trait + * @param mixed $object Object that should contains given property + * @param array $propertiesValues Key-value pairs, where key - name of the property, value - value of the property * - * @return Generator + * @dataProvider provideObjectAndNotExistingProperties */ - public function provideInvalidClassAndTrait() + public function testSetPropertiesValuesUsingNotExistingProperties($object, array $propertiesValues): void { - yield[ - '', - '', - ]; + $this->expectException(NotExistingPropertyException::class); + Reflection::setPropertiesValues($object, $propertiesValues); + } - yield[ - null, - null, - ]; + public function testSetPropertiesValuesWithoutProperties(): void + { + $object = new G(); + Reflection::setPropertiesValues($object, []); - yield[ - 0, - 0, - ]; - - yield[ - [], - [], - ]; + static::assertSame($object->getFirstName(), 'John'); + static::assertSame($object->getLastName(), 'Scott'); } /** - * Provides object and name of not existing property + * @param mixed $object Object that should contains given property + * @param string $property Name of the property + * @param mixed $value Value of the property * - * @return Generator + * @dataProvider provideObjectPropertyAndValue */ - public function provideObjectAndNotExistingProperty() + public function testSetPropertyValue($object, $property, $value): void { - yield[ - new \stdClass(), - 'test', - ]; + $oldValue = Reflection::getPropertyValue($object, $property); + Reflection::setPropertyValue($object, $property, $value); + $newValue = Reflection::getPropertyValue($object, $property); - yield[ - new A(), - 'test', - ]; - - yield[ - new B(), - 'firstName', - ]; + static::assertNotSame($oldValue, $value); + static::assertSame($newValue, $value); } /** - * Provides object, name of property and value of the property + * @param mixed $object Object that should contains given property + * @param string $property Name of the property * - * @return Generator + * @dataProvider provideObjectAndNotExistingProperty */ - public function provideObjectPropertyAndValue() + public function testSetPropertyValueUsingNotExistingProperty($object, $property): void { - yield[ - new A(), - 'count', - 123, - ]; + $this->expectException(NotExistingPropertyException::class); + Reflection::setPropertyValue($object, $property, 'test test test'); + } - yield[ - new B(), - 'name', - 'test test', - ]; + public function testUsesTraitExistingClass(): void + { + self::assertTrue(Reflection::usesTrait(A::class, E::class)); + self::assertFalse(Reflection::usesTrait(B::class, E::class)); + self::assertFalse(Reflection::usesTrait(C::class, E::class)); + self::assertFalse(Reflection::usesTrait(D::class, E::class)); + } - yield[ - new G(), - 'firstName', - 'Jane', - ]; - - yield[ - new G(), - 'lastName', - 'Smith', - ]; + public function testUsesTraitExistingClassAndVerifyParents(): void + { + self::assertTrue(Reflection::usesTrait(A::class, E::class, true)); + self::assertTrue(Reflection::usesTrait(B::class, E::class, true)); + self::assertTrue(Reflection::usesTrait(C::class, E::class, true)); + self::assertFalse(Reflection::usesTrait(D::class, E::class, true)); } /** - * Provides object and not existing properties + * @param array|object|string $class An array of objects, namespaces, object or namespace + * @param array|string $trait An array of strings or string * - * @return Generator + * @dataProvider provideInvalidClassAndTrait */ - public function provideObjectAndNotExistingProperties() + public function testUsesTraitInvalidClass($class, $trait): void { - yield[ - new \stdClass(), - [ - 'test' => 1, - ], - ]; - - yield[ - new A(), - [ - 'test' => 2, - ], - ]; - - yield[ - new B(), - [ - 'firstName' => '', - ], - ]; + $this->expectException(CannotResolveClassNameException::class); + self::assertNull(Reflection::usesTrait($class, $trait)); } /** - * Provides object and its new values of properties - * - * @return Generator + * @param mixed $trait Empty value, e.g. "" + * @dataProvider provideEmptyValue */ - public function provideObjectAndPropertiesValues() + public function testUsesTraitInvalidTrait($trait): void { - yield[ - new A(), - [ - 'count' => 123, - ], - ]; - - yield[ - new B(), - [ - 'name' => 'test test', - ], - ]; - - yield[ - new G(), - [ - 'firstName' => 'Jane', - ], - ]; - - yield[ - new G(), - [ - 'lastName' => 'Smith', - ], - ]; - - yield[ - new G(), - [ - 'firstName' => 'Jane', - 'lastName' => 'Brown', - ], - ]; - - yield[ - new F( - 123, - 'New York', - 'USA', - 'UnKnown' - ), - [ - 'gInstance' => new G(), - ], - ]; - - yield[ - new F( - 123, - 'New York', - 'USA', - 'UnKnown', - 'Mary', - 'Brown' - ), - [ - 'country' => 'Canada', - 'accountBalance' => 456, - ], - ]; + $this->expectException(CannotResolveClassNameException::class); + Reflection::usesTrait(DateTime::class, $trait); } } diff --git a/tests/Utilities/RegexTest.php b/tests/Utilities/RegexTest.php index c1fb3bc..a92021e 100644 --- a/tests/Utilities/RegexTest.php +++ b/tests/Utilities/RegexTest.php @@ -6,29 +6,1774 @@ * file that was distributed with this source code. */ -namespace Meritoo\Common\Utilities; +namespace Meritoo\Test\Common\Utilities; use Generator; use Meritoo\Common\Exception\Regex\IncorrectColorHexLengthException; use Meritoo\Common\Exception\Regex\InvalidColorHexValueException; use Meritoo\Common\Test\Base\BaseTestCase; +use Meritoo\Common\Utilities\Regex; +use stdClass; /** * Test case of the useful regular expressions methods * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\Regex */ class RegexTest extends BaseTestCase { private $simpleText; private $camelCaseText; + /** + * Provides value to verify if it is a binary value + * + * @return Generator + */ + public function provideBinaryValue() + { + $file1Path = $this->getFilePathForTesting('lorem-ipsum.txt'); + $file2Path = $this->getFilePathForTesting('minion.jpg'); + + yield [ + null, + false, + ]; + + yield [ + [], + false, + ]; + + yield [ + '', + false, + ]; + + yield [ + 'abc', + false, + ]; + + yield [ + '1234', + false, + ]; + + yield [ + 1234, + false, + ]; + + yield [ + 12.34, + false, + ]; + + yield [ + fread(fopen($file1Path, 'rb'), 1), + false, + ]; + + yield [ + fread(fopen($file2Path, 'rb'), 1), + true, + ]; + } + + /** + * Provides name of bundle and information if it's valid name + * + * @return Generator + */ + public function provideBundleName() + { + yield [ + 'something', + false, + ]; + + yield [ + 'something_different', + false, + ]; + + yield [ + 'something-else', + false, + ]; + + yield [ + 'myExtraBundle', + false, + ]; + + yield [ + 'MyExtra', + false, + ]; + + yield [ + 'MyExtraBundle', + true, + ]; + + yield [ + 'MySuperExtraGorgeousBundle', + true, + ]; + } + + /** + * Provides value of color + * + * @return Generator + */ + public function provideColor() + { + yield [ + '#1b0', + '11bb00', + ]; + + yield [ + '#1B0', + '11bb00', + ]; + + yield [ + '#1ab1ab', + '1ab1ab', + ]; + + yield [ + '#1AB1AB', + '1ab1ab', + ]; + + yield [ + '#000', + '000000', + ]; + } + + /** + * Provides empty non color-related value + * + * @return Generator + */ + public function provideColorEmptyValue() + { + yield [ + '', + ]; + + yield [ + 0, + ]; + + yield [ + '0', + ]; + + yield [ + false, + ]; + } + + /** + * Provides value of color with incorrect length + * + * @return Generator + */ + public function provideColorIncorrectLength() + { + yield [ + '12', + ]; + + yield [ + '1234', + ]; + + yield [ + '12345678', + ]; + + yield [ + '#12', + ]; + + yield [ + '#1234', + ]; + + yield [ + '#12345678', + ]; + } + + /** + * Provides invalid value of color + * + * @return Generator + */ + public function provideColorInvalidValue() + { + yield [ + '#qwerty', + ]; + + yield [ + 'qwerty', + ]; + } + + /** + * Provides e-mail and information if it's valid + * + * @return Generator + */ + public function provideEmail() + { + yield [ + '1', + false, + ]; + + yield [ + 1, + false, + ]; + + yield [ + 'a@a', + false, + ]; + + yield [ + 'a@a.com', + false, + ]; + + yield [ + 'aa@a.com', + true, + ]; + + yield [ + 'a.b@d.com', + true, + ]; + } + + /** + * Provides empty non money-related value + * + * @return Generator + */ + public function provideEmptyNonMoneyValue() + { + yield ['']; + yield [' ']; + yield [null]; + yield [false]; + yield [[]]; + } + + public function provideFileName(): ?Generator + { + yield [ + 'An empty string', + '', + false, + ]; + + yield [ + 'Path of this file, of file with test case', + __DIR__, + false, + ]; + + yield [ + 'Name of this file, of file with test case', + __FILE__, + true, + ]; + + yield [ + 'Complicated name of file', + 'this-1_2 3 & my! 4+file.jpg', + true, + ]; + + yield [ + 'Complicated name of file', + 'directory1/directory2/this-1_2 3 & my! 4+file.jpg', + true, + ]; + } + + /** + * Provides html attribute and information if it's valid + * + * @return Generator + */ + public function provideHtmlAttribute() + { + yield [ + 'abc = def', + false, + ]; + + yield [ + 'a b c=def', + false, + ]; + + yield [ + 'abc=def', + false, + ]; + + yield [ + 'a1b2c=d3e4f', + false, + ]; + + yield [ + 'abc="def"', + true, + ]; + + yield [ + 'a1b2c="d3e4f"', + true, + ]; + } + + /** + * Provides html attribute and information if attributes are valid + * + * @return Generator + */ + public function provideHtmlAttributes() + { + yield [ + 'abc = def', + false, + ]; + + yield [ + 'abc = def ghi = jkl', + false, + ]; + + yield [ + 'abc=def ghi=jkl', + false, + ]; + + yield [ + 'abc=def ghi=jkl mno=pqr', + false, + ]; + + yield [ + 'abc="def"', + true, + ]; + + yield [ + 'abc="def" ghi="jkl"', + true, + ]; + + yield [ + 'abc="def" ghi="jkl" mno="pqr"', + true, + ]; + + yield [ + 'a2bc="d4ef" ghi="j k l" mno="pq9r"', + true, + ]; + } + + /** + * Provides money-related value and information if the value is valid + * + * @return Generator + */ + public function provideMoneyValue() + { + yield [ + 'abc', + false, + ]; + + yield [ + '-a.b', + false, + ]; + + yield [ + 'a,b', + false, + ]; + + yield [ + 0, + true, + ]; + + yield [ + 1, + true, + ]; + + yield [ + -1, + true, + ]; + + yield [ + 1.2, + true, + ]; + + yield [ + 1.202, + true, + ]; + + yield [ + -1.202, + true, + ]; + + yield [ + '0', + true, + ]; + + yield [ + '1', + true, + ]; + + yield [ + '-1', + true, + ]; + + yield [ + '1.2', + true, + ]; + + yield [ + '1.202', + true, + ]; + + yield [ + '-1.202', + true, + ]; + + yield [ + '1,202', + true, + ]; + + yield [ + '-1,2', + true, + ]; + + yield [ + '-1,202', + true, + ]; + } + + /** + * Provides pattern and array with keys that should match that pattern + * + * @return Generator + */ + public function providePatternForArrayKeys() + { + yield [ + '/\d/', + [], + [], + ]; + + yield [ + '/\d+/', + [ + 'lorem' => 'ipsum', + 'dolor' => 123, + 'sit', + 4 => '456', + ], + [ + 0 => 'sit', + 4 => '456', + ], + ]; + + yield [ + '/\d+-[a-z]+/', + [ + 'lorem', + '456-ipsum' => 123, + '001-sit' => false, + 'dolor', + ], + [ + '456-ipsum' => 123, + '001-sit' => false, + ], + ]; + } + + /** + * Provides pattern and array with values that should match that pattern + * + * @return Generator + */ + public function providePatternForArrayValues() + { + yield [ + '/\d/', + [], + [], + ]; + + yield [ + '/\d+/', + [ + 'lorem', + 'ipsum', + 123, + 'dolor', + '456', + ], + [ + 2 => 123, + 4 => '456', + ], + ]; + + yield [ + '/\d+-[a-z]+/', + [ + 'lorem', + 123, + false, + 'dolor', + '456-ipsum', + ], + [ + 4 => '456-ipsum', + ], + ]; + } + + /** + * Provides patterns and subject for the pregMultiMatch() method + * + * @return Generator + */ + public function providePatternsAndSubjectForPregMultiMatch() + { + yield [ + '', + '', + false, + ]; + + yield [ + [], + '', + false, + ]; + + yield [ + '/\d+/', + 'Lorem ipsum dolor sit', + false, + ]; + + yield [ + [ + '/\d+/', + '/^[a-z]{4}$/', + ], + 'Lorem ipsum dolor sit', + false, + ]; + + yield [ + '/\w+/', + 'Lorem ipsum dolor sit', + true, + ]; + + yield [ + [ + '/\d+/', + '/\w+/', + ], + 'Lorem ipsum dolor sit', + true, + ]; + } + + /** + * Provides patterns and subject for the pregMultiMatch() method when must match all patterns + * + * @return Generator + */ + public function providePatternsAndSubjectForPregMultiMatchWhenMustMatchAllPatterns() + { + yield [ + '', + '', + false, + ]; + + yield [ + [], + '', + false, + ]; + + yield [ + '/\d+/', + 'Lorem ipsum dolor sit', + false, + ]; + + yield [ + [ + '/\d+/', + '/^[a-z]{4}$/', + ], + 'Lorem ipsum dolor sit', + false, + ]; + + yield [ + '/\w+/', + 'Lorem ipsum dolor sit', + true, + ]; + + yield [ + [ + '/[a-zA-Z ]+/', + '/\w+/', + ], + 'Lorem ipsum dolor sit', + true, + ]; + } + + /** + * Provides phone number and information if it's valid + * + * @return Generator + */ + public function providePhoneNumber() + { + yield [ + 'abc', + false, + ]; + + yield [ + '1-2-3', + false, + ]; + + yield [ + '123', + true, + ]; + + yield [ + '123 456 789', + true, + ]; + + yield [ + '123456789', + true, + ]; + } + + /** + * Provides regular expression for array filtering and the array + * + * @return Generator + */ + public function provideRegularExpressionForArrayFiltering() + { + yield [ + [], + 'id', + '/\d+/', + [], + ]; + + yield [ + [ + [ + 'id' => 1, + 'first_name' => 'Jane', + 'last_name' => 'Scott', + 'is_active' => true, + ], + [ + 'id' => 2, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + 'birth_date', + '/\d+/', + [ + [ + 'id' => 1, + 'first_name' => 'Jane', + 'last_name' => 'Scott', + 'is_active' => true, + ], + [ + 'id' => 2, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + ]; + + yield [ + [ + [ + 'id' => 1, + 'first_name' => 'Jane', + 'last_name' => 'Scott', + 'is_active' => true, + ], + [ + 'id' => 123, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + 'id', + '/\d{3}/', + [ + 1 => [ + 'id' => 123, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + ], + ]; + + yield [ + [ + [ + 'id' => 1, + 'first_name' => 'Jane', + 'last_name' => 'Scott', + 'is_active' => true, + ], + [ + 'id' => 123, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + [ + 'id' => 456, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + 'first_name', + '/George|Mike/', + [ + 1 => [ + 'id' => 123, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + 2 => [ + 'id' => 456, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + ]; + + yield [ + [ + [ + 'id' => 1, + 'first_name' => 'Jane', + 'last_name' => 'Scott', + 'is_active' => true, + ], + [ + 'id' => 2, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green-Blue', + 'is_active' => false, + ], + ], + 'last_name', + '/\w+-\w+/', + [ + 2 => [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green-Blue', + 'is_active' => false, + ], + ], + ]; + } + + /** + * Provides simple compare expression for array filtering and the array + * + * @return Generator + */ + public function provideSimpleExpressionForArrayFiltering() + { + yield [ + [], + 'id', + ' == 2', + [], + ]; + + yield [ + [ + [ + 'id' => 1, + 'first_name' => 'Jane', + 'last_name' => 'Scott', + 'is_active' => true, + ], + [ + 'id' => 2, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + 'birth_date', + ' == 2', + [ + [ + 'id' => 1, + 'first_name' => 'Jane', + 'last_name' => 'Scott', + 'is_active' => true, + ], + [ + 'id' => 2, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + ]; + + yield [ + [ + [ + 'id' => 1, + 'first_name' => 'Jane', + 'last_name' => 'Scott', + 'is_active' => true, + ], + [ + 'id' => 2, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + 'id', + ' == 2', + [ + 1 => [ + 'id' => 2, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + ], + ]; + + yield [ + [ + [ + 'id' => 1, + 'first_name' => 'Jane', + 'last_name' => 'Scott', + 'is_active' => true, + ], + [ + 'id' => 2, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + 'id', + ' >= 2', + [ + 1 => [ + 'id' => 2, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + 2 => [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + ]; + + yield [ + [ + [ + 'id' => 1, + 'first_name' => 'Jane', + 'last_name' => 'Scott', + 'is_active' => true, + ], + [ + 'id' => 2, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + 'is_active', + ' !== true', + [ + 2 => [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + ]; + + yield [ + [ + [ + 'id' => 1, + 'first_name' => 'Jane', + 'last_name' => 'Scott', + 'is_active' => true, + ], + [ + 'id' => 2, + 'first_name' => 'George', + 'last_name' => 'Brown', + 'is_active' => true, + ], + [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + 'first_name', + ' == \'Mike\'', + [ + 2 => [ + 'id' => 3, + 'first_name' => 'Mike', + 'last_name' => 'Green', + 'is_active' => false, + ], + ], + ]; + } + + public function provideSizeToVerify() + { + yield [ + 'One number only', + 200, + ' x ', + false, + ]; + + yield [ + 'One number only as string', + '200', + ' x ', + false, + ]; + + yield [ + 'The " " as invalid separator', + '200 100', + ' x ', + false, + ]; + + yield [ + 'The "|" as separator (invalid separator)', + '200 | 100', + ' x ', + false, + ]; + + yield [ + 'The "|" as invalid separator and no spaces around separator', + '200|100', + ' x ', + false, + ]; + + yield [ + 'The "X" as invalid separator', + '200 X 100', + ' x ', + false, + ]; + + yield [ + 'Simple, valid size', + '200 x 100', + ' x ', + true, + ]; + + yield [ + 'Too much spaces at the right of separator', + '200 x 100', + ' x ', + true, + ]; + + yield [ + 'Too much spaces at the left of separator', + '200 x 100', + ' x ', + true, + ]; + + yield [ + 'Too much spaces around separator', + '200 x 100', + ' x ', + true, + ]; + + yield [ + 'Too much spaces before width', + ' 200 x 100', + ' x ', + true, + ]; + + yield [ + 'Too much spaces after height', + '200 x 100 ', + ' x ', + true, + ]; + + yield [ + 'Too much spaces before width and after height', + ' 200 x 100 ', + ' x ', + true, + ]; + + yield [ + 'Too much spaces everywhere (1st)', + ' 200 x 100 ', + ' x ', + true, + ]; + + yield [ + 'Too much spaces everywhere (2nd)', + ' 200 x 100 ', + ' x ', + true, + ]; + + yield [ + 'Too much spaces everywhere (3rd)', + ' 200 x 100 ', + ' x ', + true, + ]; + + yield [ + 'The " X " as custom separator', + '200 X 100', + ' X ', + true, + ]; + + yield [ + 'The "|" as custom separator', + '200|100', + '|', + true, + ]; + + yield [ + 'The " | " as custom separator', + '200 | 100', + ' | ', + true, + ]; + + yield [ + 'The "::" as custom separator', + '200::100', + '::', + true, + ]; + + yield [ + 'The " :: " as custom separator', + '200 :: 100', + ' :: ', + true, + ]; + + yield [ + 'The "." as custom separator', + '200.100', + '.', + true, + ]; + + yield [ + 'The " . " as custom separator', + '200 . 100', + ' . ', + true, + ]; + + yield [ + 'The "/" as custom separator', + '200/100', + '/', + true, + ]; + + yield [ + 'The " / " as custom separator', + '200 / 100', + ' / ', + true, + ]; + + yield [ + 'The " : " as custom separator and too much spaces everywhere', + ' 200 : 100 ', + ' : ', + true, + ]; + } + + public function provideStringToClearBeginningSlash(): ?Generator + { + yield [ + '', + '', + ]; + + yield [ + '/', + '', + ]; + + yield [ + '\\', + '\\', + ]; + + yield [ + '//', + '/', + ]; + + yield [ + 'lorem ipsum', + 'lorem ipsum', + ]; + + yield [ + '1234', + '1234', + ]; + + yield [ + 'lorem/ipsum', + 'lorem/ipsum', + ]; + + yield [ + 'lorem / ipsum', + 'lorem / ipsum', + ]; + + yield [ + 'lorem\ipsum', + 'lorem\ipsum', + ]; + + yield [ + 'lorem \ ipsum', + 'lorem \ ipsum', + ]; + + yield [ + '\lorem ipsum', + '\lorem ipsum', + ]; + + yield [ + '\ lorem ipsum', + '\ lorem ipsum', + ]; + + yield [ + 'lorem ipsum/', + 'lorem ipsum/', + ]; + + yield [ + 'lorem ipsum /', + 'lorem ipsum /', + ]; + + yield [ + '/lorem ipsum', + 'lorem ipsum', + ]; + + yield [ + '/ lorem ipsum', + ' lorem ipsum', + ]; + + yield [ + '/123 456', + '123 456', + ]; + + yield [ + '/ 123 456', + ' 123 456', + ]; + + yield [ + '/lorem 123 ipsum 456', + 'lorem 123 ipsum 456', + ]; + + yield [ + '/ lorem 123 ipsum 456', + ' lorem 123 ipsum 456', + ]; + } + + public function provideStringToClearEndingSlash(): ?Generator + { + yield [ + '', + '', + ]; + + yield [ + '/', + '', + ]; + + yield [ + '\\', + '\\', + ]; + + yield [ + '//', + '/', + ]; + + yield [ + 'lorem ipsum', + 'lorem ipsum', + ]; + + yield [ + '1234', + '1234', + ]; + + yield [ + 'lorem/ipsum', + 'lorem/ipsum', + ]; + + yield [ + 'lorem / ipsum', + 'lorem / ipsum', + ]; + + yield [ + 'lorem\ipsum', + 'lorem\ipsum', + ]; + + yield [ + 'lorem \ ipsum', + 'lorem \ ipsum', + ]; + + yield [ + '\lorem ipsum', + '\lorem ipsum', + ]; + + yield [ + '\ lorem ipsum', + '\ lorem ipsum', + ]; + + yield [ + '/lorem ipsum', + '/lorem ipsum', + ]; + + yield [ + '/ lorem ipsum', + '/ lorem ipsum', + ]; + + yield [ + 'lorem ipsum/', + 'lorem ipsum', + ]; + + yield [ + 'lorem ipsum /', + 'lorem ipsum ', + ]; + + yield [ + '123 456/', + '123 456', + ]; + + yield [ + '123 456 /', + '123 456 ', + ]; + + yield [ + 'lorem 123 ipsum 456/', + 'lorem 123 ipsum 456', + ]; + + yield [ + 'lorem 123 ipsum 456 /', + 'lorem 123 ipsum 456 ', + ]; + } + + /** + * Provides tax ID and information if it's valid + * + * @return Generator + */ + public function provideTaxId() + { + yield [ + '123', + false, + ]; + + yield [ + '12345', + false, + ]; + + yield [ + '1122334455', + false, + ]; + + yield [ + '1234567890', + false, + ]; + + yield [ + '0987654321', + false, + ]; + + // Microsoft sp. z o.o. + yield [ + '5270103391', + true, + ]; + + // Onet S.A. + yield [ + '7340009469', + true, + ]; + } + + /** + * Provide value to create slug + * + * @return Generator + */ + public function provideValueSlug() + { + yield [ + [], + false, + ]; + + yield [ + null, + false, + ]; + + yield [ + '', + '', + ]; + + yield [ + 1234, + '1234', + ]; + + yield [ + '1234', + '1234', + ]; + + yield [ + '1/2/3/4', + '1234', + ]; + + yield [ + '1 / 2 / 3 / 4', + '1-2-3-4', + ]; + + yield [ + 'test', + 'test', + ]; + + yield [ + 'test test', + 'test-test', + ]; + + yield [ + 'lorem ipsum dolor sit', + 'lorem-ipsum-dolor-sit', + ]; + + yield [ + 'Lorem ipsum. Dolor sit 12.34 amet.', + 'lorem-ipsum-dolor-sit-1234-amet', + ]; + + yield [ + 'Was sind Löwen, Bären, Vögel und Käfer (für die Prüfung)?', + 'was-sind-lowen-baren-vogel-und-kafer-fur-die-prufung', + ]; + + yield [ + 'äöü (ÄÖÜ)', + 'aou-aou', + ]; + + yield [ + 'Półka dębowa. Kolor: żółędziowy. Wymiary: 80 x 30 cm.', + 'polka-debowa-kolor-zoledziowy-wymiary-80-x-30-cm', + ]; + + yield [ + 'ąęółńśżźć (ĄĘÓŁŃŚŻŹĆ)', + 'aeolnszzc-aeolnszzc', + ]; + } + + /** + * @param string $htmlAttributes The html attributes to verify + * @param bool $expected Information if attributes are valid + * + * @dataProvider provideHtmlAttributes + */ + public static function testAreValidHtmlAttributes($htmlAttributes, $expected) + { + self::assertEquals($expected, Regex::areValidHtmlAttributes($htmlAttributes)); + } + + /** + * @param mixed $emptyValue Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public static function testAreValidHtmlAttributesUsingEmptyValue($emptyValue) + { + self::assertFalse(Regex::areValidHtmlAttributes($emptyValue)); + } + + /** + * @param array $array The array that should be filtered + * @param string $arrayColumnKey Column name + * @param string $filterExpression Regular expression, e.g. "/\d+/" or "/[a-z]+[,;]{2,}/" + * @param array $expected Expected array + * + * @dataProvider provideRegularExpressionForArrayFiltering + */ + public function testArrayFilterUsingRegularExpression($array, $arrayColumnKey, $filterExpression, $expected) + { + self::assertEquals($expected, Regex::arrayFilter($array, $arrayColumnKey, $filterExpression, true)); + } + + /** + * @param array $array The array that should be filtered + * @param string $arrayColumnKey Column name + * @param string $filterExpression Simple filter expression, e.g. "== 2" or "!= \'home\'" + * @param array $expected Expected array + * + * @dataProvider provideSimpleExpressionForArrayFiltering + */ + public function testArrayFilterUsingSimpleExpression($array, $arrayColumnKey, $filterExpression, $expected) + { + self::assertEquals($expected, Regex::arrayFilter($array, $arrayColumnKey, $filterExpression)); + } + + public function testCamelCase2humanReadable() + { + self::assertEquals('', Regex::camelCase2humanReadable('')); + self::assertEquals('lorem', Regex::camelCase2humanReadable('lorem')); + + self::assertEquals($this->simpleText, Regex::camelCase2humanReadable($this->camelCaseText)); + self::assertEquals(ucfirst($this->simpleText), Regex::camelCase2humanReadable($this->camelCaseText, true)); + } + + public function testCamelCase2simpleLowercase() + { + self::assertEquals('', Regex::camelCase2simpleLowercase('')); + self::assertEquals('lorem', Regex::camelCase2simpleLowercase('lorem')); + self::assertEquals('Lorem', Regex::camelCase2simpleLowercase('Lorem', '', false)); + self::assertEquals('lorem-ipsum-dolor-sit', Regex::camelCase2simpleLowercase($this->camelCaseText, '-')); + self::assertEquals('lorem-Ipsum-Dolor-Sit', Regex::camelCase2simpleLowercase($this->camelCaseText, '-', false)); + } + + /** + * @param string $string + * @param string $expected + * + * @dataProvider provideStringToClearBeginningSlash + */ + public function testClearBeginningSlash(string $string, string $expected): void + { + static::assertSame($expected, Regex::clearBeginningSlash($string)); + } + + /** + * @param string $string + * @param string $expected + * + * @dataProvider provideStringToClearEndingSlash + */ + public function testClearEndingSlash(string $string, string $expected): void + { + static::assertSame($expected, Regex::clearEndingSlash($string)); + } + public function testConstructor() { static::assertHasNoConstructor(Regex::class); } + public function testContains() + { + self::assertTrue(Regex::contains($this->simpleText, 'ipsum')); + self::assertFalse(Regex::contains($this->simpleText, 'neque')); + + self::assertFalse(Regex::contains($this->simpleText, '.')); + self::assertTrue(Regex::contains($this->simpleText, 'l')); + } + + public function testContainsEntities() + { + self::assertFalse(Regex::containsEntities('Lorem ipsum')); + self::assertTrue(Regex::containsEntities('Lorem ipsum »')); + } + + /** + * @param string $value Value that should be transformed to slug + * @param string $expected Expected slug + * + * @dataProvider provideValueSlug + */ + public function testCreateSlug($value, $expected) + { + self::assertSame($expected, Regex::createSlug($value)); + } + + public function testEndsWith() + { + self::assertFalse(Regex::endsWith($this->simpleText, '\.\.\.')); + self::assertFalse(Regex::endsWith($this->simpleText, '\.')); + self::assertTrue(Regex::endsWith($this->simpleText, 't')); + } + + public function testEndsWithDirectorySeparator() + { + // Not provided, default separator + self::assertTrue(Regex::endsWithDirectorySeparator('my simple text/')); + self::assertFalse(Regex::endsWithDirectorySeparator('my simple text')); + + // Slash as separator + $separatorSlash = '/'; + + self::assertTrue(Regex::endsWithDirectorySeparator('my simple text/', $separatorSlash)); + self::assertFalse(Regex::endsWithDirectorySeparator('my simple text', $separatorSlash)); + + // Backslash as separator + $separatorBackslash = '\\'; + + self::assertTrue(Regex::endsWithDirectorySeparator('my simple text\\', $separatorBackslash)); + self::assertFalse(Regex::endsWithDirectorySeparator('my simple text', $separatorBackslash)); + } + + /** + * @param string $pattern Pattern to match + * @param array $dataArray The array + * @param array $expected Expected array + * + * @dataProvider providePatternForArrayKeys + */ + public static function testGetArrayValuesByPatternUsingKeys($pattern, array $dataArray, $expected) + { + self::assertEquals($expected, Regex::getArrayValuesByPattern($pattern, $dataArray, true)); + } + + /** + * @param string $pattern Pattern to match + * @param array $dataArray The array + * @param array $expected Expected array + * + * @dataProvider providePatternForArrayValues + */ + public static function testGetArrayValuesByPatternUsingValues($pattern, array $dataArray, $expected) + { + self::assertEquals($expected, Regex::getArrayValuesByPattern($pattern, $dataArray)); + } + + public function testGetBundleNamePattern() + { + self::assertEquals('/^(([A-Z]{1}[a-z0-9]+)((?2))*)(Bundle)$/', Regex::getBundleNamePattern()); + } + public function testGetCamelCaseParts() { $parts = []; @@ -60,22 +1805,328 @@ class RegexTest extends BaseTestCase self::assertEquals($parts, Regex::getCamelCaseParts($string)); } - public function testCamelCase2humanReadable() + public function testGetHtmlAttributePattern() { - self::assertEquals('', Regex::camelCase2humanReadable('')); - self::assertEquals('lorem', Regex::camelCase2humanReadable('lorem')); - - self::assertEquals($this->simpleText, Regex::camelCase2humanReadable($this->camelCaseText)); - self::assertEquals(ucfirst($this->simpleText), Regex::camelCase2humanReadable($this->camelCaseText, true)); + self::assertEquals('/([\w-]+)="([\w -]+)"/', Regex::getHtmlAttributePattern()); } - public function testCamelCase2simpleLowercase() + public function testGetMoneyPattern() { - self::assertEquals('', Regex::camelCase2simpleLowercase('')); - self::assertEquals('lorem', Regex::camelCase2simpleLowercase('lorem')); - self::assertEquals('Lorem', Regex::camelCase2simpleLowercase('Lorem', '', false)); - self::assertEquals('lorem-ipsum-dolor-sit', Regex::camelCase2simpleLowercase($this->camelCaseText, '-')); - self::assertEquals('lorem-Ipsum-Dolor-Sit', Regex::camelCase2simpleLowercase($this->camelCaseText, '-', false)); + self::assertEquals('/^[-+]?\d+([\.,]{1}\d*)?$/', Regex::getMoneyPattern()); + } + + public static function testGetUrlPatternWithProtocolRequired() + { + $pattern = '/^([a-z]+:\/\/)([\da-z\.-]+)\.([a-z\.]{2,6})(\/)?([\w\.\-]*)?(\?)?([\w \.\-\/=&]*)\/?$/i'; + self::assertEquals($pattern, Regex::getUrlPattern(true)); + } + + public static function testGetUrlPatternWithoutProtocol() + { + $pattern = '/^([a-z]+:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})(\/)?([\w\.\-]*)?(\?)?([\w \.\-\/=&]*)\/?$/i'; + self::assertEquals($pattern, Regex::getUrlPattern()); + } + + /** + * @param string $color Color to verify + * @param string $expected Expected value of color + * + * @dataProvider provideColor + */ + public function testGetValidColorHexValue($color, $expected) + { + self::assertEquals($expected, Regex::getValidColorHexValue($color)); + } + + /** + * @param mixed $emptyValue Empty value, e.g. "" + * @dataProvider provideColorEmptyValue + */ + public function testGetValidColorHexValueUsingEmptyValue($emptyValue) + { + $this->expectException(IncorrectColorHexLengthException::class); + Regex::getValidColorHexValue($emptyValue); + } + + /** + * @param mixed $emptyValue Empty value, e.g. "" + * @dataProvider provideColorEmptyValue + */ + public function testGetValidColorHexValueUsingEmptyValueWithoutException($emptyValue) + { + self::assertFalse(Regex::getValidColorHexValue($emptyValue, false)); + } + + /** + * @param string $incorrectColor Incorrect value of color + * @dataProvider provideColorIncorrectLength + */ + public function testGetValidColorHexValueUsingIncorrectValue($incorrectColor) + { + $this->expectException(IncorrectColorHexLengthException::class); + Regex::getValidColorHexValue($incorrectColor); + } + + /** + * @param string $incorrectColor Incorrect value of color + * @dataProvider provideColorIncorrectLength + */ + public function testGetValidColorHexValueUsingIncorrectValueWithoutException($incorrectColor) + { + self::assertFalse(Regex::getValidColorHexValue($incorrectColor, false)); + } + + /** + * @param string $invalidColor Invalid value of color + * @dataProvider provideColorInvalidValue + */ + public function testGetValidColorHexValueUsingInvalidValue($invalidColor) + { + $this->expectException(InvalidColorHexValueException::class); + Regex::getValidColorHexValue($invalidColor); + } + + /** + * @param string $invalidColor Invalid value of color + * @dataProvider provideColorInvalidValue + */ + public function testGetValidColorHexValueUsingInvalidValueWithoutException($invalidColor) + { + self::assertFalse(Regex::getValidColorHexValue($invalidColor, false)); + } + + /** + * @param mixed $nonScalarValue Non scalar value, e.g. [] or null + * @dataProvider provideNonScalarValue + */ + public function testGetValidColorHexValueUsingNonScalarValue($nonScalarValue) + { + self::assertFalse(Regex::getValidColorHexValue($nonScalarValue)); + } + + /** + * @param string $value Value to verify + * @param bool $expected Information if value is a binary value + * + * @dataProvider provideBinaryValue + */ + public static function testIsBinaryValue($value, $expected) + { + self::assertEquals($expected, Regex::isBinaryValue($value)); + } + + /** + * @param string $description Description of test + * @param string $fileName + * @param bool $expected Expected result + * + * @dataProvider provideFileName + */ + public function testIsFileName(string $description, string $fileName, bool $expected): void + { + static::assertSame($expected, Regex::isFileName($fileName), $description); + } + + public function testIsLetterOrDigit() + { + self::assertTrue(Regex::isLetterOrDigit('a')); + self::assertTrue(Regex::isLetterOrDigit(10)); + self::assertFalse(Regex::isLetterOrDigit(';')); + } + + public function testIsQuoted() + { + self::assertTrue(Regex::isQuoted('\'lorem ipsum\'')); + self::assertTrue(Regex::isQuoted('"lorem ipsum"')); + + self::assertFalse(Regex::isQuoted('lorem ipsum')); + self::assertFalse(Regex::isQuoted(new stdClass())); + } + + public function testIsSetUriParameter() + { + $uri = 'www.domain.com/?name=phil&type=4'; + + $parameterName = 'type'; + self::assertTrue(Regex::isSetUriParameter($uri, $parameterName)); + + $parameterName = 'color'; + self::assertFalse(Regex::isSetUriParameter($uri, $parameterName)); + } + + /** + * @param string $description Description of test + * @param string $value Value to verify + * @param string $separator Separator used to split width and height + * @param bool $expected Expected result of verification + * + * @dataProvider provideSizeToVerify + */ + public function testIsSizeValue($description, $value, $separator, $expected) + { + self::assertEquals($expected, Regex::isSizeValue($value, $separator), $description); + } + + /** + * @param mixed $emptyValue Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public static function testIsSizeValueUsingEmptyValue($emptyValue) + { + self::assertFalse(Regex::isSizeValue($emptyValue)); + } + + public function testIsSubPathOf() + { + self::assertFalse(Regex::isSubPathOf(null, null)); + self::assertFalse(Regex::isSubPathOf('', '')); + + self::assertFalse(Regex::isSubPathOf('', '/my/directory')); + self::assertFalse(Regex::isSubPathOf('/my/file', '')); + self::assertFalse(Regex::isSubPathOf('/my/file', '/my/directory')); + + self::assertTrue(Regex::isSubPathOf('/my/directory', '/my/directory')); + self::assertTrue(Regex::isSubPathOf('/my/directory/', '/my/directory')); + self::assertTrue(Regex::isSubPathOf('/my/directory', '/my/directory/')); + self::assertTrue(Regex::isSubPathOf('/my/directory/', '/my/directory/')); + + self::assertTrue(Regex::isSubPathOf('/my/another/directory/another/file', '/my/another/directory')); + } + + /** + * @param string $bundleName Full name of bundle to verify, e.g. "MyExtraBundle" + * @param bool $expected Information if it's valid name + * + * @dataProvider provideBundleName + */ + public function testIsValidBundleName($bundleName, $expected) + { + self::assertEquals($expected, Regex::isValidBundleName($bundleName)); + } + + /** + * @param mixed $emptyValue Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public function testIsValidBundleNameUsingEmptyValue($emptyValue) + { + self::assertFalse(Regex::isValidBundleName($emptyValue)); + } + + /** + * @param string $email E-mail address to validate / verify + * @param bool $expected Information if e-mail is valid + * + * @dataProvider provideEmail + */ + public static function testIsValidEmail($email, $expected) + { + self::assertEquals($expected, Regex::isValidEmail($email)); + } + + /** + * @param mixed $emptyValue Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public static function testIsValidEmailUsingEmptyValue($emptyValue) + { + self::assertFalse(Regex::isValidEmail($emptyValue)); + } + + /** + * @param string $htmlAttribute The html attribute to verify + * @param bool $expected Information if it's valid attribute + * + * @dataProvider provideHtmlAttribute + */ + public function testIsValidHtmlAttribute($htmlAttribute, $expected) + { + self::assertEquals($expected, Regex::isValidHtmlAttribute($htmlAttribute)); + } + + /** + * @param mixed $emptyValue Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public function testIsValidHtmlAttributeUsingEmptyValue($emptyValue) + { + self::assertFalse(Regex::isValidHtmlAttribute($emptyValue)); + } + + /** + * @param mixed $value Value to verify + * @param bool $expected Information if given value is a money value + * + * @dataProvider provideMoneyValue + */ + public function testIsValidMoneyValue($value, $expected) + { + self::assertEquals($expected, Regex::isValidMoneyValue($value)); + } + + /** + * @param mixed $emptyValue Empty value, e.g. "" + * @dataProvider provideEmptyNonMoneyValue + */ + public function testIsValidMoneyValueUsingEmptyValue($emptyValue) + { + self::assertFalse(Regex::isValidMoneyValue($emptyValue)); + } + + public function testIsValidNip() + { + self::assertFalse(Regex::isValidNip(null)); + self::assertFalse(Regex::isValidNip('')); + self::assertFalse(Regex::isValidNip(1234)); + self::assertFalse(Regex::isValidNip(1234567890)); + self::assertFalse(Regex::isValidNip(0000000000)); + self::assertFalse(Regex::isValidNip('1234567890')); + self::assertFalse(Regex::isValidNip('0000000000')); + self::assertFalse(Regex::isValidNip('abc')); + self::assertFalse(Regex::isValidNip($this->simpleText)); + + self::assertTrue(Regex::isValidNip('7340009469')); // Onet S.A. + self::assertTrue(Regex::isValidNip('5252530705')); // Facebook Poland sp. z o.o. + } + + /** + * @param string $phoneNumber The phone number to validate / verify + * @param bool $expected Information if phone number is valid + * + * @dataProvider providePhoneNumber + */ + public static function testIsValidPhoneNumber($phoneNumber, $expected) + { + self::assertEquals($expected, Regex::isValidPhoneNumber($phoneNumber)); + } + + /** + * @param mixed $emptyValue Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public static function testIsValidPhoneNumberUsingEmptyValue($emptyValue) + { + self::assertFalse(Regex::isValidPhoneNumber($emptyValue)); + } + + /** + * @param string $taxIdString Tax ID (NIP) string + * @param bool $expected Information if tax ID is valid + * + * @dataProvider provideTaxId + */ + public static function testIsValidTaxId($taxIdString, $expected) + { + self::assertEquals($expected, Regex::isValidTaxId($taxIdString)); + } + + /** + * @param mixed $emptyValue Empty value, e.g. "" + * @dataProvider provideEmptyValue + */ + public static function testIsValidTaxIdUsingEmptyValue($emptyValue) + { + self::assertFalse(Regex::isValidTaxId($emptyValue)); } public function testIsValidUrl() @@ -120,28 +2171,37 @@ class RegexTest extends BaseTestCase } } - public function testIsSubPathOf() + public function testIsWindowsBasedPath() { - self::assertFalse(Regex::isSubPathOf(null, null)); - self::assertFalse(Regex::isSubPathOf('', '')); + self::assertTrue(Regex::isWindowsBasedPath('C:\path\to\directory')); + self::assertTrue(Regex::isWindowsBasedPath('C:\path\to\file.jpg')); - self::assertFalse(Regex::isSubPathOf('', '/my/directory')); - self::assertFalse(Regex::isSubPathOf('/my/file', '')); - self::assertFalse(Regex::isSubPathOf('/my/file', '/my/directory')); - - self::assertTrue(Regex::isSubPathOf('/my/directory', '/my/directory')); - self::assertTrue(Regex::isSubPathOf('/my/directory/', '/my/directory')); - self::assertTrue(Regex::isSubPathOf('/my/directory', '/my/directory/')); - self::assertTrue(Regex::isSubPathOf('/my/directory/', '/my/directory/')); - - self::assertTrue(Regex::isSubPathOf('/my/another/directory/another/file', '/my/another/directory')); + self::assertFalse(Regex::isWindowsBasedPath('/path/to/directory')); + self::assertFalse(Regex::isWindowsBasedPath('/path/to/file.jpg')); } - public function testIsLetterOrDigit() + /** + * @param array|string $patterns The patterns to match + * @param string $subject The string to check + * @param bool $expected Information if given $subject matches given $patterns + * + * @dataProvider providePatternsAndSubjectForPregMultiMatch + */ + public function testPregMultiMatch($patterns, $subject, $expected) { - self::assertTrue(Regex::isLetterOrDigit('a')); - self::assertTrue(Regex::isLetterOrDigit(10)); - self::assertFalse(Regex::isLetterOrDigit(';')); + self::assertEquals($expected, Regex::pregMultiMatch($patterns, $subject)); + } + + /** + * @param array|string $patterns The patterns to match + * @param string $subject The string to check + * @param bool $expected Information if given $subject matches given $patterns + * + * @dataProvider providePatternsAndSubjectForPregMultiMatchWhenMustMatchAllPatterns + */ + public function testPregMultiMatchWhenMustMatchAllPatterns($patterns, $subject, $expected) + { + self::assertEquals($expected, Regex::pregMultiMatch($patterns, $subject, true)); } public function testStartsWith() @@ -171,1834 +2231,27 @@ class RegexTest extends BaseTestCase public function testStartsWithDirectorySeparator() { - /* - * Not provided, default separator - */ + // Not provided, default separator self::assertTrue(Regex::startsWithDirectorySeparator('/my/extra/directory')); self::assertFalse(Regex::startsWithDirectorySeparator('my/extra/directory')); - /* - * Slash as separator - */ + // Slash as separator $separatorSlash = '/'; self::assertTrue(Regex::startsWithDirectorySeparator('/my/extra/directory', $separatorSlash)); self::assertFalse(Regex::startsWithDirectorySeparator('my/extra/directory', $separatorSlash)); - /* - * Backslash as separator - */ + // Backslash as separator $separatorBackslash = '\\'; self::assertTrue(Regex::startsWithDirectorySeparator('\my\extra\directory', $separatorBackslash)); self::assertFalse(Regex::startsWithDirectorySeparator('my\extra\directory', $separatorBackslash)); } - public function testEndsWithDirectorySeparator() - { - /* - * Not provided, default separator - */ - self::assertTrue(Regex::endsWithDirectorySeparator('my simple text/')); - self::assertFalse(Regex::endsWithDirectorySeparator('my simple text')); - - /* - * Slash as separator - */ - $separatorSlash = '/'; - - self::assertTrue(Regex::endsWithDirectorySeparator('my simple text/', $separatorSlash)); - self::assertFalse(Regex::endsWithDirectorySeparator('my simple text', $separatorSlash)); - - /* - * Backslash as separator - */ - $separatorBackslash = '\\'; - - self::assertTrue(Regex::endsWithDirectorySeparator('my simple text\\', $separatorBackslash)); - self::assertFalse(Regex::endsWithDirectorySeparator('my simple text', $separatorBackslash)); - } - - public function testEndsWith() - { - self::assertFalse(Regex::endsWith($this->simpleText, '\.\.\.')); - self::assertFalse(Regex::endsWith($this->simpleText, '\.')); - self::assertTrue(Regex::endsWith($this->simpleText, 't')); - } - - public function testIsSetUriParameter() - { - $uri = 'www.domain.com/?name=phil&type=4'; - - $parameterName = 'type'; - self::assertTrue(Regex::isSetUriParameter($uri, $parameterName)); - - $parameterName = 'color'; - self::assertFalse(Regex::isSetUriParameter($uri, $parameterName)); - } - - public function testContainsEntities() - { - self::assertFalse(Regex::containsEntities('Lorem ipsum')); - self::assertTrue(Regex::containsEntities('Lorem ipsum »')); - } - - public function testContains() - { - self::assertTrue(Regex::contains($this->simpleText, 'ipsum')); - self::assertFalse(Regex::contains($this->simpleText, 'neque')); - - self::assertFalse(Regex::contains($this->simpleText, '.')); - self::assertTrue(Regex::contains($this->simpleText, 'l')); - } - - public function testIsFileName() - { - $filePath = __FILE__; - $directoryPath = dirname($filePath); - - self::assertTrue(Regex::isFileName($filePath)); - self::assertFalse(Regex::isFileName($directoryPath)); - } - - public function testIsQuoted() - { - self::assertTrue(Regex::isQuoted('\'lorem ipsum\'')); - self::assertTrue(Regex::isQuoted('"lorem ipsum"')); - - self::assertFalse(Regex::isQuoted('lorem ipsum')); - self::assertFalse(Regex::isQuoted(new \stdClass())); - } - - public function testIsWindowsBasedPath() - { - self::assertTrue(Regex::isWindowsBasedPath('C:\path\to\directory')); - self::assertTrue(Regex::isWindowsBasedPath('C:\path\to\file.jpg')); - - self::assertFalse(Regex::isWindowsBasedPath('/path/to/directory')); - self::assertFalse(Regex::isWindowsBasedPath('/path/to/file.jpg')); - } - - public function testIsValidNip() - { - self::assertFalse(Regex::isValidNip(null)); - self::assertFalse(Regex::isValidNip('')); - self::assertFalse(Regex::isValidNip(1234)); - self::assertFalse(Regex::isValidNip(1234567890)); - self::assertFalse(Regex::isValidNip(0000000000)); - self::assertFalse(Regex::isValidNip('1234567890')); - self::assertFalse(Regex::isValidNip('0000000000')); - self::assertFalse(Regex::isValidNip('abc')); - self::assertFalse(Regex::isValidNip($this->simpleText)); - - self::assertTrue(Regex::isValidNip('7340009469')); // Onet S.A. - self::assertTrue(Regex::isValidNip('5252530705')); // Facebook Poland sp. z o.o. - } - - /** - * @param mixed $emptyValue Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testIsValidBundleNameUsingEmptyValue($emptyValue) - { - self::assertFalse(Regex::isValidBundleName($emptyValue)); - } - - /** - * @param string $bundleName Full name of bundle to verify, e.g. "MyExtraBundle" - * @param bool $expected Information if it's valid name - * - * @dataProvider provideBundleName - */ - public function testIsValidBundleName($bundleName, $expected) - { - self::assertEquals($expected, Regex::isValidBundleName($bundleName)); - } - - public function testGetBundleNamePattern() - { - self::assertEquals('/^(([A-Z]{1}[a-z0-9]+)((?2))*)(Bundle)$/', Regex::getBundleNamePattern()); - } - - public function testGetHtmlAttributePattern() - { - self::assertEquals('/([\w-]+)="([\w -]+)"/', Regex::getHtmlAttributePattern()); - } - - /** - * @param mixed $emptyValue Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testIsValidHtmlAttributeUsingEmptyValue($emptyValue) - { - self::assertFalse(Regex::isValidHtmlAttribute($emptyValue)); - } - - /** - * @param string $htmlAttribute The html attribute to verify - * @param bool $expected Information if it's valid attribute - * - * @dataProvider provideHtmlAttribute - */ - public function testIsValidHtmlAttribute($htmlAttribute, $expected) - { - self::assertEquals($expected, Regex::isValidHtmlAttribute($htmlAttribute)); - } - - /** - * @param mixed $emptyValue Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public static function testAreValidHtmlAttributesUsingEmptyValue($emptyValue) - { - self::assertFalse(Regex::areValidHtmlAttributes($emptyValue)); - } - - /** - * @param string $htmlAttributes The html attributes to verify - * @param bool $expected Information if attributes are valid - * - * @dataProvider provideHtmlAttributes - */ - public static function testAreValidHtmlAttributes($htmlAttributes, $expected) - { - self::assertEquals($expected, Regex::areValidHtmlAttributes($htmlAttributes)); - } - - /** - * @param string $value Value to verify - * @param bool $expected Information if value is a binary value - * - * @dataProvider provideBinaryValue - */ - public static function testIsBinaryValue($value, $expected) - { - self::assertEquals($expected, Regex::isBinaryValue($value)); - } - - /** - * @param mixed $emptyValue Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public static function testIsValidEmailUsingEmptyValue($emptyValue) - { - self::assertFalse(Regex::isValidEmail($emptyValue)); - } - - /** - * @param string $email E-mail address to validate / verify - * @param bool $expected Information if e-mail is valid - * - * @dataProvider provideEmail - */ - public static function testIsValidEmail($email, $expected) - { - self::assertEquals($expected, Regex::isValidEmail($email)); - } - - /** - * @param mixed $emptyValue Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public static function testIsValidTaxIdUsingEmptyValue($emptyValue) - { - self::assertFalse(Regex::isValidTaxId($emptyValue)); - } - - /** - * @param string $taxIdString Tax ID (NIP) string - * @param bool $expected Information if tax ID is valid - * - * @dataProvider provideTaxId - */ - public static function testIsValidTaxId($taxIdString, $expected) - { - self::assertEquals($expected, Regex::isValidTaxId($taxIdString)); - } - - /** - * @param mixed $emptyValue Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public static function testIsValidPhoneNumberUsingEmptyValue($emptyValue) - { - self::assertFalse(Regex::isValidPhoneNumber($emptyValue)); - } - - /** - * @param string $phoneNumber The phone number to validate / verify - * @param bool $expected Information if phone number is valid - * - * @dataProvider providePhoneNumber - */ - public static function testIsValidPhoneNumber($phoneNumber, $expected) - { - self::assertEquals($expected, Regex::isValidPhoneNumber($phoneNumber)); - } - - /** - * @param string $pattern Pattern to match - * @param array $dataArray The array - * @param array $expected Expected array - * - * @dataProvider providePatternForArrayValues - */ - public static function testGetArrayValuesByPatternUsingValues($pattern, array $dataArray, $expected) - { - self::assertEquals($expected, Regex::getArrayValuesByPattern($pattern, $dataArray)); - } - - /** - * @param string $pattern Pattern to match - * @param array $dataArray The array - * @param array $expected Expected array - * - * @dataProvider providePatternForArrayKeys - */ - public static function testGetArrayValuesByPatternUsingKeys($pattern, array $dataArray, $expected) - { - self::assertEquals($expected, Regex::getArrayValuesByPattern($pattern, $dataArray, true)); - } - - public static function testGetUrlPatternWithProtocolRequired() - { - $pattern = '/^([a-z]+:\/\/)([\da-z\.-]+)\.([a-z\.]{2,6})(\/)?([\w\.\-]*)?(\?)?([\w \.\-\/=&]*)\/?$/i'; - self::assertEquals($pattern, Regex::getUrlPattern(true)); - } - - public static function testGetUrlPatternWithoutProtocol() - { - $pattern = '/^([a-z]+:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})(\/)?([\w\.\-]*)?(\?)?([\w \.\-\/=&]*)\/?$/i'; - self::assertEquals($pattern, Regex::getUrlPattern()); - } - - /** - * @param array $array The array that should be filtered - * @param string $arrayColumnKey Column name - * @param string $filterExpression Simple filter expression, e.g. "== 2" or "!= \'home\'" - * @param array $expected Expected array - * - * @dataProvider provideSimpleExpressionForArrayFiltering - */ - public function testArrayFilterUsingSimpleExpression($array, $arrayColumnKey, $filterExpression, $expected) - { - self::assertEquals($expected, Regex::arrayFilter($array, $arrayColumnKey, $filterExpression)); - } - - /** - * @param array $array The array that should be filtered - * @param string $arrayColumnKey Column name - * @param string $filterExpression Regular expression, e.g. "/\d+/" or "/[a-z]+[,;]{2,}/" - * @param array $expected Expected array - * - * @dataProvider provideRegularExpressionForArrayFiltering - */ - public function testArrayFilterUsingRegularExpression($array, $arrayColumnKey, $filterExpression, $expected) - { - self::assertEquals($expected, Regex::arrayFilter($array, $arrayColumnKey, $filterExpression, true)); - } - - /** - * @param array|string $patterns The patterns to match - * @param string $subject The string to check - * @param bool $expected Information if given $subject matches given $patterns - * - * @dataProvider providePatternsAndSubjectForPregMultiMatch - */ - public function testPregMultiMatch($patterns, $subject, $expected) - { - self::assertEquals($expected, Regex::pregMultiMatch($patterns, $subject)); - } - - /** - * @param array|string $patterns The patterns to match - * @param string $subject The string to check - * @param bool $expected Information if given $subject matches given $patterns - * - * @dataProvider providePatternsAndSubjectForPregMultiMatchWhenMustMatchAllPatterns - */ - public function testPregMultiMatchWhenMustMatchAllPatterns($patterns, $subject, $expected) - { - self::assertEquals($expected, Regex::pregMultiMatch($patterns, $subject, true)); - } - - public function testGetMoneyPattern() - { - self::assertEquals('/^[-+]?\d+([\.,]{1}\d*)?$/', Regex::getMoneyPattern()); - } - - /** - * @param mixed $emptyValue Empty value, e.g. "" - * @dataProvider provideEmptyNonMoneyValue - */ - public function testIsValidMoneyValueUsingEmptyValue($emptyValue) - { - self::assertFalse(Regex::isValidMoneyValue($emptyValue)); - } - - /** - * @param mixed $value Value to verify - * @param bool $expected Information if given value is a money value - * - * @dataProvider provideMoneyValue - */ - public function testIsValidMoneyValue($value, $expected) - { - self::assertEquals($expected, Regex::isValidMoneyValue($value)); - } - - /** - * @param mixed $nonScalarValue Non scalar value, e.g. [] or null - * @dataProvider provideNonScalarValue - */ - public function testGetValidColorHexValueUsingNonScalarValue($nonScalarValue) - { - self::assertFalse(Regex::getValidColorHexValue($nonScalarValue)); - } - - /** - * @param mixed $emptyValue Empty value, e.g. "" - * @dataProvider provideColorEmptyValue - */ - public function testGetValidColorHexValueUsingEmptyValueWithoutException($emptyValue) - { - self::assertFalse(Regex::getValidColorHexValue($emptyValue, false)); - } - - /** - * @param mixed $emptyValue Empty value, e.g. "" - * @dataProvider provideColorEmptyValue - */ - public function testGetValidColorHexValueUsingEmptyValue($emptyValue) - { - $this->setExpectedException(IncorrectColorHexLengthException::class); - Regex::getValidColorHexValue($emptyValue); - } - - /** - * @param string $incorrectColor Incorrect value of color - * @dataProvider provideColorIncorrectLength - */ - public function testGetValidColorHexValueUsingIncorrectValueWithoutException($incorrectColor) - { - self::assertFalse(Regex::getValidColorHexValue($incorrectColor, false)); - } - - /** - * @param string $incorrectColor Incorrect value of color - * @dataProvider provideColorIncorrectLength - */ - public function testGetValidColorHexValueUsingIncorrectValue($incorrectColor) - { - $this->setExpectedException(IncorrectColorHexLengthException::class); - Regex::getValidColorHexValue($incorrectColor); - } - - /** - * @param string $invalidColor Invalid value of color - * @dataProvider provideColorInvalidValue - */ - public function testGetValidColorHexValueUsingInvalidValueWithoutException($invalidColor) - { - self::assertFalse(Regex::getValidColorHexValue($invalidColor, false)); - } - - /** - * @param string $invalidColor Invalid value of color - * @dataProvider provideColorInvalidValue - */ - public function testGetValidColorHexValueUsingInvalidValue($invalidColor) - { - $this->setExpectedException(InvalidColorHexValueException::class); - Regex::getValidColorHexValue($invalidColor); - } - - /** - * @param string $color Color to verify - * @param string $expected Expected value of color - * - * @dataProvider provideColor - */ - public function testGetValidColorHexValue($color, $expected) - { - self::assertEquals($expected, Regex::getValidColorHexValue($color)); - } - - /** - * @param mixed $emptyValue Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public static function testIsSizeValueUsingEmptyValue($emptyValue) - { - self::assertFalse(Regex::isSizeValue($emptyValue)); - } - - /** - * @param string $description Description of test - * @param string $value Value to verify - * @param string $separator Separator used to split width and height - * @param bool $expected Expected result of verification - * - * @dataProvider provideSizeToVerify - */ - public function testIsSizeValue($description, $value, $separator, $expected) - { - self::assertEquals($expected, Regex::isSizeValue($value, $separator), $description); - } - - /** - * @param string $value Value that should be transformed to slug - * @param string $expected Expected slug - * - * @dataProvider provideValueSlug - */ - public function testCreateSlug($value, $expected) - { - self::assertSame($expected, Regex::createSlug($value)); - } - - /** - * Provides name of bundle and information if it's valid name - * - * @return Generator - */ - public function provideBundleName() - { - yield[ - 'something', - false, - ]; - - yield[ - 'something_different', - false, - ]; - - yield[ - 'something-else', - false, - ]; - - yield[ - 'myExtraBundle', - false, - ]; - - yield[ - 'MyExtra', - false, - ]; - - yield[ - 'MyExtraBundle', - true, - ]; - - yield[ - 'MySuperExtraGorgeousBundle', - true, - ]; - } - - /** - * Provides html attribute and information if it's valid - * - * @return Generator - */ - public function provideHtmlAttribute() - { - yield[ - 'abc = def', - false, - ]; - - yield[ - 'a b c=def', - false, - ]; - - yield[ - 'abc=def', - false, - ]; - - yield[ - 'a1b2c=d3e4f', - false, - ]; - - yield[ - 'abc="def"', - true, - ]; - - yield[ - 'a1b2c="d3e4f"', - true, - ]; - } - - /** - * Provides html attribute and information if attributes are valid - * - * @return Generator - */ - public function provideHtmlAttributes() - { - yield[ - 'abc = def', - false, - ]; - - yield[ - 'abc = def ghi = jkl', - false, - ]; - - yield[ - 'abc=def ghi=jkl', - false, - ]; - - yield[ - 'abc=def ghi=jkl mno=pqr', - false, - ]; - - yield[ - 'abc="def"', - true, - ]; - - yield[ - 'abc="def" ghi="jkl"', - true, - ]; - - yield[ - 'abc="def" ghi="jkl" mno="pqr"', - true, - ]; - - yield[ - 'a2bc="d4ef" ghi="j k l" mno="pq9r"', - true, - ]; - } - - /** - * Provides value to verify if it is a binary value - * - * @return Generator - */ - public function provideBinaryValue() - { - $file1Path = $this->getFilePathForTesting('lorem-ipsum.txt'); - $file2Path = $this->getFilePathForTesting('minion.jpg'); - - yield[ - null, - false, - ]; - - yield[ - [], - false, - ]; - - yield[ - '', - false, - ]; - - yield[ - 'abc', - false, - ]; - - yield[ - '1234', - false, - ]; - - yield[ - 1234, - false, - ]; - - yield[ - 12.34, - false, - ]; - - yield[ - fread(fopen($file1Path, 'rb'), 1), - false, - ]; - - yield[ - fread(fopen($file2Path, 'rb'), 1), - true, - ]; - } - - /** - * Provides e-mail and information if it's valid - * - * @return Generator - */ - public function provideEmail() - { - yield[ - '1', - false, - ]; - - yield[ - 1, - false, - ]; - - yield[ - 'a@a', - false, - ]; - - yield[ - 'a@a.com', - false, - ]; - - yield[ - 'aa@a.com', - true, - ]; - - yield[ - 'a.b@d.com', - true, - ]; - } - - /** - * Provides tax ID and information if it's valid - * - * @return Generator - */ - public function provideTaxId() - { - yield[ - '123', - false, - ]; - - yield[ - '12345', - false, - ]; - - yield[ - '1122334455', - false, - ]; - - yield[ - '1234567890', - false, - ]; - - yield[ - '0987654321', - false, - ]; - - /* - * Microsoft sp. z o.o. - */ - yield[ - '5270103391', - true, - ]; - - /* - * Onet S.A. - */ - yield[ - '7340009469', - true, - ]; - } - - /** - * Provides phone number and information if it's valid - * - * @return Generator - */ - public function providePhoneNumber() - { - yield[ - 'abc', - false, - ]; - - yield[ - '1-2-3', - false, - ]; - - yield[ - '123', - true, - ]; - - yield[ - '123 456 789', - true, - ]; - - yield[ - '123456789', - true, - ]; - } - - /** - * Provides pattern and array with values that should match that pattern - * - * @return Generator - */ - public function providePatternForArrayValues() - { - yield[ - '/\d/', - [], - [], - ]; - - yield[ - '/\d+/', - [ - 'lorem', - 'ipsum', - 123, - 'dolor', - '456', - ], - [ - 2 => 123, - 4 => '456', - ], - ]; - - yield[ - '/\d+-[a-z]+/', - [ - 'lorem', - 123, - false, - 'dolor', - '456-ipsum', - ], - [ - 4 => '456-ipsum', - ], - ]; - } - - /** - * Provides pattern and array with keys that should match that pattern - * - * @return Generator - */ - public function providePatternForArrayKeys() - { - yield[ - '/\d/', - [], - [], - ]; - - yield[ - '/\d+/', - [ - 'lorem' => 'ipsum', - 'dolor' => 123, - 'sit', - 4 => '456', - ], - [ - 0 => 'sit', - 4 => '456', - ], - ]; - - yield[ - '/\d+-[a-z]+/', - [ - 'lorem', - '456-ipsum' => 123, - '001-sit' => false, - 'dolor', - ], - [ - '456-ipsum' => 123, - '001-sit' => false, - ], - ]; - } - - /** - * Provides simple compare expression for array filtering and the array - * - * @return Generator - */ - public function provideSimpleExpressionForArrayFiltering() - { - yield[ - [], - 'id', - ' == 2', - [], - ]; - - yield[ - [ - [ - 'id' => 1, - 'first_name' => 'Jane', - 'last_name' => 'Scott', - 'is_active' => true, - ], - [ - 'id' => 2, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - 'birth_date', - ' == 2', - [ - [ - 'id' => 1, - 'first_name' => 'Jane', - 'last_name' => 'Scott', - 'is_active' => true, - ], - [ - 'id' => 2, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - ]; - - yield[ - [ - [ - 'id' => 1, - 'first_name' => 'Jane', - 'last_name' => 'Scott', - 'is_active' => true, - ], - [ - 'id' => 2, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - 'id', - ' == 2', - [ - 1 => [ - 'id' => 2, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - ], - ]; - - yield[ - [ - [ - 'id' => 1, - 'first_name' => 'Jane', - 'last_name' => 'Scott', - 'is_active' => true, - ], - [ - 'id' => 2, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - 'id', - ' >= 2', - [ - 1 => [ - 'id' => 2, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - 2 => [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - ]; - - yield[ - [ - [ - 'id' => 1, - 'first_name' => 'Jane', - 'last_name' => 'Scott', - 'is_active' => true, - ], - [ - 'id' => 2, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - 'is_active', - ' !== true', - [ - 2 => [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - ]; - - yield[ - [ - [ - 'id' => 1, - 'first_name' => 'Jane', - 'last_name' => 'Scott', - 'is_active' => true, - ], - [ - 'id' => 2, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - 'first_name', - ' == \'Mike\'', - [ - 2 => [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - ]; - } - - /** - * Provides regular expression for array filtering and the array - * - * @return Generator - */ - public function provideRegularExpressionForArrayFiltering() - { - yield[ - [], - 'id', - '/\d+/', - [], - ]; - - yield[ - [ - [ - 'id' => 1, - 'first_name' => 'Jane', - 'last_name' => 'Scott', - 'is_active' => true, - ], - [ - 'id' => 2, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - 'birth_date', - '/\d+/', - [ - [ - 'id' => 1, - 'first_name' => 'Jane', - 'last_name' => 'Scott', - 'is_active' => true, - ], - [ - 'id' => 2, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - ]; - - yield[ - [ - [ - 'id' => 1, - 'first_name' => 'Jane', - 'last_name' => 'Scott', - 'is_active' => true, - ], - [ - 'id' => 123, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - 'id', - '/\d{3}/', - [ - 1 => [ - 'id' => 123, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - ], - ]; - - yield[ - [ - [ - 'id' => 1, - 'first_name' => 'Jane', - 'last_name' => 'Scott', - 'is_active' => true, - ], - [ - 'id' => 123, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - [ - 'id' => 456, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - 'first_name', - '/George|Mike/', - [ - 1 => [ - 'id' => 123, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - 2 => [ - 'id' => 456, - 'first_name' => 'Mike', - 'last_name' => 'Green', - 'is_active' => false, - ], - ], - ]; - - yield[ - [ - [ - 'id' => 1, - 'first_name' => 'Jane', - 'last_name' => 'Scott', - 'is_active' => true, - ], - [ - 'id' => 2, - 'first_name' => 'George', - 'last_name' => 'Brown', - 'is_active' => true, - ], - [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green-Blue', - 'is_active' => false, - ], - ], - 'last_name', - '/\w+-\w+/', - [ - 2 => [ - 'id' => 3, - 'first_name' => 'Mike', - 'last_name' => 'Green-Blue', - 'is_active' => false, - ], - ], - ]; - } - - /** - * Provides patterns and subject for the pregMultiMatch() method - * - * @return Generator - */ - public function providePatternsAndSubjectForPregMultiMatch() - { - yield[ - '', - '', - false, - ]; - - yield[ - [], - '', - false, - ]; - - yield[ - '/\d+/', - 'Lorem ipsum dolor sit', - false, - ]; - - yield[ - [ - '/\d+/', - '/^[a-z]{4}$/', - ], - 'Lorem ipsum dolor sit', - false, - ]; - - yield[ - '/\w+/', - 'Lorem ipsum dolor sit', - true, - ]; - - yield[ - [ - '/\d+/', - '/\w+/', - ], - 'Lorem ipsum dolor sit', - true, - ]; - } - - /** - * Provides patterns and subject for the pregMultiMatch() method when must match all patterns - * - * @return Generator - */ - public function providePatternsAndSubjectForPregMultiMatchWhenMustMatchAllPatterns() - { - yield[ - '', - '', - false, - ]; - - yield[ - [], - '', - false, - ]; - - yield[ - '/\d+/', - 'Lorem ipsum dolor sit', - false, - ]; - - yield[ - [ - '/\d+/', - '/^[a-z]{4}$/', - ], - 'Lorem ipsum dolor sit', - false, - ]; - - yield[ - '/\w+/', - 'Lorem ipsum dolor sit', - true, - ]; - - yield[ - [ - '/[a-zA-Z ]+/', - '/\w+/', - ], - 'Lorem ipsum dolor sit', - true, - ]; - } - - /** - * Provides empty non money-related value - * - * @return Generator - */ - public function provideEmptyNonMoneyValue() - { - yield['']; - yield[' ']; - yield[null]; - yield[false]; - yield[[]]; - } - - /** - * Provides money-related value and information if the value is valid - * - * @return Generator - */ - public function provideMoneyValue() - { - yield[ - 'abc', - false, - ]; - - yield[ - '-a.b', - false, - ]; - - yield[ - 'a,b', - false, - ]; - - yield[ - 0, - true, - ]; - - yield[ - 1, - true, - ]; - - yield[ - -1, - true, - ]; - - yield[ - 1.2, - true, - ]; - - yield[ - 1.202, - true, - ]; - - yield[ - -1.202, - true, - ]; - - yield[ - '0', - true, - ]; - - yield[ - '1', - true, - ]; - - yield[ - '-1', - true, - ]; - - yield[ - '1.2', - true, - ]; - - yield[ - '1.202', - true, - ]; - - yield[ - '-1.202', - true, - ]; - - yield[ - '1,202', - true, - ]; - - yield[ - '-1,2', - true, - ]; - - yield[ - '-1,202', - true, - ]; - } - - /** - * Provides value of color with incorrect length - * - * @return Generator - */ - public function provideColorIncorrectLength() - { - yield[ - '12', - ]; - - yield[ - '1234', - ]; - - yield[ - '12345678', - ]; - - yield[ - '#12', - ]; - - yield[ - '#1234', - ]; - - yield[ - '#12345678', - ]; - } - - /** - * Provides invalid value of color - * - * @return Generator - */ - public function provideColorInvalidValue() - { - yield[ - '#qwerty', - ]; - - yield[ - 'qwerty', - ]; - } - - /** - * Provides empty non color-related value - * - * @return Generator - */ - public function provideColorEmptyValue() - { - yield[ - '', - ]; - - yield[ - 0, - ]; - - yield[ - '0', - ]; - - yield[ - false, - ]; - } - - /** - * Provides value of color - * - * @return Generator - */ - public function provideColor() - { - yield[ - '#1b0', - '11bb00', - ]; - - yield[ - '#1B0', - '11bb00', - ]; - - yield[ - '#1ab1ab', - '1ab1ab', - ]; - - yield[ - '#1AB1AB', - '1ab1ab', - ]; - - yield[ - '#000', - '000000', - ]; - } - - /** - * Provide value to create slug - * - * @return Generator - */ - public function provideValueSlug() - { - yield[ - [], - false, - ]; - - yield[ - null, - false, - ]; - - yield[ - '', - '', - ]; - - yield[ - 1234, - '1234', - ]; - - yield[ - '1234', - '1234', - ]; - - yield[ - '1/2/3/4', - '1234', - ]; - - yield[ - '1 / 2 / 3 / 4', - '1-2-3-4', - ]; - - yield[ - 'test', - 'test', - ]; - - yield[ - 'test test', - 'test-test', - ]; - - yield[ - 'lorem ipsum dolor sit', - 'lorem-ipsum-dolor-sit', - ]; - - yield[ - 'Lorem ipsum. Dolor sit 12.34 amet.', - 'lorem-ipsum-dolor-sit-1234-amet', - ]; - - yield[ - 'Was sind Löwen, Bären, Vögel und Käfer (für die Prüfung)?', - 'was-sind-lowen-baren-vogel-und-kafer-fur-die-prufung', - ]; - - yield[ - 'äöü (ÄÖÜ)', - 'aou-aou', - ]; - - yield[ - 'Półka dębowa. Kolor: żółędziowy. Wymiary: 80 x 30 cm.', - 'polka-debowa-kolor-zoledziowy-wymiary-80-x-30-cm', - ]; - - yield[ - 'ąęółńśżźć (ĄĘÓŁŃŚŻŹĆ)', - 'aeolnszzc-aeolnszzc', - ]; - } - - public function provideSizeToVerify() - { - yield[ - 'One number only', - 200, - ' x ', - false, - ]; - - yield[ - 'One number only as string', - '200', - ' x ', - false, - ]; - - yield[ - 'The " " as invalid separator', - '200 100', - ' x ', - false, - ]; - - yield[ - 'The "|" as separator (invalid separator)', - '200 | 100', - ' x ', - false, - ]; - - yield[ - 'The "|" as invalid separator and no spaces around separator', - '200|100', - ' x ', - false, - ]; - - yield[ - 'The "X" as invalid separator', - '200 X 100', - ' x ', - false, - ]; - - yield[ - 'Simple, valid size', - '200 x 100', - ' x ', - true, - ]; - - yield[ - 'Too much spaces at the right of separator', - '200 x 100', - ' x ', - true, - ]; - - yield[ - 'Too much spaces at the left of separator', - '200 x 100', - ' x ', - true, - ]; - - yield[ - 'Too much spaces around separator', - '200 x 100', - ' x ', - true, - ]; - - yield[ - 'Too much spaces before width', - ' 200 x 100', - ' x ', - true, - ]; - - yield[ - 'Too much spaces after height', - '200 x 100 ', - ' x ', - true, - ]; - - yield[ - 'Too much spaces before width and after height', - ' 200 x 100 ', - ' x ', - true, - ]; - - yield[ - 'Too much spaces everywhere (1st)', - ' 200 x 100 ', - ' x ', - true, - ]; - - yield[ - 'Too much spaces everywhere (2nd)', - ' 200 x 100 ', - ' x ', - true, - ]; - - yield[ - 'Too much spaces everywhere (3rd)', - ' 200 x 100 ', - ' x ', - true, - ]; - - yield[ - 'The " X " as custom separator', - '200 X 100', - ' X ', - true, - ]; - - yield[ - 'The "|" as custom separator', - '200|100', - '|', - true, - ]; - - yield[ - 'The " | " as custom separator', - '200 | 100', - ' | ', - true, - ]; - - yield[ - 'The "::" as custom separator', - '200::100', - '::', - true, - ]; - - yield[ - 'The " :: " as custom separator', - '200 :: 100', - ' :: ', - true, - ]; - - yield[ - 'The "." as custom separator', - '200.100', - '.', - true, - ]; - - yield[ - 'The " . " as custom separator', - '200 . 100', - ' . ', - true, - ]; - - yield[ - 'The "/" as custom separator', - '200/100', - '/', - true, - ]; - - yield[ - 'The " / " as custom separator', - '200 / 100', - ' / ', - true, - ]; - - yield[ - 'The " : " as custom separator and too much spaces everywhere', - ' 200 : 100 ', - ' : ', - true, - ]; - } - /** * {@inheritdoc} */ - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -2010,7 +2263,7 @@ class RegexTest extends BaseTestCase /** * {@inheritdoc} */ - protected function tearDown() + protected function tearDown(): void { parent::tearDown(); unset($this->simpleText, $this->camelCaseText); diff --git a/tests/Utilities/Repository/Sortable.php b/tests/Utilities/Repository/Sortable.php index fc877b6..728438f 100644 --- a/tests/Utilities/Repository/Sortable.php +++ b/tests/Utilities/Repository/Sortable.php @@ -14,6 +14,9 @@ namespace Meritoo\Test\Common\Utilities\Repository; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @coversNothing */ class Sortable { @@ -34,6 +37,16 @@ class Sortable $this->position = $position; } + /** + * Returns representation of object as string + * + * @return string + */ + public function __toString() + { + return sprintf('%s (position: %d)', self::class, $this->getPosition()); + } + /** * Returns position used while sorting * @@ -53,14 +66,4 @@ class Sortable { $this->position = $position; } - - /** - * Returns representation of object as string - * - * @return string - */ - public function __toString() - { - return sprintf('%s (position: %d)', self::class, $this->getPosition()); - } } diff --git a/tests/Utilities/RepositoryTest.php b/tests/Utilities/RepositoryTest.php index 72d58a7..572b9ef 100644 --- a/tests/Utilities/RepositoryTest.php +++ b/tests/Utilities/RepositoryTest.php @@ -10,7 +10,6 @@ namespace Meritoo\Test\Common\Utilities; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; -use Doctrine\ORM\Query\Expr\OrderBy; use Doctrine\ORM\QueryBuilder; use Generator; use Meritoo\Common\Test\Base\BaseTestCase; @@ -23,59 +22,794 @@ use stdClass; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\Repository */ class RepositoryTest extends BaseTestCase { + /** + * Provides arrays with extreme position used to replenish positions of them + * + * @return Generator + */ + public function provideArraysWithExtremePosition() + { + yield [ + [ + [ + Repository::POSITION_KEY => 1, + ], + [], + [], + ], + true, + [ + [ + Repository::POSITION_KEY => 1, + ], + [ + Repository::POSITION_KEY => 2, + ], + [ + Repository::POSITION_KEY => 3, + ], + ], + ]; + + yield [ + [ + [], + [], + [ + Repository::POSITION_KEY => 1, + ], + ], + true, + [ + [ + Repository::POSITION_KEY => 2, + ], + [ + Repository::POSITION_KEY => 3, + ], + [ + Repository::POSITION_KEY => 1, + ], + ], + ]; + + yield [ + [ + [ + Repository::POSITION_KEY => 1, + ], + [], + [], + ], + false, + [ + [ + Repository::POSITION_KEY => 1, + ], + [ + Repository::POSITION_KEY => 0, + ], + [ + Repository::POSITION_KEY => -1, + ], + ], + ]; + + yield [ + [ + [], + [], + [ + Repository::POSITION_KEY => 1, + ], + ], + false, + [ + [ + Repository::POSITION_KEY => 0, + ], + [ + Repository::POSITION_KEY => -1, + ], + [ + Repository::POSITION_KEY => 1, + ], + ], + ]; + } + + /** + * Provides arrays with extreme position used to get extreme position + * + * @return Generator + */ + public function provideArraysWithExtremePositionToGetExtremePosition() + { + yield [ + [ + [ + Repository::POSITION_KEY => 1, + ], + [], + [], + ], + true, + 1, + ]; + + yield [ + [ + [ + Repository::POSITION_KEY => 1, + ], + [], + [], + ], + false, + 1, + ]; + + yield [ + [ + [], + [], + [ + Repository::POSITION_KEY => 1, + ], + ], + true, + 1, + ]; + + yield [ + [ + [], + [], + [ + Repository::POSITION_KEY => 1, + ], + ], + false, + 1, + ]; + + yield [ + [ + [ + Repository::POSITION_KEY => 1, + ], + [], + [ + Repository::POSITION_KEY => 2, + ], + [], + ], + true, + 2, + ]; + + yield [ + [ + [ + Repository::POSITION_KEY => 1, + ], + [], + [ + Repository::POSITION_KEY => 2, + ], + [], + ], + false, + 1, + ]; + } + + /** + * Provides arrays without extreme position used to replenish positions of them + * + * @return Generator + */ + public function provideArraysWithoutExtremePosition() + { + yield [ + [ + [], + [], + ], + true, + [ + [ + Repository::POSITION_KEY => 1, + ], + [ + Repository::POSITION_KEY => 2, + ], + ], + ]; + + yield [ + [ + [], + [], + ], + false, + [ + [ + Repository::POSITION_KEY => -1, + ], + [ + Repository::POSITION_KEY => -2, + ], + ], + ]; + + yield [ + [ + [ + 'lorem' => 'ipsum', + 'dolor', + 'sit' => 1, + ], + [ + 'abc' => 'def', + 'ghi' => null, + 'jkl' => 10, + ], + ], + true, + [ + [ + 'lorem' => 'ipsum', + 'dolor', + 'sit' => 1, + Repository::POSITION_KEY => 1, + ], + [ + 'abc' => 'def', + 'ghi' => null, + 'jkl' => 10, + Repository::POSITION_KEY => 2, + ], + ], + ]; + + yield [ + [ + [ + 'lorem' => 'ipsum', + 'dolor', + 'sit' => 1, + ], + [ + 'abc' => 'def', + 'ghi' => null, + 'jkl' => 10, + ], + ], + false, + [ + [ + 'lorem' => 'ipsum', + 'dolor', + 'sit' => 1, + Repository::POSITION_KEY => -1, + ], + [ + 'abc' => 'def', + 'ghi' => null, + 'jkl' => 10, + Repository::POSITION_KEY => -2, + ], + ], + ]; + } + + /** + * Provides arrays without extreme position used to get extreme position + * + * @return Generator + */ + public function provideArraysWithoutExtremePositionToGetExtremePosition() + { + yield [ + [], + false, + null, + ]; + + yield [ + [], + true, + null, + ]; + + yield [ + [ + [ + 'lorem' => 'ipsum', + 'dolor', + 'sit' => 1, + ], + [ + 'abc' => 'def', + 'ghi' => null, + 'jkl' => 10, + ], + ], + true, + null, + ]; + + yield [ + [ + [ + 'lorem' => 'ipsum', + 'dolor', + 'sit' => 1, + ], + [ + 'abc' => 'def', + 'ghi' => null, + 'jkl' => 10, + ], + ], + false, + null, + ]; + } + + /** + * Provides objects with extreme position used to replenish positions of them + * + * @return Generator + */ + public function provideObjectsWithExtremePosition() + { + yield [ + [ + new Sortable(1), + new Sortable(), + new Sortable(), + ], + true, + [ + new Sortable(1), + new Sortable(2), + new Sortable(3), + ], + ]; + + yield [ + [ + new Sortable(), + new Sortable(1), + new Sortable(), + ], + true, + [ + new Sortable(2), + new Sortable(1), + new Sortable(3), + ], + ]; + + yield [ + [ + new Sortable(1), + new Sortable(), + new Sortable(), + ], + false, + [ + new Sortable(1), + new Sortable(0), + new Sortable(-1), + ], + ]; + } + + /** + * Provides objects with extreme position used to get extreme position + * + * @return Generator + */ + public function provideObjectsWithExtremePositionToGetExtremePosition() + { + yield [ + [ + new Sortable(1), + new Sortable(2), + new Sortable(3), + ], + true, + 3, + ]; + + yield [ + [ + new Sortable(1), + new Sortable(2), + new Sortable(3), + ], + false, + 1, + ]; + } + + /** + * Provides objects without extreme position used to replenish positions of them + * + * @return Generator + */ + public function provideObjectsWithoutExtremePosition() + { + yield [ + [ + new Sortable(), + new Sortable(), + new Sortable(), + ], + true, + [ + new Sortable(1), + new Sortable(2), + new Sortable(3), + ], + ]; + + yield [ + [ + new Sortable(), + new Sortable(), + new Sortable(), + ], + false, + [ + new Sortable(-1), + new Sortable(-2), + new Sortable(-3), + ], + ]; + } + + /** + * Provides objects without extreme position used to get extreme position + * + * @return Generator + */ + public function provideObjectsWithoutExtremePositionToGetExtremePosition() + { + yield [ + [], + false, + null, + ]; + + yield [ + [], + true, + null, + ]; + + yield [ + [ + new Sortable(), + new Sortable(), + new Sortable(), + ], + true, + null, + ]; + + yield [ + [ + new Sortable(), + new Sortable(), + new Sortable(), + ], + false, + null, + ]; + } + + /** + * Provide name of property, direction and expected ORDER BY clause used to get query builder + * + * @return Generator + */ + public function providePropertyAndDirectionToGetEntityOrderedQueryBuilder() + { + yield [ + null, + null, + '', + ]; + + yield [ + '', + '', + '', + ]; + + yield [ + 'first_name', + '', + 'qb.first_name ASC', + ]; + + yield [ + 'first_name', + 'asc', + 'qb.first_name asc', + ]; + + yield [ + 'first_name', + 'ASC', + 'qb.first_name ASC', + ]; + + yield [ + 'first_name', + 'desc', + 'qb.first_name desc', + ]; + + yield [ + 'first_name', + 'DESC', + 'qb.first_name DESC', + ]; + } + + public function provideSortedItems() + { + $sortable1 = new Sortable(); + $sortable1->setPosition(1); + + $sortable2 = new Sortable(); + $sortable2->setPosition(2); + + $sortable3 = new Sortable(); + $sortable3->setPosition(309); + + yield [ + 'An array with 1 item only', + [ + [ + 'test 1', + 'position' => 1, + ], + ], + [ + [ + 'test 1', + 'position' => 1, + ], + ], + ]; + + yield [ + 'An array with more than 1 item', + [ + [ + 'test 1', + 'position' => 1, + ], + [ + 'test 2', + 'position' => 2, + ], + [ + 'test 3', + 'position' => 309, + ], + ], + [ + [ + 'test 1', + 'position' => 1, + ], + [ + 'test 2', + 'position' => 2, + ], + [ + 'test 3', + 'position' => 309, + ], + ], + ]; + + yield [ + '1 object only', + [ + $sortable1, + ], + [ + $sortable1, + ], + ]; + + yield [ + 'More than 1 object', + [ + $sortable1, + $sortable2, + $sortable3, + ], + [ + $sortable1, + $sortable2, + $sortable3, + ], + ]; + } + public function testConstructor() { static::assertHasNoConstructor(Repository::class); } - public function testReplenishPositionsWithoutItems() + /** + * @param string $property Name of property used by the ORDER BY clause + * @param string $direction Direction used by the ORDER BY clause ("ASC" or "DESC") + * @param string $expectedOrderBy Expected ORDER BY clause + * + * @dataProvider providePropertyAndDirectionToGetEntityOrderedQueryBuilder + */ + public function testGetEntityOrderedQueryBuilder($property, $direction, $expectedOrderBy) { - $items = []; - Repository::replenishPositions($items); + $entityManager = $this->createMock(EntityManagerInterface::class); - static::assertSame([], $items); + $entityRepository = $this + ->getMockBuilder(EntityRepository::class) + ->disableOriginalConstructor() + ->setMethods([ + 'createQueryBuilder', + ]) + ->getMock(); + + $expectedQueryBuilder = new QueryBuilder($entityManager); + $expectedQueryBuilder->from('any_table_name', 'qb'); + + $entityRepository + ->expects(static::once()) + ->method('createQueryBuilder') + ->willReturn($expectedQueryBuilder) + ; + + $queryBuilder = Repository::getEntityOrderedQueryBuilder($entityRepository, $property, $direction); + $selectDQLPart = $queryBuilder->getDQLPart('select'); + $whereDQLPart = $queryBuilder->getDQLPart('where'); + $orderDQLPart = $queryBuilder->getDQLPart('orderBy'); + $rootAliases = $queryBuilder->getRootAliases(); + + static::assertInstanceOf(QueryBuilder::class, $queryBuilder); + static::assertArrayHasKey(0, $rootAliases); + static::assertSame('qb', $rootAliases[0]); + static::assertSame([], $selectDQLPart); + static::assertNull($whereDQLPart); + + if (empty($property)) { + static::assertSame([], $orderDQLPart); + } else { + /** @var OrderBy $orderBy */ + $orderBy = $orderDQLPart[0]; + + static::assertSame([$expectedOrderBy], $orderBy->getParts()); + } } - public function testReplenishPositionsUsingNotSortableObjects() + public function testGetEntityOrderedQueryBuilderUsingDefaults() { - $before = [ - new stdClass(), - new stdClass(), - new stdClass(), - ]; + $entityManager = $this->createMock(EntityManagerInterface::class); - $after = [ - new stdClass(), - new stdClass(), - new stdClass(), - ]; + $entityRepository = $this + ->getMockBuilder(EntityRepository::class) + ->disableOriginalConstructor() + ->setMethods([ + 'createQueryBuilder', + ]) + ->getMock(); - /* - * Using defaults - */ - Repository::replenishPositions($before); - static::assertEquals($before, $after); + $expectedQueryBuilder = new QueryBuilder($entityManager); + $expectedQueryBuilder->from('any_table_name', 'qb'); - /* - * Place items at the top - */ - Repository::replenishPositions($before, false); - static::assertEquals($before, $after); + $entityRepository + ->expects(static::once()) + ->method('createQueryBuilder') + ->willReturn($expectedQueryBuilder) + ; - /* - * Set positions even there is no extreme position (at the end) - */ - Repository::replenishPositions($before, true, true); - static::assertEquals($before, $after); + $queryBuilder = Repository::getEntityOrderedQueryBuilder($entityRepository); + $selectDQLPart = $queryBuilder->getDQLPart('select'); + $whereDQLPart = $queryBuilder->getDQLPart('where'); + $orderDQLPart = $queryBuilder->getDQLPart('orderBy'); + $rootAliases = $queryBuilder->getRootAliases(); - /* - * Set positions even there is no extreme position (at the top) - */ - Repository::replenishPositions($before, false, true); - static::assertEquals($before, $after); + /** @var OrderBy $orderBy */ + $orderBy = $orderDQLPart[0]; + + static::assertInstanceOf(QueryBuilder::class, $queryBuilder); + static::assertArrayHasKey(0, $rootAliases); + static::assertSame('qb', $rootAliases[0]); + static::assertSame([], $selectDQLPart); + static::assertNull($whereDQLPart); + static::assertSame(['qb.name ASC'], $orderBy->getParts()); + } + + /** + * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays + * @param bool $max If is set to true, maximum value is returned. Otherwise - minimum. + * @param int $expected Extreme position (max or min) of given items + * + * @dataProvider provideArraysWithExtremePositionToGetExtremePosition + */ + public function testGetExtremePositionUsingArraysWithExtremePosition(array $items, $max, $expected) + { + static::assertSame($expected, Repository::getExtremePosition($items, $max)); + } + + /** + * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays + * @param bool $max If is set to true, maximum value is returned. Otherwise - minimum. + * @param int $expected Extreme position (max or min) of given items + * + * @dataProvider provideArraysWithoutExtremePositionToGetExtremePosition + */ + public function testGetExtremePositionUsingArraysWithoutExtremePosition(array $items, $max, $expected) + { + static::assertSame($expected, Repository::getExtremePosition($items, $max)); + } + + /** + * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays + * @param bool $max If is set to true, maximum value is returned. Otherwise - minimum. + * @param int $expected Extreme position (max or min) of given items + * + * @dataProvider provideObjectsWithExtremePositionToGetExtremePosition + */ + public function testGetExtremePositionUsingObjectsWithExtremePosition(array $items, $max, $expected) + { + static::assertSame($expected, Repository::getExtremePosition($items, $max)); + } + + /** + * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays + * @param bool $max If is set to true, maximum value is returned. Otherwise - minimum. + * @param int $expected Extreme position (max or min) of given items + * + * @dataProvider provideObjectsWithoutExtremePositionToGetExtremePosition + */ + public function testGetExtremePositionUsingObjectsWithoutExtremePosition(array $items, $max, $expected) + { + static::assertSame($expected, Repository::getExtremePosition($items, $max)); + } + + public function testGetExtremePositionWithoutItems() + { + static::assertNull(Repository::getExtremePosition([])); + static::assertNull(Repository::getExtremePosition([], false)); + } + + /** + * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays + * @param bool $asLast If is set to true, items are placed at the end (default behaviour). Otherwise - at top. + * @param array $expected Items with replenished positions + * + * @dataProvider provideArraysWithExtremePosition + */ + public function testReplenishPositionsUsingArraysWithExtremePositionForce(array $items, $asLast, array $expected) + { + Repository::replenishPositions($items, $asLast, true); + static::assertSame($expected, $items); } /** @@ -104,17 +838,48 @@ class RepositoryTest extends BaseTestCase static::assertSame($expected, $items); } + public function testReplenishPositionsUsingNotSortableObjects() + { + $before = [ + new stdClass(), + new stdClass(), + new stdClass(), + ]; + + $after = [ + new stdClass(), + new stdClass(), + new stdClass(), + ]; + + // Using defaults + Repository::replenishPositions($before); + static::assertEquals($before, $after); + + // Place items at the top + Repository::replenishPositions($before, false); + static::assertEquals($before, $after); + + // Set positions even there is no extreme position (at the end) + Repository::replenishPositions($before, true, true); + static::assertEquals($before, $after); + + // Set positions even there is no extreme position (at the top) + Repository::replenishPositions($before, false, true); + static::assertEquals($before, $after); + } + /** * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays * @param bool $asLast If is set to true, items are placed at the end (default behaviour). Otherwise - at top. * @param array $expected Items with replenished positions * - * @dataProvider provideArraysWithExtremePosition + * @dataProvider provideObjectsWithExtremePosition */ - public function testReplenishPositionsUsingArraysWithExtremePositionForce(array $items, $asLast, array $expected) + public function testReplenishPositionsUsingObjectsWithExtremePositionForce(array $items, $asLast, array $expected) { Repository::replenishPositions($items, $asLast, true); - static::assertSame($expected, $items); + static::assertEquals($expected, $items); } /** @@ -144,695 +909,23 @@ class RepositoryTest extends BaseTestCase } /** - * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays - * @param bool $asLast If is set to true, items are placed at the end (default behaviour). Otherwise - at top. - * @param array $expected Items with replenished positions + * @param string $description Description of test + * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays + * @param array $expected Expected items with positions replenished * - * @dataProvider provideObjectsWithExtremePosition + * @dataProvider provideSortedItems */ - public function testReplenishPositionsUsingObjectsWithExtremePositionForce(array $items, $asLast, array $expected) + public function testReplenishPositionsUsingSortedItems(string $description, array $items, array $expected) { - Repository::replenishPositions($items, $asLast, true); - static::assertEquals($expected, $items); + Repository::replenishPositions($items); + static::assertSame($expected, $items, $description); } - public function testGetExtremePositionWithoutItems() + public function testReplenishPositionsWithoutItems() { - static::assertNull(Repository::getExtremePosition([])); - static::assertNull(Repository::getExtremePosition([], false)); - } + $items = []; + Repository::replenishPositions($items); - /** - * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays - * @param bool $max If is set to true, maximum value is returned. Otherwise - minimum. - * @param int $expected Extreme position (max or min) of given items - * - * @dataProvider provideArraysWithoutExtremePositionToGetExtremePosition - */ - public function testGetExtremePositionUsingArraysWithoutExtremePosition(array $items, $max, $expected) - { - static::assertSame($expected, Repository::getExtremePosition($items, $max)); - } - - /** - * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays - * @param bool $max If is set to true, maximum value is returned. Otherwise - minimum. - * @param int $expected Extreme position (max or min) of given items - * - * @dataProvider provideArraysWithExtremePositionToGetExtremePosition - */ - public function testGetExtremePositionUsingArraysWithExtremePosition(array $items, $max, $expected) - { - static::assertSame($expected, Repository::getExtremePosition($items, $max)); - } - - /** - * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays - * @param bool $max If is set to true, maximum value is returned. Otherwise - minimum. - * @param int $expected Extreme position (max or min) of given items - * - * @dataProvider provideObjectsWithoutExtremePositionToGetExtremePosition - */ - public function testGetExtremePositionUsingObjectsWithoutExtremePosition(array $items, $max, $expected) - { - static::assertSame($expected, Repository::getExtremePosition($items, $max)); - } - - /** - * @param array $items Objects who have "getPosition()" and "setPosition()" methods or arrays - * @param bool $max If is set to true, maximum value is returned. Otherwise - minimum. - * @param int $expected Extreme position (max or min) of given items - * - * @dataProvider provideObjectsWithExtremePositionToGetExtremePosition - */ - public function testGetExtremePositionUsingObjectsWithExtremePosition(array $items, $max, $expected) - { - static::assertSame($expected, Repository::getExtremePosition($items, $max)); - } - - public function testGetEntityOrderedQueryBuilderUsingDefaults() - { - $entityManager = $this->getMock(EntityManagerInterface::class); - - $entityRepository = $this - ->getMockBuilder(EntityRepository::class) - ->disableOriginalConstructor() - ->setMethods([ - 'createQueryBuilder', - ]) - ->getMock() - ; - - $expectedQueryBuilder = new QueryBuilder($entityManager); - $expectedQueryBuilder->from('any_table_name', 'qb'); - - $entityRepository - ->expects(static::once()) - ->method('createQueryBuilder') - ->willReturn($expectedQueryBuilder) - ; - - $queryBuilder = Repository::getEntityOrderedQueryBuilder($entityRepository); - $selectDQLPart = $queryBuilder->getDQLPart('select'); - $whereDQLPart = $queryBuilder->getDQLPart('where'); - $orderDQLPart = $queryBuilder->getDQLPart('orderBy'); - - /* @var OrderBy $orderBy */ - $orderBy = $orderDQLPart[0]; - - static::assertInstanceOf(QueryBuilder::class, $queryBuilder); - static::assertArraySubset(['qb'], $queryBuilder->getRootAliases()); - static::assertSame([], $selectDQLPart); - static::assertNull($whereDQLPart); - static::assertSame(['qb.name ASC'], $orderBy->getParts()); - } - - /** - * @param string $property Name of property used by the ORDER BY clause - * @param string $direction Direction used by the ORDER BY clause ("ASC" or "DESC") - * @param string $expectedOrderBy Expected ORDER BY clause - * - * @dataProvider providePropertyAndDirectionToGetEntityOrderedQueryBuilder - */ - public function testGetEntityOrderedQueryBuilder($property, $direction, $expectedOrderBy) - { - $entityManager = $this->getMock(EntityManagerInterface::class); - - $entityRepository = $this - ->getMockBuilder(EntityRepository::class) - ->disableOriginalConstructor() - ->setMethods([ - 'createQueryBuilder', - ]) - ->getMock() - ; - - $expectedQueryBuilder = new QueryBuilder($entityManager); - $expectedQueryBuilder->from('any_table_name', 'qb'); - - $entityRepository - ->expects(static::once()) - ->method('createQueryBuilder') - ->willReturn($expectedQueryBuilder) - ; - - $queryBuilder = Repository::getEntityOrderedQueryBuilder($entityRepository, $property, $direction); - $selectDQLPart = $queryBuilder->getDQLPart('select'); - $whereDQLPart = $queryBuilder->getDQLPart('where'); - $orderDQLPart = $queryBuilder->getDQLPart('orderBy'); - - static::assertInstanceOf(QueryBuilder::class, $queryBuilder); - static::assertArraySubset(['qb'], $queryBuilder->getRootAliases()); - static::assertSame([], $selectDQLPart); - static::assertNull($whereDQLPart); - - if (empty($property)) { - static::assertSame([], $orderDQLPart); - } else { - /* @var OrderBy $orderBy */ - $orderBy = $orderDQLPart[0]; - - static::assertSame([$expectedOrderBy], $orderBy->getParts()); - } - } - - /** - * Provides arrays without extreme position used to replenish positions of them - * - * @return Generator - */ - public function provideArraysWithoutExtremePosition() - { - yield[ - [ - [], - [], - ], - true, - [ - [ - Repository::POSITION_KEY => 1, - ], - [ - Repository::POSITION_KEY => 2, - ], - ], - ]; - - yield[ - [ - [], - [], - ], - false, - [ - [ - Repository::POSITION_KEY => -1, - ], - [ - Repository::POSITION_KEY => -2, - ], - ], - ]; - - yield[ - [ - [ - 'lorem' => 'ipsum', - 'dolor', - 'sit' => 1, - ], - [ - 'abc' => 'def', - 'ghi' => null, - 'jkl' => 10, - ], - ], - true, - [ - [ - 'lorem' => 'ipsum', - 'dolor', - 'sit' => 1, - Repository::POSITION_KEY => 1, - ], - [ - 'abc' => 'def', - 'ghi' => null, - 'jkl' => 10, - Repository::POSITION_KEY => 2, - ], - ], - ]; - - yield[ - [ - [ - 'lorem' => 'ipsum', - 'dolor', - 'sit' => 1, - ], - [ - 'abc' => 'def', - 'ghi' => null, - 'jkl' => 10, - ], - ], - false, - [ - [ - 'lorem' => 'ipsum', - 'dolor', - 'sit' => 1, - Repository::POSITION_KEY => -1, - ], - [ - 'abc' => 'def', - 'ghi' => null, - 'jkl' => 10, - Repository::POSITION_KEY => -2, - ], - ], - ]; - } - - /** - * Provides arrays with extreme position used to replenish positions of them - * - * @return Generator - */ - public function provideArraysWithExtremePosition() - { - yield[ - [ - [ - Repository::POSITION_KEY => 1, - ], - [], - [], - ], - true, - [ - [ - Repository::POSITION_KEY => 1, - ], - [ - Repository::POSITION_KEY => 2, - ], - [ - Repository::POSITION_KEY => 3, - ], - ], - ]; - - yield[ - [ - [], - [], - [ - Repository::POSITION_KEY => 1, - ], - ], - true, - [ - [ - Repository::POSITION_KEY => 2, - ], - [ - Repository::POSITION_KEY => 3, - ], - [ - Repository::POSITION_KEY => 1, - ], - ], - ]; - - yield[ - [ - [ - Repository::POSITION_KEY => 1, - ], - [], - [], - ], - false, - [ - [ - Repository::POSITION_KEY => 1, - ], - [ - Repository::POSITION_KEY => 0, - ], - [ - Repository::POSITION_KEY => -1, - ], - ], - ]; - - yield[ - [ - [], - [], - [ - Repository::POSITION_KEY => 1, - ], - ], - false, - [ - [ - Repository::POSITION_KEY => 0, - ], - [ - Repository::POSITION_KEY => -1, - ], - [ - Repository::POSITION_KEY => 1, - ], - ], - ]; - } - - /** - * Provides objects without extreme position used to replenish positions of them - * - * @return Generator - */ - public function provideObjectsWithoutExtremePosition() - { - yield[ - [ - new Sortable(), - new Sortable(), - new Sortable(), - ], - true, - [ - new Sortable(1), - new Sortable(2), - new Sortable(3), - ], - ]; - - yield[ - [ - new Sortable(), - new Sortable(), - new Sortable(), - ], - false, - [ - new Sortable(-1), - new Sortable(-2), - new Sortable(-3), - ], - ]; - } - - /** - * Provides objects with extreme position used to replenish positions of them - * - * @return Generator - */ - public function provideObjectsWithExtremePosition() - { - yield[ - [ - new Sortable(1), - new Sortable(), - new Sortable(), - ], - true, - [ - new Sortable(1), - new Sortable(2), - new Sortable(3), - ], - ]; - - yield[ - [ - new Sortable(), - new Sortable(1), - new Sortable(), - ], - true, - [ - new Sortable(2), - new Sortable(1), - new Sortable(3), - ], - ]; - - yield[ - [ - new Sortable(1), - new Sortable(), - new Sortable(), - ], - false, - [ - new Sortable(1), - new Sortable(0), - new Sortable(-1), - ], - ]; - } - - /** - * Provides arrays without extreme position used to get extreme position - * - * @return Generator - */ - public function provideArraysWithoutExtremePositionToGetExtremePosition() - { - yield[ - [], - false, - null, - ]; - - yield[ - [], - true, - null, - ]; - - yield[ - [ - [ - 'lorem' => 'ipsum', - 'dolor', - 'sit' => 1, - ], - [ - 'abc' => 'def', - 'ghi' => null, - 'jkl' => 10, - ], - ], - true, - null, - ]; - - yield[ - [ - [ - 'lorem' => 'ipsum', - 'dolor', - 'sit' => 1, - ], - [ - 'abc' => 'def', - 'ghi' => null, - 'jkl' => 10, - ], - ], - false, - null, - ]; - } - - /** - * Provides arrays with extreme position used to get extreme position - * - * @return Generator - */ - public function provideArraysWithExtremePositionToGetExtremePosition() - { - yield[ - [ - [ - Repository::POSITION_KEY => 1, - ], - [], - [], - ], - true, - 1, - ]; - - yield[ - [ - [ - Repository::POSITION_KEY => 1, - ], - [], - [], - ], - false, - 1, - ]; - - yield[ - [ - [], - [], - [ - Repository::POSITION_KEY => 1, - ], - ], - true, - 1, - ]; - - yield[ - [ - [], - [], - [ - Repository::POSITION_KEY => 1, - ], - ], - false, - 1, - ]; - - yield[ - [ - [ - Repository::POSITION_KEY => 1, - ], - [], - [ - Repository::POSITION_KEY => 2, - ], - [], - ], - true, - 2, - ]; - - yield[ - [ - [ - Repository::POSITION_KEY => 1, - ], - [], - [ - Repository::POSITION_KEY => 2, - ], - [], - ], - false, - 1, - ]; - } - - /** - * Provides objects without extreme position used to get extreme position - * - * @return Generator - */ - public function provideObjectsWithoutExtremePositionToGetExtremePosition() - { - yield[ - [], - false, - null, - ]; - - yield[ - [], - true, - null, - ]; - - yield[ - [ - new Sortable(), - new Sortable(), - new Sortable(), - ], - true, - null, - ]; - - yield[ - [ - new Sortable(), - new Sortable(), - new Sortable(), - ], - false, - null, - ]; - } - - /** - * Provides objects with extreme position used to get extreme position - * - * @return Generator - */ - public function provideObjectsWithExtremePositionToGetExtremePosition() - { - yield[ - [ - new Sortable(1), - new Sortable(2), - new Sortable(3), - ], - true, - 3, - ]; - - yield[ - [ - new Sortable(1), - new Sortable(2), - new Sortable(3), - ], - false, - 1, - ]; - } - - /** - * Provide name of property, direction and expected ORDER BY clause used to get query builder - * - * @return Generator - */ - public function providePropertyAndDirectionToGetEntityOrderedQueryBuilder() - { - yield[ - null, - null, - '', - ]; - - yield[ - '', - '', - '', - ]; - - yield[ - 'first_name', - '', - 'qb.first_name ASC', - ]; - - yield[ - 'first_name', - 'asc', - 'qb.first_name asc', - ]; - - yield[ - 'first_name', - 'ASC', - 'qb.first_name ASC', - ]; - - yield[ - 'first_name', - 'desc', - 'qb.first_name desc', - ]; - - yield[ - 'first_name', - 'DESC', - 'qb.first_name DESC', - ]; + static::assertSame([], $items); } } diff --git a/tests/Utilities/UriTest.php b/tests/Utilities/UriTest.php index 20b1eaf..da660ff 100644 --- a/tests/Utilities/UriTest.php +++ b/tests/Utilities/UriTest.php @@ -17,12 +17,180 @@ use Meritoo\Common\Utilities\Uri; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\Uri */ class UriTest extends BaseTestCase { - public function testConstructor() + /** + * Provides data used to build secured url + * + * @return Generator + */ + public function provideDataForSecuredUrl() { - static::assertHasNoConstructor(Uri::class); + yield [ + '', + '', + '', + '', + ]; + + yield [ + '/', + '', + '', + 'http://lorem.com/', + ]; + + yield [ + 'contact', + '', + '', + 'http://lorem.com/contact', + ]; + + yield [ + 'contact', + 'john', + '', + 'http://lorem.com/contact', + ]; + + yield [ + 'contact', + '', + 'pass123', + 'http://lorem.com/contact', + ]; + + yield [ + 'contact', + 'john', + 'pass123', + 'http://john:pass123@lorem.com/contact', + ]; + } + + public function provideRootUrlAndUrlParts(): ?Generator + { + yield [ + '', + '', + ]; + + yield [ + '', + '', + '', + ]; + + yield [ + 'http://my.example', + 'http://my.example', + '', + ]; + + yield [ + 'http://my.example', + 'http://my.example', + '', + '', + ]; + + yield [ + 'http://my.example//test/12/test/', + 'http://my.example', + '', + 'test', + '12/test', + '', + ]; + + yield [ + 'http://my.example//test/12/test', + 'http://my.example', + '', + 'test/', + '12/test/', + ]; + + yield [ + 'http://my.example/test/12/test/', + 'http://my.example', + '/test/', + '/12/test', + '', + ]; + } + + /** + * Provides url to replenish protocol + * + * @return Generator + */ + public function provideUrlToReplenishProtocol() + { + yield [ + 'http://test', + 'test', + '', + ]; + + yield [ + 'ftp://lorem.ipsum', + 'lorem.ipsum', + 'ftp', + ]; + } + + /** + * Provides url used to verify if it's external, from another server / domain + * + * @return Generator + */ + public function provideUrlToVerifyIfIsExternal() + { + yield [ + '', + false, + ]; + + yield [ + '/', + false, + ]; + + yield [ + 'http://something.different/first-page', + true, + ]; + + yield [ + 'something.different/first-page', + true, + ]; + + yield [ + 'http://lorem.com', + false, + ]; + + yield [ + 'http://lorem.com/contact', + false, + ]; + + yield [ + 'lorem.com', + false, + ]; + + yield [ + 'lorem.com/contact', + false, + ]; } public function testAddProtocolToUrl() @@ -42,52 +210,20 @@ class UriTest extends BaseTestCase } /** - * @param mixed $url Empty value, e.g. "" - * @dataProvider provideEmptyValue - */ - public function testReplenishProtocolEmptyUrl($url) - { - self::assertEquals('', Uri::replenishProtocol($url)); - } - - /** - * @param string $expected Expected result - * @param string $url The url to check and replenish - * @param string $protocol (optional) The protocol which is replenished. If is empty, protocol of current request - * is used. + * @param string $expected + * @param string $rootUrl + * @param string ...$urlParts * - * @dataProvider provideUrlToReplenishProtocol + * @dataProvider provideRootUrlAndUrlParts */ - public function testReplenishProtocol($expected, $url, $protocol = '') + public function testBuildUrl(string $expected, string $rootUrl, string ...$urlParts): void { - self::assertSame($expected, Uri::replenishProtocol($url, $protocol)); + static::assertSame($expected, Uri::buildUrl($rootUrl, ...$urlParts)); } - public function testGetServerNameOrIpWithoutProtocol() + public function testConstructor() { - $_SERVER['HTTP_HOST'] = ''; - self::assertEquals('', Uri::getServerNameOrIp()); - - $host = 'lorem.com'; - $_SERVER['HTTP_HOST'] = $host; - - self::assertEquals($host, Uri::getServerNameOrIp()); - } - - public function testGetServerNameOrIpWithProtocol() - { - $_SERVER['HTTP_HOST'] = ''; - $_SERVER['SERVER_PROTOCOL'] = ''; - - self::assertEquals('', Uri::getServerNameOrIp(true)); - - $host = 'lorem.com'; - $protocol = 'HTTP/1.1'; - - $_SERVER['HTTP_HOST'] = $host; - $_SERVER['SERVER_PROTOCOL'] = $protocol; - - self::assertEquals(sprintf('http://%s', $host), Uri::getServerNameOrIp(true)); + static::assertHasNoConstructor(Uri::class); } public function testGetFullUriWithHost() @@ -145,6 +281,49 @@ class UriTest extends BaseTestCase self::assertEquals($refererUrl, Uri::getRefererUri()); } + /** + * @param string $url A path / url to some resource, e.g. page, image, css file + * @param string $user User name used to log in + * @param string $password User password used to log in + * @param string $expectedUrl Expected, secured url + * + * @dataProvider provideDataForSecuredUrl + */ + public function testGetSecuredUrl($url, $user, $password, $expectedUrl) + { + $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; + $_SERVER['HTTP_HOST'] = 'lorem.com'; + + self::assertEquals($expectedUrl, Uri::getSecuredUrl($url, $user, $password)); + } + + public function testGetServerNameOrIpWithProtocol() + { + $_SERVER['HTTP_HOST'] = ''; + $_SERVER['SERVER_PROTOCOL'] = ''; + + self::assertEquals('', Uri::getServerNameOrIp(true)); + + $host = 'lorem.com'; + $protocol = 'HTTP/1.1'; + + $_SERVER['HTTP_HOST'] = $host; + $_SERVER['SERVER_PROTOCOL'] = $protocol; + + self::assertEquals(sprintf('http://%s', $host), Uri::getServerNameOrIp(true)); + } + + public function testGetServerNameOrIpWithoutProtocol() + { + $_SERVER['HTTP_HOST'] = ''; + self::assertEquals('', Uri::getServerNameOrIp()); + + $host = 'lorem.com'; + $_SERVER['HTTP_HOST'] = $host; + + self::assertEquals($host, Uri::getServerNameOrIp()); + } + public function testGetUserAddressIp() { $_SERVER['REMOTE_ADDR'] = ''; @@ -156,58 +335,49 @@ class UriTest extends BaseTestCase self::assertEquals($userAddressIp, Uri::getUserAddressIp()); } + public function testGetUserOperatingSystemName() + { + $_SERVER['HTTP_USER_AGENT'] = ''; + self::assertEquals('', Uri::getUserOperatingSystemName()); + + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/600.2.5 (KHTML, like' + .' Gecko) Version/8.0.2 Safari/600.2.5'; + + self::assertEquals('Mac OS', Uri::getUserOperatingSystemName()); + } + public function testGetUserWebBrowserInfo() { $_SERVER['HTTP_USER_AGENT'] = ''; self::assertEquals('', Uri::getUserWebBrowserInfo()); $browserInfo = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/600.2.5 (KHTML, like Gecko)' - . ' Version/8.0.2 Safari/600.2.5'; + .' Version/8.0.2 Safari/600.2.5'; $_SERVER['HTTP_USER_AGENT'] = $browserInfo; self::assertEquals($browserInfo, Uri::getUserWebBrowserInfo()); } - public function testGetUserWebBrowserNameWithoutVersion() - { - $_SERVER['HTTP_USER_AGENT'] = ''; - self::assertEquals('', Uri::getUserWebBrowserName()); - - $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/600.2.5 (KHTML, like' - . ' Gecko) Version/8.0.2 Safari/600.2.5'; - - self::assertEquals('Apple Safari', Uri::getUserWebBrowserName()); - } - public function testGetUserWebBrowserNameWithVersion() { $_SERVER['HTTP_USER_AGENT'] = ''; self::assertEquals('', Uri::getUserWebBrowserName(true)); $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/600.2.5 (KHTML, like' - . ' Gecko) Version/8.0.2 Safari/600.2.5'; + .' Gecko) Version/8.0.2 Safari/600.2.5'; self::assertEquals('Apple Safari 600.2.5', Uri::getUserWebBrowserName(true)); } - public function testGetUserOperatingSystemName() + public function testGetUserWebBrowserNameWithoutVersion() { $_SERVER['HTTP_USER_AGENT'] = ''; - self::assertEquals('', Uri::getUserOperatingSystemName()); + self::assertEquals('', Uri::getUserWebBrowserName()); $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/600.2.5 (KHTML, like' - . ' Gecko) Version/8.0.2 Safari/600.2.5'; + .' Gecko) Version/8.0.2 Safari/600.2.5'; - self::assertEquals('Mac OS', Uri::getUserOperatingSystemName()); - } - - public function testIsServerLocalhost() - { - $_SERVER['HTTP_HOST'] = ''; - self::assertFalse(Uri::isServerLocalhost()); - - $_SERVER['HTTP_HOST'] = '127.0.0.1'; - self::assertTrue(Uri::isServerLocalhost()); + self::assertEquals('Apple Safari', Uri::getUserWebBrowserName()); } /** @@ -227,137 +397,43 @@ class UriTest extends BaseTestCase self::assertEquals($expected, Uri::isExternalUrl($url)); } - /** - * @param string $url A path / url to some resource, e.g. page, image, css file - * @param string $user User name used to log in - * @param string $password User password used to log in - * @param string $expectedUrl Expected, secured url - * - * @dataProvider provideDataForSecuredUrl - */ - public function testGetSecuredUrl($url, $user, $password, $expectedUrl) + public function testIsServerLocalhost() { + $_SERVER['HTTP_HOST'] = ''; + self::assertFalse(Uri::isServerLocalhost()); + + $_SERVER['HTTP_HOST'] = '127.0.0.1'; + self::assertTrue(Uri::isServerLocalhost()); + } + + /** + * @param string $expected Expected result + * @param string $url The url to check and replenish + * @param string $protocol (optional) The protocol which is replenished. If is empty, protocol of current request + * is used. + * + * @dataProvider provideUrlToReplenishProtocol + */ + public function testReplenishProtocol($expected, $url, $protocol = '') + { + /* + * Required to get protocol when it's not provided and to void test failure: + * + * Failed asserting that two strings are identical. + * Expected :'://test' + * Actual :'http://test' + */ $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; - $_SERVER['HTTP_HOST'] = 'lorem.com'; - self::assertEquals($expectedUrl, Uri::getSecuredUrl($url, $user, $password)); + self::assertSame($expected, Uri::replenishProtocol($url, $protocol)); } /** - * Provides url to replenish protocol - * - * @return Generator + * @param mixed $url Empty value, e.g. "" + * @dataProvider provideEmptyValue */ - public function provideUrlToReplenishProtocol() + public function testReplenishProtocolEmptyUrl($url) { - yield[ - '://test', - 'test', - '', - ]; - - yield[ - 'ftp://lorem.ipsum', - 'lorem.ipsum', - 'ftp', - ]; - } - - /** - * Provides url used to verify if it's external, from another server / domain - * - * @return Generator - */ - public function provideUrlToVerifyIfIsExternal() - { - yield[ - '', - false, - ]; - - yield[ - '/', - false, - ]; - - yield[ - 'http://something.different/first-page', - true, - ]; - - yield[ - 'something.different/first-page', - true, - ]; - - yield[ - 'http://lorem.com', - false, - ]; - - yield[ - 'http://lorem.com/contact', - false, - ]; - - yield[ - 'lorem.com', - false, - ]; - - yield[ - 'lorem.com/contact', - false, - ]; - } - - /** - * Provides data used to build secured url - * - * @return Generator - */ - public function provideDataForSecuredUrl() - { - yield[ - '', - '', - '', - '', - ]; - - yield[ - '/', - '', - '', - 'http://lorem.com/', - ]; - - yield[ - 'contact', - '', - '', - 'http://lorem.com/contact', - ]; - - yield[ - 'contact', - 'john', - '', - 'http://lorem.com/contact', - ]; - - yield[ - 'contact', - '', - 'pass123', - 'http://lorem.com/contact', - ]; - - yield[ - 'contact', - 'john', - 'pass123', - 'http://john:pass123@lorem.com/contact', - ]; + self::assertEquals('', Uri::replenishProtocol($url)); } } diff --git a/tests/Utilities/XmlTest.php b/tests/Utilities/XmlTest.php index 494aabf..1683bc1 100644 --- a/tests/Utilities/XmlTest.php +++ b/tests/Utilities/XmlTest.php @@ -17,6 +17,9 @@ use SimpleXMLElement; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\Utilities\Xml */ class XmlTest extends BaseTestCase { @@ -30,29 +33,25 @@ class XmlTest extends BaseTestCase public function testMergeNodes() { - /* - * An empty XMLs - */ + // An empty XMLs $element1 = new SimpleXMLElement(''); $element2 = new SimpleXMLElement(''); $merged = Xml::mergeNodes($element1, $element2); - self::assertEquals('', (string)$merged); + self::assertEquals('', (string) $merged); - /* - * XMLs with data - */ + // XMLs with data $element1 = new SimpleXMLElement($this->simpleXml); $element2 = new SimpleXMLElement($this->advancedXml); $merged = Xml::mergeNodes($element1, $element2); - self::assertEquals('John', (string)$merged->author[0]->first_name); + self::assertEquals('John', (string) $merged->author[0]->first_name); } /** * {@inheritdoc} */ - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -86,11 +85,10 @@ class XmlTest extends BaseTestCase /** * {@inheritdoc} */ - protected function tearDown() + protected function tearDown(): void { parent::tearDown(); - unset($this->simpleXml); - unset($this->advancedXml); + unset($this->simpleXml, $this->advancedXml); } } diff --git a/tests/ValueObject/AddressTest.php b/tests/ValueObject/AddressTest.php index 5cd1899..63981e7 100644 --- a/tests/ValueObject/AddressTest.php +++ b/tests/ValueObject/AddressTest.php @@ -17,6 +17,9 @@ use Meritoo\Common\ValueObject\Address; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\ValueObject\Address */ class AddressTest extends BaseTestCase { @@ -45,13 +48,6 @@ class AddressTest extends BaseTestCase ); } - public function testGetFlatNumber() - { - static::assertSame('200', $this->address->getFlatNumber()); - static::assertSame('', $this->addressWithoutFlat->getFlatNumber()); - static::assertSame('300', $this->addressWithoutStreet->getFlatNumber()); - } - public function testGetBuildingNumber() { static::assertSame('10', $this->address->getBuildingNumber()); @@ -59,11 +55,18 @@ class AddressTest extends BaseTestCase static::assertSame('1', $this->addressWithoutStreet->getBuildingNumber()); } - public function testGetStreet() + public function testGetCity() { - static::assertSame('4th Avenue', $this->address->getStreet()); - static::assertSame('Green Street', $this->addressWithoutFlat->getStreet()); - static::assertSame('', $this->addressWithoutStreet->getStreet()); + static::assertSame('New York', $this->address->getCity()); + static::assertSame('San Francisco', $this->addressWithoutFlat->getCity()); + static::assertSame('Saint Louis', $this->addressWithoutStreet->getCity()); + } + + public function testGetFlatNumber() + { + static::assertSame('200', $this->address->getFlatNumber()); + static::assertSame('', $this->addressWithoutFlat->getFlatNumber()); + static::assertSame('300', $this->addressWithoutStreet->getFlatNumber()); } public function testGetFullStreet() @@ -73,11 +76,11 @@ class AddressTest extends BaseTestCase static::assertSame('', $this->addressWithoutStreet->getFullStreet()); } - public function testGetCity() + public function testGetStreet() { - static::assertSame('New York', $this->address->getCity()); - static::assertSame('San Francisco', $this->addressWithoutFlat->getCity()); - static::assertSame('Saint Louis', $this->addressWithoutStreet->getCity()); + static::assertSame('4th Avenue', $this->address->getStreet()); + static::assertSame('Green Street', $this->addressWithoutFlat->getStreet()); + static::assertSame('', $this->addressWithoutStreet->getStreet()); } public function testGetZipCode() @@ -89,12 +92,12 @@ class AddressTest extends BaseTestCase public function testToString() { - static::assertSame('4th Avenue 10/200, 00123, New York', (string)$this->address); - static::assertSame('Green Street 22, 00456, San Francisco', (string)$this->addressWithoutFlat); - static::assertSame('00111, Saint Louis', (string)$this->addressWithoutStreet); + static::assertSame('4th Avenue 10/200, 00123, New York', (string) $this->address); + static::assertSame('Green Street 22, 00456, San Francisco', (string) $this->addressWithoutFlat); + static::assertSame('00111, Saint Louis', (string) $this->addressWithoutStreet); } - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/tests/ValueObject/BankAccountTest.php b/tests/ValueObject/BankAccountTest.php index ac38dbc..52277be 100644 --- a/tests/ValueObject/BankAccountTest.php +++ b/tests/ValueObject/BankAccountTest.php @@ -17,6 +17,9 @@ use Meritoo\Common\ValueObject\BankAccount; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\ValueObject\BankAccount */ class BankAccountTest extends BaseTestCase { @@ -54,14 +57,14 @@ class BankAccountTest extends BaseTestCase public function testToString() { - static::assertSame('', (string)$this->emptyBankAccount); - static::assertSame('Bank of America, 1234567890', (string)$this->bankAccount); + static::assertSame('', (string) $this->emptyBankAccount); + static::assertSame('Bank of America, 1234567890', (string) $this->bankAccount); } /** * {@inheritdoc} */ - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/tests/ValueObject/CompanyTest.php b/tests/ValueObject/CompanyTest.php index 383b012..dcd66c7 100644 --- a/tests/ValueObject/CompanyTest.php +++ b/tests/ValueObject/CompanyTest.php @@ -19,6 +19,9 @@ use Meritoo\Common\ValueObject\Company; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\ValueObject\Company */ class CompanyTest extends BaseTestCase { @@ -42,12 +45,6 @@ class CompanyTest extends BaseTestCase ); } - public function testGetName() - { - static::assertSame('Test 1', $this->company->getName()); - static::assertSame('Test 2', $this->companyWithoutBankAccount->getName()); - } - public function testGetAddress() { static::assertEquals( @@ -71,16 +68,22 @@ class CompanyTest extends BaseTestCase static::assertNull($this->companyWithoutBankAccount->getBankAccount()); } + public function testGetName() + { + static::assertSame('Test 1', $this->company->getName()); + static::assertSame('Test 2', $this->companyWithoutBankAccount->getName()); + } + public function testToString() { - static::assertSame('Test 1, 4th Avenue 10/200, 00123, New York, Bank 1, 12345', (string)$this->company); - static::assertSame('Test 2, Green Street 22, 00456, San Francisco', (string)$this->companyWithoutBankAccount); + static::assertSame('Test 1, 4th Avenue 10/200, 00123, New York, Bank 1, 12345', (string) $this->company); + static::assertSame('Test 2, Green Street 22, 00456, San Francisco', (string) $this->companyWithoutBankAccount); } /** * {@inheritdoc} */ - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/tests/ValueObject/HumanTest.php b/tests/ValueObject/HumanTest.php index 1a58e35..63e39d3 100644 --- a/tests/ValueObject/HumanTest.php +++ b/tests/ValueObject/HumanTest.php @@ -8,6 +8,7 @@ namespace Meritoo\Test\Common\ValueObject; +use DateTime; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; use Meritoo\Common\ValueObject\Human; @@ -17,9 +18,39 @@ use Meritoo\Common\ValueObject\Human; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\ValueObject\Human */ class HumanTest extends BaseTestCase { + public function provideHuman() + { + yield [ + 'Without any data (an empty human)', + new Human('', ''), + '', + ]; + + yield [ + 'With first and last name only', + new Human('John', 'Scott'), + 'John Scott', + ]; + + yield [ + 'With first name, last name and email', + new Human('John', 'Scott', 'john@scott.com'), + 'John Scott ', + ]; + + yield [ + 'With whole/complete data', + new Human('John', 'Scott', 'john@scott.com', new DateTime('2001-01-01')), + 'John Scott ', + ]; + } + public function testConstructor() { static::assertConstructorVisibilityAndArguments( @@ -30,52 +61,13 @@ class HumanTest extends BaseTestCase ); } - /** - * @param string $description Description of test - * @param Human $human Human to verify - * @param string $expected Expected string - * - * @dataProvider provideHuman - */ - public function testToString($description, Human $human, $expected) - { - static::assertSame($expected, (string)$human, $description); - } - - public function testGetFirstName() - { - $empty = new Human('', ''); - static::assertSame('', $empty->getFirstName()); - - $human = new Human('John', 'Scott'); - static::assertSame('John', $human->getFirstName()); - } - - public function testGetLastName() - { - $empty = new Human('', ''); - static::assertSame('', $empty->getLastName()); - - $human = new Human('John', 'Scott'); - static::assertSame('Scott', $human->getLastName()); - } - public function testGetBirthDate() { $empty = new Human('', ''); static::assertNull($empty->getBirthDate()); - $human = new Human('John', 'Scott', '', new \DateTime('2001-01-01')); - static::assertEquals(new \DateTime('2001-01-01'), $human->getBirthDate()); - } - - public function testGetFullName() - { - $empty = new Human('', ''); - static::assertSame('', $empty->getFullName()); - - $human = new Human('John', 'Scott', '', new \DateTime('2001-01-01')); - static::assertSame('John Scott', $human->getFullName()); + $human = new Human('John', 'Scott', '', new DateTime('2001-01-01')); + static::assertEquals(new DateTime('2001-01-01'), $human->getBirthDate()); } public function testGetEmail() @@ -87,30 +79,42 @@ class HumanTest extends BaseTestCase static::assertSame('john@scott.com', $human->getEmail()); } - public function provideHuman() + public function testGetFirstName() { - yield[ - 'Without any data (an empty human)', - new Human('', ''), - '', - ]; + $empty = new Human('', ''); + static::assertSame('', $empty->getFirstName()); - yield[ - 'With first and last name only', - new Human('John', 'Scott'), - 'John Scott', - ]; + $human = new Human('John', 'Scott'); + static::assertSame('John', $human->getFirstName()); + } - yield[ - 'With first name, last name and email', - new Human('John', 'Scott', 'john@scott.com'), - 'John Scott ', - ]; + public function testGetFullName() + { + $empty = new Human('', ''); + static::assertSame('', $empty->getFullName()); - yield[ - 'With whole/complete data', - new Human('John', 'Scott', 'john@scott.com', new \DateTime('2001-01-01')), - 'John Scott ', - ]; + $human = new Human('John', 'Scott', '', new DateTime('2001-01-01')); + static::assertSame('John Scott', $human->getFullName()); + } + + public function testGetLastName() + { + $empty = new Human('', ''); + static::assertSame('', $empty->getLastName()); + + $human = new Human('John', 'Scott'); + static::assertSame('Scott', $human->getLastName()); + } + + /** + * @param string $description Description of test + * @param Human $human Human to verify + * @param string $expected Expected string + * + * @dataProvider provideHuman + */ + public function testToString($description, Human $human, $expected) + { + static::assertSame($expected, (string) $human, $description); } } diff --git a/tests/ValueObject/SizeTest.php b/tests/ValueObject/SizeTest.php index b3dbbbd..37044f2 100644 --- a/tests/ValueObject/SizeTest.php +++ b/tests/ValueObject/SizeTest.php @@ -8,6 +8,7 @@ namespace Meritoo\Test\Common\ValueObject; +use Generator; use Meritoo\Common\Exception\ValueObject\InvalidSizeDimensionsException; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; @@ -18,9 +19,920 @@ use Meritoo\Common\ValueObject\Size; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\ValueObject\Size */ class SizeTest extends BaseTestCase { + /** + * Provides invalid size (as an array) + * + * @return Generator + */ + public function provideInvalidSizeAsArray() + { + yield [ + [ + 10, + -1, + ], + ]; + + yield [ + [ + -1, + 10, + ], + ]; + + yield [ + [ + -1, + -1, + ], + ]; + } + + public function provideSizeForConvertingToString() + { + yield [ + 'Created using an empty array', + Size::fromArray([]), + '', + ]; + + yield [ + 'Created using an empty string', + Size::fromString(''), + '', + ]; + + yield [ + 'Created using an array with integers', + Size::fromArray([ + 200, + 100, + ]), + '200 x 100', + ]; + + yield [ + 'Created using an array with strings', + Size::fromArray([ + '200', + '100', + ]), + '200 x 100', + ]; + + yield [ + 'Created using simple string', + Size::fromString('200x100', '', 'x'), + '200x100', + ]; + + yield [ + 'Created using string with too much spaces everywhere', + Size::fromString(' 200 x 100 '), + '200 x 100', + ]; + } + + public function provideSizeForFromArray() + { + yield [ + 'An empty array', + [], + '', + null, + ]; + + yield [ + 'One number only', + [ + 200, + ], + '', + null, + ]; + + yield [ + 'One number only as string', + [ + '200', + ], + '', + null, + ]; + + yield [ + '0 as dimensions', + [ + 0, + 0, + ], + 'px', + Size::fromString('0 x 0'), + ]; + + yield [ + 'Simple, valid size', + [ + 200, + 100, + ], + 'px', + Size::fromString('200 x 100'), + ]; + + yield [ + 'Simple, valid size (using strings)', + [ + '200', + '100', + ], + 'mm', + Size::fromString('200 x 100', 'mm'), + ]; + } + + public function provideSizeForFromString() + { + yield [ + 'One number only', + 200, + '', + ' x ', + null, + ]; + + yield [ + 'One number only as string', + '200', + '', + ' x ', + null, + ]; + + yield [ + 'The " " as invalid separator', + '200 100', + '', + ' x ', + null, + ]; + + yield [ + 'The "|" as separator (invalid separator)', + '200 | 100', + '', + ' x ', + null, + ]; + + yield [ + 'The "|" as invalid separator and no spaces around separator', + '200|100', + '', + ' x ', + null, + ]; + + yield [ + 'The "X" as invalid separator', + '200 X 100', + '', + ' x ', + null, + ]; + + yield [ + 'Simple, valid size', + '200 x 100', + 'px', + ' x ', + Size::fromArray([ + 200, + 100, + ]), + ]; + + yield [ + 'Simple, valid size using custom separator', + '200 X 100', + 'px', + ' X ', + Size::fromArray([ + 200, + 100, + ])->setSeparator(' X '), + ]; + + yield [ + 'Too much spaces at the right of separator', + '200 x 100', + 'px', + ' x ', + Size::fromArray([ + 200, + 100, + ]), + ]; + + yield [ + 'Too much spaces at the left of separator', + '200 x 100', + 'px', + ' x ', + Size::fromArray([ + 200, + 100, + ]), + ]; + + yield [ + 'Too much spaces around separator', + '200 x 100', + 'px', + ' x ', + Size::fromArray([ + 200, + 100, + ]), + ]; + + yield [ + 'Too much spaces before width (1st)', + ' 200 x 100', + 'px', + ' x ', + Size::fromArray([ + 200, + 100, + ]), + ]; + + yield [ + 'Too much spaces before width (2nd) and custom separator', + ' 200 X 100', + 'px', + ' X ', + Size::fromArray([ + 200, + 100, + ])->setSeparator(' X '), + ]; + + yield [ + 'Too much spaces after height (1st)', + '200 x 100 ', + 'px', + ' x ', + Size::fromArray([ + 200, + 100, + ]), + ]; + + yield [ + 'Too much spaces after height (2nd) and custom separator', + '200 X 100 ', + 'px', + ' X ', + Size::fromArray([ + 200, + 100, + ])->setSeparator(' X '), + ]; + + yield [ + 'Too much spaces before width and after height (1st)', + ' 200 x 100 ', + 'km', + ' x ', + Size::fromArray( + [ + 200, + 100, + ], + 'km' + ), + ]; + + yield [ + 'Too much spaces before width and after height (2nd) and custom separator', + ' 200 X 100 ', + 'px', + ' X ', + Size::fromArray([ + 200, + 100, + ])->setSeparator(' X '), + ]; + + yield [ + 'Too much spaces everywhere (1st)', + ' 200 x 100 ', + 'px', + ' x ', + Size::fromArray([ + 200, + 100, + ]), + ]; + + yield [ + 'Too much spaces everywhere (2nd) and custom separator', + ' 200 X 100 ', + 'px', + ' X ', + Size::fromArray([ + 200, + 100, + ])->setSeparator(' X '), + ]; + + yield [ + 'Too much spaces everywhere (3rd)', + ' 200 x 100 ', + 'px', + ' x ', + Size::fromArray([ + 200, + 100, + ]), + ]; + + yield [ + 'Too much spaces everywhere (4th) and custom separator', + ' 200 : 100 ', + 'px', + ' : ', + Size::fromArray([ + 200, + 100, + ])->setSeparator(' : '), + ]; + + yield [ + 'Too much spaces everywhere (5th)', + ' 200 x 100 ', + 'mm', + ' x ', + Size::fromArray( + [ + 200, + 100, + ], + 'mm' + ), + ]; + } + + public function provideSizeForToArray() + { + yield [ + 'Created using an array with integers', + Size::fromArray([ + 200, + 100, + ]), + false, + [ + 200, + 100, + ], + ]; + + yield [ + 'Created using an array with integers (converting with unit)', + Size::fromArray([ + 200, + 100, + ]), + true, + [ + '200 px', + '100 px', + ], + ]; + + yield [ + 'Created using an array with strings', + Size::fromArray([ + '200', + '100', + ]), + false, + [ + 200, + 100, + ], + ]; + + yield [ + 'Created using an array with strings (converting with unit)', + Size::fromArray([ + '200', + '100', + ]), + true, + [ + '200 px', + '100 px', + ], + ]; + + yield [ + 'Created using simple string', + Size::fromString('200 x 100'), + false, + [ + 200, + 100, + ], + ]; + + yield [ + 'Created using simple string and custom separator', + Size::fromString('200 X 100', '', ' X '), + false, + [ + 200, + 100, + ], + ]; + + yield [ + 'Created using simple string, custom separator and custom unit (with unit)', + Size::fromString('200 : 100', 'mm', ' : '), + true, + [ + '200 mm', + '100 mm', + ], + ]; + + yield [ + 'Created using simple string (converting with unit)', + Size::fromString('200 x 100'), + true, + [ + '200 px', + '100 px', + ], + ]; + + yield [ + 'Created using string with too much spaces everywhere', + Size::fromString(' 200 x 100 '), + false, + [ + 200, + 100, + ], + ]; + + yield [ + 'Created using string with too much spaces everywhere (converting with unit)', + Size::fromString(' 200 x 100 '), + true, + [ + '200 px', + '100 px', + ], + ]; + } + + public function provideSizeForToString() + { + yield [ + 'With unknown dimensions', + Size::fromArray([ + null, + null, + ]), + false, + '0 x 0', + ]; + + yield [ + 'With unknown dimensions (converting with unit)', + Size::fromArray([ + null, + null, + ]), + true, + '0 px x 0 px', + ]; + + yield [ + 'Created using an array with integers', + Size::fromArray([ + 200, + 100, + ]), + false, + '200 x 100', + ]; + + yield [ + 'Created using an array with integers (converting with unit)', + Size::fromArray([ + 200, + 100, + ]), + true, + '200 px x 100 px', + ]; + + yield [ + 'Created using an array with strings', + Size::fromArray([ + '200', + '100', + ]), + false, + '200 x 100', + ]; + + yield [ + 'Created using an array with strings (converting with unit)', + Size::fromArray([ + '200', + '100', + ]), + true, + '200 px x 100 px', + ]; + + yield [ + 'Created using simple string', + Size::fromString('200 x 100'), + false, + '200 x 100', + ]; + + yield [ + 'Created using simple string', + Size::fromString('200 x 100'), + false, + '200 x 100', + ]; + + yield [ + 'Created using simple string and custom separator', + Size::fromString('200 X 100', '', ' X '), + false, + '200 X 100', + ]; + + yield [ + 'Created using simple string, custom separator and custom unit (with unit)', + Size::fromString('200 : 100', 'mm', ' : '), + true, + '200 mm : 100 mm', + ]; + + yield [ + 'Created using simple string (converting with unit)', + Size::fromString('200 x 100'), + true, + '200 px x 100 px', + ]; + + yield [ + 'Created using string with too much spaces everywhere', + Size::fromString(' 200 x 100 '), + false, + '200 x 100', + ]; + + yield [ + 'Created using string with too much spaces everywhere (converting with unit)', + Size::fromString(' 200 x 100 '), + true, + '200 px x 100 px', + ]; + } + + public function provideSizeToGetHeight() + { + yield [ + 'Created using an array with integers', + Size::fromArray([ + 200, + 100, + ]), + false, + 100, + ]; + + yield [ + 'Created using an array with integers (with unit)', + Size::fromArray([ + 200, + 100, + ]), + true, + '100 px', + ]; + + yield [ + 'Created using an array with strings', + Size::fromArray([ + '200', + '100', + ]), + false, + 100, + ]; + + yield [ + 'Created using an array with strings (with unit)', + Size::fromArray([ + '200', + '100', + ]), + true, + '100 px', + ]; + + yield [ + 'Created using simple string', + Size::fromString('200 x 100'), + false, + 100, + ]; + + yield [ + 'Created using simple string (with unit)', + Size::fromString('200 x 100'), + true, + '100 px', + ]; + + yield [ + 'Created using simple string and custom separator', + Size::fromString('200 X 100', '', ' X '), + false, + 100, + ]; + + yield [ + 'Created using simple string, custom separator and custom unit (with unit)', + Size::fromString('200 : 100', 'mm', ' : '), + true, + '100 mm', + ]; + + yield [ + 'Created using string with too much spaces everywhere', + Size::fromString(' 200 x 100 '), + false, + 100, + ]; + + yield [ + 'Created using string with too much spaces everywhere (with unit)', + Size::fromString(' 200 x 100 '), + true, + '100 px', + ]; + } + + public function provideSizeToGetWidth() + { + yield [ + 'Created using an array with integers', + Size::fromArray([ + 200, + 100, + ]), + false, + 200, + ]; + + yield [ + 'Created using an array with integers (with unit)', + Size::fromArray([ + 200, + 100, + ]), + true, + '200 px', + ]; + + yield [ + 'Created using an array with strings', + Size::fromArray([ + '200', + '100', + ]), + false, + 200, + ]; + + yield [ + 'Created using an array with strings (with unit)', + Size::fromArray([ + '200', + '100', + ]), + true, + '200 px', + ]; + + yield [ + 'Created using simple string', + Size::fromString('200 x 100'), + false, + 200, + ]; + + yield [ + 'Created using simple string (with unit)', + Size::fromString('200 x 100'), + true, + '200 px', + ]; + + yield [ + 'Created using simple string and custom separator', + Size::fromString('200 X 100', '', ' X '), + false, + 200, + ]; + + yield [ + 'Created using simple string, custom separator and custom unit (with unit)', + Size::fromString('200 : 100', 'mm', ' : '), + true, + '200 mm', + ]; + + yield [ + 'Created using string with too much spaces everywhere', + Size::fromString(' 200 x 100 '), + false, + 200, + ]; + + yield [ + 'Created using string with too much spaces everywhere (with unit)', + Size::fromString(' 200 x 100 '), + true, + '200 px', + ]; + } + + public function provideSizeToSetHeight() + { + yield [ + 'Null as height', + Size::fromArray([ + 200, + 100, + ]), + null, + 0, + ]; + + yield [ + 'An empty string', + Size::fromArray([ + 200, + 100, + ]), + '', + 0, + ]; + + yield [ + 'Negative value', + Size::fromArray([ + 200, + 100, + ]), + -1, + -1, + ]; + + yield [ + 'Negative value as string', + Size::fromArray([ + 200, + 100, + ]), + '-1', + -1, + ]; + + yield [ + '0 as height', + Size::fromArray([ + 200, + 100, + ]), + 0, + 0, + ]; + + yield [ + 'Positive value', + Size::fromArray([ + 200, + 100, + ]), + 300, + 300, + ]; + + yield [ + 'Positive value as string', + Size::fromArray([ + 200, + 100, + ]), + '300', + 300, + ]; + } + + public function provideSizeToSetWidth() + { + yield [ + 'Null as width', + Size::fromArray([ + 200, + 100, + ]), + null, + 0, + ]; + + yield [ + 'An empty string', + Size::fromArray([ + 200, + 100, + ]), + '', + 0, + ]; + + yield [ + 'Negative value', + Size::fromArray([ + 200, + 100, + ]), + -1, + -1, + ]; + + yield [ + 'Negative value as string', + Size::fromArray([ + 200, + 100, + ]), + '-1', + -1, + ]; + + yield [ + '0 as width', + Size::fromArray([ + 200, + 100, + ]), + 0, + 0, + ]; + + yield [ + 'Positive value', + Size::fromArray([ + 200, + 100, + ]), + 300, + 300, + ]; + + yield [ + 'Positive value as string', + Size::fromArray([ + 200, + 100, + ]), + '300', + 300, + ]; + } + public function testConstructor() { static::assertConstructorVisibilityAndArguments( @@ -30,111 +942,6 @@ class SizeTest extends BaseTestCase ); } - /** - * @param string $description Description of test - * @param Size|null $size Size to convert - * @param string $expected Expected result - * - * @dataProvider provideSizeForConvertingToString - */ - public function test__toString($description, $size, $expected) - { - static::assertEquals($expected, (string)$size, $description); - } - - public function testSetSeparator() - { - $size = Size::fromArray([ - 200, - 100, - ]); - - static::assertInstanceOf(Size::class, $size->setSeparator(' / ')); - static::assertSame('200 / 100', $size->toString()); - } - - /** - * @param string $description Description of test - * @param Size $size Size to get width - * @param bool $withUnit If is set to true, width is returned with unit ("px"). Otherwise - without. - * @param string|int $expected Expected width - * - * @dataProvider provideSizeToGetWidth - */ - public function testGetWidth($description, Size $size, $withUnit, $expected) - { - static::assertSame($expected, $size->getWidth($withUnit), $description); - } - - /** - * @param string $description Description of test - * @param Size $size Size to set width - * @param int|string $width The width - * @param string|int $expected Expected width - * - * @dataProvider provideSizeToSetWidth - */ - public function testSetWidth($description, Size $size, $width, $expected) - { - $result = $size->setWidth($width); - - static::assertInstanceOf(Size::class, $result, $description); - static::assertSame($expected, $size->getWidth(), $description); - } - - /** - * @param string $description Description of test - * @param Size $size Size to get width - * @param bool $withUnit If is set to true, width is returned with unit ("px"). Otherwise - without. - * @param string|int $expected Expected width - * - * @dataProvider provideSizeToGetHeight - */ - public function testGetHeight($description, Size $size, $withUnit, $expected) - { - static::assertSame($expected, $size->getHeight($withUnit), $description); - } - - /** - * @param string $description Description of test - * @param Size $size Size to set height - * @param int|string $height The height - * @param string|int $expected Expected height - * - * @dataProvider provideSizeToSetHeight - */ - public function testSetHeight($description, Size $size, $height, $expected) - { - $result = $size->setHeight($height); - - static::assertInstanceOf(Size::class, $result, $description); - static::assertSame($expected, $size->getHeight(), $description); - } - - /** - * @param string $description Description of test - * @param Size $size Size to convert - * @param bool $withUnit If is set to true, width and height are returned with unit ("px"). Otherwise - - * without. - * @param string $expected Expected result - * - * @dataProvider provideSizeForToString - */ - public function testToString($description, Size $size, $withUnit, $expected) - { - static::assertSame($expected, $size->toString($withUnit), $description); - } - - /** - * @param array $size Invalid size - * @dataProvider provideInvalidSizeAsArray - */ - public function testFromArrayUsingInvalidSizeAsArray(array $size) - { - $this->setExpectedException(InvalidSizeDimensionsException::class); - Size::fromArray($size); - } - /** * @param string $description Description of test * @param array $size The size represented as array @@ -149,17 +956,13 @@ class SizeTest extends BaseTestCase } /** - * @param string $description Description of test - * @param Size $size Size to convert - * @param bool $withUnit If is set to true, width and height are returned with unit ("px"). Otherwise - - * without. - * @param array $expected Expected result - * - * @dataProvider provideSizeForToArray + * @param array $size Invalid size + * @dataProvider provideInvalidSizeAsArray */ - public function testToArray($description, Size $size, $withUnit, array $expected) + public function testFromArrayUsingInvalidSizeAsArray(array $size) { - static::assertSame($expected, $size->toArray($withUnit), $description); + $this->expectException(InvalidSizeDimensionsException::class); + Size::fromArray($size); } /** @@ -167,7 +970,7 @@ class SizeTest extends BaseTestCase * @param string $size The size represented as string (width and height separated by "x") * @param string $unit Unit used when width or height should be returned with unit * @param string $separator Separator used to split width and height - * @param Size|null $expected Expected result + * @param null|Size $expected Expected result * * @dataProvider provideSizeForFromString */ @@ -185,911 +988,112 @@ class SizeTest extends BaseTestCase static::assertNull(Size::fromString($emptySize)); } - public function provideSizeForConvertingToString() + /** + * @param string $description Description of test + * @param Size $size Size to get width + * @param bool $withUnit If is set to true, width is returned with unit ("px"). Otherwise - without. + * @param int|string $expected Expected width + * + * @dataProvider provideSizeToGetHeight + */ + public function testGetHeight($description, Size $size, $withUnit, $expected) { - yield[ - 'Created using an empty array', - Size::fromArray([]), - '', - ]; - - yield[ - 'Created using an empty string', - Size::fromString(''), - '', - ]; - - yield[ - 'Created using an array with integers', - Size::fromArray([ - 200, - 100, - ]), - '200 x 100', - ]; - - yield[ - 'Created using an array with strings', - Size::fromArray([ - '200', - '100', - ]), - '200 x 100', - ]; - - yield[ - 'Created using simple string', - Size::fromString('200x100', '', 'x'), - '200x100', - ]; - - yield[ - 'Created using string with too much spaces everywhere', - Size::fromString(' 200 x 100 '), - '200 x 100', - ]; + static::assertSame($expected, $size->getHeight($withUnit), $description); } /** - * Provides invalid size (as an array) + * @param string $description Description of test + * @param Size $size Size to get width + * @param bool $withUnit If is set to true, width is returned with unit ("px"). Otherwise - without. + * @param int|string $expected Expected width * - * @return \Generator + * @dataProvider provideSizeToGetWidth */ - public function provideInvalidSizeAsArray() + public function testGetWidth($description, Size $size, $withUnit, $expected) { - yield[ - [ - 10, - -1, - ], - ]; - - yield[ - [ - -1, - 10, - ], - ]; - - yield[ - [ - -1, - -1, - ], - ]; + static::assertSame($expected, $size->getWidth($withUnit), $description); } - public function provideSizeToGetWidth() + /** + * @param string $description Description of test + * @param Size $size Size to set height + * @param int|string $height The height + * @param int|string $expected Expected height + * + * @dataProvider provideSizeToSetHeight + */ + public function testSetHeight($description, Size $size, $height, $expected) { - yield[ - 'Created using an array with integers', - Size::fromArray([ - 200, - 100, - ]), - false, - 200, - ]; + $result = $size->setHeight($height); - yield[ - 'Created using an array with integers (with unit)', - Size::fromArray([ - 200, - 100, - ]), - true, - '200 px', - ]; - - yield[ - 'Created using an array with strings', - Size::fromArray([ - '200', - '100', - ]), - false, - 200, - ]; - - yield[ - 'Created using an array with strings (with unit)', - Size::fromArray([ - '200', - '100', - ]), - true, - '200 px', - ]; - - yield[ - 'Created using simple string', - Size::fromString('200 x 100'), - false, - 200, - ]; - - yield[ - 'Created using simple string (with unit)', - Size::fromString('200 x 100'), - true, - '200 px', - ]; - - yield[ - 'Created using simple string and custom separator', - Size::fromString('200 X 100', '', ' X '), - false, - 200, - ]; - - yield[ - 'Created using simple string, custom separator and custom unit (with unit)', - Size::fromString('200 : 100', 'mm', ' : '), - true, - '200 mm', - ]; - - yield[ - 'Created using string with too much spaces everywhere', - Size::fromString(' 200 x 100 '), - false, - 200, - ]; - - yield[ - 'Created using string with too much spaces everywhere (with unit)', - Size::fromString(' 200 x 100 '), - true, - '200 px', - ]; + static::assertInstanceOf(Size::class, $result, $description); + static::assertSame($expected, $size->getHeight(), $description); } - public function provideSizeToGetHeight() + public function testSetSeparator() { - yield[ - 'Created using an array with integers', - Size::fromArray([ - 200, - 100, - ]), - false, + $size = Size::fromArray([ + 200, 100, - ]; + ]); - yield[ - 'Created using an array with integers (with unit)', - Size::fromArray([ - 200, - 100, - ]), - true, - '100 px', - ]; - - yield[ - 'Created using an array with strings', - Size::fromArray([ - '200', - '100', - ]), - false, - 100, - ]; - - yield[ - 'Created using an array with strings (with unit)', - Size::fromArray([ - '200', - '100', - ]), - true, - '100 px', - ]; - - yield[ - 'Created using simple string', - Size::fromString('200 x 100'), - false, - 100, - ]; - - yield[ - 'Created using simple string (with unit)', - Size::fromString('200 x 100'), - true, - '100 px', - ]; - - yield[ - 'Created using simple string and custom separator', - Size::fromString('200 X 100', '', ' X '), - false, - 100, - ]; - - yield[ - 'Created using simple string, custom separator and custom unit (with unit)', - Size::fromString('200 : 100', 'mm', ' : '), - true, - '100 mm', - ]; - - yield[ - 'Created using string with too much spaces everywhere', - Size::fromString(' 200 x 100 '), - false, - 100, - ]; - - yield[ - 'Created using string with too much spaces everywhere (with unit)', - Size::fromString(' 200 x 100 '), - true, - '100 px', - ]; + static::assertInstanceOf(Size::class, $size->setSeparator(' / ')); + static::assertSame('200 / 100', $size->toString()); } - public function provideSizeToSetWidth() + /** + * @param string $description Description of test + * @param Size $size Size to set width + * @param int|string $width The width + * @param int|string $expected Expected width + * + * @dataProvider provideSizeToSetWidth + */ + public function testSetWidth($description, Size $size, $width, $expected) { - yield[ - 'Null as width', - Size::fromArray([ - 200, - 100, - ]), - null, - 0, - ]; + $result = $size->setWidth($width); - yield[ - 'An empty string', - Size::fromArray([ - 200, - 100, - ]), - '', - 0, - ]; - - yield[ - 'Negative value', - Size::fromArray([ - 200, - 100, - ]), - -1, - -1, - ]; - - yield[ - 'Negative value as string', - Size::fromArray([ - 200, - 100, - ]), - '-1', - -1, - ]; - - yield[ - '0 as width', - Size::fromArray([ - 200, - 100, - ]), - 0, - 0, - ]; - - yield[ - 'Positive value', - Size::fromArray([ - 200, - 100, - ]), - 300, - 300, - ]; - - yield[ - 'Positive value as string', - Size::fromArray([ - 200, - 100, - ]), - '300', - 300, - ]; + static::assertInstanceOf(Size::class, $result, $description); + static::assertSame($expected, $size->getWidth(), $description); } - public function provideSizeToSetHeight() + /** + * @param string $description Description of test + * @param Size $size Size to convert + * @param bool $withUnit If is set to true, width and height are returned with unit ("px"). Otherwise - + * without. + * @param array $expected Expected result + * + * @dataProvider provideSizeForToArray + */ + public function testToArray($description, Size $size, $withUnit, array $expected) { - yield[ - 'Null as height', - Size::fromArray([ - 200, - 100, - ]), - null, - 0, - ]; - - yield[ - 'An empty string', - Size::fromArray([ - 200, - 100, - ]), - '', - 0, - ]; - - yield[ - 'Negative value', - Size::fromArray([ - 200, - 100, - ]), - -1, - -1, - ]; - - yield[ - 'Negative value as string', - Size::fromArray([ - 200, - 100, - ]), - '-1', - -1, - ]; - - yield[ - '0 as height', - Size::fromArray([ - 200, - 100, - ]), - 0, - 0, - ]; - - yield[ - 'Positive value', - Size::fromArray([ - 200, - 100, - ]), - 300, - 300, - ]; - - yield[ - 'Positive value as string', - Size::fromArray([ - 200, - 100, - ]), - '300', - 300, - ]; + static::assertSame($expected, $size->toArray($withUnit), $description); } - public function provideSizeForToString() + /** + * @param string $description Description of test + * @param Size $size Size to convert + * @param bool $withUnit If is set to true, width and height are returned with unit ("px"). Otherwise - + * without. + * @param string $expected Expected result + * + * @dataProvider provideSizeForToString + */ + public function testToString($description, Size $size, $withUnit, $expected) { - yield[ - 'With unknown dimensions', - Size::fromArray([ - null, - null, - ]), - false, - '0 x 0', - ]; - - yield[ - 'With unknown dimensions (converting with unit)', - Size::fromArray([ - null, - null, - ]), - true, - '0 px x 0 px', - ]; - - yield[ - 'Created using an array with integers', - Size::fromArray([ - 200, - 100, - ]), - false, - '200 x 100', - ]; - - yield[ - 'Created using an array with integers (converting with unit)', - Size::fromArray([ - 200, - 100, - ]), - true, - '200 px x 100 px', - ]; - - yield[ - 'Created using an array with strings', - Size::fromArray([ - '200', - '100', - ]), - false, - '200 x 100', - ]; - - yield[ - 'Created using an array with strings (converting with unit)', - Size::fromArray([ - '200', - '100', - ]), - true, - '200 px x 100 px', - ]; - - yield[ - 'Created using simple string', - Size::fromString('200 x 100'), - false, - '200 x 100', - ]; - - yield[ - 'Created using simple string', - Size::fromString('200 x 100'), - false, - '200 x 100', - ]; - - yield[ - 'Created using simple string and custom separator', - Size::fromString('200 X 100', '', ' X '), - false, - '200 X 100', - ]; - - yield[ - 'Created using simple string, custom separator and custom unit (with unit)', - Size::fromString('200 : 100', 'mm', ' : '), - true, - '200 mm : 100 mm', - ]; - - yield[ - 'Created using simple string (converting with unit)', - Size::fromString('200 x 100'), - true, - '200 px x 100 px', - ]; - - yield[ - 'Created using string with too much spaces everywhere', - Size::fromString(' 200 x 100 '), - false, - '200 x 100', - ]; - - yield[ - 'Created using string with too much spaces everywhere (converting with unit)', - Size::fromString(' 200 x 100 '), - true, - '200 px x 100 px', - ]; + static::assertSame($expected, $size->toString($withUnit), $description); } - public function provideSizeForToArray() + /** + * @param string $description Description of test + * @param null|Size $size Size to convert + * @param string $expected Expected result + * + * @dataProvider provideSizeForConvertingToString + */ + public function testToStringConverting($description, $size, $expected) { - yield[ - 'Created using an array with integers', - Size::fromArray([ - 200, - 100, - ]), - false, - [ - 200, - 100, - ], - ]; - - yield[ - 'Created using an array with integers (converting with unit)', - Size::fromArray([ - 200, - 100, - ]), - true, - [ - '200 px', - '100 px', - ], - ]; - - yield[ - 'Created using an array with strings', - Size::fromArray([ - '200', - '100', - ]), - false, - [ - 200, - 100, - ], - ]; - - yield[ - 'Created using an array with strings (converting with unit)', - Size::fromArray([ - '200', - '100', - ]), - true, - [ - '200 px', - '100 px', - ], - ]; - - yield[ - 'Created using simple string', - Size::fromString('200 x 100'), - false, - [ - 200, - 100, - ], - ]; - - yield[ - 'Created using simple string and custom separator', - Size::fromString('200 X 100', '', ' X '), - false, - [ - 200, - 100, - ], - ]; - - yield[ - 'Created using simple string, custom separator and custom unit (with unit)', - Size::fromString('200 : 100', 'mm', ' : '), - true, - [ - '200 mm', - '100 mm', - ], - ]; - - yield[ - 'Created using simple string (converting with unit)', - Size::fromString('200 x 100'), - true, - [ - '200 px', - '100 px', - ], - ]; - - yield[ - 'Created using string with too much spaces everywhere', - Size::fromString(' 200 x 100 '), - false, - [ - 200, - 100, - ], - ]; - - yield[ - 'Created using string with too much spaces everywhere (converting with unit)', - Size::fromString(' 200 x 100 '), - true, - [ - '200 px', - '100 px', - ], - ]; - } - - public function provideSizeForFromString() - { - yield[ - 'One number only', - 200, - '', - ' x ', - null, - ]; - - yield[ - 'One number only as string', - '200', - '', - ' x ', - null, - ]; - - yield[ - 'The " " as invalid separator', - '200 100', - '', - ' x ', - null, - ]; - - yield[ - 'The "|" as separator (invalid separator)', - '200 | 100', - '', - ' x ', - null, - ]; - - yield[ - 'The "|" as invalid separator and no spaces around separator', - '200|100', - '', - ' x ', - null, - ]; - - yield[ - 'The "X" as invalid separator', - '200 X 100', - '', - ' x ', - null, - ]; - - yield[ - 'Simple, valid size', - '200 x 100', - 'px', - ' x ', - Size::fromArray([ - 200, - 100, - ]), - ]; - - yield[ - 'Simple, valid size using custom separator', - '200 X 100', - 'px', - ' X ', - Size::fromArray([ - 200, - 100, - ])->setSeparator(' X '), - ]; - - yield[ - 'Too much spaces at the right of separator', - '200 x 100', - 'px', - ' x ', - Size::fromArray([ - 200, - 100, - ]), - ]; - - yield[ - 'Too much spaces at the left of separator', - '200 x 100', - 'px', - ' x ', - Size::fromArray([ - 200, - 100, - ]), - ]; - - yield[ - 'Too much spaces around separator', - '200 x 100', - 'px', - ' x ', - Size::fromArray([ - 200, - 100, - ]), - ]; - - yield[ - 'Too much spaces before width (1st)', - ' 200 x 100', - 'px', - ' x ', - Size::fromArray([ - 200, - 100, - ]), - ]; - - yield[ - 'Too much spaces before width (2nd) and custom separator', - ' 200 X 100', - 'px', - ' X ', - Size::fromArray([ - 200, - 100, - ])->setSeparator(' X '), - ]; - - yield[ - 'Too much spaces after height (1st)', - '200 x 100 ', - 'px', - ' x ', - Size::fromArray([ - 200, - 100, - ]), - ]; - - yield[ - 'Too much spaces after height (2nd) and custom separator', - '200 X 100 ', - 'px', - ' X ', - Size::fromArray([ - 200, - 100, - ])->setSeparator(' X '), - ]; - - yield[ - 'Too much spaces before width and after height (1st)', - ' 200 x 100 ', - 'km', - ' x ', - Size::fromArray( - [ - 200, - 100, - ], - 'km' - ), - ]; - - yield[ - 'Too much spaces before width and after height (2nd) and custom separator', - ' 200 X 100 ', - 'px', - ' X ', - Size::fromArray([ - 200, - 100, - ])->setSeparator(' X '), - ]; - - yield[ - 'Too much spaces everywhere (1st)', - ' 200 x 100 ', - 'px', - ' x ', - Size::fromArray([ - 200, - 100, - ]), - ]; - - yield[ - 'Too much spaces everywhere (2nd) and custom separator', - ' 200 X 100 ', - 'px', - ' X ', - Size::fromArray([ - 200, - 100, - ])->setSeparator(' X '), - ]; - - yield[ - 'Too much spaces everywhere (3rd)', - ' 200 x 100 ', - 'px', - ' x ', - Size::fromArray([ - 200, - 100, - ]), - ]; - - yield[ - 'Too much spaces everywhere (4th) and custom separator', - ' 200 : 100 ', - 'px', - ' : ', - Size::fromArray([ - 200, - 100, - ])->setSeparator(' : '), - ]; - - yield[ - 'Too much spaces everywhere (5th)', - ' 200 x 100 ', - 'mm', - ' x ', - Size::fromArray( - [ - 200, - 100, - ], - 'mm' - ), - ]; - } - - public function provideSizeForFromArray() - { - yield[ - 'An empty array', - [], - '', - null, - ]; - - yield[ - 'One number only', - [ - 200, - ], - '', - null, - ]; - - yield[ - 'One number only as string', - [ - '200', - ], - '', - null, - ]; - - yield[ - '0 as dimensions', - [ - 0, - 0, - ], - 'px', - Size::fromString('0 x 0'), - ]; - - yield[ - 'Simple, valid size', - [ - 200, - 100, - ], - 'px', - Size::fromString('200 x 100'), - ]; - - yield[ - 'Simple, valid size (using strings)', - [ - '200', - '100', - ], - 'mm', - Size::fromString('200 x 100', 'mm'), - ]; + static::assertEquals($expected, (string) $size, $description); } } diff --git a/tests/ValueObject/TemplateTest.php b/tests/ValueObject/TemplateTest.php new file mode 100644 index 0000000..8dc8f7c --- /dev/null +++ b/tests/ValueObject/TemplateTest.php @@ -0,0 +1,272 @@ + + * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\ValueObject\Template + */ +class TemplateTest extends BaseTestCase +{ + public function provideInvalidContent(): ?Generator + { + $template = 'Content of template \'%s\' is invalid. Did you use string with 1 placeholder at least?'; + + yield [ + 'An empty string' => '', + sprintf($template, ''), + ]; + + yield [ + 'Without placeholders' => 'test', + sprintf($template, 'test'), + ]; + + yield [ + 'With starting tag only (invalid placeholder)' => 'This is %test', + sprintf($template, 'This is %test'), + ]; + + yield [ + 'With ending tag only (invalid placeholder)' => 'This is test%', + sprintf($template, 'This is test%'), + ]; + } + + public function provideTemplateToFill(): ?Generator + { + yield [ + 'Template with 1 placeholder', + new Template('%test%'), + [ + 'test' => 123, + ], + '123', + ]; + + yield [ + 'Template with 1 placeholder, but more values', + new Template('%test%'), + [ + 'test' => 123, + 'anotherTest' => 456, + ], + '123', + ]; + + yield [ + 'Template with 2 placeholders', + new Template('My name is %name% and I am %profession%'), + [ + 'name' => 'Jane', + 'profession' => 'photographer', + ], + 'My name is Jane and I am photographer', + ]; + + yield [ + 'Template with 2 placeholders, but more values', + new Template('My name is %name% and I am %profession%'), + [ + 'name' => 'Jane', + 'test-test' => 123, + 'profession' => 'photographer', + 'anotherTest' => 456, + ], + 'My name is Jane and I am photographer', + ]; + + yield [ + 'Template with 2 placeholders that contains space', + new Template('My name is %first name% %last name% and I live in %current location%'), + [ + 'first name' => 'Jane', + 'last name' => 'Brown', + 'current location' => 'NY, USA', + ], + 'My name is Jane Brown and I live in NY, USA', + ]; + + yield [ + 'Template with 2 placeholders that contains space, but more values', + new Template('My name is %first name% %last name% and I live in %current location%'), + [ + 'first name' => 'Jane', + 'profession' => 'photographer', + 'last name' => 'Brown', + 'test-test' => 123, + 'anotherTest' => 456, + 'current location' => 'NY, USA', + ], + 'My name is Jane Brown and I live in NY, USA', + ]; + } + + public function provideTemplateToFillUsingIncorrectValues(): ?Generator + { + $template = 'Cannot fill template \'%s\', because of missing values for placeholder(s): %s. Did you provide all' + .' required values?'; + + yield [ + new Template('%test%'), + [ + 'something' => 123, + ], + sprintf( + $template, + '%test%', + 'test' + ), + ]; + + yield [ + new Template('%test%'), + [], + sprintf( + $template, + '%test%', + 'test' + ), + ]; + + yield [ + new Template('%test1% - %test2%'), + [ + 'test1' => 123, + ], + sprintf( + $template, + '%test1% - %test2%', + 'test2' + ), + ]; + + yield [ + new Template('%test1% - %test2%'), + [ + 'test1' => 123, + 'test3' => 456, + ], + sprintf( + $template, + '%test1% - %test2%', + 'test2' + ), + ]; + + yield [ + new Template('%test1% / %test2% / %test3%'), + [ + 'test1' => 123, + ], + sprintf( + $template, + '%test1% / %test2% / %test3%', + 'test2, test3' + ), + ]; + } + + public function provideValidContent(): ?Generator + { + yield [ + 'Template with 1 placeholder', + '%test%', + ]; + + yield [ + 'Template with 2 placeholders', + 'My name is %name% and I am %profession%', + ]; + + yield [ + 'Template with 2 placeholders that contains space', + 'My name is %first name% %last name% and I live in %current location%', + ]; + } + + public function testConstructor(): void + { + static::assertConstructorVisibilityAndArguments( + Template::class, + OopVisibilityType::IS_PUBLIC, + 1, + 1 + ); + } + + /** + * @param string $description Description of test + * @param Template $template Template to fill + * @param array $values Pairs of key-value where: key - name of placeholder, value - value of the + * placeholder + * @param string $expected Expected result + * + * @dataProvider provideTemplateToFill + */ + public function testFill(string $description, Template $template, array $values, string $expected): void + { + static::assertSame($expected, $template->fill($values), $description); + } + + /** + * @param Template $template Template to fill + * @param array $values Pairs of key-value where: key - name of placeholder, value - value of the + * placeholder + * @param string $exceptionMessage Expected message of exception + * + * @dataProvider provideTemplateToFillUsingIncorrectValues + */ + public function testFillUsingIncorrectValues(Template $template, array $values, string $exceptionMessage): void + { + $this->expectException(MissingPlaceholdersInValuesException::class); + $this->expectExceptionMessage($exceptionMessage); + + $template->fill($values); + } + + /** + * @param string $content Raw string with placeholders (content of the template) + * @param string $exceptionMessage Expected message of exception + * + * @dataProvider provideInvalidContent + */ + public function testIsValidUsingInvalidContent(string $content, string $exceptionMessage): void + { + $this->expectException(InvalidContentException::class); + $this->expectExceptionMessage($exceptionMessage); + + new Template($content); + } + + /** + * @param string $description Description of test + * @param string $content Raw string with placeholders (content of the template) + * + * @dataProvider provideValidContent + */ + public function testIsValidUsingValidContent(string $description, string $content): void + { + $template = new Template($content); + static::assertSame($content, Reflection::getPropertyValue($template, 'content', true), $description); + } +} diff --git a/tests/ValueObject/VersionTest.php b/tests/ValueObject/VersionTest.php index fb0e783..210374f 100644 --- a/tests/ValueObject/VersionTest.php +++ b/tests/ValueObject/VersionTest.php @@ -19,14 +19,155 @@ use Meritoo\Common\ValueObject\Version; * * @author Meritoo * @copyright Meritoo + * + * @internal + * @covers \Meritoo\Common\ValueObject\Version */ class VersionTest extends BaseTestCase { + /** + * Provide version as array and expected instance of version + * + * @return Generator + */ + public function provideAsArray() + { + yield [ + [], + null, + ]; + + yield [ + [ + 1, + 0, + ], + null, + ]; + + yield [ + [ + 10, + ], + null, + ]; + + yield [ + [ + 0, + 0, + 0, + ], + new Version(0, 0, 0), + ]; + + yield [ + [ + 1, + 0, + 2, + ], + new Version(1, 0, 2), + ]; + + yield [ + [ + 10, + 5, + 41, + ], + new Version(10, 5, 41), + ]; + } + + /** + * Provide version as string and expected instance of version + * + * @return Generator + */ + public function provideAsString() + { + yield [ + '', + null, + ]; + + yield [ + '1.0', + null, + ]; + + yield [ + '10', + null, + ]; + + yield [ + '0.0.0', + new Version(0, 0, 0), + ]; + + yield [ + '1.0.2', + new Version(1, 0, 2), + ]; + + yield [ + '10.5.41', + new Version(10, 5, 41), + ]; + } + + /** + * Provide instance of version and expected version converted to string + * + * @return Generator + */ + public function provideConvertedToString() + { + yield [ + new Version(0, 0, 0), + '0.0.0', + ]; + + yield [ + new Version(1, 0, 2), + '1.0.2', + ]; + + yield [ + new Version(10, 5, 41), + '10.5.41', + ]; + } + public function testConstructor() { static::assertConstructorVisibilityAndArguments(Version::class, OopVisibilityType::IS_PUBLIC, 3, 3); } + /** + * @param array $version The version + * @param Version $expected (optional) Expected version + * + * @dataProvider provideAsArray + */ + public function testFromArray(array $version, Version $expected = null) + { + static::assertEquals($expected, Version::fromArray($version)); + } + + /** + * @param string $version The version + * @param Version $expected (optional) Expected version + * + * @dataProvider provideAsString + */ + public function testFromString($version, Version $expected = null) + { + static::assertEquals($expected, Version::fromString($version)); + } + public function testNewInstance() { $version = new Version(1, 0, 2); @@ -45,144 +186,6 @@ class VersionTest extends BaseTestCase */ public function testToString(Version $version, $expected) { - static::assertSame($expected, (string)$version); - } - - /** - * @param string $version The version - * @param Version $expected (optional) Expected version - * - * @dataProvider provideAsString - */ - public function testFromString($version, Version $expected = null) - { - static::assertEquals($expected, Version::fromString($version)); - } - - /** - * @param array $version The version - * @param Version $expected (optional) Expected version - * - * @dataProvider provideAsArray - */ - public function testFromArray(array $version, Version $expected = null) - { - static::assertEquals($expected, Version::fromArray($version)); - } - - /** - * Provide instance of version and expected version converted to string - * - * @return Generator - */ - public function provideConvertedToString() - { - yield[ - new Version(0, 0, 0), - '0.0.0', - ]; - - yield[ - new Version(1, 0, 2), - '1.0.2', - ]; - - yield[ - new Version(10, 5, 41), - '10.5.41', - ]; - } - - /** - * Provide version as string and expected instance of version - * - * @return Generator - */ - public function provideAsString() - { - yield[ - '', - null, - ]; - - yield[ - '1.0', - null, - ]; - - yield[ - '10', - null, - ]; - - yield[ - '0.0.0', - new Version(0, 0, 0), - ]; - - yield[ - '1.0.2', - new Version(1, 0, 2), - ]; - - yield[ - '10.5.41', - new Version(10, 5, 41), - ]; - } - - /** - * Provide version as array and expected instance of version - * - * @return Generator - */ - public function provideAsArray() - { - yield[ - [], - null, - ]; - - yield[ - [ - 1, - 0, - ], - null, - ]; - - yield[ - [ - 10, - ], - null, - ]; - - yield[ - [ - 0, - 0, - 0, - ], - new Version(0, 0, 0), - ]; - - yield[ - [ - 1, - 0, - 2, - ], - new Version(1, 0, 2), - ]; - - yield[ - [ - 10, - 5, - 41, - ], - new Version(10, 5, 41), - ]; + static::assertSame($expected, (string) $version); } }