EntityRepository.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. <?php
  2. /*
  3. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14. *
  15. * This software consists of voluntary contributions made by many individuals
  16. * and is licensed under the MIT license. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\ORM;
  20. use BadMethodCallException;
  21. use Doctrine\Common\Collections\Collection;
  22. use Doctrine\Common\Collections\Criteria;
  23. use Doctrine\Common\Collections\Selectable;
  24. use Doctrine\Inflector\Inflector;
  25. use Doctrine\Inflector\InflectorFactory;
  26. use Doctrine\ORM\Mapping\ClassMetadata;
  27. use Doctrine\ORM\Query\ResultSetMappingBuilder;
  28. use Doctrine\Persistence\ObjectRepository;
  29. use function array_slice;
  30. use function lcfirst;
  31. use function sprintf;
  32. use function strpos;
  33. use function substr;
  34. use function trigger_error;
  35. use const E_USER_DEPRECATED;
  36. /**
  37. * An EntityRepository serves as a repository for entities with generic as well as
  38. * business specific methods for retrieving entities.
  39. *
  40. * This class is designed for inheritance and users can subclass this class to
  41. * write their own repositories with business-specific methods to locate entities.
  42. *
  43. * @template T
  44. * @template-implements Selectable<int,T>
  45. * @template-implements ObjectRepository<T>
  46. */
  47. class EntityRepository implements ObjectRepository, Selectable
  48. {
  49. /** @var string */
  50. protected $_entityName;
  51. /** @var EntityManager */
  52. protected $_em;
  53. /** @var ClassMetadata */
  54. protected $_class;
  55. /** @var Inflector */
  56. private static $inflector;
  57. /**
  58. * Initializes a new <tt>EntityRepository</tt>.
  59. *
  60. * @psalm-param Mapping\ClassMetadata $class
  61. */
  62. public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $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 $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->_em, ResultSetMappingBuilder::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. * @param string $queryName
  101. *
  102. * @return Query
  103. */
  104. public function createNamedQuery($queryName)
  105. {
  106. return $this->_em->createQuery($this->_class->getNamedQuery($queryName));
  107. }
  108. /**
  109. * Creates a native SQL query.
  110. *
  111. * @param string $queryName
  112. *
  113. * @return NativeQuery
  114. */
  115. public function createNativeNamedQuery($queryName)
  116. {
  117. $queryMapping = $this->_class->getNamedNativeQuery($queryName);
  118. $rsm = new Query\ResultSetMappingBuilder($this->_em);
  119. $rsm->addNamedNativeQueryMapping($this->_class, $queryMapping);
  120. return $this->_em->createNativeQuery($queryMapping['query'], $rsm);
  121. }
  122. /**
  123. * Clears the repository, causing all managed entities to become detached.
  124. *
  125. * @deprecated 2.8 This method is being removed from the ORM and won't have any replacement
  126. *
  127. * @return void
  128. */
  129. public function clear()
  130. {
  131. @trigger_error('Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0.', E_USER_DEPRECATED);
  132. $this->_em->clear($this->_class->rootEntityName);
  133. }
  134. /**
  135. * Finds an entity by its primary key / identifier.
  136. *
  137. * @param mixed $id The identifier.
  138. * @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants
  139. * or NULL if no specific lock mode should be used
  140. * during the search.
  141. * @param int|null $lockVersion The lock version.
  142. *
  143. * @return object|null The entity instance or NULL if the entity can not be found.
  144. *
  145. * @psalm-return ?T
  146. */
  147. public function find($id, $lockMode = null, $lockVersion = null)
  148. {
  149. return $this->_em->find($this->_entityName, $id, $lockMode, $lockVersion);
  150. }
  151. /**
  152. * Finds all entities in the repository.
  153. *
  154. * @psalm-return list<T> The entities.
  155. */
  156. public function findAll()
  157. {
  158. return $this->findBy([]);
  159. }
  160. /**
  161. * Finds entities by a set of criteria.
  162. *
  163. * @param int|null $limit
  164. * @param int|null $offset
  165. *
  166. * @psalm-param array<string, mixed> $criteria
  167. * @psalm-param array<string, string>|null $orderBy
  168. * @psalm-return list<T> The objects.
  169. */
  170. public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
  171. {
  172. $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
  173. return $persister->loadAll($criteria, $orderBy, $limit, $offset);
  174. }
  175. /**
  176. * Finds a single entity by a set of criteria.
  177. *
  178. * @return object|null The entity instance or NULL if the entity can not be found.
  179. *
  180. * @psalm-param array<string, mixed> $criteria
  181. * @psalm-param array<string, string>|null $orderBy
  182. * @psalm-return ?T
  183. */
  184. public function findOneBy(array $criteria, ?array $orderBy = null)
  185. {
  186. $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
  187. return $persister->load($criteria, null, null, [], null, 1, $orderBy);
  188. }
  189. /**
  190. * Counts entities by a set of criteria.
  191. *
  192. * @return int The cardinality of the objects that match the given criteria.
  193. *
  194. * @psalm-param array<string, mixed> $criteria
  195. * @todo Add this method to `ObjectRepository` interface in the next major release
  196. */
  197. public function count(array $criteria)
  198. {
  199. return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->count($criteria);
  200. }
  201. /**
  202. * Adds support for magic method calls.
  203. *
  204. * @param string $method
  205. *
  206. * @return mixed The returned value from the resolved method.
  207. *
  208. * @throws ORMException
  209. * @throws BadMethodCallException If the method called is invalid.
  210. *
  211. * @psalm-param list<mixed> $arguments
  212. */
  213. public function __call($method, $arguments)
  214. {
  215. if (strpos($method, 'findBy') === 0) {
  216. return $this->resolveMagicCall('findBy', substr($method, 6), $arguments);
  217. }
  218. if (strpos($method, 'findOneBy') === 0) {
  219. return $this->resolveMagicCall('findOneBy', substr($method, 9), $arguments);
  220. }
  221. if (strpos($method, 'countBy') === 0) {
  222. return $this->resolveMagicCall('count', substr($method, 7), $arguments);
  223. }
  224. throw new BadMethodCallException(sprintf(
  225. 'Undefined method "%s". The method name must start with ' .
  226. 'either findBy, findOneBy or countBy!',
  227. $method
  228. ));
  229. }
  230. /**
  231. * @return string
  232. */
  233. protected function getEntityName()
  234. {
  235. return $this->_entityName;
  236. }
  237. /**
  238. * @return string
  239. */
  240. public function getClassName()
  241. {
  242. return $this->getEntityName();
  243. }
  244. /**
  245. * @return EntityManager
  246. */
  247. protected function getEntityManager()
  248. {
  249. return $this->_em;
  250. }
  251. /**
  252. * @return Mapping\ClassMetadata
  253. */
  254. protected function getClassMetadata()
  255. {
  256. return $this->_class;
  257. }
  258. /**
  259. * Select all elements from a selectable that match the expression and
  260. * return a new collection containing these elements.
  261. *
  262. * @psalm-return Collection<int, T>
  263. */
  264. public function matching(Criteria $criteria)
  265. {
  266. $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
  267. return new LazyCriteriaCollection($persister, $criteria);
  268. }
  269. /**
  270. * Resolves a magic method call to the proper existent method at `EntityRepository`.
  271. *
  272. * @param string $method The method to call
  273. * @param string $by The property name used as condition
  274. *
  275. * @return mixed
  276. *
  277. * @throws ORMException If the method called is invalid or the requested field/association does not exist.
  278. *
  279. * @psalm-param list<mixed> $arguments The arguments to pass at method call
  280. */
  281. private function resolveMagicCall(string $method, string $by, array $arguments)
  282. {
  283. if (! $arguments) {
  284. throw ORMException::findByRequiresParameter($method . $by);
  285. }
  286. if (self::$inflector === null) {
  287. self::$inflector = InflectorFactory::create()->build();
  288. }
  289. $fieldName = lcfirst(self::$inflector->classify($by));
  290. if (! ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName))) {
  291. throw ORMException::invalidMagicCall($this->_entityName, $fieldName, $method . $by);
  292. }
  293. return $this->$method([$fieldName => $arguments[0]], ...array_slice($arguments, 1));
  294. }
  295. }