Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ v2.3.0
* Feat: Add `Bdf\Prime\Mapper\Attribute\DiscriminatorMap` attribute to define the discriminator map on single table inheritance in replacement of overriding properties `discriminatorColumn` and `discriminatorMap` of the mapper
* Feat: Add `Bdf\Prime\Mapper\Mapper::setDiscriminatorMap()`
* Change: Mapper's configurators attributes now handle inheritance (i.e. attributes declared on parent class are inherited by child classes)
* Change: Allow iterable criteria and closure filters on `EntityRepository::count()` and `EntityRelation::count()`

BC Breaks
* Deprecated: Returning `false` on callback of `EntityRepository::transaction()` to rollback the transaction is deprecated. Use exception instead.
Expand Down
2 changes: 1 addition & 1 deletion src/Entity/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
* @psalm-method static EntityQuery filter(\Closure $filter)
*
* @method static int updateBy(array $attributes, array $criteria = [])
* @method static int count(array $criteria = [], $attributes = null)
* @method static int count(iterable|callable $criteria = [], $attributes = null)
* @method static bool exists(self $entity)
* @method static static|null refresh(self $entity)
*/
Expand Down
28 changes: 27 additions & 1 deletion src/Relations/EntityRelation.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Bdf\Prime\Connection\ConnectionInterface;
use Bdf\Prime\Exception\PrimeException;
use Bdf\Prime\Query\Contract\Aggregatable;
use Bdf\Prime\Query\Contract\ReadOperation;
use Bdf\Prime\Query\Contract\WriteOperation;
use Bdf\Prime\Query\Custom\KeyValue\KeyValueQuery;
Expand All @@ -16,6 +17,7 @@
use ReflectionClass;
use ReflectionException;

use function assert;
use function sprintf;

/**
Expand All @@ -39,7 +41,6 @@
* @psalm-method R findByIdOrNew(mixed|array $pk) Get one entity by its primary key or instantiate a new one, using where clause criteria if not found in repository
* @psalm-method R firstOrFail() Get the first result of the query, or throws an exception if no result
* @psalm-method R firstOrNew(bool $useCriteriaAsDefault = true) Get the first result of the query, or create a new instance if no result. If $useCriteriaAsDefault is true, the where criteria will be used as default values for the new instance.
* @psalm-method int count()
*
* @mixin ReadCommandInterface<\Bdf\Prime\Connection\ConnectionInterface, R>
* @psalm-no-seal-methods
Expand Down Expand Up @@ -257,6 +258,31 @@ public function query(?string $queryClass = null): ReadCommandInterface
return $this->relation->link($this->owner, $queryClass);
}

/**
* Count number of related entities matching the criteria
*
* Usage:
* ```php
* $entity->relation('relation')->count(); // Count all entities
* $entity->relation('relation')->count(['status' => 'active']); // Count with criteria
* $entity->relation('relation')->count(fn ($query) => $query->where('status', 'active')->where('age', '>', 18)); // Count with callback
* ```
*
* @param iterable<string,mixed>|callable(QueryInterface):void $criteria The filtering criteria. If not set, will count all entities of the repository
* @param string|array|null $attributes The attribute(s) to count. If null, will count all (COUNT(*))
*
* @return int
* @throws PrimeException
*/
#[ReadOperation]
public function count($criteria = [], $attributes = null): int
{
$query = $this->query()->where($criteria);
assert($query instanceof Aggregatable);

return $query->count($attributes);
}

/**
* Save the relation from an entity
*
Expand Down
23 changes: 17 additions & 6 deletions src/Repository/EntityRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Bdf\Prime\Exception\PrimeException;
use Bdf\Prime\Mapper\Mapper;
use Bdf\Prime\Mapper\Metadata;
use Bdf\Prime\Query\Contract\Aggregatable;
use Bdf\Prime\Query\Contract\ReadOperation;
use Bdf\Prime\Query\Contract\WriteOperation;
use Bdf\Prime\Query\Custom\KeyValue\KeyValueQuery;
Expand Down Expand Up @@ -50,6 +51,7 @@
use Doctrine\DBAL\Connection;
use Exception;

use function assert;
use function method_exists;
use function trigger_error;

Expand Down Expand Up @@ -576,19 +578,28 @@ public function writer(): WriterInterface
}

/**
* Count entity
* Count number of entities matching the criteria
*
* @param array $criteria
* @param string|array|null $attributes
* Usage:
* ```php
* MyEntity::repository()->count(); // Count all entities
* MyEntity::repository()->count(['status' => 'active']); // Count with criteria
* MyEntity::repository()->count(fn ($query) => $query->where('status', 'active')->where('age', '>', 18)); // Count with callback
* ```
*
* @param iterable<string,mixed>|callable(QueryInterface):void $criteria The filtering criteria. If not set, will count all entities of the repository
* @param string|array|null $attributes The attribute(s) to count. If null, will count all (COUNT(*))
*
* @return int
* @throws PrimeException
*/
#[ReadOperation]
public function count(array $criteria = [], $attributes = null): int
public function count($criteria = [], $attributes = null): int
{
/** @psalm-suppress UndefinedInterfaceMethod */
return $this->builder()->where($criteria)->count($attributes);
$query = $this->queries->builder()->where($criteria);
assert($query instanceof Aggregatable);

return $query->count($attributes);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Repository/RepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,12 @@ public function writer(): WriterInterface;
/**
* Count entity
*
* @param array $criteria
* @param array<string, mixed> $criteria
* @param string|array|null $attributes
*
* @return int
* @throws PrimeException
* @todo Update signature in prime 3.0 to match EntityRepository::count()
*/
#[ReadOperation]
public function count(array $criteria = [], $attributes = null): int;
Expand Down
36 changes: 36 additions & 0 deletions tests/Relations/FunctionnalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Bdf\Prime\Pack;
use Bdf\Prime\CustomerPack;
use Bdf\Prime\Project;
use Bdf\Prime\Query\Expression\Attribute;
use Bdf\Prime\Test\TestPack;
use Bdf\Prime\TestFile;
use Bdf\Prime\User;
Expand Down Expand Up @@ -357,6 +358,41 @@ public function test_eager_relation_constraints()
);
}

/**
*
*/
public function test_count_on_entity_relation()
{
TestPack::pack()->nonPersist([
'project' => $project = new Project([
'id' => 1,
'name' => 'Projet 1'
]),
'company' => new Company([
'id' => 1,
'name' => 'Société 1'
])
])
->nonPersist([
'developer' => new Developer([
'id' => 1,
'name' => 'Dév 1',
'project' => TestPack::pack()->get('project'),
'company' => $this->getTestPack()->get('company')
]),
'leadDeveloper' => new Developer([
'id' => 2,
'name' => 'Dév 2',
'lead' => true,
'project' => TestPack::pack()->get('project'),
'company' => $this->getTestPack()->get('company')
]),
]);

$this->assertSame(2, $project->relation('developers')->count());
$this->assertSame(1, $project->relation('developers')->count(['lead' => true]));
}

/**
*
*/
Expand Down
2 changes: 2 additions & 0 deletions tests/Repository/EntityRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Bdf\Prime\Prime;
use Bdf\Prime\PrimeTestCase;
use Bdf\Prime\Query\Custom\KeyValue\KeyValueQuery;
use Bdf\Prime\Query\Expression\Like;
use Bdf\Prime\Query\Query;
use Bdf\Prime\Relations\Exceptions\RelationNotFoundException;
use Bdf\Prime\Repository\Event\AfterLoad;
Expand Down Expand Up @@ -140,6 +141,7 @@ public function test_count()

$this->assertEquals(2, Prime::repository('Bdf\Prime\TestEntity')->count());
$this->assertEquals(1, Prime::repository('Bdf\Prime\TestEntity')->count(['name :like' => '%2']));
$this->assertEquals(1, Prime::repository('Bdf\Prime\TestEntity')->count(fn (Query $query) => $query->where('name', (new Like(2))->endsWith())));
}

/**
Expand Down
4 changes: 4 additions & 0 deletions tests/StaticAnalysis/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ public function utilityMethods(): void
{
$this->checkInt(Person::count());
$this->checkInt(Person::repository()->count());
$this->checkInt(Person::repository()->count(['firstName' => 'John']));
$this->checkInt(Person::repository()->count(fn(QueryInterface $query) => $query->where('firstName', 'John')));
$this->checkInt(Person::updateBy(['firstName' => 'XXX'], ['firstName' => 'John']));
$this->checkInt(Person::repository()->updateBy(['firstName' => 'XXX'], ['firstName' => 'John']));
$this->checkBool(Person::exists(new Person()));
Expand Down Expand Up @@ -167,6 +169,8 @@ public function test_relation(): void
$this->checkAddressCollection($relation->by('zipCode')->all());
$this->checkAddress($relation->create());
$this->checkInt($relation->count());
$this->checkInt($relation->count(['zipCode' => '84660']));
$this->checkInt($relation->count(fn(QueryInterface $query) => $query->where('zipCode', '84660')));
}

public function test_relation_with_class(): void
Expand Down