Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Add EntityRepository->countBy() method to complement findBy() #560

Closed
wants to merge 1 commit into from

5 participants

Alex Andrienko doctrinebot Benjamin Eberlei Marco Pivetta Christophe Coevoet
Alex Andrienko

It would be reasonable to add the method to ObjectRepository interface, but I might not be aware of all its implementations that will become broken

Alex Andrienko alex-signature-it Add EntityRepository->countBy() method to complement findBy()
It would be reasonable to add the method to ObjectRepository interface, but I might not be aware of all its implementations that will become broken
717fd3d
doctrinebot
Collaborator

Hello,

thank you for positing this Pull Request. I have automatically opened an issue on our Jira Bug Tracker for you with the details of this Pull-Request. See the Link:

http://doctrine-project.org/jira/browse/DDC-2263

Christophe Coevoet stof commented on the diff
...e/Tests/ORM/Functional/SingleTableInheritanceTest.php
@@ -5,6 +5,8 @@
5 5 use Doctrine\ORM\Mapping\ClassMetadata;
6 6 use Doctrine\Common\Collections\Criteria;
7 7
  8 +require_once __DIR__ . '/../../TestInit.php';
2
Christophe Coevoet
stof added a note

this must be removed. This file is already the phpunit bootstrap file so it does not need to be included again (except if you copied an outdated phpunit.xml)

I totally agree, that it's better to include it from bootstrap only, but that's just not my call to make such a change.
I must point out, that I made my phpunit.xml file from dbproperties.xml.dev according to the instructions provided in its comment, and it doesn't mention any bootstrap. Without this line I wasn't able to run tests for this particular class only, and if you'll look at almost any other functional test class, you'll notice that most of them explicitly require_once this file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Christophe Coevoet stof commented on the diff
tests/Doctrine/Tests/ORM/Functional/TypeTest.php
@@ -127,7 +127,9 @@ public function testDateTime()
127 127 $this->assertEquals('2009-10-02 20:10:52', $dateTimeDb->datetime->format('Y-m-d H:i:s'));
128 128
129 129 $articles = $this->_em->getRepository( 'Doctrine\Tests\Models\Generic\DateTimeModel' )->findBy( array( 'datetime' => new \DateTime( "now" ) ) );
130   - $this->assertEquals( 0, count( $articles ) );
  130 + $this->assertEquals(0, count($articles));
1
Christophe Coevoet
stof added a note

you should use assertCount

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Christophe Coevoet stof commented on the diff
lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
@@ -1127,6 +1141,40 @@ protected function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit
1127 1141 }
1128 1142
1129 1143 /**
  1144 + * Gets the SELECT SQL to count entities by a set of field criteria.
  1145 + *
  1146 + * @param array|\Doctrine\Common\Collections\Criteria $criteria
  1147 + * @return string
  1148 + */
  1149 + protected function _getCountSQL($criteria)
  1150 + {
  1151 + $conditionSql = ($criteria instanceof Criteria)
1
Christophe Coevoet
stof added a note

you are allowing a Criteria object bere but not in countAll

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Christophe Coevoet stof commented on the diff
lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
((17 lines not shown))
  1157 + $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform);
  1158 +
  1159 + if ('' !== $filterSql) {
  1160 + $conditionSql = $conditionSql
  1161 + ? $conditionSql . ' AND ' . $filterSql
  1162 + : $filterSql;
  1163 + }
  1164 +
  1165 + $select = 'SELECT COUNT(1)';
  1166 + $from = ' FROM ' . $tableName . ' ' . $tableAlias;
  1167 + $join = $this->selectJoinSql;
  1168 + $where = ($conditionSql ? ' WHERE ' . $conditionSql : '');
  1169 + $query = $select
  1170 + . $from
  1171 + . $join
  1172 + . $where;
1
Christophe Coevoet
stof added a note

you should keep this concatenation on 1 line

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Christophe Coevoet stof commented on the diff
lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
((13 lines not shown))
  1153 + : $this->getSelectConditionSQL($criteria);
  1154 +
  1155 + $tableAlias = $this->getSQLTableAlias($this->class->name);
  1156 + $filterSql = $this->generateFilterConditionSQL($this->class, $tableAlias);
  1157 + $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform);
  1158 +
  1159 + if ('' !== $filterSql) {
  1160 + $conditionSql = $conditionSql
  1161 + ? $conditionSql . ' AND ' . $filterSql
  1162 + : $filterSql;
  1163 + }
  1164 +
  1165 + $select = 'SELECT COUNT(1)';
  1166 + $from = ' FROM ' . $tableName . ' ' . $tableAlias;
  1167 + $join = $this->selectJoinSql;
  1168 + $where = ($conditionSql ? ' WHERE ' . $conditionSql : '');
1
Christophe Coevoet
stof added a note

the braces are useless

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Benjamin Eberlei
Owner

Thank you for this PR @alex-signature-it, however this is what DQL is for, we cannot add everything to the repository just to avoid having to write DQL. This would add a duplicate feature and i am not sure it provides additional value.

Furthermore, this essentially expands the ObjectManager interface and increases incompatibility accross implementations even more.

Alex Andrienko

First of all, I'd like to thank @stof for the feedback. Although most of the comments are related to the code and interfaces that were there before I made any changes, all of them are reasonable and I'll attend them.

Alex Andrienko

Now I'd like to appeal to @beberlei to reconsider decision to reject this pull request by the following reasoning:
countBy() method naturally complements findBy(), if you allow one of them, chances are high that you'll need the other as well.

Consider the following use case: you need to provide some kind of a view for a list of entities with option to filter it by entity attributes. It's quite natural to use findBy() method to load relevant data. Now consider that you also would like to provide any kind of incremental loading feature for this list (paging, AJAX incremental loading etc.). While being organic extension of the previous feature, you'll have to drastically change the way you work with ORM.

Marco Pivetta
Collaborator

@alex-signature-it we have a paginator written especially for this. What can eventually be done in your case is extending the criteria API so that it allows counting items in a lazy collection in a manner that doesn't load all the items in the collection itself.

And that's an implementation detail of an already existing API :)

$criteria = new Criteria();
// configure criteria here
$collection = $repo->matching($criteria);
var_dump($collection->count());
Christophe Coevoet

@Ocramius I opened DDC-2217 1 month ago for this.

Marco Pivetta
Collaborator

@stof that's awesome =)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Jan 27, 2013
Alex Andrienko alex-signature-it Add EntityRepository->countBy() method to complement findBy()
It would be reasonable to add the method to ObjectRepository interface, but I might not be aware of all its implementations that will become broken
717fd3d
This page is out of date. Refresh to see the latest.
18 lib/Doctrine/ORM/EntityRepository.php
@@ -182,6 +182,24 @@ public function findBy(array $criteria, array $orderBy = null, $limit = null, $o
182 182 }
183 183
184 184 /**
  185 + * Counts objects that satisfy a set of criteria.
  186 + *
  187 + * An implementation may throw an UnexpectedValueException if certain values
  188 + * of the limiting details are not supported.
  189 + *
  190 + * @throws \UnexpectedValueException
  191 + * @param array $criteria
  192 + * @return int
  193 + */
  194 + public function countBy(array $criteria)
  195 + {
  196 + $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
  197 +
  198 + return $persister->countAll($criteria);
  199 + }
  200 +
  201 +
  202 + /**
185 203 * Finds a single entity by a set of criteria.
186 204 *
187 205 * @param array $criteria
48 lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
@@ -912,6 +912,20 @@ public function loadAll(array $criteria = array(), array $orderBy = null, $limit
912 912 }
913 913
914 914 /**
  915 + * Counts entities by a list of field criteria.
  916 + *
  917 + * @param array $criteria
  918 + * @return int
  919 + */
  920 + public function countAll(array $criteria = array())
  921 + {
  922 + $sql = $this->_getCountSQL($criteria);
  923 + list($params, $types) = $this->expandParameters($criteria);
  924 + $stmt = $this->conn->executeQuery($sql, $params, $types);
  925 + return intval($stmt->fetchColumn());
  926 + }
  927 +
  928 + /**
915 929 * Gets (sliced or full) elements of the given collection.
916 930 *
917 931 * @param array $assoc
@@ -1127,6 +1141,40 @@ protected function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit
1127 1141 }
1128 1142
1129 1143 /**
  1144 + * Gets the SELECT SQL to count entities by a set of field criteria.
  1145 + *
  1146 + * @param array|\Doctrine\Common\Collections\Criteria $criteria
  1147 + * @return string
  1148 + */
  1149 + protected function _getCountSQL($criteria)
  1150 + {
  1151 + $conditionSql = ($criteria instanceof Criteria)
  1152 + ? $this->getSelectConditionCriteriaSQL($criteria)
  1153 + : $this->getSelectConditionSQL($criteria);
  1154 +
  1155 + $tableAlias = $this->getSQLTableAlias($this->class->name);
  1156 + $filterSql = $this->generateFilterConditionSQL($this->class, $tableAlias);
  1157 + $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform);
  1158 +
  1159 + if ('' !== $filterSql) {
  1160 + $conditionSql = $conditionSql
  1161 + ? $conditionSql . ' AND ' . $filterSql
  1162 + : $filterSql;
  1163 + }
  1164 +
  1165 + $select = 'SELECT COUNT(1)';
  1166 + $from = ' FROM ' . $tableName . ' ' . $tableAlias;
  1167 + $join = $this->selectJoinSql;
  1168 + $where = ($conditionSql ? ' WHERE ' . $conditionSql : '');
  1169 + $query = $select
  1170 + . $from
  1171 + . $join
  1172 + . $where;
  1173 +
  1174 + return $query;
  1175 + }
  1176 +
  1177 + /**
1130 1178 * Gets the ORDER BY SQL snippet for ordered collections.
1131 1179 *
1132 1180 * @param array $orderBy
83 tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php
@@ -185,6 +185,15 @@ public function testFindByField()
185 185 $this->assertEquals('dev', $users[0]->status);
186 186 }
187 187
  188 + public function testCountByField()
  189 + {
  190 + $user1Id = $this->loadFixture();
  191 + $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
  192 +
  193 + $userCount = $repos->countBy(array('status' => 'dev'));
  194 + $this->assertEquals(2, $userCount);
  195 + }
  196 +
188 197 public function testFindByAssociationWithIntegerAsParameter()
189 198 {
190 199 $address1 = $this->buildAddress('Germany', 'Berlim', 'Foo st.', '123456');
@@ -209,6 +218,29 @@ public function testFindByAssociationWithIntegerAsParameter()
209 218 $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress',$addresses[0]);
210 219 }
211 220
  221 + public function testCountByAssociationWithIntegerAsParameter()
  222 + {
  223 + $address1 = $this->buildAddress('Germany', 'Berlim', 'Foo st.', '123456');
  224 + $user1 = $this->buildUser('Benjamin', 'beberlei', 'dev', $address1);
  225 +
  226 + $address2 = $this->buildAddress('Brazil', 'São Paulo', 'Bar st.', '654321');
  227 + $user2 = $this->buildUser('Guilherme', 'guilhermeblanco', 'freak', $address2);
  228 +
  229 + $address3 = $this->buildAddress('USA', 'Nashville', 'Woo st.', '321654');
  230 + $user3 = $this->buildUser('Jonathan', 'jwage', 'dev', $address3);
  231 +
  232 + unset($address1);
  233 + unset($address2);
  234 + unset($address3);
  235 +
  236 + $this->_em->clear();
  237 +
  238 + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress');
  239 + $addressCount = $repository->countBy(array('user' => array($user1->getId(), $user2->getId())));
  240 +
  241 + $this->assertEquals(2, $addressCount);
  242 + }
  243 +
212 244 public function testFindByAssociationWithObjectAsParameter()
213 245 {
214 246 $address1 = $this->buildAddress('Germany', 'Berlim', 'Foo st.', '123456');
@@ -233,6 +265,29 @@ public function testFindByAssociationWithObjectAsParameter()
233 265 $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress',$addresses[0]);
234 266 }
235 267
  268 + public function testCountByAssociationWithObjectAsParameter()
  269 + {
  270 + $address1 = $this->buildAddress('Germany', 'Berlim', 'Foo st.', '123456');
  271 + $user1 = $this->buildUser('Benjamin', 'beberlei', 'dev', $address1);
  272 +
  273 + $address2 = $this->buildAddress('Brazil', 'São Paulo', 'Bar st.', '654321');
  274 + $user2 = $this->buildUser('Guilherme', 'guilhermeblanco', 'freak', $address2);
  275 +
  276 + $address3 = $this->buildAddress('USA', 'Nashville', 'Woo st.', '321654');
  277 + $user3 = $this->buildUser('Jonathan', 'jwage', 'dev', $address3);
  278 +
  279 + unset($address1);
  280 + unset($address2);
  281 + unset($address3);
  282 +
  283 + $this->_em->clear();
  284 +
  285 + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress');
  286 + $addressCount = $repository->countBy(array('user' => array($user1, $user2)));
  287 +
  288 + $this->assertEquals(2, $addressCount);
  289 + }
  290 +
236 291 public function testFindFieldByMagicCall()
237 292 {
238 293 $user1Id = $this->loadFixture();
@@ -376,6 +431,15 @@ public function testFindByAssociationKey_ExceptionOnInverseSide()
376 431 $user = $repos->findBy(array('address' => $addressId));
377 432 }
378 433
  434 + public function testCountByAssociationKey_ExceptionOnInverseSide()
  435 + {
  436 + list($userId, $addressId) = $this->loadAssociatedFixture();
  437 + $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
  438 +
  439 + $this->setExpectedException('Doctrine\ORM\ORMException', "You cannot search for the association field 'Doctrine\Tests\Models\CMS\CmsUser#address', because it is the inverse side of an association. Find methods only work on owning side associations.");
  440 + $userCount = $repos->countBy(array('address' => $addressId));
  441 + }
  442 +
379 443 /**
380 444 * @group DDC-817
381 445 */
@@ -417,6 +481,15 @@ public function testFindByAssociationKey()
417 481 $this->assertEquals($addressId, $addresses[0]->id);
418 482 }
419 483
  484 + public function testCountByAssociationKey()
  485 + {
  486 + list($userId, $addressId) = $this->loadAssociatedFixture();
  487 + $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress');
  488 + $addressCount = $repos->countBy(array('user' => $userId));
  489 +
  490 + $this->assertEquals(1, $addressCount);
  491 + }
  492 +
420 493 /**
421 494 * @group DDC-817
422 495 */
@@ -486,6 +559,16 @@ public function testIsNullCriteria()
486 559 $this->assertEquals(1, count($users));
487 560 }
488 561
  562 + public function testCountByNullCriteria()
  563 + {
  564 + $this->loadFixture();
  565 +
  566 + $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
  567 +
  568 + $userCount = $repos->countBy(array('status' => null));
  569 + $this->assertEquals(1, $userCount);
  570 + }
  571 +
489 572 /**
490 573 * @group DDC-1094
491 574 */
12 tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php
@@ -402,6 +402,18 @@ public function testRepositoryFindBy()
402 402 $this->assertCount(0, $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsGroup')->findBy(array('id' => $this->groupId2)));
403 403 }
404 404
  405 + public function testRepositoryCountBy()
  406 + {
  407 + $this->loadFixtureData();
  408 +
  409 + $this->assertSame(1, $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsGroup')->countBy(array('id' => $this->groupId2)));
  410 +
  411 + $this->useCMSGroupPrefixFilter();
  412 + $this->_em->clear();
  413 +
  414 + $this->assertSame(0, $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsGroup')->countBy(array('id' => $this->groupId2)));
  415 + }
  416 +
405 417 public function testRepositoryFindByX()
406 418 {
407 419 $this->loadFixtureData();
23 tests/Doctrine/Tests/ORM/Functional/SingleTableInheritanceTest.php
@@ -5,6 +5,8 @@
5 5 use Doctrine\ORM\Mapping\ClassMetadata;
6 6 use Doctrine\Common\Collections\Criteria;
7 7
  8 +require_once __DIR__ . '/../../TestInit.php';
  9 +
8 10 class SingleTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase
9 11 {
10 12 private $salesPerson;
@@ -334,6 +336,27 @@ public function testFindByAssociation()
334 336 $this->assertEquals(1, count($contracts), "There should be 1 entities related to " . $this->salesPerson->getId() . " for 'Doctrine\Tests\Models\Company\CompanyFlexUltraContract'");
335 337 }
336 338
  339 + public function testCountByAssociation()
  340 + {
  341 + $this->loadFullFixture();
  342 +
  343 + $repos = $this->_em->getRepository("Doctrine\Tests\Models\Company\CompanyContract");
  344 + $contractCount = $repos->countBy(array('salesPerson' => $this->salesPerson->getId()));
  345 + $this->assertEquals(3, $contractCount, "There should be 3 entities related to " . $this->salesPerson->getId() . " for 'Doctrine\Tests\Models\Company\CompanyContract'");
  346 +
  347 + $repos = $this->_em->getRepository("Doctrine\Tests\Models\Company\CompanyFixContract");
  348 + $contractCount = $repos->countBy(array('salesPerson' => $this->salesPerson->getId()));
  349 + $this->assertEquals(1, $contractCount, "There should be 1 entities related to " . $this->salesPerson->getId() . " for 'Doctrine\Tests\Models\Company\CompanyFixContract'");
  350 +
  351 + $repos = $this->_em->getRepository("Doctrine\Tests\Models\Company\CompanyFlexContract");
  352 + $contractCount = $repos->countBy(array('salesPerson' => $this->salesPerson->getId()));
  353 + $this->assertEquals(2, $contractCount, "There should be 2 entities related to " . $this->salesPerson->getId() . " for 'Doctrine\Tests\Models\Company\CompanyFlexContract'");
  354 +
  355 + $repos = $this->_em->getRepository("Doctrine\Tests\Models\Company\CompanyFlexUltraContract");
  356 + $contractCount = $repos->countBy(array('salesPerson' => $this->salesPerson->getId()));
  357 + $this->assertEquals(1, $contractCount, "There should be 1 entities related to " . $this->salesPerson->getId() . " for 'Doctrine\Tests\Models\Company\CompanyFlexUltraContract'");
  358 + }
  359 +
337 360 /**
338 361 * @group DDC-1637
339 362 */
4 tests/Doctrine/Tests/ORM/Functional/TypeTest.php
@@ -127,7 +127,9 @@ public function testDateTime()
127 127 $this->assertEquals('2009-10-02 20:10:52', $dateTimeDb->datetime->format('Y-m-d H:i:s'));
128 128
129 129 $articles = $this->_em->getRepository( 'Doctrine\Tests\Models\Generic\DateTimeModel' )->findBy( array( 'datetime' => new \DateTime( "now" ) ) );
130   - $this->assertEquals( 0, count( $articles ) );
  130 + $this->assertEquals(0, count($articles));
  131 + $articleCount = $this->_em->getRepository('Doctrine\Tests\Models\Generic\DateTimeModel')->countBy(array('datetime' => new \DateTime("now")));
  132 + $this->assertEquals(0, $articleCount);
131 133 }
132 134
133 135 public function testDqlQueryBindDateTimeInstance()

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.