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 20namespace Doctrine\ORM; 21 22use Doctrine\Common\Collections\Criteria; 23use Doctrine\Common\Collections\Selectable; 24use Doctrine\Common\Inflector\Inflector; 25use Doctrine\ORM\Query\ResultSetMappingBuilder; 26use Doctrine\Persistence\ObjectRepository; 27 28/** 29 * An EntityRepository serves as a repository for entities with generic as well as 30 * business specific methods for retrieving entities. 31 * 32 * This class is designed for inheritance and users can subclass this class to 33 * write their own repositories with business-specific methods to locate entities. 34 * 35 * @since 2.0 36 * @author Benjamin Eberlei <kontakt@beberlei.de> 37 * @author Guilherme Blanco <guilhermeblanco@hotmail.com> 38 * @author Jonathan Wage <jonwage@gmail.com> 39 * @author Roman Borschel <roman@code-factory.org> 40 */ 41class EntityRepository implements ObjectRepository, Selectable 42{ 43 /** 44 * @var string 45 */ 46 protected $_entityName; 47 48 /** 49 * @var EntityManager 50 */ 51 protected $_em; 52 53 /** 54 * @var \Doctrine\ORM\Mapping\ClassMetadata 55 */ 56 protected $_class; 57 58 /** 59 * Initializes a new <tt>EntityRepository</tt>. 60 */ 61 public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class) 62 { 63 $this->_entityName = $class->name; 64 $this->_em = $em; 65 $this->_class = $class; 66 } 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 /** 84 * Creates a new result set mapping builder for this entity. 85 * 86 * The column naming strategy is "INCREMENT". 87 * 88 * @param string $alias 89 * 90 * @return ResultSetMappingBuilder 91 */ 92 public function createResultSetMappingBuilder($alias) 93 { 94 $rsm = new ResultSetMappingBuilder($this->_em, ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT); 95 $rsm->addRootEntityFromClassMetadata($this->_entityName, $alias); 96 97 return $rsm; 98 } 99 100 /** 101 * Creates a new Query instance based on a predefined metadata named query. 102 * 103 * @param string $queryName 104 * 105 * @return Query 106 */ 107 public function createNamedQuery($queryName) 108 { 109 return $this->_em->createQuery($this->_class->getNamedQuery($queryName)); 110 } 111 112 /** 113 * Creates a native SQL query. 114 * 115 * @param string $queryName 116 * 117 * @return NativeQuery 118 */ 119 public function createNativeNamedQuery($queryName) 120 { 121 $queryMapping = $this->_class->getNamedNativeQuery($queryName); 122 $rsm = new Query\ResultSetMappingBuilder($this->_em); 123 $rsm->addNamedNativeQueryMapping($this->_class, $queryMapping); 124 125 return $this->_em->createNativeQuery($queryMapping['query'], $rsm); 126 } 127 128 /** 129 * Clears the repository, causing all managed entities to become detached. 130 * 131 * @return void 132 */ 133 public function clear() 134 { 135 $this->_em->clear($this->_class->rootEntityName); 136 } 137 138 /** 139 * Finds an entity by its primary key / identifier. 140 * 141 * @param mixed $id The identifier. 142 * @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants 143 * or NULL if no specific lock mode should be used 144 * during the search. 145 * @param int|null $lockVersion The lock version. 146 * 147 * @return object|null The entity instance or NULL if the entity can not be found. 148 */ 149 public function find($id, $lockMode = null, $lockVersion = null) 150 { 151 return $this->_em->find($this->_entityName, $id, $lockMode, $lockVersion); 152 } 153 154 /** 155 * Finds all entities in the repository. 156 * 157 * @return array The entities. 158 */ 159 public function findAll() 160 { 161 return $this->findBy([]); 162 } 163 164 /** 165 * Finds entities by a set of criteria. 166 * 167 * @param array $criteria 168 * @param array|null $orderBy 169 * @param int|null $limit 170 * @param int|null $offset 171 * 172 * @return array The objects. 173 */ 174 public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) 175 { 176 $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); 177 178 return $persister->loadAll($criteria, $orderBy, $limit, $offset); 179 } 180 181 /** 182 * Finds a single entity by a set of criteria. 183 * 184 * @param array $criteria 185 * @param array|null $orderBy 186 * 187 * @return object|null The entity instance or NULL if the entity can not be found. 188 */ 189 public function findOneBy(array $criteria, array $orderBy = null) 190 { 191 $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); 192 193 return $persister->load($criteria, null, null, [], null, 1, $orderBy); 194 } 195 196 /** 197 * Counts entities by a set of criteria. 198 * 199 * @todo Add this method to `ObjectRepository` interface in the next major release 200 * 201 * @param array $criteria 202 * 203 * @return int The cardinality of the objects that match the given criteria. 204 */ 205 public function count(array $criteria) 206 { 207 return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->count($criteria); 208 } 209 210 /** 211 * Adds support for magic method calls. 212 * 213 * @param string $method 214 * @param array $arguments 215 * 216 * @return mixed The returned value from the resolved method. 217 * 218 * @throws ORMException 219 * @throws \BadMethodCallException If the method called is invalid 220 */ 221 public function __call($method, $arguments) 222 { 223 if (0 === strpos($method, 'findBy')) { 224 return $this->resolveMagicCall('findBy', substr($method, 6), $arguments); 225 } 226 227 if (0 === strpos($method, 'findOneBy')) { 228 return $this->resolveMagicCall('findOneBy', substr($method, 9), $arguments); 229 } 230 231 if (0 === strpos($method, 'countBy')) { 232 return $this->resolveMagicCall('count', substr($method, 7), $arguments); 233 } 234 235 throw new \BadMethodCallException( 236 "Undefined method '$method'. The method name must start with ". 237 "either findBy, findOneBy or countBy!" 238 ); 239 } 240 241 /** 242 * @return string 243 */ 244 protected function getEntityName() 245 { 246 return $this->_entityName; 247 } 248 249 /** 250 * @return string 251 */ 252 public function getClassName() 253 { 254 return $this->getEntityName(); 255 } 256 257 /** 258 * @return EntityManager 259 */ 260 protected function getEntityManager() 261 { 262 return $this->_em; 263 } 264 265 /** 266 * @return Mapping\ClassMetadata 267 */ 268 protected function getClassMetadata() 269 { 270 return $this->_class; 271 } 272 273 /** 274 * Select all elements from a selectable that match the expression and 275 * return a new collection containing these elements. 276 * 277 * @param \Doctrine\Common\Collections\Criteria $criteria 278 * 279 * @return \Doctrine\Common\Collections\Collection 280 */ 281 public function matching(Criteria $criteria) 282 { 283 $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); 284 285 return new LazyCriteriaCollection($persister, $criteria); 286 } 287 288 /** 289 * Resolves a magic method call to the proper existent method at `EntityRepository`. 290 * 291 * @param string $method The method to call 292 * @param string $by The property name used as condition 293 * @param array $arguments The arguments to pass at method call 294 * 295 * @throws ORMException If the method called is invalid or the requested field/association does not exist 296 * 297 * @return mixed 298 */ 299 private function resolveMagicCall($method, $by, array $arguments) 300 { 301 if (! $arguments) { 302 throw ORMException::findByRequiresParameter($method . $by); 303 } 304 305 $fieldName = lcfirst(Inflector::classify($by)); 306 307 if (! ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName))) { 308 throw ORMException::invalidMagicCall($this->_entityName, $fieldName, $method . $by); 309 } 310 311 return $this->$method([$fieldName => $arguments[0]], ...array_slice($arguments, 1)); 312 } 313} 314