getting-started.rst 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537
  1. Getting Started with Doctrine
  2. =============================
  3. This guide covers getting started with the Doctrine ORM. After working
  4. through the guide you should know:
  5. - How to install and configure Doctrine by connecting it to a database
  6. - Mapping PHP objects to database tables
  7. - Generating a database schema from PHP objects
  8. - Using the ``EntityManager`` to insert, update, delete and find
  9. objects in the database.
  10. Guide Assumptions
  11. -----------------
  12. This guide is designed for beginners that haven't worked with Doctrine ORM
  13. before. There are some prerequesites for the tutorial that have to be
  14. installed:
  15. - PHP 5.3.3 or above
  16. - Composer Package Manager (`Install Composer
  17. <http://getcomposer.org/doc/00-intro.md>`_)
  18. The code of this tutorial is `available on Github <https://github.com/doctrine/doctrine2-orm-tutorial>`_.
  19. .. note::
  20. This tutorial assumes you work with Doctrine 2.4 and above.
  21. Some of the code will not work with lower versions.
  22. What is Doctrine?
  23. -----------------
  24. Doctrine 2 is an `object-relational mapper (ORM)
  25. <http://en.wikipedia.org/wiki/Object-relational_mapping>`_ for PHP 5.3.3+ that
  26. provides transparent persistence for PHP objects. It uses the Data Mapper
  27. pattern at the heart, aiming for a complete separation of your domain/business
  28. logic from the persistence in a relational database management system.
  29. The benefit of Doctrine for the programmer is the ability to focus
  30. on the object-oriented business logic and worry about persistence only
  31. as a secondary problem. This doesn't mean persistence is downplayed by Doctrine
  32. 2, however it is our belief that there are considerable benefits for
  33. object-oriented programming if persistence and entities are kept
  34. separated.
  35. What are Entities?
  36. ~~~~~~~~~~~~~~~~~~
  37. Entities are PHP Objects that can be identified over many requests
  38. by a unique identifier or primary key. These classes don't need to extend any
  39. abstract base class or interface. An entity class must not be final
  40. or contain final methods. Additionally it must not implement
  41. **clone** nor **wakeup** or :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
  42. An entity contains persistable properties. A persistable property
  43. is an instance variable of the entity that is saved into and retrieved from the database
  44. by Doctrine's data mapping capabilities.
  45. An Example Model: Bug Tracker
  46. -----------------------------
  47. For this Getting Started Guide for Doctrine we will implement the
  48. Bug Tracker domain model from the
  49. `Zend\_Db\_Table <http://framework.zend.com/manual/en/zend.db.table.html>`_
  50. documentation. Reading their documentation we can extract the
  51. requirements:
  52. - A Bugs has a description, creation date, status, reporter and
  53. engineer
  54. - A bug can occur on different products (platforms)
  55. - Products have a name.
  56. - Bug Reporter and Engineers are both Users of the System.
  57. - A user can create new bugs.
  58. - The assigned engineer can close a bug.
  59. - A user can see all his reported or assigned bugs.
  60. - Bugs can be paginated through a list-view.
  61. Setup Project
  62. -------------
  63. Create a new empty folder for this tutorial project, for example
  64. ``doctrine2-tutorial`` and create a new file ``composer.json`` with
  65. the following contents:
  66. ::
  67. {
  68. "require": {
  69. "doctrine/orm": "2.*",
  70. "symfony/yaml": "2.*"
  71. },
  72. "autoload": {
  73. "psr-0": {"": "src/"}
  74. }
  75. }
  76. Install Doctrine using the Composer Dependency Management tool, by calling:
  77. ::
  78. $ composer install
  79. This will install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM,
  80. Symfony YAML and Symfony Console. Both Symfony dependencies are optional
  81. but will be used in this tutorial.
  82. You can prepare the directory structure:
  83. ::
  84. project
  85. |-- composer.json
  86. |-- config
  87. | |-- xml
  88. | `-- yaml
  89. `-- src
  90. Obtaining the EntityManager
  91. ---------------------------
  92. Doctrine's public interface is the EntityManager, it provides the
  93. access point to the complete lifecycle management of your entities
  94. and transforms entities from and back to persistence. You have to
  95. configure and create it to use your entities with Doctrine 2. I
  96. will show the configuration steps and then discuss them step by
  97. step:
  98. .. code-block:: php
  99. <?php
  100. // bootstrap.php
  101. use Doctrine\ORM\Tools\Setup;
  102. use Doctrine\ORM\EntityManager;
  103. require_once "vendor/autoload.php";
  104. // Create a simple "default" Doctrine ORM configuration for Annotations
  105. $isDevMode = true;
  106. $config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode);
  107. // or if you prefer yaml or XML
  108. //$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
  109. //$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
  110. // database configuration parameters
  111. $conn = array(
  112. 'driver' => 'pdo_sqlite',
  113. 'path' => __DIR__ . '/db.sqlite',
  114. );
  115. // obtaining the entity manager
  116. $entityManager = EntityManager::create($conn, $config);
  117. The first require statement sets up the autoloading capabilities of Doctrine
  118. using the Composer autoload.
  119. The second block consists of the instantiation of the ORM
  120. ``Configuration`` object using the Setup helper. It assumes a bunch
  121. of defaults that you don't have to bother about for now. You can
  122. read up on the configuration details in the
  123. :doc:`reference chapter on configuration <../reference/configuration>`.
  124. The third block shows the configuration options required to connect
  125. to a database, in my case a file-based sqlite database. All the
  126. configuration options for all the shipped drivers are given in the
  127. `DBAL Configuration section of the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/dbal>`_.
  128. The last block shows how the ``EntityManager`` is obtained from a
  129. factory method.
  130. Generating the Database Schema
  131. ------------------------------
  132. Now that we have defined the Metadata Mappings and bootstrapped the
  133. EntityManager we want to generate the relational database schema
  134. from it. Doctrine has a Command-Line-Interface that allows you to
  135. access the SchemaTool, a component that generates the required
  136. tables to work with the metadata.
  137. For the command-line tool to work a cli-config.php file has to be
  138. present in the project root directory, where you will execute the
  139. doctrine command. Its a fairly simple file:
  140. .. code-block:: php
  141. <?php
  142. // cli-config.php
  143. require_once "bootstrap.php";
  144. return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);
  145. You can then change into your project directory and call the
  146. Doctrine command-line tool:
  147. ::
  148. $ cd project/
  149. $ php vendor/bin/doctrine orm:schema-tool:create
  150. During the development you probably need to re-create the database
  151. several times when changing the Entity metadata. You can then
  152. either re-create the database:
  153. ::
  154. $ php vendor/bin/doctrine orm:schema-tool:drop --force
  155. $ php vendor/bin/doctrine orm:schema-tool:create
  156. Or use the update functionality:
  157. ::
  158. $ php vendor/bin/doctrine orm:schema-tool:update --force
  159. The updating of databases uses a Diff Algorithm for a given
  160. Database Schema, a cornerstone of the ``Doctrine\DBAL`` package,
  161. which can even be used without the Doctrine ORM package. However
  162. its not available in SQLite since it does not support ALTER TABLE.
  163. Starting with the Product
  164. -------------------------
  165. We start with the Product entity requirements, because it is the most simple one
  166. to get started. Create a ``src/Product.php`` file and put the ``Product``
  167. entity definition in there:
  168. .. code-block:: php
  169. <?php
  170. // src/Product.php
  171. class Product
  172. {
  173. /**
  174. * @var int
  175. */
  176. protected $id;
  177. /**
  178. * @var string
  179. */
  180. protected $name;
  181. public function getId()
  182. {
  183. return $this->id;
  184. }
  185. public function getName()
  186. {
  187. return $this->name;
  188. }
  189. public function setName($name)
  190. {
  191. $this->name = $name;
  192. }
  193. }
  194. Note how the properties have getter and setter methods defined except
  195. ``$id``. To access data from entities Doctrine 2 uses the Reflection API, so it
  196. is possible for Doctrine to access the value of ``$id``. You don't have to
  197. take Doctrine into account when designing access to the state of your objects.
  198. The next step for persistence with Doctrine is to describe the
  199. structure of the ``Product`` entity to Doctrine using a metadata
  200. language. The metadata language describes how entities, their
  201. properties and references should be persisted and what constraints
  202. should be applied to them.
  203. Metadata for entities are configured using a XML, YAML or Docblock Annotations.
  204. This Getting Started Guide will show the mappings for all Mapping Drivers.
  205. References in the text will be made to the XML mapping.
  206. .. configuration-block::
  207. .. code-block:: php
  208. <?php
  209. // src/Product.php
  210. /**
  211. * @Entity @Table(name="products")
  212. **/
  213. class Product
  214. {
  215. /** @Id @Column(type="integer") @GeneratedValue **/
  216. protected $id;
  217. /** @Column(type="string") **/
  218. protected $name;
  219. // .. (other code)
  220. }
  221. .. code-block:: xml
  222. <!-- config/xml/Product.dcm.xml -->
  223. <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
  224. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  225. xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
  226. http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
  227. <entity name="Product" table="products">
  228. <id name="id" type="integer">
  229. <generator strategy="AUTO" />
  230. </id>
  231. <field name="name" type="string" />
  232. </entity>
  233. </doctrine-mapping>
  234. .. code-block:: yaml
  235. # config/yaml/Product.dcm.yml
  236. Product:
  237. type: entity
  238. table: products
  239. id:
  240. id:
  241. type: integer
  242. generator:
  243. strategy: AUTO
  244. fields:
  245. name:
  246. type: string
  247. The top-level ``entity`` definition tag specifies information about
  248. the class and table-name. The primitive type ``Product::$name`` is
  249. defined as ``field`` attributes. The Id property is defined with
  250. the ``id`` tag. The id has a ``generator`` tag nested inside which
  251. defines that the primary key generation mechanism automatically
  252. uses the database platforms native id generation strategy, for
  253. example AUTO INCREMENT in the case of MySql or Sequences in the
  254. case of PostgreSql and Oracle.
  255. You have to update the database now, because we have a first Entity now:
  256. ::
  257. $ php vendor/bin/doctrine orm:schema-tool:update --force --dump-sql
  258. Specifying both flags ``--force`` and ``-dump-sql`` prints and executes the DDL
  259. statements.
  260. Now create a new script that will insert products into the database:
  261. .. code-block:: php
  262. <?php
  263. // create_product.php
  264. require_once "bootstrap.php";
  265. $newProductName = $argv[1];
  266. $product = new Product();
  267. $product->setName($newProductName);
  268. $entityManager->persist($product);
  269. $entityManager->flush();
  270. echo "Created Product with ID " . $product->getId() . "\n";
  271. Call this script from the command line to see how new products are created:
  272. ::
  273. $ php create_product.php ORM
  274. $ php create_product.php DBAL
  275. What is happening here? Using the ``Product`` is pretty standard OOP.
  276. The interesting bits are the use of the ``EntityManager`` service. To
  277. notify the EntityManager that a new entity should be inserted into the database
  278. you have to call ``persist()``. To intiate a transaction to actually perform
  279. the insertion, You have to explicitly call ``flush()`` on the ``EntityManager``.
  280. This distinction between persist and flush is allows to aggregate all writes
  281. (INSERT, UPDATE, DELETE) into one single transaction, which is executed when
  282. flush is called. Using this approach the write-performance is significantly
  283. better than in a scenario where updates are done for each entity in isolation.
  284. Doctrine follows the UnitOfWork pattern which additionally detects all entities
  285. that were fetched and have changed during the request. You don't have to keep track of
  286. entities yourself, when Doctrine already knowns about them.
  287. As a next step we want to fetch a list of all the products. Let's create a
  288. new script for this:
  289. .. code-block:: php
  290. <?php
  291. // list_products.php
  292. require_once "bootstrap.php";
  293. $productRepository = $entityManager->getRepository('Product');
  294. $products = $productRepository->findAll();
  295. foreach ($products as $product) {
  296. echo sprintf("-%s\n", $product->getName());
  297. }
  298. The ``EntityManager#getRepository()`` method can create a finder object (called
  299. repository) for every entity. It is provided by Doctrine and contains some
  300. finder methods such as ``findAll()``.
  301. Let's continue with displaying the name of a product based on its ID:
  302. .. code-block:: php
  303. <?php
  304. // show_product.php <id>
  305. require_once "bootstrap.php";
  306. $id = $argv[1];
  307. $product = $entityManager->find('Product', $id);
  308. if ($product === null) {
  309. echo "No product found.\n";
  310. exit(1);
  311. }
  312. echo sprintf("-%s\n", $product->getName());
  313. Updating a product name demonstrates the functionality UnitOfWork of pattern
  314. discussed before. We only need to find a product entity and all changes to its
  315. properties are written to the database:
  316. .. code-block:: php
  317. <?php
  318. // update_product.php <id> <new-name>
  319. require_once "bootstrap.php";
  320. $id = $argv[1];
  321. $newName = $argv[2];
  322. $product = $entityManager->find('Product', $id);
  323. if ($product === null) {
  324. echo "Product $id does not exist.\n";
  325. exit(1);
  326. }
  327. $product->setName($newName);
  328. $entityManager->flush();
  329. After calling this script on one of the existing products, you can verify the
  330. product name changed by calling the ``show_product.php`` script.
  331. Adding Bug and User Entities
  332. ----------------------------
  333. We continue with the bug tracker domain, by creating the missing classes
  334. ``Bug`` and ``User`` and putting them into ``src/Bug.php`` and
  335. ``src/User.php`` respectively.
  336. .. code-block:: php
  337. <?php
  338. // src/Bug.php
  339. /**
  340. * @Entity(repositoryClass="BugRepository") @Table(name="bugs")
  341. */
  342. class Bug
  343. {
  344. /**
  345. * @Id @Column(type="integer") @GeneratedValue
  346. * @var int
  347. */
  348. protected $id;
  349. /**
  350. * @Column(type="string")
  351. * @var string
  352. */
  353. protected $description;
  354. /**
  355. * @Column(type="datetime")
  356. * @var DateTime
  357. */
  358. protected $created;
  359. /**
  360. * @Column(type="string")
  361. * @var string
  362. */
  363. protected $status;
  364. public function getId()
  365. {
  366. return $this->id;
  367. }
  368. public function getDescription()
  369. {
  370. return $this->description;
  371. }
  372. public function setDescription($description)
  373. {
  374. $this->description = $description;
  375. }
  376. public function setCreated(DateTime $created)
  377. {
  378. $this->created = $created;
  379. }
  380. public function getCreated()
  381. {
  382. return $this->created;
  383. }
  384. public function setStatus($status)
  385. {
  386. $this->status = $status;
  387. }
  388. public function getStatus()
  389. {
  390. return $this->status;
  391. }
  392. }
  393. .. code-block:: php
  394. <?php
  395. // src/User.php
  396. /**
  397. * @Entity @Table(name="users")
  398. */
  399. class User
  400. {
  401. /**
  402. * @Id @GeneratedValue @Column(type="integer")
  403. * @var int
  404. */
  405. protected $id;
  406. /**
  407. * @Column(type="string")
  408. * @var string
  409. */
  410. protected $name;
  411. public function getId()
  412. {
  413. return $this->id;
  414. }
  415. public function getName()
  416. {
  417. return $this->name;
  418. }
  419. public function setName($name)
  420. {
  421. $this->name = $name;
  422. }
  423. }
  424. All of the properties discussed so far are simple string and integer values,
  425. for example the id fields of the entities, their names, description, status and
  426. change dates. With just the scalar values this model cannot describe the dynamics that we want. We
  427. want to model references between entities.
  428. References between objects are foreign keys in the database. You never have to
  429. work with the foreign keys directly, only with objects that represent the
  430. foreign key through their own identity.
  431. For every foreign key you either have a Doctrine ManyToOne or OneToOne
  432. association. On the inverse sides of these foreign keys you can have
  433. OneToMany associations. Obviously you can have ManyToMany associations
  434. that connect two tables with each other through a join table with
  435. two foreign keys.
  436. Now that you know the basics about references in Doctrine, we can extend the
  437. domain model to match the requirements:
  438. .. code-block:: php
  439. <?php
  440. // src/Bug.php
  441. use Doctrine\Common\Collections\ArrayCollection;
  442. class Bug
  443. {
  444. // ... (previous code)
  445. protected $products;
  446. public function __construct()
  447. {
  448. $this->products = new ArrayCollection();
  449. }
  450. }
  451. .. code-block:: php
  452. <?php
  453. // src/User.php
  454. use Doctrine\Common\Collections\ArrayCollection;
  455. class User
  456. {
  457. // ... (previous code)
  458. protected $reportedBugs;
  459. protected $assignedBugs;
  460. public function __construct()
  461. {
  462. $this->reportedBugs = new ArrayCollection();
  463. $this->assignedBugs = new ArrayCollection();
  464. }
  465. }
  466. Whenever an entity is recreated from the database, an Collection
  467. implementation of the type Doctrine is injected into your entity
  468. instead of an array. Compared to the ArrayCollection this
  469. implementation helps the Doctrine ORM understand the changes that
  470. have happened to the collection which are noteworthy for
  471. persistence.
  472. .. warning::
  473. Lazy load proxies always contain an instance of
  474. Doctrine's EntityManager and all its dependencies. Therefore a
  475. var\_dump() will possibly dump a very large recursive structure
  476. which is impossible to render and read. You have to use
  477. ``Doctrine\Common\Util\Debug::dump()`` to restrict the dumping to a
  478. human readable level. Additionally you should be aware that dumping
  479. the EntityManager to a Browser may take several minutes, and the
  480. Debug::dump() method just ignores any occurrences of it in Proxy
  481. instances.
  482. Because we only work with collections for the references we must be
  483. careful to implement a bidirectional reference in the domain model.
  484. The concept of owning or inverse side of a relation is central to
  485. this notion and should always be kept in mind. The following
  486. assumptions are made about relations and have to be followed to be
  487. able to work with Doctrine 2. These assumptions are not unique to
  488. Doctrine 2 but are best practices in handling database relations
  489. and Object-Relational Mapping.
  490. - Changes to Collections are saved or updated, when the entity on
  491. the *owning* side of the collection is saved or updated.
  492. - Saving an Entity at the inverse side of a relation never
  493. triggers a persist operation to changes to the collection.
  494. - In a one-to-one relation the entity holding the foreign key of
  495. the related entity on its own database table is *always* the owning
  496. side of the relation.
  497. - In a many-to-many relation, both sides can be the owning side of
  498. the relation. However in a bi-directional many-to-many relation
  499. only one is allowed to be.
  500. - In a many-to-one relation the Many-side is the owning side by
  501. default, because it holds the foreign key.
  502. - The OneToMany side of a relation is inverse by default, since
  503. the foreign key is saved on the Many side. A OneToMany relation can
  504. only be the owning side, if its implemented using a ManyToMany
  505. relation with join table and restricting the one side to allow only
  506. UNIQUE values per database constraint.
  507. .. note::
  508. Consistency of bi-directional references on the inverse side of a
  509. relation have to be managed in userland application code. Doctrine
  510. cannot magically update your collections to be consistent.
  511. In the case of Users and Bugs we have references back and forth to
  512. the assigned and reported bugs from a user, making this relation
  513. bi-directional. We have to change the code to ensure consistency of
  514. the bi-directional reference:
  515. .. code-block:: php
  516. <?php
  517. // src/Bug.php
  518. class Bug
  519. {
  520. // ... (previous code)
  521. protected $engineer;
  522. protected $reporter;
  523. public function setEngineer($engineer)
  524. {
  525. $engineer->assignedToBug($this);
  526. $this->engineer = $engineer;
  527. }
  528. public function setReporter($reporter)
  529. {
  530. $reporter->addReportedBug($this);
  531. $this->reporter = $reporter;
  532. }
  533. public function getEngineer()
  534. {
  535. return $this->engineer;
  536. }
  537. public function getReporter()
  538. {
  539. return $this->reporter;
  540. }
  541. }
  542. .. code-block:: php
  543. <?php
  544. // src/User.php
  545. class User
  546. {
  547. // ... (previous code)
  548. protected $reportedBugs = null;
  549. protected $assignedBugs = null;
  550. public function addReportedBug($bug)
  551. {
  552. $this->reportedBugs[] = $bug;
  553. }
  554. public function assignedToBug($bug)
  555. {
  556. $this->assignedBugs[] = $bug;
  557. }
  558. }
  559. I chose to name the inverse methods in past-tense, which should
  560. indicate that the actual assigning has already taken place and the
  561. methods are only used for ensuring consistency of the references.
  562. This approach is my personal preference, you can choose whatever
  563. method to make this work.
  564. You can see from ``User::addReportedBug()`` and
  565. ``User::assignedToBug()`` that using this method in userland alone
  566. would not add the Bug to the collection of the owning side in
  567. ``Bug::$reporter`` or ``Bug::$engineer``. Using these methods and
  568. calling Doctrine for persistence would not update the collections
  569. representation in the database.
  570. Only using ``Bug::setEngineer()`` or ``Bug::setReporter()``
  571. correctly saves the relation information. We also set both
  572. collection instance variables to protected, however with PHP 5.3's
  573. new features Doctrine is still able to use Reflection to set and
  574. get values from protected and private properties.
  575. The ``Bug::$reporter`` and ``Bug::$engineer`` properties are
  576. Many-To-One relations, which point to a User. In a normalized
  577. relational model the foreign key is saved on the Bug's table, hence
  578. in our object-relation model the Bug is at the owning side of the
  579. relation. You should always make sure that the use-cases of your
  580. domain model should drive which side is an inverse or owning one in
  581. your Doctrine mapping. In our example, whenever a new bug is saved
  582. or an engineer is assigned to the bug, we don't want to update the
  583. User to persist the reference, but the Bug. This is the case with
  584. the Bug being at the owning side of the relation.
  585. Bugs reference Products by an uni-directional ManyToMany relation in
  586. the database that points from Bugs to Products.
  587. .. code-block:: php
  588. <?php
  589. // src/Bug.php
  590. class Bug
  591. {
  592. // ... (previous code)
  593. protected $products = null;
  594. public function assignToProduct($product)
  595. {
  596. $this->products[] = $product;
  597. }
  598. public function getProducts()
  599. {
  600. return $this->products;
  601. }
  602. }
  603. We are now finished with the domain model given the requirements.
  604. Now we continue adding metadata mappings for the ``User`` and ``Bug``
  605. as we did for the ``Product`` before:
  606. .. configuration-block::
  607. .. code-block:: php
  608. <?php
  609. // src/Bug.php
  610. /**
  611. * @Entity @Table(name="bugs")
  612. **/
  613. class Bug
  614. {
  615. /**
  616. * @Id @Column(type="integer") @GeneratedValue
  617. **/
  618. protected $id;
  619. /**
  620. * @Column(type="string")
  621. **/
  622. protected $description;
  623. /**
  624. * @Column(type="datetime")
  625. **/
  626. protected $created;
  627. /**
  628. * @Column(type="string")
  629. **/
  630. protected $status;
  631. /**
  632. * @ManyToOne(targetEntity="User", inversedBy="assignedBugs")
  633. **/
  634. protected $engineer;
  635. /**
  636. * @ManyToOne(targetEntity="User", inversedBy="reportedBugs")
  637. **/
  638. protected $reporter;
  639. /**
  640. * @ManyToMany(targetEntity="Product")
  641. **/
  642. protected $products;
  643. // ... (other code)
  644. }
  645. .. code-block:: xml
  646. <!-- config/xml/Bug.dcm.xml -->
  647. <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
  648. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  649. xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
  650. http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
  651. <entity name="Bug" table="bugs">
  652. <id name="id" type="integer">
  653. <generator strategy="AUTO" />
  654. </id>
  655. <field name="description" type="text" />
  656. <field name="created" type="datetime" />
  657. <field name="status" type="string" />
  658. <many-to-one target-entity="User" field="reporter" inversed-by="reportedBugs" />
  659. <many-to-one target-entity="User" field="engineer" inversed-by="assignedBugs" />
  660. <many-to-many target-entity="Product" field="products" />
  661. </entity>
  662. </doctrine-mapping>
  663. .. code-block:: yaml
  664. # config/yaml/Bug.dcm.yml
  665. Bug:
  666. type: entity
  667. table: bugs
  668. id:
  669. id:
  670. type: integer
  671. generator:
  672. strategy: AUTO
  673. fields:
  674. description:
  675. type: text
  676. created:
  677. type: datetime
  678. status:
  679. type: string
  680. manyToOne:
  681. reporter:
  682. targetEntity: User
  683. inversedBy: reportedBugs
  684. engineer:
  685. targetEntity: User
  686. inversedBy: assignedBugs
  687. manyToMany:
  688. products:
  689. targetEntity: Product
  690. Here we have the entity, id and primitive type definitions.
  691. The column names are used from the Zend\_Db\_Table examples and
  692. have different names than the properties on the Bug class.
  693. Additionally for the "created" field it is specified that it is of
  694. the Type "DATETIME", which translates the YYYY-mm-dd HH:mm:ss
  695. Database format into a PHP DateTime instance and back.
  696. After the field definitions the two qualified references to the
  697. user entity are defined. They are created by the ``many-to-one``
  698. tag. The class name of the related entity has to be specified with
  699. the ``target-entity`` attribute, which is enough information for
  700. the database mapper to access the foreign-table. Since
  701. ``reporter`` and ``engineer`` are on the owning side of a
  702. bi-directional relation we also have to specify the ``inversed-by``
  703. attribute. They have to point to the field names on the inverse
  704. side of the relationship. We will see in the next example that the ``inversed-by``
  705. attribute has a counterpart ``mapped-by`` which makes that
  706. the inverse side.
  707. The last missing property is the ``Bug::$products`` collection. It
  708. holds all products where the specific bug is occurring in. Again
  709. you have to define the ``target-entity`` and ``field`` attributes
  710. on the ``many-to-many`` tag. Furthermore you have to specify the
  711. details of the many-to-many join-table and its foreign key columns.
  712. The definition is rather complex, however relying on the XML
  713. auto-completion I got it working easily, although I forget the
  714. schema details all the time.
  715. The last missing definition is that of the User entity:
  716. .. configuration-block::
  717. .. code-block:: php
  718. <?php
  719. // src/User.php
  720. /**
  721. * @Entity @Table(name="users")
  722. **/
  723. class User
  724. {
  725. /**
  726. * @Id @GeneratedValue @Column(type="integer")
  727. * @var int
  728. **/
  729. protected $id;
  730. /**
  731. * @Column(type="string")
  732. * @var string
  733. **/
  734. protected $name;
  735. /**
  736. * @OneToMany(targetEntity="Bug", mappedBy="reporter")
  737. * @var Bug[]
  738. **/
  739. protected $reportedBugs = null;
  740. /**
  741. * @OneToMany(targetEntity="Bug", mappedBy="engineer")
  742. * @var Bug[]
  743. **/
  744. protected $assignedBugs = null;
  745. // .. (other code)
  746. }
  747. .. code-block:: xml
  748. <!-- config/xml/User.dcm.xml -->
  749. <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
  750. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  751. xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
  752. http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
  753. <entity name="User" table="users">
  754. <id name="id" type="integer">
  755. <generator strategy="AUTO" />
  756. </id>
  757. <field name="name" type="string" />
  758. <one-to-many target-entity="Bug" field="reportedBugs" mapped-by="reporter" />
  759. <one-to-many target-entity="Bug" field="assignedBugs" mapped-by="engineer" />
  760. </entity>
  761. </doctrine-mapping>
  762. .. code-block:: yaml
  763. # config/xml/User.dcm.yml
  764. User:
  765. type: entity
  766. table: users
  767. id:
  768. id:
  769. type: integer
  770. generator:
  771. strategy: AUTO
  772. fields:
  773. name:
  774. type: string
  775. oneToMany:
  776. reportedBugs:
  777. targetEntity: Bug
  778. mappedBy: reporter
  779. assignedBugs:
  780. targetEntity: Bug
  781. mappedBy: engineer
  782. Here are some new things to mention about the ``one-to-many`` tags.
  783. Remember that we discussed about the inverse and owning side. Now
  784. both reportedBugs and assignedBugs are inverse relations, which
  785. means the join details have already been defined on the owning
  786. side. Therefore we only have to specify the property on the Bug
  787. class that holds the owning sides.
  788. This example has a fair overview of the most basic features of the
  789. metadata definition language.
  790. Implementing more Requirements
  791. ------------------------------
  792. For starters we need a create user entities:
  793. .. code-block:: php
  794. <?php
  795. // create_user.php
  796. require_once "bootstrap.php";
  797. $newUsername = $argv[1];
  798. $user = new User();
  799. $user->setName($newUsername);
  800. $entityManager->persist($user);
  801. $entityManager->flush();
  802. echo "Created User with ID " . $user->getId() . "\n";
  803. Now call:
  804. ::
  805. $ php create_user.php beberlei
  806. We now have the data to create a bug and the code for this scenario may look
  807. like this:
  808. .. code-block:: php
  809. <?php
  810. // create_bug.php
  811. require_once "bootstrap.php";
  812. $theReporterId = $argv[1];
  813. $theDefaultEngineerId = $argv[1];
  814. $productIds = explode(",", $argv[3]);
  815. $reporter = $entityManager->find("User", $theReporterId);
  816. $engineer = $entityManager->find("User", $theDefaultEngineerId);
  817. if (!$reporter || !$engineer) {
  818. echo "No reporter and/or engineer found for the input.\n";
  819. exit(1);
  820. }
  821. $bug = new Bug();
  822. $bug->setDescription("Something does not work!");
  823. $bug->setCreated(new DateTime("now"));
  824. $bug->setStatus("OPEN");
  825. foreach ($productIds AS $productId) {
  826. $product = $entityManager->find("Product", $productId);
  827. $bug->assignToProduct($product);
  828. }
  829. $bug->setReporter($reporter);
  830. $bug->setEngineer($engineer);
  831. $entityManager->persist($bug);
  832. $entityManager->flush();
  833. echo "Your new Bug Id: ".$bug->getId()."\n";
  834. Since we only have one user and product, probably with the ID of 1, we can call this script with:
  835. ::
  836. php create_bug.php 1 1 1
  837. This is the first contact with the read API of the EntityManager,
  838. showing that a call to ``EntityManager#find($name, $id)`` returns a
  839. single instance of an entity queried by primary key. Besides this
  840. we see the persist + flush pattern again to save the Bug into the
  841. database.
  842. See how simple relating Bug, Reporter, Engineer and Products is
  843. done by using the discussed methods in the "A first prototype"
  844. section. The UnitOfWork will detect this relations when flush is
  845. called and relate them in the database appropriately.
  846. Queries for Application Use-Cases
  847. ---------------------------------
  848. List of Bugs
  849. ~~~~~~~~~~~~
  850. Using the previous examples we can fill up the database quite a
  851. bit, however we now need to discuss how to query the underlying
  852. mapper for the required view representations. When opening the
  853. application, bugs can be paginated through a list-view, which is
  854. the first read-only use-case:
  855. .. code-block:: php
  856. <?php
  857. // list_bugs.php
  858. require_once "bootstrap.php";
  859. $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ORDER BY b.created DESC";
  860. $query = $entityManager->createQuery($dql);
  861. $query->setMaxResults(30);
  862. $bugs = $query->getResult();
  863. foreach($bugs AS $bug) {
  864. echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n";
  865. echo " Reported by: ".$bug->getReporter()->getName()."\n";
  866. echo " Assigned to: ".$bug->getEngineer()->getName()."\n";
  867. foreach($bug->getProducts() AS $product) {
  868. echo " Platform: ".$product->getName()."\n";
  869. }
  870. echo "\n";
  871. }
  872. The DQL Query in this example fetches the 30 most recent bugs with
  873. their respective engineer and reporter in one single SQL statement.
  874. The console output of this script is then:
  875. ::
  876. Something does not work! - 02.04.2010
  877. Reported by: beberlei
  878. Assigned to: beberlei
  879. Platform: My Product
  880. .. note::
  881. **Dql is not Sql**
  882. You may wonder why we start writing SQL at the beginning of this
  883. use-case. Don't we use an ORM to get rid of all the endless
  884. hand-writing of SQL? Doctrine introduces DQL which is best
  885. described as **object-query-language** and is a dialect of
  886. `OQL <http://en.wikipedia.org/wiki/Object_Query_Language>`_ and
  887. similar to `HQL <http://www.hibernate.org>`_ or
  888. `JPQL <http://en.wikipedia.org/wiki/Java_Persistence_Query_Language>`_.
  889. It does not know the concept of columns and tables, but only those
  890. of Entity-Class and property. Using the Metadata we defined before
  891. it allows for very short distinctive and powerful queries.
  892. An important reason why DQL is favourable to the Query API of most
  893. ORMs is its similarity to SQL. The DQL language allows query
  894. constructs that most ORMs don't, GROUP BY even with HAVING,
  895. Sub-selects, Fetch-Joins of nested classes, mixed results with
  896. entities and scalar data such as COUNT() results and much more.
  897. Using DQL you should seldom come to the point where you want to
  898. throw your ORM into the dumpster, because it doesn't support some
  899. the more powerful SQL concepts.
  900. Besides handwriting DQL you can however also use the
  901. ``QueryBuilder`` retrieved by calling
  902. ``$entityManager->createQueryBuilder()`` which is a Query Object
  903. around the DQL language.
  904. As a last resort you can however also use Native SQL and a
  905. description of the result set to retrieve entities from the
  906. database. DQL boils down to a Native SQL statement and a
  907. ``ResultSetMapping`` instance itself. Using Native SQL you could
  908. even use stored procedures for data retrieval, or make use of
  909. advanced non-portable database queries like PostgreSql's recursive
  910. queries.
  911. Array Hydration of the Bug List
  912. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  913. In the previous use-case we retrieved the result as their
  914. respective object instances. We are not limited to retrieving
  915. objects only from Doctrine however. For a simple list view like the
  916. previous one we only need read access to our entities and can
  917. switch the hydration from objects to simple PHP arrays instead.
  918. This can obviously yield considerable performance benefits for
  919. read-only requests.
  920. Implementing the same list view as before using array hydration we
  921. can rewrite our code:
  922. .. code-block:: php
  923. <?php
  924. // list_bugs_array.php
  925. require_once "bootstrap.php";
  926. $dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ".
  927. "JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC";
  928. $query = $entityManager->createQuery($dql);
  929. $bugs = $query->getArrayResult();
  930. foreach ($bugs AS $bug) {
  931. echo $bug['description'] . " - " . $bug['created']->format('d.m.Y')."\n";
  932. echo " Reported by: ".$bug['reporter']['name']."\n";
  933. echo " Assigned to: ".$bug['engineer']['name']."\n";
  934. foreach($bug['products'] AS $product) {
  935. echo " Platform: ".$product['name']."\n";
  936. }
  937. echo "\n";
  938. }
  939. There is one significant difference in the DQL query however, we
  940. have to add an additional fetch-join for the products connected to
  941. a bug. The resulting SQL query for this single select statement is
  942. pretty large, however still more efficient to retrieve compared to
  943. hydrating objects.
  944. Find by Primary Key
  945. ~~~~~~~~~~~~~~~~~~~
  946. The next Use-Case is displaying a Bug by primary key. This could be
  947. done using DQL as in the previous example with a where clause,
  948. however there is a convenience method on the ``EntityManager`` that
  949. handles loading by primary key, which we have already seen in the
  950. write scenarios:
  951. .. code-block:: php
  952. <?php
  953. // show_bug.php
  954. require_once "bootstrap.php";
  955. $theBugId = $argv[1];
  956. $bug = $entityManager->find("Bug", (int)$theBugId);
  957. echo "Bug: ".$bug->getDescription()."\n";
  958. echo "Engineer: ".$bug->getEngineer()->getName()."\n";
  959. The output of the engineers name is fetched from the database! What is happening?
  960. Since we only retrieved the bug by primary key both the engineer and reporter
  961. are not immediately loaded from the database but are replaced by LazyLoading
  962. proxies. These proxies will load behind the scenes, when the first method
  963. is called on them.
  964. Sample code of this proxy generated code can be found in the specified Proxy
  965. Directory, it looks like:
  966. .. code-block:: php
  967. <?php
  968. namespace MyProject\Proxies;
  969. /**
  970. * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
  971. **/
  972. class UserProxy extends \User implements \Doctrine\ORM\Proxy\Proxy
  973. {
  974. // .. lazy load code here
  975. public function addReportedBug($bug)
  976. {
  977. $this->_load();
  978. return parent::addReportedBug($bug);
  979. }
  980. public function assignedToBug($bug)
  981. {
  982. $this->_load();
  983. return parent::assignedToBug($bug);
  984. }
  985. }
  986. See how upon each method call the proxy is lazily loaded from the
  987. database?
  988. The call prints:
  989. ::
  990. $ php show_bug.php 1
  991. Bug: Something does not work!
  992. Engineer: beberlei
  993. Dashboard of the User
  994. ---------------------
  995. For the next use-case we want to retrieve the dashboard view, a
  996. list of all open bugs the user reported or was assigned to. This
  997. will be achieved using DQL again, this time with some WHERE clauses
  998. and usage of bound parameters:
  999. .. code-block:: php
  1000. <?php
  1001. // dashboard.php
  1002. require_once "bootstrap.php";
  1003. $theUserId = $argv[1];
  1004. $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ".
  1005. "WHERE b.status = 'OPEN' AND (e.id = ?1 OR r.id = ?1) ORDER BY b.created DESC";
  1006. $myBugs = $entityManager->createQuery($dql)
  1007. ->setParameter(1, $theUserId)
  1008. ->setMaxResults(15)
  1009. ->getResult();
  1010. echo "You have created or assigned to " . count($myBugs) . " open bugs:\n\n";
  1011. foreach ($myBugs AS $bug) {
  1012. echo $bug->getId() . " - " . $bug->getDescription()."\n";
  1013. }
  1014. Number of Bugs
  1015. --------------
  1016. Until now we only retrieved entities or their array representation.
  1017. Doctrine also supports the retrieval of non-entities through DQL.
  1018. These values are called "scalar result values" and may even be
  1019. aggregate values using COUNT, SUM, MIN, MAX or AVG functions.
  1020. We will need this knowledge to retrieve the number of open bugs
  1021. grouped by product:
  1022. .. code-block:: php
  1023. <?php
  1024. // products.php
  1025. require_once "bootstrap.php";
  1026. $dql = "SELECT p.id, p.name, count(b.id) AS openBugs FROM Bug b ".
  1027. "JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id";
  1028. $productBugs = $entityManager->createQuery($dql)->getScalarResult();
  1029. foreach($productBugs as $productBug) {
  1030. echo $productBug['name']." has " . $productBug['openBugs'] . " open bugs!\n";
  1031. }
  1032. Updating Entities
  1033. -----------------
  1034. There is a single use-case missing from the requirements, Engineers
  1035. should be able to close a bug. This looks like:
  1036. .. code-block:: php
  1037. <?php
  1038. // src/Bug.php
  1039. class Bug
  1040. {
  1041. public function close()
  1042. {
  1043. $this->status = "CLOSE";
  1044. }
  1045. }
  1046. .. code-block:: php
  1047. <?php
  1048. // close_bug.php
  1049. require_once "bootstrap.php";
  1050. $theBugId = $argv[1];
  1051. $bug = $entityManager->find("Bug", (int)$theBugId);
  1052. $bug->close();
  1053. $entityManager->flush();
  1054. When retrieving the Bug from the database it is inserted into the
  1055. IdentityMap inside the UnitOfWork of Doctrine. This means your Bug
  1056. with exactly this id can only exist once during the whole request
  1057. no matter how often you call ``EntityManager#find()``. It even
  1058. detects entities that are hydrated using DQL and are already
  1059. present in the Identity Map.
  1060. When flush is called the EntityManager loops over all the entities
  1061. in the identity map and performs a comparison between the values
  1062. originally retrieved from the database and those values the entity
  1063. currently has. If at least one of these properties is different the
  1064. entity is scheduled for an UPDATE against the database. Only the
  1065. changed columns are updated, which offers a pretty good performance
  1066. improvement compared to updating all the properties.
  1067. Entity Repositories
  1068. -------------------
  1069. For now we have not discussed how to separate the Doctrine query logic from your model.
  1070. In Doctrine 1 there was the concept of ``Doctrine_Table`` instances for this
  1071. separation. The similar concept in Doctrine2 is called Entity Repositories, integrating
  1072. the `repository pattern <http://martinfowler.com/eaaCatalog/repository.html>`_ at the heart of Doctrine.
  1073. Every Entity uses a default repository by default and offers a bunch of convenience
  1074. methods that you can use to query for instances of that Entity. Take for example
  1075. our Product entity. If we wanted to Query by name, we can use:
  1076. .. code-block:: php
  1077. <?php
  1078. $product = $entityManager->getRepository('Product')
  1079. ->findOneBy(array('name' => $productName));
  1080. The method ``findOneBy()`` takes an array of fields or association keys and the values to match against.
  1081. If you want to find all entities matching a condition you can use ``findBy()``, for
  1082. example querying for all closed bugs:
  1083. .. code-block:: php
  1084. <?php
  1085. $bugs = $entityManager->getRepository('Bug')
  1086. ->findBy(array('status' => 'CLOSED'));
  1087. foreach ($bugs AS $bug) {
  1088. // do stuff
  1089. }
  1090. Compared to DQL these query methods are falling short of functionality very fast.
  1091. Doctrine offers you a convenient way to extend the functionalities of the default ``EntityRepository``
  1092. and put all the specialized DQL query logic on it. For this you have to create a subclass
  1093. of ``Doctrine\ORM\EntityRepository``, in our case a ``BugRepository`` and group all
  1094. the previously discussed query functionality in it:
  1095. .. code-block:: php
  1096. <?php
  1097. // src/BugRepository.php
  1098. use Doctrine\ORM\EntityRepository;
  1099. class BugRepository extends EntityRepository
  1100. {
  1101. public function getRecentBugs($number = 30)
  1102. {
  1103. $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ORDER BY b.created DESC";
  1104. $query = $this->getEntityManager()->createQuery($dql);
  1105. $query->setMaxResults($number);
  1106. return $query->getResult();
  1107. }
  1108. public function getRecentBugsArray($number = 30)
  1109. {
  1110. $dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ".
  1111. "JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC";
  1112. $query = $this->getEntityManager()->createQuery($dql);
  1113. $query->setMaxResults($number);
  1114. return $query->getArrayResult();
  1115. }
  1116. public function getUsersBugs($userId, $number = 15)
  1117. {
  1118. $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ".
  1119. "WHERE b.status = 'OPEN' AND e.id = ?1 OR r.id = ?1 ORDER BY b.created DESC";
  1120. return $this->getEntityManager()->createQuery($dql)
  1121. ->setParameter(1, $userId)
  1122. ->setMaxResults($number)
  1123. ->getResult();
  1124. }
  1125. public function getOpenBugsByProduct()
  1126. {
  1127. $dql = "SELECT p.id, p.name, count(b.id) AS openBugs FROM Bug b ".
  1128. "JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id";
  1129. return $this->getEntityManager()->createQuery($dql)->getScalarResult();
  1130. }
  1131. }
  1132. Don't forget to add a `require_once` call for this class to the bootstrap.php
  1133. To be able to use this query logic through ``$this->getEntityManager()->getRepository('Bug')``
  1134. we have to adjust the metadata slightly.
  1135. .. configuration-block::
  1136. .. code-block:: php
  1137. <?php
  1138. /**
  1139. * @Entity(repositoryClass="BugRepository")
  1140. * @Table(name="bugs")
  1141. **/
  1142. class Bug
  1143. {
  1144. //...
  1145. }
  1146. .. code-block:: xml
  1147. <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
  1148. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  1149. xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
  1150. http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
  1151. <entity name="Bug" table="bugs" repository-class="BugRepository">
  1152. </entity>
  1153. </doctrine-mapping>
  1154. .. code-block:: yaml
  1155. Bug:
  1156. type: entity
  1157. repositoryClass: BugRepository
  1158. Now we can remove our query logic in all the places and instead use them through the EntityRepository.
  1159. As an example here is the code of the first use case "List of Bugs":
  1160. .. code-block:: php
  1161. <?php
  1162. // list_bugs_repository.php
  1163. require_once "bootstrap.php";
  1164. $bugs = $entityManager->getRepository('Bug')->getRecentBugs();
  1165. foreach($bugs AS $bug) {
  1166. echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n";
  1167. echo " Reported by: ".$bug->getReporter()->getName()."\n";
  1168. echo " Assigned to: ".$bug->getEngineer()->getName()."\n";
  1169. foreach($bug->getProducts() AS $product) {
  1170. echo " Platform: ".$product->getName()."\n";
  1171. }
  1172. echo "\n";
  1173. }
  1174. Using EntityRepositories you can avoid coupling your model with specific query logic.
  1175. You can also re-use query logic easily throughout your application.
  1176. Conclusion
  1177. ----------
  1178. This tutorial is over here, I hope you had fun. Additional content
  1179. will be added to this tutorial incrementally, topics will include:
  1180. - More on Association Mappings
  1181. - Lifecycle Events triggered in the UnitOfWork
  1182. - Ordering of Collections
  1183. Additional details on all the topics discussed here can be found in
  1184. the respective manual chapters.