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

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 ObjectRepository, Selectable
  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 $em, ClassMetadata $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->_em, ResultSetMappingBuilder::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($criteria, null, null, [], null, 1, $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($method, 6), $arguments);
  245. }
  246. if (str_starts_with($method, 'findOneBy')) {
  247. return $this->resolveMagicCall('findOneBy', substr($method, 9), $arguments);
  248. }
  249. if (str_starts_with($method, 'countBy')) {
  250. return $this->resolveMagicCall('count', substr($method, 7), $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 $method, string $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($arguments, 1));
  327. }
  328. }