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 4ff17e75bb4..f5c07c3c5e1 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -4,320 +4,128 @@ 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. + This article is all about using the Doctrine ORM. If you prefer to use raw + database queries, see the ":doc:`/doctrine/dbal`" article instead. + + You can also persist data to `MongoDB`_ using Doctrine ODM library. See the + "`DoctrineMongoDBBundle`_" documentation. + +Installing Doctrine +------------------- - You can also persist data to `MongoDB`_ using Doctrine ODM library. For - more information, read the "`DoctrineMongoDBBundle`_" - documentation. +First, install Doctrine, as well as the MakerBundle, which will help generate some +code: -A Simple Example: A Product ---------------------------- +.. code-block:: terminal -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. + composer require doctrine maker Configuring the Database ~~~~~~~~~~~~~~~~~~~~~~~~ -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: +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:: yaml +.. code-block:: text - # app/config/parameters.yml - parameters: - database_host: localhost - database_name: test_project - database_user: root - database_password: password + # .env - # ... + # customize this line! + DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name" -.. note:: + # to use sqlite: + # DATABASE_URL="sqlite://%kernel.project_dir%/var/app.db" - Defining the configuration via ``parameters.yml`` is just a convention. - The parameters defined in that file are referenced by the main configuration - file when setting up Doctrine: - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine: - dbal: - driver: pdo_mysql - host: '%database_host%' - dbname: '%database_name%' - user: '%database_user%' - password: '%database_password%' - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // 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%', - ), - )); - - 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`. - -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`_. +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. -.. 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. Run ``php bin/console list doctrine`` + to see a full list. 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") + */ + 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; @@ -327,22 +135,16 @@ directly inside the ``Product`` class via DocBlock annotations: private $name; /** - * @ORM\Column(type="decimal", scale=2) + * @ORM\Column(type="decimal", scale=2, nullable=true) */ private $price; - - /** - * @ORM\Column(type="text") - */ - private $description; } .. code-block:: yaml - # src/Resources/config/doctrine/Product.orm.yml + # config/doctrine/Product.orm.yml App\Entity\Product: type: entity - table: product id: id: type: integer @@ -354,317 +156,319 @@ directly inside the ``Product`` class via DocBlock annotations: price: type: decimal scale: 2 - description: - type: text + nullable: true .. code-block:: xml - + - + - - + -.. 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`_. +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. - 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")`` above the class or configure 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 - After creating your entities you should validate the mappings with the - following command: + $ php bin/console doctrine:migrations:diff - .. code-block:: terminal +This time, the SQL in the generated file will look like this: + +.. code-block:: sql + + ALTER TABLE product ADD description LONGTEXT NOT NULL + +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: + +.. code-block:: terminal - $ php bin/console doctrine:schema:validate + $ 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 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // src/Entity/Product + // ... -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: + class Product + { + // all of your properties -.. code-block:: terminal + public function getId() + { + return $this->id; + } - $ php bin/console doctrine:schema:update --force + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + // ... getters & setters for price & description + } .. tip:: - Actually, this command is incredibly powerful. It compares what - your database *should* look like (based on the mapping information of - your entities) with how it *actually* looks, and executes the SQL statements - needed to *update* the database schema to where it should be. In other - words, if you add a new property with mapping metadata to ``Product`` - and run this command, it will execute the "ALTER TABLE" statement needed - to add that new column to the existing ``product`` table. + Typically you won't need a ``setId()`` method: Doctrine will set this for you + automatically. - 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. +Persisting Objects to the Database +---------------------------------- - 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. +It's time to save a ``Product`` object to the database! Let's create a new controller +to experiment: -Your database now has a fully-functional ``product`` table with columns that -match the metadata you've specified. +.. code-block:: terminal -Persisting Objects to the Database -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + $ php bin/console make:controller ProductController -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:: +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 - If you're following along with this example, you'll need to create a - route that points to this action to see it work. +Congratulations! You just created your first row in the ``product`` table. To prove it, +you can query the database directly: + +.. code-block:: terminal + + $ 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. - -.. 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. +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. 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 }} + // return $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:: +job is to help you fetch entities of a certain class. - 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. - -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); + // query for a single Product by its primary key (usually "id") + $product = $repository->find($id); - // dynamic method names to find a single product based on a column value - $product = $repository->findOneById($productId); - $product = $repository->findOneByName('Keyboard'); + // 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, + ]); - // dynamic method names to find a group of products based on a column value - $products = $repository->findByPrice(19.99); + // query for multiple Product objects matching the name, ordered by price + $products = $repository->findBy( + ['name' => 'Keyboard'], + ['price' => 'ASC'] + ); - // find *all* products + // find *all* Product objects $products = $repository->findAll(); -.. note:: - - Of course, you can also issue complex queries, which you'll learn more - about in the :ref:`doctrine-queries` section. - -You can also take advantage of the useful ``findBy()`` and ``findOneBy()`` methods -to easily fetch objects based on multiple conditions:: - - $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 - $products = $repository->findBy( - array('name' => 'Keyboard'), - array('price' => 'ASC') - ); +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 @@ -672,32 +476,66 @@ to easily fetch objects based on multiple conditions:: 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. -Updating an Object -~~~~~~~~~~~~~~~~~~ +Automatically Fetching Objects (ParamConverter) +----------------------------------------------- -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:: +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; // ... - public function updateAction($productId) + /** + * @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 +------------------ + +Once you've fetched an object from Doctrine, updating it is easy:: + + /** + * @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: @@ -706,13 +544,11 @@ 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 -~~~~~~~~~~~~~~~~~~ +------------------ Deleting an object is very similar, but requires a call to the ``remove()`` method of the entity manager:: @@ -721,168 +557,192 @@ 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 + { + public function __construct(RegistryInterface $registry) + { + parent::__construct($registry, Product::class); + } + } - $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 + { + public function __construct(RegistryInterface $registry) + { + parent::__construct($registry, Product::class); + } + + /** + * @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(['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`. +(also known as associations), including ManyToOne, OneToMany, OneToOne and ManyToMany +relationships. -Final Thoughts --------------- +For info, see :doc:`/doctrine/associations`. -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. +Dummy Data Fixtures +------------------- -Doctrine has a lot more complex features to learn, like relationships, complex queries, -and event listeners. +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 ---------- .. 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/ -.. _`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 -.. _`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 80f305fffff..e5688549baf 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:: -This command generates the ``Category`` entity for you, with an ``id`` field, -a ``name`` field and the associated getter and setter functions. + // src/Entity/Category + // ... + + 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. + - - - - - - - - - - .. 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 - + 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/mongodb_session_storage.rst b/doctrine/mongodb_session_storage.rst index fcfecfe3dd2..2435529f11a 100644 --- a/doctrine/mongodb_session_storage.rst +++ b/doctrine/mongodb_session_storage.rst @@ -8,165 +8,24 @@ in a multi-webserver environment. Symfony has a built-in solution for NoSQL database session storage called :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler`. -MongoDB is an open-source document database that provides high performance, -high availability and automatic scaling. This article assumes that you have -already `installed and configured a MongoDB server`_. To use it, you just -need to change/add some parameters in the main configuration file: +To use this, you will need to: -.. configuration-block:: +A) Register a ``MongoDbSessionHandler`` service; - .. code-block:: yaml +B) Configure this under ``framework.session.handler_id`` configuration. - # app/config/config.yml - framework: - session: - # ... - 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 - - services: - # ... - mongo_client: - class: MongoClient - # if using a username and password - arguments: ['mongodb://%mongodb_username%:%mongodb_password%@%mongodb_host%:27017'] - # if not using a username and password - arguments: ['mongodb://%mongodb_host%:27017'] - session.handler.mongo: - class: Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler - arguments: ['@mongo_client', '%mongo.session.options%'] - - .. 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/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()``. 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 - + `. - To finish this, just remove the ``property`` key from the user provider in ``security.yml``: