Skip to content
  • About
    • What is Symfony?
    • Community
    • News
    • Contributing
    • Support
  • Documentation
    • Symfony Docs
    • Symfony Book
    • Screencasts
    • Symfony Bundles
    • Symfony Cloud
    • Training
  • Services
    • SensioLabs Professional services to help you with Symfony
    • Platform.sh for Symfony Best platform to deploy Symfony apps
    • SymfonyInsight Automatic quality checks for your apps
    • Symfony Certification Prove your knowledge and boost your career
    • Blackfire Profile and monitor performance of your apps
  • Other
  • Blog
  • Download
sponsored by SensioLabs
  1. Home
  2. Documentation
  3. Databases and the Doctrine ORM
  • Documentation
  • Book
  • Reference
  • Bundles
  • Cloud

Table of Contents

  • Installing Doctrine
    • Configuring the Database
  • Creating an Entity Class
  • Migrations: Creating the Database Tables/Schema
  • Migrations & Adding more Fields
  • Persisting Objects to the Database
  • Fetching Objects from the Database
  • Automatically Fetching Objects (ParamConverter)
  • Updating an Object
  • Deleting an Object
  • Querying for Objects: The Repository
  • Querying with DQL or SQL
  • Configuration
  • Relationships and Associations
  • Dummy Data Fixtures
  • Learn more

Databases and the Doctrine ORM

Edit this page

Warning: You are browsing the documentation for Symfony 4.2, which is no longer maintained.

Read the updated version of this page for Symfony 6.2 (the current stable version).

Databases and the Doctrine ORM

Screencast

Do you prefer video tutorials? Check out the Doctrine screencast series.

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

This article is all about using the Doctrine ORM. If you prefer to use raw database queries, see the "How to Use Doctrine DBAL" article instead.

You can also persist data to MongoDB using Doctrine ODM library. See the "`DoctrineMongoDBBundle`_" documentation.

Installing Doctrine

First, install Doctrine support via the ORM pack, as well as the MakerBundle, which will help generate some code:

1
2
$ composer require symfony/orm-pack
$ composer require --dev symfony/maker-bundle

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:

1
2
3
4
5
6
7
# .env (or override DATABASE_URL in .env.local to avoid committing your changes)

# customize this line!
DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name"

# to use sqlite:
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"

Caution

If the username, password, host or database name contain any character considered special in a URI (such as +, @, $, #, /, :, *, !), you must encode them. See RFC 3986 for the full list of reserved characters or use the urlencode function to encode them. In this case you need to remove the resolve: prefix in config/packages/doctrine.yaml to avoid errors: url: '%env(resolve:DATABASE_URL)%'

Now that your connection parameters are setup, Doctrine can create the db_name database for you:

1
$ php bin/console doctrine:database:create

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. 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.

You can use the make:entity command to create this class and any fields you need. The command will ask you some questions - answer them like done below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ php bin/console make:entity

Class name of the entity to create or update:
> Product

New property name (press <return> to stop adding fields):
> name

Field type (enter ? to see all types) [string]:
> string

Field length [255]:
> 255

Can this field be null in the database (nullable) (yes/no) [no]:
> no

New property name (press <return> to stop adding fields):
> price

Field type (enter ? to see all types) [string]:
> integer

Can this field be null in the database (nullable) (yes/no) [no]:
> no

New property name (press <return> to stop adding fields):
>
(press enter again to finish)

1.3

The interactive behavior of the make:entity command was introduced in MakerBundle 1.3.

Woh! You now have a new src/Entity/Product.php file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// src/Entity/Product.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\ProductRepository")
 */
class Product
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="integer")
     */
    private $price;

    public function getId()
    {
        return $this->id;
    }

    // ... getter and setter methods
}

Note

Confused why the price is an integer? Don't worry: this is just an example. But, storing prices as integers (e.g. 100 = $1 USD) can avoid rounding issues.

Note

If you are using an SQLite database, you'll see the following error: PDOException: SQLSTATE[HY000]: General error: 1 Cannot add a NOT NULL column with default value NULL. Add a nullable=true option to the description property to fix the problem.

Caution

There is a limit of 767 bytes for the index key prefix when using InnoDB tables in MySQL 5.6 and earlier versions. String columns with 255 character length and utf8mb4 encoding surpass that limit. This means that any column of type string and unique=true must set its maximum length to 190. Otherwise, you'll see this error: "[PDOException] SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes".

This class is called an "entity". And soon, you'll be able to save and query Product objects to a product table in your database. Each property in the Product entity can be mapped to a column in that table. This is usually done with annotations: the @ORM\... comments that you see above each property:

The make:entity command is a tool to make life easier. But this is your code: add/remove fields, add/remove methods or update configuration.

Doctrine supports a wide variety of field types, each with their own options. To see a full list, check out 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.

Caution

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, change the table name with @ORM\Table(name="groups") above the class or configure the column name with the name="group_name" option.

Migrations: Creating the Database Tables/Schema

The Product class is fully-configured and ready to save to a product table. If you just defined this class, your database doesn't actually have the product table yet. To add it, you can leverage the DoctrineMigrationsBundle, which is already installed:

1
$ php bin/console make:migration

If everything worked, you should see something like this:

SUCCESS!

Next: Review the new migration "src/Migrations/Version20180207231217.php" Then: Run the migration with php bin/console doctrine:migrations:migrate

If you open this file, it contains the SQL needed to update your database! To run that SQL, execute your migrations:

1
$ php bin/console doctrine:migrations:migrate

This command executes all migration files that have not already been run against your database. You should run this command on production when you deploy to keep your production database up-to-date.

Migrations & Adding more Fields

But what if you need to add a new field property to Product, like a description? You can edit the class to add the new property. But, you can also use make:entity again:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ php bin/console make:entity

Class name of the entity to create or update
> Product

New property name (press <return> to stop adding fields):
> description

Field type (enter ? to see all types) [string]:
> text

Can this field be null in the database (nullable) (yes/no) [no]:
> no

New property name (press <return> to stop adding fields):
>
(press enter again to finish)

This adds the new description property and getDescription() and setDescription() methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Entity/Product.php
// ...

class Product
{
    // ...

+     /**
+      * @ORM\Column(type="text")
+      */
+     private $description;

    // getDescription() & setDescription() were also added
}

The new property is mapped, but it doesn't exist yet in the product table. No problem! Generate a new migration:

1
$ php bin/console make:migration

This time, the SQL in the generated file will look like this:

1
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! Like before, execute your migrations:

1
$ 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 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 execute them when you deploy.

Tip

If you prefer to add new properties manually, the make:entity command can generate the getter & setter methods for you:

1
$ php bin/console make:entity --regenerate

If you make some changes and want to regenerate all getter/setter methods, also pass --overwrite.

Persisting Objects to the Database

It's time to save a Product object to the database! Let's create a new controller to experiment:

1
$ php bin/console make:controller ProductController

Inside the controller, you can create a new Product object, set data on it, and save it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// src/Controller/ProductController.php
namespace App\Controller;

// ...
use App\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response;

class ProductController extends AbstractController
{
    /**
     * @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 $entityManager)
        $entityManager = $this->getDoctrine()->getManager();

        $product = new Product();
        $product->setName('Keyboard');
        $product->setPrice(1999);
        $product->setDescription('Ergonomic and stylish!');

        // tell Doctrine you want to (eventually) save the Product (no queries yet)
        $entityManager->persist($product);

        // actually executes the queries (i.e. the INSERT query)
        $entityManager->flush();

        return new Response('Saved new product with id '.$product->getId());
    }
}

Try it out!

http://localhost:8000/product

Congratulations! You just created your first row in the product table. To prove it, you can query the database directly:

1
2
3
4
$ php bin/console doctrine:query:sql 'SELECT * FROM product'

# on Windows systems not using Powershell, run this command instead:
# php bin/console doctrine:query:sql "SELECT * FROM product"

Take a look at the previous example in more detail:

  • line 18 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 20-23 In this section, you instantiate and work with the $product object like any other normal PHP object.
  • line 26 The persist($product) call tells Doctrine to "manage" the $product object. This does not cause a query to be made to the database.
  • line 29 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

If the flush() call fails, a Doctrine\ORM\ORMException exception is thrown. See Transactions and Concurrency.

Whether you're creating or updating objects, the workflow is always the same: Doctrine is smart enough to know if it should INSERT or UPDATE your entity.

Fetching Objects from the Database

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Controller/ProductController.php
// ...

/**
 * @Route("/product/{id}", name="product_show")
 */
public function show($id)
{
    $product = $this->getDoctrine()
        ->getRepository(Product::class)
        ->find($id);

    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$id
        );
    }

    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]);
}

Try it out!

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.

Once you have a repository object, you have many helper methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$repository = $this->getDoctrine()->getRepository(Product::class);

// look for a single Product by its primary key (usually "id")
$product = $repository->find($id);

// look for a single Product by name
$product = $repository->findOneBy(['name' => 'Keyboard']);
// or find by name and price
$product = $repository->findOneBy([
    'name' => 'Keyboard',
    'price' => 1999,
]);

// look for multiple Product objects matching the name, ordered by price
$products = $repository->findBy(
    ['name' => 'Keyboard'],
    ['price' => 'ASC']
);

// look for *all* Product objects
$products = $repository->findAll();

You can also add custom methods for more complex queries! More on that later in the Databases and the Doctrine ORM section.

Tip

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:

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. If you don't see the web debug toolbar, try running composer require --dev symfony/profiler-pack 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:

1
$ composer require sensio/framework-extra-bundle

Now, simplify your controller:

1
2
3
4
5
6
7
8
9
10
11
// src/Controller/ProductController.php
use App\Entity\Product;

/**
 * @Route("/product/{id}", name="product_show")
 */
public function show(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, you interact with it the same as with any PHP model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * @Route("/product/edit/{id}")
 */
public function update($id)
{
    $entityManager = $this->getDoctrine()->getManager();
    $product = $entityManager->getRepository(Product::class)->find($id);

    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$id
        );
    }

    $product->setName('New product name!');
    $entityManager->flush();

    return $this->redirectToRoute('product_show', [
        'id' => $product->getId()
    ]);
}

Using Doctrine to edit an existing product consists of three steps:

  1. fetching the object from Doctrine;
  2. modifying the object;
  3. calling flush() on the entity manager.

You can call $entityManager->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:

1
2
$entityManager->remove($product);
$entityManager->flush();

As you might expect, the remove() method notifies Doctrine that you'd like to remove the given object from the database. The DELETE query isn't actually executed until the flush() method is called.

Querying for Objects: The Repository

You've already seen how the repository object allows you to run basic queries without any work:

1
2
3
4
// from inside a controller
$repository = $this->getDoctrine()->getRepository(Product::class);

$product = $repository->find($id);

But what if you need a more complex query? When you generated your entity with make:entity, the command also generated a ProductRepository class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Repository/ProductRepository.php
namespace App\Repository;

use App\Entity\Product;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;

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 actually an instance of this object! This is because of the repositoryClass config that was generated at the top of your Product entity class.

Suppose you want to query for all Product objects greater than a certain price. Add a new method for this to your repository:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// src/Repository/ProductRepository.php

// ...
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 select 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 = $qb->setMaxResults(1)->getOneOrNullResult();
    }
}

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:

1
2
3
4
5
6
7
8
// from inside a controller
$minPrice = 1000;

$products = $this->getDoctrine()
    ->getRepository(Product::class)
    ->findAllGreaterThanPrice($minPrice);

// ...

If you're in a Service Container, you can type-hint the ProductRepository class and inject it like normal.

For more details, see the Query Builder Documentation from Doctrine.

Querying with DQL or SQL

In addition to the query builder, you can also query with Doctrine Query Language:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Repository/ProductRepository.php
// ...

public function findAllGreaterThanPrice($price): array
{
    $entityManager = $this->getEntityManager();

    $query = $entityManager->createQuery(
        'SELECT p
        FROM App\Entity\Product p
        WHERE p.price > :price
        ORDER BY p.price ASC'
    )->setParameter('price', $price);

    // returns an array of Product objects
    return $query->execute();
}

Or directly with SQL if you need to:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Repository/ProductRepository.php
// ...

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' => $price]);

    // returns an array of arrays (i.e. a raw data set)
    return $stmt->fetchAll();
}

With SQL, you will get back raw data, not objects (unless you use the NativeQuery functionality).

Configuration

See the Doctrine config reference.

Relationships and Associations

Doctrine provides all the functionality you need to manage database relationships (also known as associations), including ManyToOne, OneToMany, OneToOne and ManyToMany relationships.

For info, see How to Work with Doctrine Associations / Relations.

Dummy Data Fixtures

Doctrine provides a library that allows you to programmatically load testing data into your project (i.e. "fixture data"). Install it with:

1
$ composer require --dev doctrine/doctrine-fixtures-bundle

Then, use the make:fixtures command to generate an empty fixture class:

1
2
3
4
$ php bin/console make:fixtures

The class name of the fixtures to create (e.g. AppFixtures):
> ProductFixture

Customize the new class to load Product objects into Doctrine:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/DataFixtures/ProductFixture.php
namespace App\DataFixtures;

use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;

class ProductFixture extends Fixture
{
    public function load(ObjectManager $manager)
    {
        $product = new Product();
        $product->setName('Priceless widget!');
        $product->setPrice(14.50);
        $product->setDescription('Ok, I guess it *does* have a price');
        $manager->persist($product);

        // add more products

        $manager->flush();
    }
}

Empty the database and reload all the fixture classes with:

1
$ php bin/console doctrine:fixtures:load

For information, see the "`DoctrineFixturesBundle`_" documentation.

Learn more

  • How to Work with Doctrine Associations / Relations
  • How to use Doctrine Extensions: Timestampable, Sluggable, Translatable, etc.
  • How to Work with Lifecycle Callbacks
  • Doctrine Event Listeners and Subscribers
  • How to Implement a Registration Form
  • How to Register custom DQL Functions
  • How to Use Doctrine DBAL
  • How to Work with multiple Entity Managers and Connections
  • How to Use PdoSessionHandler to Store Sessions in the Database
  • How to Use MongoDbSessionHandler to Store Sessions in a MongoDB Database
  • How to Define Relationships with Abstract Classes and Interfaces
  • How to Generate Entities from an Existing Database
  • DoctrineFixturesBundle
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version
    We stand with Ukraine.
    Version:
    Check Code Performance in Dev, Test, Staging & Production

    Check Code Performance in Dev, Test, Staging & Production

    Be trained by SensioLabs experts (2 to 6 day sessions -- French or English).

    Be trained by SensioLabs experts (2 to 6 day sessions -- French or English).

    Symfony footer

    ↓ Our footer now uses the colors of the Ukrainian flag because Symfony stands with the people of Ukraine.

    Avatar of Daniel Werner, a Symfony contributor

    Thanks Daniel Werner (@powerdan) for being a Symfony contributor

    5 commits • 23 lines changed

    View all contributors that help us make Symfony

    Become a Symfony contributor

    Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

    Learn how to contribute

    Symfony™ is a trademark of Symfony SAS. All rights reserved.

    • What is Symfony?

      • Symfony at a Glance
      • Symfony Components
      • Case Studies
      • Symfony Releases
      • Security Policy
      • Logo & Screenshots
      • Trademark & Licenses
      • symfony1 Legacy
    • Learn Symfony

      • Symfony Docs
      • Symfony Book
      • Reference
      • Bundles
      • Best Practices
      • Training
      • eLearning Platform
      • Certification
    • Screencasts

      • Learn Symfony
      • Learn PHP
      • Learn JavaScript
      • Learn Drupal
      • Learn RESTful APIs
    • Community

      • SymfonyConnect
      • Support
      • How to be Involved
      • Code of Conduct
      • Events & Meetups
      • Projects using Symfony
      • Downloads Stats
      • Contributors
      • Backers
    • Blog

      • Events & Meetups
      • A week of symfony
      • Case studies
      • Cloud
      • Community
      • Conferences
      • Diversity
      • Documentation
      • Living on the edge
      • Releases
      • Security Advisories
      • SymfonyInsight
      • Twig
      • SensioLabs
    • Services

      • SensioLabs services
      • Train developers
      • Manage your project quality
      • Improve your project performance
      • Host Symfony projects

      Deployed on

    Follow Symfony

    Search by Algolia