From ec9d39dc3437bfdbc70e8f7cd326ab392a95a019 Mon Sep 17 00:00:00 2001 From: Milos Tomic Date: Fri, 8 Apr 2016 09:20:27 +0200 Subject: [PATCH] initial --- .gitignore | 7 +- .php_cs | 23 ++++ .scrutinizer.yml | 2 + .travis.yml | 36 ++++++ README.md | 52 +++++++- autoload.php | 8 ++ composer.json | 33 +++++ phpunit.xml.dist | 40 ++++++ src/Tmilos/Value/AbstractEnum.php | 115 ++++++++++++++++++ src/Tmilos/Value/AbstractValue.php | 79 ++++++++++++ src/Tmilos/Value/Enum.php | 25 ++++ src/Tmilos/Value/Spec/Gender.php | 30 +++++ src/Tmilos/Value/Spec/IntValue.php | 22 ++++ src/Tmilos/Value/Spec/UuidValue.php | 23 ++++ src/Tmilos/Value/Value.php | 46 +++++++ .../Tmilos/Value/Tests/AbstractValueTest.php | 16 +++ tests/Tmilos/Value/Tests/Spec/GenderTest.php | 59 +++++++++ .../Tmilos/Value/Tests/Spec/IntValueTest.php | 23 ++++ .../Tmilos/Value/Tests/Spec/UuidValueTest.php | 16 +++ 19 files changed, 650 insertions(+), 5 deletions(-) create mode 100644 .php_cs create mode 100644 .scrutinizer.yml create mode 100644 .travis.yml create mode 100644 autoload.php create mode 100644 composer.json create mode 100644 phpunit.xml.dist create mode 100644 src/Tmilos/Value/AbstractEnum.php create mode 100644 src/Tmilos/Value/AbstractValue.php create mode 100644 src/Tmilos/Value/Enum.php create mode 100644 src/Tmilos/Value/Spec/Gender.php create mode 100644 src/Tmilos/Value/Spec/IntValue.php create mode 100644 src/Tmilos/Value/Spec/UuidValue.php create mode 100644 src/Tmilos/Value/Value.php create mode 100644 tests/Tmilos/Value/Tests/AbstractValueTest.php create mode 100644 tests/Tmilos/Value/Tests/Spec/GenderTest.php create mode 100644 tests/Tmilos/Value/Tests/Spec/IntValueTest.php create mode 100644 tests/Tmilos/Value/Tests/Spec/UuidValueTest.php diff --git a/.gitignore b/.gitignore index c422267..5da7515 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ composer.phar /vendor/ - -# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file -# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file -# composer.lock +composer.lock +/build/logs +/bin diff --git a/.php_cs b/.php_cs new file mode 100644 index 0000000..c116fb6 --- /dev/null +++ b/.php_cs @@ -0,0 +1,23 @@ +in('src') +; + +$header = << + +This source file is subject to the MIT license that is bundled +with this source code in the file LICENSE. +EOT; + +Symfony\CS\Fixer\Contrib\HeaderCommentFixer::setHeader($header); + +return Symfony\CS\Config\Config::create() + ->setUsingCache(false) + ->level(Symfony\CS\FixerInterface::SYMFONY_LEVEL) + ->fixers(array('-empty_return', '-phpdoc_no_empty_return', 'header_comment')) + ->finder($finder) +; diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..97b9fac --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,2 @@ +tools: + external_code_coverage: false diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..cb57638 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,36 @@ +language: php + +sudo: false + +cache: + directories: + - $HOME/.composer/cache + +php: + - 5.5 + - 5.6 + - 7.0 + - hhvm + +matrix: + fast_finish: true + allow_failures: + - php: hhvm + include: + - php: 5.5 + env: COMPOSER_FLAGS="--prefer-lowest" + +before_install: + - composer self-update + - composer --version + - wget http://get.sensiolabs.org/php-cs-fixer.phar -O php-cs-fixer.phar + +install: + - composer update $COMPOSER_FLAGS + +script: + - php php-cs-fixer.phar fix --dry-run -v + - bin/phpunit --coverage-clover build/logs/clover.xml + +after_script: + - php bin/coveralls -v diff --git a/README.md b/README.md index 33243f3..7b0325a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,52 @@ -# value +# Value object PHP lib + Abstract value and enum objects + +## Value objects + +```php +class IntValue extends AbstractValue +{ + public static function isValid($value) + { + return is_int($value); + } +} + +$x = new IntValue(10); // ok +print $x->getValue(); // 10 +$y = new IntValue(10); +var_dump($x->equal($y)); // true + +$z = new IntValue('20'); // throws \UnexpectedValueException +``` + +## Enum value object + +All class defined constants are valid values, and magic method with same constant name can be called to instantiate Enum with that value. + +```php +class Gender extends AbstractEnum +{ + const MALE = 'male'; + const FEMALE = 'female'; + + private static $titles = [ + self::MALE => 'gender.male', + self::FEMALE => 'gender.female', + ]; + + public function getTitle() + { + return self::$titles[$this->getValue()]; + } +} + +var_dump(Gender::all()); // ['male' => Gender() => 'female' => Gender() ] +var_dump(Gender::values()); // [ 0 => 'male', 1 => 'female' ] +$m = Gender::MALE(); +print $m->getValue(); // male +print $m->getTitle(); // female +var_dump(Gender::isValid('male')); // true +var_dump(Gender::isValid('something')); // false +``` diff --git a/autoload.php b/autoload.php new file mode 100644 index 0000000..2398823 --- /dev/null +++ b/autoload.php @@ -0,0 +1,8 @@ +=5.5.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.8", + "satooshi/php-coveralls": "~0.6", + "ramsey/uuid": "^3.3", + "moontoast/math": "~1.1" + }, + "config": { + "bin-dir": "bin" + }, + "prefer-stable": true, + "minimum-stability": "stable" +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..ea876e9 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,40 @@ + + + + + + + + + + + + tests + + + + + + + + src + + + + + diff --git a/src/Tmilos/Value/AbstractEnum.php b/src/Tmilos/Value/AbstractEnum.php new file mode 100644 index 0000000..422a8f5 --- /dev/null +++ b/src/Tmilos/Value/AbstractEnum.php @@ -0,0 +1,115 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Tmilos\Value; + +abstract class AbstractEnum extends AbstractValue implements Enum +{ + /** @var array */ + private static $constantsCache = []; + + /** @var array */ + private static $reflectionClassCache = []; + + /** + * Returns array of all Enum instances index by their values. + * + * @return Enum[] Constant name => Enum + */ + public static function all() + { + $result = []; + foreach (self::getConstants() as $k => $v) { + /** @var Value $object */ + $object = new static($v); + $result[$object->getValue()] = $object; + } + + return $result; + } + + /** + * Returns array of constant values. + * + * @return array + */ + public static function values() + { + return array_values(self::getConstants()); + } + + /** + * Check if is valid enum value. + * + * @param $value + * + * @return bool + */ + public static function isValid($value) + { + return in_array($value, self::getConstants(), true); + } + + /** + * Returns a value when called statically like so: MyEnum::SOME_VALUE() given SOME_VALUE is a class constant. + * + * @param string $name + * @param array $arguments + * + * @return static + * + * @throws \BadMethodCallException + */ + public static function __callStatic($name, array $arguments = []) + { + $array = self::getConstants(); + if (array_key_exists($name, $array)) { + $reflectionClass = self::getReflectionClass(); + array_unshift($arguments, $array[$name]); + + return $reflectionClass->newInstanceArgs($arguments); + } + + throw new \BadMethodCallException(sprintf('No static method or enum constant "%s" in class "%s"', $name, get_called_class())); + } + + /** + * @return \ReflectionClass + */ + private static function getReflectionClass() + { + $class = get_called_class(); + if (!array_key_exists($class, self::$reflectionClassCache)) { + self::inspect($class); + } + + return self::$reflectionClassCache[$class]; + } + + private static function getConstants() + { + $class = get_called_class(); + if (!array_key_exists($class, self::$constantsCache)) { + self::inspect($class); + } + + return self::$constantsCache[$class]; + } + + private static function inspect($class) + { + if (!array_key_exists($class, self::$reflectionClassCache)) { + $reflection = new \ReflectionClass($class); + self::$reflectionClassCache[$class] = $reflection; + self::$constantsCache[$class] = $reflection->getConstants(); + } + } +} diff --git a/src/Tmilos/Value/AbstractValue.php b/src/Tmilos/Value/AbstractValue.php new file mode 100644 index 0000000..491a100 --- /dev/null +++ b/src/Tmilos/Value/AbstractValue.php @@ -0,0 +1,79 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Tmilos\Value; + +abstract class AbstractValue implements Value +{ + /** @var int|string */ + private $value; + + /** + * ScalarValue constructor. + * + * @param int|string $value + * + * @throws \UnexpectedValueException + */ + public function __construct($value) + { + if (false === static::isValid($value)) { + throw new \UnexpectedValueException(sprintf('Value "%s" is not valid value of class %s', $value, get_called_class())); + } + $this->value = $value; + } + + /** + * @return int|string + */ + public function getValue() + { + return $this->value; + } + + /** + * @return string + */ + public function getTitle() + { + return (string) $this->value; + } + + /** + * @param mixed $other + * + * @return bool + */ + public function equals($other) + { + $value = $other instanceof Value ? $other->getValue() : $other; + + return $this->getValue() === $value; + } + + /** + * @param mixed $other + * + * @return bool + */ + public function notEquals($other) + { + return false === $this->equals($other); + } + + /** + * @return string + */ + public function __toString() + { + return $this->getTitle(); + } +} diff --git a/src/Tmilos/Value/Enum.php b/src/Tmilos/Value/Enum.php new file mode 100644 index 0000000..164303e --- /dev/null +++ b/src/Tmilos/Value/Enum.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Tmilos\Value; + +interface Enum extends Value +{ + /** + * @return Enum[] + */ + public static function all(); + + /** + * @return array + */ + public static function values(); +} diff --git a/src/Tmilos/Value/Spec/Gender.php b/src/Tmilos/Value/Spec/Gender.php new file mode 100644 index 0000000..b6b97eb --- /dev/null +++ b/src/Tmilos/Value/Spec/Gender.php @@ -0,0 +1,30 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Tmilos\Value\Spec; + +use Tmilos\Value\AbstractEnum; + +class Gender extends AbstractEnum +{ + const MALE = 'male'; + const FEMALE = 'female'; + + private static $titles = [ + self::MALE => 'gender.male', + self::FEMALE => 'gender.female', + ]; + + public function getTitle() + { + return self::$titles[$this->getValue()]; + } +} diff --git a/src/Tmilos/Value/Spec/IntValue.php b/src/Tmilos/Value/Spec/IntValue.php new file mode 100644 index 0000000..5722301 --- /dev/null +++ b/src/Tmilos/Value/Spec/IntValue.php @@ -0,0 +1,22 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Tmilos\Value\Spec; + +use Tmilos\Value\AbstractValue; + +class IntValue extends AbstractValue +{ + public static function isValid($value) + { + return is_int($value); + } +} diff --git a/src/Tmilos/Value/Spec/UuidValue.php b/src/Tmilos/Value/Spec/UuidValue.php new file mode 100644 index 0000000..60ff645 --- /dev/null +++ b/src/Tmilos/Value/Spec/UuidValue.php @@ -0,0 +1,23 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Tmilos\Value\Spec; + +use Ramsey\Uuid\UuidInterface; +use Tmilos\Value\AbstractValue; + +class UuidValue extends AbstractValue +{ + public static function isValid($value) + { + return $value instanceof UuidInterface; + } +} diff --git a/src/Tmilos/Value/Value.php b/src/Tmilos/Value/Value.php new file mode 100644 index 0000000..4e513c3 --- /dev/null +++ b/src/Tmilos/Value/Value.php @@ -0,0 +1,46 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Tmilos\Value; + +interface Value +{ + /** + * @param $value + * + * @return bool + */ + public static function isValid($value); + + /** + * @return mixed + */ + public function getValue(); + + /** + * @return string + */ + public function getTitle(); + + /** + * @param mixed $other + * + * @return bool + */ + public function equals($other); + + /** + * @param mixed $other + * + * @return bool + */ + public function notEquals($other); +} diff --git a/tests/Tmilos/Value/Tests/AbstractValueTest.php b/tests/Tmilos/Value/Tests/AbstractValueTest.php new file mode 100644 index 0000000..19bcc74 --- /dev/null +++ b/tests/Tmilos/Value/Tests/AbstractValueTest.php @@ -0,0 +1,16 @@ +assertTrue($reflection->implementsInterface(Value::class)); + } + +} diff --git a/tests/Tmilos/Value/Tests/Spec/GenderTest.php b/tests/Tmilos/Value/Tests/Spec/GenderTest.php new file mode 100644 index 0000000..2892604 --- /dev/null +++ b/tests/Tmilos/Value/Tests/Spec/GenderTest.php @@ -0,0 +1,59 @@ +assertInternalType('array', $all); + $this->assertCount(2, $all); + $this->assertArrayHasKey(Gender::MALE, $all); + $this->assertArrayHasKey(Gender::FEMALE, $all); + } + + public function test_instantiates_with_constant_name_method() + { + /** @var Gender $m */ + $m = Gender::MALE(); + $this->assertInstanceOf(Gender::class, $m); + $this->assertEquals(Gender::MALE, $m->getValue()); + $this->assertEquals('gender.male', $m->getTitle()); + } + + public function test_values_method_return_array_of_values() + { + $values = Gender::values(); + var_dump($values); + $this->assertInternalType('array', $values); + $this->assertEquals([Gender::MALE, Gender::FEMALE], $values); + } + + public function test_titles() + { + $this->assertEquals('gender.male', Gender::MALE()->getTitle()); + $this->assertEquals('gender.female', Gender::FEMALE()->getTitle()); + } + + public function test_equals() + { + $this->assertTrue(Gender::MALE()->equals(Gender::MALE())); + $this->assertTrue(Gender::FEMALE()->equals(Gender::FEMALE())); + } + + public function test_not_equals() + { + $this->assertTrue(Gender::MALE()->notEquals(Gender::FEMALE())); + $this->assertTrue(Gender::MALE()->notEquals(Gender::FEMALE())); + } + + public function test_casts_to_string_with_title() + { + $v = Gender::MALE(); + $s = (string)$v; + $this->assertSame($v->getTitle(), $s); + } +} diff --git a/tests/Tmilos/Value/Tests/Spec/IntValueTest.php b/tests/Tmilos/Value/Tests/Spec/IntValueTest.php new file mode 100644 index 0000000..60d510a --- /dev/null +++ b/tests/Tmilos/Value/Tests/Spec/IntValueTest.php @@ -0,0 +1,23 @@ +assertSame($expected, $v->getValue()); + } + + /** + * @expectedException \UnexpectedValueException + * @expectedExceptionMessage Value "10" is not valid value of class Tmilos\Value\Spec\IntValue + */ + public function test_construct_error_with_non_integer() + { + new IntValue('10'); + } +} diff --git a/tests/Tmilos/Value/Tests/Spec/UuidValueTest.php b/tests/Tmilos/Value/Tests/Spec/UuidValueTest.php new file mode 100644 index 0000000..503ae1e --- /dev/null +++ b/tests/Tmilos/Value/Tests/Spec/UuidValueTest.php @@ -0,0 +1,16 @@ +assertSame($uuid, $v->getValue()); + } +}