diff --git a/CHANGELOG.md b/CHANGELOG.md index 03351e6..e8177d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Common and useful classes, methods, exceptions etc. +# 0.1.2 + +1. Documentation > Value Objects +2. Docker > improve performance +3. Utilities > Reflection > setPropertyValue() method > sets value of given property + # 0.1.1 1. TravisCI > run using PHP 7.2 too diff --git a/README.md b/README.md index 7b2e20e..94a0f44 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ composer require meritoo/common-library 2. [Collection of elements](docs/Collection-of-elements.md) 3. [Exceptions](docs/Static-methods.md) 4. [Static methods](docs/Static-methods.md) +5. [Value Objects](docs/Value-Objects.md) # Development diff --git a/VERSION b/VERSION index 17e51c3..d917d3e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.1 +0.1.2 diff --git a/docker-compose.yml b/docker-compose.yml index ef072c8..e13bca3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,12 +9,12 @@ services: build: context: ./docker/config args: - - TIMEZONE=$TIMEZONE + - TIMEZONE=${TIMEZONE} volumes: - - .:/project + - .:/project:cached composer: image: ${DOCKER_CONTAINER_OWNER}/${DOCKER_CONTAINER_PROJECT}-php container_name: ${DOCKER_CONTAINER_OWNER}-${DOCKER_CONTAINER_PROJECT}-composer entrypoint: composer volumes: - - .:/project + - .:/project:cached diff --git a/docs/Base-test-case.md b/docs/Base-test-case.md index f8b7b1a..b0a038b 100644 --- a/docs/Base-test-case.md +++ b/docs/Base-test-case.md @@ -48,5 +48,6 @@ class MimeTypesTest extends BaseTestCase 2. [Collection of elements](Collection-of-elements.md) 3. [Exceptions](Exceptions.md) 4. [Static methods](Static-methods.md) +5. [Value Objects](Value-Objects.md) [‹ Back to `Readme`](../README.md) diff --git a/docs/Collection-of-elements.md b/docs/Collection-of-elements.md index fb0a600..67fde9f 100644 --- a/docs/Collection-of-elements.md +++ b/docs/Collection-of-elements.md @@ -46,5 +46,6 @@ var_dump($simpleCollection->has('dolor')); // bool(true) 2. [**Collection of elements**](Collection-of-elements.md) 3. [Exceptions](Exceptions.md) 4. [Static methods](Static-methods.md) +5. [Value Objects](Value-Objects.md) [‹ Back to `Readme`](../README.md) diff --git a/docs/Exceptions.md b/docs/Exceptions.md index eda4362..87b12c1 100644 --- a/docs/Exceptions.md +++ b/docs/Exceptions.md @@ -57,5 +57,6 @@ class UnknownSimpleTypeException extends UnknownTypeException 2. [Collection of elements](Collection-of-elements.md) 3. [**Exceptions**](Exceptions.md) 4. [Static methods](Static-methods.md) +5. [Value Objects](Value-Objects.md) [‹ Back to `Readme`](../README.md) diff --git a/docs/Static-methods.md b/docs/Static-methods.md index 87cd6fe..e503fe9 100644 --- a/docs/Static-methods.md +++ b/docs/Static-methods.md @@ -19,5 +19,6 @@ var_dump($firstElement); // string(5) "lorem" 2. [Collection of elements](Collection-of-elements.md) 3. [Exceptions](Exceptions.md) 4. [**Static methods**](Static-methods.md) +5. [Value Objects](Value-Objects.md) [‹ Back to `Readme`](../README.md) diff --git a/docs/Value-Objects.md b/docs/Value-Objects.md new file mode 100644 index 0000000..d26a0ae --- /dev/null +++ b/docs/Value-Objects.md @@ -0,0 +1,53 @@ +# Meritoo Common Library + +Common and useful classes, methods, exceptions etc. + +# Value Objects + +Located in `Meritoo\Common\ValueObject` namespace. + +### Version + +##### Namespace + +`Meritoo\Common\ValueObject\Version` + +##### Info + +Represents version of software. Contains 3 properties: +1. `$majorPart` - the "major" part of version +2. `$minorPart` - the "minor" part of version +3. `$patchPart` - the "patch" part of version + +##### New instance + +New instance can be created using: + +1. Constructor: + + ```php + new Version(1, 0, 2); + ``` + +2. Static methods: + 1. `fromArray()` - creates new instance using given version as array + + ```php + Version::fromArray([1, 0, 2]); + ``` + + 2. `fromString()` - creates new instance using given version as string: + + ```php + Version::fromString('1.0.2'); + ``` + +# More + +1. [Base test case (with common methods and data providers)](Base-test-case.md) +2. [Collection of elements](Collection-of-elements.md) +3. [Exceptions](Exceptions.md) +4. [Static methods](Static-methods.md) +5. [**Value Objects**](Value-Objects.md) + +[‹ Back to `Readme`](../README.md) diff --git a/src/Exception/Reflection/NotExistingPropertyException.php b/src/Exception/Reflection/NotExistingPropertyException.php new file mode 100644 index 0000000..4b7484f --- /dev/null +++ b/src/Exception/Reflection/NotExistingPropertyException.php @@ -0,0 +1,33 @@ + + * @copyright Meritoo + */ +class NotExistingPropertyException extends \Exception +{ + /** + * Creates exception + * + * @param mixed $object Object that should contains given property + * @param string $property Name of the property + * @return NotExistingPropertyException + */ + public static function create($object, $property) + { + $template = 'Property \'%s\' does not exist in instance of class \'%s\'. Did you use proper name of property?'; + $message = sprintf($template, $property, get_class($object)); + + return new static($message); + } +} diff --git a/src/Utilities/Reflection.php b/src/Utilities/Reflection.php index 9f80b5c..b03a0bd 100644 --- a/src/Utilities/Reflection.php +++ b/src/Utilities/Reflection.php @@ -13,12 +13,8 @@ use Doctrine\Common\Util\Inflector; use Meritoo\Common\Collection\Collection; use Meritoo\Common\Exception\Reflection\CannotResolveClassNameException; use Meritoo\Common\Exception\Reflection\MissingChildClassesException; +use Meritoo\Common\Exception\Reflection\NotExistingPropertyException; use Meritoo\Common\Exception\Reflection\TooManyChildClassesException; -use ReflectionClass; -use ReflectionException; -use ReflectionMethod; -use ReflectionObject; -use ReflectionProperty; /** * Useful reflection methods @@ -34,21 +30,20 @@ class Reflection * @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. - * @throws ReflectionException * @return array */ public static function getMethods($class, $withoutInheritance = false) { $effect = []; - $reflection = new ReflectionClass($class); + $reflection = new \ReflectionClass($class); $methods = $reflection->getMethods(); if (!empty($methods)) { $className = self::getClassName($class); foreach ($methods as $method) { - if ($method instanceof ReflectionMethod) { + if ($method instanceof \ReflectionMethod) { if ($withoutInheritance && $className !== $method->class) { continue; } @@ -65,12 +60,11 @@ class Reflection * Returns constants of given class / object * * @param object|string $class The object or name of object's class - * @throws ReflectionException * @return array */ public static function getConstants($class) { - $reflection = new ReflectionClass($class); + $reflection = new \ReflectionClass($class); return $reflection->getConstants(); } @@ -80,7 +74,6 @@ class Reflection * Constants whose values are integers are considered only. * * @param object|string $class The object or name of object's class - * @throws ReflectionException * @return int|null */ public static function getMaxNumberConstant($class) @@ -107,12 +100,11 @@ class Reflection * * @param object|string $class The object or name of object's class * @param string $method Name of the method to find - * @throws ReflectionException * @return bool */ public static function hasMethod($class, $method) { - $reflection = new ReflectionClass($class); + $reflection = new \ReflectionClass($class); return $reflection->hasMethod($method); } @@ -122,12 +114,11 @@ class Reflection * * @param object|string $class The object or name of object's class * @param string $property Name of the property to find - * @throws ReflectionException * @return bool */ public static function hasProperty($class, $property) { - $reflection = new ReflectionClass($class); + $reflection = new \ReflectionClass($class); return $reflection->hasProperty($property); } @@ -137,12 +128,11 @@ class Reflection * * @param object|string $class The object or name of object's class * @param string $constant Name of the constant to find - * @throws ReflectionException * @return bool */ public static function hasConstant($class, $constant) { - $reflection = new ReflectionClass($class); + $reflection = new \ReflectionClass($class); return $reflection->hasConstant($constant); } @@ -152,12 +142,11 @@ class Reflection * * @param object|string $class The object or name of object's class * @param string $constant Name of the constant that contains a value - * @throws ReflectionException * @return mixed */ public static function getConstantValue($class, $constant) { - $reflection = new ReflectionClass($class); + $reflection = new \ReflectionClass($class); if (self::hasConstant($class, $constant)) { return $reflection->getConstant($constant); @@ -175,7 +164,6 @@ class Reflection * 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. - * @throws ReflectionException * @return mixed */ public static function getPropertyValue($object, $property, $force = false) @@ -197,9 +185,9 @@ class Reflection * Let's dig more and get proper value * * Required to avoid bug: - * ReflectionObject::__construct() expects parameter 1 to be object, null given + * \ReflectionObject::__construct() expects parameter 1 to be object, null given * (...) - * 4. at ReflectionObject->__construct (null) + * 4. at \ReflectionObject->__construct (null) * 5. at Reflection ::getPropertyValue (null, 'name', true) * 6. at ListService->getItemValue (object(Deal), 'project.name', '0') * @@ -224,17 +212,17 @@ class Reflection * Use \ReflectionObject class */ try { - $reflectionProperty = new ReflectionProperty($className, $property); + $reflectionProperty = new \ReflectionProperty($className, $property); $value = $reflectionProperty->getValue($object); - } catch (ReflectionException $exception) { + } catch (\ReflectionException $exception) { /* * 2nd try: * Look for the get / has / is methods */ - $class = new ReflectionObject($object); + $class = new \ReflectionObject($object); $valueFound = false; - if ($class->hasProperty($property) || $force) { + if ($force || $class->hasProperty($property)) { $property = Inflector::classify($property); $getterPrefixes = [ @@ -247,7 +235,7 @@ class Reflection $getterName = sprintf('%s%s', $prefix, $property); if ($class->hasMethod($getterName)) { - $method = new ReflectionMethod($object, $getterName); + $method = new \ReflectionMethod($object, $getterName); /* * Getter is not accessible publicly? @@ -292,7 +280,6 @@ class Reflection * @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. - * @throws ReflectionException * @return array */ public static function getPropertyValues($objects, $property, $force = false) @@ -395,13 +382,13 @@ class Reflection { $fullClassName = self::getClassName($source); - if (empty($fullClassName)) { + if (null === $fullClassName || '' === $fullClassName) { return ''; } $className = self::getClassName($source, true); - if ($className == $fullClassName) { + if ($className === $fullClassName) { return $className; } @@ -420,7 +407,7 @@ class Reflection $className = self::getClassName($source); $interfaces = class_implements($className); - return in_array($interface, $interfaces); + return in_array($interface, $interfaces, true); } /** @@ -438,7 +425,7 @@ class Reflection $parents = class_parents($childClassName); if (is_array($parents) && 0 < count($parents)) { - return in_array($parentClassName, $parents); + return in_array($parentClassName, $parents, true); } return false; @@ -448,23 +435,22 @@ class Reflection * 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 + * @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. - * @throws ReflectionException - * @return array|ReflectionProperty + * @return array|\ReflectionProperty */ public static function getProperties($source, $filter = null, $includeParents = false) { $className = self::getClassName($source); - $reflection = new ReflectionClass($className); + $reflection = new \ReflectionClass($className); if (null === $filter) { - $filter = ReflectionProperty::IS_PRIVATE - + ReflectionProperty::IS_PROTECTED - + ReflectionProperty::IS_PUBLIC - + ReflectionProperty::IS_STATIC; + $filter = \ReflectionProperty::IS_PRIVATE + + \ReflectionProperty::IS_PROTECTED + + \ReflectionProperty::IS_PUBLIC + + \ReflectionProperty::IS_STATIC; } $properties = $reflection->getProperties($filter); @@ -486,13 +472,12 @@ class Reflection * 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 - * @throws ReflectionException - * @return ReflectionClass|bool + * @return \ReflectionClass|bool */ public static function getParentClass($source) { $className = self::getClassName($source); - $reflection = new ReflectionClass($className); + $reflection = new \ReflectionClass($className); return $reflection->getParentClass(); } @@ -541,7 +526,7 @@ class Reflection */ $realClass = ClassUtils::getRealClass($oneClass); - if (in_array($realClass, $childClasses)) { + if (in_array($realClass, $childClasses, true)) { continue; } @@ -586,14 +571,13 @@ class Reflection } /** - * Returns property, the ReflectionProperty instance, of given object + * Returns property, the \ReflectionProperty instance, of given object * * @param array|object|string $class An array of objects, namespaces, object or namespace * @param string $property Name of the property - * @param int $filter (optional) Filter of properties. Uses ReflectionProperty class constants. + * @param int $filter (optional) Filter of properties. Uses \ReflectionProperty class constants. * By default all properties are allowed / processed. - * @throws ReflectionException - * @return null|ReflectionProperty + * @return null|\ReflectionProperty */ public static function getProperty($class, $property, $filter = null) { @@ -601,9 +585,9 @@ class Reflection $properties = self::getProperties($className, $filter); if (!empty($properties)) { - /* @var $reflectionProperty ReflectionProperty */ + /* @var $reflectionProperty \ReflectionProperty */ foreach ($properties as $reflectionProperty) { - if ($reflectionProperty->getName() == $property) { + if ($reflectionProperty->getName() === $property) { return $reflectionProperty; } } @@ -630,21 +614,21 @@ class Reflection /* * Oops, cannot resolve class */ - if (empty($className)) { + if (null === $className || '' === $className) { throw CannotResolveClassNameException::create($class); } /* * Oops, cannot resolve trait */ - if (empty($traitName)) { + if (null === $traitName || '' === $traitName) { throw new CannotResolveClassNameException($class, false); } - $reflection = new ReflectionClass($className); + $reflection = new \ReflectionClass($className); $traitsNames = $reflection->getTraitNames(); - $uses = in_array($traitName, $traitsNames); + $uses = in_array($traitName, $traitsNames, true); if (!$uses && $verifyParents) { $parentClassName = self::getParentClassName($className); @@ -662,13 +646,12 @@ class Reflection * If given class does not extend another, returns null. * * @param array|object|string $class An array of objects, namespaces, object or namespace - * @throws ReflectionException * @return string|null */ public static function getParentClassName($class) { $className = self::getClassName($class); - $reflection = new ReflectionClass($className); + $reflection = new \ReflectionClass($className); $parentClass = $reflection->getParentClass(); if (null === $parentClass || false === $parentClass) { @@ -677,4 +660,36 @@ class Reflection return $parentClass->getName(); } + + /** + * Sets value of given property + * + * @param mixed $object Object that should contains given property + * @param string $property Name of the property + * @param mixed $value Value of the property + * @throws NotExistingPropertyException + */ + public static function setPropertyValue($object, $property, $value) + { + $reflectionProperty = self::getProperty($object, $property); + + /* + * Oops, property does not exist + */ + if (null === $reflectionProperty) { + throw NotExistingPropertyException::create($object, $property); + } + + $notAccessible = $reflectionProperty->isPrivate() || $reflectionProperty->isProtected(); + + if ($notAccessible) { + $reflectionProperty->setAccessible(true); + } + + $reflectionProperty->setValue($object, $value); + + if ($notAccessible) { + $reflectionProperty->setAccessible(false); + } + } } diff --git a/tests/Utilities/ReflectionTest.php b/tests/Utilities/ReflectionTest.php index 06df8e8..69ce170 100644 --- a/tests/Utilities/ReflectionTest.php +++ b/tests/Utilities/ReflectionTest.php @@ -13,6 +13,7 @@ use Generator; use Meritoo\Common\Collection\Collection; use Meritoo\Common\Exception\Reflection\CannotResolveClassNameException; use Meritoo\Common\Exception\Reflection\MissingChildClassesException; +use Meritoo\Common\Exception\Reflection\NotExistingPropertyException; use Meritoo\Common\Exception\Reflection\TooManyChildClassesException; use Meritoo\Common\Test\Base\BaseTestCase; use Meritoo\Common\Test\Utilities\Reflection\A; @@ -474,6 +475,37 @@ class ReflectionTest extends BaseTestCase static::assertEquals('name', $property->getName()); } + /** + * @param mixed $object Object that should contains given property + * @param string $property Name of the property + * + * @dataProvider provideObjectAndNotExistingProperty + */ + public function testSetPropertyValueUsingNotExistingProperty($object, $property) + { + $this->setExpectedException(NotExistingPropertyException::class); + + $object = new \stdClass(); + Reflection::setPropertyValue($object, 'test', 'test test test'); + } + + /** + * @param mixed $object Object that should contains given property + * @param string $property Name of the property + * @param mixed $value Value of the property + * + * @dataProvider provideObjectPropertyAndValue + */ + public function testSetPropertyValue($object, $property, $value) + { + $oldValue = Reflection::getPropertyValue($object, $property); + Reflection::setPropertyValue($object, $property, $value); + $newValue = Reflection::getPropertyValue($object, $property); + + static::assertNotSame($oldValue, $value); + static::assertSame($newValue, $value); + } + /** * Provides invalid class and trait * @@ -501,4 +533,59 @@ class ReflectionTest extends BaseTestCase [], ]; } + + /** + * Provides object and name of not existing property + * + * @return Generator + */ + public function provideObjectAndNotExistingProperty() + { + yield[ + new \stdClass(), + 'test', + ]; + + yield[ + new A(), + 'test', + ]; + + yield[ + new B(), + 'firstName', + ]; + } + + /** + * Provides object, name of property and value of the property + * + * @return Generator + */ + public function provideObjectPropertyAndValue() + { + yield[ + new A(), + 'count', + 123, + ]; + + yield[ + new B(), + 'name', + 'test test', + ]; + + yield[ + new G(), + 'firstName', + 'Jane', + ]; + + yield[ + new G(), + 'lastName', + 'Smith', + ]; + } }