├── .gitignore
├── docs
├── _config.yml
├── index.md
├── ru
│ ├── index.md
│ └── release.md
└── release.md
├── tests
├── autoload.php
├── Core
│ ├── ExceptionTest.php
│ ├── ConfigAwareTraitTest.php
│ ├── DateTimeTest.php
│ ├── InstanceableTraitTest.php
│ ├── CollectionTest.php
│ ├── TypedCollectionTest.php
│ ├── SingletonTraitTest.php
│ ├── HasSchemaTraitTest.php
│ ├── StdGetSetTraitTest.php
│ ├── ConfigSaveTest.php
│ ├── ConfigTest.php
│ ├── StdTest.php
│ ├── ExceptionsTest.php
│ ├── TypedCollectionTraitTest.php
│ ├── StdGetSetValidateSanitizeTraitTest.php
│ ├── ObjectAsArrayTraitTest.php
│ └── CollectionTraitTest.php
├── phpunit.xml
├── Di
│ ├── ContainerExceptionTest.php
│ ├── ContainerEntryNotFoundExceptionTest.php
│ ├── ContainerTest.php
│ └── ContainerResolveTest.php
└── Reflection
│ └── ReflectionHelpersTest.php
├── src
├── Reflection
│ ├── Exception.php
│ └── ReflectionHelpers.php
├── Storages
│ ├── StorageInterface.php
│ ├── StorageAwareInterface.php
│ ├── KeyValueStorageAwareInterface.php
│ ├── SingleValueStorageAwareInterface.php
│ ├── SingleValueStorageInterface.php
│ └── KeyValueStorageInterface.php
├── Di
│ ├── Container.php
│ ├── ContainerException.php
│ ├── ContainerEntryNotFoundException.php
│ ├── ContainerInterface.php
│ └── ContainerTrait.php
└── Core
│ ├── HasInnerCastingInterface.php
│ ├── HasInnerValidationInterface.php
│ ├── HasInnerSanitizationInterface.php
│ ├── SingletonInterface.php
│ ├── HasRequiredInterface.php
│ ├── TypedCollectionInterface.php
│ ├── Exception.php
│ ├── InstanceableInterface.php
│ ├── StdGetSetInterface.php
│ ├── ConfigAwareInterface.php
│ ├── Collection.php
│ ├── InstanceableTrait.php
│ ├── TypedCollection.php
│ ├── InstanceableByConfigInterface.php
│ ├── HasSchemaInterface.php
│ ├── DateTime.php
│ ├── ConfigAwareTrait.php
│ ├── ArrayCastingInterface.php
│ ├── SingletonTrait.php
│ ├── Exceptions.php
│ ├── ObjectAsArrayInterface.php
│ ├── StdGetSetTrait.php
│ ├── CollectionInterface.php
│ ├── Std.php
│ ├── HasSchemaTrait.php
│ ├── TypedCollectionTrait.php
│ ├── Config.php
│ ├── StdGetSetValidateSanitizeTrait.php
│ ├── ObjectAsArrayTrait.php
│ └── CollectionTrait.php
├── .travis.yml
├── README.md
├── composer.json
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-slate
--------------------------------------------------------------------------------
/tests/autoload.php:
--------------------------------------------------------------------------------
1 | $this->getCode(), 'message' => $this->getMessage()];
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/src/Core/InstanceableInterface.php:
--------------------------------------------------------------------------------
1 | fromArray($data);
24 | }
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/Core/InstanceableTrait.php:
--------------------------------------------------------------------------------
1 | fromArray($data);
23 | }
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/Storages/StorageAwareInterface.php:
--------------------------------------------------------------------------------
1 | assertSame('{"code":0,"message":""}', json_encode($e));
15 |
16 | $e = new Exception('Some error');
17 | $this->assertSame('{"code":0,"message":"Some error"}', json_encode($e));
18 |
19 | $e = new Exception('Some error', 42);
20 | $this->assertSame('{"code":42,"message":"Some error"}', json_encode($e));
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/Core/DateTime.php:
--------------------------------------------------------------------------------
1 | json_encode,
18 | * which is a value of any type other than a resource.
19 | * @since 5.4.0
20 | */
21 | public function jsonSerialize()
22 | {
23 | return $this->format('c');
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/Core/ConfigAwareTrait.php:
--------------------------------------------------------------------------------
1 | config = $config;
26 | return $this;
27 | }
28 |
29 | /**
30 | * @return \Runn\Core\Config|null
31 | */
32 | public function getConfig(): ?Config
33 | {
34 | return $this->config;
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/Core/ArrayCastingInterface.php:
--------------------------------------------------------------------------------
1 | assertNull($obj->getConfig());
18 |
19 | $config = new Config(['foo' => 'bar']);
20 |
21 | $ret = $obj->setConfig($config);
22 | $this->assertSame($config, $obj->getConfig());
23 | $this->assertSame($obj, $ret);
24 |
25 | $ret = $obj->setConfig(null);
26 | $this->assertNull($obj->getConfig());
27 | $this->assertSame($obj, $ret);
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Core/DateTimeTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(DateTime::class, $datetime);
17 | $this->assertInstanceOf(\DateTime::class, $datetime);
18 | $this->assertInstanceOf(\JsonSerializable::class, $datetime);
19 |
20 | $this->assertEquals(new \DateTime($time), $datetime);
21 | }
22 |
23 | public function testJson()
24 | {
25 | $time = date('Y-m-d H:i:s', time());
26 | $datetime = new DateTime($time);
27 |
28 | $this->assertSame('"' . date('c', strtotime($time)) . '"', json_encode($datetime));
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/Core/SingletonTrait.php:
--------------------------------------------------------------------------------
1 | =7.4",
29 | "psr/container": "*"
30 | },
31 | "require-dev": {
32 | "phpunit/phpunit": "8.*"
33 | },
34 | "autoload": {
35 | "psr-4": {
36 | "Runn\\": "src/"
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/Storages/SingleValueStorageInterface.php:
--------------------------------------------------------------------------------
1 | id = $id;
26 | parent::__construct($previous);
27 | }
28 |
29 | /**
30 | * Container entry identifier
31 | *
32 | * @return string
33 | */
34 | public function getId(): string
35 | {
36 | return $this->id;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/tests/phpunit.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 | Core
10 |
11 |
12 | Di
13 |
14 |
15 | Reflection
16 |
17 |
18 |
19 |
20 |
21 | ../src
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/tests/Core/InstanceableTraitTest.php:
--------------------------------------------------------------------------------
1 | x = $x;
19 | $this->y = $y;
20 | }
21 | }
22 |
23 | class InstanceableTraitTest extends TestCase
24 | {
25 |
26 | public function testWoArguments()
27 | {
28 | $obj = testClass1::instance();
29 |
30 | $this->assertInstanceOf(testClass1::class, $obj);
31 | $this->assertNull($obj->x);
32 | $this->assertNull($obj->y);
33 | }
34 |
35 |
36 | public function testWArguments()
37 | {
38 | $obj = testClass1::instance(1, -1);
39 |
40 | $this->assertInstanceOf(testClass1::class, $obj);
41 | $this->assertEquals(1, $obj->x);
42 | $this->assertEquals(-1, $obj->y);
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/Core/Exceptions.php:
--------------------------------------------------------------------------------
1 | fromArray($data);
32 | }
33 | }
34 |
35 | /**
36 | * @param $value
37 | * @return $this
38 | */
39 | public function add($value)
40 | {
41 | if ($value instanceof self) {
42 | foreach ($value as $v) {
43 | $this->collectionAdd($v);
44 | }
45 | } else {
46 | $this->collectionAdd($value);
47 | }
48 | return $this;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Di/ContainerExceptionTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(ContainerException::class, $exception);
16 | $this->assertInstanceOf(ContainerExceptionInterface::class, $exception);
17 | $this->assertEmpty($exception->getMessage());
18 | $this->assertEmpty($exception->getCode());
19 | $this->assertEmpty($exception->getPrevious());
20 |
21 | $prev = new \Exception;
22 | $exception = new ContainerException($prev);
23 | $this->assertInstanceOf(ContainerException::class, $exception);
24 | $this->assertInstanceOf(ContainerExceptionInterface::class, $exception);
25 | $this->assertEmpty($exception->getMessage());
26 | $this->assertEmpty($exception->getCode());
27 | $this->assertSame($prev, $exception->getPrevious());
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/Storages/KeyValueStorageInterface.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Collection::class, $collection);
17 | $this->assertInstanceOf(CollectionInterface::class, $collection);
18 | $this->assertCount(0, $collection);
19 |
20 | $collection = new Collection([100, 200, 300, [400, 500]]);
21 |
22 | $this->assertInstanceOf(Collection::class, $collection);
23 | $this->assertInstanceOf(CollectionInterface::class, $collection);
24 | $this->assertCount(4, $collection);
25 |
26 | $this->assertEquals(
27 | [100, 200, 300, new Collection([400, 500])],
28 | $collection->values()
29 | );
30 | $this->assertEquals(
31 | [100, 200, 300, [400, 500]],
32 | $collection->toArray()
33 | );
34 | $this->assertEquals(100, $collection[0]);
35 | $this->assertEquals(200, $collection[1]);
36 | $this->assertEquals(300, $collection[2]);
37 | $this->assertEquals(new Collection([400, 500]), $collection[3]);
38 | }
39 |
40 | public function testConstructWithZeroKey()
41 | {
42 | $collection = new Collection([1 => '1', 0 => '0', 2 => '2']);
43 | $this->assertCount(3, $collection);
44 | $this->assertSame([1 => '1', 0 => '0', 2 => '2'], $collection->values());
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/Core/StdGetSetTrait.php:
--------------------------------------------------------------------------------
1 | innerIsSet($key);
29 | }
30 |
31 | public function __unset($key)
32 | {
33 | $this->innerUnSet($key);
34 | }
35 |
36 | public function __get($key)
37 | {
38 | if (!$this->innerIsSet($key)) {
39 | $debug = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 1)[0];
40 | if ($debug['function'] == '__get' && $debug['object'] === $this && $debug['type'] == '->') {
41 | $property = $debug['args']['0'];
42 | $line = (file($debug['file'])[$debug['line'] - 1]);
43 | if (preg_match('~\-\>' . $property . '\-\>.+\=~', $line, $m)) {
44 | $this->__data[$property] = new static;
45 | return $this->__data[$property];
46 | }
47 | }
48 | }
49 | return $this->innerGet($key);
50 | }
51 |
52 | public function __set($key, $val)
53 | {
54 | $this->innerSet($key, $val);
55 | }
56 |
57 | }
--------------------------------------------------------------------------------
/tests/Di/ContainerEntryNotFoundExceptionTest.php:
--------------------------------------------------------------------------------
1 | expectException(\ArgumentCountError::class);
16 | new ContainerEntryNotFoundException;
17 | }
18 |
19 | public function testConstruct()
20 | {
21 | $exception = new ContainerEntryNotFoundException('foo');
22 | $this->assertInstanceOf(ContainerEntryNotFoundException::class, $exception);
23 | $this->assertInstanceOf(ContainerException::class, $exception);
24 | $this->assertInstanceOf(ContainerExceptionInterface::class, $exception);
25 | $this->assertSame('foo', $exception->getId());
26 | $this->assertEmpty($exception->getMessage());
27 | $this->assertEmpty($exception->getCode());
28 | $this->assertEmpty($exception->getPrevious());
29 |
30 | $prev = new \Exception;
31 | $exception = new ContainerEntryNotFoundException('foo', $prev);
32 | $this->assertInstanceOf(ContainerException::class, $exception);
33 | $this->assertInstanceOf(ContainerExceptionInterface::class, $exception);
34 | $this->assertSame('foo', $exception->getId());
35 | $this->assertEmpty($exception->getMessage());
36 | $this->assertEmpty($exception->getCode());
37 | $this->assertSame($prev, $exception->getPrevious());
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Core/TypedCollectionTest.php:
--------------------------------------------------------------------------------
1 | 1], (object)['bar' => 2]])
15 | extends TypedCollection {
16 | public static function getType() { return \stdClass::class;}
17 | };
18 |
19 | $this->assertInstanceOf(TypedCollection::class, $collection);
20 | $this->assertSame(\stdClass::class, $collection->getType());
21 | $this->assertCount(2, $collection);
22 |
23 | $this->assertEquals((object)['foo' => 1], $collection[0]);
24 | $this->assertEquals((object)['bar' => 2], $collection[1]);
25 | }
26 |
27 | public function testGetType()
28 | {
29 | $collection = new class(['type' => (object)['value' => 42]])
30 | extends TypedCollection {
31 | public static function getType() { return \stdClass::class;}
32 | };
33 |
34 | $this->assertSame(\stdClass::class, $collection->getType());
35 | $this->assertSame(42, $collection['type']->value);
36 | }
37 |
38 | public function testConstructTypeMismatch()
39 | {
40 | $this->expectException(Exception::class);
41 | $this->expectExceptionMessage('Typed collection type mismatch');
42 | $collection = new class(['foo' => 1, 'bar' => 2])
43 | extends TypedCollection {
44 | public static function getType() { return \stdClass::class;}
45 | };
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/Core/CollectionInterface.php:
--------------------------------------------------------------------------------
1 | x = $x;
22 | $this->y = $y;
23 | }
24 | }
25 |
26 | class SingletonTraitTest extends TestCase
27 | {
28 |
29 | public function testWoArguments()
30 | {
31 | $obj1 = testClass1::instance();
32 | $obj2 = testClass1::instance();
33 |
34 | $this->assertSame($obj2, $obj1);
35 | }
36 |
37 | public function testDifferentInstances()
38 | {
39 | $obj1 = testClass1::instance();
40 | $obj2 = testClass2::instance(1, -1);
41 |
42 | $this->assertNotSame($obj1, $obj2);
43 | $this->assertInstanceOf(testClass1::class, $obj1);
44 | $this->assertInstanceOf(testClass2::class, $obj2);
45 | }
46 |
47 | public function testWArguments()
48 | {
49 | $obj1 = testClass2::instance(1, -1);
50 | $obj2 = testClass2::instance(1, -1);
51 |
52 | $this->assertSame($obj2, $obj1);
53 | $this->assertEquals(1, $obj1->x);
54 | $this->assertEquals(-1, $obj1->y);
55 | }
56 |
57 | public function testConstruct()
58 | {
59 | $reflector = new \ReflectionClass(testClass1::class);
60 | $this->assertFalse( $reflector->getMethod('__construct')->isPublic() );
61 | }
62 |
63 | public function testClone()
64 | {
65 | $reflector = new \ReflectionClass(testClass1::class);
66 | $this->assertFalse( $reflector->getMethod('__clone')->isPublic() );
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/docs/release.md:
--------------------------------------------------------------------------------
1 | 7.4.11, 8.0.11
2 | ==============
3 | * PHP 7.2 support is dropped
4 | * PHP 7.3 support is dropped
5 | * PHP 8.0 support is added
6 | * PSR Container compatibility fix
7 |
8 | 7.2.10, 7.3.10, 7.4.10
9 | ======================
10 | * Container implementation refactoring
11 | * `Container::resolve()` method is added
12 |
13 | 7.2.9, 7.3.9, 7.4.9
14 | ===================
15 | * Minor fix with `ReflectionType`
16 |
17 | 7.2.8, 7.3.8, 7.4.8
18 | ===================
19 | * Tests are using PHPUnit 8 framework now
20 |
21 | 7.2.7, 7.3.7, 7.4.7
22 | ===================
23 | * Runn\Core\DateTime class is added
24 |
25 | 7.2.6, 7.3.6, 7.4.6
26 | ===================
27 | * PHP 7.0 support is dropped
28 | * PHP 7.1 support is dropped
29 | * PHP 7.4 support is added
30 |
31 | 7.0.5, 7.1.5, 7.2.5
32 | ===================
33 | * PSR DI Container is added
34 |
35 | 7.0.4, 7.1.4, 7.2.4
36 | ===================
37 | * More PHP 7.1 compatibility
38 | * License identifier update
39 |
40 | 7.0.3, 7.1.3, 7.2.3
41 | ===================
42 | * Strict type check in TypedCollection is added
43 | * notgetters() and notsetters() protected methods are added instead of $__notgetters and $__notsetters
44 |
45 | 7.0.2, 7.1.2, 7.2.2
46 | ===================
47 | * InstanceableInterface and its implementation by InstanceableTrait are added
48 | * \#3 $__notgetters and $__notsetters properties are added in ObjectAsArray for getters and setters ignore
49 | * ObjectAsArrayInterface::isEmpty() method is renamed to empty()
50 | * ConfigAwareInterface interface and its implementation by ConfigAwareTrait are added
51 | * HasSchemaInterface and HasSchemaTrait are added for classes with data schema
52 | * ReflectionHelpers class is added
53 | * ObjectAsArrayTrait::needCasting() fix
54 |
55 | 7.0.1, 7.1.1, 7.2.1
56 | ===================
57 | * Zero keys in Objects-as-Arrays fix
58 |
59 | 7.0.0, 7.1.0, 7.2.0
60 | ===================
61 | * First released version. Code is transfered from Running.FM project
--------------------------------------------------------------------------------
/src/Core/Std.php:
--------------------------------------------------------------------------------
1 | getRequiredKeys() as $required) {
47 | if (!isset($this->$required)) {
48 | $errors->add(new Exception('Required property "' . $required . '" is missing'));
49 | }
50 | }
51 | if (!$errors->empty()) {
52 | throw $errors;
53 | }
54 | return true;
55 | }
56 |
57 | /**
58 | * Std constructor.
59 | * @param iterable|null $data
60 | * @throws \Runn\Core\Exceptions
61 | */
62 | public function __construct(iterable $data = null)
63 | {
64 | $errors = new Exceptions();
65 |
66 | if (null !== $data) {
67 | try {
68 | $this->fromArray($data);
69 | } catch (Exceptions $errors) {}
70 | }
71 |
72 | try {
73 | $this->checkRequired();
74 | } catch (\Throwable $e) {
75 | $errors->add($e);
76 | }
77 |
78 | if (!$errors->empty()) {
79 | throw $errors;
80 | }
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/docs/ru/release.md:
--------------------------------------------------------------------------------
1 | 7.4.11, 8.0.11
2 | ==============
3 | * Прекращена поддержка PHP 7.2
4 | * Прекращена поддержка PHP 7.3
5 | * Добавлена поддержка PHP 8.0
6 | * Исправлена ошибка совместимости с новой версией PSR Container
7 |
8 | 7.2.10, 7.3.10, 7.4.10
9 | ======================
10 | * Рефакторинг реализации контейнера
11 | * Добавлен метод `Container::resolve()`
12 |
13 | 7.2.9, 7.3.9, 7.4.9
14 | ===================
15 | * Исправление ошибки с `ReflectionType`
16 |
17 | 7.2.8, 7.3.8, 7.4.8
18 | ===================
19 | * Тесты теперь используют фреймворк PHPUnit 8
20 |
21 | 7.2.7, 7.3.7, 7.4.7
22 | ===================
23 | * Добавлен класс Runn\Core\DateTime
24 |
25 | 7.2.6, 7.3.6, 7.4.6
26 | ===================
27 | * Прекращена поддержка PHP 7.0
28 | * Прекращена поддержка PHP 7.1
29 | * Добавлена поддержка PHP 7.4
30 |
31 | 7.0.5, 7.1.5, 7.2.5
32 | ===================
33 | * Добавлена реализация PSR DI Container
34 |
35 | 7.0.4, 7.1.4, 7.2.4
36 | ===================
37 | * Улучшена совместимость с PHP 7.1
38 | * Обновлен идентификатор лицензии библиотеки
39 |
40 | 7.0.3, 7.1.3, 7.2.3
41 | ===================
42 | * Добавлен строгий (без наследников) режим проверки типов в TypedCollection
43 | * Вместо свойств $__notgetters и $__notsetters добавлены защищенные методы notgetters() и notsetters()
44 |
45 | 7.0.2, 7.1.2, 7.2.2
46 | ===================
47 | * Добавлен интерфейс InstanceableInterface и его эталонная реализация InstanceableTrait
48 | * \#3 В ObjectAsArray добавлены свойства $__notgetters и $__notsetters и их использования для игнорирования геттеров и сеттеров
49 | * Метод ObjectAsArrayInterface::isEmpty() переименован в empty()
50 | * Добавлен интерфейс ConfigAwareInterface и его эталонная реализация ConfigAwareTrait
51 | * Для классов, имеющих схему данных, добавлены HasSchemaInterface и HasSchemaTrait
52 | * Добавлен класс ReflectionHelpers
53 | * Исправление логики ObjectAsArrayTrait::needCasting()
54 |
55 | 7.0.1, 7.1.1, 7.2.1
56 | ===================
57 | * Исправление ошибки с нулевыми ключами в Objects-as-Arrays
58 |
59 | 7.0.0, 7.1.0, 7.2.0
60 | ===================
61 | * Первая релизная версия. Перенос кода из проекта Running.FM
--------------------------------------------------------------------------------
/src/Core/HasSchemaTrait.php:
--------------------------------------------------------------------------------
1 | $def) {
38 | $value = $this->prepareValueBySchemaDef($key, $def);
39 | $data[$key] = $value;
40 | }
41 | }
42 | return $data;
43 | }
44 |
45 | /**
46 | * @param string $key
47 | * @param mixed $def
48 | * @return mixed
49 | */
50 | protected function prepareValueBySchemaDef($key, $def)
51 | {
52 | if (!empty($def['class'])) {
53 |
54 | $class = $def['class'];
55 | unset($def['class']);
56 |
57 | // check if $def has only digital keys
58 | if (ctype_digit(implode('', array_keys($def)))) {
59 | return new $class(...array_values($def));
60 |
61 | // or not - it has string keys?
62 | } else {
63 | $ctor = ReflectionHelpers::getClassMethodArgs($class, '__construct');
64 | $args = ReflectionHelpers::prepareArgs($ctor, $def);
65 | return new $class(...array_values($args));
66 | }
67 | } else {
68 | return $def;
69 | }
70 | }
71 |
72 | /**
73 | * @param iterable $schema
74 | * @return $this
75 | */
76 | public function fromSchema(iterable $schema = null)
77 | {
78 | $data = $this->prepareDataBySchema($schema);
79 | $this->fromArray($data);
80 | return $this;
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/Core/TypedCollectionTrait.php:
--------------------------------------------------------------------------------
1 | isValueTypeValid($value)) {
59 | throw new Exception('Typed collection type mismatch');
60 | }
61 | }
62 |
63 | public function append($value)
64 | {
65 | $this->checkValueType($value);
66 | return $this->collectionAppend($value);
67 | }
68 |
69 | public function prepend($value)
70 | {
71 | $this->checkValueType($value);
72 | return $this->collectionPrepend($value);
73 | }
74 |
75 | public function innerSet($key, $value)
76 | {
77 | $this->checkValueType($value);
78 | $this->collectionInnerSet($key, $value);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/tests/Core/HasSchemaTraitTest.php:
--------------------------------------------------------------------------------
1 | foo = $foo;
18 | $this->bar = $bar;
19 | }
20 | }
21 |
22 | class testClass implements ObjectAsArrayInterface, HasSchemaInterface
23 | {
24 | protected static $schema = [
25 | 'x' => ['class' => Std::class, ['foo' => 'bar', 'baz' => 42]],
26 | 'y' => ['class' => testConstructClass::class, 'bar' => 'test2', 'foo' => 'test1'],
27 | 'z' => ['class' => testConstructClass::class, 'foo' => 'test1'],
28 | 42,
29 | ];
30 |
31 | use ObjectAsArrayTrait;
32 | use HasSchemaTrait;
33 | }
34 |
35 | class HasSchemaTraitTest extends TestCase
36 | {
37 |
38 | public function testInterfaces()
39 | {
40 | $obj = new testClass();
41 |
42 | $this->assertInstanceOf(ObjectAsArrayInterface::class, $obj);
43 | $this->assertInstanceOf(HasSchemaInterface::class, $obj);
44 | }
45 |
46 | public function testGetSchema()
47 | {
48 | $obj = new testClass();
49 | $reflector = new \ReflectionProperty(testClass::class, 'schema');
50 | $reflector->setAccessible(true);
51 | $this->assertSame($reflector->getValue($obj), $obj->getSchema());
52 | }
53 |
54 | public function testFromSchema()
55 | {
56 | $obj = new testClass();
57 | $schema = $obj->getSchema();
58 |
59 | $obj->fromSchema($schema);
60 |
61 | $this->assertInstanceOf(Std::class, $obj['x']);
62 | $this->assertSame('bar', $obj['x']->foo);
63 | $this->assertSame(42, $obj['x']->baz);
64 |
65 | $this->assertInstanceOf(testConstructClass::class, $obj['y']);
66 | $this->assertSame('test1', $obj['y']->foo);
67 | $this->assertSame('test2', $obj['y']->bar);
68 |
69 | $this->assertInstanceOf(testConstructClass::class, $obj['z']);
70 | $this->assertSame('test1', $obj['z']->foo);
71 | $this->assertSame('bar', $obj['z']->bar);
72 |
73 | $this->assertSame(42, $obj[0]);
74 |
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/tests/Core/StdGetSetTraitTest.php:
--------------------------------------------------------------------------------
1 | foo = 42;
22 | $obj->bar = 'bla-bla';
23 | $obj->baz = [1, 2, 3];
24 |
25 | $this->assertInstanceOf(StdGetSetInterface::class, $obj);
26 |
27 | $this->assertCount(3, $obj);
28 |
29 | $this->assertTrue(isset($obj->foo));
30 | $this->assertTrue(isset($obj['foo']));
31 | $this->assertTrue(isset($obj->bar));
32 | $this->assertTrue(isset($obj['bar']));
33 | $this->assertTrue(isset($obj->baz));
34 | $this->assertTrue(isset($obj['baz']));
35 |
36 | $this->assertEquals(42, $obj->foo);
37 | $this->assertEquals(42, $obj['foo']);
38 | $this->assertEquals($obj->foo, $obj['foo']);
39 | $this->assertEquals('bla-bla', $obj->bar);
40 | $this->assertEquals('bla-bla', $obj['bar']);
41 | $this->assertEquals($obj->bar, $obj['bar']);
42 | $this->assertEquals([1, 2, 3], $obj->baz);
43 | $this->assertEquals([1, 2, 3], $obj['baz']);
44 | $this->assertEquals($obj->baz, $obj['baz']);
45 |
46 | unset($obj->baz);
47 |
48 | $this->assertCount(2, $obj);
49 | $this->assertFalse(isset($obj->baz));
50 | $this->assertFalse(isset($obj['baz']));
51 | }
52 |
53 | public function testChain()
54 | {
55 | $obj = new testClass();
56 | $this->assertFalse(isset($obj->foo));
57 | $this->assertFalse(isset($obj->foo->bar));
58 | $this->assertTrue(empty($obj->foo));
59 | $this->assertTrue(empty($obj->foo->bar));
60 |
61 | $obj->foo->bar = 'baz';
62 | $this->assertTrue(isset($obj->foo));
63 | $this->assertTrue(isset($obj->foo->bar));
64 | $this->assertFalse(empty($obj->foo));
65 | $this->assertFalse(empty($obj->foo->bar));
66 |
67 | $this->assertTrue($obj->foo instanceof testClass);
68 | $this->assertEquals((new testClass)->fromArray(['bar' => 'baz']), $obj->foo);
69 | $this->assertEquals('baz', $obj->foo->bar);
70 |
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/tests/Core/ConfigSaveTest.php:
--------------------------------------------------------------------------------
1 | file = $file;
18 | }
19 |
20 | public function load() {
21 | $this->data = unserialize(file_get_contents($this->file));
22 | }
23 |
24 | public function save() {
25 | file_put_contents($this->file, serialize($this->data));
26 | }
27 |
28 | public function get()
29 | {
30 | return $this->data;
31 | }
32 |
33 | public function set($value)
34 | {
35 | $this->data = $value;
36 | }
37 | }
38 |
39 | class ConfigSaveTest extends TestCase
40 | {
41 |
42 | const TMP_PATH = __DIR__ . '/tmp';
43 |
44 | protected function setUp(): void
45 | {
46 | mkdir(self::TMP_PATH);
47 | file_put_contents(self::TMP_PATH . '/savetest.config', serialize(['application' => ['name' => 'Test Application']]));
48 | }
49 |
50 | public function testEmptyStorageSave()
51 | {
52 | $config = new Config();
53 | $config->foo = 'bar';
54 |
55 | $this->expectException(Exception::class);
56 | $this->expectExceptionMessage('Empty config storage');
57 | $config->save();
58 | }
59 |
60 | public function testSave()
61 | {
62 | $config = new Config(new SimpleFileStorage(self::TMP_PATH . '/savetest.config'));
63 | $this->assertEquals('Test Application', $config->application->name);
64 |
65 | $config->foo = 'bar';
66 | $config->baz = [1, 2, 3];
67 | $config->songs = ['Hey' => 'Jude', 'I just' => ['call' => ['to' => 'say']]];
68 |
69 | $config->save();
70 |
71 | $expectedText = serialize(['application' => ['name' => 'Test Application'], 'foo' => 'bar', 'baz' => [1, 2, 3], 'songs' => ['Hey' => 'Jude', 'I just' => ['call' => ['to' => 'say']]]]);
72 | $this->assertEquals(
73 | str_replace("\r\n", "\n", $expectedText),
74 | str_replace("\r\n", "\n", file_get_contents(self::TMP_PATH . '/savetest.config'))
75 | );
76 | }
77 |
78 | protected function tearDown(): void
79 | {
80 | unlink(self::TMP_PATH . '/savetest.config');
81 | rmdir(self::TMP_PATH);
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/Core/Config.php:
--------------------------------------------------------------------------------
1 | setStorage($arg)->load();
49 | } else {
50 | parent::__construct($arg);
51 | }
52 | }
53 |
54 | /**
55 | * @param \Runn\Storages\SingleValueStorageInterface|null $storage
56 | * @return $this
57 | */
58 | public function setStorage(?SingleValueStorageInterface $storage)
59 | {
60 | $this->storage = $storage;
61 | return $this;
62 | }
63 |
64 | /**
65 | * @return \Runn\Storages\SingleValueStorageInterface|null
66 | */
67 | public function getStorage(): ?SingleValueStorageInterface
68 | {
69 | return $this->storage;
70 | }
71 |
72 | /**
73 | * Loads config from storage
74 | *
75 | * @return $this
76 | * @throws \Runn\Core\Exception
77 | */
78 | public function load()
79 | {
80 | if (empty($this->storage)) {
81 | throw new Exception('Empty config storage');
82 | }
83 | $storage = $this->getStorage();
84 | $storage->load();
85 | return $this->fromArray($storage->get());
86 | }
87 |
88 | /**
89 | * @return $this
90 | * @throws \Runn\Core\Exception
91 | */
92 | public function save()
93 | {
94 | if (empty($this->storage)) {
95 | throw new Exception('Empty config storage');
96 | }
97 | $storage = $this->getStorage();
98 | $storage->set($this->toArray());
99 | $storage->save();
100 | return $this;
101 | }
102 |
103 | public function get()
104 | {
105 | throw new \BadMethodCallException();
106 | }
107 |
108 | public function set($value)
109 | {
110 | throw new \BadMethodCallException();
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/src/Reflection/ReflectionHelpers.php:
--------------------------------------------------------------------------------
1 | getParameters();
33 | if (empty($params)) {
34 | $cache[$class][$method] = [];
35 | return [];
36 | }
37 | $args = [];
38 | foreach ($params as $param) {
39 | $args[$param->name] = [
40 | 'optional' => $param->isOptional(),
41 | 'variadic' => $param->isVariadic(),
42 | ];
43 | if ($param->isOptional() && $param->isDefaultValueAvailable()) {
44 | $args[$param->name]['default'] = $param->getDefaultValue();
45 | }
46 | if ($param->hasType() && $param->getType() instanceof \ReflectionNamedType) {
47 | $args[$param->name]['type'] = $param->getType()->getName();
48 | }
49 | }
50 | $cache[$class][$method] = $args;
51 | return $args;
52 | }
53 |
54 | /**
55 | * Returns the argument list by object and its method name
56 | *
57 | * @param object $obj
58 | * @param string $method
59 | * @return array
60 | * @throws \Runn\Reflection\Exception
61 | */
62 | public static function getObjectMethodArgs($obj, string $method): array
63 | {
64 | if (!is_object($obj)) {
65 | throw new Exception('$obj is not an object');
66 | }
67 | return static::getClassMethodArgs(get_class($obj), $method);
68 | }
69 |
70 | /**
71 | * @param array $args
72 | * @param array|\Runn\Core\ObjectAsArrayInterface $data
73 | * @return array
74 | * @throws \Runn\Core\Exceptions
75 | */
76 | public static function prepareArgs(array $args, $data): array
77 | {
78 | $ret = [];
79 | $errors = new Exceptions;
80 |
81 | foreach ($args as $name => $def) {
82 | if ((is_array($data) && array_key_exists($name, $data)) || isset($data[$name])) {
83 | $ret[$name] = $data[$name];
84 | continue;
85 | }
86 | if (array_key_exists('optional', $def) && true === $def['optional'] && array_key_exists('default', $def)) {
87 | $ret[$name] = $def['default'];
88 | continue;
89 | }
90 | $errors[] = new Exception('Argument "' . $name . '" has not set or default value');
91 | }
92 | if (!$errors->empty()) {
93 | throw $errors;
94 | }
95 | return $ret;
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/src/Core/StdGetSetValidateSanitizeTrait.php:
--------------------------------------------------------------------------------
1 | notsetters()) && method_exists($this, $setMethod) ) {
38 | $this->$setMethod($val);
39 | } else {
40 |
41 | $validateMethod = 'validate' . ucfirst($key);
42 |
43 | if (method_exists($this, $validateMethod)) {
44 |
45 | $errors = new Exceptions();
46 |
47 | try {
48 | $validateResult = $this->$validateMethod($val);
49 | if (false === $validateResult) {
50 | return;
51 | }
52 | if ($validateResult instanceof \Generator) {
53 | foreach ($validateResult as $error) {
54 | if ($error instanceof \Throwable) {
55 | $errors->add($error);
56 | }
57 | }
58 | }
59 | } catch (\Throwable $e) {
60 | $errors->add($e);
61 | }
62 |
63 | if (!$errors->empty()) {
64 | throw $errors;
65 | }
66 |
67 | }
68 |
69 | $sanitizeMethod = 'sanitize' . ucfirst($key);
70 | if (method_exists($this, $sanitizeMethod)) {
71 | $val = $this->$sanitizeMethod($val);
72 | }
73 |
74 | if (null === $key) {
75 | $this->__data[] = $val;
76 | } else {
77 | $this->__data[$key] = $val;
78 | }
79 | }
80 | }
81 |
82 | /**
83 | * @param iterable $data
84 | * @return $this
85 | * @throws \Runn\Core\Exceptions
86 | */
87 | public function merge(iterable $data)
88 | {
89 | if ($data instanceof ArrayCastingInterface) {
90 | $data = $data->toArray();
91 | }
92 |
93 | $errors = new Exceptions();
94 |
95 | foreach ($data as $key => $value) {
96 | try {
97 | if ($this->needCasting($key, $value)) {
98 | $value = $this->innerCast($key, $value);
99 | }
100 | $this->innerSet($key, $value);
101 | } catch (\Throwable $e) {
102 | $errors->add($e);
103 | }
104 | }
105 |
106 | if (!$errors->empty()) {
107 | throw $errors;
108 | }
109 |
110 | return $this;
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/tests/Core/ConfigTest.php:
--------------------------------------------------------------------------------
1 | data = ['foo' => 42, 'bar' => 'bla-bla', 'baz' => [1, 2, 3]];
22 | }
23 |
24 | public function save() {}
25 |
26 | public function get()
27 | {
28 | return $this->data;
29 | }
30 |
31 | public function set($value)
32 | {
33 | $this->data = $value;
34 | }
35 | }
36 |
37 | class ConfigTest extends TestCase
38 | {
39 |
40 | public function testConstructWData()
41 | {
42 | $obj = new Config(['foo' => 42, 'bar' => 'bla-bla', 'baz' => [1, 2, 3]]);
43 |
44 | $this->assertInstanceOf(ObjectAsArrayInterface::class, $obj);
45 | $this->assertInstanceOf(StdGetSetInterface::class, $obj);
46 | $this->assertInstanceOf(HasInnerValidationInterface::class, $obj);
47 | $this->assertInstanceOf(HasInnerSanitizationInterface::class, $obj);
48 | $this->assertInstanceOf(Std::class, $obj);
49 | $this->assertInstanceOf(SingleValueStorageInterface::class, $obj);
50 | $this->assertInstanceOf(Config::class, $obj);
51 |
52 | $this->assertCount(3, $obj);
53 | $this->assertEquals(42, $obj->foo);
54 | $this->assertEquals('bla-bla', $obj->bar);
55 | $this->assertEquals(new Config([1, 2, 3]), $obj->baz);
56 | }
57 |
58 | public function testConstructWFile()
59 | {
60 | $obj = new Config(new FakeStorage());
61 |
62 | $this->assertInstanceOf(Config::class, $obj);
63 | $this->assertCount(3, $obj);
64 | $this->assertEquals(42, $obj->foo);
65 | $this->assertEquals('bla-bla', $obj->bar);
66 | $this->assertEquals(new Config([1, 2, 3]), $obj->baz);
67 | }
68 |
69 | public function testSetGetStorage()
70 | {
71 | $obj = new Config();
72 |
73 | $this->assertNull($obj->getStorage());
74 |
75 | $file = new FakeStorage();
76 | $ret = $obj->setStorage($file);
77 |
78 | $this->assertSame($obj, $ret);
79 | $this->assertEquals($file, $obj->getStorage());
80 |
81 | $ret = $obj->setStorage(null);
82 |
83 | $this->assertSame($obj, $ret);
84 | $this->assertNull($obj->getStorage());
85 | }
86 |
87 | public function testMagicStorage()
88 | {
89 | $obj = new Config();
90 |
91 | $this->assertNull($obj->storage);
92 | $this->assertNull($obj->getStorage());
93 |
94 | $obj->storage = 'test.txt';
95 |
96 | $this->assertEquals('test.txt', $obj->storage);
97 | $this->assertNull($obj->getStorage());
98 |
99 | $obj->setStorage(new FakeStorage());
100 |
101 | $this->assertEquals('test.txt', $obj->storage);
102 | $this->assertEquals(new FakeStorage(), $obj->getStorage());
103 | }
104 |
105 | public function testLoadEmptyFile()
106 | {
107 | $obj = new Config();
108 |
109 | $this->expectException(Exception::class);
110 | $this->expectExceptionMessage('Empty config storage');
111 | $obj->load();
112 | }
113 |
114 | public function testLoad()
115 | {
116 | $file = new FakeStorage();
117 | $file->load();
118 |
119 | $obj = new Config();
120 | $obj->setStorage(new FakeStorage())->load();
121 |
122 | $this->assertEquals($file, $obj->getStorage());
123 |
124 | $this->assertCount(3, $obj);
125 | $this->assertEquals(42, $obj->foo);
126 | $this->assertEquals('bla-bla', $obj->bar);
127 | $this->assertEquals(new Config([1, 2, 3]), $obj->baz);
128 |
129 | $obj->foo = null;
130 | unset($obj->bar);
131 | $obj->baz = 'nothing';
132 |
133 | $obj->load();
134 |
135 | $this->assertCount(3, $obj);
136 | $this->assertEquals(42, $obj->foo);
137 | $this->assertEquals('bla-bla', $obj->bar);
138 | $this->assertEquals(new Config([1, 2, 3]), $obj->baz);
139 | }
140 |
141 | public function testSet()
142 | {
143 | $config = new Config();
144 |
145 | $this->expectException(\BadMethodCallException::class);
146 | $config->set(42);
147 | }
148 |
149 | public function testGet()
150 | {
151 | $config = new Config(new FakeStorage());
152 |
153 | $this->expectException(\BadMethodCallException::class);
154 | $data = $config->get();
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/tests/Di/ContainerTest.php:
--------------------------------------------------------------------------------
1 | expectException(\TypeError::class);
19 | $container->set('id', 'foo');
20 | }
21 |
22 | public function testSetInvalidArgument2()
23 | {
24 | $container = new Container();
25 |
26 | $this->expectException(\TypeError::class);
27 | $container->id = 'foo';
28 | }
29 |
30 | public function testSetInvalidArgument3()
31 | {
32 | $container = new Container();
33 |
34 | $this->expectException(\TypeError::class);
35 | $container['id'] = 'foo';
36 | }
37 |
38 | public function testGetInvalidId1()
39 | {
40 | try {
41 | $container = new Container();
42 | $res = $container->get('test');
43 | } catch (ContainerEntryNotFoundException $e) {
44 | $this->assertSame('test', $e->getId());
45 | return;
46 | }
47 | $this->fail();
48 | }
49 |
50 | public function testGetInvalidId2()
51 | {
52 | try {
53 | $container = new Container();
54 | $res = $container->test;
55 | } catch (ContainerEntryNotFoundException $e) {
56 | $this->assertSame('test', $e->getId());
57 | return;
58 | }
59 | $this->fail();
60 | }
61 |
62 | public function testGetInvalidId3()
63 | {
64 | try {
65 | $container = new Container();
66 | $res = $container['test'];
67 | } catch (ContainerEntryNotFoundException $e) {
68 | $this->assertSame('test', $e->getId());
69 | return;
70 | }
71 | $this->fail();
72 | }
73 |
74 | public function testGetWithException1()
75 | {
76 | $exception = new \Exception;
77 | try {
78 | $container = new Container();
79 | $container->set('test', function () use ($exception) {
80 | throw $exception;
81 | });
82 | $res = $container->get('test');
83 | } catch (ContainerException $e) {
84 | $this->assertSame($exception, $e->getPrevious());
85 | return;
86 | }
87 | $this->fail();
88 | }
89 |
90 | public function testGetWithException2()
91 | {
92 | $exception = new \Exception;
93 | try {
94 | $container = new Container();
95 | $container->set('test', function () use ($exception) {
96 | throw $exception;
97 | });
98 | $res = $container->test;
99 | } catch (ContainerException $e) {
100 | $this->assertSame($exception, $e->getPrevious());
101 | return;
102 | }
103 | $this->fail();
104 | }
105 |
106 | public function testGetWithException3()
107 | {
108 | $exception = new \Exception;
109 | try {
110 | $container = new Container();
111 | $container->set('test', function () use ($exception) {
112 | throw $exception;
113 | });
114 | $res = $container['test'];
115 | } catch (ContainerException $e) {
116 | $this->assertSame($exception, $e->getPrevious());
117 | return;
118 | }
119 | $this->fail();
120 | }
121 |
122 | public function testSetGet()
123 | {
124 | $container = new Container();
125 |
126 | $this->assertFalse($container->has('test'));
127 |
128 | $container->set('test', function () {return 42;});
129 |
130 | $this->assertTrue($container->has('test'));
131 | $this->assertTrue(isset($container->test));
132 | $this->assertTrue(isset($container['test']));
133 |
134 | $this->assertSame(42, $container->get('test'));
135 | $this->assertSame(42, $container->test);
136 | $this->assertSame(42, $container['test']);
137 |
138 | $container->test = function () {return 24;};
139 |
140 | $this->assertSame(24, $container->get('test'));
141 | $this->assertSame(24, $container->test);
142 | $this->assertSame(24, $container['test']);
143 |
144 | $container['test'] = function () {return 12;};
145 |
146 | $this->assertSame(12, $container->get('test'));
147 | $this->assertSame(12, $container->test);
148 | $this->assertSame(12, $container['test']);
149 | }
150 |
151 | public function testSingleton()
152 | {
153 | $container = new Container();
154 |
155 | $this->assertFalse($container->has('test'));
156 |
157 | $container->singleton('test', function () {return new Std(['foo' => 'bar']);});
158 |
159 | $this->assertTrue($container->has('test'));
160 |
161 | $obj1 = $container->get('test');
162 | $this->assertEquals(new Std(['foo' => 'bar']), $obj1);
163 |
164 | $obj2 = $container->get('test');
165 | $this->assertEquals(new Std(['foo' => 'bar']), $obj2);
166 |
167 | $this->assertSame($obj1, $obj2);
168 | }
169 |
170 | }
171 |
--------------------------------------------------------------------------------
/tests/Di/ContainerResolveTest.php:
--------------------------------------------------------------------------------
1 | foo = $foo;
25 | $this->bar = $bar;
26 | $this->baz = $baz;
27 | }
28 | }
29 |
30 | class testWithUndefinedDependency {
31 | public $foo;
32 | public $bar;
33 | public function __construct(Baz $baz) {
34 | $this->baz = $baz;
35 | }
36 | }
37 |
38 | class Foo111 {}
39 | class Foo11 {
40 | public $foo111;
41 | public function __construct(Foo111 $foo111)
42 | {
43 | $this->foo111 = $foo111;
44 | }
45 | }
46 | class Foo1 {
47 | public $foo11;
48 | public function __construct(Foo11 $foo11)
49 | {
50 | $this->foo11 = $foo11;
51 | }
52 | }
53 |
54 | class ContainerResolveTest extends TestCase
55 | {
56 |
57 |
58 | public function testResolveInvalidId()
59 | {
60 | try {
61 | $container = new Container();
62 | $res = $container->resolve('test');
63 | } catch (ContainerEntryNotFoundException $e) {
64 | $this->assertSame('test', $e->getId());
65 | return;
66 | }
67 | $this->fail();
68 | }
69 |
70 |
71 | public function testResolveWithException()
72 | {
73 | $exception = new \Exception;
74 | try {
75 | $container = new Container();
76 | $container->set('test', function () use ($exception) {
77 | throw $exception;
78 | });
79 | $res = $container->resolve('test');
80 | } catch (ContainerException $e) {
81 | $this->assertSame($exception, $e->getPrevious());
82 | return;
83 | }
84 | $this->fail();
85 | }
86 |
87 |
88 | public function testSetGet()
89 | {
90 | $container = new Container();
91 | $container->set('test', function () {return 42;});
92 | $this->assertSame(42, $container->resolve('test'));
93 | }
94 |
95 | public function testSingleton()
96 | {
97 | $container = new Container();
98 | $container->singleton('test', function () {return new Std(['foo' => 'bar']);});
99 |
100 | $obj1 = $container->resolve('test');
101 | $this->assertEquals(new Std(['foo' => 'bar']), $obj1);
102 |
103 | $obj2 = $container->resolve('test');
104 | $this->assertEquals(new Std(['foo' => 'bar']), $obj2);
105 |
106 | $this->assertSame($obj1, $obj2);
107 | }
108 |
109 | public function testResolveExistingClassWithoutConstructor()
110 | {
111 | $container = new Container();
112 |
113 | $res1 = $container->resolve(testWithoutConstructor::class);
114 | $this->assertEquals(new testWithoutConstructor(), $res1);
115 |
116 | $res2 = $container->resolve(testWithoutConstructor::class);
117 | $this->assertEquals(new testWithoutConstructor(), $res2);
118 |
119 | $this->assertSame($res1, $res2);
120 | }
121 |
122 | public function testResolveExistingClassWithConstructorWithoutArgs()
123 | {
124 | $container = new Container();
125 |
126 | $res1 = $container->resolve(testWithConstructorWithoutArgs::class);
127 | $this->assertEquals(new testWithConstructorWithoutArgs(), $res1);
128 |
129 | $res2 = $container->resolve(testWithConstructorWithoutArgs::class);
130 | $this->assertEquals(new testWithConstructorWithoutArgs(), $res2);
131 |
132 | $this->assertSame($res1, $res2);
133 | }
134 |
135 | public function testResolveWithDependecies()
136 | {
137 | $container = new Container();
138 |
139 | $res1 = $container->resolve(testWithDependencies::class);
140 | $this->assertInstanceOf(testWithDependencies::class, $res1);
141 | $this->assertEquals(new Foo(), $res1->foo);
142 | $this->assertEquals(new Bar(), $res1->bar);
143 | $this->assertNull($res1->baz);
144 |
145 | $res2 = $container->resolve(testWithDependencies::class);
146 | $this->assertSame($res1, $res2);
147 | }
148 |
149 | public function testResolveUndefinedDependecy()
150 | {
151 | try {
152 | $container = new Container();
153 | $res = $container->resolve(testWithUndefinedDependency::class);
154 | } catch (ContainerEntryNotFoundException $e) {
155 | $this->assertSame(__NAMESPACE__ . '\\Baz', $e->getId());
156 | return;
157 | }
158 | $this->fail();
159 | }
160 |
161 | public function testResolveNestedDependecies()
162 | {
163 | $container = new Container();
164 |
165 | $res1 = $container->resolve(Foo1::class);
166 | $this->assertInstanceOf(Foo1::class, $res1);
167 | $this->assertEquals(new Foo11(new Foo111()), $res1->foo11);
168 | $this->assertEquals(new Foo111, $res1->foo11->foo111);
169 |
170 | $res2 = $container->resolve(Foo1::class);
171 | $this->assertSame($res1, $res2);
172 | }
173 |
174 | }
175 |
--------------------------------------------------------------------------------
/src/Di/ContainerTrait.php:
--------------------------------------------------------------------------------
1 | innerSet($id, $resolver);
35 | unset($this->resolved[$id]);
36 | unset($this->singletons[$id]);
37 | return $this;
38 | }
39 |
40 | /**
41 | * Sets a resolver for entry of the container by its identifier as singleton.
42 | *
43 | * @param string $id Identifier of the entry to look for.
44 | * @param callable $resolver Function that resolves the entry and returns it.
45 | *
46 | * @return $this.
47 | */
48 | public function singleton(string $id, callable $resolver)
49 | {
50 | $this->innerSet($id, $resolver);
51 | unset($this->resolved[$id]);
52 | $this->singletons[$id] = true;
53 | return $this;
54 | }
55 |
56 | /**
57 | * Finds an entry of the container by its identifier and returns it.
58 | *
59 | * @param string $id Identifier of the entry to look for.
60 | *
61 | * @throws NotFoundExceptionInterface No entry was found for **this** identifier.
62 | * @throws ContainerExceptionInterface Error while retrieving the entry.
63 | *
64 | * @return mixed Entry.
65 | */
66 | public function get(string $id)
67 | {
68 | if (!$this->has($id)) {
69 | throw new ContainerEntryNotFoundException($id);
70 | }
71 | try {
72 | if (isset($this->singletons[$id])) {
73 | if (!isset($this->resolved[$id])) {
74 | $this->resolved[$id] = $this->innerGet($id)();
75 | }
76 | return $this->resolved[$id];
77 | }
78 | return $this->innerGet($id)();
79 | } catch (\Throwable $e) {
80 | throw new ContainerException($e);
81 | }
82 | }
83 |
84 | /**
85 | * Returns true if the container can return an entry for the given identifier.
86 | * Returns false otherwise.
87 | *
88 | * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
89 | * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
90 | *
91 | * @param string $id Identifier of the entry to look for.
92 | *
93 | * @return bool
94 | */
95 | public function has(string $id): bool
96 | {
97 | return isset($this[$id]);
98 | }
99 |
100 | /**
101 | * Resolves and returns an entry with all ones dependencies
102 | *
103 | * @param string $id
104 | * @return mixed
105 | * @throws ContainerEntryNotFoundException
106 | * @throws ContainerException
107 | */
108 | public function resolve(string $id)
109 | {
110 | if ($this->has($id)) {
111 | return $this->get($id);
112 | }
113 | // Check if $id is available class name
114 | if (class_exists($id, true)) {
115 |
116 | // Check if class has not constructor
117 | if (!method_exists($id, '__construct')) {
118 | $resolved = new $id;
119 | $this->set($id, static function () use ($resolved) { return $resolved; } );
120 | return $this->get($id);
121 | }
122 |
123 | // Check if constructor has not arguments
124 | $constructArgs = ReflectionHelpers::getClassMethodArgs($id, '__construct');
125 | if (empty($constructArgs)) {
126 | $resolved = new $id;
127 | $this->set($id, static function () use ($resolved) { return $resolved; } );
128 | return $this->get($id);
129 | }
130 |
131 | $args = [];
132 | foreach ($constructArgs as $neededArg) {
133 | try {
134 | $args[] = $this->resolve($neededArg['type']);
135 | } catch (ContainerEntryNotFoundException $e) {
136 | if ($neededArg['optional']) {
137 | $args[] = $neededArg['default'];
138 | } else {
139 | throw $e;
140 | }
141 | }
142 | }
143 |
144 | $resolved = new $id(...$args);
145 | $this->set($id, static function () use ($resolved) { return $resolved; } );
146 | return $this->get($id);
147 |
148 | }
149 | throw new ContainerEntryNotFoundException($id);
150 | }
151 |
152 | /**
153 | * @param $key
154 | * @param $val
155 | */
156 | public function __set($key, $val)
157 | {
158 | $this->set($key, $val);
159 | }
160 |
161 | /**
162 | * @param $key
163 | * @return mixed
164 | * @throws ContainerEntryNotFoundException
165 | * @throws ContainerException
166 | */
167 | public function __get($key)
168 | {
169 | return $this->get($key);
170 | }
171 |
172 | /**
173 | * @param mixed $offset
174 | * @param mixed $value
175 | */
176 | public function offsetSet($offset, $value)
177 | {
178 | $this->set($offset, $value);
179 | }
180 |
181 | /**
182 | * @param mixed $offset
183 | * @return mixed
184 | * @throws ContainerEntryNotFoundException
185 | * @throws ContainerException
186 | */
187 | public function offsetGet($offset)
188 | {
189 | return $this->get($offset);
190 | }
191 |
192 | }
193 |
--------------------------------------------------------------------------------
/tests/Core/StdTest.php:
--------------------------------------------------------------------------------
1 | 0;
19 | }
20 | protected function sanitizeBar($val) {
21 | return trim($val);
22 | }
23 | protected function setBaz($val) {
24 | $this->__data['baz'] = $val*2;
25 | }
26 | }
27 |
28 | class testClassWExceptions extends Std
29 | {
30 | protected function validateFoo($val) {
31 | if ($val < 0) {
32 | throw new Exception('Minus');
33 | }
34 | return true;
35 | }
36 | protected function validateBar($val) {
37 | if (strlen($val) < 3) {
38 | yield new Exception('Small');
39 | }
40 | if (false !== strpos($val, '0')) {
41 | yield new Exception('Zero');
42 | }
43 | return true;
44 | }
45 | protected function validateBaz($val) {
46 | if ($val > 100) {
47 | throw new Exception('Large');
48 | }
49 | return true;
50 | }
51 | }
52 |
53 | class testClassWithRequired extends Std
54 | {
55 | protected static $required = ['foo', 'bar'];
56 | protected function validateBaz() {
57 | throw new Exception('Invalid baz');
58 | }
59 | }
60 |
61 | class StdTest extends TestCase
62 | {
63 |
64 | public function testEmptyConstruct()
65 | {
66 | $obj = new Std;
67 |
68 | $this->assertInstanceOf(ObjectAsArrayInterface::class, $obj);
69 | $this->assertInstanceOf(StdGetSetInterface::class, $obj);
70 | $this->assertInstanceOf(HasInnerValidationInterface::class, $obj);
71 | $this->assertInstanceOf(HasInnerSanitizationInterface::class, $obj);
72 | $this->assertInstanceOf(HasRequiredInterface::class, $obj);
73 |
74 | $this->assertCount(0, $obj);
75 | }
76 |
77 | public function testValidConstruct()
78 | {
79 | $obj = new Std(['foo' => 42, 'bar' => 'bla-bla', 'baz' => [1, 2, 3]]);
80 |
81 | $this->assertInstanceOf(ObjectAsArrayInterface::class, $obj);
82 | $this->assertInstanceOf(StdGetSetInterface::class, $obj);
83 | $this->assertInstanceOf(HasInnerValidationInterface::class, $obj);
84 | $this->assertInstanceOf(HasInnerSanitizationInterface::class, $obj);
85 | $this->assertInstanceOf(HasRequiredInterface::class, $obj);
86 |
87 | $this->assertCount(3, $obj);
88 |
89 | $this->assertEquals(42, $obj->foo);
90 | $this->assertEquals('bla-bla', $obj->bar);
91 | $this->assertEquals(new Std([1, 2, 3]), $obj->baz);
92 | }
93 |
94 | public function testGetRequiredKeys()
95 | {
96 | $obj = new testClassWithRequired(['foo' => 1, 'bar' => 2]);
97 | $obj->requiredKeys = 42;
98 |
99 | $this->assertSame(['foo', 'bar'], $obj->getRequiredKeys());
100 | $this->assertSame(42, $obj->requiredKeys);
101 | }
102 |
103 | public function testMerge()
104 | {
105 | $obj1 = new Std(['foo' => 1]);
106 | $obj1->merge(['bar' => 2]);
107 | $this->assertEquals(1, $obj1->foo);
108 | $this->assertEquals(2, $obj1->bar);
109 | $this->assertEquals(new Std(['foo' => 1, 'bar' => 2]), $obj1);
110 |
111 | $obj2 = new Std(['foo' => 11]);
112 | $obj2->merge(new Std(['bar' => 21]));
113 | $this->assertEquals(11, $obj2->foo);
114 | $this->assertEquals(21, $obj2->bar);
115 | $this->assertEquals(new Std(['foo' => 11, 'bar' => 21]), $obj2);
116 |
117 | $obj2 = new Std(['foo' => 11, 'bar' => 12]);
118 | $obj2->merge(new Std(['bar' => 21]));
119 | $this->assertEquals(11, $obj2->foo);
120 | $this->assertEquals(21, $obj2->bar);
121 | $this->assertEquals(new Std(['foo' => 11, 'bar' => 21]), $obj2);
122 | }
123 |
124 | public function testSetter()
125 | {
126 | $obj = new testClass();
127 | $obj->baz = 42;
128 |
129 | $this->assertTrue(isset($obj->baz));
130 | $this->assertEquals(84, $obj->baz);
131 | }
132 |
133 | public function testValidate()
134 | {
135 | $obj = new testClass();
136 | $obj->foo = 42;
137 |
138 | $this->assertTrue(isset($obj->foo));
139 | $this->assertEquals(42, $obj->foo);
140 |
141 | $obj = new testClass();
142 | $obj->foo = -42;
143 |
144 | $this->assertFalse(isset($obj->foo));
145 | }
146 |
147 | public function testSanitize()
148 | {
149 | $obj = new testClass();
150 | $obj->bar = ' test ';
151 |
152 | $this->assertTrue(isset($obj->bar));
153 | $this->assertEquals('test', $obj->bar);
154 | }
155 |
156 | public function testRequiredMissing()
157 | {
158 | try {
159 | $obj = new testClassWithRequired();
160 | $this->fail();
161 | } catch (Exceptions $errors) {
162 | $this->assertCount(2, $errors);
163 | $this->assertInstanceOf(Exception::class, $errors[0]);
164 | $this->assertInstanceOf(Exception::class, $errors[1]);
165 | $this->assertEquals('Required property "foo" is missing', $errors[0]->getMessage());
166 | $this->assertEquals('Required property "bar" is missing', $errors[1]->getMessage());
167 | return;
168 | }
169 | }
170 |
171 | public function testRequiredMissingAndInvalid()
172 | {
173 | try {
174 | $obj = new testClassWithRequired(['baz' => 1]);
175 | $this->fail();
176 | } catch (Exceptions $errors) {
177 | $this->assertCount(3, $errors);
178 | $this->assertInstanceOf(Exception::class, $errors[0]);
179 | $this->assertInstanceOf(Exception::class, $errors[1]);
180 | $this->assertInstanceOf(Exception::class, $errors[2]);
181 | $this->assertEquals('Invalid baz', $errors[0]->getMessage());
182 | $this->assertEquals('Required property "foo" is missing', $errors[1]->getMessage());
183 | $this->assertEquals('Required property "bar" is missing', $errors[2]->getMessage());
184 | return;
185 | }
186 | }
187 |
188 | public function testRequiredValid()
189 | {
190 | try {
191 | $obj = new testClassWithRequired(['foo' => 1, 'bar' => 2]);
192 | $this->assertCount(2, $obj);
193 | } catch (Exceptions $errors) {
194 | $this->fail();
195 | }
196 | }
197 |
198 | }
199 |
--------------------------------------------------------------------------------
/tests/Core/ExceptionsTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(
21 | Exceptions::class,
22 | $errors
23 | );
24 | $this->assertInstanceOf(
25 | CollectionInterface::class,
26 | $errors
27 | );
28 | $this->assertTrue($errors->empty());
29 | }
30 |
31 | public function testConstructValid()
32 | {
33 | $errors = new Exceptions([new Exception('First'), new Exception('Second')]);
34 |
35 | $this->assertEquals(2, $errors->count());
36 | $this->assertEquals(
37 | [new Exception('First'), new Exception('Second')],
38 | $errors->toArray()
39 | );
40 | }
41 |
42 | public function testConstructInvalid()
43 | {
44 | $this->expectException(Exception::class);
45 | $this->expectExceptionMessage('Typed collection type mismatch');
46 | $errors = new Exceptions([new \stdClass('First'), new \stdClass('Second')]);
47 | }
48 |
49 | public function testAppend()
50 | {
51 | $errors = new Exceptions;
52 | $this->assertTrue($errors->empty());
53 |
54 | $errors->append(new Exception('First'));
55 | $this->assertFalse($errors->empty());
56 | $this->assertEquals(1, $errors->count());
57 |
58 | $errors->append(new Exception('Second'));
59 | $this->assertFalse($errors->empty());
60 | $this->assertEquals(2, $errors->count());
61 |
62 | $this->assertEquals(
63 | [new Exception('First'), new Exception('Second')],
64 | $errors->toArray()
65 | );
66 | }
67 |
68 | public function testPrepend()
69 | {
70 | $errors = new Exceptions;
71 | $this->assertTrue($errors->empty());
72 |
73 | $errors->prepend(new Exception('First'));
74 | $this->assertFalse($errors->empty());
75 | $this->assertEquals(1, $errors->count());
76 |
77 | $errors->prepend(new Exception('Second'));
78 | $this->assertFalse($errors->empty());
79 | $this->assertEquals(2, $errors->count());
80 |
81 | $this->assertEquals(
82 | [new Exception('Second'), new Exception('First')],
83 | $errors->toArray()
84 | );
85 | }
86 |
87 | public function testAdd()
88 | {
89 | $errors = new Exceptions;
90 | $this->assertTrue($errors->empty());
91 |
92 | $errors->add(new Exception('First'));
93 | $this->assertFalse($errors->empty());
94 | $this->assertEquals(1, $errors->count());
95 |
96 | $errors->add(new Exception('Second'));
97 | $this->assertFalse($errors->empty());
98 | $this->assertEquals(2, $errors->count());
99 |
100 | $this->assertInstanceOf(
101 | \Runn\Core\Exception::class,
102 | $errors[0]
103 | );
104 | $this->assertInstanceOf(
105 | \Runn\Core\Exception::class,
106 | $errors[1]
107 | );
108 | $this->assertEquals(new Exception('First'), $errors[0]);
109 | $this->assertEquals(new Exception('Second'), $errors[1]);
110 | }
111 |
112 | public function testAddSelf()
113 | {
114 | $errors = new Exceptions;
115 | $this->assertTrue($errors->empty());
116 |
117 | $errors->add(new Exception('First'));
118 | $this->assertFalse($errors->empty());
119 | $this->assertEquals(1, $errors->count());
120 |
121 | $merged = new Exceptions;
122 | $merged[] = new Exception('Second');
123 | $merged[] = new Exception('Third');
124 | $this->assertEquals(2, $merged->count());
125 |
126 | $errors->add($merged);
127 | $this->assertEquals(3, $errors->count());
128 | $this->assertEquals(new Exception('First'), $errors[0]);
129 | $this->assertEquals(new Exception('Second'), $errors[1]);
130 | $this->assertEquals(new Exception('Third'), $errors[2]);
131 | }
132 |
133 | public function testInvalidClassPrepend()
134 | {
135 | $errors = new class extends Exceptions
136 | {
137 | public static function getType()
138 | {
139 | return SomeException::class;
140 | }
141 | };
142 |
143 | $this->expectException(Exception::class);
144 | $this->expectExceptionMessage('Typed collection type mismatch');
145 | $errors->prepend(new Exception);
146 | }
147 |
148 | public function testInvalidClassAppend()
149 | {
150 | $errors = new class extends Exceptions
151 | {
152 | public static function getType()
153 | {
154 | return SomeException::class;
155 | }
156 | };
157 |
158 | $this->expectException(Exception::class);
159 | $this->expectExceptionMessage('Typed collection type mismatch');
160 | $errors->append(new Exception);
161 | }
162 |
163 | public function testInvalidInnserSet()
164 | {
165 | $errors = new class extends Exceptions
166 | {
167 | public static function getType()
168 | {
169 | return SomeException::class;
170 | }
171 | };
172 |
173 | $this->expectException(Exception::class);
174 | $this->expectExceptionMessage('Typed collection type mismatch');
175 | $errors[] = new Exception;
176 | }
177 |
178 | public function testThrow()
179 | {
180 | try {
181 |
182 | $errors = new Exceptions();
183 | $errors->add(new Exception('Foo'));
184 | $errors->add(new Exception('Bar'));
185 | $errors->add(new Exception('Baz'));
186 |
187 | if (!$errors->empty()) {
188 | throw $errors;
189 | }
190 |
191 | $this->fail();
192 |
193 | } catch (Exceptions $ex) {
194 |
195 | $this->assertEquals(3, $ex->count());
196 |
197 | $this->assertInstanceOf(
198 | \Runn\Core\Exception::class,
199 | $ex[0]
200 | );
201 | $this->assertInstanceOf(
202 | \Runn\Core\Exception::class,
203 | $ex[1]
204 | );
205 | $this->assertInstanceOf(
206 | \Runn\Core\Exception::class,
207 | $ex[2]
208 | );
209 |
210 | $this->assertEquals('Foo', $ex[0]->getMessage());
211 | $this->assertEquals('Bar', $ex[1]->getMessage());
212 | $this->assertEquals('Baz', $ex[2]->getMessage());
213 |
214 | }
215 | }
216 |
217 | }
218 |
--------------------------------------------------------------------------------
/tests/Reflection/ReflectionHelpersTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(is_array($args));
22 | $this->assertCount(0, $args);
23 |
24 | $object = new class {
25 | public function foo($arg1) {}
26 | };
27 |
28 | $args = ReflectionHelpers::getClassMethodArgs(get_class($object), 'foo');
29 | $this->assertTrue(is_array($args));
30 | $this->assertCount(1, $args);
31 | $this->assertFalse($args['arg1']['optional']);
32 | $this->assertFalse($args['arg1']['variadic']);
33 | $this->assertFalse(isset($args['arg1']['default']));
34 |
35 | $object = new class {
36 | public function foo($arg1 = 'test') {}
37 | };
38 |
39 | $args = ReflectionHelpers::getClassMethodArgs(get_class($object), 'foo');
40 | $this->assertTrue(is_array($args));
41 | $this->assertCount(1, $args);
42 | $this->assertTrue($args['arg1']['optional']);
43 | $this->assertFalse($args['arg1']['variadic']);
44 | $this->assertTrue(isset($args['arg1']['default']));
45 | $this->assertSame('test', $args['arg1']['default']);
46 |
47 | $object = new class {
48 | public function foo(string $arg1, int $arg2, array $arg3, $arg4) {}
49 | };
50 |
51 | $args = ReflectionHelpers::getClassMethodArgs(get_class($object), 'foo');
52 | $this->assertIsArray($args);
53 | $this->assertCount(4, $args);
54 | $this->assertSame('string', $args['arg1']['type']);
55 | $this->assertSame('int', $args['arg2']['type']);
56 | $this->assertSame('array', $args['arg3']['type']);
57 | $this->assertFalse(isset($args['arg4']['type']));
58 |
59 | $object = new class {
60 | public function foo(...$args) {}
61 | };
62 |
63 | $args = ReflectionHelpers::getClassMethodArgs(get_class($object), 'foo');
64 | $this->assertTrue(is_array($args));
65 | $this->assertCount(1, $args);
66 | $this->assertTrue($args['args']['optional']);
67 | $this->assertTrue($args['args']['variadic']);
68 | $this->assertFalse(isset($args['args']['default']));
69 | $this->assertFalse(isset($args['args']['type']));
70 |
71 | $reflector = new \ReflectionMethod(ReflectionHelpers::class, 'getClassMethodArgs');
72 | $staticVariables = $reflector->getStaticVariables();
73 | $this->assertIsArray($staticVariables);
74 | $this->assertCount(1, $staticVariables);
75 | $this->assertNotNull($staticVariables['cache']);
76 |
77 | $object2 = new ReflectionHelpers();
78 | ReflectionHelpers::getClassMethodArgs(get_class($object2), 'getClassMethodArgs');
79 | $reflector = new \ReflectionMethod(ReflectionHelpers::class, 'getClassMethodArgs');
80 | $staticVariables = $reflector->getStaticVariables();
81 | $this->assertIsArray($staticVariables['cache']);
82 | $this->assertNotNull($staticVariables['cache'][get_class($object)]['foo']);
83 | $this->assertNotNull($staticVariables['cache'][get_class($object2)]['getClassMethodArgs']);
84 | }
85 |
86 | public function testGetObjectMethodInvalidObject()
87 | {
88 | $this->expectException(Exception::class);
89 | $args = ReflectionHelpers::getObjectMethodArgs('', 'foo');
90 | }
91 |
92 | public function testGetObjectMethodArgs()
93 | {
94 | $object = new class {
95 | public function foo() {}
96 | };
97 |
98 | $args = ReflectionHelpers::getObjectMethodArgs($object, 'foo');
99 | $this->assertTrue(is_array($args));
100 | $this->assertCount(0, $args);
101 |
102 | $object = new class {
103 | public function foo($arg1) {}
104 | };
105 |
106 | $args = ReflectionHelpers::getObjectMethodArgs($object, 'foo');
107 | $this->assertTrue(is_array($args));
108 | $this->assertCount(1, $args);
109 | $this->assertFalse($args['arg1']['optional']);
110 | $this->assertFalse($args['arg1']['variadic']);
111 | $this->assertFalse(isset($args['arg1']['default']));
112 |
113 | $object = new class {
114 | public function foo($arg1 = 'test') {}
115 | };
116 |
117 | $args = ReflectionHelpers::getObjectMethodArgs($object, 'foo');
118 | $this->assertTrue(is_array($args));
119 | $this->assertCount(1, $args);
120 | $this->assertTrue($args['arg1']['optional']);
121 | $this->assertFalse($args['arg1']['variadic']);
122 | $this->assertTrue(isset($args['arg1']['default']));
123 | $this->assertSame('test', $args['arg1']['default']);
124 |
125 | $object = new class {
126 | public function foo(...$args) {}
127 | };
128 |
129 | $args = ReflectionHelpers::getObjectMethodArgs($object, 'foo');
130 | $this->assertTrue(is_array($args));
131 | $this->assertCount(1, $args);
132 | $this->assertTrue($args['args']['optional']);
133 | $this->assertTrue($args['args']['variadic']);
134 | $this->assertFalse(isset($args['args']['default']));
135 | }
136 |
137 | public function testPrepareArgs()
138 | {
139 | $args = [];
140 | $data = [1, 2, 3];
141 | $this->assertSame([], ReflectionHelpers::prepareArgs($args, $data));
142 |
143 | $args = ['foo' => [], 'bar' => []];
144 | $data = ['foo' => 42, 'bar' => null, 'baz' => 'some'];
145 | $this->assertSame(['foo' => 42, 'bar' => null], ReflectionHelpers::prepareArgs($args, $data));
146 |
147 | $args = ['foo' => [], 'bar' => []];
148 | $data = new Std(['foo' => 42, 'bar' => null, 'baz' => 'some']);
149 | $this->assertSame(['foo' => 42, 'bar' => null], ReflectionHelpers::prepareArgs($args, $data));
150 |
151 | $args = ['foo' => [], 'bar' => ['optional' => true, 'default' => 'test']];
152 | $data = ['foo' => 42, 'baz' => 'some'];
153 | $this->assertSame(['foo' => 42, 'bar' => 'test'], ReflectionHelpers::prepareArgs($args, $data));
154 |
155 | try {
156 | $args = ['foo' => [], 'bar' => []];
157 | $data = ['foo' => 42, 'baz' => 'some'];
158 | ReflectionHelpers::prepareArgs($args, $data);
159 | } catch (Exceptions $errors) {
160 | $this->assertCount(1, $errors);
161 | $this->assertInstanceOf(Exception::class, $errors[0]);
162 | $this->assertSame('Argument "bar" has not set or default value', $errors[0]->getMessage());
163 | return;
164 | }
165 | $this->fail();
166 | }
167 |
168 | }
169 |
--------------------------------------------------------------------------------
/tests/Core/TypedCollectionTraitTest.php:
--------------------------------------------------------------------------------
1 | isValueTypeValid($value, true)) {
29 | throw new Exception('Typed collection type mismatch');
30 | }
31 | }
32 | }
33 |
34 | class testFloatClass implements TypedCollectionInterface
35 | {
36 | use TypedCollectionTrait;
37 | public static function getType()
38 | {
39 | return 'float';
40 | }
41 | }
42 |
43 | class testIntClass implements TypedCollectionInterface
44 | {
45 | use TypedCollectionTrait;
46 | public static function getType()
47 | {
48 | return 'int';
49 | }
50 | }
51 |
52 | class testBoolClass implements TypedCollectionInterface
53 | {
54 | use TypedCollectionTrait;
55 | public static function getType()
56 | {
57 | return 'bool';
58 | }
59 | }
60 |
61 | class testIncorrectTypeClass implements TypedCollectionInterface
62 | {
63 | use TypedCollectionTrait;
64 | public static function getType()
65 | {
66 | return 'foo';
67 | }
68 | }
69 |
70 | class testValueClass
71 | {
72 | protected $data;
73 | public function __construct($x)
74 | {
75 | $this->data = $x;
76 | }
77 | public function getValue()
78 | {
79 | return $this->data;
80 | }
81 | }
82 |
83 |
84 | class TypedCollectionTraitTest extends TestCase
85 | {
86 |
87 | public function testInvalidFromArray()
88 | {
89 | $this->expectException(Exception::class);
90 | $this->expectExceptionMessage('Typed collection type mismatch');
91 | (new testClass)->fromArray([1, 2, 3]);
92 | }
93 |
94 | public function testInvalidAppend()
95 | {
96 | $collection = new testClass();
97 |
98 | $this->expectException(Exception::class);
99 | $this->expectExceptionMessage('Typed collection type mismatch');
100 | $collection->append(42);
101 | }
102 |
103 | public function testInvalidPrepend()
104 | {
105 | $collection = new testClass();
106 |
107 | $this->expectException(Exception::class);
108 | $this->expectExceptionMessage('Typed collection type mismatch');
109 | $collection->prepend(new class {});
110 | }
111 |
112 | public function testInvalidInnerSet()
113 | {
114 | $collection = new testClass();
115 |
116 | $this->expectException(Exception::class);
117 | $this->expectExceptionMessage('Typed collection type mismatch');
118 | $collection[] = new class {};
119 | }
120 |
121 | public function testValid()
122 | {
123 | $this->assertSame(testValueClass::class, testClass::getType());
124 |
125 | $collection = (new testClass)->fromArray([new testValueClass(1), new testValueClass(2)]);
126 |
127 | $this->assertInstanceOf(TypedCollectionInterface::class, $collection);
128 | $this->assertCount(2, $collection);
129 |
130 | $collection->append(new testValueClass(3));
131 | $this->assertCount(3, $collection);
132 |
133 | $collection->prepend(new testValueClass(4));
134 | $this->assertCount(4, $collection);
135 |
136 | $collection[] = new testValueClass(5);
137 | $this->assertCount(5, $collection);
138 | }
139 |
140 | public function testValidStrictType()
141 | {
142 | $this->assertSame(testValueClass::class, testStrictClass::getType());
143 | $collection = (new testStrictClass)->fromArray([new testValueClass(1), new testValueClass(2)]);
144 |
145 | $this->assertInstanceOf(TypedCollectionInterface::class, $collection);
146 | $this->assertCount(2, $collection);
147 | }
148 |
149 | public function testInvalidStrictType()
150 | {
151 | $this->assertSame(testValueClass::class, testStrictClass::getType());
152 |
153 | $this->expectException(Exception::class);
154 | $this->expectExceptionMessage('Typed collection type mismatch');
155 | (new testStrictClass)->fromArray([new class (1) extends testValueClass {}]);
156 | }
157 |
158 | public function testInvalidIntegerClass()
159 | {
160 | $collection = new testClass();
161 |
162 | $this->expectException(Exception::class);
163 | $this->expectExceptionMessage('Typed collection type mismatch');
164 | $collection->append('42');
165 | }
166 |
167 | public function testValidIntegerClass()
168 | {
169 | $collection = (new testIntClass)->fromArray([1, 2]);
170 |
171 | $this->assertInstanceOf(TypedCollectionInterface::class, $collection);
172 | $this->assertCount(2, $collection);
173 |
174 | $collection->append(3);
175 | $this->assertCount(3, $collection);
176 |
177 | $collection->prepend(4);
178 | $this->assertCount(4, $collection);
179 |
180 | $collection[] = 5;
181 | $this->assertCount(5, $collection);
182 | }
183 |
184 | public function testInvalidFloatClass()
185 | {
186 | $collection = new testClass();
187 |
188 | $this->expectException(Exception::class);
189 | $this->expectExceptionMessage('Typed collection type mismatch');
190 | $collection->append(true);
191 | }
192 |
193 | public function testValidFloatClass()
194 | {
195 | $collection = (new testFloatClass)->fromArray([1.0, 2.1]);
196 |
197 | $this->assertInstanceOf(TypedCollectionInterface::class, $collection);
198 | $this->assertCount(2, $collection);
199 |
200 | $collection->append(3.2);
201 | $this->assertCount(3, $collection);
202 |
203 | $collection->prepend(4.3);
204 | $this->assertCount(4, $collection);
205 |
206 | $collection[] = 5.4;
207 | $this->assertCount(5, $collection);
208 | }
209 |
210 | public function testInvalidBooleanClass()
211 | {
212 | $collection = new testClass();
213 |
214 | $this->expectException(Exception::class);
215 | $this->expectExceptionMessage('Typed collection type mismatch');
216 | $collection->append('42');
217 | }
218 |
219 | public function testValidBooleanClass()
220 | {
221 | $collection = (new testBoolClass)->fromArray([true, true]);
222 |
223 | $this->assertInstanceOf(TypedCollectionInterface::class, $collection);
224 | $this->assertCount(2, $collection);
225 |
226 | $collection->append(false);
227 | $this->assertCount(3, $collection);
228 |
229 | $collection->prepend(true);
230 | $this->assertCount(4, $collection);
231 |
232 | $collection[] = false;
233 | $this->assertCount(5, $collection);
234 | }
235 |
236 | public function testIncorrectTypeClass()
237 | {
238 | $collection = new testIncorrectTypeClass();
239 |
240 | $this->expectException(Exception::class);
241 | $this->expectExceptionMessage('Typed collection type mismatch');
242 | $collection->append(42);
243 | }
244 |
245 | }
246 |
--------------------------------------------------------------------------------
/tests/Core/StdGetSetValidateSanitizeTraitTest.php:
--------------------------------------------------------------------------------
1 | __data['bla1'] = '!!!';
39 | }
40 | public function setBla2($val) {
41 | $this->__data['bla2'] = '!!!';
42 | }
43 | }
44 |
45 | class StdGetSetValidateSanitizeTraitTest extends TestCase
46 | {
47 |
48 | public function testNullKey()
49 | {
50 | $obj = new testClass();
51 |
52 | $obj[] = '1';
53 | $this->assertCount(1, $obj);
54 | $this->assertSame('1', $obj[0]);
55 |
56 | $obj[2] = '2';
57 | $this->assertCount(2, $obj);
58 | $this->assertSame('1', $obj[0]);
59 | $this->assertSame('2', $obj[2]);
60 | }
61 |
62 | public function testNotSetters()
63 | {
64 | $obj = new testClass();
65 |
66 | $obj->bla1 = 'test1';
67 | $this->assertSame('test1', $obj->bla1);
68 |
69 | $obj->bla2 = 'test2';
70 | $this->assertSame('!!!', $obj->bla2);
71 | }
72 |
73 | public function testSimpleExceptionFromArray()
74 | {
75 | try {
76 | $obj = new testClass();
77 | $obj->fromArray(['foo' => 1]);
78 | $this->fail();
79 | } catch (Exceptions $errors) {
80 |
81 | $this->assertCount(0, $obj);
82 | $this->assertCount(1, $errors);
83 | $this->assertSame('Invalid foo', $errors[0]->getMessage());
84 |
85 | return;
86 | }
87 | $this->fail();
88 | }
89 |
90 | public function testSimpleExceptionMerge()
91 | {
92 | try {
93 | $obj = (new testClass())->fromArray(['sample' => 'test']);
94 | $obj->merge(['foo' => 1]);
95 | $this->fail();
96 | } catch (Exceptions $errors) {
97 |
98 | $this->assertCount(1, $obj);
99 | $this->assertSame('test', $obj->sample);
100 |
101 | $this->assertCount(1, $errors);
102 | $this->assertSame('Invalid foo', $errors[0]->getMessage());
103 |
104 | return;
105 | }
106 | $this->fail();
107 | }
108 |
109 | public function testMultiExceptionFromArray()
110 | {
111 | try {
112 | $obj = new testClass();
113 | $obj->fromArray(['bar' => 1]);
114 | $this->fail();
115 | } catch (Exceptions $errors) {
116 |
117 | $this->assertCount(0, $obj);
118 | $this->assertCount(2, $errors);
119 | $this->assertSame('Invalid bar 1', $errors[0]->getMessage());
120 | $this->assertSame('Invalid bar 2', $errors[1]->getMessage());
121 |
122 | return;
123 | }
124 | $this->fail();
125 | }
126 |
127 | public function testMultiExceptionMerge()
128 | {
129 | try {
130 | $obj = (new testClass())->fromArray(['sample' => 'test']);
131 | $obj->merge(['bar' => 1]);
132 | $this->fail();
133 | } catch (Exceptions $errors) {
134 |
135 | $this->assertCount(1, $obj);
136 | $this->assertSame('test', $obj->sample);
137 |
138 | $this->assertCount(2, $errors);
139 | $this->assertSame('Invalid bar 1', $errors[0]->getMessage());
140 | $this->assertSame('Invalid bar 2', $errors[1]->getMessage());
141 |
142 | return;
143 | }
144 | $this->fail();
145 | }
146 |
147 | public function testGenerateExceptionFromArray()
148 | {
149 | try {
150 | $obj = new testClass();
151 | $obj->fromArray(['baz' => 1]);
152 | $this->fail();
153 | } catch (Exceptions $errors) {
154 |
155 | $this->assertCount(0, $obj);
156 | $this->assertCount(2, $errors);
157 | $this->assertSame('Invalid baz 1', $errors[0]->getMessage());
158 | $this->assertSame('Invalid baz 2', $errors[1]->getMessage());
159 |
160 | return;
161 | }
162 | $this->fail();
163 | }
164 |
165 | public function testGenerateExceptionMerge()
166 | {
167 | try {
168 | $obj = (new testClass())->fromArray(['sample' => 'test']);
169 | $obj->merge(['baz' => 1]);
170 | $this->fail();
171 | } catch (Exceptions $errors) {
172 |
173 | $this->assertCount(1, $obj);
174 | $this->assertSame('test', $obj->sample);
175 |
176 | $this->assertCount(2, $errors);
177 | $this->assertSame('Invalid baz 1', $errors[0]->getMessage());
178 | $this->assertSame('Invalid baz 2', $errors[1]->getMessage());
179 |
180 | return;
181 | }
182 | $this->fail();
183 | }
184 |
185 | public function testComplexFromArray()
186 | {
187 | try {
188 | $obj = new testClass();
189 | $obj->fromArray(['foo' => 1, 'bar' =>2, 'baz' => 3]);
190 | $this->fail();
191 | } catch (Exceptions $errors) {
192 |
193 | $this->assertCount(0, $obj);
194 | $this->assertCount(5, $errors);
195 | $this->assertSame('Invalid foo', $errors[0]->getMessage());
196 | $this->assertSame('Invalid bar 1', $errors[1]->getMessage());
197 | $this->assertSame('Invalid bar 2', $errors[2]->getMessage());
198 | $this->assertSame('Invalid baz 1', $errors[3]->getMessage());
199 | $this->assertSame('Invalid baz 2', $errors[4]->getMessage());
200 |
201 | return;
202 | }
203 | $this->fail();
204 | }
205 |
206 | public function testComplexMerge()
207 | {
208 | try {
209 | $obj = (new testClass())->fromArray(['sample' => 'test']);
210 | $obj->merge(['foo' => 1, 'bar' =>2, 'baz' => 3]);
211 | $this->fail();
212 | } catch (Exceptions $errors) {
213 |
214 | $this->assertCount(1, $obj);
215 | $this->assertSame('test', $obj->sample);
216 |
217 | $this->assertCount(5, $errors);
218 | $this->assertSame('Invalid foo', $errors[0]->getMessage());
219 | $this->assertSame('Invalid bar 1', $errors[1]->getMessage());
220 | $this->assertSame('Invalid bar 2', $errors[2]->getMessage());
221 | $this->assertSame('Invalid baz 1', $errors[3]->getMessage());
222 | $this->assertSame('Invalid baz 2', $errors[4]->getMessage());
223 |
224 | return;
225 | }
226 | $this->fail();
227 | }
228 |
229 | }
230 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/src/Core/ObjectAsArrayTrait.php:
--------------------------------------------------------------------------------
1 | __data)
49 | ||
50 | ( !in_array($key, $this->notgetters()) && method_exists($this, 'get' . ucfirst($key)) ) ;
51 | }
52 |
53 | protected function innerUnSet($key)
54 | {
55 | unset($this->__data[$key]);
56 | }
57 |
58 | protected function innerGet($key)
59 | {
60 | $method = 'get' . ucfirst($key);
61 | if ( !in_array($key, $this->notgetters()) && method_exists($this, $method) ) {
62 | return $this->$method();
63 | }
64 | return isset($this->__data[$key]) ? $this->__data[$key] : null;
65 | }
66 |
67 | protected function innerSet($key, $val)
68 | {
69 | $method = 'set' . ucfirst($key);
70 | if ( !in_array($key, $this->notsetters()) && method_exists($this, $method) ) {
71 | $this->$method($val);
72 | } else {
73 | if (null === $key) {
74 | $this->__data[] = $val;
75 | } else {
76 | $this->__data[$key] = $val;
77 | }
78 | }
79 | }
80 |
81 | /*
82 | * ObjectAsArrayInterface implementation
83 | */
84 |
85 | /**
86 | * Returns array of all object's keys
87 | * @return array
88 | */
89 | public function keys(): array
90 | {
91 | return array_keys($this->__data);
92 | }
93 |
94 | /**
95 | * Returns array of all object's stored values
96 | * @return array
97 | */
98 | public function values(): array
99 | {
100 | $ret = [];
101 | foreach (array_keys($this->__data) as $key) {
102 | $ret[$key] = $this->innerGet($key);
103 | }
104 | return $ret;
105 | }
106 |
107 | /**
108 | * Returns true if this object-as-array ie empty (contains zero elements)
109 | * Otherwise returns false
110 | * @return bool
111 | */
112 | public function empty(): bool
113 | {
114 | return empty($this->__data);
115 | }
116 |
117 | /**
118 | * Returns true if the same (===) element exists in this object-as-array
119 | * Otherwise returns false
120 | * @param $element
121 | * @return bool
122 | */
123 | public function existsSame($element): bool
124 | {
125 | return false !== array_search($element, $this->values(), true);
126 | }
127 |
128 | /**
129 | * Returns index of first found same (===) element
130 | * MUST returns null if the same element is not found
131 | * @param mixed $element
132 | * @return int|string|bool|null
133 | */
134 | public function searchSame($element)
135 | {
136 | $key = array_search($element, $this->values(), true);
137 | return false === $key ? null : $key;
138 | }
139 |
140 | /**
141 | * \ArrayAccess implementation
142 | */
143 | public function offsetExists($offset)
144 | {
145 | return $this->innerIsSet($offset);
146 | }
147 |
148 | public function offsetUnset($offset)
149 | {
150 | $this->innerUnSet($offset);
151 | }
152 |
153 | public function offsetGet($offset)
154 | {
155 | return $this->innerGet($offset);
156 | }
157 |
158 | public function offsetSet($offset, $value)
159 | {
160 | $this->innerSet($offset, $value);
161 | }
162 |
163 | /**
164 | * \Countable implementation
165 | */
166 | public function count()
167 | {
168 | return count($this->__data);
169 | }
170 |
171 | /**
172 | * \Iterator implementation
173 | */
174 | public function current()
175 | {
176 | return $this->innerGet(key($this->__data));
177 | }
178 |
179 | public function next()
180 | {
181 | next($this->__data);
182 | }
183 |
184 | public function key()
185 | {
186 | return key($this->__data);
187 | }
188 |
189 | public function valid()
190 | {
191 | return null !== key($this->__data);
192 | }
193 |
194 | public function rewind()
195 | {
196 | reset($this->__data);
197 | }
198 |
199 | /**
200 | * \Runn\Core\HasInnerCastingInterface and \Runn\Core\ArrayCastingInterface implementation
201 | */
202 |
203 | /**
204 | * Does value need cast to this (or another) class?
205 | * @param mixed $key
206 | * @param mixed $value
207 | * @return bool
208 | */
209 | protected function needCasting($key, $value): bool
210 | {
211 | if (is_null($value) || is_scalar($value) || is_object($value)) {
212 | return false;
213 | }
214 | return true;
215 | }
216 |
217 | /**
218 | * @param $key
219 | * @param $value
220 | * @return mixed
221 | */
222 | protected function innerCast($key, $value)
223 | {
224 | return (new static)->fromArray($value);
225 | }
226 |
227 | /**
228 | * @param iterable $data
229 | * @return $this
230 | */
231 | public function fromArray(iterable $data)
232 | {
233 | $this->__data = [];
234 | $this->merge($data);
235 | return $this;
236 | }
237 |
238 | /**
239 | * @param iterable $data
240 | * @return $this
241 | */
242 | public function merge(iterable $data)
243 | {
244 | if ($data instanceof ArrayCastingInterface) {
245 | $data = $data->toArray();
246 | }
247 |
248 | foreach ($data as $key => $value) {
249 | if ($this->needCasting($key, $value)) {
250 | $value = $this->innerCast($key, $value);
251 | }
252 | $this->innerSet($key, $value);
253 | }
254 |
255 | return $this;
256 | }
257 |
258 | /**
259 | * @return array
260 | */
261 | public function toArray() : array
262 | {
263 | $data = [];
264 | foreach (array_keys($this->__data) as $key) {
265 | $value = $this->innerGet($key);
266 | if ($value instanceof self) {
267 | $data[$key] = $value->toArray();
268 | } else {
269 | $data[$key] = $value;
270 | }
271 | }
272 | return $data;
273 | }
274 | /**
275 | * @return array
276 | */
277 | public function toArrayRecursive() : array
278 | {
279 | $data = [];
280 | foreach (array_keys($this->__data) as $key) {
281 | $value = $this->innerGet($key);
282 | if ($value instanceof ArrayCastingInterface) {
283 | $data[$key] = $value->toArrayRecursive();
284 | } else {
285 | $data[$key] = $value;
286 | }
287 | }
288 | return $data;
289 | }
290 |
291 | /**
292 | * \Serializable implementation
293 | */
294 |
295 | /**
296 | * @return string
297 | */
298 | public function serialize()
299 | {
300 | return serialize($this->__data);
301 | }
302 |
303 | /**
304 | * @param string $serialized
305 | */
306 | public function unserialize($serialized)
307 | {
308 | $this->__data = unserialize($serialized);
309 | }
310 |
311 | /**
312 | * \JsonSerializable implementation
313 | */
314 |
315 | /**
316 | * @return array
317 | */
318 | public function jsonSerialize()
319 | {
320 | return $this->values();
321 | }
322 |
323 | }
324 |
--------------------------------------------------------------------------------
/src/Core/CollectionTrait.php:
--------------------------------------------------------------------------------
1 | fromArray($value);
40 | }
41 |
42 | /**
43 | * @param iterable $data
44 | * @return $this
45 | */
46 | public function merge(iterable $data)
47 | {
48 | if ($data instanceof ArrayCastingInterface) {
49 | $data = $data->toArray();
50 | }
51 |
52 | foreach ($data as $key => $value) {
53 | if ($this->needCasting($key, $value)) {
54 | $value = $this->innerCast($key, $value);
55 | }
56 | if (is_int($key) && $this->innerIsSet($key)) {
57 | $this->innerSet(null, $value);
58 | } else {
59 | $this->innerSet($key, $value);
60 | }
61 | }
62 |
63 | return $this;
64 | }
65 |
66 | /**
67 | * @param mixed $value
68 | * @return $this
69 | */
70 | public function add($value)
71 | {
72 | return $this->append($value);
73 | }
74 |
75 | /**
76 | * @param mixed $value
77 | * @return $this
78 | */
79 | public function append($value)
80 | {
81 | if ($this->needCasting(null, $value)) {
82 | $value = $this->innerCast(null, $value);
83 | }
84 | $this->__data = array_merge($this->__data, [$value]);
85 | return $this;
86 | }
87 |
88 | /**
89 | * @param mixed $value
90 | * @return $this
91 | */
92 | public function prepend($value)
93 | {
94 | if ($this->needCasting(null, $value)) {
95 | $value = $this->innerCast(null, $value);
96 | }
97 | $this->__data = array_merge([$value], $this->__data);
98 | return $this;
99 | }
100 |
101 | /**
102 | * @param int $offset
103 | * @param int|null $length
104 | * @return mixed
105 | */
106 | public function slice(int $offset, int $length = null)
107 | {
108 | return (new static)->fromArray(array_slice($this->__data, $offset, $length));
109 | }
110 |
111 | /**
112 | * @return mixed
113 | */
114 | public function first()
115 | {
116 | return $this->slice(0, 1)[0];
117 | }
118 |
119 | /**
120 | * @return mixed
121 | */
122 | public function last()
123 | {
124 | return $this->slice(-1, 1)[0];
125 | }
126 |
127 | /**
128 | * @param iterable $attributes
129 | * @return bool
130 | */
131 | public function existsElementByAttributes(iterable $attributes): bool
132 | {
133 | if (empty($attributes)) {
134 | return false;
135 | }
136 | foreach ($this as $element) {
137 | $elementAttributes = [];
138 | if (!is_array($element) && !(is_object($element) && $element instanceof \Traversable)) {
139 | continue;
140 | }
141 | foreach ($element as $key => $val) {
142 | foreach ($attributes as $attrkey => $attribute) {
143 | if ($attrkey == $key) {
144 | $elementAttributes[$key] = $val;
145 | }
146 | }
147 | }
148 | if ($attributes == $elementAttributes)
149 | return true;
150 | }
151 | return false;
152 | }
153 |
154 | /**
155 | * @param iterable $attributes
156 | * @return static
157 | */
158 | public function findAllByAttributes(iterable $attributes)
159 | {
160 | return $this->filter(function ($x) use ($attributes) {
161 | if (!is_array($x) && !(is_object($x) && $x instanceof \Traversable)) {
162 | return false;
163 | }
164 | $elementAttributes = [];
165 | foreach ($x as $key => $value) {
166 | foreach ($attributes as $attrkey => $attribute) {
167 | if ($attrkey == $key) {
168 | $elementAttributes[$key] = $value;
169 | }
170 | }
171 | }
172 | return $elementAttributes == $attributes;
173 | });
174 | }
175 |
176 | /**
177 | * @param iterable $attributes
178 | * @return mixed|null
179 | */
180 | public function findByAttributes(iterable $attributes)
181 | {
182 | $all = $this->findAllByAttributes($attributes);
183 | return $all->empty() ? null : $all[0];
184 | }
185 |
186 | /**
187 | * @return static
188 | */
189 | public function asort()
190 | {
191 | $copy = $this->toArray();
192 | asort($copy);
193 | return (new static)->fromArray($copy);
194 | }
195 |
196 | /**
197 | * @return static
198 | */
199 | public function ksort()
200 | {
201 | $copy = $this->toArray();
202 | ksort($copy);
203 | return (new static)->fromArray($copy);
204 | }
205 |
206 | /**
207 | * @param callable $callback
208 | * @return static
209 | */
210 | public function uasort(callable $callback) {
211 | $copy = $this->toArray();
212 | uasort($copy, $callback);
213 | return (new static)->fromArray($copy);
214 | }
215 |
216 | /**
217 | * @param callable $callback
218 | * @return static
219 | */
220 | public function uksort(callable $callback) {
221 | $copy = $this->toArray();
222 | uksort($copy, $callback);
223 | return (new static)->fromArray($copy);
224 | }
225 |
226 | /**
227 | * @return static
228 | */
229 | public function natsort() {
230 | $copy = $this->toArray();
231 | natsort($copy);
232 | return (new static)->fromArray($copy);
233 | }
234 |
235 | /**
236 | * @return static
237 | */
238 | public function natcasesort() {
239 | $copy = $this->toArray();
240 | natcasesort($copy);
241 | return (new static)->fromArray($copy);
242 | }
243 |
244 | /**
245 | * @param callable $callback
246 | * @return static
247 | */
248 | public function sort(callable $callback)
249 | {
250 | return $this->uasort($callback);
251 | }
252 |
253 | /**
254 | * @return static
255 | */
256 | public function reverse() {
257 | $clone = clone $this;
258 | $clone->__data = array_reverse($clone->__data, true);
259 | return $clone;
260 | }
261 |
262 |
263 | /**
264 | * @param callable $callback
265 | * @return static
266 | */
267 | public function map(callable $callback)
268 | {
269 | return (new static)->fromArray(array_values(array_map($callback, $this->toArray())));
270 | }
271 |
272 | /**
273 | * @param callable $callback
274 | * @return static
275 | */
276 | public function filter(callable $callback)
277 | {
278 | return (new static)->fromArray(array_values(array_filter($this->toArray(), $callback)));
279 | }
280 |
281 | /**
282 | * @param mixed $start
283 | * @param callable $callback
284 | * @return mixed
285 | */
286 | public function reduce($start, callable $callback)
287 | {
288 | return array_reduce($this->toArray(), $callback, $start);
289 | }
290 |
291 | /**
292 | * @param mixed $what
293 | * @return array
294 | */
295 | public function collect($what)
296 | {
297 | $ret = [];
298 | foreach ($this as $element) {
299 | if ($what instanceof \Closure) {
300 | $ret[] = $what($element);
301 | } elseif (is_array($element) || ($element instanceof ObjectAsArrayInterface)) {
302 | $ret[] = $element[$what];
303 | } elseif (is_object($element)) {
304 | $ret[] = $element->$what;
305 | }
306 | }
307 | return $ret;
308 | }
309 |
310 | /**
311 | * @param $by
312 | * @return array|static[]
313 | */
314 | public function group($by)
315 | {
316 | $ret = [];
317 | foreach ($this as $element) {
318 | if ($by instanceof \Closure) {
319 | $key = $by($element);
320 | } elseif (is_array($element) || ($element instanceof ObjectAsArrayInterface)) {
321 | $key = $element[$by];
322 | } elseif (is_object($element)) {
323 | $key = $element->$by;
324 | }
325 | if (!isset($ret[$key])) {
326 | $ret[$key] = new static;
327 | }
328 | $ret[$key]->add($element);
329 | }
330 | return $ret;
331 | }
332 |
333 | public function __call(string $method, array $params = [])
334 | {
335 | foreach ($this as $element) {
336 | call_user_func_array([$element, $method], $params);
337 | }
338 | }
339 |
340 | }
341 |
--------------------------------------------------------------------------------
/tests/Core/ObjectAsArrayTraitTest.php:
--------------------------------------------------------------------------------
1 | __data['foo'] = $val*2;
50 | }
51 | }
52 |
53 | class testWithSetterNotsetterClass implements ObjectAsArrayInterface
54 | {
55 | use ObjectAsArrayTrait;
56 | protected function notsetters(): array { return ['bar']; }
57 | protected function setFoo($val)
58 | {
59 | $this->__data['foo'] = $val*2;
60 | }
61 | protected function setBar($val)
62 | {
63 | $this->__data['bar'] = $val/2;
64 | }
65 | }
66 |
67 | class ObjectAsArrayTraitTest extends TestCase
68 | {
69 |
70 | public function testInterfaces()
71 | {
72 | $obj = new testClass();
73 |
74 | $this->assertInstanceOf(ObjectAsArrayInterface::class, $obj);
75 | $this->assertInstanceOf(\ArrayAccess::class, $obj);
76 | $this->assertInstanceOf(\Countable::class, $obj);
77 | $this->assertInstanceOf(\Iterator::class, $obj);
78 | $this->assertInstanceOf(ArrayCastingInterface::class, $obj);
79 | $this->assertInstanceOf(HasInnerCastingInterface::class, $obj);
80 | $this->assertInstanceOf(\Serializable::class, $obj);
81 | $this->assertInstanceOf(\JsonSerializable::class, $obj);
82 | }
83 |
84 | public function testKeysValues()
85 | {
86 | $obj = new testClass();
87 | $obj[1] = 100;
88 | $obj[2] = '200';
89 | $obj['foo'] = 'bar';
90 | $obj[] = 'baz';
91 |
92 | $this->assertSame([1, 2, 'foo', 3], $obj->keys());
93 | $this->assertSame([1=>100, 2=>'200', 'foo'=>'bar', 3=>'baz'], $obj->values());
94 | }
95 |
96 | public function testIsEmpty()
97 | {
98 | $obj = new testClass();
99 | $this->assertTrue($obj->empty());
100 |
101 | $obj[0] = 1;
102 | $this->assertFalse($obj->empty());
103 |
104 | unset($obj[0]);
105 | $this->assertTrue($obj->empty());
106 | }
107 |
108 | public function testExistsSame()
109 | {
110 | $obj = new testClass();
111 | $obj[1] = 100;
112 | $obj[2] = '200';
113 |
114 | $this->assertTrue($obj->existsSame(100));
115 | $this->assertFalse($obj->existsSame('100'));
116 | $this->assertTrue($obj->existsSame('200'));
117 | $this->assertFalse($obj->existsSame(200));
118 | }
119 |
120 | public function testSearchSame()
121 | {
122 | $obj = new testClass();
123 | $obj[1] = 100;
124 | $obj[2] = '200';
125 |
126 | $this->assertSame(1, $obj->searchSame(100));
127 | $this->assertNull($obj->searchSame('100'));
128 | $this->assertSame(2, $obj->searchSame('200'));
129 | $this->assertNull($obj->searchSame(200));
130 | $this->assertNull($obj->searchSame(300));
131 | }
132 |
133 | public function testArrayAccess()
134 | {
135 | $obj = new testClass();
136 | $obj[1] = 100;
137 | $obj[2] = '200';
138 | $obj[] = 300;
139 |
140 | $this->assertTrue(isset($obj[1]));
141 | $this->assertTrue(isset($obj[2]));
142 | $this->assertTrue(isset($obj[3]));
143 |
144 | $this->assertEquals(100, $obj[1]);
145 | $this->assertEquals('200', $obj[2]);
146 | $this->assertEquals(300, $obj[3]);
147 |
148 | unset($obj[2]);
149 |
150 | $this->assertTrue(isset($obj[1]));
151 | $this->assertFalse(isset($obj[2]));
152 | $this->assertTrue(isset($obj[3]));
153 | }
154 |
155 | public function testCountable()
156 | {
157 | $obj = new testClass();
158 |
159 | $this->assertCount(0, $obj);
160 |
161 | $obj[] = 'foo';
162 | $obj[] = 'bar';
163 |
164 | $this->assertCount(2, $obj);
165 |
166 | unset($obj[0]);
167 |
168 | $this->assertCount(1, $obj);
169 | }
170 |
171 | public function testIterator()
172 | {
173 | $obj = new testClass();
174 | $obj['foo'] = 100;
175 | $obj['bar'] = 200;
176 | $obj[300] = 'baz';
177 |
178 | $res = '';
179 | foreach ($obj as $key => $val) {
180 | $res .= $key . '=' . $val . ';';
181 | }
182 |
183 | $this->assertEquals('foo=100;bar=200;300=baz;', $res);
184 | }
185 |
186 | public function testNeedCasting()
187 | {
188 | $method = new \ReflectionMethod(testClass::class, 'needCasting');
189 | $closure = $method->getClosure(new testClass());
190 |
191 | $this->assertFalse($closure(null, null));
192 | $this->assertFalse($closure(null, 42));
193 | $this->assertFalse($closure(null, 3.14159));
194 | $this->assertFalse($closure(null, 'foo'));
195 | $this->assertFalse($closure(null, function () {return 0;}));
196 | $this->assertFalse($closure(null, new testClass(['foo' => 'bar'])));
197 | $this->assertFalse($closure(null, new \stdClass()));
198 |
199 | $this->assertTrue($closure(null, [1, 2, 3]));
200 | }
201 |
202 | public function testFromArray()
203 | {
204 | // First: create data
205 | $obj = new testClass();
206 | $obj->fromArray(['foo' => 100, 'bar' => 200, 'baz' => ['one' => 1, 'two' => 2]]);
207 |
208 | $this->assertInstanceOf(testClass::class, $obj);
209 | $this->assertInstanceOf(testClass::class, $obj['baz']);
210 |
211 | $this->assertCount(3, $obj);
212 |
213 | $this->assertEquals(100, $obj['foo']);
214 | $this->assertEquals(200, $obj['bar']);
215 |
216 | $this->assertEquals((new testClass())->fromArray(['one' => 1, 'two' => 2]), $obj['baz']);
217 | $this->assertEquals(1, $obj['baz']['one']);
218 | $this->assertEquals(2, $obj['baz']['two']);
219 |
220 | // Next: rewrite data
221 | $obj->fromArray(['sample' => 1, 'test' => 2]);
222 |
223 | $this->assertCount(2, $obj);
224 |
225 | $this->assertEquals(1, $obj['sample']);
226 | $this->assertEquals(2, $obj['test']);
227 |
228 | $this->assertFalse(isset($obj['foo']));
229 | $this->assertFalse(isset($obj['bar']));
230 | $this->assertFalse(isset($obj['baz']));
231 | }
232 |
233 | public function testMerge()
234 | {
235 | // Create data
236 | $obj = new testClass();
237 | $obj->fromArray(['foo' => 100, 'bar' => 200, 'baz' => ['one' => 1, 'two' => 2]]);
238 |
239 | // Merge new data now!
240 | $obj->merge(['sample' => 1, 'test' => 2]);
241 |
242 | $this->assertCount(5, $obj);
243 |
244 | $this->assertEquals(100, $obj['foo']);
245 | $this->assertEquals(200, $obj['bar']);
246 | $this->assertEquals((new testClass())->fromArray(['one' => 1, 'two' => 2]), $obj['baz']);
247 | $this->assertEquals(1, $obj['baz']['one']);
248 | $this->assertEquals(2, $obj['baz']['two']);
249 | $this->assertEquals(1, $obj['sample']);
250 | $this->assertEquals(2, $obj['test']);
251 | }
252 |
253 | public function testMergeWithArrayable()
254 | {
255 | // Create data
256 | $obj = new testClass();
257 | $obj->fromArray(['sample' => 100, 'test' => 200]);
258 |
259 | $this->assertCount(2, $obj);
260 | $this->assertEquals(100, $obj['sample']);
261 | $this->assertEquals(200, $obj['test']);
262 |
263 | // Create arrayable
264 | $merged = new testWithGetterClass();
265 | $merged['foo'] = 300;
266 |
267 | // Merge now
268 | $obj->merge($merged);
269 |
270 | $this->assertCount(3, $obj);
271 | $this->assertEquals(100, $obj['sample']);
272 | $this->assertEquals(200, $obj['test']);
273 | $this->assertEquals(42, $obj['foo']);
274 | }
275 |
276 | public function testToArray()
277 | {
278 | $obj = new testClass();
279 | $obj->fromArray(['foo' => 100, 'bar' => 200, 'baz' => ['one' => 1, 'two' => 2]]);
280 | $arr = $obj->toArray();
281 |
282 | $this->assertTrue(is_array($arr));
283 | $this->assertEquals(
284 | ['foo' => 100, 'bar' => 200, 'baz' => ['one' => 1, 'two' => 2]],
285 | $arr
286 | );
287 |
288 | $obj = new testClass();
289 | $obj['foo'] = 100;
290 | $obj['bar'] = 200;
291 | $obj['baz'] = (new testAnotherClass())->fromArray(['one' => 1, 'two' => 2]);
292 | $arr = $obj->toArray();
293 |
294 | $this->assertTrue(is_array($arr));
295 | $this->assertEquals(
296 | ['foo' => 100, 'bar' => 200, 'baz' => (new testAnotherClass())->fromArray(['one' => 1, 'two' => 2])],
297 | $arr
298 | );
299 | }
300 |
301 | public function testToArrayRecursive()
302 | {
303 | $obj = new testClass();
304 | $obj['foo'] = 100;
305 | $obj['bar'] = 200;
306 | $obj['baz'] = (new testAnotherClass())->fromArray(['one' => 1, 'two' => 2]);
307 | $arr = $obj->toArrayRecursive();
308 |
309 | $this->assertTrue(is_array($arr));
310 | $this->assertEquals(
311 | ['foo' => 100, 'bar' => 200, 'baz' => ['one' => 1, 'two' => 2]],
312 | $arr
313 | );
314 | }
315 |
316 | public function testGetter()
317 | {
318 | $obj = new testWithGetterClass();
319 |
320 | $this->assertTrue(isset($obj['foo']));
321 |
322 | $obj[1] = 100;
323 | $obj['foo'] = 200;
324 |
325 | $this->assertEquals(100, $obj[1]);
326 | $this->assertEquals(42, $obj['foo']);
327 | }
328 |
329 | public function testGetterNotGetter()
330 | {
331 | $obj = new testWithGetterNotgetterClass();
332 |
333 | $this->assertTrue(isset($obj['foo']));
334 | $this->assertFalse(isset($obj['bar']));
335 |
336 | $this->assertSame(42, $obj['foo']);
337 | $this->assertSame(null, $obj['bar']);
338 | }
339 |
340 | public function testSetter()
341 | {
342 | $obj = new testWithSetterClass();
343 | $obj[1] = 100;
344 | $obj['foo'] = 200;
345 |
346 | $this->assertEquals(100, $obj[1]);
347 | $this->assertEquals(400, $obj['foo']);
348 | }
349 |
350 | public function testSetterNotsetter()
351 | {
352 | $obj = new testWithSetterNotsetterClass();
353 | $obj['foo'] = 200;
354 | $obj['bar'] = 400;
355 |
356 | $this->assertEquals(400, $obj['foo']);
357 | $this->assertEquals(400, $obj['bar']);
358 | }
359 |
360 | public function testSerialize()
361 | {
362 | $obj = new testClass();
363 | $obj->fromArray([1=>100, 2=>200, 'foo'=>'bar']);
364 |
365 | $this->assertStringContainsString('{a:3:{i:1;i:100;i:2;i:200;s:3:"foo";s:3:"bar";}', serialize($obj));
366 | $this->assertEquals($obj, unserialize(serialize($obj)));
367 | }
368 |
369 | public function testJsonSerialize()
370 | {
371 | $obj = new testClass;
372 | $obj->fromArray([1=>100, 2=>200, 'foo'=>'bar']);
373 |
374 | $this->assertEquals('{"1":100,"2":200,"foo":"bar"}', json_encode($obj));
375 |
376 | $obj = new testWithGetterClass;
377 | $obj->fromArray(['foo' => 100]);
378 |
379 | $this->assertEquals('{"foo":42}', json_encode($obj));
380 | }
381 |
382 | public function testSiblingClasses()
383 | {
384 | $obj = new testClass();
385 | $obj->fromArray(['foo' => new testAnotherClass()]);
386 |
387 | $this->assertInstanceOf(testClass::class, $obj);
388 | $this->assertInstanceOf(testAnotherClass::class, $obj['foo']);
389 | }
390 |
391 | }
392 |
--------------------------------------------------------------------------------
/tests/Core/CollectionTraitTest.php:
--------------------------------------------------------------------------------
1 | data = $x;
21 | }
22 | public function increment()
23 | {
24 | $this->data++;
25 | }
26 | }
27 |
28 | class CollectionTraitTest extends TestCase
29 | {
30 |
31 | public function testFromArray()
32 | {
33 | $collection = (new testClass)->fromArray([100, 200, 300, [400, 500]]);
34 |
35 | $this->assertInstanceOf(CollectionInterface::class, $collection);
36 | $this->assertCount(4, $collection);
37 | $this->assertEquals(
38 | [100, 200, 300, (new testClass)->fromArray([400, 500])],
39 | $collection->values()
40 | );
41 | $this->assertEquals(
42 | [100, 200, 300, [400, 500]],
43 | $collection->toArray()
44 | );
45 | $this->assertEquals(100, $collection[0]);
46 | $this->assertEquals(200, $collection[1]);
47 | $this->assertEquals(300, $collection[2]);
48 | $this->assertEquals((new testClass)->fromArray([400, 500]), $collection[3]);
49 | }
50 |
51 | public function testAppendPrependAdd()
52 | {
53 | $collection = new testClass();
54 |
55 | $collection->append(100);
56 | $collection->append(200);
57 |
58 | $this->assertCount(2, $collection);
59 | $this->assertEquals(100, $collection[0]);
60 | $this->assertEquals(200, $collection[1]);
61 | $this->assertEquals(
62 | [100, 200],
63 | $collection->toArray()
64 | );
65 |
66 | $collection->prepend(300);
67 |
68 | $this->assertCount(3, $collection);
69 | $this->assertEquals(300, $collection[0]);
70 | $this->assertEquals(100, $collection[1]);
71 | $this->assertEquals(200, $collection[2]);
72 | $this->assertEquals(
73 | [300, 100, 200],
74 | $collection->toArray()
75 | );
76 |
77 | $collection->add(400);
78 |
79 | $this->assertCount(4, $collection);
80 | $this->assertEquals(300, $collection[0]);
81 | $this->assertEquals(100, $collection[1]);
82 | $this->assertEquals(200, $collection[2]);
83 | $this->assertEquals(400, $collection[3]);
84 | $this->assertEquals(
85 | [300, 100, 200, 400],
86 | $collection->toArray()
87 | );
88 |
89 | $collection->append([1, 2]);
90 | $this->assertCount(5, $collection);
91 | $this->assertEquals(
92 | [300, 100, 200, 400, (new testClass)->fromArray([1, 2])],
93 | $collection->values()
94 | );
95 |
96 | $collection->prepend([3, 4]);
97 | $this->assertCount(6, $collection);
98 | $this->assertEquals(
99 | [(new testClass)->fromArray([3, 4]), 300, 100, 200, 400, (new testClass)->fromArray([1, 2])],
100 | $collection->values()
101 | );
102 |
103 | }
104 |
105 | public function testMerge()
106 | {
107 | $collection = (new testClass)->fromArray([1, 2]);
108 |
109 | $collection->merge([3, 4]);
110 | $this->assertCount(4, $collection);
111 | $expected = (new testClass)->fromArray([1, 2, 3, 4]);
112 | $this->assertEquals($expected->toArray(), $collection->toArray());
113 |
114 | $collection->merge((new testClass)->fromArray([5, 6]));
115 | $this->assertCount(6, $collection);
116 | $expected = (new testClass)->fromArray([1, 2, 3, 4, 5, 6]);
117 | $this->assertEquals($expected->toArray(), $collection->toArray());
118 | }
119 |
120 | public function testSlice()
121 | {
122 | $collection = (new testClass)->fromArray([10, 20, 30, 40, 50]);
123 | $this->assertEquals(
124 | (new testClass)->fromArray([30, 40, 50]),
125 | $collection->slice(2)
126 | );
127 | $this->assertEquals(
128 | (new testClass)->fromArray([40, 50]),
129 | $collection->slice(-2)
130 | );
131 | $this->assertEquals(
132 | (new testClass)->fromArray([30, 40]),
133 | $collection->slice(2, 2)
134 | );
135 | $this->assertEquals(
136 | (new testClass)->fromArray([40]),
137 | $collection->slice(-2, 1)
138 | );
139 | }
140 |
141 | public function testFirstLast()
142 | {
143 | $collection = (new testClass)->fromArray([10, 20, 30, 40, 50]);
144 | $this->assertEquals(
145 | 10,
146 | $collection->first()
147 | );
148 | $this->assertEquals(
149 | 50,
150 | $collection->last()
151 | );
152 | }
153 |
154 | public function testExistsElementByAttributes()
155 | {
156 | $collection = new testClass();
157 | $el1 = new \Runn\Core\Std(['id' => 1, 'title' => 'foo', 'text' => 'FooFooFoo']);
158 | $collection->append($el1);
159 | $el2 = new \Runn\Core\Std(['id' => 2, 'title' => 'bar', 'text' => 'BarBarBar']);
160 | $collection->append($el2);
161 | $collection->append(42);
162 |
163 | $this->assertFalse($collection->existsElementByAttributes([]));
164 | $this->assertTrue($collection->existsElementByAttributes(['id' => 1]));
165 | $this->assertFalse($collection->existsElementByAttributes(['id' => 3]));
166 | $this->assertTrue($collection->existsElementByAttributes(['title' => 'foo']));
167 | $this->assertTrue($collection->existsElementByAttributes(['title' => 'foo', 'text' => 'FooFooFoo']));
168 | $this->assertFalse($collection->existsElementByAttributes(['title' => 'foo', 'text' => 'BarBarBar']));
169 | }
170 |
171 | public function testFindAllByAttibutes()
172 | {
173 | $collection = new testClass();
174 | $el1 = new \Runn\Core\Std(['id' => 1, 'title' => 'foo', 'text' => 'FooFooFoo']);
175 | $collection->append($el1);
176 | $el2 = new \Runn\Core\Std(['id' => 2, 'title' => 'foo', 'text' => 'AnotherFoo']);
177 | $collection->append($el2);
178 | $collection->append(42);
179 |
180 | $this->assertEquals(
181 | (new testClass)->fromArray([
182 | new \Runn\Core\Std(['id' => 1, 'title' => 'foo', 'text' => 'FooFooFoo']),
183 | new \Runn\Core\Std(['id' => 2, 'title' => 'foo', 'text' => 'AnotherFoo'])
184 | ]),
185 | $collection->findAllByAttributes(['title' => 'foo'])
186 | );
187 | }
188 |
189 | public function testFindByAttibutes()
190 | {
191 | $collection = new testClass();
192 | $el1 = new \Runn\Core\Std(['id' => 1, 'title' => 'foo', 'text' => 'FooFooFoo']);
193 | $collection->append($el1);
194 | $el2 = new \Runn\Core\Std(['id' => 2, 'title' => 'foo', 'text' => 'AnotherFoo']);
195 | $collection->append($el2);
196 | $collection->append(42);
197 |
198 | $this->assertEquals(
199 | new \Runn\Core\Std(['id' => 1, 'title' => 'foo', 'text' => 'FooFooFoo']),
200 | $collection->findByAttributes(['title' => 'foo'])
201 | );
202 | }
203 |
204 | public function testSort()
205 | {
206 | $collection = (new testClass)->fromArray([10 => 1, 30 => 3, 20 => 2, 'a' => -1, 'b' => 0, 'c' => 42, 1 => '1', '111', '11']);
207 |
208 | $result = $collection->asort();
209 |
210 | $expected = (new testClass)->fromArray(['a' => -1, 'b' => 0, 1 => '1', 10 => 1, 20 => 2, 30 => 3, 32 => '11', 'c' => 42, 31 => '111']);
211 | $this->assertEquals($expected->toArray(), $result->toArray());
212 |
213 | $result = $collection->ksort();
214 |
215 | $expected = (new testClass)->fromArray(['a' => -1, 'b' => 0, 'c' => 42, 1 => '1', 10 => 1, 20 => 2, 30 => 3, 31 => '111', 32 => '11']);
216 | $this->assertEquals($expected->toArray(), $result->toArray());
217 |
218 | $result = $collection->uasort(function ($a, $b) { return $a < $b ? 1 : ($a > $b ? -1 : 0);});
219 |
220 | $expected = (new testClass)->fromArray([31 => '111', 'c' => 42, 32 => '11', 30 => 3, 20 => 2, 10 => 1, 1 => '1', 'b' => 0, 'a' => -1]);
221 | $this->assertEquals($expected->toArray(), $result->toArray());
222 |
223 | $result = $collection->sort(function ($a, $b) { return -($a <=> $b);});
224 |
225 | $expected = (new testClass)->fromArray([31 => '111', 'c' => 42, 32 => '11', 30 => 3, 20 => 2, 10 => 1, 1 => '1', 'b' => 0, 'a' => -1]);
226 | $this->assertEquals($expected->toArray(), $result->toArray());
227 |
228 | $result = $collection->uksort(function ($a, $b) { return $a < $b ? 1 : ($a > $b ? -1 : 0);});
229 |
230 | $expected = (new testClass)->fromArray([32 => '11', 31 => '111', 30 => 3, 20 => 2, 10 => 1, 1 => '1', 'c' => 42, 'b' => 0, 'a' => -1]);
231 | $this->assertEquals($expected->toArray(), $result->toArray());
232 |
233 | $collection = (new testClass)->fromArray([0 => '12', 1 => '10', 2 => '2', 3 => '1']);
234 | $result = $collection->natsort();
235 | $expected = (new testClass)->fromArray([3 => '1', 2 => '2', 1 => '10', 0 => '12']);
236 | $this->assertEquals($expected->toArray(), $result->toArray());
237 |
238 | $collection = (new testClass)->fromArray([0 => 'IMG0.png', 1 => 'img12.png', 2 => 'img10.png', 3 => 'img2.png', 4 => 'img1.png', 5 => 'IMG3.png']);
239 | $result = $collection->natcasesort();
240 | $expected = (new testClass)->fromArray([0 => 'IMG0.png', 4 => 'img1.png', 3 => 'img2.png', 5 => 'IMG3.png', 2 => 'img10.png', 1 => 'img12.png']);
241 | $this->assertEquals($expected->toArray(), $result->toArray());
242 | }
243 |
244 | public function testReverse()
245 | {
246 | $collection = (new testClass)->fromArray([10 => 1, 30 => 3, 20 => 2, 'a' => -1, 'b' => 0, 'c' => 42, '111', '11']);
247 | $result = $collection->reverse();
248 |
249 | $expected = (new testClass)->fromArray([32 => '11', 31 => '111', 'c' => 42, 'b' => 0, 'a' => -1, 20 => 2, 30 => 3, 10 => 1]);
250 | $this->assertEquals($expected->toArray(), $result->toArray());
251 | }
252 |
253 | public function testMap()
254 | {
255 | $collection = (new testClass)->fromArray([1, 2, 3]);
256 | $result = $collection->map(function ($x) {return $x*2;});
257 |
258 | $expected = (new testClass)->fromArray([2, 4, 6]);
259 | $this->assertEquals(array_values($expected->toArray()), array_values($result->toArray()));
260 | }
261 |
262 | public function testFilter()
263 | {
264 | $collection = (new testClass)->fromArray([1, -1, 0, 2, 3, -5]);
265 | $result = $collection->filter(function ($x) {return $x>0;});
266 |
267 | $expected = (new testClass)->fromArray([1, 2, 3]);
268 | $this->assertEquals($expected->toArray(), $result->toArray());
269 | }
270 |
271 | public function testReduce()
272 | {
273 | $collection = (new testClass)->fromArray([1, 2, 3, 4]);
274 | $reduced = $collection->reduce(0, function($carry, $item) {
275 | return $carry + $item;
276 | });
277 | $this->assertEquals(10, $reduced);
278 | }
279 |
280 | public function testCollect()
281 | {
282 | $i1 = new \Runn\Core\Std(['id' => 1, 'title' => 'foo']);
283 | $i2 = new \Runn\Core\Std(['id' => 2, 'title' => 'bar']);
284 | $i3 = (object)['id' => 3, 'title' => 'baz'];
285 |
286 | $collection = new testClass();
287 | $collection->append($i1);
288 | $collection->append($i2);
289 | $collection->append($i3);
290 |
291 | $this->assertEquals(
292 | [
293 | new \Runn\Core\Std(['id' => 1, 'title' => 'foo']),
294 | new \Runn\Core\Std(['id' => 2, 'title' => 'bar']),
295 | (object)['id' => 3, 'title' => 'baz']
296 | ],
297 | $collection->toArray()
298 | );
299 |
300 | $ids = $collection->collect('id');
301 | $this->assertEquals([1, 2, 3], $ids);
302 |
303 | $titles = $collection->collect(function ($x) {
304 | return $x->title;
305 | });
306 | $this->assertEquals(['foo', 'bar', 'baz'], $titles);
307 |
308 | $collection = (new testClass)->fromArray([
309 | ['id' => 1, 'title' => 'foo'],
310 | ['id' => 2, 'title' => 'bar'],
311 | ['id' => 3, 'title' => 'baz'],
312 | ]);
313 |
314 | $ids = $collection->collect('id');
315 | $this->assertEquals([1, 2, 3], $ids);
316 |
317 | $titles = $collection->collect(function ($x) {
318 | return $x['title'];
319 | });
320 | $this->assertEquals(['foo', 'bar', 'baz'], $titles);
321 | }
322 |
323 | public function testGroup()
324 | {
325 | $collection = (new testClass)->fromArray([
326 | ['date' => '2000-01-01', 'title' => 'First'],
327 | ['date' => '2000-01-01', 'title' => 'Second'],
328 | ['date' => '2000-01-02', 'title' => 'Third'],
329 | (object)['date' => '2000-01-04', 'title' => 'Fourth'],
330 | ]);
331 |
332 | $grouped = $collection->group('date');
333 | $this->assertEquals([
334 | '2000-01-01' => (new testClass)->fromArray([['date' => '2000-01-01', 'title' => 'First'], ['date' => '2000-01-01', 'title' => 'Second']]),
335 | '2000-01-02' => (new testClass)->fromArray([['date' => '2000-01-02', 'title' => 'Third']]),
336 | '2000-01-04' => (new testClass)->fromArray([(object)['date' => '2000-01-04', 'title' => 'Fourth']]),
337 | ], $grouped);
338 |
339 | $grouped = $collection->group(function ($x) {return date('m-d', strtotime($x instanceof ObjectAsArrayInterface ? $x['date'] : $x->date));});
340 | $this->assertEquals([
341 | '01-01' => (new testClass)->fromArray([['date' => '2000-01-01', 'title' => 'First'], ['date' => '2000-01-01', 'title' => 'Second']]),
342 | '01-02' => (new testClass)->fromArray([['date' => '2000-01-02', 'title' => 'Third']]),
343 | '01-04' => (new testClass)->fromArray([(object)['date' => '2000-01-04', 'title' => 'Fourth']]),
344 | ], $grouped);
345 | }
346 |
347 | public function testCall()
348 | {
349 | $collection = new testClass();
350 | $collection->append(new Number(1));
351 | $collection->append(new Number(2));
352 | $collection->append(new Number(3));
353 |
354 | $collectionExpected = new testClass();
355 | $collectionExpected->append(new Number(2));
356 | $collectionExpected->append(new Number(3));
357 | $collectionExpected->append(new Number(4));
358 |
359 | $collection->increment();
360 | $this->assertEquals($collectionExpected, $collection);
361 | }
362 |
363 | }
364 |
--------------------------------------------------------------------------------