From afbed0d24bfd35ca3948cb7104d12a2aba655b84 Mon Sep 17 00:00:00 2001 From: Talita Kocjan Zager Date: Fri, 18 Mar 2016 19:37:50 +0100 Subject: [PATCH 1/3] doctrine ch. review --- book/doctrine.rst | 810 +++++++++++++++++++++++++++++----------------- 1 file changed, 507 insertions(+), 303 deletions(-) diff --git a/book/doctrine.rst b/book/doctrine.rst index 6287f49ecdb..690e43f5909 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -6,7 +6,7 @@ Databases and Doctrine One of the most common and challenging tasks for any application involves persisting and reading information to and from a database. Although -the Symfony full-stack Framework doesn't integrate any ORM by default, +the Symfony full-stack Framework doesn't integrate any `ORM`_ by default, the Symfony Standard Edition, which is the most widely used distribution, comes integrated with `Doctrine`_, a library whose sole goal is to give you powerful tools to make this easy. In this chapter, you'll learn the @@ -18,22 +18,17 @@ can be. Doctrine is totally decoupled from Symfony and using it is optional. This chapter is all about the Doctrine ORM, which aims to let you map objects to a relational database (such as *MySQL*, *PostgreSQL* or - *Microsoft SQL*). If you prefer to use raw database queries, this is - easy, and explained in the ":doc:`/cookbook/doctrine/dbal`" cookbook entry. + *Microsoft SQL*). You can also persist data to document-oriented databases + like `MongoDB`_ using Doctrine ODM library. For more information, read the + `DoctrineMongoDBBundle`_ documentation. If you prefer to use raw database + queries, this is easy, and explained in the cookbook article + :doc:`/cookbook/doctrine/dbal`. - You can also persist data to `MongoDB`_ using Doctrine ODM library. For - more information, read the "`DoctrineMongoDBBundle`_" - documentation. - -A Simple Example: A Product ---------------------------- +.. index:: + single: Doctrine; Database configuration -The easiest way to understand how Doctrine works is to see it in action. -In this section, you'll configure your database, create a ``Product`` object, -persist it to the database and fetch it back out. - -Configuring the Database -~~~~~~~~~~~~~~~~~~~~~~~~ +Database Configuration +---------------------- Before you really begin, you'll need to configure your database connection information. By convention, this information is usually configured in an @@ -51,92 +46,142 @@ information. By convention, this information is usually configured in an # ... -.. note:: - - Defining the configuration via ``parameters.yml`` is just a convention. - The parameters defined in that file are referenced by the main configuration - file when setting up Doctrine: - - .. configuration-block:: - - .. code-block:: yaml +Defining the configuration via ``parameters.yml`` is just a convention. +The parameters defined in that file are referenced by the default configuration +file when setting up Doctrine: - # app/config/config.yml - doctrine: - dbal: - driver: '%database_driver%' - host: '%database_host%' - dbname: '%database_name%' - user: '%database_user%' - password: '%database_password%' +.. configuration-block:: - .. code-block:: xml + .. code-block:: yaml - - - + # app/config/config.yml + doctrine: + dbal: + driver: '%database_driver%' + host: '%database_host%' + dbname: '%database_name%' + user: '%database_user%' + password: '%database_password%' - - - - + .. code-block:: xml - .. code-block:: php + + + + + + + + + + .. code-block:: php + + // app/config/config.php + $configuration->loadFromExtension('doctrine', array( + 'dbal' => array( + 'driver' => '%database_driver%', + 'host' => '%database_host%', + 'dbname' => '%database_name%', + 'user' => '%database_user%', + 'password' => '%database_password%', + ), + )); + +By separating the database information into a separate file, you can +easily keep different versions of the file on each server. You can also +easily store database configuration (or any sensitive information) outside +of your project, like inside your web server configuration, for example. For +more information, see :doc:`/cookbook/configuration/external_parameters` +cookbook article. - // app/config/config.php - $configuration->loadFromExtension('doctrine', array( - 'dbal' => array( - 'driver' => '%database_driver%', - 'host' => '%database_host%', - 'dbname' => '%database_name%', - 'user' => '%database_user%', - 'password' => '%database_password%', - ), - )); +.. index:: + single: Doctrine; Database charset and collation - By separating the database information into a separate file, you can - easily keep different versions of the file on each server. You can also - easily store database configuration (or any sensitive information) outside - of your project, like inside your Apache configuration, for example. For - more information, see :doc:`/cookbook/configuration/external_parameters`. +Database Charset and Collation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Now that Doctrine can connect to your database, the following command -can automatically generate an empty ``test_project`` database for you: +One mistake even seasoned developers make when starting a Symfony project +is forgetting to set up default charset and collation (the way strings are +sorted) on their database, ending up with latin type collations, which are +default for most databases. +They might even remember to do it the very first time, but forget that +it's all gone after running a relatively common command during development: .. code-block:: bash + $ php app/console doctrine:database:drop --force $ php app/console doctrine:database:create -.. sidebar:: Setting up the Database to be UTF8 +There's no way to configure these defaults inside Doctrine, as it tries to be +as agnostic as possible in terms of environment configuration. One way to solve +this problem is to configure server-level defaults. - One mistake even seasoned developers make when starting a Symfony project - is forgetting to set up default charset and collation on their database, - ending up with latin type collations, which are default for most databases. - They might even remember to do it the very first time, but forget that - it's all gone after running a relatively common command during development: +.. sidebar:: Setting up the Database to be UTF8 - .. code-block:: bash + The MySQL server can support multiple character sets. To list the available + character sets, use the MySQL ``SHOW CHARACTER SET`` statement. Any given character + set always has at least one collation. It may have several collations. To + list the collations for a character set, use the ``SHOW COLLATION`` statement. + There are default settings for character sets and collations at four levels: + server, database, table, and column. - $ php app/console doctrine:database:drop --force - $ php app/console doctrine:database:create + These are MySQL default settings: - There's no way to configure these defaults inside Doctrine, as it tries to be - as agnostic as possible in terms of environment configuration. One way to solve - this problem is to configure server-level defaults. + .. code-block:: bash - Setting UTF8 defaults for MySQL is as simple as adding a few lines to - your configuration file (typically ``my.cnf``): + mysql> show variables like 'char%'; + +--------------------------+----------------------------+ + | Variable_name | Value | + +--------------------------+----------------------------+ + | character_set_client | utf8 | + | character_set_connection | utf8 | + | character_set_database | latin1 | + | character_set_filesystem | binary | + | character_set_results | utf8 | + | character_set_server | latin1 | + | character_set_system | utf8 | + | character_sets_dir | /usr/share/mysql/charsets/ | + +--------------------------+----------------------------+ + + mysql> show variables like 'collation%'; + +----------------------+-------------------+ + | Variable_name | Value | + +----------------------+-------------------+ + | collation_connection | utf8_general_ci | + | collation_database | latin1_swedish_ci | + | collation_server | latin1_swedish_ci | + +----------------------+-------------------+ + + * The **server** character set and collation are the values of the + ``character_set_server`` and ``collation_server`` system variables; + * The character set and collation of the default **database** are the + values of the ``character_set_database`` and ``collation_database`` + system variables. + + Additional character set and collation system variables are involved in + handling traffic for the connection between a client and the server. Every + client has connection-related character set and collation system variables. + + * ``character_set_client`` stores the character set in which statements are + sent by the client; + * Server converts statements sent by the client from ``character_set_client`` + to ``character_set_connection``; + * ``character_set_results`` indicates the character set in which the server + returns query results to the client. + + Setting ``utf8`` defaults for MySQL is as simple as adding a few lines to + MySQL configuration file (typically ``my.cnf``) and restarting MySQL: .. code-block:: ini @@ -145,9 +190,60 @@ can automatically generate an empty ``test_project`` database for you: collation-server = utf8mb4_general_ci # Replaces utf8_general_ci character-set-server = utf8mb4 # Replaces utf8 - We recommend against MySQL's ``utf8`` character set, since it does not + These settings apply server-wide and apply as the defaults for databases + created by any application, and for tables created in those databases: + + .. code-block:: bash + + mysql> show variables like 'char%'; + +--------------------------+----------------------------+ + | Variable_name | Value | + +--------------------------+----------------------------+ + | character_set_client | utf8 | + | character_set_connection | utf8 | + | character_set_database | utf8 | + | character_set_filesystem | binary | + | character_set_results | utf8 | + | character_set_server | utf8 | + | character_set_system | utf8 | + | character_sets_dir | /usr/share/mysql/charsets/ | + +--------------------------+----------------------------+ + + mysql> show variables like 'collation%'; + +----------------------+--------------------+ + | Variable_name | Value | + +----------------------+--------------------+ + | collation_connection | utf8_general_ci | + | collation_database | utf8_general_ci | + | collation_server | utf8_general_ci | + +----------------------+--------------------+ + + However, it seems Doctrine specifically defines the encoding and collation + when creating a new tables no matter server-wide defaults - settings on the + database-level are overwritten by the table-specific configurations set by + Doctrine. Therefore we need to set the charset and collation for every table + since there is no global option and Doctrine doesn't respect the setting + defined in MySQL. + + The related options are called "charset" and "collate" and can be set using + all configuration formats, for example, when using annotations:: + + /** + * @ORM\Entity() + * @ORM\Table(name="citizenship", + * options={"collate"="utf8_general_ci", "charset"="utf8"}) + */ + + Symfony recommends against MySQL's ``utf8`` character set, since it does not support 4-byte unicode characters, and strings containing them will be - truncated. This is fixed by the `newer utf8mb4 character set`_. + truncated. This is fixed by the `newer utf8mb4 character set`_. But + before converting your tables from ``utf8`` to ``utf8mb4`` there is + one thing you need to consider. An index in InnoDB always has a maximum size + of 767, bytes regardless of the number of bytes used for a single character. + Consider a column with a maximum of 255 characters, this would result in 765 + bytes when using ``utf8`` but in 1020 bytes when using ``utf8mb4``. If you + need an index on a ``utf8mb4`` column the maximum number of characters is 191 + instead of 255. .. note:: @@ -196,13 +292,44 @@ can automatically generate an empty ``test_project`` database for you: ), )); +.. index:: + single: Doctrine; Database creation + +Database Creation +----------------- + +Now that Doctrine knows about your database, you can have it create for you +using ``doctrine:database:create`` console command: + +.. code-block:: bash + + $ php app/console doctrine:database:create + +.. index:: + single: Doctrine; Configuration + +Configuration +------------- + +Doctrine is highly configurable, though you probably won't ever need to worry +about most of its options. To find out more about configuring Doctrine, see +the Doctrine section of the +:doc:`configuration reference `. + +A Simple Example: A Product +--------------------------- + +The easiest way to understand how Doctrine works is to see it in action. +In this section, you'll configure your database, create a ``Product`` object, +persist it to the database and fetch it back out. + Creating an Entity Class ~~~~~~~~~~~~~~~~~~~~~~~~ Suppose you're building an application where products need to be displayed. Without even thinking about Doctrine or databases, you already know that you need a ``Product`` object to represent those products. Create this class -inside the ``Entity`` directory of your AppBundle:: +inside the ``Entity/`` directory of your AppBundle:: // src/AppBundle/Entity/Product.php namespace AppBundle\Entity; @@ -222,8 +349,8 @@ just a simple PHP class. .. tip:: Once you learn the concepts behind Doctrine, you can have Doctrine create - simple entity classes for you. This will ask you interactive questions - to help you build any entity: + simple entity classes for you. This can be done using ``doctrine:generate:entity`` + console command which asks interactive questions to help build any entity: .. code-block:: bash @@ -234,7 +361,7 @@ just a simple PHP class. .. _book-doctrine-adding-mapping: -Add Mapping Information +Adding Mapping Metadata ~~~~~~~~~~~~~~~~~~~~~~~ Doctrine allows you to work with databases in a much more interesting way @@ -331,29 +458,30 @@ directly inside the ``Product`` class via DocBlock annotations: +.. seealso:: + + You can check out Doctrine's `Basic Mapping documentation`_ for all + details about mapping information. + .. note:: A bundle can accept only one metadata definition format. For example, it's not possible to mix YAML metadata definitions with annotated PHP entity class definitions. -.. tip:: - - The table name is optional and if omitted, will be determined automatically - based on the name of the entity class. +If you use annotations, you'll need to prepend all annotations with ``ORM\`` +(e.g. ``ORM\Column(...)``), which is not shown in Doctrine's documentation. +You'll also need to include the ``use Doctrine\ORM\Mapping as ORM;`` statement, +which *imports* the ``ORM`` annotations prefix. Doctrine allows you to choose from a wide variety of different field types, each with their own options. For information on the available field types, -see the :ref:`book-doctrine-field-types` section. +see the :ref:`book-doctrine-field-types` section of this chapter. -.. seealso:: +.. tip:: - You can also check out Doctrine's `Basic Mapping Documentation`_ for - all details about mapping information. If you use annotations, you'll - need to prepend all annotations with ``ORM\`` (e.g. ``ORM\Column(...)``), - which is not shown in Doctrine's documentation. You'll also need to include - the ``use Doctrine\ORM\Mapping as ORM;`` statement, which *imports* the - ``ORM`` annotations prefix. + The table name is optional and if omitted, will be determined automatically + based on the name of the entity class. .. caution:: @@ -382,6 +510,24 @@ see the :ref:`book-doctrine-field-types` section. class Product // ... +.. index:: + single: Doctrine; Doctrine field types reference + +.. _book-doctrine-field-types: + +Doctrine Field Types Reference +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Doctrine comes with numerous field types available. Each of these +maps a PHP data type to a specific column type in whatever database you're +using. For each field type, the ``Column`` can be configured further, setting +the ``length``, ``nullable`` behavior, ``name`` and other options. To see a +list of all available types and more information, see Doctrine's +`Mapping Types documentation`_. + +.. index:: + single: Doctrine; Generating getters and setters + .. _book-doctrine-generating-getters-and-setters: Generating Getters and Setters @@ -400,36 +546,23 @@ the following command can generate these boilerplate methods automatically: This command makes sure that all the getters and setters are generated for the ``Product`` class. This is a safe command - you can run it over and -over again: it only generates getters and setters that don't exist (i.e. it -doesn't replace your existing methods). +over again: it **only generates getters and setters that don't exist** (i.e. +it doesn't replace your existing methods). -.. caution:: - - Keep in mind that Doctrine's entity generator produces simple getters/setters. - You should review the generated methods and add any logic, if necessary, - to suit the needs of your application. - -.. sidebar:: More about ``doctrine:generate:entities`` - - With the ``doctrine:generate:entities`` command you can: - - * generate getter and setter methods in entity classes; - - * generate repository classes on behalf of entities configured with the - ``@ORM\Entity(repositoryClass="...")`` annotation; - - * generate the appropriate constructor for 1:n and n:m relations. +Keep in mind that Doctrine's entity generator produces simple getters/setters. +You should review the generated methods and add any logic, if necessary, +to suit the needs of your application. - The ``doctrine:generate:entities`` command saves a backup of the original - ``Product.php`` named ``Product.php~``. In some cases, the presence of - this file can cause a "Cannot redeclare class" error. It can be safely - removed. You can also use the ``--no-backup`` option to prevent generating - these backup files. +The ``doctrine:generate:entities`` command saves a backup of the original +``Product.php`` named ``Product.php~``. In some cases, the presence of +this file can cause a "Cannot redeclare class" error. It can be safely +removed. You can also use the ``--no-backup`` option to prevent generating +these backup files. - Note that you don't *need* to use this command. You could also write the - necessary getters and setters by hand. This option simply exists to save - you time, since creating these methods is often a common task during - development. +Note that you don't *need* to use this command. You could also write the +necessary getters and setters by hand. This option simply exists to save +you time, since creating these methods is often a common task during +development. You can also generate all known entities (i.e. any PHP class with Doctrine mapping information) of a bundle or an entire namespace: @@ -442,30 +575,45 @@ mapping information) of a bundle or an entire namespace: # generates all entities of bundles in the Acme namespace $ php app/console doctrine:generate:entities Acme +.. sidebar:: More about ``doctrine:generate:entities`` + + Besides getters and setters ``doctrine:generate:entities`` command can + also generates: + + * the appropriate constructor for 1:n and n:m relations. + + * repository classes on behalf of entities configured with the + ``@ORM\Entity(repositoryClass="...")`` annotation + (see :ref:`book-doctrine-custom-repository-classes` section of this + chapter); + +.. index:: + single: Doctrine; Creating the database tables/schema + single: Doctrine; Updating the database tables/schema + .. _book-doctrine-creating-the-database-tables-schema: -Creating the Database Tables/Schema -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Creating (and updating) the Database Tables/Schema +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You now have a usable ``Product`` class with mapping information so that Doctrine knows exactly how to persist it. Of course, you don't yet have the corresponding ``product`` table in your database. Fortunately, Doctrine can automatically create all the database tables needed for every known entity -in your application. To do this, run: +in your application. This can be done using ``doctrine:schema:update`` console +command: .. code-block:: bash $ php app/console doctrine:schema:update --force -.. tip:: - - Actually, this command is incredibly powerful. It compares what - your database *should* look like (based on the mapping information of - your entities) with how it *actually* looks, and executes the SQL statements - needed to *update* the database schema to where it should be. In other - words, if you add a new property with mapping metadata to ``Product`` - and run this task, it will execute the "ALTER TABLE" statement needed - to add that new column to the existing ``product`` table. +This command is incredibly powerful. It compares what your database *should* +look like (based on the mapping information of your entities) with how it +*actually* looks, and executes the SQL statements needed to *update* the +database schema to where it should be. In other words, if you add a new property with +mapping metadata to ``Product`` and run this task, it will generate the +"ALTER TABLE" statement needed to add that new column to the existing +``product`` table. An even better way to take advantage of this functionality is via `migrations`_, which allow you to generate these SQL statements and store @@ -480,6 +628,9 @@ in your application. To do this, run: Your database now has a fully-functional ``product`` table with columns that match the metadata you've specified. +.. index:: + single: Doctrine; Persisting objects + Persisting Objects to the Database ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -519,54 +670,60 @@ a controller, this is pretty easy. Add the following method to the If you're following along with this example, you'll need to create a route that points to this action to see it work. -.. tip:: - - This article shows working with Doctrine from within a controller by using - the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::getDoctrine` - method of the controller. This method is a shortcut to get the - ``doctrine`` service. You can work with Doctrine anywhere else - by injecting that service in the service. See - :doc:`/book/service_container` for more on creating your own services. +:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::getDoctrine` +method is a method of Symfony base ``Controller`` class. This method is a shortcut to +get the ``doctrine`` service. -Take a look at the previous example in more detail: +Take a look at the example in more detail: -* **lines 10-13** In this section, you instantiate and work with the ``$product`` - object like any other normal PHP object. +* **lines 10-13**: The ``$product`` object is instantiate and worked with like + any other, normal PHP object. -* **line 15** This line fetches Doctrine's *entity manager* object, which is +* **line 15**: This line fetches Doctrine's *entity manager* object, which is responsible for the process of persisting objects to, and fetching objects from, the database. * **line 17** The ``persist($product)`` call tells Doctrine to "manage" the ``$product`` object. This does **not** cause a query to be made to the database. -* **line 18** When the ``flush()`` method is called, Doctrine looks through +* **line 18**: When the ``flush()`` method is called, Doctrine looks through all of the objects that it's managing to see if they need to be persisted to the database. In this example, the ``$product`` object's data doesn't exist in the database, so the entity manager executes an ``INSERT`` query, creating a new row in the ``product`` table. -.. note:: - - In fact, since Doctrine is aware of all your managed entities, when you call - the ``flush()`` method, it calculates an overall changeset and executes - the queries in the correct order. It utilizes cached prepared statement to - slightly improve the performance. For example, if you persist a total of 100 - ``Product`` objects and then subsequently call ``flush()``, Doctrine will - execute 100 ``INSERT`` queries using a single prepared statement object. +In fact, since Doctrine is aware of all your managed entities, when you call +the ``flush()`` method, it calculates an overall changeset and executes +the queries in the correct order. It utilizes cached prepared statement to +slightly improve the performance. For example, if you persist a total of 100 +``Product`` objects and then subsequently call ``flush()``, Doctrine will +execute 100 ``INSERT`` queries using a single prepared statement object. Whether creating or updating objects, the workflow is always the same. In the next section, you'll see how Doctrine is smart enough to automatically issue an ``UPDATE`` query if the entity already exists in the database. -.. tip:: +.. seealso:: Doctrine provides a library that allows you to programmatically load testing data into your project (i.e. "fixture data"). For information, see - the "`DoctrineFixturesBundle`_" documentation. + the "`DoctrineFixturesBundle documentation`_" . -Fetching Objects from the Database -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. seealso:: + + To use Doctrine outside controller classes (which extend Symfony base + ``Controller`` class), for example in your own custom services you have + to inject ``doctrine`` service into the service. To learn about services + and custom services read :doc:`Service container chapter /book/service_container`. + +.. index:: + single: Doctrine; Fetching objects from the database using getRepository() + single: Doctrine; Default finder methods + +.. database-fetching-obj-using-repo: + +Fetching Objects from the Database Using ``getRepository()`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Fetching an object back out of the database is even easier. For example, suppose you've configured a route to display a specific ``Product`` based @@ -604,12 +761,15 @@ repository object for an entity class via:: .. note:: The ``AppBundle:Product`` string is a shortcut you can use anywhere - in Doctrine instead of the full class name of the entity (i.e. ``AppBundle\Entity\Product``). - As long as your entity lives under the ``Entity`` namespace of your bundle, - this will work. + in Doctrine instead of the full class name of the entity (i.e. + ``AppBundle\Entity\Product``). As long as your entity lives under the + ``Entity`` namespace of your bundle, this will work. Once you have a repository object, you can access all sorts of helpful methods:: + $em = $this->getDoctrine()->getManager(); + $repository = $em->getRepository('AppBundle:Product'); + // query for a single product by its primary key (usually "id") $product = $repository->find($productId); @@ -623,13 +783,8 @@ Once you have a repository object, you can access all sorts of helpful methods:: // find *all* products $products = $repository->findAll(); -.. note:: - - Of course, you can also issue complex queries, which you'll learn more - about in the :ref:`book-doctrine-queries` section. - -You can also take advantage of the useful ``findBy`` and ``findOneBy`` methods -to easily fetch objects based on multiple conditions:: +You can also take advantage of the useful ``findBy()`` and ``findOneBy()`` +methods to easily fetch objects based on multiple conditions:: // query for a single product matching the given name and price $product = $repository->findOneBy( @@ -642,6 +797,9 @@ to easily fetch objects based on multiple conditions:: array('price' => 'ASC') ); +Of course, you can also issue complex queries, which you'll learn more +about in the :ref:`book-doctrine-queries` section of this chapter. + .. tip:: When you render any page, you can see how many queries were made in the @@ -658,6 +816,9 @@ to easily fetch objects based on multiple conditions:: The icon will turn yellow if there were more than 50 queries on the page. This could indicate that something is not correct. +.. index:: + single: Doctrine; Updating objects + Updating an Object ~~~~~~~~~~~~~~~~~~ @@ -685,13 +846,16 @@ Updating an object involves just three steps: #. fetching the object from Doctrine; #. modifying the object; -#. calling ``flush()`` on the entity manager +#. calling ``flush()`` on the entity manager. Notice that calling ``$em->persist($product)`` isn't necessary. Recall that this method simply tells Doctrine to manage or "watch" the ``$product`` object. In this case, since you fetched the ``$product`` object from Doctrine, it's already managed. +.. index:: + single: Doctrine; Deleting objects + Deleting an Object ~~~~~~~~~~~~~~~~~~ @@ -705,27 +869,37 @@ As you might expect, the ``remove()`` method notifies Doctrine that you'd like to remove the given object from the database. The actual ``DELETE`` query, however, isn't actually executed until the ``flush()`` method is called. -.. _`book-doctrine-queries`: +.. index:: + single: Doctrine; Querying objects from the database + +.. _book-doctrine-queries: Querying for Objects -------------------- -You've already seen how the repository object allows you to run basic queries -without any work:: +You've already :ref:`seen ` how the +**repository object** allows you to run basic queries without any work:: + $em = $this->getDoctrine()->getManager(); + $repository = $em->getRepository('AppBundle:Product'); $product = $repository->find($productId); $product = $repository->findOneByName('Keyboard'); -Of course, Doctrine also allows you to write more complex queries using the -Doctrine Query Language (DQL). DQL is similar to SQL except that you should -imagine that you're querying for one or more objects of an entity class (e.g. ``Product``) -instead of querying for rows on a table (e.g. ``product``). +Of course, Doctrine also allows you to write more complex queries. +You have two other options: -When querying in Doctrine, you have two options: writing pure Doctrine queries -or using Doctrine's Query Builder. +* writing pure Doctrine queries with DQL; +* using Doctrine's Query Builder. -Querying for Objects with DQL -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Using the **Doctrine Query Language** (DQL). DQL is similar to SQL except that you should +imagine that you're **querying for one or more *objects* of an entity class** +(e.g. ``Product``) **instead of querying for rows on a table** (e.g. ``product``). + +.. index:: + single: Doctrine; Doctrine Query Language (DQL) using createQuery() + +DQL using createQuery() +~~~~~~~~~~~~~~~~~~~~~~~ Imagine that you want to query for products, but only return products that cost more than ``19.99``, ordered from cheapest to most expensive. You can use @@ -741,40 +915,44 @@ Doctrine's native SQL-like language called DQL to make a query for this:: $products = $query->getResult(); +The ``getResult()`` method *returns an array of results*. To get only one +result, you can use ``getOneOrNullResult()``:: + + $product = $query->setMaxResults(1)->getOneOrNullResult(); + +``createQuery()`` method returns a normal ``Query`` object, which can be used +to get the result of the query. + If you're comfortable with SQL, then DQL should feel very natural. The biggest difference is that you need to think in terms of "objects" instead of rows in a database. For this reason, you select *from* the ``AppBundle:Product`` *object* (an optional shortcut for ``AppBundle\Entity\Product``) and then alias it as ``p``. -.. tip:: - - Take note of the ``setParameter()`` method. When working with Doctrine, - it's always a good idea to set any external values as "placeholders" - (``:price`` in the example above) as it prevents SQL injection attacks. - -The ``getResult()`` method returns an array of results. To get only one -result, you can use ``getOneOrNullResult()``:: - - $product = $query->setMaxResults(1)->getOneOrNullResult(); +Take note of the ``setParameter()`` method. When working with Doctrine, +it's always a good idea to set any external values as "placeholders" +(``:price`` in the example above) as it **prevents SQL injection attacks**. The DQL syntax is incredibly powerful, allowing you to easily join between entities (the topic of :ref:`relations ` will be -covered later), group, etc. For more information, see the official -`Doctrine Query Language`_ documentation. +covered later in this chapter), group, etc. For more information, see the +official `Doctrine Query Language documentation`_. + +.. index:: + single: Doctrine; Doctrine's Query Builder -Querying for Objects Using Doctrine's Query Builder +Doctrine's Query Builder using createQueryBuilder() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Instead of writing a DQL string, you can use a helpful object called the -``QueryBuilder`` to build that string for you. This is useful when the actual query -depends on dynamic conditions, as your code soon becomes hard to read with +``QueryBuilder`` to build that string for you. This is useful when the actual +query depends on dynamic conditions, as your code soon becomes hard to read with DQL as you start to concatenate strings:: $repository = $this->getDoctrine() ->getRepository('AppBundle:Product'); - // createQueryBuilder automatically selects FROM AppBundle:Product + // createQueryBuilder() automatically selects FROM AppBundle:Product // and aliases it to "p" $query = $repository->createQueryBuilder('p') ->where('p.price > :price') @@ -783,7 +961,10 @@ DQL as you start to concatenate strings:: ->getQuery(); $products = $query->getResult(); - // to get just one result: + +The ``getResult()`` method *returns an array of results*. To get only one +result, you can use ``getOneOrNullResult()``:: + // $product = $query->setMaxResults(1)->getOneOrNullResult(); The ``QueryBuilder`` object contains every method necessary to build your @@ -791,7 +972,11 @@ query. By calling the ``getQuery()`` method, the query builder returns a normal ``Query`` object, which can be used to get the result of the query. For more information on Doctrine's Query Builder, consult Doctrine's -`Query Builder`_ documentation. +`Query Builder documentation`_. + +.. index:: + single: Doctrine; Custom repository classes + single: Doctrine; Custom finder methods .. _book-doctrine-custom-repository-classes: @@ -799,11 +984,10 @@ Custom Repository Classes ~~~~~~~~~~~~~~~~~~~~~~~~~ In the previous sections, you began constructing and using more complex queries -from inside a controller. In order to isolate, test and reuse these queries, +from *inside a controller*. In order to isolate, test and reuse these queries, it's a good practice to create a custom repository class for your entity and -add methods with your query logic there. - -To do this, add the name of the repository class to your mapping definition: +add methods with your query logic there. To do this, add the name of the +repository class to your mapping definition: .. configuration-block:: @@ -847,18 +1031,17 @@ To do this, add the name of the repository class to your mapping definition: -Doctrine can generate the repository class for you by running the same command -used earlier to generate the missing getter and setter methods: +Doctrine can generate the repository class for you by running the same console +command used :ref:`earlier ` to +generate the missing getter and setter methods: .. code-block:: bash $ php app/console doctrine:generate:entities AppBundle -Next, add a new method - ``findAllOrderedByName()`` - to the newly generated +Next, add a new method called ``findAllOrderedByName()`` to the newly generated repository class. This method will query for all the ``Product`` entities, -ordered alphabetically. - -.. code-block:: php +ordered alphabetically:: // src/AppBundle/Entity/ProductRepository.php namespace AppBundle\Entity; @@ -877,21 +1060,23 @@ ordered alphabetically. } } -.. tip:: - - The entity manager can be accessed via ``$this->getEntityManager()`` - from inside the repository. +Repository class extends Doctrine's ``Doctrine\ORM\EntityRepository`` class +which supplies our repository class with ``getEntityManager()`` method. +Via this method the entity manager can be accessed from inside the repository. -You can use this new method just like the default finder methods of the repository:: +**You can use this new method just like the default finder methods of the +repository**:: $em = $this->getDoctrine()->getManager(); $products = $em->getRepository('AppBundle:Product') ->findAllOrderedByName(); -.. note:: +When using a custom repository class, you still have access to the default +finder methods such as ``find()`` and ``findAll()`` talked about +:ref:`here `. - When using a custom repository class, you still have access to the default - finder methods such as ``find()`` and ``findAll()``. +.. index:: + single: Doctrine; Entity Relationships/Associations .. _`book-doctrine-relations`: @@ -913,6 +1098,9 @@ you can let Doctrine create the class for you. This task generates the ``Category`` entity for you, with an ``id`` field, a ``name`` field and the associated getter and setter functions. +.. index:: + single: Doctrine; Relationship mapping metadata + Relationship Mapping Metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -981,9 +1169,9 @@ To relate the ``Category`` and ``Product`` entities, start by creating a First, since a ``Category`` object will relate to many ``Product`` objects, a ``products`` array property is added to hold those ``Product`` objects. -Again, this isn't done because Doctrine needs it, but instead because it -makes sense in the application for each ``Category`` to hold an array of -``Product`` objects. +Noticed the plural form. Again, this isn't done because Doctrine needs it, +but instead because it makes sense in the application for each ``Category`` +to hold an array of ``Product`` objects. .. note:: @@ -995,13 +1183,14 @@ makes sense in the application for each ``Category`` to hold an array of .. tip:: - The targetEntity value in the decorator used above can reference any entity + The ``targetEntity`` value in the decorator used above can reference any entity with a valid namespace, not just entities defined in the same namespace. To relate to an entity defined in a different class or bundle, enter a full - namespace as the targetEntity. + namespace as the ``targetEntity``. Next, since each ``Product`` class can relate to exactly one ``Category`` -object, you'll want to add a ``$category`` property to the ``Product`` class: +object, you'll want to add a ``$category`` property to the ``Product`` class. +Noticed the singular form.:: .. configuration-block:: @@ -1057,14 +1246,6 @@ object, you'll want to add a ``$category`` property to the ``Product`` class: -Finally, now that you've added a new property to both the ``Category`` and -``Product`` classes, tell Doctrine to generate the missing getter and setter -methods for you: - -.. code-block:: bash - - $ php app/console doctrine:generate:entities AppBundle - Ignore the Doctrine metadata for a moment. You now have two classes - ``Category`` and ``Product`` with a natural one-to-many relationship. The ``Category`` class holds an array of ``Product`` objects and the ``Product`` object can @@ -1100,6 +1281,17 @@ table, and ``product.category_id`` column, and new foreign key: method of systematically updating your production database, read about `migrations`_. +Finally, now that you've added a new property to both the ``Category`` and +``Product`` classes, tell Doctrine to generate the missing getter and setter +methods for you: + +.. code-block:: bash + + $ php app/console doctrine:generate:entities AppBundle + +.. index:: + single: Doctrine; Saving related entities + Saving Related Entities ~~~~~~~~~~~~~~~~~~~~~~~ @@ -1143,6 +1335,14 @@ The ``product.category_id`` column for the new product is set to whatever the ``id`` is of the new category. Doctrine manages the persistence of this relationship for you. +Notice that ``persist()`` method is called twice. Recall that this method +tells Doctrine to manage or "watch" an object. In this case, since we haven't +fetch objects from Doctrine so that they would be already managed but created +new ones, we need to tell Doctrine to manage this new object. + +.. index:: + single: Doctrine; Fetching related entities + Fetching Related Objects ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1156,17 +1356,19 @@ did before. First, fetch a ``$product`` object and then access its related ->getRepository('AppBundle:Product') ->find($productId); - $categoryName = $product->getCategory()->getName(); + $category = $product->getCategory(); + $categoryName = $category->getName(); // ... } In this example, you first query for a ``Product`` object based on the product's ``id``. This issues a query for *just* the product data and hydrates the -``$product`` object with that data. Later, when you call ``$product->getCategory()->getName()``, -Doctrine silently makes a second query to find the ``Category`` that's related -to this ``Product``. It prepares the ``$category`` object and returns it to -you. +``$product`` object with that data. Later, when you call +``$product->getCategory()`` the category data isn't actually retrieved until you +ask for it with ``$category->getName()``. At this point Doctrine silently makes +a second query to find the ``Category`` that's related to this ``Product``. It +prepares the ``$category`` object and returns it to you. .. image:: /images/book/doctrine_image_3.png :align: center @@ -1194,33 +1396,40 @@ objects, but only once/if you ask for them (i.e. when you call ``->getProducts() The ``$products`` variable is an array of all ``Product`` objects that relate to the given ``Category`` object via their ``category_id`` value. -.. sidebar:: Relationships and Proxy Classes +.. index:: + single: Doctrine; Relationships, lazy loading and proxy classes - This "lazy loading" is possible because, when necessary, Doctrine returns - a "proxy" object in place of the true object. Look again at the above - example:: +Relationships, Lazy Loading and Proxy Classes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - $product = $this->getDoctrine() - ->getRepository('AppBundle:Product') - ->find($productId); +This "lazy loading" is possible because, when necessary, Doctrine returns +a "proxy" object in place of the true object. Look again at the above +example:: - $category = $product->getCategory(); + $product = $this->getDoctrine() + ->getRepository('AppBundle:Product') + ->find($productId); + + $category = $product->getCategory(); + + // prints "Proxies\AppBundleEntityCategoryProxy" + var_dump(get_class($category)); - // prints "Proxies\AppBundleEntityCategoryProxy" - var_dump(get_class($category)); +This proxy object extends the true ``Category`` object, and looks and +acts exactly like it. The difference is that, by using a proxy object, +Doctrine can delay querying for the real ``Category`` data until you +actually need that data (e.g. until you call ``$category->getName()``). - This proxy object extends the true ``Category`` object, and looks and - acts exactly like it. The difference is that, by using a proxy object, - Doctrine can delay querying for the real ``Category`` data until you - actually need that data (e.g. until you call ``$category->getName()``). +The proxy classes are generated by Doctrine and stored in the cache directory. +And though you'll probably never even notice that your ``$category`` +object is actually a proxy object, it's important to keep it in mind. - The proxy classes are generated by Doctrine and stored in the cache directory. - And though you'll probably never even notice that your ``$category`` - object is actually a proxy object, it's important to keep it in mind. +In the next section, when you retrieve the product and category data +all at once (via a *join*), Doctrine will return the *true* ``Category`` +object, since nothing needs to be lazily loaded. - In the next section, when you retrieve the product and category data - all at once (via a *join*), Doctrine will return the *true* ``Category`` - object, since nothing needs to be lazily loaded. +.. index:: + single: Doctrine; Joining Related Records Joining Related Records ~~~~~~~~~~~~~~~~~~~~~~~ @@ -1269,13 +1478,16 @@ object and its related ``Category`` with just one query:: // ... } -More Information on Associations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. index:: + single: Doctrine; More information on relationships/associations + +More Information on Relationships/Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This section has been an introduction to one common type of entity relationship, the one-to-many relationship. For more advanced details and examples of how to use other types of relations (e.g. one-to-one, many-to-many), see -Doctrine's `Association Mapping Documentation`_. +Doctrine's `Association Mapping documentation`_. .. note:: @@ -1284,12 +1496,8 @@ Doctrine's `Association Mapping Documentation`_. documentation. You'll also need to include the ``use Doctrine\ORM\Mapping as ORM;`` statement, which *imports* the ``ORM`` annotations prefix. -Configuration -------------- - -Doctrine is highly configurable, though you probably won't ever need to worry -about most of its options. To find out more about configuring Doctrine, see -the Doctrine section of the :doc:`config reference `. +.. index:: + single: Doctrine; Lifecycle callbacks Lifecycle Callbacks ------------------- @@ -1301,7 +1509,7 @@ stages of the lifecycle of an entity (e.g. the entity is inserted, updated, deleted, etc). If you're using annotations for your metadata, start by enabling the lifecycle -callbacks. This is not necessary if you're using YAML or XML for your mapping. +callbacks. This is not necessary if you're using YAML or XML for your mapping: .. code-block:: php-annotations @@ -1381,19 +1589,8 @@ Doctrine's `Lifecycle Events documentation`_. If you need to do some heavier lifting - like performing logging or sending an email - you should register an external class as an event listener or subscriber and give it access to whatever resources you need. For - more information, see :doc:`/cookbook/doctrine/event_listeners_subscribers`. - -.. _book-doctrine-field-types: - -Doctrine Field Types Reference ------------------------------- - -Doctrine comes with numerous field types available. Each of these -maps a PHP data type to a specific column type in whatever database you're -using. For each field type, the ``Column`` can be configured further, setting -the ``length``, ``nullable`` behavior, ``name`` and other options. To see a -list of all available types and more information, see Doctrine's -`Mapping Types documentation`_. + more information, see cookbook article + :doc:`/cookbook/doctrine/event_listeners_subscribers`. Summary ------- @@ -1409,30 +1606,37 @@ powerful, allowing you to create complex queries and subscribe to events that allow you to take different actions as objects go through their persistence lifecycle. -Learn more -~~~~~~~~~~ - -For more information about Doctrine, see the *Doctrine* section of the -:doc:`cookbook `. Some useful articles might be: +Learn more from the Cookbook +---------------------------- -* :doc:`/cookbook/doctrine/common_extensions` +* :doc:`/cookbook/doctrine/reverse_engineering` +* :doc:`/cookbook/doctrine/multiple_entity_managers` * :doc:`/cookbook/doctrine/console` -* `DoctrineFixturesBundle`_ -* `DoctrineMongoDBBundle`_ +* :doc:`/cookbook/doctrine/event_listeners_subscribers` +* :doc:`/cookbook/doctrine/file_uploads` +* :doc:`/cookbook/doctrine/common_extensions` +* :doc:`/cookbook/doctrine/registration_form` +* :doc:`/cookbook/doctrine/resolve_target_entity` +* :doc:`/cookbook/doctrine/dbal` +* :doc:`/cookbook/doctrine/pdo_session_storage` +* :doc:`/cookbook/doctrine/custom_dql_functions` +* :doc:`/cookbook/doctrine/mapping_model_classes` + +.. _`ORM`: https://en.wikipedia.org/wiki/Object-relational_mapping .. _`Doctrine`: http://www.doctrine-project.org/ .. _`MongoDB`: https://www.mongodb.org/ -.. _`Basic Mapping Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html -.. _`Query Builder`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html -.. _`Doctrine Query Language`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html -.. _`Association Mapping Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html -.. _`Mapping Types Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping -.. _`Property Mapping`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping -.. _`Lifecycle Events documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#lifecycle-events +.. _`DoctrineMongoDBBundle`: https://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html +.. _`newer utf8mb4 character set`: https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html +.. _`Basic Mapping documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html .. _`Reserved SQL keywords documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#quoting-reserved-words .. _`Creating Classes for the Database`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#creating-classes-for-the-database -.. _`DoctrineMongoDBBundle`: https://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html +.. _`Property Mapping`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping +.. _`Mapping Types documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping .. _`migrations`: https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html -.. _`DoctrineFixturesBundle`: https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html +.. _`DoctrineFixturesBundle documentation`: https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html .. _`FrameworkExtraBundle documentation`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html -.. _`newer utf8mb4 character set`: https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html +.. _`Doctrine Query Language documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html +.. _`Query Builder documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html +.. _`Association Mapping documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html +.. _`Lifecycle Events documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#lifecycle-events \ No newline at end of file From 841ba072d9ba89902c4f7cf9cd723ecf2ece5a80 Mon Sep 17 00:00:00 2001 From: Talita Kocjan Zager Date: Fri, 18 Mar 2016 19:57:52 +0100 Subject: [PATCH 2/3] typo in doc ref --- book/doctrine.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/doctrine.rst b/book/doctrine.rst index 690e43f5909..3626fddb1ea 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -714,7 +714,7 @@ issue an ``UPDATE`` query if the entity already exists in the database. To use Doctrine outside controller classes (which extend Symfony base ``Controller`` class), for example in your own custom services you have to inject ``doctrine`` service into the service. To learn about services - and custom services read :doc:`Service container chapter /book/service_container`. + and custom services read :doc:`Service container chapter `. .. index:: single: Doctrine; Fetching objects from the database using getRepository() From e536645566e20a9c68ca0c045d85c1a16f7649d3 Mon Sep 17 00:00:00 2001 From: Talita Kocjan Zager Date: Fri, 18 Mar 2016 20:18:27 +0100 Subject: [PATCH 3/3] typo --- book/doctrine.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/doctrine.rst b/book/doctrine.rst index 3626fddb1ea..5b40a0b5d1d 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -720,7 +720,7 @@ issue an ``UPDATE`` query if the entity already exists in the database. single: Doctrine; Fetching objects from the database using getRepository() single: Doctrine; Default finder methods -.. database-fetching-obj-using-repo: +.. _database-fetching-obj-using-repo: Fetching Objects from the Database Using ``getRepository()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~