collection methods

This commit is contained in:
Yuri Kuznetsov
2025-02-12 12:43:31 +02:00
parent 67df53dda8
commit 1d6f654c2f
4 changed files with 231 additions and 2 deletions
+69
View File
@@ -37,6 +37,7 @@ use SeekableIterator;
use RuntimeException;
use OutOfBoundsException;
use InvalidArgumentException;
use Closure;
/**
* A standard collection of entities. It allocates a memory for all entities.
@@ -399,4 +400,72 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
return $obj;
}
/**
* Filter.
*
* @param Closure(TEntity): bool $callback A filter callback.
* @return self<TEntity> A filtered collection. A new instance.
* @since 9.1.0
*/
public function filter(Closure $callback): self
{
$newList = [];
foreach ($this as $entity) {
if ($callback($entity)) {
$newList[] = $entity;
}
}
return new EntityCollection($newList, $this->entityType, $this->entityFactory);
}
/**
* Sort.
*
* @param Closure(TEntity, TEntity): int $callback The comparison function.
* @return self<TEntity> A sorted collection. A new instance.
* @since 9.1.0
*/
public function sort(Closure $callback): self
{
$newList = [...$this];
usort($newList, $callback);
return new EntityCollection($newList, $this->entityType, $this->entityFactory);
}
/**
* Reverse.
*
* @return self<TEntity> A reversed collection.
* @since 9.1.0
*/
public function reverse(): self
{
$newList = array_reverse([...$this]);
return new EntityCollection($newList, $this->entityType, $this->entityFactory);
}
/**
* Find.
*
* @param Closure(TEntity): bool $callback A filter callback.
* @return ?TEntity
* @since 9.1.0
* @noinspection PhpDocSignatureInspection
*/
public function find(Closure $callback): ?Entity
{
foreach ($this as $entity) {
if ($callback($entity)) {
return $entity;
}
}
return null;
}
}
+61 -2
View File
@@ -30,7 +30,6 @@
namespace Espo\ORM;
use Espo\ORM\Query\Select as SelectQuery;
use IteratorAggregate;
use Countable;
use Traversable;
@@ -38,6 +37,7 @@ use PDO;
use PDOStatement;
use RuntimeException;
use LogicException;
use Closure;
/**
* Reasonable to use when selecting a large number of records.
@@ -144,7 +144,7 @@ class SthCollection implements Collection, IteratorAggregate, Countable
$list = [];
foreach ($this as $entity) {
$list[] = $entity->getValueMap();;
$list[] = $entity->getValueMap();
}
return $list;
@@ -170,6 +170,7 @@ class SthCollection implements Collection, IteratorAggregate, Countable
* Create from a query.
*
* @return self<Entity>
* @internal
*/
public static function fromQuery(SelectQuery $query, EntityManager $entityManager): self
{
@@ -192,6 +193,7 @@ class SthCollection implements Collection, IteratorAggregate, Countable
* Create from an SQL.
*
* @return self<Entity>
* @internal
*/
public static function fromSql(string $entityType, string $sql, EntityManager $entityManager): self
{
@@ -203,4 +205,61 @@ class SthCollection implements Collection, IteratorAggregate, Countable
return $obj;
}
/**
* Filter.
*
* @param Closure(TEntity): bool $callback A filter callback.
* @return EntityCollection<TEntity> A filtered collection.
* @since 9.1.0
*/
public function filter(Closure $callback): EntityCollection
{
return $this->toEntityCollection()->filter($callback);
}
/**
* Sort.
*
* @param Closure(TEntity, TEntity): int $callback The comparison function.
* @return EntityCollection<TEntity> A sorted collection. A new instance.
* @since 9.1.0
*/
public function sort(Closure $callback): EntityCollection
{
return $this->toEntityCollection()->sort($callback);
}
/**
* Reverse.
*
* @return EntityCollection<TEntity> A reversed collection.
* @since 9.1.0
*/
public function reverse(): EntityCollection
{
return $this->toEntityCollection()->reverse();
}
/**
* Find.
*
* @param Closure(TEntity): bool $callback A filter callback.
* @return ?TEntity
* @since 9.1.0
* @noinspection PhpDocSignatureInspection
*/
public function find(Closure $callback): ?Entity
{
return $this->toEntityCollection()->find($callback);
}
/**
* @return EntityCollection<TEntity>
*/
private function toEntityCollection(): EntityCollection
{
/** @var EntityCollection<TEntity> */
return (new EntityCollection([...$this], $this->entityType));
}
}
@@ -29,6 +29,7 @@
namespace tests\integration\Espo\ORM;
use Espo\Modules\Crm\Entities\Account;
use Espo\ORM\EntityManager;
use Espo\ORM\SthCollection;
use tests\integration\Core\BaseTestCase;
@@ -234,4 +235,25 @@ class SthCollectionTest extends BaseTestCase
$this->assertEquals(2, $count);
}
public function testMethods(): void
{
$e1 = $this->getEntityManager()->createEntity(Account::ENTITY_TYPE, ['name' => '1']);
$e2 = $this->getEntityManager()->createEntity(Account::ENTITY_TYPE, ['name' => '2']);
$e3 = $this->getEntityManager()->createEntity(Account::ENTITY_TYPE, ['name' => '3']);
$e4 = $this->getEntityManager()->createEntity(Account::ENTITY_TYPE, ['name' => '4']);
$collection = $this->getEntityManager()
->getRDBRepositoryByClass(Account::class)
->sth()
->order('name')
->find();
$filtered = $collection->filter(function ($e) use ($e2, $e3) {
return $e->getId() !== $e2->getId() && $e->getId() !== $e3->getId();
});
$this->assertEquals([$e1->getId(), $e4->getId()], array_map(fn ($it) => $it->getId(), [...$filtered]));
$this->assertEquals($collection->getEntityType(), $filtered->getEntityType());
}
}
+79
View File
@@ -32,12 +32,17 @@ namespace tests\unit\Espo\ORM;
use Espo\ORM\BaseEntity;
use Espo\ORM\Defs;
use Espo\ORM\Defs\DefsData;
use Espo\ORM\Entity;
use Espo\ORM\EntityCollection;
use Espo\ORM\Executor\QueryExecutor;
use Espo\ORM\Metadata;
use Espo\ORM\MetadataDataProvider;
use Espo\Core\ORM\EntityManager;
use Espo\ORM\SthCollection;
use PDOStatement;
use PHPUnit\Framework\TestCase;
use SplObjectStorage;
require_once 'tests/unit/testData/DB/Entities.php';
@@ -117,4 +122,78 @@ class CollectionTest extends TestCase
$this->assertEquals(1, $collection->count());
}
public function testFilter(): void
{
$e1 = $this->createMock(Entity::class);
$e2 = $this->createMock(Entity::class);
$e3 = $this->createMock(Entity::class);
$e4 = $this->createMock(Entity::class);
$collection = new EntityCollection([$e1, $e2, $e3, $e4], 'Account');
$filtered = $collection->filter(function ($e) use ($e2, $e3) {
return $e !== $e2 && $e !== $e3;
});
$this->assertEquals([$e1, $e2], [...$filtered]);
$this->assertEquals($collection->getEntityType(), $filtered->getEntityType());
}
public function testSort(): void
{
$e1 = $this->createMock(Entity::class);
$e2 = $this->createMock(Entity::class);
$e3 = $this->createMock(Entity::class);
$map = new SplObjectStorage();
$map[$e1] = 3;
$map[$e2] = 2;
$map[$e3] = 1;
$collection = new EntityCollection([$e1, $e2, $e3], 'Account');
$sorted = $collection->sort(function ($e1, $e2) use ($map) {
return $map[$e1] - $map[$e2];
});
$this->assertEquals([$e3, $e2, $e1], [...$sorted]);
$this->assertEquals($collection->getEntityType(), $sorted->getEntityType());
}
public function testFind(): void
{
$e1 = $this->createMock(Entity::class);
$e2 = $this->createMock(Entity::class);
$e3 = $this->createMock(Entity::class);
$collection = new EntityCollection([$e1, $e2, $e3]);
$e = $collection->find(function ($e) use ($e2) {
return $e === $e2;
});
$this->assertSame($e2, $e);
$e = $collection->find(function ($e) use ($e2) {
return $e === 0;
});
$this->assertNull($e);
}
public function testReverse(): void
{
$e1 = $this->createMock(Entity::class);
$e2 = $this->createMock(Entity::class);
$e3 = $this->createMock(Entity::class);
$collection = new EntityCollection([$e1, $e2, $e3], 'Account');
$reversed = $collection->reverse();
$this->assertEquals([$e3, $e2, $e1], [...$reversed]);
$this->assertEquals($collection->getEntityType(), $reversed->getEntityType());
}
}