nkopylov / php-typed-collections
Generatable statically typed collections for PHP entities
Requires
- php: ^7.4|^8.0
Requires (Dev)
- phpunit/phpunit: ^9.1
README
Generatable statically typed collections for PHP entities.
Example of the collection
<?php /** * Autogenerated. * Time: 2022-10-04 06:17:44 */ declare(strict_types=1); namespace Nkopylov\Test\PhpCollections; use JetBrains\PhpStorm\Pure; use Nkopylov\Test\PhpCollections\TestClass as CollectionEntity; /** * Autogenerated typed collection for Nkopylov\Test\PhpCollections\TestClass objects. * Check nkopylov/php-typed-collections for more information * * @implements \Iterator<mixed, CollectionEntity> * @implements \ArrayAccess<mixed, CollectionEntity> * @codeCoverageIgnore */ class TestCollection_testCollection implements \Iterator, \ArrayAccess { /** * @var CollectionEntity[] */ private array $elements = []; /** * @var callable */ private $idMapper; /** * @phpstan-pure * Creates new collection * @return static */ public static function create(): self { return new static(function() {static $counter = 0; return $counter++;}); } final public function __construct(callable $idMapper) { $this->idMapper = $idMapper; } /** * @phpstan-pure * Returns the first entity from the collection. * @return CollectionEntity|null */ public function first(): ?CollectionEntity { if ($this->count() === 0) { return null; } $this->rewind(); return $this->current(); } /** * {@inheritdoc} * @return CollectionEntity */ public function current(): CollectionEntity { $entity = current($this->elements); if ($entity === false) { throw new \LogicException('Collection is empty'); } return $entity; } /** * {@inheritdoc} */ public function next(): void { next($this->elements); } /** * {@inheritdoc} */ public function key(): mixed { return key($this->elements); } /** * {@inheritdoc} */ public function valid(): bool { return $this->key() !== null; } /** * {@inheritdoc} */ public function rewind(): void { reset($this->elements); } /** * {@inheritdoc} */ public function offsetExists($offset): bool { return isset($this->elements[$offset]); } /** * {@inheritdoc} */ public function offsetSet($offset, $value): void { if (!($value instanceof CollectionEntity)) { throw new \InvalidArgumentException(sprintf("Can't insert object of class %s to %s", get_class($value), self::class)); } $this->elements[$offset] = $value; } /** * {@inheritdoc} */ public function offsetUnset($offset): void { unset($this->elements[$offset]); } /** * {@inheritdoc} */ public function offsetGet($offset): CollectionEntity { return $this->elements[$offset]; } /** * Add entity to the collection * @param CollectionEntity $item * @return $this */ public function add(CollectionEntity $item): self { $this->elements[($this->idMapper)($item)] = $item; return $this; } /** * Add entities from array to the collection * @param iterable<CollectionEntity> $items * @return $this */ public function addArray(iterable $items): self { foreach ($items as $item) { $this->add($item); } return $this; } /** * Remove entity from collection by id * @param mixed $id * @return $this */ public function remove($id): self { unset($this->elements[$id]); return $this; } /** * Check if entity exists by id * @param mixed $id * @return bool */ public function has($id): bool { return isset($this->elements[$id]); } /** * Get entity by id. * @param mixed $id * @return CollectionEntity|null */ public function get($id): ?CollectionEntity { return $this->elements[$id] ?? null; } /** * @phpstan-pure * Map collection by given callable. Creates new collection * @param callable $mapper * @return static */ public function map(callable $mapper): self { return (new static($this->idMapper)) ->addArray(array_map($mapper, $this->elements)); } /** * @phpstan-pure * Creates a new collection mapped by key defined by given callable * @param callable $idMapper * @return static */ public function mapKeys(callable $idMapper): self { return (new static($idMapper))->addArray($this->elements); } /** * Map collection to array by given callable. * @param callable $mapper * @return array<mixed, CollectionEntity> */ public function mapToArray(callable $mapper): array { return array_map($mapper, $this->elements); } /** * Transform current collection with given function * @param callable $mapper * @return $this */ public function transform(callable $mapper): self { $this->elements = array_map($mapper, $this->elements); return $this; } /** * @phpstan-pure * Filter collection by given callback. Returns new collection * @param callable $mapper * @return static */ public function filter(callable $mapper): self { return (new static($this->idMapper))->addArray(array_filter($this->elements, $mapper) ?? []); } /** * Sort collection by given callback * @param callable $sorter * @return $this */ public function sort(callable $sorter): self { uasort($this->elements, $sorter); return $this; } /** * @phpstan-pure * Split collection on the given number of chunks. Returns array of new collections * @param int $size * @return array<int, self> */ public function chunk(int $size): array { return array_map( fn(array $chunk) => (new static($this->idMapper))->addArray($chunk), array_chunk($this->elements, $size) ); } /** * @phpstan-pure * Groups collection by some value returned by a given callable * @param callable $keyResolver * @return static[] */ public function groupBy(callable $keyResolver): array { $result = []; foreach ($this->elements as $element) { $key = $keyResolver($element); if (!isset($result[$key])) { $result[$key] = new static($this->idMapper); } $result[$key]->add($element); } return $result; } /** * Returns collection size. * @return int */ public function count(): int { return count($this->elements); } /** * Clear collection * @return $this */ public function clear(): self { $this->elements = []; return $this; } /** * @phpstan-pure * Merges current collection with a given collection. Returns new collection * @param self $collection * @return self */ public function merge(self $collection): self { return (clone $collection)->addArray($this->elements); } /** * @phpstan-pure * Slices collection. Returns new collection as a result * @param int $offset * @param int|null $limit * @return static */ public function slice(int $offset, ?int $limit = null): self { if ($offset === 0 && empty($limit)) { return $this; } $collection = new static($this->idMapper); $collection->addArray(array_slice($this->elements, $offset, $limit, true)); return $collection; } /** * Cast collection to array * @return CollectionEntity[] */ public function toArray(): array { return $this->elements; } /** * Return collection keys * @return array<mixed> */ public function keys(): array { return array_keys($this->elements); } }
Installation
Install this package as a dependency using Composer.
composer require nkopylov/php-typed-collections
Usage
vendor/bin/typed-collections generate <--class=> <--generated-class-name=> <--path=> <--namespace=> <--template=> <--parent-class=> <--interfaces=> <--traits=>
generate
command generates a new statically typed collection and puts it alongside the collectable PHP class.
Command parameters:
--class
- mandatory parameter. Contains full class name for the collectable entity.--generated-class-name
- by default collection class name would be Collection. If you want to redefine this name - use this parameter for the short name of the class.--namespace
- by default collection would be created in the same namespace as the collectable class. If you want to redefine the namespace - use this parameter for the full namespace name--path
- by default collection would be placed alongside the collectable class in the file system. If you changed namespace or you need this collection to be put anywhere else - use this parameter for the new path--template
- default template is already set. It contains default methods and supports\Iterable
and\ArrayAccess
interfaces. Use this parameter if you want to set your own template. See src/templates/collection.template for the example.--parent-class
- use this parameter if you want generated collection to extend some class.--interfaces
- use this parameter if you need generated collection to implement some interfaces. Provide full name for each interface, split interfaces by comma.--traits
- use this parameter if you need generated collection to use some traits. Provide list of full trait names, separated by comma.
Example
vendor/bin/typed-collections generate --class=\MyEntity
This command will generate a collection \MyEntityCollection and put it alongside \MyEntity class.
Key mapping
php-typed-collections
supports three ways to set keys for collection entities:
- Autogenerated keys by int autoincrement (default)
getId()
method in the collectable class. Implement\Nkopylov\PhpCollections\ObjectWithIdentifier
to let generator knows, that you class supports that feature.- You can also provide your own function, that will be used for the key generation. Check
\Nkopylov\PhpCollections\Mappable
interface for more information. Also, test object\Nkopylov\Test\PhpCollections\TestObjectWithMappable
will help you.
Contributing
Contributions are welcome! Before contributing to this project, familiarize yourself with CONTRIBUTING.md.
To develop this project, you will need PHP 7.4 or greater and Composer.
After cloning this repository locally, execute the following commands:
cd /path/to/repository
composer install --dev
Now, you are ready to develop!