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

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