.doctrine-project.json000064400000001276144760112640011003 0ustar00{ "active": true, "name": "Collections", "slug": "collections", "docsSlug": "doctrine-collections", "versions": [ { "name": "2.0", "branchName": "2.0.x", "slug": "latest", "upcoming": true }, { "name": "1.8", "branchName": "1.8.x", "slug": "1.8", "upcoming": true }, { "name": "1.7", "branchName": "1.7.x", "slug": "1.7", "current": true }, { "name": "1.6", "branchName": "1.6.x", "slug": "1.6", "maintained": false } ] } CONTRIBUTING.md000064400000002641144760112640007005 0ustar00# Contribute to Doctrine Thank you for contributing to Doctrine! Before we can merge your Pull-Request here are some guidelines that you need to follow. These guidelines exist not to annoy you, but to keep the code base clean, unified and future proof. ## Coding Standard We use the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard). ## Unit-Tests Please try to add a test for your pull-request. * If you want to contribute new functionality add unit- or functional tests depending on the scope of the feature. You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project. It will run all the project tests. In order to do that, you will need a fresh copy of doctrine/collections, and you will have to run a composer installation in the project: ```sh git clone git@github.com:doctrine/collections.git cd collections curl -sS https://getcomposer.org/installer | php -- ./composer.phar install ``` ## Github Actions We automatically run your pull request through Github Actions against supported PHP versions. If you break the tests, we cannot merge your code, so please make sure that your code is working before opening up a Pull-Request. ## Getting merged Please allow us time to review your pull requests. We will give our best to review everything as fast as possible, but cannot always live up to our own expectations. Thank you very much again for your contribution! LICENSE000064400000002051144760112640005554 0ustar00Copyright (c) 2006-2013 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. README.md000064400000000547144760112640006036 0ustar00# Doctrine Collections [![Build Status](https://github.com/doctrine/collections/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/collections/actions) [![Code Coverage](https://codecov.io/gh/doctrine/collections/branch/2.0.x/graph/badge.svg)](https://codecov.io/gh/doctrine/collections/branch/2.0.x) Collections Abstraction library composer.json000064400000003164144760112640007277 0ustar00{ "name": "doctrine/collections", "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", "license": "MIT", "type": "library", "keywords": [ "php", "collections", "array", "iterators" ], "authors": [ { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, { "name": "Roman Borschel", "email": "roman@code-factory.org" }, { "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" } ], "homepage": "https://www.doctrine-project.org/projects/collections.html", "require": { "php": "^7.1.3 || ^8.0", "doctrine/deprecations": "^0.5.3 || ^1" }, "require-dev": { "doctrine/coding-standard": "^9.0", "phpstan/phpstan": "^1.4.8", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", "vimeo/psalm": "^4.22" }, "autoload": { "psr-4": { "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine/Tests" } }, "config": { "allow-plugins": { "composer/package-versions-deprecated": true, "dealerdirect/phpcodesniffer-composer-installer": true } } } docs/en/derived-collections.rst000064400000001342144760112640012573 0ustar00Derived Collections =================== You can create custom collection classes by extending the ``Doctrine\Common\Collections\ArrayCollection`` class. If the ``__construct`` semantics are different from the default ``ArrayCollection`` you can override the ``createFrom`` method: .. code-block:: php final class DerivedArrayCollection extends ArrayCollection { /** @var \stdClass */ private $foo; public function __construct(\stdClass $foo, array $elements = []) { $this->foo = $foo; parent::__construct($elements); } protected function createFrom(array $elements) : self { return new static($this->foo, $elements); } } docs/en/expression-builder.rst000064400000006726144760112640012473 0ustar00Expression Builder ================== The Expression Builder is a convenient fluent interface for building expressions to be used with the ``Doctrine\Common\Collections\Criteria`` class: .. code-block:: php $expressionBuilder = Criteria::expr(); $criteria = new Criteria(); $criteria->where($expressionBuilder->eq('name', 'jwage')); $criteria->orWhere($expressionBuilder->eq('name', 'romanb')); $collection->matching($criteria); The ``ExpressionBuilder`` has the following API: andX ---- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->andX( $expressionBuilder->eq('foo', 1), $expressionBuilder->eq('bar', 1) ); $collection->matching(new Criteria($expression)); orX --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->orX( $expressionBuilder->eq('foo', 1), $expressionBuilder->eq('bar', 1) ); $collection->matching(new Criteria($expression)); eq --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->eq('foo', 1); $collection->matching(new Criteria($expression)); gt --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->gt('foo', 1); $collection->matching(new Criteria($expression)); lt --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->lt('foo', 1); $collection->matching(new Criteria($expression)); gte --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->gte('foo', 1); $collection->matching(new Criteria($expression)); lte --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->lte('foo', 1); $collection->matching(new Criteria($expression)); neq --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->neq('foo', 1); $collection->matching(new Criteria($expression)); isNull ------ .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->isNull('foo'); $collection->matching(new Criteria($expression)); in --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->in('foo', ['value1', 'value2']); $collection->matching(new Criteria($expression)); notIn ----- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->notIn('foo', ['value1', 'value2']); $collection->matching(new Criteria($expression)); contains -------- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->contains('foo', 'value1'); $collection->matching(new Criteria($expression)); memberOf -------- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->memberOf('foo', ['value1', 'value2']); $collection->matching(new Criteria($expression)); startsWith ---------- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->startsWith('foo', 'hello'); $collection->matching(new Criteria($expression)); endsWith -------- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->endsWith('foo', 'world'); $collection->matching(new Criteria($expression)); docs/en/expressions.rst000064400000004263144760112640011224 0ustar00Expressions =========== The ``Doctrine\Common\Collections\Expr\Comparison`` class can be used to create expressions to be used with the ``Doctrine\Common\Collections\Criteria`` class. It has the following operator constants: - ``Comparison::EQ`` - ``Comparison::NEQ`` - ``Comparison::LT`` - ``Comparison::LTE`` - ``Comparison::GT`` - ``Comparison::GTE`` - ``Comparison::IS`` - ``Comparison::IN`` - ``Comparison::NIN`` - ``Comparison::CONTAINS`` - ``Comparison::MEMBER_OF`` - ``Comparison::STARTS_WITH`` - ``Comparison::ENDS_WITH`` The ``Doctrine\Common\Collections\Criteria`` class has the following API to be used with expressions: where ----- Sets the where expression to evaluate when this Criteria is searched for. .. code-block:: php $expr = new Comparison('key', Comparison::EQ, 'value'); $criteria->where($expr); andWhere -------- Appends the where expression to evaluate when this Criteria is searched for using an AND with previous expression. .. code-block:: php $expr = new Comparison('key', Comparison::EQ, 'value'); $criteria->andWhere($expr); orWhere ------- Appends the where expression to evaluate when this Criteria is searched for using an OR with previous expression. .. code-block:: php $expr1 = new Comparison('key', Comparison::EQ, 'value1'); $expr2 = new Comparison('key', Comparison::EQ, 'value2'); $criteria->where($expr1); $criteria->orWhere($expr2); orderBy ------- Sets the ordering of the result of this Criteria. .. code-block:: php $criteria->orderBy(['name' => Criteria::ASC]); setFirstResult -------------- Set the number of first result that this Criteria should return. .. code-block:: php $criteria->setFirstResult(0); getFirstResult -------------- Gets the current first result option of this Criteria. .. code-block:: php $criteria->setFirstResult(10); echo $criteria->getFirstResult(); // 10 setMaxResults ------------- Sets the max results that this Criteria should return. .. code-block:: php $criteria->setMaxResults(20); getMaxResults ------------- Gets the current max results option of this Criteria. .. code-block:: php $criteria->setMaxResults(20); echo $criteria->getMaxResults(); // 20 docs/en/index.rst000064400000017677144760112640007766 0ustar00Introduction ============ Doctrine Collections is a library that contains classes for working with arrays of data. Here is an example using the simple ``Doctrine\Common\Collections\ArrayCollection`` class: .. code-block:: php filter(function($element) { return $element > 1; }); // [2, 3] Collection Methods ================== Doctrine Collections provides an interface named ``Doctrine\Common\Collections\Collection`` that resembles the nature of a regular PHP array. That is, it is essentially an **ordered map** that can also be used like a list. A Collection has an internal iterator just like a PHP array. In addition, a Collection can be iterated with external iterators, which is preferable. To use an external iterator simply use the foreach language construct to iterate over the collection, which calls ``getIterator()`` internally, or explicitly retrieve an iterator though ``getIterator()`` which can then be used to iterate over the collection. You can not rely on the internal iterator of the collection being at a certain position unless you explicitly positioned it before. The methods available on the interface are: add --- Adds an element at the end of the collection. .. code-block:: php $collection->add('test'); clear ----- Clears the collection, removing all elements. .. code-block:: php $collection->clear(); contains -------- Checks whether an element is contained in the collection. This is an O(n) operation, where n is the size of the collection. .. code-block:: php $collection = new Collection(['test']); $contains = $collection->contains('test'); // true containsKey ----------- Checks whether the collection contains an element with the specified key/index. .. code-block:: php $collection = new Collection(['test' => true]); $contains = $collection->containsKey('test'); // true current ------- Gets the element of the collection at the current iterator position. .. code-block:: php $collection = new Collection(['first', 'second', 'third']); $current = $collection->current(); // first get --- Gets the element at the specified key/index. .. code-block:: php $collection = new Collection([ 'key' => 'value', ]); $value = $collection->get('key'); // value getKeys ------- Gets all keys/indices of the collection. .. code-block:: php $collection = new Collection(['a', 'b', 'c']); $keys = $collection->getKeys(); // [0, 1, 2] getValues --------- Gets all values of the collection. .. code-block:: php $collection = new Collection([ 'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', ]); $values = $collection->getValues(); // ['value1', 'value2', 'value3'] isEmpty ------- Checks whether the collection is empty (contains no elements). .. code-block:: php $collection = new Collection(['a', 'b', 'c']); $isEmpty = $collection->isEmpty(); // false first ----- Sets the internal iterator to the first element in the collection and returns this element. .. code-block:: php $collection = new Collection(['first', 'second', 'third']); $first = $collection->first(); // first exists ------ Tests for the existence of an element that satisfies the given predicate. .. code-block:: php $collection = new Collection(['first', 'second', 'third']); $exists = $collection->exists(function($key, $value) { return $value === 'first'; }); // true filter ------ Returns all the elements of this collection for which your callback function returns `true`. The order and keys of the elements are preserved. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $filteredCollection = $collection->filter(function($element) { return $element > 1; }); // [2, 3] forAll ------ Tests whether the given predicate holds for all elements of this collection. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $forAll = $collection->forAll(function($key, $value) { return $value > 1; }); // false indexOf ------- Gets the index/key of a given element. The comparison of two elements is strict, that means not only the value but also the type must match. For objects this means reference equality. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $indexOf = $collection->indexOf(3); // 2 key --- Gets the key/index of the element at the current iterator position. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $collection->next(); $key = $collection->key(); // 1 last ---- Sets the internal iterator to the last element in the collection and returns this element. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $last = $collection->last(); // 3 map --- Applies the given function to each element in the collection and returns a new collection with the elements returned by the function. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $mappedCollection = $collection->map(function($value) { return $value + 1; }); // [2, 3, 4] next ---- Moves the internal iterator position to the next element and returns this element. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $next = $collection->next(); // 2 partition --------- Partitions this collection in two collections according to a predicate. Keys are preserved in the resulting collections. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $mappedCollection = $collection->partition(function($key, $value) { return $value > 1 }); // [[2, 3], [1]] remove ------ Removes the element at the specified index from the collection. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $collection->remove(0); // [2, 3] removeElement ------------- Removes the specified element from the collection, if it is found. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $collection->removeElement(3); // [1, 2] set --- Sets an element in the collection at the specified key/index. .. code-block:: php $collection = new ArrayCollection(); $collection->set('name', 'jwage'); slice ----- Extracts a slice of $length elements starting at position $offset from the Collection. If $length is null it returns all elements from $offset to the end of the Collection. Keys have to be preserved by this method. Calling this method will only return the selected slice and NOT change the elements contained in the collection slice is called on. .. code-block:: php $collection = new ArrayCollection([0, 1, 2, 3, 4, 5]); $slice = $collection->slice(1, 2); // [1, 2] toArray ------- Gets a native PHP array representation of the collection. .. code-block:: php $collection = new ArrayCollection([0, 1, 2, 3, 4, 5]); $array = $collection->toArray(); // [0, 1, 2, 3, 4, 5] Selectable Methods ================== Some Doctrine Collections, like ``Doctrine\Common\Collections\ArrayCollection``, implement an interface named ``Doctrine\Common\Collections\Selectable`` that offers the usage of a powerful expressions API, where conditions can be applied to a collection to get a result with matching elements only. matching -------- Selects all elements from a selectable that match the expression and returns a new collection containing these elements. .. code-block:: php use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\Expr\Comparison; $collection = new ArrayCollection([ [ 'name' => 'jwage', ], [ 'name' => 'romanb', ], ]); $expr = new Comparison('name', '=', 'jwage'); $criteria = new Criteria(); $criteria->where($expr); $matched = $collection->matching($criteria); // ['jwage'] You can read more about expressions :ref:`here `. docs/en/lazy-collections.rst000064400000001344144760112640012132 0ustar00Lazy Collections ================ To create a lazy collection you can extend the ``Doctrine\Common\Collections\AbstractLazyCollection`` class and define the ``doInitialize`` method. Here is an example where we lazily query the database for a collection of user records: .. code-block:: php use Doctrine\DBAL\Connection; class UsersLazyCollection extends AbstractLazyCollection { /** @var Connection */ private $connection; public function __construct(Connection $connection) { $this->connection = $connection; } protected function doInitialize() : void { $this->collection = $this->connection->fetchAll('SELECT * FROM users'); } } docs/en/sidebar.rst000064400000000172144760112640010246 0ustar00.. toctree:: :depth: 3 index expressions expression-builder derived-collections lazy-collections lib/Doctrine/Common/Collections/AbstractLazyCollection.php000064400000015217144760112640017732 0ustar00 */ abstract class AbstractLazyCollection implements Collection { /** * The backed collection to use * * @psalm-var Collection|null * @var Collection|null */ protected $collection; /** @var bool */ protected $initialized = false; /** * {@inheritDoc} * * @return int */ #[ReturnTypeWillChange] public function count() { $this->initialize(); return $this->collection->count(); } /** * {@inheritDoc} */ public function add($element) { $this->initialize(); return $this->collection->add($element); } /** * {@inheritDoc} */ public function clear() { $this->initialize(); $this->collection->clear(); } /** * {@inheritDoc} */ public function contains($element) { $this->initialize(); return $this->collection->contains($element); } /** * {@inheritDoc} */ public function isEmpty() { $this->initialize(); return $this->collection->isEmpty(); } /** * {@inheritDoc} */ public function remove($key) { $this->initialize(); return $this->collection->remove($key); } /** * {@inheritDoc} */ public function removeElement($element) { $this->initialize(); return $this->collection->removeElement($element); } /** * {@inheritDoc} */ public function containsKey($key) { $this->initialize(); return $this->collection->containsKey($key); } /** * {@inheritDoc} */ public function get($key) { $this->initialize(); return $this->collection->get($key); } /** * {@inheritDoc} */ public function getKeys() { $this->initialize(); return $this->collection->getKeys(); } /** * {@inheritDoc} */ public function getValues() { $this->initialize(); return $this->collection->getValues(); } /** * {@inheritDoc} */ public function set($key, $value) { $this->initialize(); $this->collection->set($key, $value); } /** * {@inheritDoc} */ public function toArray() { $this->initialize(); return $this->collection->toArray(); } /** * {@inheritDoc} */ public function first() { $this->initialize(); return $this->collection->first(); } /** * {@inheritDoc} */ public function last() { $this->initialize(); return $this->collection->last(); } /** * {@inheritDoc} */ public function key() { $this->initialize(); return $this->collection->key(); } /** * {@inheritDoc} */ public function current() { $this->initialize(); return $this->collection->current(); } /** * {@inheritDoc} */ public function next() { $this->initialize(); return $this->collection->next(); } /** * {@inheritDoc} */ public function exists(Closure $p) { $this->initialize(); return $this->collection->exists($p); } /** * {@inheritDoc} */ public function filter(Closure $p) { $this->initialize(); return $this->collection->filter($p); } /** * {@inheritDoc} */ public function forAll(Closure $p) { $this->initialize(); return $this->collection->forAll($p); } /** * {@inheritDoc} */ public function map(Closure $func) { $this->initialize(); return $this->collection->map($func); } /** * {@inheritDoc} */ public function partition(Closure $p) { $this->initialize(); return $this->collection->partition($p); } /** * {@inheritDoc} */ public function indexOf($element) { $this->initialize(); return $this->collection->indexOf($element); } /** * {@inheritDoc} */ public function slice($offset, $length = null) { $this->initialize(); return $this->collection->slice($offset, $length); } /** * {@inheritDoc} * * @return Traversable * @psalm-return Traversable */ #[ReturnTypeWillChange] public function getIterator() { $this->initialize(); return $this->collection->getIterator(); } /** * @param TKey $offset * * @return bool */ #[ReturnTypeWillChange] public function offsetExists($offset) { $this->initialize(); return $this->collection->offsetExists($offset); } /** * @param TKey $offset * * @return mixed */ #[ReturnTypeWillChange] public function offsetGet($offset) { $this->initialize(); return $this->collection->offsetGet($offset); } /** * @param TKey|null $offset * @param T $value * * @return void */ #[ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->initialize(); $this->collection->offsetSet($offset, $value); } /** * @param TKey $offset * * @return void */ #[ReturnTypeWillChange] public function offsetUnset($offset) { $this->initialize(); $this->collection->offsetUnset($offset); } /** * Is the lazy collection already initialized? * * @return bool * * @psalm-assert-if-true Collection $this->collection */ public function isInitialized() { return $this->initialized; } /** * Initialize the collection * * @return void * * @psalm-assert Collection $this->collection */ protected function initialize() { if ($this->initialized) { return; } $this->doInitialize(); $this->initialized = true; if ($this->collection === null) { throw new LogicException('You must initialize the collection property in the doInitialize() method.'); } } /** * Do the initialization logic * * @return void */ abstract protected function doInitialize(); } lib/Doctrine/Common/Collections/ArrayCollection.php000064400000022267144760112640016410 0ustar00 * @template-implements Selectable * @psalm-consistent-constructor */ class ArrayCollection implements Collection, Selectable { /** * An array containing the entries of this collection. * * @psalm-var array * @var mixed[] */ private $elements; /** * Initializes a new ArrayCollection. * * @param array $elements * @psalm-param array $elements */ public function __construct(array $elements = []) { $this->elements = $elements; } /** * {@inheritDoc} */ public function toArray() { return $this->elements; } /** * {@inheritDoc} */ public function first() { return reset($this->elements); } /** * Creates a new instance from the specified elements. * * This method is provided for derived classes to specify how a new * instance should be created when constructor semantics have changed. * * @param array $elements Elements. * @psalm-param array $elements * * @return static * @psalm-return static * * @psalm-template K of array-key * @psalm-template V */ protected function createFrom(array $elements) { return new static($elements); } /** * {@inheritDoc} */ public function last() { return end($this->elements); } /** * {@inheritDoc} */ public function key() { return key($this->elements); } /** * {@inheritDoc} */ public function next() { return next($this->elements); } /** * {@inheritDoc} */ public function current() { return current($this->elements); } /** * {@inheritDoc} */ public function remove($key) { if (! isset($this->elements[$key]) && ! array_key_exists($key, $this->elements)) { return null; } $removed = $this->elements[$key]; unset($this->elements[$key]); return $removed; } /** * {@inheritDoc} */ public function removeElement($element) { $key = array_search($element, $this->elements, true); if ($key === false) { return false; } unset($this->elements[$key]); return true; } /** * Required by interface ArrayAccess. * * @param TKey $offset * * @return bool */ #[ReturnTypeWillChange] public function offsetExists($offset) { return $this->containsKey($offset); } /** * Required by interface ArrayAccess. * * @param TKey $offset * * @return mixed */ #[ReturnTypeWillChange] public function offsetGet($offset) { return $this->get($offset); } /** * Required by interface ArrayAccess. * * @param TKey|null $offset * @param T $value * * @return void */ #[ReturnTypeWillChange] public function offsetSet($offset, $value) { if (! isset($offset)) { $this->add($value); return; } $this->set($offset, $value); } /** * Required by interface ArrayAccess. * * @param TKey $offset * * @return void */ #[ReturnTypeWillChange] public function offsetUnset($offset) { $this->remove($offset); } /** * {@inheritDoc} */ public function containsKey($key) { return isset($this->elements[$key]) || array_key_exists($key, $this->elements); } /** * {@inheritDoc} */ public function contains($element) { return in_array($element, $this->elements, true); } /** * {@inheritDoc} */ public function exists(Closure $p) { foreach ($this->elements as $key => $element) { if ($p($key, $element)) { return true; } } return false; } /** * {@inheritDoc} */ public function indexOf($element) { return array_search($element, $this->elements, true); } /** * {@inheritDoc} */ public function get($key) { return $this->elements[$key] ?? null; } /** * {@inheritDoc} */ public function getKeys() { return array_keys($this->elements); } /** * {@inheritDoc} */ public function getValues() { return array_values($this->elements); } /** * {@inheritDoc} * * @return int */ #[ReturnTypeWillChange] public function count() { return count($this->elements); } /** * {@inheritDoc} */ public function set($key, $value) { $this->elements[$key] = $value; } /** * {@inheritDoc} * * @psalm-suppress InvalidPropertyAssignmentValue * * This breaks assumptions about the template type, but it would * be a backwards-incompatible change to remove this method */ public function add($element) { $this->elements[] = $element; return true; } /** * {@inheritDoc} */ public function isEmpty() { return empty($this->elements); } /** * {@inheritDoc} * * @return Traversable * @psalm-return Traversable */ #[ReturnTypeWillChange] public function getIterator() { return new ArrayIterator($this->elements); } /** * {@inheritDoc} * * @psalm-param Closure(T=):U $func * * @return static * @psalm-return static * * @psalm-template U */ public function map(Closure $func) { return $this->createFrom(array_map($func, $this->elements)); } /** * {@inheritDoc} * * @return static * @psalm-return static */ public function filter(Closure $p) { return $this->createFrom(array_filter($this->elements, $p, ARRAY_FILTER_USE_BOTH)); } /** * {@inheritDoc} */ public function forAll(Closure $p) { foreach ($this->elements as $key => $element) { if (! $p($key, $element)) { return false; } } return true; } /** * {@inheritDoc} */ public function partition(Closure $p) { $matches = $noMatches = []; foreach ($this->elements as $key => $element) { if ($p($key, $element)) { $matches[$key] = $element; } else { $noMatches[$key] = $element; } } return [$this->createFrom($matches), $this->createFrom($noMatches)]; } /** * Returns a string representation of this object. * * @return string */ public function __toString() { return self::class . '@' . spl_object_hash($this); } /** * {@inheritDoc} */ public function clear() { $this->elements = []; } /** * {@inheritDoc} */ public function slice($offset, $length = null) { return array_slice($this->elements, $offset, $length, true); } /** * {@inheritDoc} */ public function matching(Criteria $criteria) { $expr = $criteria->getWhereExpression(); $filtered = $this->elements; if ($expr) { $visitor = new ClosureExpressionVisitor(); $filter = $visitor->dispatch($expr); $filtered = array_filter($filtered, $filter); } $orderings = $criteria->getOrderings(); if ($orderings) { $next = null; foreach (array_reverse($orderings) as $field => $ordering) { $next = ClosureExpressionVisitor::sortByField($field, $ordering === Criteria::DESC ? -1 : 1, $next); } uasort($filtered, $next); } $offset = $criteria->getFirstResult(); $length = $criteria->getMaxResults(); if ($offset || $length) { $filtered = array_slice($filtered, (int) $offset, $length); } return $this->createFrom($filtered); } } lib/Doctrine/Common/Collections/Collection.php000064400000021120144760112640015374 0ustar00ordered map that can also be used * like a list. * * A Collection has an internal iterator just like a PHP array. In addition, * a Collection can be iterated with external iterators, which is preferable. * To use an external iterator simply use the foreach language construct to * iterate over the collection (which calls {@link getIterator()} internally) or * explicitly retrieve an iterator though {@link getIterator()} which can then be * used to iterate over the collection. * You can not rely on the internal iterator of the collection being at a certain * position unless you explicitly positioned it before. Prefer iteration with * external iterators. * * @psalm-template TKey of array-key * @psalm-template T * @template-extends IteratorAggregate * @template-extends ArrayAccess */ interface Collection extends Countable, IteratorAggregate, ArrayAccess { /** * Adds an element at the end of the collection. * * @param mixed $element The element to add. * @psalm-param T $element * * @return true Always TRUE. */ public function add($element); /** * Clears the collection, removing all elements. * * @return void */ public function clear(); /** * Checks whether an element is contained in the collection. * This is an O(n) operation, where n is the size of the collection. * * @param mixed $element The element to search for. * @psalm-param T $element * * @return bool TRUE if the collection contains the element, FALSE otherwise. */ public function contains($element); /** * Checks whether the collection is empty (contains no elements). * * @return bool TRUE if the collection is empty, FALSE otherwise. */ public function isEmpty(); /** * Removes the element at the specified index from the collection. * * @param string|int $key The key/index of the element to remove. * @psalm-param TKey $key * * @return mixed The removed element or NULL, if the collection did not contain the element. * @psalm-return T|null */ public function remove($key); /** * Removes the specified element from the collection, if it is found. * * @param mixed $element The element to remove. * @psalm-param T $element * * @return bool TRUE if this collection contained the specified element, FALSE otherwise. */ public function removeElement($element); /** * Checks whether the collection contains an element with the specified key/index. * * @param string|int $key The key/index to check for. * @psalm-param TKey $key * * @return bool TRUE if the collection contains an element with the specified key/index, * FALSE otherwise. */ public function containsKey($key); /** * Gets the element at the specified key/index. * * @param string|int $key The key/index of the element to retrieve. * @psalm-param TKey $key * * @return mixed * @psalm-return T|null */ public function get($key); /** * Gets all keys/indices of the collection. * * @return int[]|string[] The keys/indices of the collection, in the order of the corresponding * elements in the collection. * @psalm-return TKey[] */ public function getKeys(); /** * Gets all values of the collection. * * @return mixed[] The values of all elements in the collection, in the * order they appear in the collection. * @psalm-return list */ public function getValues(); /** * Sets an element in the collection at the specified key/index. * * @param string|int $key The key/index of the element to set. * @param mixed $value The element to set. * @psalm-param TKey $key * @psalm-param T $value * * @return void */ public function set($key, $value); /** * Gets a native PHP array representation of the collection. * * @return mixed[] * @psalm-return array */ public function toArray(); /** * Sets the internal iterator to the first element in the collection and returns this element. * * @return mixed * @psalm-return T|false */ public function first(); /** * Sets the internal iterator to the last element in the collection and returns this element. * * @return mixed * @psalm-return T|false */ public function last(); /** * Gets the key/index of the element at the current iterator position. * * @return int|string|null * @psalm-return TKey|null */ public function key(); /** * Gets the element of the collection at the current iterator position. * * @return mixed * @psalm-return T|false */ public function current(); /** * Moves the internal iterator position to the next element and returns this element. * * @return mixed * @psalm-return T|false */ public function next(); /** * Tests for the existence of an element that satisfies the given predicate. * * @param Closure $p The predicate. * @psalm-param Closure(TKey=, T=):bool $p * * @return bool TRUE if the predicate is TRUE for at least one element, FALSE otherwise. */ public function exists(Closure $p); /** * Returns all the elements of this collection that satisfy the predicate p. * The order of the elements is preserved. * * @param Closure $p The predicate used for filtering. * @psalm-param Closure(T=):bool $p * * @return Collection A collection with the results of the filter operation. * @psalm-return Collection */ public function filter(Closure $p); /** * Tests whether the given predicate p holds for all elements of this collection. * * @param Closure $p The predicate. * @psalm-param Closure(TKey=, T=):bool $p * * @return bool TRUE, if the predicate yields TRUE for all elements, FALSE otherwise. */ public function forAll(Closure $p); /** * Applies the given function to each element in the collection and returns * a new collection with the elements returned by the function. * * @psalm-param Closure(T=):U $func * * @return Collection * @psalm-return Collection * * @psalm-template U */ public function map(Closure $func); /** * Partitions this collection in two collections according to a predicate. * Keys are preserved in the resulting collections. * * @param Closure $p The predicate on which to partition. * @psalm-param Closure(TKey=, T=):bool $p * * @return Collection[] An array with two elements. The first element contains the collection * of elements where the predicate returned TRUE, the second element * contains the collection of elements where the predicate returned FALSE. * @psalm-return array{0: Collection, 1: Collection} */ public function partition(Closure $p); /** * Gets the index/key of a given element. The comparison of two elements is strict, * that means not only the value but also the type must match. * For objects this means reference equality. * * @param mixed $element The element to search for. * @psalm-param T $element * * @return int|string|bool The key/index of the element or FALSE if the element was not found. * @psalm-return TKey|false */ public function indexOf($element); /** * Extracts a slice of $length elements starting at position $offset from the Collection. * * If $length is null it returns all elements from $offset to the end of the Collection. * Keys have to be preserved by this method. Calling this method will only return the * selected slice and NOT change the elements contained in the collection slice is called on. * * @param int $offset The offset to start from. * @param int|null $length The maximum number of elements to return, or null for no limit. * * @return mixed[] * @psalm-return array */ public function slice($offset, $length = null); } lib/Doctrine/Common/Collections/Criteria.php000064400000013002144760112640015043 0ustar00expression = $expression; if ($firstResult === null && func_num_args() > 2) { Deprecation::trigger( 'doctrine/collections', 'https://github.com/doctrine/collections/pull/311', 'Passing null as $firstResult to the constructor of %s is deprecated. Pass 0 instead or omit the argument.', self::class ); } $this->setFirstResult($firstResult); $this->setMaxResults($maxResults); if ($orderings === null) { return; } $this->orderBy($orderings); } /** * Sets the where expression to evaluate when this Criteria is searched for. * * @return $this */ public function where(Expression $expression) { $this->expression = $expression; return $this; } /** * Appends the where expression to evaluate when this Criteria is searched for * using an AND with previous expression. * * @return $this */ public function andWhere(Expression $expression) { if ($this->expression === null) { return $this->where($expression); } $this->expression = new CompositeExpression( CompositeExpression::TYPE_AND, [$this->expression, $expression] ); return $this; } /** * Appends the where expression to evaluate when this Criteria is searched for * using an OR with previous expression. * * @return $this */ public function orWhere(Expression $expression) { if ($this->expression === null) { return $this->where($expression); } $this->expression = new CompositeExpression( CompositeExpression::TYPE_OR, [$this->expression, $expression] ); return $this; } /** * Gets the expression attached to this Criteria. * * @return Expression|null */ public function getWhereExpression() { return $this->expression; } /** * Gets the current orderings of this Criteria. * * @return string[] */ public function getOrderings() { return $this->orderings; } /** * Sets the ordering of the result of this Criteria. * * Keys are field and values are the order, being either ASC or DESC. * * @see Criteria::ASC * @see Criteria::DESC * * @param string[] $orderings * * @return $this */ public function orderBy(array $orderings) { $this->orderings = array_map( static function (string $ordering): string { return strtoupper($ordering) === Criteria::ASC ? Criteria::ASC : Criteria::DESC; }, $orderings ); return $this; } /** * Gets the current first result option of this Criteria. * * @return int|null */ public function getFirstResult() { return $this->firstResult; } /** * Set the number of first result that this Criteria should return. * * @param int|null $firstResult The value to set. * * @return $this */ public function setFirstResult($firstResult) { if ($firstResult === null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/collections', 'https://github.com/doctrine/collections/pull/311', 'Passing null to %s() is deprecated, pass 0 instead.', __METHOD__ ); } $this->firstResult = $firstResult; return $this; } /** * Gets maxResults. * * @return int|null */ public function getMaxResults() { return $this->maxResults; } /** * Sets maxResults. * * @param int|null $maxResults The value to set. * * @return $this */ public function setMaxResults($maxResults) { $this->maxResults = $maxResults; return $this; } } lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php000064400000020711144760112640021300 0ustar00$accessor(); } } if (preg_match('/^is[A-Z]+/', $field) === 1 && method_exists($object, $field)) { return $object->$field(); } // __call should be triggered for get. $accessor = $accessors[0] . $field; if (method_exists($object, '__call')) { return $object->$accessor(); } if ($object instanceof ArrayAccess) { return $object[$field]; } if (isset($object->$field)) { return $object->$field; } // camelcase field name to support different variable naming conventions $ccField = preg_replace_callback('/_(.?)/', static function ($matches) { return strtoupper($matches[1]); }, $field); foreach ($accessors as $accessor) { $accessor .= $ccField; if (method_exists($object, $accessor)) { return $object->$accessor(); } } return $object->$field; } /** * Helper for sorting arrays of objects based on multiple fields + orientations. * * @param string $name * @param int $orientation * * @return Closure */ public static function sortByField($name, $orientation = 1, ?Closure $next = null) { if (! $next) { $next = static function (): int { return 0; }; } return static function ($a, $b) use ($name, $next, $orientation): int { $aValue = ClosureExpressionVisitor::getObjectFieldValue($a, $name); $bValue = ClosureExpressionVisitor::getObjectFieldValue($b, $name); if ($aValue === $bValue) { return $next($a, $b); } return ($aValue > $bValue ? 1 : -1) * $orientation; }; } /** * {@inheritDoc} */ public function walkComparison(Comparison $comparison) { $field = $comparison->getField(); $value = $comparison->getValue()->getValue(); // shortcut for walkValue() switch ($comparison->getOperator()) { case Comparison::EQ: return static function ($object) use ($field, $value): bool { return ClosureExpressionVisitor::getObjectFieldValue($object, $field) === $value; }; case Comparison::NEQ: return static function ($object) use ($field, $value): bool { return ClosureExpressionVisitor::getObjectFieldValue($object, $field) !== $value; }; case Comparison::LT: return static function ($object) use ($field, $value): bool { return ClosureExpressionVisitor::getObjectFieldValue($object, $field) < $value; }; case Comparison::LTE: return static function ($object) use ($field, $value): bool { return ClosureExpressionVisitor::getObjectFieldValue($object, $field) <= $value; }; case Comparison::GT: return static function ($object) use ($field, $value): bool { return ClosureExpressionVisitor::getObjectFieldValue($object, $field) > $value; }; case Comparison::GTE: return static function ($object) use ($field, $value): bool { return ClosureExpressionVisitor::getObjectFieldValue($object, $field) >= $value; }; case Comparison::IN: return static function ($object) use ($field, $value): bool { $fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field); return in_array($fieldValue, $value, is_scalar($fieldValue)); }; case Comparison::NIN: return static function ($object) use ($field, $value): bool { $fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field); return ! in_array($fieldValue, $value, is_scalar($fieldValue)); }; case Comparison::CONTAINS: return static function ($object) use ($field, $value) { return strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value) !== false; }; case Comparison::MEMBER_OF: return static function ($object) use ($field, $value): bool { $fieldValues = ClosureExpressionVisitor::getObjectFieldValue($object, $field); if (! is_array($fieldValues)) { $fieldValues = iterator_to_array($fieldValues); } return in_array($value, $fieldValues, true); }; case Comparison::STARTS_WITH: return static function ($object) use ($field, $value): bool { return strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value) === 0; }; case Comparison::ENDS_WITH: return static function ($object) use ($field, $value): bool { return $value === substr(ClosureExpressionVisitor::getObjectFieldValue($object, $field), -strlen($value)); }; default: throw new RuntimeException('Unknown comparison operator: ' . $comparison->getOperator()); } } /** * {@inheritDoc} */ public function walkValue(Value $value) { return $value->getValue(); } /** * {@inheritDoc} */ public function walkCompositeExpression(CompositeExpression $expr) { $expressionList = []; foreach ($expr->getExpressionList() as $child) { $expressionList[] = $this->dispatch($child); } switch ($expr->getType()) { case CompositeExpression::TYPE_AND: return $this->andExpressions($expressionList); case CompositeExpression::TYPE_OR: return $this->orExpressions($expressionList); default: throw new RuntimeException('Unknown composite ' . $expr->getType()); } } /** * @param callable[] $expressions */ private function andExpressions(array $expressions): callable { return static function ($object) use ($expressions): bool { foreach ($expressions as $expression) { if (! $expression($object)) { return false; } } return true; }; } /** * @param callable[] $expressions */ private function orExpressions(array $expressions): callable { return static function ($object) use ($expressions): bool { foreach ($expressions as $expression) { if ($expression($object)) { return true; } } return false; }; } } lib/Doctrine/Common/Collections/Expr/Comparison.php000064400000003211144760112640016332 0ustar00'; public const LT = '<'; public const LTE = '<='; public const GT = '>'; public const GTE = '>='; public const IS = '='; // no difference with EQ public const IN = 'IN'; public const NIN = 'NIN'; public const CONTAINS = 'CONTAINS'; public const MEMBER_OF = 'MEMBER_OF'; public const STARTS_WITH = 'STARTS_WITH'; public const ENDS_WITH = 'ENDS_WITH'; /** @var string */ private $field; /** @var string */ private $op; /** @var Value */ private $value; /** * @param string $field * @param string $operator * @param mixed $value */ public function __construct($field, $operator, $value) { if (! ($value instanceof Value)) { $value = new Value($value); } $this->field = $field; $this->op = $operator; $this->value = $value; } /** * @return string */ public function getField() { return $this->field; } /** * @return Value */ public function getValue() { return $this->value; } /** * @return string */ public function getOperator() { return $this->op; } /** * {@inheritDoc} */ public function visit(ExpressionVisitor $visitor) { return $visitor->walkComparison($this); } } lib/Doctrine/Common/Collections/Expr/CompositeExpression.php000064400000002723144760112640020251 0ustar00type = $type; foreach ($expressions as $expr) { if ($expr instanceof Value) { throw new RuntimeException('Values are not supported expressions as children of and/or expressions.'); } if (! ($expr instanceof Expression)) { throw new RuntimeException('No expression given to CompositeExpression.'); } $this->expressions[] = $expr; } } /** * Returns the list of expressions nested in this composite. * * @return Expression[] */ public function getExpressionList() { return $this->expressions; } /** * @return string */ public function getType() { return $this->type; } /** * {@inheritDoc} */ public function visit(ExpressionVisitor $visitor) { return $visitor->walkCompositeExpression($this); } } lib/Doctrine/Common/Collections/Expr/Expression.php000064400000000345144760112640016364 0ustar00walkComparison($expr); case $expr instanceof Value: return $this->walkValue($expr); case $expr instanceof CompositeExpression: return $this->walkCompositeExpression($expr); default: throw new RuntimeException('Unknown Expression ' . get_class($expr)); } } } lib/Doctrine/Common/Collections/Expr/Value.php000064400000001004144760112640015272 0ustar00value = $value; } /** * @return mixed */ public function getValue() { return $this->value; } /** * {@inheritDoc} */ public function visit(ExpressionVisitor $visitor) { return $visitor->walkValue($this); } } lib/Doctrine/Common/Collections/ExpressionBuilder.php000064400000007701144760112640016760 0ustar00&Selectable * @psalm-return Collection&Selectable */ public function matching(Criteria $criteria); }