1
0
mirror of synced 2025-02-13 10:49:25 +03:00

Expose EntityPersister::count() through EntityRepository::count()

This commit is contained in:
Javier Spagnoletti 2016-09-02 15:43:28 -03:00 committed by Marco Pivetta
parent 35341769ea
commit a90035e81a
5 changed files with 121 additions and 56 deletions

View File

@ -41,7 +41,7 @@ headline "Hello World" with the ID 1234:
<?php <?php
$article = $entityManager->find('CMS\Article', 1234); $article = $entityManager->find('CMS\Article', 1234);
$article->setHeadline('Hello World dude!'); $article->setHeadline('Hello World dude!');
$article2 = $entityManager->find('CMS\Article', 1234); $article2 = $entityManager->find('CMS\Article', 1234);
echo $article2->getHeadline(); echo $article2->getHeadline();
@ -93,25 +93,25 @@ from newly opened EntityManager.
{ {
/** @Id @Column(type="integer") @GeneratedValue */ /** @Id @Column(type="integer") @GeneratedValue */
private $id; private $id;
/** @Column(type="string") */ /** @Column(type="string") */
private $headline; private $headline;
/** @ManyToOne(targetEntity="User") */ /** @ManyToOne(targetEntity="User") */
private $author; private $author;
/** @OneToMany(targetEntity="Comment", mappedBy="article") */ /** @OneToMany(targetEntity="Comment", mappedBy="article") */
private $comments; private $comments;
public function __construct() public function __construct()
{ {
$this->comments = new ArrayCollection(); $this->comments = new ArrayCollection();
} }
public function getAuthor() { return $this->author; } public function getAuthor() { return $this->author; }
public function getComments() { return $this->comments; } public function getComments() { return $this->comments; }
} }
$article = $em->find('Article', 1); $article = $em->find('Article', 1);
This code only retrieves the ``Article`` instance with id 1 executing This code only retrieves the ``Article`` instance with id 1 executing
@ -132,22 +132,22 @@ your code. See the following code:
<?php <?php
$article = $em->find('Article', 1); $article = $em->find('Article', 1);
// accessing a method of the user instance triggers the lazy-load // accessing a method of the user instance triggers the lazy-load
echo "Author: " . $article->getAuthor()->getName() . "\n"; echo "Author: " . $article->getAuthor()->getName() . "\n";
// Lazy Loading Proxies pass instanceof tests: // Lazy Loading Proxies pass instanceof tests:
if ($article->getAuthor() instanceof User) { if ($article->getAuthor() instanceof User) {
// a User Proxy is a generated "UserProxy" class // a User Proxy is a generated "UserProxy" class
} }
// accessing the comments as an iterator triggers the lazy-load // accessing the comments as an iterator triggers the lazy-load
// retrieving ALL the comments of this article from the database // retrieving ALL the comments of this article from the database
// using a single SELECT statement // using a single SELECT statement
foreach ($article->getComments() as $comment) { foreach ($article->getComments() as $comment) {
echo $comment->getText() . "\n\n"; echo $comment->getText() . "\n\n";
} }
// Article::$comments passes instanceof tests for the Collection interface // Article::$comments passes instanceof tests for the Collection interface
// But it will NOT pass for the ArrayCollection interface // But it will NOT pass for the ArrayCollection interface
if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) { if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) {
@ -167,7 +167,7 @@ methods along the lines of the ``getName()`` method shown below:
{ {
// lazy loading code // lazy loading code
} }
public function getName() public function getName()
{ {
$this->_load(); $this->_load();
@ -262,7 +262,7 @@ which means that its persistent state will be deleted once
for and appear in query and collection results. See for and appear in query and collection results. See
the section on :ref:`Database and UnitOfWork Out-Of-Sync <workingobjects_database_uow_outofsync>` the section on :ref:`Database and UnitOfWork Out-Of-Sync <workingobjects_database_uow_outofsync>`
for more information. for more information.
Example: Example:
@ -681,13 +681,13 @@ methods on a repository as follows:
<?php <?php
// $em instanceof EntityManager // $em instanceof EntityManager
// All users that are 20 years old // All users that are 20 years old
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20)); $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20));
// All users that are 20 years old and have a surname of 'Miller' // All users that are 20 years old and have a surname of 'Miller'
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller')); $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller'));
// A single user by its nickname // A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
@ -723,10 +723,18 @@ examples are equivalent:
<?php <?php
// A single user by its nickname // A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
// A single user by its nickname (__call magic) // A single user by its nickname (__call magic)
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb'); $user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
Additionally, you can just count the result of the provided conditions when you don't really need the data:
.. code-block:: php
<?php
// Check there is no user with nickname
$availableNickname = 0 === $em->getRepository('MyProject\Domain\User')->count(array('nickname' => 'nonexistent'));
By Criteria By Criteria
~~~~~~~~~~~ ~~~~~~~~~~~
@ -774,7 +782,7 @@ A DQL query is represented by an instance of the
<?php <?php
// $em instanceof EntityManager // $em instanceof EntityManager
// All users with an age between 20 and 30 (inclusive). // All users with an age between 20 and 30 (inclusive).
$q = $em->createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30"); $q = $em->createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30");
$users = $q->getResult(); $users = $q->getResult();
@ -817,18 +825,18 @@ in a central location.
<?php <?php
namespace MyDomain\Model; namespace MyDomain\Model;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
/** /**
* @ORM\Entity(repositoryClass="MyDomain\Model\UserRepository") * @ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
*/ */
class User class User
{ {
} }
class UserRepository extends EntityRepository class UserRepository extends EntityRepository
{ {
public function getAllAdminUsers() public function getAllAdminUsers()
@ -844,7 +852,7 @@ You can access your repository now by calling:
<?php <?php
// $em instanceof EntityManager // $em instanceof EntityManager
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers(); $admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();

View File

@ -103,7 +103,7 @@ Install Doctrine using the Composer Dependency Management tool, by calling:
$ composer install $ composer install
This will install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM, This will install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM,
Symfony YAML and Symfony Console into the `vendor` directory. The Symfony Symfony YAML and Symfony Console into the `vendor` directory. The Symfony
dependencies are not required by Doctrine but will be used in this tutorial. dependencies are not required by Doctrine but will be used in this tutorial.
Add the following directories: Add the following directories:
@ -131,22 +131,22 @@ step:
// bootstrap.php // bootstrap.php
use Doctrine\ORM\Tools\Setup; use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
require_once "vendor/autoload.php"; require_once "vendor/autoload.php";
// Create a simple "default" Doctrine ORM configuration for Annotations // Create a simple "default" Doctrine ORM configuration for Annotations
$isDevMode = true; $isDevMode = true;
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode); $config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode);
// or if you prefer yaml or XML // or if you prefer yaml or XML
//$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode); //$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode); //$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
// database configuration parameters // database configuration parameters
$conn = array( $conn = array(
'driver' => 'pdo_sqlite', 'driver' => 'pdo_sqlite',
'path' => __DIR__ . '/db.sqlite', 'path' => __DIR__ . '/db.sqlite',
); );
// obtaining the entity manager // obtaining the entity manager
$entityManager = EntityManager::create($conn, $config); $entityManager = EntityManager::create($conn, $config);
@ -185,7 +185,7 @@ doctrine command. Its a fairly simple file:
<?php <?php
// cli-config.php // cli-config.php
require_once "bootstrap.php"; require_once "bootstrap.php";
return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager); return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);
You can then change into your project directory and call the You can then change into your project directory and call the
@ -196,11 +196,11 @@ Doctrine command-line tool:
$ cd project/ $ cd project/
$ vendor/bin/doctrine orm:schema-tool:create $ vendor/bin/doctrine orm:schema-tool:create
At this point no entity metadata exists in `src` so you will see a message like At this point no entity metadata exists in `src` so you will see a message like
"No Metadata Classes to process." Don't worry, we'll create a Product entity and "No Metadata Classes to process." Don't worry, we'll create a Product entity and
corresponding metadata in the next section. corresponding metadata in the next section.
You should be aware that during the development process you'll periodically need You should be aware that during the development process you'll periodically need
to update your database schema to be in sync with your Entities metadata. to update your database schema to be in sync with your Entities metadata.
You can easily recreate the database: You can easily recreate the database:
@ -257,15 +257,15 @@ entity definition:
} }
} }
Note that all fields are set to protected (not public) with a Note that all fields are set to protected (not public) with a
mutator (getter and setter) defined for every field except $id. mutator (getter and setter) defined for every field except $id.
The use of mutators allows Doctrine to hook into calls which The use of mutators allows Doctrine to hook into calls which
manipulate the entities in ways that it could not if you just manipulate the entities in ways that it could not if you just
directly set the values with ``entity#field = foo;`` directly set the values with ``entity#field = foo;``
The id field has no setter since, generally speaking, your code The id field has no setter since, generally speaking, your code
should not set this value since it represents a database id value. should not set this value since it represents a database id value.
(Note that Doctrine itself can still set the value using the (Note that Doctrine itself can still set the value using the
Reflection API instead of a defined setter function) Reflection API instead of a defined setter function)
The next step for persistence with Doctrine is to describe the The next step for persistence with Doctrine is to describe the
@ -567,13 +567,13 @@ change dates. Next we will model the dynamic relationships between the entities
by defining the references between entities. by defining the references between entities.
References between objects are foreign keys in the database. You never have to References between objects are foreign keys in the database. You never have to
(and never should) work with the foreign keys directly, only with the objects (and never should) work with the foreign keys directly, only with the objects
that represent the foreign key through their own identity. that represent the foreign key through their own identity.
For every foreign key you either have a Doctrine ManyToOne or OneToOne For every foreign key you either have a Doctrine ManyToOne or OneToOne
association. On the inverse sides of these foreign keys you can have association. On the inverse sides of these foreign keys you can have
OneToMany associations. Obviously you can have ManyToMany associations OneToMany associations. Obviously you can have ManyToMany associations
that connect two tables with each other through a join table with that connect two tables with each other through a join table with
two foreign keys. two foreign keys.
Now that you know the basics about references in Doctrine, we can extend the Now that you know the basics about references in Doctrine, we can extend the
@ -783,7 +783,7 @@ the database that points from Bugs to Products.
} }
We are now finished with the domain model given the requirements. We are now finished with the domain model given the requirements.
Lets add metadata mappings for the ``User`` and ``Bug`` as we did for Lets add metadata mappings for the ``User`` and ``Bug`` as we did for
the ``Product`` before: the ``Product`` before:
.. configuration-block:: .. configuration-block::
@ -886,8 +886,8 @@ the ``Product`` before:
Here we have the entity, id and primitive type definitions. Here we have the entity, id and primitive type definitions.
For the "created" field we have used the ``datetime`` type, For the "created" field we have used the ``datetime`` type,
which translates the YYYY-mm-dd HH:mm:ss database format which translates the YYYY-mm-dd HH:mm:ss database format
into a PHP DateTime instance and back. into a PHP DateTime instance and back.
After the field definitions the two qualified references to the After the field definitions the two qualified references to the
@ -1164,10 +1164,10 @@ The console output of this script is then:
As a last resort you can still use Native SQL and a description of the As a last resort you can still use Native SQL and a description of the
result set to retrieve entities from the database. DQL boils down to a result set to retrieve entities from the database. DQL boils down to a
Native SQL statement and a ``ResultSetMapping`` instance itself. Using Native SQL statement and a ``ResultSetMapping`` instance itself. Using
Native SQL you could even use stored procedures for data retrieval, or Native SQL you could even use stored procedures for data retrieval, or
make use of advanced non-portable database queries like PostgreSql's make use of advanced non-portable database queries like PostgreSql's
recursive queries. recursive queries.
@ -1180,7 +1180,7 @@ objects only from Doctrine however. For a simple list view like the
previous one we only need read access to our entities and can previous one we only need read access to our entities and can
switch the hydration from objects to simple PHP arrays instead. switch the hydration from objects to simple PHP arrays instead.
Hydration can be an expensive process so only retrieving what you need can Hydration can be an expensive process so only retrieving what you need can
yield considerable performance benefits for read-only requests. yield considerable performance benefits for read-only requests.
Implementing the same list view as before using array hydration we Implementing the same list view as before using array hydration we
@ -1535,6 +1535,16 @@ As an example here is the code of the first use case "List of Bugs":
Using EntityRepositories you can avoid coupling your model with specific query logic. Using EntityRepositories you can avoid coupling your model with specific query logic.
You can also re-use query logic easily throughout your application. You can also re-use query logic easily throughout your application.
The method ``count()`` takes an array of fields or association keys and the values to match against.
This provides you with a convenient and lightweight way to count a resultset when you don't need to
deal with it:
.. code-block:: php
<?php
$productCount = $entityManager->getRepository('Product')
->count(array('name' => $productName));
Conclusion Conclusion
---------- ----------

View File

@ -196,16 +196,32 @@ class EntityRepository implements ObjectRepository, Selectable
} }
/** /**
* Adds support for magic finders. * Counts entities by a set of criteria.
*
* @todo Add this method to `ObjectRepository` interface in the next major release
*
* @param array $criteria
*
* @return int The quantity of objects that matches the criteria.
*/
public function count(array $criteria)
{
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
return $persister->count($criteria);
}
/**
* Adds support for magic finders/counters.
* *
* @param string $method * @param string $method
* @param array $arguments * @param array $arguments
* *
* @return array|object The found entity/entities. * @return array|object|int The found entity/entities or its resulting count.
* *
* @throws ORMException * @throws ORMException
* @throws \BadMethodCallException If the method called is an invalid find* method * @throws \BadMethodCallException If the method called is an invalid find* or countBy method
* or no find* method at all and therefore an invalid * or no find* or countBy method at all and therefore an invalid
* method call. * method call.
*/ */
public function __call($method, $arguments) public function __call($method, $arguments)
@ -221,6 +237,11 @@ class EntityRepository implements ObjectRepository, Selectable
$method = 'findOneBy'; $method = 'findOneBy';
break; break;
case (0 === strpos($method, 'countBy')):
$by = substr($method, 7);
$method = 'count';
break;
default: default:
throw new \BadMethodCallException( throw new \BadMethodCallException(
"Undefined method '$method'. The method name must start with ". "Undefined method '$method'. The method name must start with ".
@ -228,14 +249,16 @@ class EntityRepository implements ObjectRepository, Selectable
); );
} }
if (empty($arguments)) { $argsCount = count($arguments);
if (0 === $argsCount) {
throw ORMException::findByRequiresParameter($method . $by); throw ORMException::findByRequiresParameter($method . $by);
} }
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by)); $fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) { if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) {
switch (count($arguments)) { switch ($argsCount) {
case 1: case 1:
return $this->$method(array($fieldName => $arguments[0])); return $this->$method(array($fieldName => $arguments[0]));

View File

@ -822,7 +822,7 @@ class BasicEntityPersister implements EntityPersister
? $this->expandCriteriaParameters($criteria) ? $this->expandCriteriaParameters($criteria)
: $this->expandParameters($criteria); : $this->expandParameters($criteria);
return $this->conn->executeQuery($sql, $params, $types)->fetchColumn(); return (int) $this->conn->executeQuery($sql, $params, $types)->fetchColumn();
} }
/** /**

View File

@ -272,6 +272,30 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
$this->assertEquals(4, count($users)); $this->assertEquals(4, count($users));
} }
public function testCount()
{
$this->loadFixture();
$repos = $this->_em->getRepository(CmsUser::class);
$userCount = $repos->count(array());
$this->assertSame(4, $userCount);
$userCount = $repos->count(array('status' => 'dev'));
$this->assertSame(2, $userCount);
$userCount = $repos->count(array('status' => 'nonexistent'));
$this->assertSame(0, $userCount);
}
public function testCountBy()
{
$this->loadFixture();
$repos = $this->_em->getRepository(CmsUser::class);
$userCount = $repos->countByStatus('dev');
$this->assertSame(2, $userCount);
}
/** /**
* @expectedException \Doctrine\ORM\ORMException * @expectedException \Doctrine\ORM\ORMException
*/ */