diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..628b006 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,58 @@ +# Select what we should cache between builds +cache: + paths: + - vendor/ +variables: + XDEBUG_MODE: coverage + +before_script: + - apt-get update -yqq + - apt-get upgrade -yqq + - apt-get install -yqq git libzip-dev unzip zip libpcre3-dev + # Install PHP extensions + - docker-php-ext-install zip + # Install & enable Xdebug for code coverage reports + - pecl install xdebug + - docker-php-ext-enable xdebug + - > + if [ "$CI_JOB_NAME" == "test:7.4" ] || [ "$CI_JOB_NAME" == "test:8.0" ]; then + pecl install ds && docker-php-ext-enable ds + fi + # Install and run Composer + - curl -sS https://getcomposer.org/installer | php + - php composer.phar install + +# Run our tests +# If Xdebug was installed you can generate a coverage report and see code coverage metrics. +test:7.4: + only: + - branches + tags: + - default + image: php:7.4 + script: + - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never +test:7.4-without-ext-ds: + only: + - branches + tags: + - default + image: php:7.4 + script: + - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never +test:8.0: + only: + - branches + tags: + - default + image: php:8.0 + script: + - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never +test:8.0-without-ext-ds: + only: + - branches + tags: + - default + image: php:7.4 + script: + - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never \ No newline at end of file diff --git a/composer.lock b/composer.lock index 815ce2e..6c283d5 100644 --- a/composer.lock +++ b/composer.lock @@ -2657,16 +2657,16 @@ }, { "name": "symfony/console", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a" + "reference": "d6d0cc30d8c0fda4e7b213c20509b0159a8f4556" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/89d4b176d12a2946a1ae4e34906a025b7b6b135a", - "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a", + "url": "https://api.github.com/repos/symfony/console/zipball/d6d0cc30d8c0fda4e7b213c20509b0159a8f4556", + "reference": "d6d0cc30d8c0fda4e7b213c20509b0159a8f4556", "shasum": "" }, "require": { @@ -2734,7 +2734,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.2.3" + "source": "https://github.com/symfony/console/tree/v5.2.4" }, "funding": [ { @@ -2750,7 +2750,7 @@ "type": "tidelift" } ], - "time": "2021-01-28T22:06:19+00:00" + "time": "2021-02-23T10:08:49+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2821,16 +2821,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367" + "reference": "d08d6ec121a425897951900ab692b612a61d6240" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4f9760f8074978ad82e2ce854dff79a71fe45367", - "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d08d6ec121a425897951900ab692b612a61d6240", + "reference": "d08d6ec121a425897951900ab692b612a61d6240", "shasum": "" }, "require": { @@ -2886,7 +2886,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.2.3" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.2.4" }, "funding": [ { @@ -2902,7 +2902,7 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:36:42+00:00" + "time": "2021-02-18T17:12:37+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -2985,16 +2985,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "262d033b57c73e8b59cd6e68a45c528318b15038" + "reference": "710d364200997a5afde34d9fe57bd52f3cc1e108" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/262d033b57c73e8b59cd6e68a45c528318b15038", - "reference": "262d033b57c73e8b59cd6e68a45c528318b15038", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/710d364200997a5afde34d9fe57bd52f3cc1e108", + "reference": "710d364200997a5afde34d9fe57bd52f3cc1e108", "shasum": "" }, "require": { @@ -3027,7 +3027,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.2.3" + "source": "https://github.com/symfony/filesystem/tree/v5.2.4" }, "funding": [ { @@ -3043,20 +3043,20 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:01:46+00:00" + "time": "2021-02-12T10:38:38+00:00" }, { "name": "symfony/finder", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "4adc8d172d602008c204c2e16956f99257248e03" + "reference": "0d639a0943822626290d169965804f79400e6a04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/4adc8d172d602008c204c2e16956f99257248e03", - "reference": "4adc8d172d602008c204c2e16956f99257248e03", + "url": "https://api.github.com/repos/symfony/finder/zipball/0d639a0943822626290d169965804f79400e6a04", + "reference": "0d639a0943822626290d169965804f79400e6a04", "shasum": "" }, "require": { @@ -3088,7 +3088,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.2.3" + "source": "https://github.com/symfony/finder/tree/v5.2.4" }, "funding": [ { @@ -3104,11 +3104,11 @@ "type": "tidelift" } ], - "time": "2021-01-28T22:06:19+00:00" + "time": "2021-02-15T18:55:04+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", @@ -3157,7 +3157,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.2.3" + "source": "https://github.com/symfony/options-resolver/tree/v5.2.4" }, "funding": [ { @@ -3724,7 +3724,7 @@ }, { "name": "symfony/process", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", @@ -3766,7 +3766,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.2.3" + "source": "https://github.com/symfony/process/tree/v5.2.4" }, "funding": [ { @@ -3865,7 +3865,7 @@ }, { "name": "symfony/stopwatch", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -3907,7 +3907,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.2.3" + "source": "https://github.com/symfony/stopwatch/tree/v5.2.4" }, "funding": [ { @@ -3927,16 +3927,16 @@ }, { "name": "symfony/string", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "c95468897f408dd0aca2ff582074423dd0455122" + "reference": "4e78d7d47061fa183639927ec40d607973699609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/c95468897f408dd0aca2ff582074423dd0455122", - "reference": "c95468897f408dd0aca2ff582074423dd0455122", + "url": "https://api.github.com/repos/symfony/string/zipball/4e78d7d47061fa183639927ec40d607973699609", + "reference": "4e78d7d47061fa183639927ec40d607973699609", "shasum": "" }, "require": { @@ -3990,7 +3990,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.2.3" + "source": "https://github.com/symfony/string/tree/v5.2.4" }, "funding": [ { @@ -4006,7 +4006,7 @@ "type": "tidelift" } ], - "time": "2021-01-25T15:14:59+00:00" + "time": "2021-02-16T10:20:28+00:00" }, { "name": "theseer/tokenizer", diff --git a/src/Olivebbs/Map/GenericMap.php b/src/Olivebbs/Map/GenericMap.php index cd5e674..a438344 100644 --- a/src/Olivebbs/Map/GenericMap.php +++ b/src/Olivebbs/Map/GenericMap.php @@ -31,17 +31,20 @@ use ArrayAccess; use Countable; use Ds\Map; use function is_array; +use function is_callable; use function is_int; use function is_object; use function is_string; -use function mb_strlen; use Olivebbs\Map\Exception\InvalidArgumentException; + +use function strtolower; use TypeError; use ValueError; class GenericMap implements ArrayAccess, Countable { public const OBJECT = 'object'; + public const CALLABLE = 'callable'; public const ARRAY = 'array'; public const INT = 'int'; public const INTEGER = 'integer'; @@ -53,10 +56,11 @@ class GenericMap implements ArrayAccess, Countable protected string $keyType; protected string $valueType; - public function __construct(?string $keyType = null, ?string $valueType = null) + public function __construct(string $keyType = self::ANY, string $valueType = self::ANY) { - $this->keyType = strtolower($keyType ?? self::ANY); - $this->valueType = strtolower($valueType ?? self::ANY); + $this->keyType = strtolower($keyType); + + $this->valueType = class_exists($valueType) ? $valueType : strtolower($valueType); if (!$this->isValidKeyType($this->keyType)) { throw new InvalidArgumentException(sprintf('Invalid key type (%s)', $this->keyType)); @@ -85,7 +89,7 @@ class GenericMap implements ArrayAccess, Countable public function offsetExists($offset): bool { if (is_array($offset) || is_object($offset)) { - throw new InvalidArgumentException('Map Keys cannot be objects or arrays'); + throw new InvalidArgumentException('Map keys cannot be objects or arrays'); } if (!$this->checkType($this->keyType, $offset)) { @@ -101,7 +105,7 @@ class GenericMap implements ArrayAccess, Countable public function offsetGet($offset) { if (is_array($offset) || is_object($offset)) { - throw new InvalidArgumentException('Map Keys cannot be objects or arrays'); + throw new InvalidArgumentException('Map keys cannot be objects or arrays'); } if (!$this->checkType($this->keyType, $offset)) { @@ -119,7 +123,7 @@ class GenericMap implements ArrayAccess, Countable public function offsetSet($offset, $value): void { if (is_array($offset) || is_object($offset)) { - throw new InvalidArgumentException('Map Keys cannot be objects or arrays'); + throw new InvalidArgumentException('Map keys cannot be objects or arrays'); } if (!$this->checkType($this->keyType, $offset)) { @@ -139,7 +143,7 @@ class GenericMap implements ArrayAccess, Countable public function offsetUnset($offset): void { if (is_array($offset) || is_object($offset)) { - throw new InvalidArgumentException('Map Keys cannot be objects or arrays'); + throw new InvalidArgumentException('Map keys cannot be objects or arrays'); } if (!$this->checkType($this->keyType, $offset)) { @@ -191,6 +195,8 @@ class GenericMap implements ArrayAccess, Countable switch ($type) { case self::OBJECT: return is_object($var); + case self::CALLABLE: + return is_callable($var); case self::ARRAY: return is_array($var); case self::INT: @@ -199,7 +205,7 @@ class GenericMap implements ArrayAccess, Countable case self::STRING: return is_string($var); case self::CHAR: - return is_string($var) && mb_strlen($var) === 1; + return is_string($var) && \mb_strlen($var) === 1; case self::ANY: return true; default: @@ -219,8 +225,13 @@ class GenericMap implements ArrayAccess, Countable private function isValidValueType(string $type): bool { + if (class_exists($type)) { + return true; + } + return in_array($type, [ self::OBJECT, + self::CALLABLE, self::ARRAY, self::INT, self::INTEGER, diff --git a/tests/GenericMapTest.php b/tests/GenericMapTest.php index c6192b0..77379e6 100644 --- a/tests/GenericMapTest.php +++ b/tests/GenericMapTest.php @@ -30,6 +30,7 @@ namespace Olivebbs\Tests\Map; use Olivebbs\Map\Exception\InvalidArgumentException; use Olivebbs\Map\GenericMap; use PHPUnit\Framework\TestCase; +use ValueError; class GenericMapTest extends TestCase { @@ -118,7 +119,7 @@ class GenericMapTest extends TestCase public function testOffsetSetThrowsValueErrorException(): void { $genericMap = $this->getGenericMap(); - $this->expectException(\ValueError::class); + $this->expectException(ValueError::class); $genericMap[0] = 'string'; } @@ -146,6 +147,26 @@ class GenericMapTest extends TestCase $genericMap = new GenericMap(GenericMap::CHAR, 'test'); } + public function testUsingClassAsValue(): void + { + $genericMap = new GenericMap(GenericMap::INT, \SplObjectStorage::class); + $splObject = new \SplObjectStorage(); + $stdClass = new \stdClass(); + $splObject->attach($stdClass); + $genericMap[0] = $splObject; + self::assertSame($splObject, $genericMap[0]); + self::assertTrue($genericMap[0]->contains($stdClass)); + } + + public function testObjectCantBeUsedAsKeyWithAny(): void + { + $genericMap = new GenericMap(GenericMap::ANY, GenericMap::ANY); + $object = new \SplObjectStorage(); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Map keys cannot be objects or arrays'); + $genericMap[$object] = 1; + } + private function resetGenericMap(): void { $this->genericMap = $this->getGenericMap();