vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php line 271

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