diff --git a/.docker/config/Dockerfile b/.docker/config/Dockerfile index cb2d05c..6102401 100644 --- a/.docker/config/Dockerfile +++ b/.docker/config/Dockerfile @@ -1,4 +1,4 @@ -FROM php:5.6-cli +FROM php:5.4-cli # # Tools & libraries @@ -31,12 +31,15 @@ COPY php.ini /usr/local/etc/php/php.ini ARG TIMEZONE RUN echo "\n""date.timezone = $TIMEZONE""\n" >> /usr/local/etc/php/php.ini +# +# Disabled, because: +# PHP versions below 5.5 are not supported # # Xdebug # -RUN pecl install xdebug \ - && docker-php-ext-enable xdebug -COPY xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini +#RUN pecl install xdebug \ +# && docker-php-ext-enable xdebug +#COPY xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini # # Phing diff --git a/.phing/tests.xml b/.phing/tests.xml index 4567626..8ce4e50 100644 --- a/.phing/tests.xml +++ b/.phing/tests.xml @@ -146,13 +146,18 @@ 2017-02-22 --> - + + diff --git a/composer.json b/composer.json index cbe3b91..e5e15f4 100644 --- a/composer.json +++ b/composer.json @@ -11,22 +11,24 @@ } ], "require": { - "php": ">=5.6", + "php": ">=5.4", "fguillot/json-rpc": "^1.2", - "meritoo/common-library": "~0.0.1" + "gedmo/doctrine-extensions": "^2.4", + "symfony/http-foundation": "^2.8" }, "require-dev": { - "phpunit/phpunit": "^5.7", - "squizlabs/php_codesniffer": "^2.9", + "phpunit/phpunit": "^4.8", + "squizlabs/php_codesniffer": "^3.1", "phpmd/phpmd": "^2.6", - "sebastian/phpcpd": "^3.0", + "sebastian/phpcpd": "^2.0", "pdepend/pdepend": "^2.5", - "phploc/phploc": "^4.0", - "friendsofphp/php-cs-fixer": "^2.6" + "phploc/phploc": "^2.1", + "friendsofphp/php-cs-fixer": "^2.2" }, "autoload": { "psr-4": { - "Meritoo\\LimeSurvey\\ApiClient\\": "src/" + "Meritoo\\Common\\": "src/Common/", + "Meritoo\\LimeSurvey\\ApiClient\\": "src/LimeSurvey/" } }, "autoload-dev": { diff --git a/src/Common/Collection/Collection.php b/src/Common/Collection/Collection.php new file mode 100644 index 0000000..75770c2 --- /dev/null +++ b/src/Common/Collection/Collection.php @@ -0,0 +1,284 @@ + + * @copyright Meritoo.pl + */ +class Collection implements Countable, ArrayAccess, IteratorAggregate +{ + /** + * The elements of collection + * + * @var array + */ + private $elements; + + /** + * Class constructor + * + * @param array $elements (optional) The elements of collection + */ + public function __construct(array $elements = []) + { + $this->elements = $elements; + } + + /** + * {@inheritdoc} + * Required by interface Countable + */ + public function count() + { + return count($this->elements); + } + + /** + * {@inheritdoc} + * Required by interface ArrayAccess + */ + public function offsetExists($offset) + { + return $this->exists($offset); + } + + /** + * {@inheritdoc} + * Required by interface ArrayAccess + */ + public function offsetGet($offset) + { + if ($this->exists($offset)) { + return $this->elements[$offset]; + } + + return null; + } + + /** + * {@inheritdoc} + * Required by interface ArrayAccess + */ + public function offsetSet($offset, $value) + { + $this->elements[$offset] = $value; + } + + /** + * {@inheritdoc} + * Required by interface ArrayAccess + */ + public function offsetUnset($offset) + { + if ($this->exists($offset)) { + unset($this->elements[$offset]); + } + } + + /** + * {@inheritdoc} + * Required by interface IteratorAggregate + */ + public function getIterator() + { + return new ArrayIterator($this->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) { + $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) { + $index = null; + } + + $this->add($element, $index); + } + } + + 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 an array representation of the collection + * + * @return array + */ + public function toArray() + { + return $this->elements; + } + + /** + * 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/Common/Exception/Base/UnknownTypeException.php b/src/Common/Exception/Base/UnknownTypeException.php new file mode 100644 index 0000000..0a4c5e9 --- /dev/null +++ b/src/Common/Exception/Base/UnknownTypeException.php @@ -0,0 +1,41 @@ + + * @copyright Meritoo.pl + */ +abstract class UnknownTypeException extends Exception +{ + /** + * Class constructor + * + * @param string|int $unknownType The unknown type of something (value of constant) + * @param BaseType $typeInstance An instance of class that contains type of the something + * @param string $typeName Name of the something + */ + public function __construct($unknownType, BaseType $typeInstance, $typeName) + { + $allTypes = $typeInstance->getAll(); + $types = Arrays::values2string($allTypes, '', ', '); + + $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.'; + + $message = sprintf(sprintf($template, $unknownType, $typeName, $types)); + parent::__construct($message); + } +} diff --git a/src/Common/Exception/Date/UnknownDatePartTypeException.php b/src/Common/Exception/Date/UnknownDatePartTypeException.php new file mode 100644 index 0000000..d25a610 --- /dev/null +++ b/src/Common/Exception/Date/UnknownDatePartTypeException.php @@ -0,0 +1,32 @@ + + * @copyright Meritoo.pl + */ +class UnknownDatePartTypeException extends UnknownTypeException +{ + /** + * Class constructor + * + * @param string $unknownDatePart Type of date part, e.g. "year". One of DatePartType class constants. + * @param string $value Incorrect value + */ + public function __construct($unknownDatePart, $value) + { + parent::__construct($unknownDatePart, new DatePartType(), sprintf('date part (with value %s)', $value)); + } +} diff --git a/src/Common/Exception/File/EmptyFileException.php b/src/Common/Exception/File/EmptyFileException.php new file mode 100644 index 0000000..14734d7 --- /dev/null +++ b/src/Common/Exception/File/EmptyFileException.php @@ -0,0 +1,31 @@ + + * @copyright Meritoo.pl + */ +class EmptyFileException extends \Exception +{ + /** + * Class constructor + * + * @param string $emptyFilePath Path of the empty file + */ + public function __construct($emptyFilePath) + { + $template = 'File with path \'%s\' is empty (has no content). Did you provide path of proper file?'; + $message = sprintf($template, $emptyFilePath); + + parent::__construct($message); + } +} diff --git a/src/Common/Exception/File/EmptyFilePathException.php b/src/Common/Exception/File/EmptyFilePathException.php new file mode 100644 index 0000000..5f80e6b --- /dev/null +++ b/src/Common/Exception/File/EmptyFilePathException.php @@ -0,0 +1,26 @@ + + * @copyright Meritoo.pl + */ +class EmptyFilePathException extends \Exception +{ + /** + * Class constructor + */ + public function __construct() + { + parent::__construct('Path of the file is empty. Did you provide path of proper file?'); + } +} diff --git a/src/Common/Exception/File/NotExistingFileException.php b/src/Common/Exception/File/NotExistingFileException.php new file mode 100644 index 0000000..51c6fa3 --- /dev/null +++ b/src/Common/Exception/File/NotExistingFileException.php @@ -0,0 +1,31 @@ + + * @copyright Meritoo.pl + */ +class NotExistingFileException extends \Exception +{ + /** + * Class constructor + * + * @param string $notExistingFilePath Path of not existing (or not readable) file + */ + public function __construct($notExistingFilePath) + { + $template = 'File with path \'%s\' does not exist (or is not readable). Did you provide path of proper file?'; + $message = sprintf($template, $notExistingFilePath); + + parent::__construct($message); + } +} diff --git a/src/Common/Exception/Method/DisabledMethodException.php b/src/Common/Exception/Method/DisabledMethodException.php new file mode 100644 index 0000000..7e12114 --- /dev/null +++ b/src/Common/Exception/Method/DisabledMethodException.php @@ -0,0 +1,40 @@ + + * @copyright Meritoo.pl + */ +class DisabledMethodException extends Exception +{ + const className = '\Meritoo\Common\Exception\Method\DisabledMethodException'; + + /** + * Class constructor + * + * @param string $disabledMethod Name of the disabled method + * @param string $alternativeMethod (optional) Name of the alternative method + */ + public function __construct($disabledMethod, $alternativeMethod = '') + { + $template = 'Method %s() cannot be called, because is disabled.'; + + if (!empty($alternativeMethod)) { + $template .= ' Use %s() instead.'; + } + + $message = sprintf($template, $disabledMethod, $alternativeMethod); + parent::__construct($message); + } +} diff --git a/src/Common/Exception/Reflection/CannotResolveClassNameException.php b/src/Common/Exception/Reflection/CannotResolveClassNameException.php new file mode 100644 index 0000000..21708a2 --- /dev/null +++ b/src/Common/Exception/Reflection/CannotResolveClassNameException.php @@ -0,0 +1,47 @@ + + * @copyright Meritoo.pl + */ +class CannotResolveClassNameException extends Exception +{ + /** + * Class constructor + * + * @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. + */ + public function __construct($source, $forClass = true) + { + $forWho = 'trait'; + $value = ''; + + if ($forClass) { + $forWho = 'class'; + } + + if (is_scalar($source)) { + $value = sprintf(' %s', (string)$source); + } + + $template = 'Name of %s from given \'%s\'%s cannot be resolved. Is there everything ok?'; + $message = sprintf($template, $forWho, gettype($source), $value); + + parent::__construct($message); + } +} diff --git a/src/Common/Exception/Reflection/MissingChildClassesException.php b/src/Common/Exception/Reflection/MissingChildClassesException.php new file mode 100644 index 0000000..73c13b6 --- /dev/null +++ b/src/Common/Exception/Reflection/MissingChildClassesException.php @@ -0,0 +1,38 @@ + + * @copyright Meritoo.pl + */ +class MissingChildClassesException extends Exception +{ + /** + * Class constructor + * + * @param array|object|string $parentClass Class that hasn't child classes, but it should. An array of objects, + * strings, object or string. + */ + public function __construct($parentClass) + { + $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?'; + + $parentClassName = Reflection::getClassName($parentClass); + $message = sprintf($template, $parentClassName); + + parent::__construct($message); + } +} diff --git a/src/Common/Exception/Reflection/TooManyChildClassesException.php b/src/Common/Exception/Reflection/TooManyChildClassesException.php new file mode 100644 index 0000000..b5a4f13 --- /dev/null +++ b/src/Common/Exception/Reflection/TooManyChildClassesException.php @@ -0,0 +1,39 @@ + + * @copyright Meritoo.pl + */ +class TooManyChildClassesException extends Exception +{ + /** + * Class constructor + * + * @param array|object|string $parentClass Class that has more than one child class, but it shouldn't. An array + * of objects, strings, object or string. + * @param array $childClasses Child classes + */ + public function __construct($parentClass, array $childClasses) + { + $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?"; + + $parentClassName = Reflection::getClassName($parentClass); + $message = sprintf($template, $parentClassName, implode("\n- ", $childClasses), $parentClassName); + + parent::__construct($message); + } +} diff --git a/src/Common/Exception/Regex/IncorrectColorHexLengthException.php b/src/Common/Exception/Regex/IncorrectColorHexLengthException.php new file mode 100644 index 0000000..89ee485 --- /dev/null +++ b/src/Common/Exception/Regex/IncorrectColorHexLengthException.php @@ -0,0 +1,32 @@ + + * @copyright Meritoo.pl + */ +class IncorrectColorHexLengthException extends \Exception +{ + /** + * Class constructor + * + * @param string $color Incorrect hexadecimal value of color + */ + public function __construct($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?'; + + $message = sprintf($template, $color, strlen($color)); + parent::__construct($message); + } +} diff --git a/src/Common/Exception/Regex/InvalidColorHexValueException.php b/src/Common/Exception/Regex/InvalidColorHexValueException.php new file mode 100644 index 0000000..f17771a --- /dev/null +++ b/src/Common/Exception/Regex/InvalidColorHexValueException.php @@ -0,0 +1,29 @@ + + * @copyright Meritoo.pl + */ +class InvalidColorHexValueException extends \Exception +{ + /** + * Class constructor + * + * @param string $color Invalid hexadecimal value of color + */ + public function __construct($color) + { + $message = sprintf('Hexadecimal value of color \'%s\' is invalid. Is there everything ok?', $color); + parent::__construct($message); + } +} diff --git a/src/Common/Exception/Regex/InvalidUrlException.php b/src/Common/Exception/Regex/InvalidUrlException.php new file mode 100644 index 0000000..22834b9 --- /dev/null +++ b/src/Common/Exception/Regex/InvalidUrlException.php @@ -0,0 +1,25 @@ + + * @copyright Meritoo.pl + */ +class InvalidUrlException extends \Exception +{ + const className = '\Meritoo\Common\Exception\Regex\InvalidUrlException'; + + /** + * Class constructor + * + * @param string $url Invalid url + */ + public function __construct($url) + { + $message = sprintf('Url \'%s\' is invalid. Is there everything ok?', $url); + parent::__construct($message); + } +} diff --git a/src/Common/Exception/Type/UnknownOopVisibilityTypeException.php b/src/Common/Exception/Type/UnknownOopVisibilityTypeException.php new file mode 100644 index 0000000..4ac7165 --- /dev/null +++ b/src/Common/Exception/Type/UnknownOopVisibilityTypeException.php @@ -0,0 +1,23 @@ + + * @copyright Meritoo.pl + */ +class UnknownOopVisibilityTypeException extends UnknownTypeException +{ + /** + * {@inheritdoc} + */ + public function __construct($unknownType) + { + parent::__construct($unknownType, new OopVisibilityType(), 'OOP-related visibility'); + } +} diff --git a/src/Common/Iterator/CommonIterator.php b/src/Common/Iterator/CommonIterator.php new file mode 100644 index 0000000..9350951 --- /dev/null +++ b/src/Common/Iterator/CommonIterator.php @@ -0,0 +1,87 @@ + + * @copyright Meritoo.pl + */ +class CommonIterator implements Iterator +{ + /** + * Index/Position of current value + * + * @var int + */ + private $currentIndex = 0; + + /** + * Maximum index/position of current value + * + * @var int + */ + private $maxIndex = 0; + + /** + * Values to iterate + * + * @var array + */ + private $values = []; + + /** + * Class constructor + * + * @param array $values Values to iterate + */ + public function __construct(array $values) + { + $this->values = $values; + } + + /** + * {@inheritdoc} + */ + public function current() + { + return $this->values[$this->currentIndex]; + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->currentIndex; + } + + /** + * {@inheritdoc} + */ + public function next() + { + if (0 <= $this->currentIndex && $this->maxIndex > $this->currentIndex) { + ++$this->currentIndex; + } + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->currentIndex = 0; + } + + /** + * {@inheritdoc} + */ + public function valid() + { + return 0 <= $this->currentIndex && $this->maxIndex >= $this->currentIndex; + } +} diff --git a/src/Common/Test/Base/BaseTestCase.php b/src/Common/Test/Base/BaseTestCase.php new file mode 100644 index 0000000..9212bb2 --- /dev/null +++ b/src/Common/Test/Base/BaseTestCase.php @@ -0,0 +1,310 @@ + + * @copyright Meritoo.pl + */ +abstract class BaseTestCase extends TestCase +{ + /** + * Path of directory with data used by test cases + * + * @var string + */ + private static $testsDataDirPath = '.data/tests'; + + /** + * Provides an empty value + * + * @return CommonIterator + * //return Generator + */ + public function provideEmptyValue() + { + return new CommonIterator([ + '', + ' ', + null, + 0, + false, + [], + ]); + + /* + yield['']; + yield[' ']; + yield[null]; + yield[0]; + yield[false]; + yield[[]]; + */ + } + + /** + * Provides boolean value + * + * @return CommonIterator + * //return Generator + */ + public function provideBooleanValue() + { + return new CommonIterator([ + true, + false, + ]); + + /* + yield[false]; + yield[true]; + */ + } + + /** + * Provides instance of DateTime class + * + * @return CommonIterator + * //return Generator + */ + public function provideDateTimeInstance() + { + return new CommonIterator([ + new DateTime(), + new DateTime('yesterday'), + new DateTime('now'), + new DateTime('tomorrow'), + ]); + + /* + yield[new DateTime()]; + yield[new DateTime('yesterday')]; + yield[new DateTime('now')]; + yield[new DateTime('tomorrow')]; + */ + } + + /** + * Provides relative / compound format of DateTime + * + * @return CommonIterator + * //return Generator + */ + public function provideDateTimeRelativeFormat() + { + return new CommonIterator([ + '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', + ]); + + /* + 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" + * + * @return CommonIterator + * //return Generator + */ + public function provideNotExistingFilePath() + { + return new CommonIterator([ + 'lets-test.doc', + 'lorem/ipsum.jpg', + 'surprise/me/one/more/time.txt', + ]); + + /* + yield['lets-test.doc']; + yield['lorem/ipsum.jpg']; + yield['surprise/me/one/more/time.txt']; + */ + } + + /** + * Returns path of file used by tests. + * It should be placed in /.data/tests directory of this project. + * + * @param string $fileName Name of file + * @param string $directoryPath (optional) Path of directory containing the file + * @return string + */ + public function getFilePathToTests($fileName, $directoryPath = '') + { + $rootPath = Miscellaneous::getProjectRootPath(); + + $paths = [ + $rootPath, + self::$testsDataDirPath, + $directoryPath, + $fileName, + ]; + + 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 + * @throws UnknownOopVisibilityTypeException + */ + protected static function assertConstructorVisibilityAndArguments( + $classNamespace, + $visibilityType, + $argumentsCount = 0, + $requiredArgumentsCount = 0 + ) { + /* + * Let's grab the constructor + */ + $reflection = new ReflectionClass($classNamespace); + $method = $reflection->getConstructor(); + + return 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) + { + static::$testsDataDirPath = $testsDataDirPath; + } + + /** + * Returns a mock object for the specified class + * + * @param string $originalClassName Name of the class to mock + * @return \PHPUnit_Framework_MockObject_MockObject + */ + protected function createMock($originalClassName) + { + $methods = []; + $arguments = []; + $mockClassName = ''; + $callOriginalConstructor = false; + + return $this->getMock($originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor); + } +} diff --git a/src/Common/Test/Base/BaseTypeTestCase.php b/src/Common/Test/Base/BaseTypeTestCase.php new file mode 100644 index 0000000..aaa0bcc --- /dev/null +++ b/src/Common/Test/Base/BaseTypeTestCase.php @@ -0,0 +1,64 @@ + + * @copyright Meritoo.pl + */ +abstract class BaseTypeTestCase extends BaseTestCase +{ + /** + * Verifies availability of all types + */ + public function testAvailabilityOfAllTypes() + { + $available = $this->getTestedTypeInstance()->getAll(); + $all = $this->getAllExpectedTypes(); + + if (isset($available['className'])) { + unset($available['className']); + } + + static::assertEquals($all, $available); + } + + /** + * 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 + * + * @dataProvider provideTypeToVerify + */ + public function testIfGivenTypeIsCorrect($type, $expected) + { + static::assertEquals($expected, $this->getTestedTypeInstance()->isCorrectType($type)); + } + + /** + * 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(); +} diff --git a/src/Common/Type/Base/BaseType.php b/src/Common/Type/Base/BaseType.php new file mode 100644 index 0000000..56f6a19 --- /dev/null +++ b/src/Common/Type/Base/BaseType.php @@ -0,0 +1,53 @@ + + * @copyright Meritoo.pl + */ +abstract class BaseType +{ + /** + * All types + * + * @var array + */ + private $all; + + /** + * Returns all types + * + * @return array + */ + public function getAll() + { + if (null === $this->all) { + $this->all = Reflection::getConstants($this); + } + + return $this->all; + } + + /** + * Returns information if given type is correct + * + * @param string $type The type to check + * @return bool + */ + public function isCorrectType($type) + { + return in_array($type, $this->getAll(), true); + } +} diff --git a/src/Common/Type/DatePartType.php b/src/Common/Type/DatePartType.php new file mode 100644 index 0000000..5467a4d --- /dev/null +++ b/src/Common/Type/DatePartType.php @@ -0,0 +1,62 @@ + + * @copyright Meritoo.pl + */ +class DatePartType extends BaseType +{ + /** + * The "day" date part + * + * @var string + */ + const DAY = 'day'; + + /** + * The "hour" date part + * + * @var string + */ + const HOUR = 'hour'; + + /** + * The "minute" date part + * + * @var string + */ + const MINUTE = 'minute'; + + /** + * The "month" date part + * + * @var string + */ + const MONTH = 'month'; + + /** + * The "second" date part + * + * @var string + */ + const SECOND = 'second'; + + /** + * The "year" date part + * + * @var string + */ + const YEAR = 'year'; +} diff --git a/src/Common/Type/OopVisibilityType.php b/src/Common/Type/OopVisibilityType.php new file mode 100644 index 0000000..59ede6d --- /dev/null +++ b/src/Common/Type/OopVisibilityType.php @@ -0,0 +1,37 @@ + + * @copyright Meritoo.pl + * + * @see http://php.net/manual/en/language.oop5.visibility.php + */ +class OopVisibilityType extends BaseType +{ + /** + * The "private" visibility of OOP + * + * @var int + */ + const IS_PRIVATE = 3; + + /** + * The "protected" visibility of OOP + * + * @var int + */ + const IS_PROTECTED = 2; + + /** + * The "public" visibility of OOP + * + * @var int + */ + const IS_PUBLIC = 1; +} diff --git a/src/Common/Utilities/Arrays.php b/src/Common/Utilities/Arrays.php new file mode 100644 index 0000000..bdfe20b --- /dev/null +++ b/src/Common/Utilities/Arrays.php @@ -0,0 +1,1641 @@ + + * @copyright Meritoo.pl + */ +class Arrays +{ + /** + * Name of the array's key used to store position of element of the array + * + * @var string + */ + const POSITION_KEY_NAME = 'position'; + + /** + * Converts given array's column to string. + * Recursive call is made for multi-dimensional arrays. + * + * @param array $array Array data to be converted + * @param string|int $arrayColumnKey (optional) Column name + * @param string $separator (optional) Separator used in resultant string + * @return string + */ + public static function values2string(array $array, $arrayColumnKey = '', $separator = ',') + { + $effect = ''; + + if (!empty($array)) { + foreach ($array as $key => $value) { + if (!empty($effect) && + ( + empty($arrayColumnKey) || (!is_array($value) && $key === $arrayColumnKey) + ) + ) { + $effect .= $separator; + } + + if (is_array($value)) { + $effect .= self::values2string($value, $arrayColumnKey, $separator); + /* + * Previous version. Doesn't work with array containing arrays, e.g.: + * array( + * 1 => array( + * 'item1', + * 'item2' + * ), + * 2 => array( + * 'item3', + * 'item4' + * ) + * ) + * + if(isset($value[$arrayColumnKey])){ + $effect .= $value[$arrayColumnKey]; + } + */ + } else { + if (empty($arrayColumnKey)) { + $effect .= $value; + } elseif ($key === $arrayColumnKey) { + $effect .= $array[$arrayColumnKey]; + } + } + } + } + + return $effect; + } + + /** + * Converts given array to string with keys, e.g. abc=1&def=2 or abc="1" def="2" + * + * @param array $array Array data to be converted + * @param string $separator (optional) Separator used between name-value pairs in resultant string + * @param string $valuesKeysSeparator (optional) Separator used between name and value in resultant string + * @param string $valuesWrapper (optional) Wrapper used to wrap values, e.g. double-quote: key="value" + * @return string + */ + public static function valuesKeys2string($array, $separator = ',', $valuesKeysSeparator = '=', $valuesWrapper = '') + { + $effect = ''; + + if (is_array($array) && !empty($array)) { + foreach ($array as $key => $value) { + if (!empty($effect)) { + $effect .= $separator; + } + + if (!empty($valuesWrapper)) { + $value = sprintf('%s%s%s', $valuesWrapper, $value, $valuesWrapper); + } + + $effect .= $key . $valuesKeysSeparator . $value; + } + } + + return $effect; + } + + /** + * Converts given array's rows to csv string + * + * @param array $array Array data to be converted. It have to be an array that represents database table. + * @param string $separator (optional) Separator used in resultant string + * @return string + */ + public static function values2csv($array, $separator = ',') + { + if (is_array($array) && !empty($array)) { + $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 &$value) { + $value = html_entity_decode($value); + } + + $rows[] = implode($separator, $row); + } + } + + if (!empty($rows)) { + return implode($lineSeparator, $rows); + } + } + + return ''; + } + + /** + * 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, $firstLevelOnly = true) + { + $firstElement = self::getFirstElement($array, $firstLevelOnly); + + return $element === $firstElement; + } + + /** + * 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, $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). + * @return bool + */ + public static function isLastElement(array $array, $element, $firstLevelOnly = true) + { + $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 + */ + 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 The array to get the breadcrumb + * @param string $separator (optional) Separator used to stick the elements + * @return string + */ + public static function getLastElementBreadCrumb($array, $separator = '/') + { + $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; + } + + $effect = ''; + $counter = 0; + + $arrayCount = count($array); + $array = self::quoteStrings($array); + $isMultiDimensional = self::isMultiDimensional($array); + + /* + * 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 (self::isMultiDimensional($array) && empty($jsVariableName)) { + $jsVariableName = 'autoGeneratedVariable'; + } + + if (!empty($jsVariableName) && is_string($jsVariableName)) { + $effect .= sprintf('var %s = ', $jsVariableName); + } + + $effect .= 'new Array('; + + if ($preserveIndexes || $isMultiDimensional) { + $effect .= $arrayCount; + $effect .= ');'; + } + + foreach ($array as $index => $value) { + ++$counter; + + if (is_array($value)) { + $variable = $index; + + if (is_integer($index)) { + $variable = 'value_' . $variable; + } + + $value = self::array2JavaScript($value, $variable, $preserveIndexes); + + if (!empty($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) { + $effect .= "\n"; + } + + $effect .= $value . "\n"; + $effect .= sprintf('%s[%s] = %s;', $jsVariableName, Miscellaneous::quoteValue($index), $variable); + + if ($counter !== $arrayCount) { + $effect .= "\n"; + } + } + } else { + if ($preserveIndexes) { + if (!empty($jsVariableName)) { + $index = Miscellaneous::quoteValue($index); + $effect .= sprintf("\n%s[%s] = %s;", $jsVariableName, $index, $value); + } + } else { + $format = '%s'; + + if ($counter < $arrayCount) { + $format .= ', '; + } + + $effect .= sprintf($format, $value); + } + } + } + + if (!$preserveIndexes && !$isMultiDimensional) { + $effect .= ');'; + } + + return $effect; + } + + /** + * Quotes (adds quotes) to elements of an array that are strings + * + * @param array $array The array to check for string values + * @return array + */ + public static function quoteStrings($array) + { + $effect = $array; + + if (is_array($array) && !empty($array)) { + $effect = []; + + foreach ($array as $index => $value) { + if (is_array($value)) { + $value = self::quoteStrings($value); + } elseif (is_string($value)) { + if (!Regex::isQuoted($value)) { + $value = '\'' . $value . '\''; + } + } + + $effect[$index] = $value; + } + } + + return $effect; + } + + /** + * 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, strlen($item) - 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) + { + $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) + { + if (empty($array) || !in_array($item, $array)) { + 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, $needle, $before = true) + { + if (is_array($array) && !empty($array)) { + if (!$before) { + $array = array_reverse($array, true); + } + + foreach ($array as $key => &$value) { + $remove = false; + + if (is_array($value)) { + self::removeElements($value, $needle, $before); + + if (is_array($value) && empty($value)) { + $remove = true; + } + } else { + if ($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 + * + * 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) + { + if (empty($array)) { + return []; + } + + $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; + } + + /** + * 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); + } + } + + 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 + * @param string $valuesKeysSeparator (optional) Separator used between name and value in the string + * @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; + } + + /** + * Returns information if given keys exist in given array + * + * @param array $keys The keys to find + * @param array $array The array which maybe contains keys + * @param bool $explicit (optional) If is set to true, all keys should exist in given array. Otherwise - not all. + * @return bool + */ + public static function areKeysInArray($keys, $array, $explicit = true) + { + $effect = false; + + if (is_array($array) && !empty($array)) { + $firstKey = true; + + foreach ($keys as $key) { + $exists = array_key_exists($key, $array); + + if ($firstKey) { + $effect = $exists; + $firstKey = false; + } else { + if ($explicit) { + $effect = $effect && $exists; + + if (!$effect) { + break; + } + } else { + $effect = $effect || $exists; + + if ($effect) { + break; + } + } + } + } + } + + return $effect; + } + + /** + * Returns paths of the last elements + * + * @param array $array The array with elements + * @param string $separator (optional) Separator used in resultant strings. 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) + * @return array + * + * Examples - $stopIfMatchedBy argument: + * a) "\d+" + * b) [ + * "lorem\-", + * "\d+", + * ]; + */ + public static function getLastElementsPaths(array $array, $separator = '.', $parentPath = '', $stopIfMatchedBy = '') + { + $paths = []; + + if (!empty($array)) { + if (!empty($stopIfMatchedBy)) { + $stopIfMatchedBy = self::makeArray($stopIfMatchedBy); + } + + foreach ($array as $key => $value) { + $path = $key; + $stopRecursion = false; + + /* + * If the path of parent element is delivered, + * I have to use it and build longer path + */ + if (!empty($parentPath)) { + $pathTemplate = '%s%s%s'; + $path = sprintf($pathTemplate, $parentPath, $separator, $key); + } + + /* + * Check if the key or current path matches one of patterns at which the process should be stopped, + * the recursive not used. It means that I have to pass current value and stop processing of the + * array (don't go to the next step). + */ + if (!empty($stopIfMatchedBy)) { + foreach ($stopIfMatchedBy as $rawPattern) { + $pattern = sprintf('|%s|', $rawPattern); + + if (preg_match($pattern, $key) || preg_match($pattern, $path)) { + $stopRecursion = true; + break; + } + } + } + + /* + * The value is passed to the returned array if: + * - it's not an array + * or + * - the process is stopped, recursive is not used + */ + if (!is_array($value) || (is_array($value) && empty($value)) || $stopRecursion) { + $paths[$path] = $value; + continue; + } + + /* + * Let's iterate through the next level, using recursive + */ + if (is_array($value)) { + $recursivePaths = self::getLastElementsPaths($value, $separator, $path, $stopIfMatchedBy); + $paths += $recursivePaths; + } + } + } + + return $paths; + } + + /** + * Makes and returns an array for given variable + * + * @param mixed $variable Variable that should be an array + * @return array + */ + public static function makeArray($variable) + { + 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, $pattern, $firstLevelOnly = false) + { + /* + * It's not an array or it's empty array? + */ + if (!is_array($array) || (is_array($array) && empty($array))) { + return false; + } + + /* + * I suppose that all are keys are matched + * and then I have to look for keys that don't matches + */ + $areMatched = true; + + /* + * 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; + } + + /** + * Returns information if keys / indexes of given array are integers, in other words if the array contains + * zero-based keys / indexes + * + * @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. + * @return bool + */ + public static function areAllKeysIntegers($array, $firstLevelOnly = false) + { + $pattern = '\d+'; + + return self::areAllKeysMatchedByPattern($array, $pattern, $firstLevelOnly); + } + + /** + * Returns value of given array set under given path of keys, of course if the value exists. + * The keys should be delivered in the same order as used by source array. + * + * @param array $array The array which should contains a value + * @param array $keys Keys, path of keys, to find in given array + * @return mixed + * + * Examples: + * a) $array + * [ + * 'some key' => [ + * 'another some key' => [ + * 'yet another key' => 123, + * ], + * 'some different key' => 456, + * ] + * ] + * + * b) $keys + * [ + * 'some key', + * 'another some key', + * 'yet another key', + * ] + * + * Based on the above examples will return: + * 123 + */ + public static function getValueByKeysPath(array $array, array $keys) + { + $value = null; + + if (!empty($array) && self::issetRecursive($array, $keys)) { + foreach ($keys as $key) { + $value = $array[$key]; + array_shift($keys); + + if (is_array($value) && !empty($keys)) { + $value = self::getValueByKeysPath($value, $keys); + } + + break; + } + } + + return $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. + * + * @param array $array The array to check + * @param array $keys Keys, path of keys, to find in given array + * @return bool + * + * Examples: + * a) $array + * [ + * 'some key' => [ + * 'another some key' => [ + * 'yet another key' => 123, + * ], + * 'some different key' => 456, + * ] + * ] + * + * b) $keys + * [ + * 'some key', + * 'another some key', + * 'yet another key', + * ] + */ + public static function issetRecursive(array $array, array $keys) + { + $isset = false; + + if (!empty($array)) { + foreach ($keys as $key) { + $isset = isset($array[$key]); + + if ($isset) { + $newArray = $array[$key]; + array_shift($keys); + + if (is_array($newArray) && !empty($keys)) { + $isset = self::issetRecursive($newArray, $keys); + } + } + + break; + } + } + + return $isset; + } + + /** + * 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 array|null + */ + public static function getAllValuesOfKey(array $array, $key) + { + 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)) { + $values = array_merge($values, $recursiveValues); + } + } + } + + return $values; + } + + /** + * Sets positions for each element / child of given array and returns the array + * + * Position for the 1st element / child of a parent is set to 1 and incremented for the next element and + * so on. Each parent is treated as separate array, so its elements are treated as positioned at 1st level. + * + * @param array $array The array which should has values of position for each element + * @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 + */ + public static function setPositions(array $array, $keyName = self::POSITION_KEY_NAME, $startPosition = null) + { + if (!empty($array)) { + $childPosition = 1; + + if (null !== $startPosition) { + $array[$keyName] = $startPosition; + } + + foreach ($array as &$value) { + if (is_array($value)) { + $value = self::setPositions($value, $keyName, $childPosition); + ++$childPosition; + } + } + } + + 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) + { + $effect = $array; + + if (!empty($array)) { + $effect = []; + + foreach ($array as $key => $value) { + if (is_array($value)) { + $effect[$key] = self::trimRecursive($value); + continue; + } + + if (is_string($value)) { + $value = trim($value); + } + + $effect[$key] = $value; + } + } + + return $effect; + } + + /** + * Sorts an array by keys given in second array as values. + * Keys which are not in array with order are pushed after sorted elements. + * + * Example: + * - array to sort: + * + * array( + * 'lorem' => array( + * 'ipsum' + * ), + * 'dolor' => array( + * 'sit', + * 'amet' + * ), + * 'neque' => 'neque' + * ) + * + * - keys order: + * + * array( + * 'dolor', + * 'lorem' + * ) + * + * - the result: + * + * array( + * 'dolor' => array( + * 'sit', + * 'amet' + * ), + * 'lorem' => array( + * 'ipsum' + * ), + * 'neque' => 'neque' // <-- the rest, values of other keys + * ) + * + * + * @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 + */ + public static function sortByCustomKeysOrder(array $array, array $keysOrder) + { + /* + * No elements? + * Nothing to do + */ + if (empty($array)) { + return null; + } + + $ordered = []; + + /* + * 1st iteration: + * Get elements in proper / required order + */ + if (!empty($keysOrder)) { + foreach ($keysOrder as $key) { + if (isset($array[$key])) { + $ordered[$key] = $array[$key]; + unset($array[$key]); + } + } + } + + /* + * 2nd iteration: + * Get the rest of elements + */ + if (!empty($array)) { + foreach ($array as $key => $element) { + $ordered[$key] = $element; + } + } + + return $ordered; + } + + /** + * 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 string + */ + public static function implodeSmart(array $array, $separator) + { + /* + * No elements? + * Nothing to do + */ + if (empty($array)) { + return ''; + } + + 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, strlen($element) - 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); + } + } else { + /* + * 2nd array hasn't key from 1st array? + * OR + * Key exists in both, 1st and 2nd array, but values are different? + */ + if (!$array2HasKey || ($array2HasKey && $value != $array2[$key])) { + $difference = $value; + } + } + + if (null !== $difference) { + $effect[] = $difference; + } + } else { + /* + * The key exists in 2nd array? + */ + if ($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; + } else { + /* + * Value is different than in 2nd array? + * OKay, I've got difference + */ + if ($value != $array2[$key]) { + $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 + */ + public static function incrementIndexes(array $array, $startIndex = null, $incrementStep = 1) + { + if (!empty($array)) { + $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; + } + } + } + } + + return $array; + } + + /** + * 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 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); + } + + /** + * 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 + */ + if (empty($array)) { + return null; + } + + return count($array) !== count($array, COUNT_RECURSIVE); + } + + /** + * 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) + { + $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; + } + + /** + * Returns neighbour (next or previous element) for given element + * + * @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 + */ + private static function getNeighbour(array $array, $element, $next = true) + { + $noNext = $next && self::isLastElement($array, $element); + $noPrevious = !$next && self::isFirstElement($array, $element); + + /* + * No elements? + * OR + * Given element does not exist in given array? + * OR + * Next neighbour should be returned and given element is last? + * OR + * Previous neighbour should be returned and given element is first? + * + * Nothing to do + */ + if (empty($array) || !in_array($element, $array) || $noNext || $noPrevious) { + return null; + } + + $neighbourKey = null; + $keys = array_keys($array); + $elementKey = self::getIndexOf($array, $element); + $indexOfKey = self::getIndexOf($keys, $elementKey); + + /* + * Index of element or of element's key is unknown? + * Probably the element does not exist in given array, so... nothing to do + */ + if (null === $elementKey || null === $indexOfKey) { + return null; + } + + /* + * Looking for key of the neighbour (next or previous element) + */ + if ($next) { + ++$indexOfKey; + } else { + --$indexOfKey; + } + + /* + * Let's prepare key of the neighbour and... + * ...we've got the neighbour :) + */ + $neighbourKey = $keys[$indexOfKey]; + + return $array[$neighbourKey]; + } +} diff --git a/src/Common/Utilities/Date.php b/src/Common/Utilities/Date.php new file mode 100644 index 0000000..8964691 --- /dev/null +++ b/src/Common/Utilities/Date.php @@ -0,0 +1,714 @@ + + * @copyright Meritoo.pl + */ +class Date +{ + /** + * The 'days' unit of date difference. + * Difference between dates in days. + * + * @var string + */ + const DATE_DIFFERENCE_UNIT_DAYS = 'days'; + + /** + * The 'hours' unit of date difference. + * Difference between dates in hours. + * + * @var string + */ + const DATE_DIFFERENCE_UNIT_HOURS = 'hours'; + + /** + * The 'minutes' unit of date difference. + * Difference between dates in minutes. + * + * @var string + */ + const DATE_DIFFERENCE_UNIT_MINUTES = 'minutes'; + + /** + * The 'months' unit of date difference. + * Difference between dates in months. + * + * @var string + */ + const DATE_DIFFERENCE_UNIT_MONTHS = 'months'; + + /** + * The 'years' unit of date difference. + * Difference between dates in years. + * + * @var string + */ + const DATE_DIFFERENCE_UNIT_YEARS = 'years'; + + /** + * Returns start and end date for given period. + * The dates are returned in an array with indexes 'start' and 'end'. + * + * @param int $period The period, type of period. One of DatePeriod class constants, e.g. DatePeriod::LAST_WEEK. + * @return DatePeriod + */ + public static function getDatesForPeriod($period) + { + $datePeriod = null; + + if (DatePeriod::isCorrectPeriod($period)) { + $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); + + $dateStart = $lastMonth->getEndDate(); + $dateStart->add(new DateInterval('P1D')); + + $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(); + + if (DatePeriod::LAST_YEAR == $period || DatePeriod::NEXT_YEAR == $period) { + $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; + } + + if (null !== $dateStart && null !== $dateEnd) { + $dateStart->setTime(0, 0, 0); + $dateEnd->setTime(23, 59, 59); + + $datePeriod = new DatePeriod($dateStart, $dateEnd); + } + } + + return $datePeriod; + } + + /** + * 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 + */ + public static function generateRandomTime($format = 'H:i:s') + { + $dateTime = new DateTime(); + + /* + * Format si empty or is incorrect? + * Nothing to do + */ + if (empty($format) || $dateTime->format($format) === $format) { + return null; + } + + $hours = []; + $minutes = []; + $seconds = []; + + for ($i = 1; $i <= 23; ++$i) { + $hours[] = $i; + } + + for ($i = 1; $i <= 59; ++$i) { + $minutes[] = $i; + } + + for ($i = 1; $i <= 59; ++$i) { + $seconds[] = $i; + } + + /* + * Prepare random time (hour, minute and second) + */ + $hour = $hours[array_rand($hours)]; + $minute = $minutes[array_rand($minutes)]; + $second = $seconds[array_rand($seconds)]; + + return $dateTime + ->setTime($hour, $minute, $second) + ->format($format); + } + + /** + * Returns current day of week + * + * @return int + */ + public static function getCurrentDayOfWeek() + { + $now = new DateTime(); + + $year = $now->format('Y'); + $month = $now->format('m'); + $day = $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 + * + * @return int + * @throws UnknownDatePartTypeException + */ + public static function getDayOfWeek($year, $month, $day) + { + $year = (int)$year; + $month = (int)$month; + $day = (int)$day; + + /* + * Oops, incorrect year + */ + if ($year <= 0) { + throw new UnknownDatePartTypeException(DatePartType::YEAR, $year); + } + + /* + * Oops, incorrect month + */ + if ($month < 1 || $month > 12) { + throw new UnknownDatePartTypeException(DatePartType::MONTH, $month); + } + + /* + * Oops, incorrect day + */ + if ($day < 1 || $day > 31) { + throw new UnknownDatePartTypeException(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() + { + $now = new DateTime(); + + $year = $now->format('Y'); + $month = $now->format('m'); + $day = $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. + * + * The difference is calculated in units based on the 3rd argument or all available unit of date difference + * (defined as DATE_DIFFERENCE_UNIT_* constants of this class). + * + * The difference is also whole / complete value for given unit instead of relative value as may be received by + * DateTime::diff() method, e.g.: + * - 2 days, 50 hours + * instead of + * - 2 days, 2 hours + * + * 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 int $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. + * @return array|int + */ + public static function getDateDifference($dateStart, $dateEnd, $differenceUnit = null) + { + $validDateStart = self::isValidDate($dateStart, true); + $validDateEnd = self::isValidDate($dateEnd, true); + + /* + * The start or end date is unknown? + * or + * The start or end date is not valid date? + * + * Nothing to do + */ + if (empty($dateStart) || empty($dateEnd) || !$validDateStart || !$validDateEnd) { + return null; + } + + $dateStart = self::getDateTime($dateStart, true); + $dateEnd = self::getDateTime($dateEnd, true); + + $difference = []; + $dateDiff = $dateEnd->getTimestamp() - $dateStart->getTimestamp(); + + $daysInSeconds = 0; + $hoursInSeconds = 0; + + $hourSeconds = 60 * 60; + $daySeconds = $hourSeconds * 24; + + /* + * These units are related, because while calculating difference in the lowest unit, difference in the + * highest unit is required, e.g. while calculating hours I have to know difference in days + */ + $relatedUnits = [ + self::DATE_DIFFERENCE_UNIT_DAYS, + self::DATE_DIFFERENCE_UNIT_HOURS, + self::DATE_DIFFERENCE_UNIT_MINUTES, + ]; + + if (null === $differenceUnit || self::DATE_DIFFERENCE_UNIT_YEARS == $differenceUnit) { + $diff = $dateEnd->diff($dateStart); + + /* + * Difference between dates in years should be returned only? + */ + if (self::DATE_DIFFERENCE_UNIT_YEARS == $differenceUnit) { + return $diff->y; + } + + $difference[self::DATE_DIFFERENCE_UNIT_YEARS] = $diff->y; + } + + if (null === $differenceUnit || self::DATE_DIFFERENCE_UNIT_MONTHS == $differenceUnit) { + $diff = $dateEnd->diff($dateStart); + + /* + * Difference between dates in months should be returned only? + */ + if (self::DATE_DIFFERENCE_UNIT_MONTHS == $differenceUnit) { + return $diff->m; + } + + $difference[self::DATE_DIFFERENCE_UNIT_MONTHS] = $diff->m; + } + + if (null === $differenceUnit || in_array($differenceUnit, $relatedUnits)) { + $days = (int)floor($dateDiff / $daySeconds); + + /* + * Difference between dates in days should be returned only? + */ + if (self::DATE_DIFFERENCE_UNIT_DAYS == $differenceUnit) { + return $days; + } + + /* + * All units should be returned? + */ + if (null === $differenceUnit) { + $difference[self::DATE_DIFFERENCE_UNIT_DAYS] = $days; + } + + /* + * Calculation for later usage + */ + $daysInSeconds = $days * $daySeconds; + } + + if (null === $differenceUnit || in_array($differenceUnit, $relatedUnits)) { + $hours = (int)floor(($dateDiff - $daysInSeconds) / $hourSeconds); + + /* + * Difference between dates in hours should be returned only? + */ + if (self::DATE_DIFFERENCE_UNIT_HOURS == $differenceUnit) { + return $hours; + } + + /* + * All units should be returned? + */ + if (null === $differenceUnit) { + $difference[self::DATE_DIFFERENCE_UNIT_HOURS] = $hours; + } + + /* + * Calculation for later usage + */ + $hoursInSeconds = $hours * $hourSeconds; + } + + if (null === $differenceUnit || self::DATE_DIFFERENCE_UNIT_MINUTES == $differenceUnit) { + $minutes = (int)floor(($dateDiff - $daysInSeconds - $hoursInSeconds) / 60); + + /* + * Difference between dates in minutes should be returned only? + */ + if (self::DATE_DIFFERENCE_UNIT_MINUTES == $differenceUnit) { + return $minutes; + } + + $difference[self::DATE_DIFFERENCE_UNIT_MINUTES] = $minutes; + } + + 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. + * @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 The start date. Start of the random date. + * @param int $start (optional) Start of random partition + * @param int $end (optional) End of random partition + * @param string $intervalTemplate (optional) Template used to build date interval. The placeholder is replaced + * with next, iterated value. + * @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, rand($start, $end))); + + return $randomDate->add($randomInterval); + } + + /** + * Returns the DateTime object for given value. + * If the DateTime object cannot be created, false is returned. + * + * @param mixed $value The value which maybe is a date + * @param bool $allowCompoundFormats (optional) If is set to true, the compound formats used to create an + * instance of DateTime class are allowed (e.g. "now", "last day of next + * month", "yyyy"). Otherwise - not and every incorrect value is refused. + * @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. + * @return DateTime|bool + */ + public static function getDateTime($value, $allowCompoundFormats = false, $dateFormat = 'Y-m-d') + { + /* + * Empty value? + * Nothing to do :) + */ + if (empty($value)) { + return false; + } + + /* + * Instance of DateTime class? + * Nothing to do :) + */ + if ($value instanceof DateTime) { + return $value; + } + + try { + try { + /* + * Pass the value to the constructor. Maybe it's one of the allowed relative formats. + * Examples: "now", "last day of next month" + */ + $date = new DateTime($value); + + /* + * Instance of the DateTime class was created. + * Let's verify if given value is really proper date. + */ + $dateFromFormat = DateTime::createFromFormat($dateFormat, $value); + + if (false === $dateFromFormat) { + /* + * Nothing to do more, because: + * a) instance of the DateTime was created + * and + * b) if createFromFormat() method failed, given value is one of the allowed relative formats + * ("now", "last day of next month") + * and... + */ + if ($allowCompoundFormats) { + /* + * ...and + * c) it's not an integer, e.g. not 10 or 100 or 1000 + */ + if (!is_numeric($value)) { + return $date; + } + } else { + $specialFormats = [ + 'now', + ]; + + /* + * ...and + * c) it's special compound format that contains characters that each may be used by + * DateTime::format() method and it raises problem while trying to verify the value at the end + * of this method: + * + * (new DateTime())->format($value); + * + * So, I have to refuse those special compound formats if they are not explicitly declared as + * compound (2nd argument of this method, set to false by default) + */ + if (in_array($value, $specialFormats)) { + return false; + } + } + } /* + * 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; + } + } + } catch (\Exception $exception) { + if (!$allowCompoundFormats) { + return false; + } + } + + /* + * Does the value is a string that may be used to format date? + * Example: "Y-m-d" + */ + $dateString = (new DateTime())->format($value); + + if ($dateString != $value) { + return new DateTime($dateString); + } + } catch (\Exception $exception) { + } + + return false; + } + + /** + * Returns information if given value is valid date + * + * @param mixed $value The value which maybe is a date + * @param bool $allowCompoundFormats (optional) If is set to true, the compound formats used to create an + * instance of DateTime class are allowed (e.g. "now", "last day of next + * month", "yyyy"). Otherwise - not and every incorrect value is refused. + * @return bool + */ + public static function isValidDate($value, $allowCompoundFormats = false) + { + return self::getDateTime($value, $allowCompoundFormats) instanceof DateTime; + } + + /** + * Returns information if given format of date is valid + * + * @param string $format The validated format of date + * @return bool + */ + public static function isValidDateFormat($format) + { + 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 + */ + if ($formatted == $format) { + return false; + } + + /* + * Validate the format used to create the datetime + */ + $fromFormat = DateTime::createFromFormat($format, $formatted); + + /* + * It's instance of DateTime? + * The format is valid + */ + if ($fromFormat instanceof DateTime) { + return true; + } + + return $fromFormat instanceof DateTime; + } +} diff --git a/src/Common/Utilities/DatePeriod.php b/src/Common/Utilities/DatePeriod.php new file mode 100644 index 0000000..3a87d73 --- /dev/null +++ b/src/Common/Utilities/DatePeriod.php @@ -0,0 +1,195 @@ + + * @copyright Meritoo.pl + */ +class DatePeriod +{ + /** + * The period constant: last month + * + * @var int + */ + const LAST_MONTH = 4; + + /** + * The period constant: last week + * + * @var int + */ + const LAST_WEEK = 1; + + /** + * The period constant: last year + * + * @var int + */ + const LAST_YEAR = 7; + + /** + * The period constant: next month + * + * @var int + */ + const NEXT_MONTH = 6; + + /** + * The period constant: next week + * + * @var int + */ + const NEXT_WEEK = 3; + + /** + * The period constant: next year + * + * @var int + */ + const NEXT_YEAR = 9; + + /** + * The period constant: this month + * + * @var int + */ + const THIS_MONTH = 5; + + /** + * The period constant: this week + * + * @var int + */ + const THIS_WEEK = 2; + + /** + * The period constant: this year + * + * @var int + */ + const THIS_YEAR = 8; + + /** + * The start date of period + * + * @var DateTime + */ + private $startDate; + + /** + * The end date of period + * + * @var DateTime + */ + private $endDate; + + /** + * Class constructor + * + * @param DateTime $startDate (optional) The start date of period + * @param DateTime $endDate (optional) The end date of period + */ + public function __construct(DateTime $startDate = null, DateTime $endDate = null) + { + $this->startDate = $startDate; + $this->endDate = $endDate; + } + + /** + * Returns information if given period is correct + * + * @param int $period The period to verify + * @return bool + */ + public static function isCorrectPeriod($period) + { + return in_array($period, Reflection::getConstants(__CLASS__)); + } + + /** + * 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. + * @return string + */ + public function getFormattedDate($format, $startDate = true) + { + $date = $this->getEndDate(); + + /* + * Start date should be formatted? + */ + if ($startDate) { + $date = $this->getStartDate(); + } + + /* + * Unknown date or format is invalid? + */ + if (null === $date || !Date::isValidDateFormat($format)) { + return ''; + } + + 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 + */ + public function getStartDate() + { + return $this->startDate; + } + + /** + * Sets the start date of period + * + * @param DateTime $startDate (optional) The start date of period + * @return $this + */ + public function setStartDate(DateTime $startDate = null) + { + $this->startDate = $startDate; + + return $this; + } +} diff --git a/src/Common/Utilities/Miscellaneous.php b/src/Common/Utilities/Miscellaneous.php new file mode 100644 index 0000000..b7e5011 --- /dev/null +++ b/src/Common/Utilities/Miscellaneous.php @@ -0,0 +1,1511 @@ + + * @copyright Meritoo.pl + */ +class Miscellaneous +{ + /** + * Returns directory's content (names of directories and files) + * + * @param string $directoryPath Path of directory who content should be returned + * @param bool $recursive (optional) If is set to true, sub-directories are also searched for content. + * 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 + */ + public static function getDirectoryContent($directoryPath, $recursive = false, $maxFilesCount = null) + { + /* + * Path of directory is unknown or does not exist and is not readable? + * Nothing to do + */ + if (empty($directoryPath) || !is_readable($directoryPath)) { + return null; + } + + $files = []; + $startFileName = ''; + + if (self::isFilePath($directoryPath)) { + $startDirectoryPath = dirname($directoryPath); + $startFileName = str_replace($startDirectoryPath, '', $directoryPath); + + $directoryPath = $startDirectoryPath; + } + + $count = 0; + $startFileFound = false; + + if (!Regex::endsWith($directoryPath, '/')) { + $directoryPath .= '/'; + } + + if (Regex::startsWith($startFileName, '/')) { + $startFileName = mb_substr($startFileName, 1); + } + + $directoryContent = scandir($directoryPath); + + if (!empty($directoryContent)) { + foreach ($directoryContent as $fileName) { + if ('.' != $fileName && '..' != $fileName) { + $content = null; + + if (!empty($startFileName) && !$startFileFound) { + if ($fileName == $startFileName) { + $startFileFound = true; + } + + continue; + } + + if ($recursive && is_dir($directoryPath . $fileName)) { + $content = self::getDirectoryContent($directoryPath . $fileName, true, $maxFilesCount - $count); + } + + if (null !== $content) { + $files[$fileName] = $content; + + if (!empty($maxFilesCount)) { + $count += Arrays::getNonArrayElementsCount($content); + } + } else { + $files[] = $fileName; + + if (!empty($maxFilesCount)) { + ++$count; + } + } + + if (!empty($maxFilesCount) && $count >= $maxFilesCount) { + break; + } + } + } + } + + 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) + { + if (self::getFileExtension($fileName, true) != strtolower($extension)) { + return sprintf('%s.%s', $fileName, $extension); + } + + return $fileName; + } + + /** + * Returns file extension + * + * @param string $fileName File name + * @param bool $asLowerCase (optional) if true extension is returned as lowercase string + * @return string + */ + public static function getFileExtension($fileName, $asLowerCase = false) + { + $extension = ''; + $matches = []; + + if (preg_match('|(.+)\.(.+)|', $fileName, $matches)) { + $extension = end($matches); + } + + if ($asLowerCase) { + return strtolower($extension); + } + + return $extension; + } + + /** + * Returns file name from given path + * + * @param string $path A path that contains file name + * @return string + */ + public static function getFileNameFromPath($path) + { + $matches = []; + $pattern = sprintf('|([^\%s.]+\.[A-Za-z0-9.]+)$|', DIRECTORY_SEPARATOR); + + if ((bool)preg_match($pattern, $path, $matches)) { + return $matches[1]; + } + + 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'; // file's name and unique key + + if ($objectId > 0) { + $template .= '-%s'; // object ID + } + + $template .= '.%s'; // file's extension + + /* + * Add some uniqueness + */ + $unique = uniqid(mt_rand(), true); + + /* + * Finally build and return the unique name + */ + if ($objectId > 0) { + return sprintf($template, $withoutExtension, $unique, $objectId, $extension); + } + + return sprintf($template, $withoutExtension, $unique, $extension); + } + + /** + * Returns file name without extension + * + * @param string $fileName The file name + * @return string + */ + public static function getFileNameWithoutExtension($fileName) + { + $matches = []; + + if (is_string($fileName) && (bool)preg_match('|(.+)\.(.+)|', $fileName, $matches)) { + return $matches[1]; + } + + return ''; + } + + /** + * Converts value to non-negative integer (element of the set {0, 1, 2, 3, ...}) + * + * @param mixed $value Value to convert + * @param int $negativeReplacement (optional) Replacement for negative value + * @return int + */ + public static function value2NonNegativeInteger($value, $negativeReplacement = 0) + { + $effect = (int)$value; + + if ($effect < 0) { + return $negativeReplacement; + } + + return $effect; + } + + /** + * Displays variable content as preformatted text (fixed-width font and preserves both spaces and line breaks) + * + * If xdebug php module is loaded, displays variable using var_dump(), otherwise
var_dump()
. + * You can pass as many variables as you wish. + * + * Pass each variable as argument of this function. Amount unlimited. Variables are loaded using the + * func_get_args() function (@see http://pl1.php.net/manual/en/function.func-get-args.php). + */ + public static function variableDump() + { + $xdebugLoaded = self::isPhpModuleLoaded('xdebug'); + + if (!$xdebugLoaded) { + echo '
';
+        }
+
+        $arguments = func_get_args();
+
+        foreach ($arguments as $argument) {
+            var_dump($argument);
+        }
+
+        if (!$xdebugLoaded) {
+            echo '
'; + } + } + + /** + * 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); + } + + /** + * Converts given string characters to latin characters + * + * @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 = '-') + { + if (is_string($string)) { + $string = trim($string); + } + + /* + * Empty value? + * Nothing to do + */ + if (empty($string)) { + return ''; + } + + $converter = Transliterator::create('Latin-ASCII;'); + + /* + * Oops, cannot instantiate converter + * Nothing to do + */ + if (null === $converter) { + return ''; + } + + $converted = $converter->transliterate($string); + + /* + * 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)); + } + } + + /* + * 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 unique string + * + * @param string $prefix (optional) Prefix of the unique string. May be used while generating the unique + * string simultaneously on several hosts at the same microsecond. + * @param bool $hashed (optional) If is set to true, the unique string is hashed additionally. Otherwise - not. + * @return string + */ + public static function getUniqueString($prefix = '', $hashed = false) + { + $unique = uniqid($prefix, true); + + if ($hashed) { + return sha1($unique); + } + + return $unique; + } + + /** + * 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 + * 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 bool $quoteStrings (optional) If is set to true, strings are surrounded with single quote sign + * @return string + * + * Example: + * a) an array of strings to search + * $subject = [ + * 'Lorem ipsum dolor sit amet.', + * 'Etiam ullamcorper. Suspendisse a pellentesque dui, non felis.', + * ]; + * + * b) an array of patterns + * $search = [ + * '|ipsum|', + * '|pellentesque|', + * ]; + * + * c) an array of strings to replace + * $replacement = [ + * 'commodo', + * 'interdum', + * ]; + * + * The result: + * [ + * 'Lorem commodo dolor sit amet.', + * 'Etiam ullamcorper. Suspendisse a interdum dui, non felis.', + * ]; + */ + public static function replace($subject, $search, $replacement, $quoteStrings = false) + { + $effect = $subject; + + $searchIsString = is_string($search); + $searchIsArray = is_array($search); + + /* + * Value to find is neither a string nor an array OR it's an empty string? + * Nothing to do + */ + if ((!$searchIsString && !$searchIsArray) || ($searchIsString && 0 == strlen($search))) { + return $effect; + } + + $replacementIsString = is_string($replacement); + $replacementIsArray = is_array($replacement); + + $bothAreStrings = $searchIsString && $replacementIsString; + $bothAreArrays = $searchIsArray && $replacementIsArray; + + /* + * First step: replace strings, simple operation with strings + */ + if ($searchIsString && $replacementIsString) { + if ($quoteStrings) { + $replacement = '\'' . $replacement . '\''; + } + + $effect = str_replace($search, $replacement, $subject); + } + + /* + * Second step: replace with regular expressions. + * Attention. Searched and replacement value should be the same type: strings or arrays. + */ + if ($effect == $subject && ($bothAreStrings || $bothAreArrays)) { + if ($quoteStrings && $replacementIsString) { + $replacement = '\'' . $replacement . '\''; + } + + /* + * I have to avoid string that contains spaces only, e.g. " ". + * It's required to avoid bug: preg_replace(): Empty regular expression. + */ + $search = trim($search); + + if ($searchIsArray || ($searchIsString && !empty($search))) { + $effect = preg_replace($search, $replacement, $subject); + } + } + + /* + * Third step: complex replace of the replacement defined as an array. + * It may be useful when you want to search for a one string and replace the string with multiple values. + */ + if ($effect == $subject && $searchIsString && $replacementIsArray) { + $subjectIsArray = is_array($subject); + $effect = ''; + + if ($subjectIsArray) { + $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) + */ + foreach ($subject as $subSubject) { + $subEffect = ''; + + $exploded = explode($search, $subSubject); + $explodedCount = count($exploded); + + if ($quoteStrings) { + foreach ($replacement as &$item) { + if (is_string($item)) { + $item = '\'' . $item . '\''; + } + } + + unset($item); + } + + foreach ($exploded as $key => $item) { + $subEffect .= $item; + + /* + * 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]; + } + } + + if ($subjectIsArray) { + $effect[] = $subEffect; + continue; + } + + $effect .= $subEffect; + } + } + + 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_uname('s'); + } + + /** + * Returns part of string preserving words + * + * @param string $text The string / text + * @param int $maxLength Maximum length of given string + * @param string $suffix (optional) The suffix to add at the end of string + * @return string + */ + public static function substringToWord($text, $maxLength, $suffix = '...') + { + $effect = $text; + + $textLength = mb_strlen($text, 'utf-8'); + $suffixLength = mb_strlen($suffix, 'utf-8'); + + $maxLength -= $suffixLength; + + if ($textLength > $maxLength) { + $effect = mb_substr($text, 0, $maxLength, 'utf-8'); + $lastSpacePosition = mb_strrpos($effect, ' ', 'utf-8'); + + if (false !== $lastSpacePosition) { + $effect = mb_substr($effect, 0, $lastSpacePosition, 'utf-8'); + } + + $effect .= $suffix; + } + + return $effect; + } + + /** + * Breaks long text + * + * @param string $text The text to check and break + * @param int $perLine (optional) Characters count per line + * @param string $separator (optional) Separator that is placed beetwen lines + * @param string $encoding (optional) Character encoding. Used by mb_substr(). + * @param int $proportionalAberration (optional) Proportional aberration for chars (percent value) + * @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 ($spacePosition > 0) { + $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; + } + + /** + * 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. Otherwise - directory is removed too. + * @return bool + */ + public static function removeDirectory($directoryPath, $contentOnly = false) + { + if (!file_exists($directoryPath)) { + return true; + } + + if (!is_dir($directoryPath)) { + return unlink($directoryPath); + } + + foreach (scandir($directoryPath) as $item) { + if ('.' == $item || '..' == $item) { + continue; + } + + if (!self::removeDirectory($directoryPath . DIRECTORY_SEPARATOR . $item)) { + return false; + } + } + + 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); + + 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 + * + * Some explanation: + * Function lcfirst() is available for PHP >= 5.30, so I wrote my own function that lowercases first character of + * the string. + */ + 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); + } + + if (function_exists('lcfirst')) { + $effect = lcfirst($effect); + } else { + $first = mb_strtolower($effect[0]); + $rest = mb_substr($effect, 1); + + $effect = $first . $rest; + } + + return $effect; + } + + /** + * 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 + * @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 uppercaseFirst($text, $restLowercase = null) + { + if (empty($text)) { + return ''; + } + + $effect = $text; + + if ($restLowercase) { + $effect = mb_strtolower($effect); + } elseif (false === $restLowercase) { + $effect = mb_strtoupper($effect); + } + + if (function_exists('ucfirst')) { + $effect = ucfirst($effect); + } else { + $first = mb_strtoupper($effect[0]); + $rest = mb_substr($effect, 1); + + $effect = $first . $rest; + } + + return $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; + } + + /** + * 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 / pow(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)); + } + + 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]; + + if (!ini_get('magic_quotes_gpc')) { + $value = addslashes($value); + } + } + } + + return $value; + } + + /** + * Returns a CURL response with parsed HTTP headers as array with "headers", "cookies" and "content" keys + * + * The headers and cookies are parsed and returned as an array, and an array of Cookie objects. Returned array looks + * like this example: + * + * [ + * 'headers' => [ + * 'Content-Type' => 'text/html; charset=UTF-8', + * ... + * ], + * 'cookies' => [ + * new Symfony\Component\HttpFoundation\Cookie(), + * new Symfony\Component\HttpFoundation\Cookie(), + * ... + * ], + * 'content' => '...' + * ] + * + * + * If you want to attach HTTP headers into response content by CURL you need to set "CURLOPT_HEADER" option + * to "true". To read exact length of HTTP headers from CURL you can use "curl_getinfo()" function + * and read "CURLINFO_HEADER_SIZE" option. + * + * @param string $response the full content of response, including HTTP headers + * @param int $headerSize The length of HTTP headers in content + * @return array + */ + public static function getCurlResponseWithHeaders($response, $headerSize) + { + $headerContent = mb_substr($response, 0, $headerSize); + $content = mb_substr($response, $headerSize); + $headers = []; + $cookies = []; + + /* + * Let's transform headers content into two arrays: headers and cookies + */ + foreach (explode("\r\n", $headerContent) as $i => $line) { + /* + * First line is only HTTP status and is unneeded so skip it + */ + if (0 === $i) { + continue; + } + + if (Regex::contains($line, ':')) { + list($key, $value) = explode(': ', $line); + + /* + * If the header is a "set-cookie" let's save it to "cookies" array + */ + if ('Set-Cookie' === $key) { + $cookieParameters = explode(';', $value); + + $name = ''; + $value = ''; + $expire = 0; + $path = '/'; + $domain = null; + $secure = false; + $httpOnly = true; + + foreach ($cookieParameters as $j => $parameter) { + $param = explode('=', $parameter); + + /* + * First parameter will be always a cookie name and it's value. It is not needed to run + * further actions for them, so save the values and move to next parameter. + */ + if (0 === $j) { + $name = trim($param[0]); + $value = trim($param[1]); + continue; + } + + /* + * Now there would be the rest of cookie parameters, names of params are sent different way so + * I need to lowercase the names and remove unneeded spaces. + */ + $paramName = mb_strtolower(trim($param[0])); + $paramValue = true; + + /* + * Some parameters don't have value e.g. "secure", but the value for them if they're specified + * is "true". Otherwise - just read a value for parameter if exists. + */ + if (array_key_exists(1, $param)) { + $paramValue = trim($param[1]); + } + + switch ($paramName) { + case 'expires': + $expire = $paramValue; + break; + case 'path': + $path = $paramValue; + break; + case 'domain': + $domain = $paramValue; + break; + case 'secure': + $secure = $paramValue; + break; + case 'httponly': + $httpOnly = $paramValue; + break; + } + } + + /* + * Create new Cookie object and add it to "cookies" array. + * I must skip to next header as cookies shouldn't be saved in "headers" array. + */ + $cookies[] = new Cookie($name, $value, $expire, $path, $domain, $secure, $httpOnly); + continue; + } + + /* + * Save response header which is not a cookie + */ + $headers[$key] = $value; + } + } + + return [ + 'headers' => $headers, + 'cookies' => $cookies, + 'content' => $content, + ]; + } + + /** + * 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 = $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, $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. + */ + $color = Regex::getValidColorHexValue($color); + + /* + * Grab color's components + */ + $red = hexdec(substr($color, 0, 2)); + $green = hexdec(substr($color, 2, 2)); + $blue = hexdec(substr($color, 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/Common/Utilities/Reflection.php b/src/Common/Utilities/Reflection.php new file mode 100644 index 0000000..4e94900 --- /dev/null +++ b/src/Common/Utilities/Reflection.php @@ -0,0 +1,668 @@ + + * @copyright Meritoo.pl + */ +class Reflection +{ + /** + * 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, $withoutInheritance = false) + { + $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; + } + + /** + * 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 constant from all constants of given class / object. + * Values of constants should be integers. + * + * @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)) { + return null; + } + + $maxNumber = 0; + + foreach ($constants as $constant) { + if (is_numeric($constant) && $constant > $maxNumber) { + $maxNumber = $constant; + } + } + + return $maxNumber; + } + + /** + * 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 + * + * Krzysztof Niziol + * 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) { + /* + * 2nd try: + * Look for the get / has / is methods + */ + $class = new ReflectionObject($object); + $valueFound = false; + + if ($class->hasProperty($property) || $force) { + $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 (!$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); + } + } + } + + 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; + } + + /** + * Returns a class name for given source + * + * @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 + */ + public static function getClassName($source, $withoutNamespace = false) + { + /* + * First argument is not proper source of class? + * Nothing to do + */ + if (empty($source) || (!is_array($source) && !is_object($source) && !is_string($source))) { + return null; + } + + $name = ''; + + /* + * An array of objects was provided? + * Let's use first of them + */ + if (is_array($source)) { + $source = Arrays::getFirstElement($source); + } + + /* + * Let's prepare name of class + */ + if (is_object($source)) { + $name = get_class($source); + } elseif (is_string($source) && (class_exists($source) || trait_exists($source))) { + $name = $source; + } + + /* + * Name of class is still unknown? + * Nothing to do + */ + if (empty($name)) { + return null; + } + + /* + * Namespace is not required? + * Let's return name of class only + */ + if ($withoutNamespace) { + $classOnly = Miscellaneous::getLastElementOfString($name, '\\'); + + if (null !== $classOnly) { + $name = $classOnly; + } + + return $name; + } + + return ClassUtils::getRealClass($name); + } + + /** + * Returns namespace of class for given source + * + * @param array|object|string $source An array of objects, namespaces, object or namespace + * @return string + */ + public static function getClassNamespace($source) + { + $fullClassName = self::getClassName($source); + + if (empty($fullClassName)) { + return ''; + } + + $className = self::getClassName($source, true); + + if ($className == $fullClassName) { + return $className; + } + + return Miscellaneous::getStringWithoutLastElement($fullClassName, '\\'); + } + + /** + * 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, $interface) + { + $className = self::getClassName($source); + $interfaces = class_implements($className); + + return in_array($interface, $interfaces); + } + + /** + * 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)) { + return in_array($parentClassName, $parents); + } + + return false; + } + + /** + * 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 array|ReflectionProperty + */ + public static function getProperties($source, $filter = null, $includeParents = false) + { + $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 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 ReflectionClass|bool + */ + public static function getParentClass($source) + { + $className = self::getClassName($source); + $reflection = new ReflectionClass($className); + + 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. + * @return array|null + * @throws CannotResolveClassNameException + */ + public static function getChildClasses($class) + { + $allClasses = get_declared_classes(); + + /* + * No classes? + * Nothing to do + */ + if (empty($allClasses)) { + return null; + } + + $className = self::getClassName($class); + + /* + * Oops, cannot resolve class + */ + if (null === $className) { + throw new CannotResolveClassNameException($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)) { + continue; + } + + $childClasses[] = $realClass; + } + } + + return $childClasses; + } + + /** + * Returns namespace of one child class which extends given class. + * Extended class should has only one child class. + * + * @param array|object|string $parentClass Class who child class should be returned. An array of objects, + * namespaces, object or namespace. + * @return mixed + * + * @throws MissingChildClassesException + * @throws TooManyChildClassesException + */ + public static function getOneChildClass($parentClass) + { + $childClasses = self::getChildClasses($parentClass); + + /* + * No child classes? + * Oops, the base / parent class hasn't child class + */ + if (empty($childClasses)) { + throw new MissingChildClassesException($parentClass); + } + + /* + * More than 1 child class? + * Oops, the base / parent class has too many child classes + */ + if (count($childClasses) > 1) { + throw new TooManyChildClassesException($parentClass, $childClasses); + } + + return trim($childClasses[0]); + } + + /** + * 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. + * By default all properties are allowed / processed. + * @return null|ReflectionProperty + */ + public static function getProperty($class, $property, $filter = null) + { + $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; + } + } + } + + return null; + } + + /** + * Returns information if given class / object uses / implements given trait + * + * @param array|object|string $class An array of objects, namespaces, object or namespace + * @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. + * @return bool|null + * @throws CannotResolveClassNameException + */ + public static function usesTrait($class, $trait, $verifyParents = false) + { + $className = self::getClassName($class); + $traitName = self::getClassName($trait); + + /* + * Oops, cannot resolve class + */ + if (empty($className)) { + throw new CannotResolveClassNameException($class); + } + + /* + * Oops, cannot resolve trait + */ + if (empty($traitName)) { + throw new CannotResolveClassNameException($class, false); + } + + $reflection = new ReflectionClass($className); + $traitsNames = $reflection->getTraitNames(); + + $uses = in_array($traitName, $traitsNames); + + if (!$uses && $verifyParents) { + $parentClassName = self::getParentClassName($className); + + if (null !== $parentClassName) { + return self::usesTrait($parentClassName, $trait, true); + } + } + + return $uses; + } + + /** + * 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 string|null + */ + public static function getParentClassName($class) + { + $className = self::getClassName($class); + $reflection = new ReflectionClass($className); + $parentClass = $reflection->getParentClass(); + + if (null === $parentClass || false === $parentClass) { + return null; + } + + return $parentClass->getName(); + } +} diff --git a/src/Common/Utilities/Regex.php b/src/Common/Utilities/Regex.php new file mode 100644 index 0000000..85cf1c0 --- /dev/null +++ b/src/Common/Utilities/Regex.php @@ -0,0 +1,726 @@ + + * @copyright Meritoo.pl + */ +class Regex +{ + /** + * Patterns used to validate / verify values + * + * @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]+;/', + 'fileName' => '/.+\.\w+$/', + 'isQuoted' => '/^[\'"]{1}.+[\'"]{1}$/', + 'windowsBasedPath' => '/^[A-Z]{1}:\\\.*$/', + 'money' => '/^[-+]?\d+([\.,]{1}\d*)?$/', + 'color' => '/^[a-f0-9]{6}$/i', + ]; + + /** + * Returns information if given e-mail address is valid + * + * @param string $email E-mail address to validate / verify + * @return bool + * + * Examples: + * a) valid e-mails: + * - ni@g-m.pl + * - ni@gm.pl + * - ni@g_m.pl + * b) invalid e-mails: + * - ni@g-m.p + * - n@g-m.pl + */ + public static function isValidEmail($email) + { + $pattern = self::getEmailPattern(); + + return (bool)preg_match($pattern, $email); + } + + /** + * Returns information if given tax ID (in polish: NIP) is valid + * + * @param string $taxidString Tax ID (NIP) string + * @return bool + */ + public static function isValidTaxid($taxidString) + { + if (!empty($taxidString)) { + $weights = [ + 6, + 5, + 7, + 2, + 3, + 4, + 5, + 6, + 7, + ]; + $taxid = preg_replace('/[\s-]/', '', $taxidString); + $sum = 0; + + if (10 == strlen($taxid) && is_numeric($taxid)) { + for ($x = 0; $x <= 8; ++$x) { + $sum += $taxid[$x] * $weights[$x]; + } + + if ((($sum % 11) % 10) == $taxid[9]) { + return true; + } + } + } + + return false; + } + + /** + * Returns information if given url address is valid + * + * @param string $url The url to validate / verify + * @param bool $requireProtocol (optional) If is set to true, the protocol is required to be passed in the url. + * Otherwise - not. + * @return bool + */ + public static function isValidUrl($url, $requireProtocol = false) + { + $pattern = self::getUrlPattern($requireProtocol); + + return (bool)preg_match($pattern, $url); + } + + /** + * Returns information if given phone number is valid + * + * @param string $phoneNumber The phone number to validate / verify + * @return bool + */ + public static function isValidPhoneNumber($phoneNumber) + { + $pattern = self::getPhoneNumberPattern(); + + return (bool)preg_match($pattern, $phoneNumber); + } + + /** + * Returns array values that matches given pattern (or values that keys matches) + * + * @param string $pattern Pattern to match + * @param array $dataArray The array + * @param bool $itsKeyPattern (optional) If is set to true, keys are checks if they match pattern. Otherwise - + * values are checks. + * @return array + */ + public static function getArrayValuesByPattern($pattern, $dataArray, $itsKeyPattern = false) + { + if ($itsKeyPattern) { + $effect = []; + + if (!empty($dataArray)) { + $matches = []; + + foreach ($dataArray as $key => $value) { + if (preg_match($pattern, $key, $matches)) { + $effect[$key] = $value; + } + } + } + + return $effect; + } + + return preg_grep($pattern, $dataArray); + } + + /** + * 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 array that should be filtered + * @param string $arrayColumnKey Column name + * @param string $filterExpression Filter expression, e.g. '== 2' or '!= \'home\'' + * @param bool $itsRegularExpression (optional) If is set to true, means that filter expression is a + * regular expression + * @return array + */ + public static function arrayFilter($array, $arrayColumnKey, $filterExpression, $itsRegularExpression = false) + { + $effect = []; + + if (!empty($array)) { + $effect = $array; + + foreach ($effect as $key => &$item) { + if (isset($item[$arrayColumnKey])) { + $value = $item[$arrayColumnKey]; + + if ($itsRegularExpression) { + $matches = []; + $pattern = '|' . $filterExpression . '|'; + $matchesCount = preg_match($pattern, $value, $matches); + + $remove = 0 == $matchesCount; + } else { + if ('' == $value) { + $value = '\'\''; + } elseif (is_string($value)) { + $value = '\'' . $value . '\''; + } + + eval('$isTrue = ' . $value . $filterExpression . ';'); + + /* @var bool $isTrue */ + $remove = !$isTrue; + } + + if ($remove) { + unset($effect[$key]); + } + } + } + } + + return $effect; + } + + /** + * Perform regular expression match with many given patterns. + * Returns information if given $subject matches one or all given $patterns. + * + * @param array|string $patterns The patterns to match + * @param string $subject The string to check + * @param bool $mustAllMatch (optional) If is set to true, $subject must match all $patterns. Otherwise - + * not. + * @return bool + */ + public static function pregMultiMatch($patterns, $subject, $mustAllMatch = false) + { + $effect = false; + $patterns = Arrays::makeArray($patterns); + + if (!empty($patterns)) { + if ($mustAllMatch) { + $effect = true; + } + + foreach ($patterns as $pattern) { + $matches = []; + $matched = (bool)preg_match_all($pattern, $subject, $matches); + + if ($mustAllMatch) { + $effect = $effect && $matched; + } else { + if ($matched) { + $effect = $matched; + break; + } + } + } + } + + 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 + * + * @param string $string String to check + * @param string $beginning The beginning of string, one or more characters + * @return bool + */ + public static function startsWith($string, $beginning) + { + if (!empty($string) && !empty($beginning)) { + if (1 == strlen($beginning) && !self::isLetterOrDigit($beginning)) { + $beginning = '\\' . $beginning; + } + + $pattern = sprintf('|^%s|', $beginning); + + 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 + * + * @param string $string String 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), system's + * separator is used. + * @return bool + */ + public static function startsWithDirectorySeparator($string, $separator = '') + { + if (empty($separator)) { + $separator = DIRECTORY_SEPARATOR; + } + + 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('/[^0-9]/', '', $nip); + + $invalidNips = [ + '1234567890', + '0000000000', + ]; + + if (!preg_match('/^[0-9]{10}$/', $nip) || in_array($nip, $invalidNips)) { + 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 == $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) + { + $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. + * @return string|bool + * + * @throws IncorrectColorHexLengthException + * @throws InvalidColorHexValueException + */ + public static function getValidColorHexValue($color, $throwException = true) + { + $color = Miscellaneous::replace($color, '/#/', ''); + $length = strlen($color); + + if (3 === $length) { + $color = Miscellaneous::replace($color, '/(.)(.)(.)/', '$1$1$2$2$3$3'); + } else { + if (6 !== $length) { + if ($throwException) { + throw new IncorrectColorHexLengthException($color); + } + + return false; + } + } + + $pattern = self::$patterns['color']; + $match = (bool)preg_match($pattern, $color); + + if (!$match) { + if ($throwException) { + throw new InvalidColorHexValueException($color); + } + + return false; + } + + return strtolower($color); + } +} diff --git a/src/Base/Result/BaseItem.php b/src/LimeSurvey/Base/Result/BaseItem.php similarity index 95% rename from src/Base/Result/BaseItem.php rename to src/LimeSurvey/Base/Result/BaseItem.php index 638e89b..02b57cb 100644 --- a/src/Base/Result/BaseItem.php +++ b/src/LimeSurvey/Base/Result/BaseItem.php @@ -16,6 +16,8 @@ namespace Meritoo\LimeSurvey\ApiClient\Base\Result; */ abstract class BaseItem { + const className = 'Meritoo\LimeSurvey\ApiClient\Base\Result\BaseItem'; + /** * Class constructor * diff --git a/src/Client/Client.php b/src/LimeSurvey/Client/Client.php similarity index 98% rename from src/Client/Client.php rename to src/LimeSurvey/Client/Client.php index 0c33fde..c0d9faa 100644 --- a/src/Client/Client.php +++ b/src/LimeSurvey/Client/Client.php @@ -24,6 +24,8 @@ use Meritoo\LimeSurvey\ApiClient\Type\MethodType; */ class Client { + const className = 'Meritoo\LimeSurvey\ApiClient\Client\Client'; + /** * Configuration used while connecting to LimeSurvey's API * diff --git a/src/Configuration/ConnectionConfiguration.php b/src/LimeSurvey/Configuration/ConnectionConfiguration.php similarity index 98% rename from src/Configuration/ConnectionConfiguration.php rename to src/LimeSurvey/Configuration/ConnectionConfiguration.php index 0faa62e..ba9afe5 100644 --- a/src/Configuration/ConnectionConfiguration.php +++ b/src/LimeSurvey/Configuration/ConnectionConfiguration.php @@ -19,6 +19,8 @@ use Meritoo\Common\Utilities\Regex; */ class ConnectionConfiguration { + const className = 'Meritoo\LimeSurvey\ApiClient\Configuration\ConnectionConfiguration'; + /** * Base url. * Protocol & domain. diff --git a/src/Exception/CannotProcessDataException.php b/src/LimeSurvey/Exception/CannotProcessDataException.php similarity index 93% rename from src/Exception/CannotProcessDataException.php rename to src/LimeSurvey/Exception/CannotProcessDataException.php index 3cc6980..2c03579 100644 --- a/src/Exception/CannotProcessDataException.php +++ b/src/LimeSurvey/Exception/CannotProcessDataException.php @@ -16,6 +16,8 @@ namespace Meritoo\LimeSurvey\ApiClient\Exception; */ class CannotProcessDataException extends \Exception { + const className = 'Meritoo\LimeSurvey\ApiClient\Exception\CannotProcessDataException'; + /** * Reason why data cannot be processed, e.g. "Invalid user name or password" * diff --git a/src/Exception/CreateSessionKeyFailedException.php b/src/LimeSurvey/Exception/CreateSessionKeyFailedException.php similarity index 90% rename from src/Exception/CreateSessionKeyFailedException.php rename to src/LimeSurvey/Exception/CreateSessionKeyFailedException.php index e326b72..0fa92a5 100644 --- a/src/Exception/CreateSessionKeyFailedException.php +++ b/src/LimeSurvey/Exception/CreateSessionKeyFailedException.php @@ -18,6 +18,8 @@ use Exception; */ class CreateSessionKeyFailedException extends Exception { + const className = 'Meritoo\LimeSurvey\ApiClient\Exception\CreateSessionKeyFailedException'; + /** * Class constructor * diff --git a/src/Exception/IncorrectClassOfResultItemException.php b/src/LimeSurvey/Exception/IncorrectClassOfResultItemException.php similarity index 90% rename from src/Exception/IncorrectClassOfResultItemException.php rename to src/LimeSurvey/Exception/IncorrectClassOfResultItemException.php index 0851d10..3146329 100644 --- a/src/Exception/IncorrectClassOfResultItemException.php +++ b/src/LimeSurvey/Exception/IncorrectClassOfResultItemException.php @@ -19,6 +19,8 @@ use Meritoo\LimeSurvey\ApiClient\Base\Result\BaseItem; */ class IncorrectClassOfResultItemException extends \Exception { + const className = 'Meritoo\LimeSurvey\ApiClient\Exception\IncorrectClassOfResultItemException'; + /** * Class constructor * @@ -29,7 +31,7 @@ class IncorrectClassOfResultItemException extends \Exception $template = 'Class %s used to create instance of one item of the result should extend %s, but it does not. Did' . ' you forget to use proper base class?'; - $message = sprintf($template, $className, BaseItem::class); + $message = sprintf($template, $className, BaseItem::className); parent::__construct($message); } } diff --git a/src/Exception/InvalidResultOfMethodRunException.php b/src/LimeSurvey/Exception/InvalidResultOfMethodRunException.php similarity index 93% rename from src/Exception/InvalidResultOfMethodRunException.php rename to src/LimeSurvey/Exception/InvalidResultOfMethodRunException.php index 7b639b0..b8e659d 100644 --- a/src/Exception/InvalidResultOfMethodRunException.php +++ b/src/LimeSurvey/Exception/InvalidResultOfMethodRunException.php @@ -19,6 +19,8 @@ use Meritoo\Common\Utilities\Arrays; */ class InvalidResultOfMethodRunException extends Exception { + const className = 'Meritoo\LimeSurvey\ApiClient\Exception\InvalidResultOfMethodRunException'; + /** * Class constructor * diff --git a/src/Exception/MissingParticipantOfSurveyException.php b/src/LimeSurvey/Exception/MissingParticipantOfSurveyException.php similarity index 89% rename from src/Exception/MissingParticipantOfSurveyException.php rename to src/LimeSurvey/Exception/MissingParticipantOfSurveyException.php index 4f58849..ab968b1 100644 --- a/src/Exception/MissingParticipantOfSurveyException.php +++ b/src/LimeSurvey/Exception/MissingParticipantOfSurveyException.php @@ -16,6 +16,8 @@ namespace Meritoo\LimeSurvey\ApiClient\Exception; */ class MissingParticipantOfSurveyException extends \Exception { + const className = 'Meritoo\LimeSurvey\ApiClient\Exception\MissingParticipantOfSurveyException'; + /** * Class constructor * diff --git a/src/Exception/UnknownInstanceOfResultItem.php b/src/LimeSurvey/Exception/UnknownInstanceOfResultItem.php similarity index 90% rename from src/Exception/UnknownInstanceOfResultItem.php rename to src/LimeSurvey/Exception/UnknownInstanceOfResultItem.php index e96c4c3..eeefbf3 100644 --- a/src/Exception/UnknownInstanceOfResultItem.php +++ b/src/LimeSurvey/Exception/UnknownInstanceOfResultItem.php @@ -20,6 +20,8 @@ use Meritoo\LimeSurvey\ApiClient\Result\Processor\ResultProcessor; */ class UnknownInstanceOfResultItem extends Exception { + const className = 'Meritoo\LimeSurvey\ApiClient\Exception\UnknownInstanceOfResultItem'; + /** * Class constructor * @@ -31,7 +33,7 @@ class UnknownInstanceOfResultItem extends Exception $template = 'Class name used to create instance of one item used by result the of \'%s\' LimeSurvey API\'s' . ' method is unknown. Proper class is not mapped in %s::%s() method. Did you forget about this?'; - $message = sprintf($template, $method, ResultProcessor::class, 'getItemClassName'); + $message = sprintf($template, $method, ResultProcessor::className, 'getItemClassName'); parent::__construct($message); } } diff --git a/src/Exception/UnknownMethodException.php b/src/LimeSurvey/Exception/UnknownMethodException.php similarity index 90% rename from src/Exception/UnknownMethodException.php rename to src/LimeSurvey/Exception/UnknownMethodException.php index a207bd7..0bea822 100644 --- a/src/Exception/UnknownMethodException.php +++ b/src/LimeSurvey/Exception/UnknownMethodException.php @@ -19,6 +19,8 @@ use Meritoo\LimeSurvey\ApiClient\Type\MethodType; */ class UnknownMethodException extends UnknownTypeException { + const className = 'Meritoo\LimeSurvey\ApiClient\Exception\UnknownMethodException'; + /** * {@inheritdoc} */ diff --git a/src/Manager/JsonRpcClientManager.php b/src/LimeSurvey/Manager/JsonRpcClientManager.php similarity index 97% rename from src/Manager/JsonRpcClientManager.php rename to src/LimeSurvey/Manager/JsonRpcClientManager.php index c41b8d2..02b3946 100644 --- a/src/Manager/JsonRpcClientManager.php +++ b/src/LimeSurvey/Manager/JsonRpcClientManager.php @@ -17,6 +17,8 @@ use Meritoo\LimeSurvey\ApiClient\Type\MethodType; */ class JsonRpcClientManager { + const className = 'Meritoo\LimeSurvey\ApiClient\Manager\JsonRpcClientManager'; + /** * Configuration used while connecting to LimeSurvey's API * diff --git a/src/Manager/SessionManager.php b/src/LimeSurvey/Manager/SessionManager.php similarity index 97% rename from src/Manager/SessionManager.php rename to src/LimeSurvey/Manager/SessionManager.php index ff03b4b..74f530e 100644 --- a/src/Manager/SessionManager.php +++ b/src/LimeSurvey/Manager/SessionManager.php @@ -13,6 +13,8 @@ use Meritoo\LimeSurvey\ApiClient\Type\SystemMethodType; */ class SessionManager { + const className = 'Meritoo\LimeSurvey\ApiClient\Manager\SessionManager'; + /** * The session key. * Used to authenticate user while connecting to LimeSurvey's API. diff --git a/src/Result/Collection/Participants.php b/src/LimeSurvey/Result/Collection/Participants.php similarity index 97% rename from src/Result/Collection/Participants.php rename to src/LimeSurvey/Result/Collection/Participants.php index 577435f..cee2244 100644 --- a/src/Result/Collection/Participants.php +++ b/src/LimeSurvey/Result/Collection/Participants.php @@ -24,6 +24,8 @@ use Meritoo\LimeSurvey\ApiClient\Result\Item\ParticipantShort; */ class Participants extends Collection { + const className = 'Meritoo\LimeSurvey\ApiClient\Result\Collection\Participants'; + /** * {@inheritdoc} */ diff --git a/src/Result/Collection/Surveys.php b/src/LimeSurvey/Result/Collection/Surveys.php similarity index 94% rename from src/Result/Collection/Surveys.php rename to src/LimeSurvey/Result/Collection/Surveys.php index a9c1337..07fceb1 100644 --- a/src/Result/Collection/Surveys.php +++ b/src/LimeSurvey/Result/Collection/Surveys.php @@ -19,6 +19,8 @@ use Meritoo\LimeSurvey\ApiClient\Result\Item\Survey; */ class Surveys extends Collection { + const className = 'Meritoo\LimeSurvey\ApiClient\Result\Collection\Surveys'; + /** * {@inheritdoc} */ diff --git a/src/Result/Item/Participant.php b/src/LimeSurvey/Result/Item/Participant.php similarity index 98% rename from src/Result/Item/Participant.php rename to src/LimeSurvey/Result/Item/Participant.php index 17f02dd..cf89f4b 100644 --- a/src/Result/Item/Participant.php +++ b/src/LimeSurvey/Result/Item/Participant.php @@ -20,6 +20,8 @@ use Meritoo\LimeSurvey\ApiClient\Base\Result\BaseItem; */ class Participant extends BaseItem { + const className = 'Meritoo\LimeSurvey\ApiClient\Result\Item\Participant'; + /** * ID of the participant * diff --git a/src/Result/Item/ParticipantShort.php b/src/LimeSurvey/Result/Item/ParticipantShort.php similarity index 96% rename from src/Result/Item/ParticipantShort.php rename to src/LimeSurvey/Result/Item/ParticipantShort.php index 67d739a..aa83c33 100644 --- a/src/Result/Item/ParticipantShort.php +++ b/src/LimeSurvey/Result/Item/ParticipantShort.php @@ -18,6 +18,8 @@ use Meritoo\LimeSurvey\ApiClient\Base\Result\BaseItem; */ class ParticipantShort extends BaseItem { + const className = 'Meritoo\LimeSurvey\ApiClient\Result\Item\ParticipantShort'; + /** * ID of the participant * diff --git a/src/Result/Item/Question.php b/src/LimeSurvey/Result/Item/Question.php similarity index 97% rename from src/Result/Item/Question.php rename to src/LimeSurvey/Result/Item/Question.php index 3270998..a7b9c1a 100644 --- a/src/Result/Item/Question.php +++ b/src/LimeSurvey/Result/Item/Question.php @@ -16,6 +16,8 @@ namespace Meritoo\LimeSurvey\ApiClient\Result\Item; */ class Question extends QuestionShort { + const className = 'Meritoo\LimeSurvey\ApiClient\Result\Item\Question'; + /** * Available answers * diff --git a/src/Result/Item/QuestionShort.php b/src/LimeSurvey/Result/Item/QuestionShort.php similarity index 98% rename from src/Result/Item/QuestionShort.php rename to src/LimeSurvey/Result/Item/QuestionShort.php index 107ded3..51c99a1 100644 --- a/src/Result/Item/QuestionShort.php +++ b/src/LimeSurvey/Result/Item/QuestionShort.php @@ -18,6 +18,8 @@ use Meritoo\LimeSurvey\ApiClient\Base\Result\BaseItem; */ class QuestionShort extends BaseItem { + const className = 'Meritoo\LimeSurvey\ApiClient\Result\Item\QuestionShort'; + /** * ID of the question * diff --git a/src/Result/Item/Survey.php b/src/LimeSurvey/Result/Item/Survey.php similarity index 97% rename from src/Result/Item/Survey.php rename to src/LimeSurvey/Result/Item/Survey.php index 0f83073..52265d9 100644 --- a/src/Result/Item/Survey.php +++ b/src/LimeSurvey/Result/Item/Survey.php @@ -20,6 +20,8 @@ use Meritoo\LimeSurvey\ApiClient\Base\Result\BaseItem; */ class Survey extends BaseItem { + const className = 'Meritoo\LimeSurvey\ApiClient\Result\Item\Survey'; + /** * ID of the survey * diff --git a/src/Result/Processor/ResultProcessor.php b/src/LimeSurvey/Result/Processor/ResultProcessor.php similarity index 91% rename from src/Result/Processor/ResultProcessor.php rename to src/LimeSurvey/Result/Processor/ResultProcessor.php index 563ebbc..7a082f6 100644 --- a/src/Result/Processor/ResultProcessor.php +++ b/src/LimeSurvey/Result/Processor/ResultProcessor.php @@ -27,6 +27,8 @@ use Meritoo\LimeSurvey\ApiClient\Type\MethodType; */ class ResultProcessor { + const className = 'Meritoo\LimeSurvey\ApiClient\Result\Processor\ResultProcessor'; + /** * Returns processed data based on the raw data returned by the LimeSurvey's API * @@ -88,23 +90,23 @@ class ResultProcessor switch ($method) { case MethodType::ADD_PARTICIPANTS: case MethodType::GET_PARTICIPANT_PROPERTIES: - $className = Participant::class; + $className = Participant::className; break; case MethodType::GET_QUESTION_PROPERTIES: - $className = Question::class; + $className = Question::className; break; case MethodType::LIST_PARTICIPANTS: - $className = ParticipantShort::class; + $className = ParticipantShort::className; break; case MethodType::LIST_QUESTIONS: - $className = QuestionShort::class; + $className = QuestionShort::className; break; case MethodType::LIST_SURVEYS: - $className = Survey::class; + $className = Survey::className; break; /* @@ -119,7 +121,7 @@ class ResultProcessor throw new UnknownInstanceOfResultItem($method); } - if (Reflection::isChildOfClass($className, BaseItem::class)) { + if (Reflection::isChildOfClass($className, BaseItem::className)) { return $className; } diff --git a/src/Result/Result.php b/src/LimeSurvey/Result/Result.php similarity index 98% rename from src/Result/Result.php rename to src/LimeSurvey/Result/Result.php index a7cc8b9..d467e43 100644 --- a/src/Result/Result.php +++ b/src/LimeSurvey/Result/Result.php @@ -22,6 +22,8 @@ use Meritoo\LimeSurvey\ApiClient\Type\MethodType; */ class Result { + const className = 'Meritoo\LimeSurvey\ApiClient\Result\Result'; + /** * Name of called method while talking to the LimeSurvey's API. One of the MethodType class constants. * diff --git a/src/Service/ParticipantService.php b/src/LimeSurvey/Service/ParticipantService.php similarity index 98% rename from src/Service/ParticipantService.php rename to src/LimeSurvey/Service/ParticipantService.php index 3d83a01..64a11b7 100644 --- a/src/Service/ParticipantService.php +++ b/src/LimeSurvey/Service/ParticipantService.php @@ -26,6 +26,8 @@ use Meritoo\LimeSurvey\ApiClient\Type\ReasonType; */ class ParticipantService { + const className = 'Meritoo\LimeSurvey\ApiClient\Service\ParticipantService'; + /** * Client of the LimeSurvey's API * diff --git a/src/Service/SurveyService.php b/src/LimeSurvey/Service/SurveyService.php similarity index 98% rename from src/Service/SurveyService.php rename to src/LimeSurvey/Service/SurveyService.php index aca16c5..51c3429 100644 --- a/src/Service/SurveyService.php +++ b/src/LimeSurvey/Service/SurveyService.php @@ -25,6 +25,8 @@ use Meritoo\LimeSurvey\ApiClient\Type\ReasonType; */ class SurveyService { + const className = 'Meritoo\LimeSurvey\ApiClient\Service\SurveyService'; + /** * Client of the LimeSurvey's API * diff --git a/src/Type/MethodType.php b/src/LimeSurvey/Type/MethodType.php similarity index 97% rename from src/Type/MethodType.php rename to src/LimeSurvey/Type/MethodType.php index 9b950d7..aba402b 100644 --- a/src/Type/MethodType.php +++ b/src/LimeSurvey/Type/MethodType.php @@ -19,6 +19,8 @@ use Meritoo\LimeSurvey\ApiClient\Exception\UnknownMethodException; */ class MethodType extends BaseType { + const className = 'Meritoo\LimeSurvey\ApiClient\Type\MethodType'; + /** * Add participants to the tokens collection of the survey * diff --git a/src/Type/ReasonType.php b/src/LimeSurvey/Type/ReasonType.php similarity index 93% rename from src/Type/ReasonType.php rename to src/LimeSurvey/Type/ReasonType.php index 3b7ca81..cd33d1b 100644 --- a/src/Type/ReasonType.php +++ b/src/LimeSurvey/Type/ReasonType.php @@ -12,6 +12,8 @@ use Meritoo\Common\Type\Base\BaseType; */ class ReasonType extends BaseType { + const className = 'Meritoo\LimeSurvey\ApiClient\Type\ReasonType'; + /** * Reason of exception when there is no survey with given ID * diff --git a/src/Type/SystemMethodType.php b/src/LimeSurvey/Type/SystemMethodType.php similarity index 90% rename from src/Type/SystemMethodType.php rename to src/LimeSurvey/Type/SystemMethodType.php index 9e1b2aa..0690787 100644 --- a/src/Type/SystemMethodType.php +++ b/src/LimeSurvey/Type/SystemMethodType.php @@ -18,6 +18,8 @@ use Meritoo\Common\Type\Base\BaseType; */ class SystemMethodType extends BaseType { + const className = 'Meritoo\LimeSurvey\ApiClient\Type\SystemMethodType'; + /** * Create and return a session key * diff --git a/tests/Base/Result/BaseItemTest.php b/tests/Base/Result/BaseItemTest.php index 1efd5c0..932a745 100644 --- a/tests/Base/Result/BaseItemTest.php +++ b/tests/Base/Result/BaseItemTest.php @@ -22,11 +22,11 @@ class BaseItemTest extends BaseTestCase { public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(BaseItem::class, OopVisibilityType::IS_PUBLIC, 1, 0); + static::assertConstructorVisibilityAndArguments(BaseItem::className, OopVisibilityType::IS_PUBLIC, 1, 0); } public function testSetValuesVisibilityAndArguments() { - static::assertMethodVisibilityAndArguments(BaseItem::class, 'setValues', OopVisibilityType::IS_PRIVATE, 1, 1); + static::assertMethodVisibilityAndArguments(BaseItem::className, 'setValues', OopVisibilityType::IS_PRIVATE, 1, 1); } } diff --git a/tests/Client/ClientTest.php b/tests/Client/ClientTest.php index fb5fa03..ba51d7d 100644 --- a/tests/Client/ClientTest.php +++ b/tests/Client/ClientTest.php @@ -8,15 +8,12 @@ namespace Meritoo\LimeSurvey\Test\ApiClient\Client; -use Generator; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; use Meritoo\LimeSurvey\ApiClient\Client\Client; use Meritoo\LimeSurvey\ApiClient\Configuration\ConnectionConfiguration; -use Meritoo\LimeSurvey\ApiClient\Exception\UnknownMethodException; use Meritoo\LimeSurvey\ApiClient\Manager\JsonRpcClientManager; use Meritoo\LimeSurvey\ApiClient\Manager\SessionManager; -use Meritoo\LimeSurvey\ApiClient\Result\Result; use Meritoo\LimeSurvey\ApiClient\Type\MethodType; /** @@ -36,7 +33,7 @@ class ClientTest extends BaseTestCase public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(Client::class, OopVisibilityType::IS_PUBLIC, 3, 1); + static::assertConstructorVisibilityAndArguments(Client::className, OopVisibilityType::IS_PUBLIC, 3, 1); } /** @@ -45,7 +42,7 @@ class ClientTest extends BaseTestCase */ public function testRunWithIncorrectMethod($incorrectMethod) { - $this->expectException(UnknownMethodException::class); + $this->setExpectedException('Meritoo\LimeSurvey\ApiClient\Exception\UnknownMethodException'); $client = new Client($this->configuration); $client->run($incorrectMethod); @@ -61,8 +58,8 @@ class ClientTest extends BaseTestCase */ public function testRun($method, $arguments, $debugMode, $expectedRawData) { - $sessionManager = $this->createMock(SessionManager::class); - $rpcClientManager = $this->createMock(JsonRpcClientManager::class); + $sessionManager = $this->createMock(SessionManager::className); + $rpcClientManager = $this->createMock(JsonRpcClientManager::className); $rpcClientManager ->expects(static::any()) @@ -78,7 +75,7 @@ class ClientTest extends BaseTestCase ); $client = new Client($configuration, $rpcClientManager, $sessionManager); - static::assertInstanceOf(Result::class, $client->run($method, $arguments)); + static::assertInstanceOf('Meritoo\LimeSurvey\ApiClient\Result\Result', $client->run($method, $arguments)); } public function testGetConfiguration() @@ -89,21 +86,29 @@ class ClientTest extends BaseTestCase public function testGetRpcClientManagerVisibilityAndArguments() { - static::assertMethodVisibilityAndArguments(Client::class, 'getRpcClientManager', OopVisibilityType::IS_PRIVATE); + static::assertMethodVisibilityAndArguments(Client::className, 'getRpcClientManager', OopVisibilityType::IS_PRIVATE); } public function testGetSessionManagerVisibilityAndArguments() { - static::assertMethodVisibilityAndArguments(Client::class, 'getRpcClientManager', OopVisibilityType::IS_PRIVATE); + static::assertMethodVisibilityAndArguments(Client::className, 'getRpcClientManager', OopVisibilityType::IS_PRIVATE); } /** * Provides incorrect name of method * - * @return Generator + * @return array + * //return Generator */ public function provideIncorrectMethod() { + return [ + ['lorem'], + ['ipsum'], + [''], + ]; + + /* yield[ 'lorem', ]; @@ -115,15 +120,39 @@ class ClientTest extends BaseTestCase yield[ '', ]; + */ } /** * Provides correct name of method * - * @return Generator + * @return array + * //return Generator */ public function provideMethod() { + return [ + [ + MethodType::GET_PARTICIPANT_PROPERTIES, + [], + true, + [], + ], + [ + MethodType::LIST_SURVEYS, + [], + false, + [], + ], + [ + MethodType::LIST_PARTICIPANTS, + [], + false, + null, + ], + ]; + + /* yield[ MethodType::GET_PARTICIPANT_PROPERTIES, [], @@ -144,6 +173,7 @@ class ClientTest extends BaseTestCase false, null, ]; + */ /* * todo: Use/Verify other types of methods diff --git a/tests/Configuration/ConnectionConfigurationTest.php b/tests/Configuration/ConnectionConfigurationTest.php index 3f1883e..695ad3a 100644 --- a/tests/Configuration/ConnectionConfigurationTest.php +++ b/tests/Configuration/ConnectionConfigurationTest.php @@ -8,7 +8,6 @@ namespace Meritoo\LimeSurvey\Test\ApiClient\Configuration; -use Generator; use Meritoo\Common\Exception\Regex\InvalidUrlException; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; @@ -38,7 +37,7 @@ class ConnectionConfigurationTest extends BaseTestCase public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(ConnectionConfiguration::class, OopVisibilityType::IS_PUBLIC, 5, 3); + static::assertConstructorVisibilityAndArguments(ConnectionConfiguration::className, OopVisibilityType::IS_PUBLIC, 5, 3); } /** @@ -47,7 +46,7 @@ class ConnectionConfigurationTest extends BaseTestCase */ public function testConstructorWithEmptyBaseUrl($emptyBaseUrl) { - $this->expectException(InvalidUrlException::class); + $this->setExpectedException(InvalidUrlException::className); new ConnectionConfiguration($emptyBaseUrl, '', ''); } @@ -57,7 +56,7 @@ class ConnectionConfigurationTest extends BaseTestCase */ public function testConstructorWithInvalidBaseUrl($invalidBaseUrl) { - $this->expectException(InvalidUrlException::class); + $this->setExpectedException(InvalidUrlException::className); new ConnectionConfiguration($invalidBaseUrl, '', ''); } @@ -94,10 +93,17 @@ class ConnectionConfigurationTest extends BaseTestCase /** * Provides empty base url * - * @return Generator + * @return array + * //return Generator */ public function provideEmptyBaseUrl() { + return [ + [''], + [null], + ]; + + /* yield[ '', ]; @@ -105,15 +111,24 @@ class ConnectionConfigurationTest extends BaseTestCase yield[ null, ]; + */ } /** * Provides invalid base url * - * @return Generator + * @return array + * //return Generator */ public function provideInvalidBaseUrl() { + return [ + ['lorem'], + ['ipsum'], + ['htp:/dolor.com'], + ]; + + /* yield[ 'lorem', ]; @@ -125,6 +140,7 @@ class ConnectionConfigurationTest extends BaseTestCase yield[ 'htp:/dolor.com', ]; + */ } /** diff --git a/tests/Exception/CannotProcessDataExceptionTest.php b/tests/Exception/CannotProcessDataExceptionTest.php index d751433..ee2b0c0 100644 --- a/tests/Exception/CannotProcessDataExceptionTest.php +++ b/tests/Exception/CannotProcessDataExceptionTest.php @@ -8,7 +8,6 @@ namespace Meritoo\LimeSurvey\Test\ApiClient\Exception; -use Generator; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; use Meritoo\LimeSurvey\ApiClient\Exception\CannotProcessDataException; @@ -23,7 +22,7 @@ class CannotProcessDataExceptionTest extends BaseTestCase { public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(CannotProcessDataException::class, OopVisibilityType::IS_PUBLIC, 1, 1); + static::assertConstructorVisibilityAndArguments(CannotProcessDataException::className, OopVisibilityType::IS_PUBLIC, 1, 1); } /** @@ -41,12 +40,25 @@ class CannotProcessDataExceptionTest extends BaseTestCase /** * Provides reason why data cannot be processed * - * @return Generator + * @return array + * //return Generator */ public function provideReason() { $template = 'Raw data returned by the LimeSurvey\'s API cannot be processed. Reason: \'%s\'.'; + return [ + [ + 'unknown', + sprintf($template, 'unknown'), + ], + [ + 'Invalid user name or password', + sprintf($template, 'Invalid user name or password'), + ], + ]; + + /* yield[ 'unknown', sprintf($template, 'unknown'), @@ -56,5 +68,6 @@ class CannotProcessDataExceptionTest extends BaseTestCase 'Invalid user name or password', sprintf($template, 'Invalid user name or password'), ]; + */ } } diff --git a/tests/Exception/CreateSessionKeyFailedExceptionTest.php b/tests/Exception/CreateSessionKeyFailedExceptionTest.php index 7a465e8..b25c315 100644 --- a/tests/Exception/CreateSessionKeyFailedExceptionTest.php +++ b/tests/Exception/CreateSessionKeyFailedExceptionTest.php @@ -8,7 +8,6 @@ namespace Meritoo\LimeSurvey\Test\ApiClient\Exception; -use Generator; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; use Meritoo\LimeSurvey\ApiClient\Exception\CreateSessionKeyFailedException; @@ -23,7 +22,7 @@ class CreateSessionKeyFailedExceptionTest extends BaseTestCase { public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(CreateSessionKeyFailedException::class, OopVisibilityType::IS_PUBLIC, 1, 0); + static::assertConstructorVisibilityAndArguments(CreateSessionKeyFailedException::className, OopVisibilityType::IS_PUBLIC, 1, 0); } /** @@ -41,13 +40,26 @@ class CreateSessionKeyFailedExceptionTest extends BaseTestCase /** * Provides reason of failure * - * @return Generator + * @return array + * //return Generator */ public function provideReason() { $shortMessage = 'Create of the session key has failed'; $longMessageTemplate = sprintf('%s. Reason: \'%s\'.', $shortMessage, '%s'); + return [ + [ + '', + $shortMessage, + ], + [ + 'Invalid user name or password', + sprintf($longMessageTemplate, 'Invalid user name or password'), + ], + ]; + + /* yield[ '', $shortMessage, @@ -57,5 +69,6 @@ class CreateSessionKeyFailedExceptionTest extends BaseTestCase 'Invalid user name or password', sprintf($longMessageTemplate, 'Invalid user name or password'), ]; + */ } } diff --git a/tests/Exception/IncorrectClassOfResultItemExceptionTest.php b/tests/Exception/IncorrectClassOfResultItemExceptionTest.php index 6fa8189..c8b9a62 100644 --- a/tests/Exception/IncorrectClassOfResultItemExceptionTest.php +++ b/tests/Exception/IncorrectClassOfResultItemExceptionTest.php @@ -8,12 +8,9 @@ namespace Meritoo\LimeSurvey\Test\ApiClient\Exception; -use Generator; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; -use Meritoo\LimeSurvey\ApiClient\Base\Result\BaseItem; use Meritoo\LimeSurvey\ApiClient\Exception\IncorrectClassOfResultItemException; -use stdClass; /** * Test case of an exception used while class used to create instance of one item of the result is incorrect @@ -25,7 +22,7 @@ class IncorrectClassOfResultItemExceptionTest extends BaseTestCase { public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(IncorrectClassOfResultItemException::class, OopVisibilityType::IS_PUBLIC, 1, 1); + static::assertConstructorVisibilityAndArguments(IncorrectClassOfResultItemException::className, OopVisibilityType::IS_PUBLIC, 1, 1); } /** @@ -43,16 +40,26 @@ class IncorrectClassOfResultItemExceptionTest extends BaseTestCase /** * Provides incorrect class name used to create instance of one item * - * @return Generator + * @return array + * //return Generator */ public function provideIncorrectClassName() { $template = 'Class %s used to create instance of one item of the result should extend %s, but it does not. Did' . ' you forget to use proper base class?'; + return [ + [ + '\stdClass', + sprintf($template, '\stdClass', 'Meritoo\LimeSurvey\ApiClient\Base\Result\BaseItem'), + ], + ]; + + /* yield[ stdClass::class, sprintf($template, stdClass::class, BaseItem::class), ]; + */ } } diff --git a/tests/Exception/InvalidResultOfMethodRunExceptionTest.php b/tests/Exception/InvalidResultOfMethodRunExceptionTest.php index 4179592..d5db24c 100644 --- a/tests/Exception/InvalidResultOfMethodRunExceptionTest.php +++ b/tests/Exception/InvalidResultOfMethodRunExceptionTest.php @@ -9,7 +9,6 @@ namespace Meritoo\LimeSurvey\Test\ApiClient\Exception; use Exception; -use Generator; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; use Meritoo\LimeSurvey\ApiClient\Exception\InvalidResultOfMethodRunException; @@ -25,7 +24,7 @@ class InvalidResultOfMethodRunExceptionTest extends BaseTestCase { public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(InvalidResultOfMethodRunException::class, OopVisibilityType::IS_PUBLIC, 3, 2); + static::assertConstructorVisibilityAndArguments(InvalidResultOfMethodRunException::className, OopVisibilityType::IS_PUBLIC, 3, 2); } /** @@ -45,7 +44,8 @@ class InvalidResultOfMethodRunExceptionTest extends BaseTestCase /** * Provides previous exception, name and arguments of called method * - * @return Generator + * @return array + * //return Generator */ public function providePreviousExceptionAndMethod() { @@ -54,6 +54,26 @@ class InvalidResultOfMethodRunExceptionTest extends BaseTestCase . "- method: %s,\n" . '- arguments: %s.'; + return [ + [ + new Exception('Lorem ipsum'), + MethodType::ADD_RESPONSE, + [], + sprintf($template, 'Lorem ipsum', MethodType::ADD_RESPONSE, '(no arguments)'), + ], + [ + new Exception('Dolor sit amet'), + MethodType::LIST_SURVEYS, + [ + 'fist_name' => 'John', + 'last_name' => 'Scott', + 'email' => 'john@scott.com', + ], + sprintf($template, 'Dolor sit amet', MethodType::LIST_SURVEYS, 'fist_name="John", last_name="Scott", email="john@scott.com"'), + ], + ]; + + /* yield[ new Exception('Lorem ipsum'), MethodType::ADD_RESPONSE, @@ -71,5 +91,6 @@ class InvalidResultOfMethodRunExceptionTest extends BaseTestCase ], sprintf($template, 'Dolor sit amet', MethodType::LIST_SURVEYS, 'fist_name="John", last_name="Scott", email="john@scott.com"'), ]; + */ } } diff --git a/tests/Exception/MissingParticipantOfSurveyExceptionTest.php b/tests/Exception/MissingParticipantOfSurveyExceptionTest.php index 081fbbe..6e1f144 100644 --- a/tests/Exception/MissingParticipantOfSurveyExceptionTest.php +++ b/tests/Exception/MissingParticipantOfSurveyExceptionTest.php @@ -22,7 +22,7 @@ class MissingParticipantOfSurveyExceptionTest extends BaseTestCase { public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(MissingParticipantOfSurveyException::class, OopVisibilityType::IS_PUBLIC, 2, 2); + static::assertConstructorVisibilityAndArguments(MissingParticipantOfSurveyException::className, OopVisibilityType::IS_PUBLIC, 2, 2); } /** @@ -41,12 +41,27 @@ class MissingParticipantOfSurveyExceptionTest extends BaseTestCase /** * Provides ID of survey and e-mail address of the participant * - * @return Generator + * @return array + * //return Generator */ public function provideSurveyIdAndEmail() { $template = 'Participant with e-mail %s of survey with ID %s is missing. Maybe was not added to the survey?'; + return [ + [ + 1, + 'lorem@ipsum.com', + sprintf($template, 'lorem@ipsum.com', 1), + ], + [ + 1234, + 'another@email.comm', + sprintf($template, 'another@email.comm', 1234), + ], + ]; + + /* yield[ 1, 'lorem@ipsum.com', @@ -58,5 +73,6 @@ class MissingParticipantOfSurveyExceptionTest extends BaseTestCase 'another@email.comm', sprintf($template, 'another@email.comm', 1234), ]; + */ } } diff --git a/tests/Exception/UnknownInstanceOfResultItemTest.php b/tests/Exception/UnknownInstanceOfResultItemTest.php index badf1e0..46a2320 100644 --- a/tests/Exception/UnknownInstanceOfResultItemTest.php +++ b/tests/Exception/UnknownInstanceOfResultItemTest.php @@ -8,11 +8,9 @@ namespace Meritoo\LimeSurvey\Test\ApiClient\Exception; -use Generator; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; use Meritoo\LimeSurvey\ApiClient\Exception\UnknownInstanceOfResultItem; -use Meritoo\LimeSurvey\ApiClient\Result\Processor\ResultProcessor; use Meritoo\LimeSurvey\ApiClient\Type\MethodType; /** @@ -26,7 +24,7 @@ class UnknownInstanceOfResultItemTest extends BaseTestCase { public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(UnknownInstanceOfResultItem::class, OopVisibilityType::IS_PUBLIC, 1, 1); + static::assertConstructorVisibilityAndArguments(UnknownInstanceOfResultItem::className, OopVisibilityType::IS_PUBLIC, 1, 1); } /** @@ -45,13 +43,26 @@ class UnknownInstanceOfResultItemTest extends BaseTestCase /** * Provides name of called method * - * @return Generator + * @return array + * //return Generator */ public function provideMethodName() { $template = 'Class name used to create instance of one item used by result the of \'%s\' LimeSurvey API\'s' . ' method is unknown. Proper class is not mapped in %s::%s() method. Did you forget about this?'; + return [ + [ + MethodType::LIST_SURVEYS, + sprintf($template, MethodType::LIST_SURVEYS, 'Meritoo\LimeSurvey\ApiClient\Result\Processor\ResultProcessor', 'getItemClassName'), + ], + [ + MethodType::ADD_PARTICIPANTS, + sprintf($template, MethodType::ADD_PARTICIPANTS, 'Meritoo\LimeSurvey\ApiClient\Result\Processor\ResultProcessor', 'getItemClassName'), + ], + ]; + + /* yield[ MethodType::LIST_SURVEYS, sprintf($template, MethodType::LIST_SURVEYS, ResultProcessor::class, 'getItemClassName'), @@ -61,5 +72,6 @@ class UnknownInstanceOfResultItemTest extends BaseTestCase MethodType::ADD_PARTICIPANTS, sprintf($template, MethodType::ADD_PARTICIPANTS, ResultProcessor::class, 'getItemClassName'), ]; + */ } } diff --git a/tests/Exception/UnknownMethodExceptionTest.php b/tests/Exception/UnknownMethodExceptionTest.php index 681bcfb..f14acaf 100644 --- a/tests/Exception/UnknownMethodExceptionTest.php +++ b/tests/Exception/UnknownMethodExceptionTest.php @@ -8,7 +8,6 @@ namespace Meritoo\LimeSurvey\Test\ApiClient\Exception; -use Generator; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; use Meritoo\LimeSurvey\ApiClient\Exception\UnknownMethodException; @@ -24,7 +23,7 @@ class UnknownMethodExceptionTest extends BaseTestCase { public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(UnknownMethodException::class, OopVisibilityType::IS_PUBLIC, 1, 1); + static::assertConstructorVisibilityAndArguments(UnknownMethodException::className, OopVisibilityType::IS_PUBLIC, 1, 1); } /** @@ -42,7 +41,8 @@ class UnknownMethodExceptionTest extends BaseTestCase /** * Provides name of called method * - * @return Generator + * @return array + * //return Generator */ public function provideUnknownType() { @@ -51,6 +51,18 @@ class UnknownMethodExceptionTest extends BaseTestCase $template = 'The \'%s\' type of name of method used while talking to the LimeSurvey\'s API is unknown. Probably' . ' doesn\'t exist or there is a typo. You should use one of these types: %s.'; + return [ + [ + MethodType::ADD_PARTICIPANTS, + sprintf($template, MethodType::ADD_PARTICIPANTS, $allMethods), + ], + [ + MethodType::ADD_PARTICIPANTS, + sprintf($template, MethodType::ADD_PARTICIPANTS, $allMethods), + ], + ]; + + /* yield[ MethodType::ADD_PARTICIPANTS, sprintf($template, MethodType::ADD_PARTICIPANTS, $allMethods), @@ -60,5 +72,6 @@ class UnknownMethodExceptionTest extends BaseTestCase MethodType::ADD_PARTICIPANTS, sprintf($template, MethodType::ADD_PARTICIPANTS, $allMethods), ]; + */ } } diff --git a/tests/Manager/JsonRpcClientManagerTest.php b/tests/Manager/JsonRpcClientManagerTest.php index 30d182d..93a9f6c 100644 --- a/tests/Manager/JsonRpcClientManagerTest.php +++ b/tests/Manager/JsonRpcClientManagerTest.php @@ -8,7 +8,6 @@ namespace Meritoo\LimeSurvey\Test\ApiClient\Manager; -use JsonRPC\Client as RpcClient; use JsonRPC\Exception\InvalidJsonFormatException; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Type\OopVisibilityType; @@ -35,15 +34,15 @@ class JsonRpcClientManagerTest extends BaseTestCase public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(JsonRpcClientManager::class, OopVisibilityType::IS_PUBLIC, 1, 1); + static::assertConstructorVisibilityAndArguments(JsonRpcClientManager::className, OopVisibilityType::IS_PUBLIC, 1, 1); } public function testRunMethodWithEmptyArrayReturned() { - $rpcClient = $this->createMock(RpcClient::class); + $rpcClient = $this->createMock('\JsonRPC\Client'); $manager = $this - ->getMockBuilder(JsonRpcClientManager::class) + ->getMockBuilder(JsonRpcClientManager::className) ->setConstructorArgs([ $this->configuration, ]) @@ -68,8 +67,8 @@ class JsonRpcClientManagerTest extends BaseTestCase public function testRunMethodWithRawDataReturned() { - $rpcClient = $this->createMock(RpcClient::class); - $manager = $this->createPartialMock(JsonRpcClientManager::class, ['getRpcClient']); + $rpcClient = $this->createMock('\JsonRPC\Client'); + $manager = $this->getMock(JsonRpcClientManager::className, ['getRpcClient'], [], '', false); $rpcClient ->expects(static::once()) @@ -87,10 +86,10 @@ class JsonRpcClientManagerTest extends BaseTestCase public function testRunMethodWithException() { - $this->expectException(InvalidResultOfMethodRunException::class); + $this->setExpectedException(InvalidResultOfMethodRunException::className); - $manager = $this->createPartialMock(JsonRpcClientManager::class, ['getRpcClient']); - $rpcClient = $this->createMock(RpcClient::class); + $manager = $this->getMock(JsonRpcClientManager::className, ['getRpcClient'], [], '', false); + $rpcClient = $this->createMock('\JsonRPC\Client'); $rpcClient ->expects(self::once()) @@ -108,7 +107,7 @@ class JsonRpcClientManagerTest extends BaseTestCase public function testGetRpcClientVisibilityAndArguments() { - static::assertMethodVisibilityAndArguments(JsonRpcClientManager::class, 'getRpcClient', OopVisibilityType::IS_PROTECTED); + static::assertMethodVisibilityAndArguments(JsonRpcClientManager::className, 'getRpcClient', OopVisibilityType::IS_PROTECTED); } /** diff --git a/tests/Manager/SessionManagerTest.php b/tests/Manager/SessionManagerTest.php index 158bcb5..ce3ddfd 100644 --- a/tests/Manager/SessionManagerTest.php +++ b/tests/Manager/SessionManagerTest.php @@ -24,15 +24,15 @@ class SessionManagerTest extends BaseTestCase { public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(SessionManager::class, OopVisibilityType::IS_PUBLIC, 1, 1); + static::assertConstructorVisibilityAndArguments(SessionManager::className, OopVisibilityType::IS_PUBLIC, 1, 1); } public function testGetSessionKeyWhenFailedWithoutReason() { - $this->expectException(CreateSessionKeyFailedException::class); - $this->expectExceptionMessage('Create of the session key has failed'); + $this->setExpectedException(CreateSessionKeyFailedException::className, 'Create of the session key has failed'); + //$this->expectExceptionMessage('Create of the session key has failed'); - $clientManager = $this->createMock(JsonRpcClientManager::class); + $clientManager = $this->createMock(JsonRpcClientManager::className); $clientManager ->expects(static::any()) @@ -46,10 +46,10 @@ class SessionManagerTest extends BaseTestCase { $reason = 'Invalid credentials'; - $this->expectException(CreateSessionKeyFailedException::class); - $this->expectExceptionMessage(sprintf('Create of the session key has failed. Reason: \'%s\'.', $reason)); + $this->setExpectedException(CreateSessionKeyFailedException::className, sprintf('Create of the session key has failed. Reason: \'%s\'.', $reason)); + //$this->expectExceptionMessage(sprintf('Create of the session key has failed. Reason: \'%s\'.', $reason)); - $clientManager = $this->createMock(JsonRpcClientManager::class); + $clientManager = $this->createMock(JsonRpcClientManager::className); $clientManager ->expects(static::any()) @@ -63,7 +63,7 @@ class SessionManagerTest extends BaseTestCase public function testGetSessionKey() { - $clientManager = $this->createMock(JsonRpcClientManager::class); + $clientManager = $this->createMock(JsonRpcClientManager::className); $clientManager ->expects(static::any()) @@ -76,7 +76,7 @@ class SessionManagerTest extends BaseTestCase public function testReleaseSessionKey() { - $clientManager = $this->createMock(JsonRpcClientManager::class); + $clientManager = $this->createMock(JsonRpcClientManager::className); $clientManager ->expects(static::any()) @@ -84,6 +84,6 @@ class SessionManagerTest extends BaseTestCase ->willReturn([]); $sessionManager = new SessionManager($clientManager); - static::assertInstanceOf(SessionManager::class, $sessionManager->releaseSessionKey()); + static::assertInstanceOf(SessionManager::className, $sessionManager->releaseSessionKey()); } } diff --git a/tests/Result/Collection/ParticipantsTest.php b/tests/Result/Collection/ParticipantsTest.php index 131c982..a4adc89 100644 --- a/tests/Result/Collection/ParticipantsTest.php +++ b/tests/Result/Collection/ParticipantsTest.php @@ -47,24 +47,24 @@ class ParticipantsTest extends BaseTestCase public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(Participants::class, OopVisibilityType::IS_PUBLIC, 1, 0); + static::assertConstructorVisibilityAndArguments(Participants::className, OopVisibilityType::IS_PUBLIC, 1, 0); } public function testAdd() { - $this->expectException(DisabledMethodException::class); + $this->setExpectedException(DisabledMethodException::className); (new Participants())->add(''); } public function testAddMultiple() { - $this->expectException(DisabledMethodException::class); + $this->setExpectedException(DisabledMethodException::className); (new Participants())->addMultiple([]); } public function testHas() { - $this->expectException(DisabledMethodException::class); + $this->setExpectedException(DisabledMethodException::className); (new Participants())->has(new Participant()); } diff --git a/tests/Result/Collection/SurveysTest.php b/tests/Result/Collection/SurveysTest.php index cc91985..5d0ff45 100644 --- a/tests/Result/Collection/SurveysTest.php +++ b/tests/Result/Collection/SurveysTest.php @@ -37,7 +37,7 @@ class SurveysTest extends BaseTestCase public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(Surveys::class, OopVisibilityType::IS_PUBLIC, 1, 0); + static::assertConstructorVisibilityAndArguments(Surveys::className, OopVisibilityType::IS_PUBLIC, 1, 0); } public function testAddWithoutIndex() diff --git a/tests/Result/Item/ParticipantShortTest.php b/tests/Result/Item/ParticipantShortTest.php index d21e1df..1f34fb0 100644 --- a/tests/Result/Item/ParticipantShortTest.php +++ b/tests/Result/Item/ParticipantShortTest.php @@ -46,7 +46,7 @@ class ParticipantShortTest extends BaseTestCase public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(ParticipantShort::class, OopVisibilityType::IS_PUBLIC, 1, 0); + static::assertConstructorVisibilityAndArguments(ParticipantShort::className, OopVisibilityType::IS_PUBLIC, 1, 0); } public function testCreateOfTheParticipant() diff --git a/tests/Result/Item/ParticipantTest.php b/tests/Result/Item/ParticipantTest.php index eddd4e9..f7b6d20 100644 --- a/tests/Result/Item/ParticipantTest.php +++ b/tests/Result/Item/ParticipantTest.php @@ -46,7 +46,7 @@ class ParticipantTest extends BaseTestCase public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(Participant::class, OopVisibilityType::IS_PUBLIC, 1, 0); + static::assertConstructorVisibilityAndArguments(Participant::className, OopVisibilityType::IS_PUBLIC, 1, 0); } public function testCreateOfTheParticipant() @@ -54,7 +54,7 @@ class ParticipantTest extends BaseTestCase $processor = new ResultProcessor(); $processed = $processor->process(MethodType::GET_PARTICIPANT_PROPERTIES, $this->rawData[0]); - static::assertInstanceOf(Participant::class, $processed); + static::assertInstanceOf(Participant::className, $processed); } public function testGetId() diff --git a/tests/Result/Item/QuestionShortTest.php b/tests/Result/Item/QuestionShortTest.php index fd056fc..c431767 100644 --- a/tests/Result/Item/QuestionShortTest.php +++ b/tests/Result/Item/QuestionShortTest.php @@ -45,7 +45,7 @@ class QuestionShortTest extends BaseTestCase public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(QuestionShort::class, OopVisibilityType::IS_PUBLIC, 1, 0); + static::assertConstructorVisibilityAndArguments(QuestionShort::className, OopVisibilityType::IS_PUBLIC, 1, 0); } public function testCreateOfTheQuestionShort() diff --git a/tests/Result/Item/QuestionTest.php b/tests/Result/Item/QuestionTest.php index 1cf1e27..1dd161c 100644 --- a/tests/Result/Item/QuestionTest.php +++ b/tests/Result/Item/QuestionTest.php @@ -45,7 +45,7 @@ class QuestionTest extends BaseTestCase public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(Question::class, OopVisibilityType::IS_PUBLIC, 1, 0); + static::assertConstructorVisibilityAndArguments(Question::className, OopVisibilityType::IS_PUBLIC, 1, 0); } public function testCreateOfTheQuestionShort() @@ -53,7 +53,7 @@ class QuestionTest extends BaseTestCase $processor = new ResultProcessor(); $processed = $processor->process(MethodType::GET_QUESTION_PROPERTIES, $this->rawData); - static::assertInstanceOf(Question::class, $processed); + static::assertInstanceOf(Question::className, $processed); } public function testGetId() diff --git a/tests/Result/Item/SurveyTest.php b/tests/Result/Item/SurveyTest.php index 638cac9..593d9ee 100644 --- a/tests/Result/Item/SurveyTest.php +++ b/tests/Result/Item/SurveyTest.php @@ -46,7 +46,7 @@ class SurveyTest extends BaseTestCase public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(Survey::class, OopVisibilityType::IS_PUBLIC, 1, 0); + static::assertConstructorVisibilityAndArguments(Survey::className, OopVisibilityType::IS_PUBLIC, 1, 0); } public function testCreateOfTheSurvey() diff --git a/tests/Result/Processor/ResultProcessorTest.php b/tests/Result/Processor/ResultProcessorTest.php index 2d98225..6bd40fd 100644 --- a/tests/Result/Processor/ResultProcessorTest.php +++ b/tests/Result/Processor/ResultProcessorTest.php @@ -27,7 +27,7 @@ class ResultProcessorTest extends BaseTestCase { public function testConstructorVisibilityAndArguments() { - static::assertHasNoConstructor(ResultProcessor::class); + static::assertHasNoConstructor(ResultProcessor::className); } public function testProcessWithEmptyRawData() @@ -72,17 +72,17 @@ class ResultProcessorTest extends BaseTestCase static::assertNotEmpty($processed); static::assertFalse(is_array($processed)); - static::assertInstanceOf(BaseItem::class, $processed); + static::assertInstanceOf(BaseItem::className, $processed); } public function testGetItemClassNameVisibilityAndArguments() { - static::assertMethodVisibilityAndArguments(ResultProcessor::class, 'getItemClassName', OopVisibilityType::IS_PRIVATE, 1, 1); + static::assertMethodVisibilityAndArguments(ResultProcessor::className, 'getItemClassName', OopVisibilityType::IS_PRIVATE, 1, 1); } public function testRunWithUnknownResultClass() { - $this->expectException(UnknownInstanceOfResultItem::class); + $this->setExpectedException(UnknownInstanceOfResultItem::className); $rawData = [ 'lorem' => 'ipsum', diff --git a/tests/Result/ResultTest.php b/tests/Result/ResultTest.php index 5ccc906..a176a1c 100644 --- a/tests/Result/ResultTest.php +++ b/tests/Result/ResultTest.php @@ -87,7 +87,7 @@ class ResultTest extends BaseTestCase public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(Result::class, OopVisibilityType::IS_PUBLIC, 2, 2); + static::assertConstructorVisibilityAndArguments(Result::className, OopVisibilityType::IS_PUBLIC, 2, 2); } public function testIsEmpty() @@ -108,7 +108,7 @@ class ResultTest extends BaseTestCase static::assertCount(count($this->emptyData), $emptyData); static::assertCount(count($this->iterableData), $iterableData); - static::assertInstanceOf(BaseItem::class, $notIterableData); + static::assertInstanceOf(BaseItem::className, $notIterableData); } public function testGetDataUsingRawData() @@ -128,18 +128,18 @@ class ResultTest extends BaseTestCase public function testGetDataUsingProcessedDataWhoCannotBeProcessed() { - $this->expectException(CannotProcessDataException::class); + $this->setExpectedException(CannotProcessDataException::className); $this->statusInsteadDataResult->getData(); } public function testGetProcessedDataVisibilityAndArguments() { - static::assertMethodVisibilityAndArguments(Result::class, 'getProcessedData', OopVisibilityType::IS_PRIVATE, 1, 1); + static::assertMethodVisibilityAndArguments(Result::className, 'getProcessedData', OopVisibilityType::IS_PRIVATE, 1, 1); } public function testGetResultProcessorVisibilityAndArguments() { - static::assertMethodVisibilityAndArguments(Result::class, 'getResultProcessor', OopVisibilityType::IS_PRIVATE); + static::assertMethodVisibilityAndArguments(Result::className, 'getResultProcessor', OopVisibilityType::IS_PRIVATE); } public function testGetStatusWhenIsNotProvided() @@ -218,6 +218,6 @@ class ResultTest extends BaseTestCase */ private function getResultMock($constructorArguments) { - return $this->getMockForAbstractClass(Result::class, $constructorArguments); + return $this->getMockForAbstractClass(Result::className, $constructorArguments); } } diff --git a/tests/Service/ParticipantServiceTest.php b/tests/Service/ParticipantServiceTest.php index 3e217b2..7853652 100644 --- a/tests/Service/ParticipantServiceTest.php +++ b/tests/Service/ParticipantServiceTest.php @@ -51,7 +51,7 @@ class ParticipantServiceTest extends BaseTestCase public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(ParticipantService::class, OopVisibilityType::IS_PUBLIC, 2, 1); + static::assertConstructorVisibilityAndArguments(ParticipantService::className, OopVisibilityType::IS_PUBLIC, 2, 1); } public function testGetClient() @@ -62,8 +62,8 @@ class ParticipantServiceTest extends BaseTestCase $this->createServiceWithoutParticipants($rpcClientManager, $sessionManager); $this->createServiceWithParticipants($rpcClientManager, $sessionManager); - static::assertInstanceOf(Client::class, $this->serviceWithoutParticipants->getClient()); - static::assertInstanceOf(Client::class, $this->serviceWithParticipants->getClient()); + static::assertInstanceOf(Client::className, $this->serviceWithoutParticipants->getClient()); + static::assertInstanceOf(Client::className, $this->serviceWithParticipants->getClient()); $connectionConfiguration = $this->getConnectionConfiguration(); $client = new Client($connectionConfiguration); @@ -90,7 +90,7 @@ class ParticipantServiceTest extends BaseTestCase public function testGetSurveyParticipantsWithImportantException() { - $this->expectException(CannotProcessDataException::class); + $this->setExpectedException(CannotProcessDataException::className); $exception = new CannotProcessDataException(ReasonType::NO_TOKEN_TABLE); $rpcClientManager = $this->getJsonRpcClientManagerWithException(1, $exception); @@ -129,7 +129,7 @@ class ParticipantServiceTest extends BaseTestCase public function testAddParticipantForNotExistingSurvey() { - $this->expectException(CannotProcessDataException::class); + $this->setExpectedException(CannotProcessDataException::className); $exception = new CannotProcessDataException(ReasonType::NOT_EXISTING_SURVEY_ID); $rpcClientManager = $this->getJsonRpcClientManagerWithException(1, $exception); @@ -169,7 +169,7 @@ class ParticipantServiceTest extends BaseTestCase $this->createServiceWithoutParticipants($rpcClientManager, $sessionManager); $result = $this->serviceWithoutParticipants->addParticipant($surveyId, $firstName, $lastName, $email); - static::assertInstanceOf(Participant::class, $result); + static::assertInstanceOf(Participant::className, $result); static::assertEquals($firstName, $result->getFirstName()); static::assertEquals($lastName, $result->getLastName()); static::assertEquals($email, $result->getEmail()); @@ -186,7 +186,7 @@ class ParticipantServiceTest extends BaseTestCase static::assertNull($this->serviceWithoutParticipants->getParticipant(1, 'john@scott.com')); $participant = $this->serviceWithParticipants->getParticipant(1, 'john@scott.com'); - static::assertInstanceOf(ParticipantShort::class, $participant); + static::assertInstanceOf(ParticipantShort::className, $participant); static::assertEquals('John', $participant->getFirstName()); static::assertEquals('Scott', $participant->getLastName()); static::assertEquals('john@scott.com', $participant->getEmail()); @@ -215,7 +215,7 @@ class ParticipantServiceTest extends BaseTestCase static::assertNull($this->serviceWithoutParticipants->getParticipantDetails(1, 'john@scott.com')); $participant = $this->serviceWithParticipants->getParticipantDetails(1, 'john@scott.com'); - static::assertInstanceOf(Participant::class, $participant); + static::assertInstanceOf(Participant::className, $participant); static::assertEquals($runMethodCallResults['tid'], $participant->getId()); static::assertEquals($runMethodCallResults['firstname'], $participant->getFirstName()); static::assertEquals($runMethodCallResults['lastname'], $participant->getLastName()); @@ -229,7 +229,7 @@ class ParticipantServiceTest extends BaseTestCase public function testHasParticipantFilledSurveyWithException() { - $this->expectException(MissingParticipantOfSurveyException::class); + $this->setExpectedException(MissingParticipantOfSurveyException::className); $rpcClientManager = $this->getJsonRpcClientManager(1); $sessionManager = $this->getSessionManager(); @@ -256,7 +256,7 @@ class ParticipantServiceTest extends BaseTestCase public function testHasParticipantFilledSurveyUsingNotExistingParticipant() { - $this->expectException(MissingParticipantOfSurveyException::class); + $this->setExpectedException(MissingParticipantOfSurveyException::className); $rpcClientManager = $this->getJsonRpcClientManager(1); $sessionManager = $this->getSessionManager(); @@ -282,7 +282,7 @@ class ParticipantServiceTest extends BaseTestCase */ private function getSessionManager() { - return $this->createMock(SessionManager::class); + return $this->createMock(SessionManager::className); } /** @@ -294,7 +294,7 @@ class ParticipantServiceTest extends BaseTestCase */ private function getJsonRpcClientManager($runMethodCallCount, array $runMethodCallResults = []) { - $rpcClientManager = $this->createMock(JsonRpcClientManager::class); + $rpcClientManager = $this->createMock(JsonRpcClientManager::className); $rpcClientManager ->expects(static::exactly($runMethodCallCount)) @@ -314,7 +314,7 @@ class ParticipantServiceTest extends BaseTestCase */ private function getJsonRpcClientManagerWithException($runMethodCallCount, Exception $exception) { - $rpcClientManager = $this->createMock(JsonRpcClientManager::class); + $rpcClientManager = $this->createMock(JsonRpcClientManager::className); $rpcClientManager ->expects(static::exactly($runMethodCallCount)) diff --git a/tests/Service/SurveyServiceTest.php b/tests/Service/SurveyServiceTest.php index a34cf68..7e71161 100644 --- a/tests/Service/SurveyServiceTest.php +++ b/tests/Service/SurveyServiceTest.php @@ -57,7 +57,7 @@ class SurveyServiceTest extends BaseTestCase public function testConstructorVisibilityAndArguments() { - static::assertConstructorVisibilityAndArguments(SurveyService::class, OopVisibilityType::IS_PUBLIC, 2, 1); + static::assertConstructorVisibilityAndArguments(SurveyService::className, OopVisibilityType::IS_PUBLIC, 2, 1); } public function testGetClient() @@ -68,8 +68,8 @@ class SurveyServiceTest extends BaseTestCase $this->createServiceWithoutSurveys($rpcClientManager, $sessionManager); $this->createServiceWithSurveys($rpcClientManager, $sessionManager); - static::assertInstanceOf(Client::class, $this->serviceWithoutSurveys->getClient()); - static::assertInstanceOf(Client::class, $this->serviceWithSurveys->getClient()); + static::assertInstanceOf(Client::className, $this->serviceWithoutSurveys->getClient()); + static::assertInstanceOf(Client::className, $this->serviceWithSurveys->getClient()); $connectionConfiguration = $this->getConnectionConfiguration(); $client = new Client($connectionConfiguration); @@ -80,7 +80,7 @@ class SurveyServiceTest extends BaseTestCase public function testGetAllSurveysWithImportantException() { - $this->expectException(CannotProcessDataException::class); + $this->setExpectedException(CannotProcessDataException::className); $exception = new CannotProcessDataException(ReasonType::NO_TOKEN_TABLE); $rpcClientManager = $this->getJsonRpcClientManagerWithException(1, $exception); @@ -202,7 +202,7 @@ class SurveyServiceTest extends BaseTestCase */ private function getSessionManager() { - return $this->createMock(SessionManager::class); + return $this->createMock(SessionManager::className); } /** @@ -214,7 +214,7 @@ class SurveyServiceTest extends BaseTestCase */ private function getJsonRpcClientManager($runMethodCallCount, array $runMethodCallResults = []) { - $rpcClientManager = $this->createMock(JsonRpcClientManager::class); + $rpcClientManager = $this->createMock(JsonRpcClientManager::className); $rpcClientManager ->expects(static::exactly($runMethodCallCount)) @@ -234,7 +234,7 @@ class SurveyServiceTest extends BaseTestCase */ private function getJsonRpcClientManagerWithException($runMethodCallCount, Exception $exception) { - $rpcClientManager = $this->createMock(JsonRpcClientManager::class); + $rpcClientManager = $this->createMock(JsonRpcClientManager::className); $rpcClientManager ->expects(static::exactly($runMethodCallCount)) diff --git a/tests/Type/MethodTypeTest.php b/tests/Type/MethodTypeTest.php index 0e9f24b..0b6d9cc 100644 --- a/tests/Type/MethodTypeTest.php +++ b/tests/Type/MethodTypeTest.php @@ -8,7 +8,6 @@ namespace Meritoo\LimeSurvey\Test\ApiClient\Type; -use Generator; use Meritoo\Common\Test\Base\BaseTypeTestCase; use Meritoo\LimeSurvey\ApiClient\Exception\UnknownMethodException; use Meritoo\LimeSurvey\ApiClient\Type\MethodType; @@ -24,7 +23,7 @@ class MethodTypeTest extends BaseTypeTestCase { public function testConstructorVisibilityAndArguments() { - static::assertHasNoConstructor(MethodType::class); + static::assertHasNoConstructor(MethodType::className); } /** @@ -33,7 +32,7 @@ class MethodTypeTest extends BaseTypeTestCase */ public function testGetValidatedMethodWithIncorrectMethod($incorrectMethod) { - $this->expectException(UnknownMethodException::class); + $this->setExpectedException(UnknownMethodException::className); MethodType::getValidatedMethod($incorrectMethod); } @@ -52,7 +51,7 @@ class MethodTypeTest extends BaseTypeTestCase */ public function testIsResultIterableWithIncorrectMethod($incorrectMethod) { - $this->expectException(UnknownMethodException::class); + $this->setExpectedException(UnknownMethodException::className); MethodType::isResultIterable($incorrectMethod); } @@ -70,10 +69,33 @@ class MethodTypeTest extends BaseTypeTestCase /** * Provides correct type of method * - * @return Generator + * @return array + * //return Generator */ public function provideMethod() { + return [ + [ + MethodType::ADD_RESPONSE, + ], + [ + MethodType::EXPORT_STATISTICS, + ], + [ + MethodType::GET_PARTICIPANT_PROPERTIES, + ], + [ + MethodType::LIST_SURVEYS, + ], + [ + SystemMethodType::GET_SESSION_KEY, + ], + [ + SystemMethodType::RELEASE_SESSION_KEY, + ], + ]; + + /* yield[ MethodType::ADD_RESPONSE, ]; @@ -97,15 +119,36 @@ class MethodTypeTest extends BaseTypeTestCase yield[ SystemMethodType::RELEASE_SESSION_KEY, ]; + */ } /** * Provides incorrect type of method * - * @return Generator + * @return array + * //return Generator */ public function provideIncorrectMethod() { + return [ + [ + '', + ], + [ + null, + ], + [ + true, + ], + [ + false, + ], + [ + 'lorem', + ], + ]; + + /* yield[ '', ]; @@ -125,15 +168,45 @@ class MethodTypeTest extends BaseTypeTestCase yield[ 'lorem', ]; + */ } /** * Provides type of method who result provided by the API is iterable and information if it's iterable * - * @return Generator + * @return array + * //return Generator */ public function provideIterableType() { + return [ + [ + MethodType::ADD_RESPONSE, + false, + ], + [ + MethodType::GET_PARTICIPANT_PROPERTIES, + false, + ], + [ + MethodType::LIST_PARTICIPANTS, + true, + ], + [ + MethodType::LIST_QUESTIONS, + true, + ], + [ + MethodType::LIST_SURVEYS, + true, + ], + [ + MethodType::LIST_USERS, + true, + ], + ]; + + /* yield[ MethodType::ADD_RESPONSE, false, @@ -163,6 +236,7 @@ class MethodTypeTest extends BaseTypeTestCase MethodType::LIST_USERS, true, ]; + */ } /** @@ -196,6 +270,26 @@ class MethodTypeTest extends BaseTypeTestCase */ public function provideTypeToVerify() { + return [ + [ + '', + false, + ], + [ + 'lorem', + false, + ], + [ + MethodType::ADD_RESPONSE, + true, + ], + [ + MethodType::GET_PARTICIPANT_PROPERTIES, + true, + ], + ]; + + /* yield[ '', false, @@ -215,5 +309,6 @@ class MethodTypeTest extends BaseTypeTestCase MethodType::GET_PARTICIPANT_PROPERTIES, true, ]; + */ } } diff --git a/tests/Type/ReasonTypeTest.php b/tests/Type/ReasonTypeTest.php index cb86bf8..ac28014 100644 --- a/tests/Type/ReasonTypeTest.php +++ b/tests/Type/ReasonTypeTest.php @@ -21,7 +21,7 @@ class ReasonTypeTest extends BaseTypeTestCase { public function testConstructorVisibilityAndArguments() { - static::assertHasNoConstructor(ReasonType::class); + static::assertHasNoConstructor(ReasonType::className); } /** @@ -50,6 +50,34 @@ class ReasonTypeTest extends BaseTypeTestCase */ public function provideTypeToVerify() { + return [ + [ + '', + false, + ], + [ + 'lorem', + false, + ], + [ + ReasonType::NOT_EXISTING_SURVEY_ID, + true, + ], + [ + ReasonType::NO_PARTICIPANTS_FOUND, + true, + ], + [ + ReasonType::NO_SURVEYS_FOUND, + true, + ], + [ + ReasonType::NO_TOKEN_TABLE, + true, + ], + ]; + + /* yield[ '', false, @@ -79,5 +107,6 @@ class ReasonTypeTest extends BaseTypeTestCase ReasonType::NO_TOKEN_TABLE, true, ]; + */ } } diff --git a/tests/Type/SystemMethodTypeTest.php b/tests/Type/SystemMethodTypeTest.php index 779c10c..aeb6c95 100644 --- a/tests/Type/SystemMethodTypeTest.php +++ b/tests/Type/SystemMethodTypeTest.php @@ -21,7 +21,7 @@ class SystemMethodTypeTest extends BaseTypeTestCase { public function testConstructorVisibilityAndArguments() { - static::assertHasNoConstructor(SystemMethodType::class); + static::assertHasNoConstructor(SystemMethodType::className); } /** @@ -48,6 +48,26 @@ class SystemMethodTypeTest extends BaseTypeTestCase */ public function provideTypeToVerify() { + return [ + [ + '', + false, + ], + [ + 'lorem', + false, + ], + [ + SystemMethodType::GET_SESSION_KEY, + true, + ], + [ + SystemMethodType::RELEASE_SESSION_KEY, + true, + ], + ]; + + /* yield[ '', false, @@ -67,5 +87,6 @@ class SystemMethodTypeTest extends BaseTypeTestCase SystemMethodType::RELEASE_SESSION_KEY, true, ]; + */ } }