Compare commits

...

6 Commits
1.0.x ... 1.x

Author SHA1 Message Date
R. Eric Wheeler 3ffe6ee387 Merge remote-tracking branch 'refs/remotes/origin/1.x' into 1.x 2022-11-30 13:34:57 -08:00
R. Eric Wheeler ce11bd23d0 Update dependencies 2022-11-30 13:32:00 -08:00
R. Eric Wheeler 1741825342 Merge branch '1.1.x' into '1.x'
Updated composer keywords

See merge request olive/PHP/map!7
2022-05-11 23:44:25 +00:00
R. Eric Wheeler edc69bb071 Updated composer keywords 2022-05-11 16:41:09 -07:00
R. Eric Wheeler e19eb6040c Merge branch '3-change-const-types-to-enum' into '1.x'
Resolve "Change const types to enum"

See merge request olive/PHP/map!5
2022-05-11 23:28:24 +00:00
R. Eric Wheeler f485a0e375 Resolve "Change const types to enum" 2022-05-11 23:28:23 +00:00
12 changed files with 363 additions and 639 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
.idea/
vendor/
.php_cs.cache
.phpunit.cache/
.php-cs-fixer.cache

View File

@ -2,18 +2,21 @@
Very simple typed map class.
Includes IntCharMap and CharMap as examples. It is best to extend `Olivebbs\Map\GenericMap `for your uses.
### Example
```php
use Olivebbs\Map\GenericMap;
use Olivebbs\Map\Enum\ValueType;
use Olivebbs\Map\Enum\KeyType;
final class MyStringMap extends GenericMap
{
public function __construct(array $values)
{
parent::__construct(GenericMap::STRING, GenericMap::STRING);
parent::__construct(KeyType::STRING, ValueType::STRING);
$this->map->putAll($values);
}
}
@ -23,8 +26,10 @@ or
```php
use Olivebbs\Map\GenericMap;
use Olivebbs\Map\Enum\ValueType;
use Olivebbs\Map\Enum\KeyType;
$myIntMap = new GenericMap(GenericMap::INT, GenericMap::INT);
$myIntMap = new GenericMap(KeyType::INT, ValueType::INT);
```
Then use it.
```php

View File

@ -2,12 +2,13 @@
"name": "olive/map",
"description": "Generic map for PHP",
"type": "library",
"keywords": ["map", "hashmap", "hash-map", "data", "data structure", "array", "arrayaccess"],
"require": {
"php": ">=8.1",
"php-ds/php-ds": "^1.4"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.8",
"friendsofphp/php-cs-fixer": "^3.13",
"phpunit/phpunit": "^9.5"
},
"license": "MPL-2.0",

737
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -28,13 +28,15 @@
namespace Olivebbs\Map;
use function array_combine;
use Olivebbs\Map\Enum\KeyType;
use Olivebbs\Map\Enum\ValueType;
use function str_split;
class CharMap extends GenericMap
{
public function __construct(array $initialValues = [])
{
parent::__construct(keyType: self::CHAR, valueType: self::CHAR);
parent::__construct(keyType: KeyType::CHAR, valueType: ValueType::CHAR);
$this->map->putAll(values: $initialValues);
}

View File

@ -0,0 +1,37 @@
<?php declare(strict_types=1);
/*
* Copyright (c) 2020 https://rewiv.com sikofitt@gmail.com
*
* This file is a part of Olive BBS
*
* This Source Code Form is subject to the
* terms of the Mozilla Public License, v. 2.0.
*
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*
* ___ ___ ___
* ( ).-. ( ) ( )
* .--. | |( __)___ ___ .--. | |.-. | |.-. .--.
* / \| |(''"( )( / \| / \| / \ / _ \
* | .-. | | | | | | | | .-. | .-. | .-. |. .' `. ;
* | | | | | | | | | | | | | | | | | | | || ' | |
* | | | | | | | | | | | |/ | | | | | | |_\_`.(___)
* | | | | | | | | | | | ' _.| | | | | | ( ). '.
* | ' | | | | | ' ' ; | .'.-| ' | | ' | || | `\ |
* ' `-' | | | | \ `' /' `-' ' `-' ;' `-' ; ; '._,' '
* `.__.(___(___) '_.' `.__.' `.__. `.__. '.___.'
*
*/
namespace Olivebbs\Map\Enum;
enum KeyType : string
{
case INT = 'int';
case INTEGER = 'integer'; // Shouldn't really use, just for compatibility.
case STRING = 'string';
case CHAR = 'char';
case ANY = 'any';
}

View File

@ -0,0 +1,41 @@
<?php declare(strict_types=1);
/*
* Copyright (c) 2020 https://rewiv.com sikofitt@gmail.com
*
* This file is a part of Olive BBS
*
* This Source Code Form is subject to the
* terms of the Mozilla Public License, v. 2.0.
*
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*
* ___ ___ ___
* ( ).-. ( ) ( )
* .--. | |( __)___ ___ .--. | |.-. | |.-. .--.
* / \| |(''"( )( / \| / \| / \ / _ \
* | .-. | | | | | | | | .-. | .-. | .-. |. .' `. ;
* | | | | | | | | | | | | | | | | | | | || ' | |
* | | | | | | | | | | | |/ | | | | | | |_\_`.(___)
* | | | | | | | | | | | ' _.| | | | | | ( ). '.
* | ' | | | | | ' ' ; | .'.-| ' | | ' | || | `\ |
* ' `-' | | | | \ `' /' `-' ' `-' ;' `-' ; ; '._,' '
* `.__.(___(___) '_.' `.__.' `.__. `.__. '.___.'
*
*/
namespace Olivebbs\Map\Enum;
enum ValueType : string
{
case OBJECT = 'object';
case CALLABLE = 'callable';
case ARRAY = 'array';
case INT = 'int';
case INTEGER = 'integer';
case STRING = 'string';
case CHAR = 'char';
case ENUM = 'enum';
case ANY = 'any';
}

View File

@ -32,56 +32,42 @@ use function class_exists;
use Countable;
use Ds\Map;
use function enum_exists;
use function in_array;
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\Enum\KeyType;
use Olivebbs\Map\Enum\ValueType;
use Olivebbs\Map\Exception\InvalidArgumentException;
use function sprintf;
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';
public const STRING = 'string';
public const CHAR = 'char';
public const ENUM = 'enum';
public const ANY = 'any';
protected Map $map;
public function __construct(protected string $keyType = self::ANY, protected string $valueType = self::ANY)
private readonly string $valueTypeValue;
private readonly string $keyTypeValue;
public function __construct(protected KeyType $keyType = KeyType::ANY, protected object|string $valueType = ValueType::ANY)
{
$this->keyType = strtolower(string: $keyType);
$this->valueType = class_exists(class: $valueType) ? $valueType : strtolower(string: $valueType);
if (!$this->isValidKeyType(type: $this->keyType)) {
throw new InvalidArgumentException(message: sprintf('Invalid key type (%s)', $this->keyType));
}
$this->keyTypeValue = $this->keyType->value;
$this->valueTypeValue = is_string(value: $this->valueType) ? $this->valueType : $this->valueType->value;
if (!$this->isValidValueType(type: $this->valueType)) {
throw new InvalidArgumentException(message: sprintf('Invalid value type (%s)', $this->valueType));
throw new InvalidArgumentException(message: sprintf('Invalid value type (%s)', $this->valueTypeValue));
}
$this->map = new Map();
}
public function getKeyType(): string
public function getKeyType(): KeyType
{
return $this->keyType;
}
public function getValueType(): string
public function getValueType(): object|string
{
return $this->valueType;
}
@ -115,7 +101,7 @@ class GenericMap implements ArrayAccess, Countable
*/
public function offsetSet(mixed $offset, mixed $value): void
{
$this->checkTypes($offset, $value);
$this->checkTypes(offset: $offset, value: $value);
$this->map->offsetSet(offset: $offset, value: $value);
}
@ -152,8 +138,8 @@ class GenericMap implements ArrayAccess, Countable
protected function assertInitialValues(array $initialValues): bool
{
foreach ($initialValues as $key => $value) {
if (!$this->checkType(type: $this->keyType, var: $key) || !$this->checkType(type: $this->valueType, var: $value)) {
throw new InvalidArgumentException(message: sprintf('Invalid types for map [%s => %s], they should be [%s => %s]', get_debug_type(value: $key), get_debug_type(value: $value), $this->keyType, $this->valueType));
if (!$this->checkType($this->keyType, $key) || !$this->checkType($this->valueType, $value)) {
throw new InvalidArgumentException(message: sprintf('Invalid types for map [%s => %s], they should be [%s => %s]', get_debug_type(value: $key), get_debug_type(value: $value), $this->keyType->value, is_string($this->valueType) ? $this->valueType : $this->valueType->value));
}
}
@ -167,63 +153,42 @@ class GenericMap implements ArrayAccess, Countable
}
if (!$this->checkType(type: $this->keyType, var: $offset)) {
throw new TypeError(message: sprintf('Key should be of value %s.', $this->keyType));
throw new InvalidArgumentException(message: sprintf('Key should be of value %s.', $this->keyTypeValue));
}
if (null !== $value && !$this->checkType(type: $this->valueType, var: $value)) {
throw new ValueError(message: sprintf('Value should be of type %s.', $this->valueType));
throw new InvalidArgumentException(message: sprintf('Value should be of type %s.', $this->valueTypeValue));
}
}
/**
* @codeCoverageIgnore
* @param string $type
* @param mixed $var
* @param KeyType|ValueType|string $type
* @param mixed $var
*
* @return bool
*/
private function checkType(string $type, mixed $var): bool
private function checkType(KeyType|ValueType|string $type, mixed $var): bool
{
return match ($type) {
self::OBJECT => is_object(value: $var),
self::CALLABLE => is_callable(value: $var),
self::ARRAY => is_array(value: $var),
self::INT, self::INTEGER => is_int(value: $var),
self::STRING => is_string(value: $var),
self::CHAR => is_string(value: $var) && (function_exists('mb_strlen') ? mb_strlen(string: $var) === 1 : strlen(string: $var) === 1),
self::ENUM => is_object(value: $var) && enum_exists(enum: $var::class),
self::ANY => true,
ValueType::OBJECT => is_object(value: $var),
ValueType::CALLABLE => is_callable(value: $var),
ValueType::ARRAY => is_array(value: $var),
KeyType::INT, KeyType::INTEGER, ValueType::INT, ValueType::INTEGER => is_int(value: $var),
KeyType::STRING, ValueType::STRING => is_string(value: $var),
KeyType::CHAR, ValueType::CHAR => is_string(value: $var) && (function_exists('mb_strlen') ? mb_strlen(string: $var) === 1 : strlen(string: $var) === 1),
ValueType::ENUM => is_object(value: $var) && enum_exists(enum: $var::class),
KeyType::ANY, ValueType::ANY => true,
default => $var instanceof $type,
};
}
private function isValidKeyType(string $type): bool
private function isValidValueType(object|string $type): bool
{
return in_array(needle: $type, haystack: [
self::INT,
self::INTEGER,
self::STRING,
self::CHAR,
self::ANY,
], strict: true);
}
private function isValidValueType(string $type): bool
{
if (class_exists(class: $type)) {
if (is_string(value: $type) && class_exists(class: $type)) {
return true;
}
return in_array(needle: $type, haystack: [
self::OBJECT,
self::CALLABLE,
self::ARRAY,
self::INT,
self::INTEGER,
self::STRING,
self::CHAR,
self::ENUM,
self::ANY,
], strict: true);
return $type instanceof ValueType;
}
}

View File

@ -27,13 +27,15 @@
namespace Olivebbs\Map;
use Olivebbs\Map\Enum\KeyType;
use Olivebbs\Map\Enum\ValueType;
use Olivebbs\Map\Exception\ImmutableMapException;
class ImmutableMap extends GenericMap
{
private function __construct(
string $keyType,
string $valueType,
KeyType $keyType,
object|string $valueType,
array $values
) {
parent::__construct(keyType: $keyType, valueType: $valueType);
@ -52,7 +54,7 @@ class ImmutableMap extends GenericMap
throw new ImmutableMapException(message: sprintf('Cannot unset values in %s', __CLASS__));
}
public static function create(string $keyType, string $valueType, array $values): static
public static function create(KeyType $keyType, ValueType $valueType, array $values): static
{
return new static(keyType: $keyType, valueType: $valueType, values: $values);
}

View File

@ -27,13 +27,15 @@
namespace Olivebbs\Map;
use Olivebbs\Map\Enum\KeyType;
use Olivebbs\Map\Enum\ValueType;
use function str_split;
class IntCharMap extends GenericMap
{
public function __construct(array $initialValues = [])
{
parent::__construct(keyType: self::INT, valueType: self::CHAR);
parent::__construct(keyType:KeyType::INT, valueType: ValueType::CHAR);
$this->map->putAll(values: $initialValues);
}

View File

@ -27,10 +27,13 @@
namespace Olivebbs\Tests\Map;
use Olivebbs\Map\Enum\KeyType;
use Olivebbs\Map\Enum\ValueType;
use Olivebbs\Map\Exception\InvalidArgumentException;
use Olivebbs\Map\GenericMap;
use PHPUnit\Framework\TestCase;
use ValueError;
use SplObjectStorage;
use stdClass;
class GenericMapTest extends TestCase
{
@ -46,13 +49,13 @@ class GenericMapTest extends TestCase
public function testHashMap(): void
{
$genericHashMap = new GenericMap(GenericMap::INT, GenericMap::INT);
$genericHashMap = new GenericMap(KeyType::INT, ValueType::INT);
foreach (range(0, 9) as $range) {
$genericHashMap[$range] = $range;
}
self::assertSame(GenericMap::INT, $this->genericMap->getKeyType());
self::assertSame(GenericMap::INT, $this->genericMap->getValueType());
self::assertSame(KeyType::INT, $this->genericMap->getKeyType());
self::assertSame(ValueType::INT, $this->genericMap->getValueType());
self::assertCount(10, $this->genericMap);
self::assertEquals(10, $this->genericMap->count());
$array = array_combine(range(0, 9), range(0, 9));
@ -64,15 +67,15 @@ class GenericMapTest extends TestCase
unset($this->genericMap[1]);
self::assertNull($this->genericMap[1]);
$this->expectException(\TypeError::class);
$this->expectException(InvalidArgumentException::class);
$this->genericMap['H'] = 1;
}
public function testInvalidArgumentExceptionThrowsIsset(): void
{
$genericHashMap = new GenericMap(GenericMap::ANY, GenericMap::ANY);
$genericHashMap = new GenericMap(KeyType::ANY, ValueType::ANY);
$this->expectException(InvalidArgumentException::class);
$obj = new \stdClass();
$obj = new stdClass();
/** @noinspection PhpExpressionResultUnusedInspection */
isset($genericHashMap[$obj]);
}
@ -80,23 +83,17 @@ class GenericMapTest extends TestCase
public function testTypeErrorThrowsIsset(): void
{
$genericMap = $this->getGenericMap();
$this->expectException(\TypeError::class);
$this->expectException(InvalidArgumentException::class);
/** @noinspection PhpExpressionResultUnusedInspection */
isset($genericMap['C']);
}
public function testInvalidArgumentExceptionThrowsOnArrayOrObject(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid key type (array)');
$genericMap = new GenericMap(GenericMap::ARRAY, GenericMap::ARRAY);
}
public function testOffsetGetThrowsInvalidArgumentException(): void
{
$genericMap = $this->getGenericMap();
$this->expectException(InvalidArgumentException::class);
$obj = new \stdClass();
$obj = new stdClass();
$test = $genericMap[$obj];
}
@ -104,7 +101,7 @@ class GenericMapTest extends TestCase
{
$genericMap = $this->getGenericMap();
$this->expectException(InvalidArgumentException::class);
$obj = new \stdClass();
$obj = new stdClass();
$genericMap[$obj] = 1;
}
@ -112,14 +109,14 @@ class GenericMapTest extends TestCase
{
$genericMap = $this->getGenericMap();
$this->expectException(InvalidArgumentException::class);
$obj = new \stdClass();
$obj = new stdClass();
unset($genericMap[$obj]);
}
public function testOffsetSetThrowsValueErrorException(): void
{
$genericMap = $this->getGenericMap();
$this->expectException(ValueError::class);
$this->expectException(InvalidArgumentException::class);
$genericMap[0] = 'string';
}
@ -128,7 +125,7 @@ class GenericMapTest extends TestCase
{
$this->resetGenericMap();
$this->expectException(\TypeError::class);
$this->expectException(InvalidArgumentException::class);
unset($this->genericMap['C']);
}
@ -136,7 +133,7 @@ class GenericMapTest extends TestCase
public function testOffsetGetThrowsTypeErrorException(): void
{
$this->resetGenericMap();
$this->expectException(\TypeError::class);
$this->expectException(InvalidArgumentException::class);
$test = $this->genericMap['C'];
}
@ -144,14 +141,14 @@ class GenericMapTest extends TestCase
public function testThrowInvalidArgumentExceptionOnConstructValue(): void
{
$this->expectException(InvalidArgumentException::class);
$genericMap = new GenericMap(GenericMap::CHAR, 'test');
$genericMap = new GenericMap(KeyType::CHAR, 'test');
}
public function testUsingClassAsValue(): void
{
$genericMap = new GenericMap(GenericMap::INT, \SplObjectStorage::class);
$splObject = new \SplObjectStorage();
$stdClass = new \stdClass();
$genericMap = new GenericMap(KeyType::INT, SplObjectStorage::class);
$splObject = new SplObjectStorage();
$stdClass = new stdClass();
$splObject->attach($stdClass);
$genericMap[0] = $splObject;
self::assertSame($splObject, $genericMap[0]);
@ -160,8 +157,8 @@ class GenericMapTest extends TestCase
public function testObjectCantBeUsedAsKeyWithAny(): void
{
$genericMap = new GenericMap(GenericMap::ANY, GenericMap::ANY);
$object = new \SplObjectStorage();
$genericMap = new GenericMap(KeyType::ANY, ValueType::ANY);
$object = new SplObjectStorage();
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Map keys cannot be objects or arrays');
$genericMap[$object] = 1;
@ -172,7 +169,7 @@ class GenericMapTest extends TestCase
$enumMap = new class(TestEnum::cases()) extends GenericMap {
public function __construct(array $values = [])
{
parent::__construct(self::INT, self::ENUM);
parent::__construct(KeyType::INT, ValueType::ENUM);
$this->assertInitialValues($values);
$this->map->putAll($values);
}
@ -190,6 +187,6 @@ class GenericMapTest extends TestCase
private function getGenericMap(): GenericMap
{
return new GenericMap(GenericMap::INT, GenericMap::INT);
return new GenericMap(KeyType::INT, ValueType::INT);
}
}

View File

@ -27,9 +27,10 @@
namespace Olivebbs\Tests\Map;
use Olivebbs\Map\Enum\KeyType;
use Olivebbs\Map\Enum\ValueType;
use Olivebbs\Map\Exception\ImmutableMapException;
use Olivebbs\Map\Exception\InvalidArgumentException;
use Olivebbs\Map\GenericMap;
use Olivebbs\Map\ImmutableMap;
use PHPUnit\Framework\TestCase;
@ -37,14 +38,14 @@ class ImmutableMapTest extends TestCase
{
public function testOffsetUnset(): void
{
$immutableMap = ImmutableMap::create(GenericMap::INTEGER, GenericMap::CHAR, range('A', 'Z'));
$immutableMap = ImmutableMap::create(KeyType::INTEGER, ValueType::CHAR, range('A', 'Z'));
$this->expectException(ImmutableMapException::class);
unset($immutableMap[0]);
}
public function testOffsetSet(): void
{
$immutableMap = ImmutableMap::create(GenericMap::INTEGER, GenericMap::CHAR, range('A', 'Z'));
$immutableMap = ImmutableMap::create(KeyType::INTEGER, ValueType::CHAR, range('A', 'Z'));
$this->expectException(ImmutableMapException::class);
$immutableMap[0] = 1;
}
@ -58,6 +59,6 @@ class ImmutableMapTest extends TestCase
];
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid types for map [string => int], they should be [int => string]');
$immutableMap = ImmutableMap::create(GenericMap::INT, GenericMap::STRING, $values);
$immutableMap = ImmutableMap::create(KeyType::INT, ValueType::STRING, $values);
}
}