vendor/doctrine/orm/src/EntityRepository.php line 220

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM;
  4. use BadMethodCallException;
  5. use Doctrine\Common\Collections\AbstractLazyCollection;
  6. use Doctrine\Common\Collections\Criteria;
  7. use Doctrine\Common\Collections\Selectable;
  8. use Doctrine\Common\Persistence\PersistentObject;
  9. use Doctrine\DBAL\LockMode;
  10. use Doctrine\Deprecations\Deprecation;
  11. use Doctrine\Inflector\Inflector;
  12. use Doctrine\Inflector\InflectorFactory;
  13. use Doctrine\ORM\Exception\NotSupported;
  14. use Doctrine\ORM\Mapping\ClassMetadata;
  15. use Doctrine\ORM\Query\ResultSetMappingBuilder;
  16. use Doctrine\ORM\Repository\Exception\InvalidMagicMethodCall;
  17. use Doctrine\Persistence\ObjectRepository;
  18. use function array_slice;
  19. use function class_exists;
  20. use function lcfirst;
  21. use function sprintf;
  22. use function str_starts_with;
  23. use function substr;
  24. /**
  25.  * An EntityRepository serves as a repository for entities with generic as well as
  26.  * business specific methods for retrieving entities.
  27.  *
  28.  * This class is designed for inheritance and users can subclass this class to
  29.  * write their own repositories with business-specific methods to locate entities.
  30.  *
  31.  * @template T of object
  32.  * @template-implements Selectable<int,T>
  33.  * @template-implements ObjectRepository<T>
  34.  */
  35. class EntityRepository implements ObjectRepositorySelectable
  36. {
  37.     /**
  38.      * @internal This property will be private in 3.0, call {@see getEntityName()} instead.
  39.      *
  40.      * @var class-string<T>
  41.      */
  42.     protected $_entityName;
  43.     /**
  44.      * @internal This property will be private in 3.0, call {@see getEntityManager()} instead.
  45.      *
  46.      * @var EntityManagerInterface
  47.      */
  48.     protected $_em;
  49.     /**
  50.      * @internal This property will be private in 3.0, call {@see getClassMetadata()} instead.
  51.      *
  52.      * @var ClassMetadata
  53.      * @psalm-var ClassMetadata<T>
  54.      */
  55.     protected $_class;
  56.     /** @var Inflector|null */
  57.     private static $inflector;
  58.     /** @psalm-param ClassMetadata<T> $class */
  59.     public function __construct(EntityManagerInterface $emClassMetadata $class)
  60.     {
  61.         $this->_entityName $class->name;
  62.         $this->_em         $em;
  63.         $this->_class      $class;
  64.     }
  65.     /**
  66.      * Creates a new QueryBuilder instance that is prepopulated for this entity name.
  67.      *
  68.      * @param string      $alias
  69.      * @param string|null $indexBy The index for the from.
  70.      *
  71.      * @return QueryBuilder
  72.      */
  73.     public function createQueryBuilder($alias$indexBy null)
  74.     {
  75.         return $this->_em->createQueryBuilder()
  76.             ->select($alias)
  77.             ->from($this->_entityName$alias$indexBy);
  78.     }
  79.     /**
  80.      * Creates a new result set mapping builder for this entity.
  81.      *
  82.      * The column naming strategy is "INCREMENT".
  83.      *
  84.      * @param string $alias
  85.      *
  86.      * @return ResultSetMappingBuilder
  87.      */
  88.     public function createResultSetMappingBuilder($alias)
  89.     {
  90.         $rsm = new ResultSetMappingBuilder($this->_emResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT);
  91.         $rsm->addRootEntityFromClassMetadata($this->_entityName$alias);
  92.         return $rsm;
  93.     }
  94.     /**
  95.      * Creates a new Query instance based on a predefined metadata named query.
  96.      *
  97.      * @deprecated
  98.      *
  99.      * @param string $queryName
  100.      *
  101.      * @return Query
  102.      */
  103.     public function createNamedQuery($queryName)
  104.     {
  105.         Deprecation::trigger(
  106.             'doctrine/orm',
  107.             'https://github.com/doctrine/orm/issues/8592',
  108.             'Named Queries are deprecated, here "%s" on entity %s. Move the query logic into EntityRepository',
  109.             $queryName,
  110.             $this->_class->name
  111.         );
  112.         return $this->_em->createQuery($this->_class->getNamedQuery($queryName));
  113.     }
  114.     /**
  115.      * Creates a native SQL query.
  116.      *
  117.      * @deprecated
  118.      *
  119.      * @param string $queryName
  120.      *
  121.      * @return NativeQuery
  122.      */
  123.     public function createNativeNamedQuery($queryName)
  124.     {
  125.         Deprecation::trigger(
  126.             'doctrine/orm',
  127.             'https://github.com/doctrine/orm/issues/8592',
  128.             'Named Native Queries are deprecated, here "%s" on entity %s. Move the query logic into EntityRepository',
  129.             $queryName,
  130.             $this->_class->name
  131.         );
  132.         $queryMapping $this->_class->getNamedNativeQuery($queryName);
  133.         $rsm          = new Query\ResultSetMappingBuilder($this->_em);
  134.         $rsm->addNamedNativeQueryMapping($this->_class$queryMapping);
  135.         return $this->_em->createNativeQuery($queryMapping['query'], $rsm);
  136.     }
  137.     /**
  138.      * Clears the repository, causing all managed entities to become detached.
  139.      *
  140.      * @deprecated 2.8 This method is being removed from the ORM and won't have any replacement
  141.      *
  142.      * @return void
  143.      */
  144.     public function clear()
  145.     {
  146.         Deprecation::trigger(
  147.             'doctrine/orm',
  148.             'https://github.com/doctrine/orm/issues/8460',
  149.             'Calling %s() is deprecated and will not be supported in Doctrine ORM 3.0.',
  150.             __METHOD__
  151.         );
  152.         if (! class_exists(PersistentObject::class)) {
  153.             throw NotSupported::createForPersistence3(sprintf(
  154.                 'Partial clearing of entities for class %s',
  155.                 $this->_class->rootEntityName
  156.             ));
  157.         }
  158.         $this->_em->clear($this->_class->rootEntityName);
  159.     }
  160.     /**
  161.      * Finds an entity by its primary key / identifier.
  162.      *
  163.      * @param mixed    $id          The identifier.
  164.      * @param int|null $lockMode    One of the \Doctrine\DBAL\LockMode::* constants
  165.      *                              or NULL if no specific lock mode should be used
  166.      *                              during the search.
  167.      * @param int|null $lockVersion The lock version.
  168.      * @psalm-param LockMode::*|null $lockMode
  169.      *
  170.      * @return object|null The entity instance or NULL if the entity can not be found.
  171.      * @psalm-return ?T
  172.      */
  173.     public function find($id$lockMode null$lockVersion null)
  174.     {
  175.         return $this->_em->find($this->_entityName$id$lockMode$lockVersion);
  176.     }
  177.     /**
  178.      * Finds all entities in the repository.
  179.      *
  180.      * @psalm-return list<T> The entities.
  181.      */
  182.     public function findAll()
  183.     {
  184.         return $this->findBy([]);
  185.     }
  186.     /**
  187.      * Finds entities by a set of criteria.
  188.      *
  189.      * @param int|null $limit
  190.      * @param int|null $offset
  191.      * @psalm-param array<string, mixed> $criteria
  192.      * @psalm-param array<string, string>|null $orderBy
  193.      *
  194.      * @return object[] The objects.
  195.      * @psalm-return list<T>
  196.      */
  197.     public function findBy(array $criteria, ?array $orderBy null$limit null$offset null)
  198.     {
  199.         $persister $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
  200.         return $persister->loadAll($criteria$orderBy$limit$offset);
  201.     }
  202.     /**
  203.      * Finds a single entity by a set of criteria.
  204.      *
  205.      * @psalm-param array<string, mixed> $criteria
  206.      * @psalm-param array<string, string>|null $orderBy
  207.      *
  208.      * @return object|null The entity instance or NULL if the entity can not be found.
  209.      * @psalm-return ?T
  210.      */
  211.     public function findOneBy(array $criteria, ?array $orderBy null)
  212.     {
  213.         $persister $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
  214.         return $persister->load($criterianullnull, [], null1$orderBy);
  215.     }
  216.     /**
  217.      * Counts entities by a set of criteria.
  218.      *
  219.      * @psalm-param array<string, mixed> $criteria
  220.      *
  221.      * @return int The cardinality of the objects that match the given criteria.
  222.      *
  223.      * @todo Add this method to `ObjectRepository` interface in the next major release
  224.      */
  225.     public function count(array $criteria)
  226.     {
  227.         return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->count($criteria);
  228.     }
  229.     /**
  230.      * Adds support for magic method calls.
  231.      *
  232.      * @param string  $method
  233.      * @param mixed[] $arguments
  234.      * @psalm-param list<mixed> $arguments
  235.      *
  236.      * @return mixed The returned value from the resolved method.
  237.      *
  238.      * @throws BadMethodCallException If the method called is invalid.
  239.      */
  240.     public function __call($method$arguments)
  241.     {
  242.         if (str_starts_with($method'findBy')) {
  243.             return $this->resolveMagicCall('findBy'substr($method6), $arguments);
  244.         }
  245.         if (str_starts_with($method'findOneBy')) {
  246.             return $this->resolveMagicCall('findOneBy'substr($method9), $arguments);
  247.         }
  248.         if (str_starts_with($method'countBy')) {
  249.             return $this->resolveMagicCall('count'substr($method7), $arguments);
  250.         }
  251.         throw new BadMethodCallException(sprintf(
  252.             'Undefined method "%s". The method name must start with ' .
  253.             'either findBy, findOneBy or countBy!',
  254.             $method
  255.         ));
  256.     }
  257.     /** @return class-string<T> */
  258.     protected function getEntityName()
  259.     {
  260.         return $this->_entityName;
  261.     }
  262.     /**
  263.      * {@inheritDoc}
  264.      */
  265.     public function getClassName()
  266.     {
  267.         return $this->getEntityName();
  268.     }
  269.     /** @return EntityManagerInterface */
  270.     protected function getEntityManager()
  271.     {
  272.         return $this->_em;
  273.     }
  274.     /**
  275.      * @return ClassMetadata
  276.      * @psalm-return ClassMetadata<T>
  277.      */
  278.     protected function getClassMetadata()
  279.     {
  280.         return $this->_class;
  281.     }
  282.     /**
  283.      * Select all elements from a selectable that match the expression and
  284.      * return a new collection containing these elements.
  285.      *
  286.      * @return AbstractLazyCollection
  287.      * @psalm-return AbstractLazyCollection<int, T>&Selectable<int, T>
  288.      */
  289.     public function matching(Criteria $criteria)
  290.     {
  291.         $persister $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
  292.         return new LazyCriteriaCollection($persister$criteria);
  293.     }
  294.     /**
  295.      * Resolves a magic method call to the proper existent method at `EntityRepository`.
  296.      *
  297.      * @param string $method The method to call
  298.      * @param string $by     The property name used as condition
  299.      * @psalm-param list<mixed> $arguments The arguments to pass at method call
  300.      *
  301.      * @return mixed
  302.      *
  303.      * @throws InvalidMagicMethodCall If the method called is invalid or the
  304.      *                                requested field/association does not exist.
  305.      */
  306.     private function resolveMagicCall(string $methodstring $by, array $arguments)
  307.     {
  308.         if (! $arguments) {
  309.             throw InvalidMagicMethodCall::onMissingParameter($method $by);
  310.         }
  311.         if (self::$inflector === null) {
  312.             self::$inflector InflectorFactory::create()->build();
  313.         }
  314.         $fieldName lcfirst(self::$inflector->classify($by));
  315.         if (! ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName))) {
  316.             throw InvalidMagicMethodCall::becauseFieldNotFoundIn(
  317.                 $this->_entityName,
  318.                 $fieldName,
  319.                 $method $by
  320.             );
  321.         }
  322.         return $this->$method([$fieldName => $arguments[0]], ...array_slice($arguments1));
  323.     }
  324. }