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

  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\DBAL\LockMode;
  9. use Doctrine\Inflector\Inflector;
  10. use Doctrine\Inflector\InflectorFactory;
  11. use Doctrine\ORM\Mapping\ClassMetadata;
  12. use Doctrine\ORM\Query\ResultSetMappingBuilder;
  13. use Doctrine\ORM\Repository\Exception\InvalidMagicMethodCall;
  14. use Doctrine\Persistence\ObjectRepository;
  15. use function array_slice;
  16. use function lcfirst;
  17. use function sprintf;
  18. use function str_starts_with;
  19. use function substr;
  20. /**
  21.  * An EntityRepository serves as a repository for entities with generic as well as
  22.  * business specific methods for retrieving entities.
  23.  *
  24.  * This class is designed for inheritance and users can subclass this class to
  25.  * write their own repositories with business-specific methods to locate entities.
  26.  *
  27.  * @template T of object
  28.  * @template-implements Selectable<int,T>
  29.  * @template-implements ObjectRepository<T>
  30.  */
  31. class EntityRepository implements ObjectRepositorySelectable
  32. {
  33.     /** @psalm-var class-string<T> */
  34.     private readonly string $entityName;
  35.     private static Inflector|null $inflector null;
  36.     /** @psalm-param ClassMetadata<T> $class */
  37.     public function __construct(
  38.         private readonly EntityManagerInterface $em,
  39.         private readonly ClassMetadata $class,
  40.     ) {
  41.         $this->entityName $class->name;
  42.     }
  43.     /**
  44.      * Creates a new QueryBuilder instance that is prepopulated for this entity name.
  45.      */
  46.     public function createQueryBuilder(string $aliasstring|null $indexBy null): QueryBuilder
  47.     {
  48.         return $this->em->createQueryBuilder()
  49.             ->select($alias)
  50.             ->from($this->entityName$alias$indexBy);
  51.     }
  52.     /**
  53.      * Creates a new result set mapping builder for this entity.
  54.      *
  55.      * The column naming strategy is "INCREMENT".
  56.      */
  57.     public function createResultSetMappingBuilder(string $alias): ResultSetMappingBuilder
  58.     {
  59.         $rsm = new ResultSetMappingBuilder($this->emResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT);
  60.         $rsm->addRootEntityFromClassMetadata($this->entityName$alias);
  61.         return $rsm;
  62.     }
  63.     /**
  64.      * Finds an entity by its primary key / identifier.
  65.      *
  66.      * @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants
  67.      *                                    or NULL if no specific lock mode should be used
  68.      *                                    during the search.
  69.      * @psalm-param LockMode::*|null $lockMode
  70.      *
  71.      * @return object|null The entity instance or NULL if the entity can not be found.
  72.      * @psalm-return ?T
  73.      */
  74.     public function find(mixed $idLockMode|int|null $lockMode nullint|null $lockVersion null): object|null
  75.     {
  76.         return $this->em->find($this->entityName$id$lockMode$lockVersion);
  77.     }
  78.     /**
  79.      * Finds all entities in the repository.
  80.      *
  81.      * @psalm-return list<T> The entities.
  82.      */
  83.     public function findAll(): array
  84.     {
  85.         return $this->findBy([]);
  86.     }
  87.     /**
  88.      * Finds entities by a set of criteria.
  89.      *
  90.      * {@inheritDoc}
  91.      *
  92.      * @psalm-return list<T>
  93.      */
  94.     public function findBy(array $criteria, array|null $orderBy nullint|null $limit nullint|null $offset null): array
  95.     {
  96.         $persister $this->em->getUnitOfWork()->getEntityPersister($this->entityName);
  97.         return $persister->loadAll($criteria$orderBy$limit$offset);
  98.     }
  99.     /**
  100.      * Finds a single entity by a set of criteria.
  101.      *
  102.      * @psalm-param array<string, mixed> $criteria
  103.      * @psalm-param array<string, string>|null $orderBy
  104.      *
  105.      * @psalm-return T|null
  106.      */
  107.     public function findOneBy(array $criteria, array|null $orderBy null): object|null
  108.     {
  109.         $persister $this->em->getUnitOfWork()->getEntityPersister($this->entityName);
  110.         return $persister->load($criterianullnull, [], null1$orderBy);
  111.     }
  112.     /**
  113.      * Counts entities by a set of criteria.
  114.      *
  115.      * @psalm-param array<string, mixed> $criteria
  116.      *
  117.      * @return int The cardinality of the objects that match the given criteria.
  118.      *
  119.      * @todo Add this method to `ObjectRepository` interface in the next major release
  120.      */
  121.     public function count(array $criteria = []): int
  122.     {
  123.         return $this->em->getUnitOfWork()->getEntityPersister($this->entityName)->count($criteria);
  124.     }
  125.     /**
  126.      * Adds support for magic method calls.
  127.      *
  128.      * @param mixed[] $arguments
  129.      * @psalm-param list<mixed> $arguments
  130.      *
  131.      * @throws BadMethodCallException If the method called is invalid.
  132.      */
  133.     public function __call(string $method, array $arguments): mixed
  134.     {
  135.         if (str_starts_with($method'findBy')) {
  136.             return $this->resolveMagicCall('findBy'substr($method6), $arguments);
  137.         }
  138.         if (str_starts_with($method'findOneBy')) {
  139.             return $this->resolveMagicCall('findOneBy'substr($method9), $arguments);
  140.         }
  141.         if (str_starts_with($method'countBy')) {
  142.             return $this->resolveMagicCall('count'substr($method7), $arguments);
  143.         }
  144.         throw new BadMethodCallException(sprintf(
  145.             'Undefined method "%s". The method name must start with ' .
  146.             'either findBy, findOneBy or countBy!',
  147.             $method,
  148.         ));
  149.     }
  150.     /** @psalm-return class-string<T> */
  151.     protected function getEntityName(): string
  152.     {
  153.         return $this->entityName;
  154.     }
  155.     public function getClassName(): string
  156.     {
  157.         return $this->getEntityName();
  158.     }
  159.     protected function getEntityManager(): EntityManagerInterface
  160.     {
  161.         return $this->em;
  162.     }
  163.     /** @psalm-return ClassMetadata<T> */
  164.     protected function getClassMetadata(): ClassMetadata
  165.     {
  166.         return $this->class;
  167.     }
  168.     /**
  169.      * Select all elements from a selectable that match the expression and
  170.      * return a new collection containing these elements.
  171.      *
  172.      * @psalm-return AbstractLazyCollection<int, T>&Selectable<int, T>
  173.      */
  174.     public function matching(Criteria $criteria): AbstractLazyCollection&Selectable
  175.     {
  176.         $persister $this->em->getUnitOfWork()->getEntityPersister($this->entityName);
  177.         return new LazyCriteriaCollection($persister$criteria);
  178.     }
  179.     /**
  180.      * Resolves a magic method call to the proper existent method at `EntityRepository`.
  181.      *
  182.      * @param string $method The method to call
  183.      * @param string $by     The property name used as condition
  184.      * @psalm-param list<mixed> $arguments The arguments to pass at method call
  185.      *
  186.      * @throws InvalidMagicMethodCall If the method called is invalid or the
  187.      *                                requested field/association does not exist.
  188.      */
  189.     private function resolveMagicCall(string $methodstring $by, array $arguments): mixed
  190.     {
  191.         if (! $arguments) {
  192.             throw InvalidMagicMethodCall::onMissingParameter($method $by);
  193.         }
  194.         self::$inflector ??= InflectorFactory::create()->build();
  195.         $fieldName lcfirst(self::$inflector->classify($by));
  196.         if (! ($this->class->hasField($fieldName) || $this->class->hasAssociation($fieldName))) {
  197.             throw InvalidMagicMethodCall::becauseFieldNotFoundIn(
  198.                 $this->entityName,
  199.                 $fieldName,
  200.                 $method $by,
  201.             );
  202.         }
  203.         return $this->$method([$fieldName => $arguments[0]], ...array_slice($arguments1));
  204.     }
  205. }