From 49386782fa4c8b5707bc853ab893d1baa5c71407 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 21 Nov 2017 16:09:34 -0500 Subject: [PATCH 1/8] WIP - starting to upgrade Doctrine doc --- doctrine.rst | 129 ++++++++++++--------------------------------------- 1 file changed, 29 insertions(+), 100 deletions(-) diff --git a/doctrine.rst b/doctrine.rst index 4ff17e75bb4..5b989103197 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -4,112 +4,41 @@ Databases and the Doctrine ORM ============================== -One of the most common and challenging tasks for any application -involves persisting and reading information to and from a database. Although -the Symfony Framework doesn't integrate any component to work with databases, -it provides tight integration with a third-party library called `Doctrine`_. -Doctrine's sole goal is to give you powerful tools to make database interactions -easy and flexible. - -In this chapter, you'll learn how to start leveraging Doctrine in your Symfony projects -to give you rich database interactions. +Symfony doesn't provide a component to work with the database, but it *does* provide +tight integration with a third-party library called `Doctrine`_. .. note:: - 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:`/doctrine/dbal`" article. - - You can also persist data to `MongoDB`_ using Doctrine ODM library. For - more information, read the "`DoctrineMongoDBBundle`_" - documentation. - -A Simple Example: A Product ---------------------------- + This article is all about using the Doctrine ORM. If you prefer to use raw + database queries, see the ":doc:`/doctrine/dbal`" article instead. -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 -~~~~~~~~~~~~~~~~~~~~~~~~ + You can also persist data to `MongoDB`_ using Doctrine ODM library. See the + "`DoctrineMongoDBBundle`_" documentation. -Before you really begin, you'll need to configure your database connection -information. By convention, this information is usually configured in an -``app/config/parameters.yml`` file: - -.. code-block:: yaml - - # app/config/parameters.yml - parameters: - database_host: localhost - database_name: test_project - database_user: root - database_password: password - - # ... - -.. note:: +Installing Doctrine +------------------- - 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: +First, install Doctrine, as well as the MakerBundle, which will help generate some +code: - .. configuration-block:: - - .. code-block:: yaml +.. code-block:: terminal - # app/config/config.yml - doctrine: - dbal: - driver: pdo_mysql - host: '%database_host%' - dbname: '%database_name%' - user: '%database_user%' - password: '%database_password%' + composer require orm maker - .. code-block:: xml +Configuring the Database +~~~~~~~~~~~~~~~~~~~~~~~~ - - - +The database connection information is stored as an environment variable called +``DATABASE_URL``. For development, you can find and customize this inside ``.env``: - - - - +.. code-block:: text - .. code-block:: php + # .env - // app/config/config.php - $container->loadFromExtension('doctrine', array( - 'dbal' => array( - 'driver' => 'pdo_mysql', - 'host' => '%database_host%', - 'dbname' => '%database_name%', - 'user' => '%database_user%', - 'password' => '%database_password%', - ), - )); + # customize this line! + DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?charset=utf8mb4&serverVersion=5.7" - 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:`/configuration/external_parameters`. +----> HERE Now that Doctrine can connect to your database, the following command can automatically generate an empty ``test_project`` database for you: @@ -242,7 +171,7 @@ can automatically generate an empty ``test_project`` database for you: )); 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 @@ -280,7 +209,7 @@ just a simple PHP class. .. _doctrine-adding-mapping: Add Mapping Information -~~~~~~~~~~~~~~~~~~~~~~~ +----------------------- Doctrine allows you to work with databases in a much more interesting way than just fetching rows of scalar data into an array. Instead, Doctrine @@ -439,7 +368,7 @@ see the :ref:`doctrine-field-types` section. .. _doctrine-generating-getters-and-setters: Generating Getters and Setters -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------ Even though Doctrine now knows how to persist a ``Product`` object to the database, the class itself isn't really useful yet. Since ``Product`` is just @@ -451,7 +380,7 @@ methods manually or with your own IDE. .. _doctrine-creating-the-database-tables-schema: Creating 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 @@ -487,7 +416,7 @@ Your database now has a fully-functional ``product`` table with columns that match the metadata you've specified. Persisting Objects to the Database -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +---------------------------------- Now that you have mapped the ``Product`` entity to its corresponding ``product`` table, you're ready to persist ``Product`` objects to the database. From inside @@ -580,7 +509,7 @@ issue an ``UPDATE`` query if the entity already exists in the database. the "`DoctrineFixturesBundle`_" documentation. Fetching Objects from the Database -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +---------------------------------- 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 @@ -675,7 +604,7 @@ to easily fetch objects based on multiple conditions:: Symfony Profiler and see the exact queries that were executed. Updating an Object -~~~~~~~~~~~~~~~~~~ +------------------ Once you've fetched an object from Doctrine, updating it is easy. Suppose you have a route that maps a product id to an update action in a controller:: @@ -712,7 +641,7 @@ In this case, since you fetched the ``$product`` object from Doctrine, it's already managed. Deleting an Object -~~~~~~~~~~~~~~~~~~ +------------------ Deleting an object is very similar, but requires a call to the ``remove()`` method of the entity manager:: From 6259931025e7cc0d59ddb804a5c1eeb00d7c855e Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 22 Nov 2017 14:24:21 -0500 Subject: [PATCH 2/8] Finishing major overhaul of the main Doctrine chapter --- _build/redirection_map | 7 +- doctrine.rst | 825 +++++++++++++---------------- doctrine/console.rst | 43 -- doctrine/mapping_model_classes.rst | 149 ------ doctrine/repository.rst | 102 ---- 5 files changed, 361 insertions(+), 765 deletions(-) delete mode 100644 doctrine/console.rst delete mode 100644 doctrine/mapping_model_classes.rst delete mode 100644 doctrine/repository.rst diff --git a/_build/redirection_map b/_build/redirection_map index 89d6cfaa510..272c7dc6782 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -138,18 +138,21 @@ /cookbook/deployment/platformsh /deployment/platformsh /cookbook/deployment/tools /deployment/tools /cookbook/doctrine/common_extensions /doctrine/common_extensions -/cookbook/doctrine/console /doctrine/console +/cookbook/doctrine/console /doctrine /cookbook/doctrine/custom_dql_functions /doctrine/custom_dql_functions /cookbook/doctrine/dbal /doctrine/dbal /cookbook/doctrine/event_listeners_subscribers /doctrine/event_listeners_subscribers /cookbook/doctrine/index /doctrine -/cookbook/doctrine/mapping_model_classes /doctrine/mapping_model_classes +/cookbook/doctrine/mapping_model_classes /doctrine +/doctrine/mapping_model_classes /doctrine /cookbook/doctrine/mongodb_session_storage /doctrine/mongodb_session_storage /cookbook/doctrine/multiple_entity_managers /doctrine/multiple_entity_managers /cookbook/doctrine/pdo_session_storage /doctrine/pdo_session_storage /cookbook/doctrine/registration_form /doctrine/registration_form /cookbook/doctrine/resolve_target_entity /doctrine/resolve_target_entity /cookbook/doctrine/reverse_engineering /doctrine/reverse_engineering +/doctrine/repository /doctrine +/doctrine/console /doctrine /cookbook/email/cloud /email/cloud /cookbook/email/dev_environment /email/dev_environment /cookbook/email/email /email diff --git a/doctrine.rst b/doctrine.rst index 5b989103197..e318bfa899b 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -38,232 +38,104 @@ The database connection information is stored as an environment variable called # customize this line! DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?charset=utf8mb4&serverVersion=5.7" -----> HERE + # to use sqlite: + # DATABASE_URL="sqlite://%kernel.project_dir%/var/app.db" -Now that Doctrine can connect to your database, the following command -can automatically generate an empty ``test_project`` database for you: +Now that your connection parameters are setup, Doctrine can create the ``db_name`` +database for you: .. code-block:: terminal $ php bin/console doctrine:database:create -.. sidebar:: Setting up the Database to be UTF8 - - 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: - - .. code-block:: terminal - - $ php bin/console doctrine:database:drop --force - $ php bin/console doctrine:database:create - - Setting UTF8 defaults for MySQL is as simple as adding a few lines to - your configuration file (typically ``my.cnf``): - - .. code-block:: ini - - [mysqld] - # Version 5.5.3 introduced "utf8mb4", which is recommended - collation-server = utf8mb4_unicode_ci # Replaces utf8_unicode_ci - character-set-server = utf8mb4 # Replaces utf8 - - You can also change the defaults for Doctrine so that the generated SQL - uses the correct character set. - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine: - dbal: - charset: utf8mb4 - default_table_options: - charset: utf8mb4 - collate: utf8mb4_unicode_ci - - .. code-block:: xml - - - - - - - - utf8mb4 - utf8mb4_unicode_ci - - - - - .. code-block:: php - - // app/config/config.php - $configuration->loadFromExtension('doctrine', array( - 'dbal' => array( - 'charset' => 'utf8mb4', - 'default_table_options' => array( - 'charset' => 'utf8mb4', - 'collate' => 'utf8mb4_unicode_ci', - ) - ), - )); - - We recommend 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`_. -.. note:: +.. tip:: - If you want to use SQLite as your database, you need to set the path - where your database file should be stored: - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine: - dbal: - driver: pdo_sqlite - path: '%kernel.project_dir%/app/sqlite.db' - charset: UTF8 - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('doctrine', array( - 'dbal' => array( - 'driver' => 'pdo_sqlite', - 'path' => '%kernel.project_dir%/app/sqlite.db', - 'charset' => 'UTF-8', - ), - )); + There are many other Doctrine commands. To see a full list, run + ``php bin/console list doctrine``. 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 ``src``:: +you need a ``Product`` object to represent those products. Use the ``make:entity`` +command to create this class for you: - // src/Entity/Product.php - namespace App\Entity; +.. code-block:: terminal - class Product - { - private $name; - private $price; - private $description; - } + $ php bin/console make:entity Product -The class - often called an "entity", meaning *a basic class that holds data* - -is simple and helps fulfill the business requirement of needing products -in your application. This class can't be persisted to a database yet - it's -just a simple PHP class. +You now have a new ``src/Entity/Product.php`` file:: -.. tip:: + // src/Entity/Product.php + namespace App\Entity; - 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: + use Doctrine\ORM\Mapping as ORM; - .. code-block:: terminal + /** + * @ORM\Entity(repositoryClass="App\Repository\ProductRepository") + * @ORM\Table() + */ + class Product + { + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + private $id; - $ php bin/console doctrine:generate:entity + // add your own fields + } -.. index:: - single: Doctrine; Adding mapping metadata +This class is called an "entity". And soon, you will be able to save and query Product +objects to a ``product`` table in your database. .. _doctrine-adding-mapping: -Add Mapping Information ------------------------ +Mapping More Fields / Columns +----------------------------- -Doctrine allows you to work with databases in a much more interesting way -than just fetching rows of scalar data into an array. Instead, Doctrine -allows you to fetch entire *objects* out of the database, and to persist -entire objects to the database. For Doctrine to be able to do this, you -must *map* your database tables to specific PHP classes, and the columns -on those tables must be mapped to specific properties on their corresponding -PHP classes. +Each property in the ``Product`` entity can be mapped to a column in the ``product`` +table. By adding some mapping configuration, Doctrine will be able to save a Product +object to the ``product`` table *and* query from the ``product`` table and turn +that data into ``Product`` objects: .. image:: /_images/doctrine/mapping_single_entity.png :align: center -You'll provide this mapping information in the form of "metadata", a collection -of rules that tells Doctrine exactly how the ``Product`` class and its -properties should be *mapped* to a specific database table. This metadata -can be specified in a number of different formats, including YAML, XML or -directly inside the ``Product`` class via DocBlock annotations: +Let's give the ``Product`` entity class three more properties and map them to columns +in the database. This is usually done with annotations: .. configuration-block:: .. code-block:: php-annotations // src/Entity/Product.php - namespace App\Entity; + // ... + // this use statement is needed for the annotations use Doctrine\ORM\Mapping as ORM; - /** - * @ORM\Entity - * @ORM\Table(name="product") - */ class Product { /** - * @ORM\Column(type="integer") * @ORM\Id - * @ORM\GeneratedValue(strategy="AUTO") + * @ORM\GeneratedValue + * @ORM\Column(type="integer") */ private $id; /** - * @ORM\Column(type="string", length=100) + * @ORM\Column(type="string", length=100, nullable=false) */ private $name; /** - * @ORM\Column(type="decimal", scale=2) + * @ORM\Column(type="decimal", scale=2, nullable=false) */ private $price; - - /** - * @ORM\Column(type="text") - */ - private $description; } .. code-block:: yaml @@ -271,7 +143,6 @@ directly inside the ``Product`` class via DocBlock annotations: # src/Resources/config/doctrine/Product.orm.yml App\Entity\Product: type: entity - table: product id: id: type: integer @@ -280,11 +151,11 @@ directly inside the ``Product`` class via DocBlock annotations: name: type: string length: 100 + nullable: false price: type: decimal scale: 2 - description: - type: text + nullable: false .. code-block:: xml @@ -295,212 +166,234 @@ directly inside the ``Product`` class via DocBlock annotations: xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd"> - + - - - + + -.. note:: +Doctrine supports a wide variety of different field types, each with their own options. +To see a full list of types and options, see `Doctrine's Mapping Types documentation`_. - 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. +.. caution:: -.. tip:: + Be careful not to use reserved SQL keywords as your table or column names + (e.g. ``GROUP`` or ``USER``). See Doctrine's `Reserved SQL keywords documentation`_ + for details on how to escape these. Or, configure the table name with + ``@ORM\Table(name="groups")`` or the column name with the ``name="group_name"`` + option. - The table name is optional and if omitted, will be determined automatically - based on the name of the entity class. +.. _doctrine-creating-the-database-tables-schema: -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:`doctrine-field-types` section. +Migrations: Creating the Database Tables/Schema +----------------------------------------------- -.. seealso:: +The ``Product`` class is fully-configured and ready to save to a ``product`` table. +Of course, your database doesn't actually have the ``product`` table yet. To add +the table, you can leverage the `DoctrineMigrationsBundle`_, which is already installed: - 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. +.. code-block:: terminal -.. caution:: + $ php bin/console doctrine:migrations:diff - Be careful if the names of your entity classes (or their properties) - are also reserved SQL keywords like ``GROUP`` or ``USER``. For example, - if your entity's class name is ``Group``, then, by default, the corresponding - table name would be ``group``. This will cause an SQL error in some database - engines. See Doctrine's `Reserved SQL keywords documentation`_ for details - on how to properly escape these names. Alternatively, if you're free - to choose your database schema, simply map to a different table name - or column name. See Doctrine's `Creating Classes for the Database`_ - and `Property Mapping`_ documentation. +If everything worked, you should see something like this: -.. note:: + Generated new migration class to + "/path/to/project/doctrine/src/Migrations/Version20171122151511.php" + from schema differences. - When using another library or program (e.g. Doxygen) that uses annotations, - you should place the ``@IgnoreAnnotation`` annotation on the class to - indicate which annotations Symfony should ignore. +If you open this file, it contains the SQL needed to update your database! To run +that SQL, execute your migrations: - For example, to prevent the ``@fn`` annotation from throwing an exception, - add the following:: +.. code-block:: terminal - /** - * @IgnoreAnnotation("fn") - */ - class Product + $ php bin/console doctrine:migrations:migrate + +This command executes all migration files that have not already been run against +your database. + +Migrations & Adding more Fields +------------------------------- + +But what if you need to add a new field property to ``Product``, like a ``description``? + +.. code-block:: diff + + // src/Entity/Product.php + // ... + + class Product + { // ... -.. tip:: + + /** + + * @ORM\Column(type="text") + + */ + + private $description; + } + +The new property is mapped, but it doesn't exist yet in the ``product`` table. No +problem! Just generate a new migration: + +.. code-block:: terminal + + $ php bin/console doctrine:migrations:diff + +This time, the SQL in the generated file will look like this: + +.. code-block:: sql - After creating your entities you should validate the mappings with the - following command: + ALTER TABLE product ADD description LONGTEXT NOT NULL - .. code-block:: terminal +The migration system is *smart*. It compares all of your entities with the current +state of the database and generates the SQL needed to synchronize them! Just like +before, execute your migrations: - $ php bin/console doctrine:schema:validate +.. code-block:: terminal + + $ php bin/console doctrine:migrations:migrate + +This will only execute the *one* new migration file, because DoctrineMigrationsBundle +knows that the first migration was already executed earlier. Behind the scenes, it +automatically manages a ``migration_versions`` table to track this. + +Each time you make a change to your schema, run these two commands to generate the +migration and then execute it. Be sure to commit the migration files and run execute +them when you deploy. .. _doctrine-generating-getters-and-setters: Generating Getters and Setters ------------------------------ -Even though Doctrine now knows how to persist a ``Product`` object to the -database, the class itself isn't really useful yet. Since ``Product`` is just -a regular PHP class with ``private`` properties, you need to create ``public`` -getter and setter methods (e.g. ``getName()``, ``setName($name)``) in order -to access its properties in the rest of your application's code. Add these -methods manually or with your own IDE. +Doctrine now knows how to persist a ``Product`` object to the database. But the class +itself isn't useful yet. All of the properties are ``private``, so there's no way +to set data on them! -.. _doctrine-creating-the-database-tables-schema: +For that reason, you should create public getters and setters for all the fields +you need to modify from outside of the class. If you use an IDE like PhpStorm, it +can generate these for you. In PhpStorm, put your cursor anywhere in the class, +then go to the Code -> Generate menu and select "Getters and Setters":: -Creating 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: + // src/Entity/Product + // ... -.. code-block:: terminal + class Product + { + // all of your properties - $ php bin/console doctrine:schema:update --force + public function getId() + { + return $this->id; + } -.. tip:: + public function getName() + { + return $this->name; + } - 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 command, it will execute the "ALTER TABLE" statement needed - to add that new column to the existing ``product`` table. + public function setName($name) + { + $this->name = $name; + } - An even better way to take advantage of this functionality is via - `migrations`_, which allow you to generate these SQL statements and store - them in migration classes that can be run systematically on your production - server in order to update and track changes to your database schema safely - and reliably. + // ... getters & setters for price & description + } - Whether or not you take advantage of migrations, the ``doctrine:schema:update`` - command should only be used during development. It should not be used in - a production environment. +.. tip:: -Your database now has a fully-functional ``product`` table with columns that -match the metadata you've specified. + Typically you won't need a ``setId()`` method: Doctrine will set this for you + automatically. Persisting Objects to the Database ---------------------------------- -Now that you have mapped the ``Product`` entity to its corresponding ``product`` -table, you're ready to persist ``Product`` objects to the database. From inside -a controller, this is pretty easy. Add the following method to the -``DefaultController`` of the bundle:: +It's time to save a ``Product`` object to the database! Let's create a new controller +to experiment: + +.. code-block:: terminal + + $ php bin/console make:controller ProductController + +Inside the controller, you can create a new ``Product`` object, set data on it, +and save it! +.. code-block:: php - // src/Controller/DefaultController.php + // src/Controller/ProductController.php + + namespace App\Controller; // ... use App\Entity\Product; - use Symfony\Component\HttpFoundation\Response; - use Doctrine\ORM\EntityManagerInterface; - public function createAction() + class ProductController extends Controller { - // you can fetch the EntityManager via $this->getDoctrine() - // or you can add an argument to your action: createAction(EntityManagerInterface $em) - $em = $this->getDoctrine()->getManager(); + /** + * @Route("/product", name="product") + */ + public function index() + { + // you can fetch the EntityManager via $this->getDoctrine() + // or you can add an argument to your action: index(EntityManagerInterface $em) + $em = $this->getDoctrine()->getManager(); - $product = new Product(); - $product->setName('Keyboard'); - $product->setPrice(19.99); - $product->setDescription('Ergonomic and stylish!'); + $product = new Product(); + $product->setName('Keyboard'); + $product->setPrice(19.99); + $product->setDescription('Ergonomic and stylish!'); - // tells Doctrine you want to (eventually) save the Product (no queries yet) - $em->persist($product); + // tell Doctrine you want to (eventually) save the Product (no queries yet) + $em->persist($product); - // actually executes the queries (i.e. the INSERT query) - $em->flush(); + // actually executes the queries (i.e. the INSERT query) + $em->flush(); - return new Response('Saved new product with id '.$product->getId()); + return new Response('Saved new product with id '.$product->getId()); + } } - // if you have multiple entity managers, use the registry to fetch them - public function editAction() - { - $doctrine = $this->getDoctrine(); - $em = $doctrine->getManager(); - $em2 = $doctrine->getManager('other_connection'); - } +Try it out! -.. note:: + http://localhost:8000/product + +Congratulations! You just created your first row the ``product`` table. To prove it, +you can query the database directly: + +.. code-block:: terminal - If you're following along with this example, you'll need to create a - route that points to this action to see it work. + $ php bin/console doctrine:query:sql 'SELECT * FROM product' Take a look at the previous example in more detail: .. _doctrine-entity-manager: -* **line 12** The ``$this->getDoctrine()->getManager()`` method gets Doctrine's +* **line 17** The ``$this->getDoctrine()->getManager()`` method gets Doctrine's *entity manager* object, which is the most important object in Doctrine. It's responsible for saving objects to, and fetching objects from, the database. -* **lines 14-17** In this section, you instantiate and work with the ``$product`` +* **lines 19-22** In this section, you instantiate and work with the ``$product`` object like any other normal PHP object. -* **line 20** The ``persist($product)`` call tells Doctrine to "manage" the +* **line 25** The ``persist($product)`` call tells Doctrine to "manage" the ``$product`` object. This does **not** cause a query to be made to the database. -* **line 23** When the ``flush()`` method is called, Doctrine looks through +* **line 28** 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. - .. note:: If the ``flush()`` call fails, a ``Doctrine\ORM\ORMException`` exception is thrown. See `Transactions and Concurrency`_. -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. +Whether you're creating or updating objects, the workflow is always the same: Doctrine +is smart enough to know if it should INSERT of UPDATE your entity. .. tip:: @@ -511,89 +404,73 @@ issue an ``UPDATE`` query if the entity already exists in the database. Fetching Objects from the Database ---------------------------------- -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 -on its ``id`` value:: +Fetching an object back out of the database is even easier. Suppose you want to +be able to go to ``/product/1`` to see your new product:: + + // src/Controller/ProductController.php + // ... - public function showAction($productId) + /** + * @Route("/product/{id}", name="product_show") + */ + public function showAction($id) { $product = $this->getDoctrine() ->getRepository(Product::class) - ->find($productId); + ->find($id); if (!$product) { throw $this->createNotFoundException( - 'No product found for id '.$productId + 'No product found for id '.$id ); } - // ... do something, like pass the $product object into a template + return new Response('Check out this great product: '.$product->getName()); + + // or render a template + // in the template, print things with {{ product.name }} + // $this->render('product/show.html.twig', ['product' => $product]); } -.. tip:: +Try it out! - You can achieve the equivalent of this without writing any code by using - the ``@ParamConverter`` shortcut. See the `FrameworkExtraBundle documentation`_ - for more details. + http://localhost:8000/product/1 When you query for a particular type of object, you always use what's known as its "repository". You can think of a repository as a PHP class whose only -job is to help you fetch entities of a certain class. You can access the -repository object for an entity class via:: - - $repository = $this->getDoctrine() - ->getRepository(Product::class); - -.. note:: - - You can also use ``App:Product`` syntax. This string is a shortcut you can use anywhere - in Doctrine instead of the full class name of the entity (i.e. ``App\Entity\Product``). - As long as your entity lives under the ``Entity`` namespace of your bundle, - this will work. +job is to help you fetch entities of a certain class. -Once you have a repository object, you can access all sorts of helpful methods:: +Once you have a repository object, you have many helper methods:: $repository = $this->getDoctrine()->getRepository(Product::class); - // query for a single product by its primary key (usually "id") - $product = $repository->find($productId); - - // dynamic method names to find a single product based on a column value - $product = $repository->findOneById($productId); - $product = $repository->findOneByName('Keyboard'); - - // dynamic method names to find a group of products based on a column value - $products = $repository->findByPrice(19.99); - - // find *all* products - $products = $repository->findAll(); - -.. note:: - - Of course, you can also issue complex queries, which you'll learn more - about in the :ref:`doctrine-queries` section. + // query for a single Product by its primary key (usually "id") + $product = $repository->find($id); -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 by name + $product = $repository->findOneBy(['name' => 'Keyboard']); + // or find by name and price + $product = $repository->findOneBy([ + 'name' => 'Keyboard', + 'price' => 19.99, + ]); - $repository = $this->getDoctrine()->getRepository(Product::class); - - // query for a single product matching the given name and price - $product = $repository->findOneBy( - array('name' => 'Keyboard', 'price' => 19.99) - ); - - // query for multiple products matching the given name, ordered by price + // query for multiple Product objects matching the name, ordered by price $products = $repository->findBy( array('name' => 'Keyboard'), array('price' => 'ASC') ); + // find *all* Product objects + $products = $repository->findAll(); + +You can also add *custom* methods for more complex queries! More on that later in +the :ref:`doctrine-queries` section. + .. tip:: - When rendering a page requires to make some database calls, the web debug - toolbar at the bottom of the page displays the number of queries and the - time it took to execute them: + When rendering an HTML page, the web debug toolbar at the bottom of the page + will display the number of queries and the time it took to execute them: .. image:: /_images/doctrine/doctrine_web_debug_toolbar.png :align: center @@ -606,27 +483,28 @@ to easily fetch objects based on multiple conditions:: Updating an Object ------------------ -Once you've fetched an object from Doctrine, updating it is easy. Suppose -you have a route that maps a product id to an update action in a controller:: - - use App\Entity\Product; - // ... +Once you've fetched an object from Doctrine, updating it is easy:: - public function updateAction($productId) + /** + * @Route("/product/edit/{id}") + */ + public function updateAction($id) { $em = $this->getDoctrine()->getManager(); - $product = $em->getRepository(Product::class)->find($productId); + $product = $em->getRepository(Product::class)->find($id); if (!$product) { throw $this->createNotFoundException( - 'No product found for id '.$productId + 'No product found for id '.$id ); } $product->setName('New product name!'); $em->flush(); - return $this->redirectToRoute('homepage'); + return $this->redirectToRoute('product_show', [ + 'id' => $product->getId() + ]); } Updating an object involves just three steps: @@ -635,10 +513,8 @@ Updating an object involves just three steps: #. modifying the object; #. 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. +You *can* call ``$em->persist($product)``, but it isn't necessary: Doctrine is already +"watching" your object for changes. Deleting an Object ------------------ @@ -650,155 +526,164 @@ method of the entity manager:: $em->flush(); 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. +like to remove the given object from the database. The ``DELETE`` query isn't +actually executed until the ``flush()`` method is called. .. _doctrine-queries: -Querying for Objects --------------------- +Querying for Objects: The Repository +------------------------------------ You've already seen how the repository object allows you to run basic queries without any work:: + // from inside a controller $repository = $this->getDoctrine()->getRepository(Product::class); - $product = $repository->find($productId); - $product = $repository->findOneByName('Keyboard'); + $product = $repository->find($id); -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``). +But what if you need a more complex query? When you generated your entity with +``make:entity``, the command *also* generated a ``ProductRepository`` class:: -When querying in Doctrine, you have two main options: writing pure DQL queries -or using Doctrine's Query Builder. + // src/Repository/ProductRepository.php + namespace App\Repository; -Querying for Objects with DQL -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + use App\Entity\Product; + use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -Imagine that you want to query for products that cost more than ``19.99``, -ordered from least to most expensive. You can use DQL, Doctrine's native -SQL-like language, to construct a query for this scenario:: + class ProductRepository extends ServiceEntityRepository + { + } - $query = $em->createQuery( - 'SELECT p - FROM App:Product p - WHERE p.price > :price - ORDER BY p.price ASC' - )->setParameter('price', 19.99); +When you fetch your repository (i.e. ``->getRepository(Product::class)``, it is +*actually* an instance of *this* object! This is because of the ``repositoryClass`` +config that was generated at the top of your ``Product`` entity class. - $products = $query->getResult(); +Suppose you want to query for all Product objects greater than a certain price. Add +a new method for this to your repository:: -If you're comfortable with SQL, then DQL should feel very natural. The biggest -difference is that you need to think in terms of selecting PHP objects, -instead of rows in a database. For this reason, you select *from* the -``App:Product`` *entity* (an optional shortcut for the -``App\Entity\Product`` class) and then alias it as ``p``. + // src/Repository/ProductRepository.php -.. tip:: + // ... + class ProductRepository extends ServiceEntityRepository + { + /** + * @param $price + * @return Product[] + */ + public function findAllGreaterThanPrice($price): array + { + // automatically knows to selects Products + // the "p" is an alias you'll use in the rest of the query + $qb = $this->createQueryBuilder('p') + ->andWhere('p.price > :price') + ->setParameter('price', $price) + ->orderBy('p.price', 'ASC') + ->getQuery(); + + return $qb->execute(); + + // to get just one result: + // $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. +This uses Doctrine's `Query Builder`_: a very powerful and user-friendly way to +write custom queries. Now, you can call this method on the repository:: -The ``getResult()`` method returns an array of results. To get only one -result, you can use ``getOneOrNullResult()``:: + // from inside a controller + $minPrice = 10; - $product = $query->setMaxResults(1)->getOneOrNullResult(); + $products = $this->getDoctrine() + ->getRepository(Product::class) + ->findAllGreaterThanPrice($minPrice); -The DQL syntax is incredibly powerful, allowing you to easily join between -entities (the topic of :doc:`relations ` will be -covered later), group, etc. For more information, see the official -`Doctrine Query Language`_ documentation. + // ... -Querying for Objects Using Doctrine's Query Builder -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you're in a :ref:`services-constructor-injection`_, you can type-hint the +``ProductRepository`` class and inject it like normal. -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 -DQL as you start to concatenate strings:: +For more details, see the `Query Builder`_ Documentation from Doctrine. - $repository = $this->getDoctrine() - ->getRepository(Product::class); +Querying with DQL or SQL +------------------------ - // createQueryBuilder() automatically selects FROM App:Product - // and aliases it to "p" - $query = $repository->createQueryBuilder('p') - ->where('p.price > :price') - ->setParameter('price', '19.99') - ->orderBy('p.price', 'ASC') - ->getQuery(); +In addition to the query builder, you can also query with `Doctrine Query Language`_:: - $products = $query->getResult(); - // to get just one result: - // $product = $query->setMaxResults(1)->getOneOrNullResult(); + // src/Repository/ProductRepository.php + // ... -The ``QueryBuilder`` object contains every method necessary to build your -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. + public function findAllGreaterThanPrice($price): array + { + $query = $em->createQuery( + 'SELECT p + FROM App\Entity\Product p + WHERE p.price > :price + ORDER BY p.price ASC' + )->setParameter('price', 10); + + // returns an array of Product objects + return $query->execute(); + } -For more information on Doctrine's Query Builder, consult Doctrine's -`Query Builder`_ documentation. +Or directly with SQL if you need to:: -Organizing Custom Queries into Repository Classes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // src/Repository/ProductRepository.php + // ... -All the queries in the previous sections were written directly in your controller. -But for organization, Doctrine provides special repository classes that allow you -to keep all your query logic in one, central place. + public function findAllGreaterThanPrice($price): array + { + $conn = $this->getEntityManager()->getConnection(); + + $sql = ' + SELECT * FROM product p + WHERE p.price > :price + ORDER BY p.price ASC + '; + $stmt = $conn->prepare($sql); + $stmt->execute(array('price' => 10)); + + // returns an array of arrays (i.e. a raw data set) + return $stmt->fetchAll(); + } -see :doc:`/doctrine/repository` for info. +With SQL, you will get back raw data, not objects (unless you use the `NativeQuery`_ +functionality). 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 `. - -.. _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`_. +See the :doc:`Doctrine config reference `. Relationships and Associations ------------------------------ Doctrine provides all the functionality you need to manage database relationships -(also known as associations). For info, see :doc:`/doctrine/associations`. - -Final Thoughts --------------- - -With Doctrine, you can focus on your *objects* and how they're used in your -application and worry about database persistence second. This is because -Doctrine allows you to use any PHP object to hold your data and relies on -mapping metadata information to map an object's data to a particular database -table. +(also known as associations), including ManyToOne, OneToMany, OneToOne and ManyToMany +relationships. -Doctrine has a lot more complex features to learn, like relationships, complex queries, -and event listeners. +For info, see :doc:`/doctrine/associations`. Learn more ---------- .. toctree:: :maxdepth: 1 - :glob: - doctrine/* + doctrine/associations + doctrine/common_extensions + doctrine/lifecycle_callbacks + doctrine/event_listeners_subscribers + doctrine/registration_form + doctrine/custom_dql_functions + doctrine/dbal + doctrine/multiple_entity_managers + doctrine/pdo_session_storage + doctrine/mongodb_session_storage + doctrine/resolve_target_entity + doctrine/reverse_engineering * `DoctrineFixturesBundle`_ -* `DoctrineMongoDBBundle`_ .. _`Doctrine`: http://www.doctrine-project.org/ .. _`MongoDB`: https://www.mongodb.org/ @@ -815,3 +700,5 @@ Learn more .. _`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 .. _`Transactions and Concurrency`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/transactions-and-concurrency.html +.. _`DoctrineMigrationsBundle`: https://github.com/doctrine/DoctrineMigrationsBundle +.. _`NativeQuery`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/native-sql.html diff --git a/doctrine/console.rst b/doctrine/console.rst deleted file mode 100644 index 5735e9ac09e..00000000000 --- a/doctrine/console.rst +++ /dev/null @@ -1,43 +0,0 @@ -.. index:: - single: Doctrine; ORM console commands - single: CLI; Doctrine ORM - -Console Commands ----------------- - -The Doctrine2 ORM integration offers several console commands under the -``doctrine`` namespace. To view the command list you can use the ``list`` -command: - -.. code-block:: terminal - - $ php bin/console list doctrine - -A list of available commands will print out. You can find out more information -about any of these commands (or any Symfony command) by running the ``help`` -command. For example, to get details about the ``doctrine:database:create`` -command, run: - -.. code-block:: terminal - - $ php bin/console help doctrine:database:create - -Some notable or interesting commands include: - -* ``doctrine:ensure-production-settings`` - checks to see if the current - environment is configured efficiently for production. This should always - be run in the ``prod`` environment: - - .. code-block:: terminal - - $ php bin/console doctrine:ensure-production-settings --env=prod - -* ``doctrine:mapping:import`` - allows Doctrine to introspect an existing - database and create mapping information. For more information, see - :doc:`/doctrine/reverse_engineering`. - -* ``doctrine:mapping:info`` - tells you all of the entities that Doctrine - is aware of and whether or not there are any basic errors with the mapping. - -* ``doctrine:query:dql`` and ``doctrine:query:sql`` - allow you to execute - DQL or SQL queries directly from the command line. diff --git a/doctrine/mapping_model_classes.rst b/doctrine/mapping_model_classes.rst deleted file mode 100644 index 134912d2dea..00000000000 --- a/doctrine/mapping_model_classes.rst +++ /dev/null @@ -1,149 +0,0 @@ -.. index:: - single: Doctrine; Mapping Model classes - -How to Provide Model Classes for several Doctrine Implementations -================================================================= - -When building a bundle that could be used not only with Doctrine ORM but -also the CouchDB ODM, MongoDB ODM or PHPCR ODM, you should still only -write one model class. The Doctrine bundles provide a compiler pass to -register the mappings for your model classes. - -.. note:: - - For non-reusable bundles, the easiest option is to put your model classes - in the default locations: ``Entity`` for the Doctrine ORM or ``Document`` - for one of the ODMs. For reusable bundles, rather than duplicate model classes - just to get the auto-mapping, use the compiler pass. - -In your bundle class, write the following code to register the compiler pass. -This one is written for the CmfRoutingBundle, so parts of it will need to -be adapted for your case:: - - use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass; - use Doctrine\Bundle\MongoDBBundle\DependencyInjection\Compiler\DoctrineMongoDBMappingsPass; - use Doctrine\Bundle\CouchDBBundle\DependencyInjection\Compiler\DoctrineCouchDBMappingsPass; - use Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\DoctrinePhpcrMappingsPass; - use Symfony\Cmf\RoutingBundle\Model; - - class CmfRoutingBundle extends Bundle - { - public function build(ContainerBuilder $container) - { - parent::build($container); - // ... - - $modelDir = realpath(__DIR__.'/Resources/config/doctrine/model'); - $mappings = array( - $modelDir => Model::class, - ); - - if (class_exists(DoctrineOrmMappingsPass::class)) { - $container->addCompilerPass( - DoctrineOrmMappingsPass::createXmlMappingDriver( - $mappings, - array('cmf_routing.model_manager_name'), - 'cmf_routing.backend_type_orm', - array('CmfRoutingBundle' => Model::class) - )); - } - - if (class_exists(DoctrineMongoDBMappingsPass::class)) { - $container->addCompilerPass( - DoctrineMongoDBMappingsPass::createXmlMappingDriver( - $mappings, - array('cmf_routing.model_manager_name'), - 'cmf_routing.backend_type_mongodb', - array('CmfRoutingBundle' => Model::class) - )); - } - - if (class_exists(DoctrineCouchDBMappingsPass::class)) { - $container->addCompilerPass( - DoctrineCouchDBMappingsPass::createXmlMappingDriver( - $mappings, - array('cmf_routing.model_manager_name'), - 'cmf_routing.backend_type_couchdb', - array('CmfRoutingBundle' => Model::class) - )); - } - - if (class_exists(DoctrinePhpcrMappingsPass::class)) { - $container->addCompilerPass( - DoctrinePhpcrMappingsPass::createXmlMappingDriver( - $mappings, - array('cmf_routing.model_manager_name'), - 'cmf_routing.backend_type_phpcr', - array('CmfRoutingBundle' => Model::class) - )); - } - } - } - -Note the :phpfunction:`class_exists()` check. This is crucial, as you do not want your -bundle to have a hard dependency on all Doctrine bundles but let the user -decide which to use. - -The compiler pass provides factory methods for all drivers provided by Doctrine: -Annotations, XML, Yaml, PHP and StaticPHP. The arguments are: - -* A map/hash of absolute directory path to namespace; -* An array of container parameters that your bundle uses to specify the name of - the Doctrine manager that it is using. In the example above, the CmfRoutingBundle - stores the manager name that's being used under the ``cmf_routing.model_manager_name`` - parameter. The compiler pass will append the parameter Doctrine is using - to specify the name of the default manager. The first parameter found is - used and the mappings are registered with that manager; -* An optional container parameter name that will be used by the compiler - pass to determine if this Doctrine type is used at all. This is relevant if - your user has more than one type of Doctrine bundle installed, but your - bundle is only used with one type of Doctrine; -* A map/hash of aliases to namespace. This should be the same convention used - by Doctrine auto-mapping. In the example above, this allows the user to call - ``$om->getRepository('CmfRoutingBundle:Route')``. - -.. note:: - - The factory method is using the ``SymfonyFileLocator`` of Doctrine, meaning - it will only see XML and YML mapping files if they do not contain the - full namespace as the filename. This is by design: the ``SymfonyFileLocator`` - simplifies things by assuming the files are just the "short" version - of the class as their filename (e.g. ``BlogPost.orm.xml``) - - If you also need to map a base class, you can register a compiler pass - with the ``DefaultFileLocator`` like this. This code is taken from the - ``DoctrineOrmMappingsPass`` and adapted to use the ``DefaultFileLocator`` - instead of the ``SymfonyFileLocator``:: - - use Doctrine\Common\Persistence\Mapping\Driver\DefaultFileLocator; - use Doctrine\ORM\Mapping\Driver\XmlDriver; - use App\Model; - - // ... - private function buildMappingCompilerPass() - { - $locator = new Definition(DefaultFileLocator::class, array( - array(realpath(__DIR__ . '/Resources/config/doctrine-base')), - '.orm.xml' - )); - $driver = new Definition(XmlDriver::class, array($locator)); - - return new DoctrineOrmMappingsPass( - $driver, - array(Model::class), - array('your_bundle.manager_name'), - 'your_bundle.orm_enabled' - ); - } - - Note that you do not need to provide a namespace alias unless your users are - expected to ask Doctrine for the base classes. - - Now place your mapping file into ``/Resources/config/doctrine-base`` with the - fully qualified class name, separated by ``.`` instead of ``\``, for example - ``Other.Namespace.Model.Name.orm.xml``. You may not mix the two as otherwise - the ``SymfonyFileLocator`` will get confused. - - Adjust accordingly for the other Doctrine implementations. - -.. _`CouchDB Mapping Compiler Pass pull request`: https://github.com/doctrine/DoctrineCouchDBBundle/pull/27 diff --git a/doctrine/repository.rst b/doctrine/repository.rst deleted file mode 100644 index 49acae61c96..00000000000 --- a/doctrine/repository.rst +++ /dev/null @@ -1,102 +0,0 @@ -.. index:: - single: Doctrine; Custom Repository Class - -How to Create custom Repository Classes -======================================= - -In the previous sections, you began constructing and using more complex queries -from inside a controller. In order to isolate, reuse and test these queries, -it's a good practice to create a custom repository class for your entity. -Methods containing your query logic can then be stored in this class. - -To do this, add the repository class name to your entity's mapping definition: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Entity/Product.php - namespace App\Entity; - - use Doctrine\ORM\Mapping as ORM; - - /** - * @ORM\Entity(repositoryClass="App\Repository\ProductRepository") - */ - class Product - { - //... - } - - .. code-block:: yaml - - # src/Resources/config/doctrine/Product.orm.yml - App\Entity\Product: - type: entity - repositoryClass: App\Repository\ProductRepository - # ... - - .. code-block:: xml - - - - - - - - - - - -Then, create an empty ``App\Repository\ProductRepository`` class extending -from ``Doctrine\ORM\EntityRepository``. - -Next, add a new method - ``findAllOrderedByName()`` - to the newly-generated -``ProductRepository`` class. This method will query for all the ``Product`` -entities, ordered alphabetically by name. - -.. code-block:: php - - // src/Repository/ProductRepository.php - namespace App\Repository; - - use Doctrine\ORM\EntityRepository; - - class ProductRepository extends EntityRepository - { - public function findAllOrderedByName() - { - return $this->getEntityManager() - ->createQuery( - 'SELECT p FROM App:Product p ORDER BY p.name ASC' - ) - ->getResult(); - } - } - -.. tip:: - - The entity manager can be accessed via ``$this->getEntityManager()`` - from inside the repository. - -You can use this new method just like the default finder methods of the repository:: - - use App\Entity\Product; - // ... - - public function listAction() - { - $products = $this->getDoctrine() - ->getRepository(Product::class) - ->findAllOrderedByName(); - } - -.. note:: - - When using a custom repository class, you still have access to the default - finder methods such as ``find()`` and ``findAll()``. From 7e77d9cd92e9fffe7bd0650c52eaa8426acf0193 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 22 Nov 2017 14:25:04 -0500 Subject: [PATCH 3/8] moving section down --- doctrine.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/doctrine.rst b/doctrine.rst index e318bfa899b..1386f336a50 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -395,12 +395,6 @@ Take a look at the previous example in more detail: Whether you're creating or updating objects, the workflow is always the same: Doctrine is smart enough to know if it should INSERT of UPDATE your entity. -.. tip:: - - 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. - Fetching Objects from the Database ---------------------------------- @@ -664,6 +658,13 @@ relationships. For info, see :doc:`/doctrine/associations`. +Dummy Data Fixtures +------------------- + +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. + Learn more ---------- From 68ac45468ac2cc8aab7cfa8e2a1ba8acf685fb4b Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 22 Nov 2017 15:17:00 -0500 Subject: [PATCH 4/8] WIP - updating associations article --- doctrine/associations.rst | 228 +++++++++++++++++++++----------------- 1 file changed, 128 insertions(+), 100 deletions(-) diff --git a/doctrine/associations.rst b/doctrine/associations.rst index 80f305fffff..b9a0fd45569 100644 --- a/doctrine/associations.rst +++ b/doctrine/associations.rst @@ -4,39 +4,76 @@ How to Work with Doctrine Associations / Relations ================================================== +There are **two** main relationship/association types: + +``ManyToOne`` / ``OneToMany`` + The most common relationship, mapped in the database with a simple foreign + key column (e.g. a ``category_id`` column on the ``product`` table). This is + actually just *one* association type, but seen from the two different *sides* + of the relation. + +``ManyToMany`` + Uses a join table and is needed when both sides of the relationship can have + many of the other side (e.g. "students" and "classes": each student is in many + classes, and each class has many students). + +First, you need to determine which relationship to use. If both sides of the relation +will contain many of the oter side (e.g. "students" and "classes"), you need a +``ManyToMany`` relation. Otherwise, you likely need a ``ManyToOne``. + +.. tip:: + + There is also a OneToOne relationship (e.g. one User has one Profile and vice + versa). In practice, using this is similar to ``ManyToOne``. + +The ManyToOne / OneToMany Association +------------------------------------- + Suppose that each product in your application belongs to exactly one category. In this case, you'll need a ``Category`` class, and a way to relate a ``Product`` object to a ``Category`` object. -Start by creating the ``Category`` entity. Since you know that you'll eventually -need to persist category objects through Doctrine, you can let Doctrine create -the class for you. +Start by creating a ``Category`` entity: .. code-block:: terminal - $ php bin/console doctrine:generate:entity --no-interaction \ - --entity="App:Category" \ - --fields="name:string(255)" + $ php bin/console make:entity Category + +Then, add a ``name`` field to that new ``Category`` class:: + + // src/Entity/Category + // ... -This command generates the ``Category`` entity for you, with an ``id`` field, -a ``name`` field and the associated getter and setter functions. + class Category + { + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + private $id; + + /** + * @ORM\Column(type="string") + */ + private $name; + + // ... getters and setters + } -Relationship Mapping Metadata ------------------------------ +Mapping the ManyToOne Relationship +---------------------------------- -In this example, each category can be associated with *many* products, while +In this example, each category can be associated with *many* products. But, each product can be associated with only *one* category. This relationship can be summarized as: *many* products to *one* category (or equivalently, *one* category to *many* products). From the perspective of the ``Product`` entity, this is a many-to-one relationship. From the perspective of the ``Category`` entity, this is a one-to-many relationship. -This is important, because the relative nature of the relationship determines -which mapping metadata to use. It also determines which class *must* hold -a reference to the other class. -To relate the ``Product`` and ``Category`` entities, simply create a ``category`` -property on the ``Product`` class, annotated as follows: +To map this, first create a ``category`` property on the ``Product`` class with +the ``ManyToOne`` annotation: .. configuration-block:: @@ -50,10 +87,20 @@ property on the ``Product`` class, annotated as follows: // ... /** - * @ORM\ManyToOne(targetEntity="Category", inversedBy="products") - * @ORM\JoinColumn(name="category_id", referencedColumnName="id") + * @ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="products") + * @ORM\JoinColumn(nullable=true) */ private $category; + + public function getCategory(): Category + { + return $this->category; + } + + public function setCategory(Category $category) + { + $this->category = $category; + } } .. code-block:: yaml @@ -64,11 +111,10 @@ property on the ``Product`` class, annotated as follows: # ... manyToOne: category: - targetEntity: Category + targetEntity: App\Entity\Category inversedBy: products joinColumn: - name: category_id - referencedColumnName: id + nullable: true .. code-block:: xml @@ -83,22 +129,19 @@ property on the ``Product`` class, annotated as follows: - - + target-entity="App\Entity\Category" + inversed-by="products"> + -This many-to-one mapping is critical. It tells Doctrine to use the ``category_id`` +This many-to-one mapping is required. It tells Doctrine to use the ``category_id`` column on the ``product`` table to relate each record in that table with a record in the ``category`` table. -Next, since a single ``Category`` object will relate to many ``Product`` -objects, a ``products`` property can be added to the ``Category`` class -to hold those associated objects. +Next, since a *one* ``Category`` object will relate to *many* ``Product`` +objects, add a ``products`` property to ``Category`` that will hold those objects: .. configuration-block:: @@ -108,13 +151,14 @@ to hold those associated objects. // ... use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\Common\Collections\Collection; class Category { // ... /** - * @ORM\OneToMany(targetEntity="Product", mappedBy="category") + * @ORM\OneToMany(targetEntity="App\Entity\Product", mappedBy="category") */ private $products; @@ -122,6 +166,14 @@ to hold those associated objects. { $this->products = new ArrayCollection(); } + + /** + * @return Collection|Product[] + */ + public function getProducts() + { + return $this->products; + } } .. code-block:: yaml @@ -132,7 +184,7 @@ to hold those associated objects. # ... oneToMany: products: - targetEntity: Product + targetEntity: App\Entity\Product mappedBy: category # Don't forget to initialize the collection in # the __construct() method of the entity @@ -150,7 +202,7 @@ to hold those associated objects. + render('product/show.html.twig', ['product' => $product]); + // return $this->render('product/show.html.twig', ['product' => $product]); } Try it out! @@ -472,7 +476,40 @@ the :ref:`doctrine-queries` section. If the number of database queries is too high, the icon will turn yellow to indicate that something may not be correct. Click on the icon to open the - Symfony Profiler and see the exact queries that were executed. + Symfony Profiler and see the exact queries that were executed. If you don't + see the web debug toolbar, try running ``composer require profiler`` to install + it. + +Automatically Fetching Objects (ParamConverter) +----------------------------------------------- + +In many cases, you can use the `SensioFrameworkExtraBundle`_ to do the query +for you automatically! First, install the bundle in case you don't have it: + +.. code-block:: terminal + + $ composer require annotations + +Now, simplify your controller:: + + // src/Controller/ProductController.php + + use App\Entity\Product; + // ... + + /** + * @Route("/product/{id}", name="product_show") + */ + public function showAction(Product $product) + { + // use the Product! + // ... + } + +That's it! The bundle uses the ``{id}`` from the route to query for the ``Product`` +by the ``id`` column. If it's not found, a 404 page is generated. + +There are many more options you can use. Read more about the `ParamConverter`_. Updating an Object ------------------ @@ -692,14 +729,11 @@ Learn more .. _`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 .. _`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 .. _`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 -.. _`migrations`: https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html .. _`DoctrineFixturesBundle`: 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 .. _`Transactions and Concurrency`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/transactions-and-concurrency.html .. _`DoctrineMigrationsBundle`: https://github.com/doctrine/DoctrineMigrationsBundle .. _`NativeQuery`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/native-sql.html +.. _`SensioFrameworkExtraBundle`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html +.. _`ParamConverter`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html diff --git a/doctrine/associations.rst b/doctrine/associations.rst index b9a0fd45569..318b907b72d 100644 --- a/doctrine/associations.rst +++ b/doctrine/associations.rst @@ -290,6 +290,14 @@ about your database, and instead *only* think about your objects. Instead of set the category's integer id onto ``Product``, you set the entire ``Category`` *object*. Doctrine takes care of the rest when saving. +.. sidebar:: Updating the Relationship from the Inverse Side + + Could you also call ``$category->setProducts()`` to set the relationship? Actually, + no! Earlier, you did *not* add a ``setProducts()`` method on ``Category``. That's + on purpose: you can *only* set data on the *owning* side of the relationship. In + other words, if you call ``$category->setProducts()`` only, that is *completely* + ignored when saving. For more details, see: `associations-inverse-side`_. + Fetching Related Objects ------------------------ @@ -330,24 +338,21 @@ the category (i.e. it's "lazily loaded"). Because we mapped the optiona ``OneToMany`` side, you can also query in the other direction:: - public function showProductsAction($categoryId) + public function showProductsAction($id) { $category = $this->getDoctrine() ->getRepository(Category::class) - ->find($categoryId); + ->find($id); $products = $category->getProducts(); // ... } -TODO TODO, STARTING HERE!!!!!!!!!!!!!!!!!! - -In this case, the same things occur: you first query out for a single ``Category`` -object, and then Doctrine makes a second query to retrieve the related ``Product`` -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. +In this case, the same things occur: you first query for a single ``Category`` +object. Then, only when (and if) you access the products, Doctrine makes a second +query to retrieve the related ``Product`` objects. This extra query can be avoided +by adding JOINs. .. sidebar:: Relationships and Proxy Classes @@ -357,7 +362,7 @@ to the given ``Category`` object via their ``category_id`` value. $product = $this->getDoctrine() ->getRepository(Product::class) - ->find($productId); + ->find($id); $category = $product->getCategory(); @@ -371,8 +376,8 @@ to the given ``Category`` object via their ``category_id`` value. 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. + You'll probably never even notice that your ``$category`` object is actually + a proxy object. In the next section, when you retrieve the product and category data all at once (via a *join*), Doctrine will return the *true* ``Category`` @@ -381,7 +386,7 @@ to the given ``Category`` object via their ``category_id`` value. Joining Related Records ----------------------- -In the above examples, two queries were made - one for the original object +In the examples above, two queries were made - one for the original object (e.g. a ``Category``) and one for the related object(s) (e.g. the ``Product`` objects). @@ -397,34 +402,129 @@ following method to the ``ProductRepository`` class:: // src/Repository/ProductRepository.php public function findOneByIdJoinedToCategory($productId) { - $query = $this->getEntityManager() - ->createQuery( - 'SELECT p, c FROM App:Product p - JOIN p.category c - WHERE p.id = :id' - )->setParameter('id', $productId); - - try { - return $query->getSingleResult(); - } catch (\Doctrine\ORM\NoResultException $e) { - return null; - } + return $this->createQueryBuilder('p') + // p.category refers to the "category" property on product + ->innerJoin('p.category', 'c') + // selects all the category data to avoid the query + ->addSelect('c') + ->andWhere('p.id = :id') + ->setParameter('id', $productId) + ->getQuery() + ->getOneOrNullResult(); } +This will *still* return an array of ``Product`` objects. But now, when you call +``$product->getCategory()`` and use that data, no second query is made. + Now, you can use this method in your controller to query for a ``Product`` object and its related ``Category`` with just one query:: - public function showAction($productId) + public function showAction($id) { $product = $this->getDoctrine() ->getRepository(Product::class) - ->findOneByIdJoinedToCategory($productId); + ->findOneByIdJoinedToCategory($id); $category = $product->getCategory(); // ... } +.. _associations-inverse-side: + +Setting Information from the Inverse Side +----------------------------------------- + +So far, you've updated the relationship by calling ``$product->setCategory($category)``. +This is no accident: you *must* set the relationship on the *owning* side. The owning +side is always where the ``ManyToOne`` mapping is set (for a ``ManyToMany`` relation, +you can choose which side is the owning side). + +Does this means it's not possible to call ``$category->setProducts()``? Actually, +it *is* possible, by writing clever methods. First, instead of a ``setProducts()`` +method, create a ``addProduct()`` method:: + + // src/Entity/Category.php + + // ... + class Category + { + // ... + + public function addProduct(Product $product) + { + if ($this->products->contains($product)) { + return; + } + + $this->products[] = $product; + // set the *owning* side! + $product->setCategory($this); + } + } + +That's it! The *key* is ``$product->setCategory($this)``, which sets the *owning* +side. Now, when you save, the relationship *will* update in the database. + +What about *removing* a ``Product`` from a ``Category``? Add a ``removeProduct()`` +method:: + + // src/Entity/Category.php + + // ... + class Category + { + // ... + + public function removeProduct(Product $product) + { + $this->products->removeElement($product); + // set the owning side to null + $product->setCategory(null); + } + } + +To make this work, you *now* need to allow ``null`` to be passed to ``Product::setCategory()``: + +.. code-block:: diff + + // src/Entity/Product.php + + // ... + class Product + { + // ... + + - public function getCategory(): Category + + public function getCategory(): ?Category + // ... + + - public function setCategory(Category $category) + + public function setCategory(Category $category = null) + { + $this->category = $category; + } + } + +And that's it! Now, if you call ``$category->removeProduct($product)``, the ``category_id`` +on that ``Product`` will be set to ``null`` in the database. + +But, instead of setting the ``category_id`` to null, what if you want the ``Product`` +to be *deleted* if it becomes "orphaned" (i.e. without a ``Category``)? To choose +that behavior, use the `orphanRemoval`_ option inside ``Category``:: + + // src/Entity/Category.php + + // ... + + /** + * @ORM\OneToMany(targetEntity="App\Entity\Product", mappedBy="category", orphanRemoval=true) + */ + private $products; + +Thanks to this, if the ``Product`` is removed from the ``Category``, it will be +removed from the database entirely. + More Information on Associations -------------------------------- @@ -437,7 +537,7 @@ Doctrine's `Association Mapping Documentation`_. If you're using annotations, you'll need to prepend all annotations with ``@ORM\`` (e.g. ``@ORM\OneToMany``), which is not reflected in Doctrine's - documentation. You'll also need to include the ``use Doctrine\ORM\Mapping as ORM;`` - statement, which *imports* the ``ORM`` annotations prefix. + documentation. .. _`Association Mapping Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html +.. _`orphanRemoval`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html#orphan-removal diff --git a/doctrine/custom_dql_functions.rst b/doctrine/custom_dql_functions.rst index e19c9a26eb6..e63e1437865 100644 --- a/doctrine/custom_dql_functions.rst +++ b/doctrine/custom_dql_functions.rst @@ -13,7 +13,7 @@ In Symfony, you can register your custom DQL functions as follows: .. code-block:: yaml - # app/config/config.yml + # config/packages/doctrine.yaml doctrine: orm: # ... @@ -28,7 +28,7 @@ In Symfony, you can register your custom DQL functions as follows: .. code-block:: xml - + - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('doctrine', array( - 'dbal' => array( - 'driver' => 'pdo_mysql', - 'dbname' => 'Symfony', - 'user' => 'root', - 'password' => null, - 'charset' => 'UTF8', - 'server_version' => '5.6', - ), - )); - -For full DBAL configuration options, or to learn how to configure multiple -connections, see :ref:`reference-dbal-configuration`. - -You can then access the Doctrine DBAL connection by accessing the -``database_connection`` service:: +You can then access the Doctrine DBAL connection by autowiring the ``Connection`` +object:: use Doctrine\DBAL\Driver\Connection; @@ -95,6 +49,8 @@ You can then access the Doctrine DBAL connection by accessing the } } +This will pass you the ``database_connection`` service. + Registering custom Mapping Types -------------------------------- @@ -106,7 +62,7 @@ mapping types, read Doctrine's `Custom Mapping Types`_ section of their document .. code-block:: yaml - # app/config/config.yml + # config/packages/doctrine.yaml doctrine: dbal: types: @@ -115,7 +71,7 @@ mapping types, read Doctrine's `Custom Mapping Types`_ section of their document .. code-block:: xml - + + loadFromExtension('doctrine', array( 'dbal' => array( 'mapping_types' => array( diff --git a/doctrine/event_listeners_subscribers.rst b/doctrine/event_listeners_subscribers.rst index 3340ea13997..acb98ac43d4 100644 --- a/doctrine/event_listeners_subscribers.rst +++ b/doctrine/event_listeners_subscribers.rst @@ -207,8 +207,7 @@ to the tag like so: .. code-block:: yaml services: - my.listener: - class: App\EventListener\SearchIndexer + App\EventListener\SearchIndexer: tags: - { name: doctrine.event_listener, event: postPersist, lazy: true } @@ -219,7 +218,7 @@ to the tag like so: xmlns:doctrine="http://symfony.com/schema/dic/doctrine"> - + @@ -230,7 +229,7 @@ to the tag like so: use App\EventListener\SearchIndexer; $container - ->register('my.listener', SearchIndexer::class) + ->autowire(SearchIndexer::class) ->addTag('doctrine.event_listener', array('event' => 'postPersist', 'lazy' => 'true')) ; diff --git a/doctrine/lifecycle_callbacks.rst b/doctrine/lifecycle_callbacks.rst index 6f7bf14a6e4..42344c8098d 100644 --- a/doctrine/lifecycle_callbacks.rst +++ b/doctrine/lifecycle_callbacks.rst @@ -44,7 +44,7 @@ the current date, only when the entity is first persisted (i.e. inserted): .. code-block:: yaml - # src/Resources/config/doctrine/Product.orm.yml + # config/doctrine/Product.orm.yml App\Entity\Product: type: entity # ... @@ -53,7 +53,7 @@ the current date, only when the entity is first persisted (i.e. inserted): .. code-block:: xml - + - - - - - - - - - - - - - mongodb://%mongodb_username%:%mongodb_password%@%mongodb_host%:27017 - - - mongodb://%mongodb_host%:27017 - - - - mongo_client - %mongo.session.options% - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Reference; - use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler; - - $container->loadFromExtension('framework', array( - 'session' => array( - // ... - 'handler_id' => 'session.handler.mongo', - 'cookie_lifetime' => 2592000, // optional, it is set to 30 days here - 'gc_maxlifetime' => 2592000, // optional, it is set to 30 days here - ), - )); - - $container->register('mongo_client', \MongoClient::class) - ->setArguments(array( - // if using a username and password - array('mongodb://%mongodb_username%:%mongodb_password%@%mongodb_host%:27017'), - // if not using a username and password - array('mongodb://%mongodb_host%:27017'), - )); - - $container->register('session.handler.mongo', MongoDbSessionHandler::class) - ->setArguments(array( - new Reference('mongo_client'), - '%mongo.session.options%', - )); - -The parameters used above should be defined somewhere in your application, often in your main -parameters configuration: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/parameters.yml - parameters: - # ... - mongo.session.options: - database: session_db # your MongoDB database name - collection: session # your MongoDB collection name - mongodb_host: 1.2.3.4 # your MongoDB server's IP - mongodb_username: my_username - mongodb_password: my_password - - .. code-block:: xml - - - - - - - - session_db - - session - - - 1.2.3.4 - my_username - my_password - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Reference; - - $container->setParameter('mongo.session.options', array( - 'database' => 'session_db', // your MongoDB database name - 'collection' => 'session', // your MongoDB collection name - )); - $container->setParameter('mongodb_host', '1.2.3.4'); // your MongoDB server's IP - $container->setParameter('mongodb_username', 'my_username'); - $container->setParameter('mongodb_password', 'my_password'); +To see how to configure a similar handler, see :doc:`/doctrine/pdo_session_storage`. Setting Up the MongoDB Collection --------------------------------- -Because MongoDB uses dynamic collection schemas, you do not need to do anything to initialize your -session collection. However, you may want to add an index to improve garbage collection performance. -From the `MongoDB shell`_: +You do not need to do anything to initialize your session collection. However, you +may want to add an index to improve garbage collection performance. From the +`MongoDB shell`_: .. code-block:: javascript use session_db db.session.ensureIndex( { "expires_at": 1 }, { expireAfterSeconds: 0 } ) -.. _installed and configured a MongoDB server: http://docs.mongodb.org/manual/installation/ .. _MongoDB shell: http://docs.mongodb.org/v2.2/tutorial/getting-started-with-the-mongo-shell/ diff --git a/doctrine/multiple_entity_managers.rst b/doctrine/multiple_entity_managers.rst index fca2155f5c9..a7fa6ce29be 100644 --- a/doctrine/multiple_entity_managers.rst +++ b/doctrine/multiple_entity_managers.rst @@ -22,6 +22,7 @@ The following configuration code shows how you can configure two entity managers .. code-block:: yaml + # config/packages/doctrine.yaml doctrine: dbal: default_connection: default @@ -58,6 +59,7 @@ The following configuration code shows how you can configure two entity managers .. code-block:: xml + loadFromExtension('doctrine', array( 'dbal' => array( 'default_connection' => 'default', @@ -172,15 +175,17 @@ When working with multiple connections to create your databases: # Play only with "customer" connection $ php bin/console doctrine:database:create --connection=customer -When working with multiple entity managers to update your schema: +When working with multiple entity managers to generate migrations: .. code-block:: terminal # Play only with "default" mappings - $ php bin/console doctrine:schema:update --force + $ php bin/console doctrine:migrations:diff + $ php bin/console doctrine:migrations:migrate # Play only with "customer" mappings - $ php bin/console doctrine:schema:update --force --em=customer + $ php bin/console doctrine:migrations:diff --em=customer + $ php bin/console doctrine:migrations:migrate --em=customer If you *do* omit the entity manager's name when asking for it, the default entity manager (i.e. ``default``) is returned:: diff --git a/doctrine/pdo_session_storage.rst b/doctrine/pdo_session_storage.rst index e215bb31721..6ce018432bd 100644 --- a/doctrine/pdo_session_storage.rst +++ b/doctrine/pdo_session_storage.rst @@ -17,19 +17,22 @@ To use it, first register a new handler service: .. code-block:: yaml - # app/config/config.yml + # config/services.yaml services: # ... Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler: - public: false arguments: - 'mysql:dbname=mydatabase' - { db_username: myuser, db_password: mypassword } + # If you're using Doctrine & want to re-use that connection, then: + # comment-out the above 2 lines and uncomment the line below + # - !service { class: PDO, factory: 'database_connection:getWrappedConnection' } + .. code-block:: xml - + register(PdoSessionHandler::class) + $storageDefinition = $container->autowire(PdoSessionHandler::class) ->setArguments(array( 'mysql:dbname=mydatabase', array('db_username' => 'myuser', 'db_password' => 'mypassword') @@ -67,7 +70,7 @@ Next, tell Symfony to use your service as the session handler: .. code-block:: yaml - # app/config/config.yml + # config/packages/doctrine.yaml framework: session: # ... @@ -75,7 +78,7 @@ Next, tell Symfony to use your service as the session handler: .. code-block:: xml - + @@ -83,7 +86,7 @@ Next, tell Symfony to use your service as the session handler: .. code-block:: php - // app/config/config.php + // config/packages/doctrine.php use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; // ... @@ -106,19 +109,18 @@ a second array argument to ``PdoSessionHandler``: .. code-block:: yaml - # app/config/config.yml + # config/services.yaml services: # ... Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler: - public: false arguments: - 'mysql:dbname=mydatabase' - { db_table: sessions, db_username: myuser, db_password: mypassword } .. code-block:: xml - + register(PdoSessionHandler::class) + $container->autowire(PdoSessionHandler::class) ->setArguments(array( 'mysql:dbname=mydatabase', array('db_table' => 'sessions', 'db_username' => 'myuser', 'db_password' => 'mypassword') @@ -168,60 +170,6 @@ These are parameters that you must configure: ``db_lifetime_col`` (default ``sess_lifetime``): The name of the lifetime column in your session table (INTEGER). - -Sharing your Database Connection Information --------------------------------------------- - -With the given configuration, the database connection settings are defined for -the session storage connection only. This is OK when you use a separate -database for the session data. - -But if you'd like to store the session data in the same database as the rest -of your project's data, you can use the connection settings from the -``parameters.yml`` file by referencing the database-related parameters defined there: - -.. configuration-block:: - - .. code-block:: yaml - - services: - # ... - - Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler: - public: false - arguments: - - 'mysql:host=%database_host%;port=%database_port%;dbname=%database_name%' - - { db_username: '%database_user%', db_password: '%database_password%' } - - .. code-block:: xml - - - - - - - mysql:host=%database_host%;port=%database_port%;dbname=%database_name% - - %database_user% - %database_password% - - - - - - .. code-block:: php - - // ... - $container->register(PdoSessionHandler::class) - ->setArguments(array( - 'mysql:host=%database_host%;port=%database_port%;dbname=%database_name%', - array('db_username' => '%database_user%', 'db_password' => '%database_password%') - )) - ; - .. _example-sql-statements: Preparing the Database to Store Sessions @@ -231,6 +179,19 @@ Before storing sessions in the database, you must create the table that stores the information. The following sections contain some examples of the SQL statements you may use for your specific database engine. +A great way to run this on production is to generate an empty migration, and then +add this SQL inside: + +.. code-block:: terminal + + $ php bin/console doctrine:migrations:generate + +Find the correct SQL below and put it inside that file. Then execute it with: + +.. code-block:: terminal + + $ php bin/console doctrine:migrations:migrate + MySQL ~~~~~ diff --git a/doctrine/registration_form.rst b/doctrine/registration_form.rst index 24b8941d8da..b423850ab2f 100644 --- a/doctrine/registration_form.rst +++ b/doctrine/registration_form.rst @@ -10,6 +10,12 @@ Creating a registration form is pretty easy - it *really* means just creating a form that will update some ``User`` model object (a Doctrine entity in this example) and then save it. +First, make sure you have all the dependencies you need installed: + +.. code-block:: terminal + + $ composer require doctrine form security + .. tip:: The popular `FOSUserBundle`_ provides a registration form, reset password @@ -271,14 +277,14 @@ encoder in the security configuration: .. code-block:: yaml - # app/config/security.yml + # config/packages/security.yml security: encoders: App\Entity\User: bcrypt .. code-block:: xml - + loadFromExtension('security', array( @@ -304,76 +310,20 @@ encoder in the security configuration: In this case the recommended ``bcrypt`` algorithm is used. If needed, check out the :ref:`user password encoding ` article. -.. note:: - - If you decide to NOT use annotation routing (shown above), then you'll - need to create a route to this controller: - - .. configuration-block:: - - .. code-block:: yaml - - # config/routes.yaml - user_registration: - path: /register - defaults: { _controller: AppBundle:Registration:register } - - .. code-block:: xml - - - - - - - AppBundle:Registration:register - - - - .. code-block:: php - - // config/routes.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('user_registration', new Route('/register', array( - '_controller' => 'AppBundle:Registration:register', - ))); - - return $collection; - Next, create the template: -.. configuration-block:: - - .. code-block:: html+twig - - {# templates/registration/register.html.twig #} - - {{ form_start(form) }} - {{ form_row(form.username) }} - {{ form_row(form.email) }} - {{ form_row(form.plainPassword.first) }} - {{ form_row(form.plainPassword.second) }} - - - {{ form_end(form) }} - - .. code-block:: html+php - - +.. code-block:: html+twig - start($form) ?> - row($form['username']) ?> - row($form['email']) ?> + {# templates/registration/register.html.twig #} - row($form['plainPassword']['first']) ?> - row($form['plainPassword']['second']) ?> + {{ form_start(form) }} + {{ form_row(form.username) }} + {{ form_row(form.email) }} + {{ form_row(form.plainPassword.first) }} + {{ form_row(form.plainPassword.second) }} - - end($form) ?> + + {{ form_end(form) }} See :doc:`/form/form_customization` for more details. @@ -385,7 +335,8 @@ your database schema using this command: .. code-block:: terminal - $ php bin/console doctrine:schema:update --force + $ php bin/console doctrine:migrations:diff + $ php bin/console doctrine:migrations:migrate That's it! Head to ``/register`` to try things out! diff --git a/doctrine/resolve_target_entity.rst b/doctrine/resolve_target_entity.rst index 4bf4aba7566..c2563f7b4dd 100644 --- a/doctrine/resolve_target_entity.rst +++ b/doctrine/resolve_target_entity.rst @@ -112,7 +112,7 @@ about the replacement: .. code-block:: yaml - # app/config/config.yml + # config/packages/doctrine.yaml doctrine: # ... orm: @@ -122,7 +122,7 @@ about the replacement: .. code-block:: xml - + Date: Thu, 23 Nov 2017 20:18:28 -0500 Subject: [PATCH 6/8] tweaks thanks to @yceruto --- doctrine.rst | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/doctrine.rst b/doctrine.rst index 5eba0395996..ada7af29348 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -130,12 +130,12 @@ in the database. This is usually done with annotations: private $id; /** - * @ORM\Column(type="string", length=100, nullable=false) + * @ORM\Column(type="string", length=100) */ private $name; /** - * @ORM\Column(type="decimal", scale=2, nullable=false) + * @ORM\Column(type="decimal", scale=2, nullable=true) */ private $price; } @@ -153,11 +153,10 @@ in the database. This is usually done with annotations: name: type: string length: 100 - nullable: false price: type: decimal scale: 2 - nullable: false + nullable: true .. code-block:: xml @@ -172,8 +171,8 @@ in the database. This is usually done with annotations: - - + + @@ -455,8 +454,8 @@ Once you have a repository object, you have many helper methods:: // query for multiple Product objects matching the name, ordered by price $products = $repository->findBy( - array('name' => 'Keyboard'), - array('price' => 'ASC') + ['name' => 'Keyboard'], + ['price' => 'ASC'] ); // find *all* Product objects @@ -584,6 +583,10 @@ But what if you need a more complex query? When you generated your entity with class ProductRepository extends ServiceEntityRepository { + public function __construct(RegistryInterface $registry) + { + parent::__construct($registry, Product::class); + } } When you fetch your repository (i.e. ``->getRepository(Product::class)``, it is @@ -598,6 +601,8 @@ a new method for this to your repository:: // ... class ProductRepository extends ServiceEntityRepository { + // ... + /** * @param $price * @return Product[] @@ -672,7 +677,7 @@ Or directly with SQL if you need to:: ORDER BY p.price ASC '; $stmt = $conn->prepare($sql); - $stmt->execute(array('price' => 10)); + $stmt->execute(['price' => 10]); // returns an array of arrays (i.e. a raw data set) return $stmt->fetchAll(); From 04603c7f497a885597240a91f3752c6efb7832a0 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 23 Nov 2017 20:20:53 -0500 Subject: [PATCH 7/8] Fixing build problems --- doctrine.rst | 4 ++-- security/entity_provider.rst | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/doctrine.rst b/doctrine.rst index ada7af29348..92875f9b562 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -636,7 +636,7 @@ write custom queries. Now, you can call this method on the repository:: // ... -If you're in a :ref:`services-constructor-injection`_, you can type-hint the +If you're in a :ref:`services-constructor-injection`, you can type-hint the ``ProductRepository`` class and inject it like normal. For more details, see the `Query Builder`_ Documentation from Doctrine. @@ -730,7 +730,7 @@ Learn more .. _`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 +.. _`Doctrine's Mapping Types 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 .. _`Mapping Types Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping diff --git a/security/entity_provider.rst b/security/entity_provider.rst index 53a534485ae..aff5dfdba03 100644 --- a/security/entity_provider.rst +++ b/security/entity_provider.rst @@ -447,11 +447,6 @@ interface only requires one method: ``loadUserByUsername($username)``:: } } -.. tip:: - - Don't forget to add the repository class to the - :doc:`mapping definition of your entity `. - To finish this, just remove the ``property`` key from the user provider in ``security.yml``: From 39c4a8321ddb432e2255695baf0870b2ac1718ea Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 27 Nov 2017 09:03:58 -0500 Subject: [PATCH 8/8] fixes thanks to the team! --- doctrine.rst | 18 +++++++++++------- doctrine/associations.rst | 2 +- doctrine/dbal.rst | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/doctrine.rst b/doctrine.rst index 92875f9b562..f5c07c3c5e1 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -48,14 +48,14 @@ database for you: $ php bin/console doctrine:database:create -There are more optiosn in ``config/packages/doctrine.yaml`` that you can configure, +There are more options in ``config/packages/doctrine.yaml`` that you can configure, including your ``server_version`` (e.g. 5.7 if you're using MySQL 5.7), which may affect how Doctrine functions. .. tip:: - There are many other Doctrine commands. To see a full list, run - ``php bin/console list doctrine``. + There are many other Doctrine commands. Run ``php bin/console list doctrine`` + to see a full list. Creating an Entity Class ------------------------ @@ -178,7 +178,8 @@ in the database. This is usually done with annotations: Doctrine supports a wide variety of different field types, each with their own options. To see a full list of types and options, see `Doctrine's Mapping Types documentation`_. -If you want to use ``xml`` instead of annotations, you'll need to configure this in your +If you want to use XML instead of annotations, add ``type: xml`` and +``dir: '%kernel.project_dir%/config/doctrine`` to the entity mappings in your ``config/packages/doctrine.yaml`` file. .. caution:: @@ -363,7 +364,7 @@ Try it out! http://localhost:8000/product -Congratulations! You just created your first row the ``product`` table. To prove it, +Congratulations! You just created your first row in the ``product`` table. To prove it, you can query the database directly: .. code-block:: terminal @@ -589,7 +590,7 @@ But what if you need a more complex query? When you generated your entity with } } -When you fetch your repository (i.e. ``->getRepository(Product::class)``, it is +When you fetch your repository (i.e. ``->getRepository(Product::class)``), it is *actually* an instance of *this* object! This is because of the ``repositoryClass`` config that was generated at the top of your ``Product`` entity class. @@ -601,7 +602,10 @@ a new method for this to your repository:: // ... class ProductRepository extends ServiceEntityRepository { - // ... + public function __construct(RegistryInterface $registry) + { + parent::__construct($registry, Product::class); + } /** * @param $price diff --git a/doctrine/associations.rst b/doctrine/associations.rst index 318b907b72d..e5688549baf 100644 --- a/doctrine/associations.rst +++ b/doctrine/associations.rst @@ -335,7 +335,7 @@ What's important is the fact that you have easy access to the product's related category, but the category data isn't actually retrieved until you ask for the category (i.e. it's "lazily loaded"). -Because we mapped the optiona ``OneToMany`` side, you can also query in the other +Because we mapped the optional ``OneToMany`` side, you can also query in the other direction:: public function showProductsAction($id) diff --git a/doctrine/dbal.rst b/doctrine/dbal.rst index aad67f2c0cc..43f387de802 100644 --- a/doctrine/dbal.rst +++ b/doctrine/dbal.rst @@ -28,7 +28,7 @@ To get started, configure the ``DATABASE_URL`` environment variable in ``.env``: # .env # customize this line! - DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?charset=utf8mb4&serverVersion=5.7" + DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name" Further things can be configured in ``config/packages/doctrine.yaml``. For the full DBAL configuration options, or to learn how to configure multiple connections,