ℹ️ Skipped - page is already crawled
| Filter | Status | Condition | Details |
|---|---|---|---|
| HTTP status | PASS | download_http_code = 200 | HTTP 200 |
| Age cutoff | PASS | download_stamp > now() - 6 MONTH | 0 months ago |
| History drop | PASS | isNull(history_drop_reason) | No drop reason |
| Spam/ban | PASS | fh_dont_index != 1 AND ml_spam_score = 0 | ml_spam_score=0 |
| Canonical | PASS | meta_canonical IS NULL OR = '' OR = src_unparsed | Not set |
| Property | Value |
|---|---|
| URL | https://symfony.com/doc/current/doctrine.html |
| Last Crawled | 2026-04-10 18:01:07 (2 hours ago) |
| First Indexed | 2016-07-28 23:13:01 (9 years ago) |
| HTTP Status Code | 200 |
| Meta Title | Databases and the Doctrine ORM (Symfony Docs) |
| Meta Description | Screencast Do you prefer video tutorials? Check out the Doctrine screencast series. Symfony provides all the tools you need to use databases in your applications thanks to Doctrine, the best set … |
| Meta Canonical | null |
| Boilerpipe Text | Symfony provides all the tools you need to use databases in your applications
thanks to
Doctrine
, the best set of PHP libraries to work with databases.
These tools support relational databases like MySQL and PostgreSQL and also
NoSQL databases like MongoDB.
Databases are a broad topic, so the documentation is divided in three articles:
This article explains the recommended way to work with
relational databases
in Symfony applications;
Read
this other article
if you need
low-level access
to perform raw SQL queries to relational databases (similar to PHP's
PDO
);
Read
DoctrineMongoDBBundle docs
if you are working with
MongoDB databases
.
Installing Doctrine
First, install Doctrine support via the
orm
Symfony pack
,
as well as the MakerBundle, which will help generate some code:
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
:
Warning
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.
You can use the
urlencode
function to encode them or
the
urlencode environment variable processor
.
In this case you need to remove the
resolve:
prefix in
config/packages/doctrine.yaml
to avoid errors:
url: '%env(DATABASE_URL)%'
Tip
To avoid URL-encoding issues with special characters in credentials, you can
use separate connection parameters instead of the URL format. Define each
value as its own environment variable and wrap it in single quotes in the
.env
file to prevent characters like
$
and
#
from being
interpreted:
Then configure Doctrine to use individual parameters:
Now that your connection parameters are setup, Doctrine can create the
db_name
database for you:
There are more options in
config/packages/doctrine.yaml
that you can configure,
including your
server_version
(e.g. 8.0.37 if you're using MySQL 8.0.37), 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:
Whoa! You now have a new
src/Entity/Product.php
file:
Tip
Starting in
MakerBundle
: v1.57.0 - You can pass either
--with-uuid
or
--with-ulid
to
make:entity
. Leveraging Symfony's
Uid Component
,
this generates an entity with the
id
type as
Uuid
or
Ulid
instead of
int
.
Note
Starting in v1.44.0 -
MakerBundle
: only supports entities using PHP attributes.
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.
Warning
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 attributes:
the
#[ORM\Column(...)]
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.
Warning
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.
Entity Field Types
Doctrine supports a wide variety of
field types
(numbers, strings, enums,
binary, dates, JSON, etc.), each with their own options. Check out the
list of Doctrine mapping types
in the Doctrine documentation.
Symfony also provides the following
additional field types
:
uuid
Class:
UuidType
Stores a
UUID
as a native GUID type if available, or
as a 16-byte binary otherwise:
See
Storing UUIDs in Databases
in the UID component
documentation for more details, including how to use UUIDs as primary keys.
ulid
Class:
UlidType
Stores a
ULID
as a native GUID type if available, or as a
16-byte binary otherwise:
See
Storing ULIDs in Databases
in the UID component
documentation for more details, including how to use ULIDs as primary keys.
DatePoint Types
These types allow storing
DatePoint
objects
from the
Clock component
. They convert to/from
DatePoint
objects automatically.
Type
Extends Doctrine type
Class
date_point
datetime_immutable
DatePointType
day_point
date_immutable
DayPointType
time_point
time_immutable
TimePointType
Example usage:
Tip
Use
date_point
when you want to work with
DatePoint
objects, which makes your code easier to test with the
Clock component
.
Use
datetime_immutable
if you don't need the Clock component features.
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:
Tip
Starting in
MakerBundle
: v1.56.0 - Passing
--formatted
to
make:migration
generates a nice and tidy migration file.
If everything worked, you should see something like this:
If you open this file, it contains the SQL needed to update your database! To run
that SQL, execute your migrations:
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:
This adds the new
description
property and
getDescription()
and
setDescription()
methods:
The new property is mapped, but it doesn't exist yet in the
product
table. No
problem! Generate a new migration:
This time, the SQL in the generated file will look like this:
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:
Warning
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.
This will only execute the
one
new migration file, because DoctrineMigrationsBundle
knows that the first migration was already executed earlier. Internally, 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:
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:
Inside the controller, you can create a new
Product
object, set data on it,
and save it:
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:
Take a look at the previous example in more detail:
line 13
The
EntityManagerInterface $entityManager
argument tells Symfony
to
inject the Entity Manager service
into
the controller method. This object is responsible for saving objects to, and
fetching objects from, the database.
lines 15-18
In this section, you instantiate and work with the
$product
object like any other normal PHP object.
line 21
The
persist($product)
call tells Doctrine to "manage" the
$product
object. This does
not
cause a query to be made to the database.
line 24
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.
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.
Validating Objects
The Symfony validator
can reuse Doctrine metadata to perform
some basic validation tasks. First, add or configure the
auto_mapping option
to define which
entities should be introspected by Symfony to add automatic validation constraints.
Consider the following controller code:
Although the
Product
entity doesn't define any explicit
validation configuration
, if the
auto_mapping
option
includes it in the list of entities to introspect, Symfony will infer some
validation rules for it and will apply them.
For example, given that the
name
property can't be
null
in the database, a
NotNull constraint
is added automatically
to the property (if it doesn't contain that constraint already).
The following table summarizes the mapping between Doctrine metadata and
the corresponding validation constraints added automatically by Symfony:
Doctrine attribute
Validation constraint
Notes
nullable=false
NotNull
Requires installing the
PropertyInfo component
type
Type
Requires installing the
PropertyInfo component
unique=true
UniqueEntity
Â
length
Length
Â
Because
the Form component
as well as
API Platform
internally
use the Validator component, all your forms and web APIs will also automatically
benefit from these automatic validation constraints.
This automatic validation is a nice feature to improve your productivity, but it
doesn't replace the validation configuration entirely. You still need to add
some
validation constraints
to ensure that data
provided by the user is correct.
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:
Another possibility is to use the
ProductRepository
using Symfony's autowiring
and injected by the dependency injection container:
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:
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, install the
profiler
Symfony pack
by running this command:
composer require --dev symfony/profiler-pack
.
For more information, read the
Symfony profiler documentation
.
Automatically Fetching Objects (EntityValueResolver)
In many cases, you can use the
EntityValueResolver
to do the query for you
automatically! You can simplify the controller to:
That's it! The attribute uses the
{id}
from the route to query for the
Product
by the
id
column. If it's not found, a 404 error is thrown.
You can change this behavior by making the controller argument optional. In that
case, no 404 is thrown automatically and you're free to handle the missing entity
yourself:
Tip
When enabled globally, it's possible to disable the behavior on a specific
controller, by using the
MapEntity
set to
disabled
:
Fetch Automatically
By default, automatic fetching only works when your route contains an
{id}
wildcard. The resolver uses it to fetch the entity by its primary key via the
find()
method:
To fetch entities by other properties, use the
{param:argument}
route
syntax. This maps a route parameter to a controller argument and tells the
resolver to query the database using that property:
Tip
If you set the
doctrine.orm.controller_resolver.auto_mapping
option
to
true
, the resolver will attempt to do a
findOneBy()
using
all
route wildcards that match properties on your entity (non-properties
are ignored). This removes the need for the
{param:argument}
syntax,
but the behavior is less explicit and no longer recommended.
You can also configure the mapping explicitly for any controller argument
using the
MapEntity
attribute. You can even control the behavior of the
EntityValueResolver
by using the
MapEntity options
:
Fetch via an Expression
If automatic fetching doesn't work for your use case, you can write an expression
using the
ExpressionLanguage component
:
In the expression, the
repository
variable will be your entity's
Repository class and any route wildcards - like
{product_id}
are
available as variables.
The repository method called in the expression can also return a list of entities.
In that case, update the type of your controller argument:
This can also be used to help resolve multiple arguments:
In the example above, the
$product
argument is handled automatically,
but
$comment
is configured with the attribute since they cannot both follow
the default convention.
If you need to get other information from the request to query the database, you
can also access the request in your expression thanks to the
request
variable. Let's say you want the first or the last comment of a product depending on a query parameter named
sort
:
Fetch via Interfaces
Suppose your
Product
class implements an interface called
ProductInterface
.
If you want to decouple your controllers from the concrete entity implementation,
you can reference the entity by its interface instead.
To enable this, first configure the
resolve_target_entities option
.
Then, your controller can type-hint the interface, and the entity will be
resolved automatically:
MapEntity Options
A number of options are available on the
MapEntity
attribute to
control behavior:
id
If an
id
option is configured and matches a route parameter, then
the resolver will find by the primary key:
mapping
Configures the properties and values to use with the
findOneBy()
method: the key is the route placeholder name and the value is the Doctrine
property name:
stripNull
If true, then when
findOneBy()
is used, any values that are
null
will not be used for the query.
objectManager
By default, the
EntityValueResolver
uses the
default
object manager, but you can configure this:
evictCache
If true, forces Doctrine to always fetch the entity from the database
instead of cache.
disabled
If true, the
EntityValueResolver
will not try to replace the argument.
message
An optional custom message displayed when there's a
NotFoundHttpException
,
but
only in the development environment
(you won't see this message in production):
Updating an Object
Once you've fetched an object from Doctrine, you interact with it the same as
with any PHP model:
Using Doctrine to edit an existing product consists of three steps:
fetching the object from Doctrine;
modifying the object;
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:
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:
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 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:
The string passed to
createQuery()
might look like SQL, but it is
Doctrine Query Language
. This allows you to type queries using commonly
known query language, but referencing PHP objects instead (i.e. in the
FROM
statement).
Now, you can call this method on the repository:
See
Service Container
for how to inject the repository into
any service.
Querying with the Query Builder
Doctrine also provides a
Query Builder
, an object-oriented way to write
queries. It is recommended to use this when queries are built dynamically (i.e.
based on PHP conditions):
Querying with SQL
In addition, you can query directly with SQL if you need to:
With SQL, you will get back raw data, not objects (unless you use the
NativeQuery
functionality).
Learn more
How to Work with Doctrine Associations / Relations
Doctrine Events
How to Register custom DQL Functions
How to Use Doctrine DBAL
How to Work with Multiple Entity Managers and Connections
Referencing Entities with Abstract Classes and Interfaces
How to Test a Doctrine Repository
This work, including the code samples, is licensed under a
Creative Commons BY-SA 3.0
license. |
| Markdown | [Skip to content](https://symfony.com/doc/current/doctrine.html#main-content)
[SymfonyHub](https://symfony.com/doc/current/doctrine.html "Toggle Symfony menu") [SFH](https://symfony.com/doc/current/doctrine.html "Toggle Symfony menu")
[ Learn Symfony today](https://symfony.com/book) [ "Symfony: The Fast Track", a new book to learn Symfony](https://symfony.com/book) [ "Symfony: The Fast Track", a new book to learn Symfony](https://symfony.com/book)
[Connect](https://symfony.com/connect/login?target=https://symfony.com/doc/current/doctrine.html)

SensioLabs is the creator of Symfony and plays a pivotal role in supporting its growth. With a passionate team pushing the boundaries of PHP, SensioLabs helps organizations get the most out of Symfony through quality, high-performance, software vendor-level training and consulting services.
- [International](https://sensiolabs.com/en)
- [France](https://sensiolabs.com/fr)
## In the Spotlight
[](https://insight.symfony.com/)
[](https://www.blackfire.io/?utm_source=symfony&utm_medium=banner&utm_campaign=profiler)
## Open Source
- [Symfony - Web framework](https://symfony.com/)
- [Twig - Templating](https://twig.symfony.com/)
- [PHP Polyfills](https://github.com/symfony/polyfill)
## Products
- [Insight: PHP Quality](https://insight.symfony.com/)
- [Blackfire: Web App performance](https://www.blackfire.io/?utm_source=symfony&utm_medium=banner&utm_campaign=profiler)
- [SymfonyCloud powered by Upsun](https://symfony.com/cloud)
## Solutions & Services
- [Training](https://training.sensiolabs.com/)
- [Certification](https://certification.symfony.com/)
- [Technical Solutions](https://sensiolabs.com/solutions)
- [SensioLabs University](https://university.sensiolabs.com/)
- [Experts](https://expert.sensiolabs.com/)
## Community
- [Community](https://connect.symfony.com/)
- [Conferences](https://live.symfony.com/)
- [Videos](https://www.youtube.com/symfonytv)
- [Partners](https://network.sensiolabs.com/en/partenaires)
## Blogs
[Symfony](https://symfony.com/blog/), [SensioLabs](https://blog.sensiolabs.com/), [Insight](https://blog.insight.symfony.com/), and [Blackfire](https://blog.blackfire.io/?utm_source=symfony&utm_medium=banner&utm_campaign=profiler).
Close
- About
- [What is Symfony?](https://symfony.com/what-is-symfony)
- [Community](https://symfony.com/community)
- [News](https://symfony.com/blog/)
- [Contributing](https://symfony.com/doc/current/contributing/index.html)
- [Support](https://symfony.com/support)
- Documentation
- [Symfony Docs](https://symfony.com/doc)
- [Symfony Book](https://symfony.com/book)
- [Screencasts](https://symfonycasts.com/)
- [Symfony Bundles](https://symfony.com/bundles)
- [Symfony Cloud](https://symfony.com/doc/cloud/)
- [Training](https://sensiolabs.com/training?utm_source=symfony&utm_medium=symfony_submenu&utm_campaign=permanent_referral)
- Services
- [Upsun for Symfony](https://symfony.com/cloud/) Best platform to deploy Symfony apps
- [SymfonyInsight](https://insight.symfony.com/) Automatic quality checks for your apps
- [Symfony Certification](https://certification.symfony.com/) Prove your knowledge and boost your career
- [SensioLabs](https://sensiolabs.com/?utm_source=symfony&utm_medium=symfony_submenu&utm_campaign=permanent_referral) Professional services to help you with Symfony
- [Blackfire](https://www.blackfire.io/?utm_source=symfony&utm_medium=symfonycom_footer&utm_campaign=profiler) Profile and monitor performance of your apps
- Other
- [Blog](https://symfony.com/blog/)
- [Download](https://symfony.com/download)
sponsored by
[SymfonyDay Montreal 2026](https://live.symfony.com/2026-montreal)
June 4, 2026
\+20 talks and workshops
Register now
1. [Home](https://symfony.com/)
2. [Documentation](https://symfony.com/doc)
3. Databases and the Doctrine ORM
Search Symfony Docs
Version:
Table of Contents
- [Installing Doctrine](https://symfony.com/doc/current/doctrine.html#installing-doctrine)
- [Configuring the Database](https://symfony.com/doc/current/doctrine.html#configuring-the-database)
- [Creating an Entity Class](https://symfony.com/doc/current/doctrine.html#creating-an-entity-class)
- [Entity Field Types](https://symfony.com/doc/current/doctrine.html#entity-field-types)
- [Migrations: Creating the Database Tables/Schema](https://symfony.com/doc/current/doctrine.html#migrations-creating-the-database-tables-schema)
- [Migrations & Adding more Fields](https://symfony.com/doc/current/doctrine.html#migrations-adding-more-fields)
- [Persisting Objects to the Database](https://symfony.com/doc/current/doctrine.html#persisting-objects-to-the-database)
- [Validating Objects](https://symfony.com/doc/current/doctrine.html#validating-objects)
- [Fetching Objects from the Database](https://symfony.com/doc/current/doctrine.html#fetching-objects-from-the-database)
- [Automatically Fetching Objects (EntityValueResolver)](https://symfony.com/doc/current/doctrine.html#automatically-fetching-objects-entityvalueresolver)
- [Fetch Automatically](https://symfony.com/doc/current/doctrine.html#fetch-automatically)
- [Fetch via an Expression](https://symfony.com/doc/current/doctrine.html#fetch-via-an-expression)
- [Fetch via Interfaces](https://symfony.com/doc/current/doctrine.html#fetch-via-interfaces)
- [MapEntity Options](https://symfony.com/doc/current/doctrine.html#mapentity-options)
- [Updating an Object](https://symfony.com/doc/current/doctrine.html#updating-an-object)
- [Deleting an Object](https://symfony.com/doc/current/doctrine.html#deleting-an-object)
- [Querying for Objects: The Repository](https://symfony.com/doc/current/doctrine.html#querying-for-objects-the-repository)
- [Querying with the Query Builder](https://symfony.com/doc/current/doctrine.html#querying-with-the-query-builder)
- [Querying with SQL](https://symfony.com/doc/current/doctrine.html#querying-with-sql)
- [Configuration](https://symfony.com/doc/current/doctrine.html#configuration)
- [Relationships and Associations](https://symfony.com/doc/current/doctrine.html#relationships-and-associations)
- [Database Testing](https://symfony.com/doc/current/doctrine.html#database-testing)
- [Doctrine Extensions (Timestampable, Translatable, etc.)](https://symfony.com/doc/current/doctrine.html#doctrine-extensions-timestampable-translatable-etc)
- [Learn more](https://symfony.com/doc/current/doctrine.html#learn-more)
# Databases and the Doctrine ORM
[Edit this page](https://github.com/symfony/symfony-docs/edit/8.0/doctrine.rst)
Screencast
Do you prefer video tutorials? Check out the [Doctrine screencast series](https://symfonycasts.com/screencast/symfony-doctrine).
Symfony provides all the tools you need to use databases in your applications thanks to [Doctrine](https://www.doctrine-project.org/), the best set of PHP libraries to work with databases. These tools support relational databases like MySQL and PostgreSQL and also NoSQL databases like MongoDB.
Databases are a broad topic, so the documentation is divided in three articles:
- This article explains the recommended way to work with **relational databases** in Symfony applications;
- Read [this other article](https://symfony.com/doc/current/doctrine/dbal.html) if you need **low-level access** to perform raw SQL queries to relational databases (similar to PHP's [PDO](https://www.php.net/pdo));
- Read [DoctrineMongoDBBundle docs](https://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html) if you are working with **MongoDB databases**.
## [Installing Doctrine](https://symfony.com/doc/current/doctrine.html#installing-doctrine "Permalink to this headline")
First, install Doctrine support via the `orm` [Symfony pack](https://symfony.com/doc/current/setup.html#symfony-packs), 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](https://symfony.com/doc/current/doctrine.html#configuring-the-database "Permalink to this headline")
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
8
9
10
11
12
13
14
15
16
```
```
# .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?serverVersion=8.0.37"
# to use mariadb:
# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=10.5.8-MariaDB"
# to use sqlite:
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"
# to use postgresql:
# DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=12.19 (Debian 12.19-1.pgdg120+1)&charset=utf8"
# to use oracle:
# DATABASE_URL="oci8://db_user:db_password@127.0.0.1:1521/db_name"
```
Warning
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](https://www.ietf.org/rfc/rfc3986.txt) for the full list of reserved characters. You can use the [urlencode](https://secure.php.net/manual/en/function.urlencode.php "urlencode") function to encode them or the [urlencode environment variable processor](https://symfony.com/doc/current/configuration/env_var_processors.html#urlencode_environment_variable_processor). In this case you need to remove the `resolve:` prefix in `config/packages/doctrine.yaml` to avoid errors: `url: '%env(DATABASE_URL)%'`
Tip
To avoid URL-encoding issues with special characters in credentials, you can use separate connection parameters instead of the URL format. Define each value as its own environment variable and wrap it in single quotes in the `.env` file to prevent characters like `$` and `#` from being interpreted:
```
1
2
```
```
# .env
DATABASE_PASSWORD='p@ss$wo#rd'
```
Then configure Doctrine to use individual parameters:
```
1
2
3
4
5
6
7
8
9
```
```
# config/packages/doctrine.yaml
doctrine:
dbal:
user: '%env(DATABASE_USER)%'
password: '%env(DATABASE_PASSWORD)%'
host: '%env(DATABASE_HOST)%'
port: '%env(DATABASE_PORT)%'
dbname: '%env(DATABASE_NAME)%'
driver: pdo_mysql
```
Now that your connection parameters are setup, Doctrine can create the `db_name` database for you:
Copy
```
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. 8.0.37 if you're using MySQL 8.0.37), 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](https://symfony.com/doc/current/doctrine.html#creating-an-entity-class "Permalink to this headline")
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)
```
Whoa! 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
```
```
// src/Entity/Product.php
namespace App\Entity;
use App\Repository\ProductRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ProductRepository::class)]
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $name = null;
#[ORM\Column]
private ?int $price = null;
public function getId(): ?int
{
return $this->id;
}
// ... getter and setter methods
}
```
Tip
Starting in [MakerBundle](https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html): v1.57.0 - You can pass either `--with-uuid` or `--with-ulid` to `make:entity`. Leveraging Symfony's [Uid Component](https://symfony.com/doc/current/components/uid.html), this generates an entity with the `id` type as [Uuid](https://symfony.com/doc/current/components/uid.html#uuid) or [Ulid](https://symfony.com/doc/current/components/uid.html#ulid) instead of `int`.
Note
Starting in v1.44.0 - [MakerBundle](https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html): only supports entities using PHP attributes.
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.
Warning
There is a [limit of 767 bytes for the index key prefix](https://dev.mysql.com/doc/refman/5.6/en/innodb-limits.html) 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 attributes: the `#[ORM\Column(...)]` 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.
Warning
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](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/basic-mapping.html#quoting-reserved-words) 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.
### [Entity Field Types](https://symfony.com/doc/current/doctrine.html#entity-field-types "Permalink to this headline")
Doctrine supports a wide variety of **field types** (numbers, strings, enums, binary, dates, JSON, etc.), each with their own options. Check out the [list of Doctrine mapping types](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/basic-mapping.html#reference-mapping-types) in the Doctrine documentation.
Symfony also provides the following **additional field types**:
#### [`uuid`](https://symfony.com/doc/current/doctrine.html#uuid "Permalink to this headline")
**Class:** [UuidType](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Bridge/Doctrine/Types/UuidType.php "Symfony\Bridge\Doctrine\Types\UuidType")
Stores a [UUID](https://symfony.com/doc/current/components/uid.html) as a native GUID type if available, or as a 16-byte binary otherwise:
```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
```
```
// src/Entity/Product.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
#[ORM\Entity]
class Product
{
#[ORM\Column(type: UuidType::NAME)]
private Uuid $sku;
// ...
}
```
See [Storing UUIDs in Databases](https://symfony.com/doc/current/components/uid.html#uid-uuid-doctrine) in the UID component documentation for more details, including how to use UUIDs as primary keys.
#### [`ulid`](https://symfony.com/doc/current/doctrine.html#ulid "Permalink to this headline")
**Class:** [UlidType](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Bridge/Doctrine/Types/UlidType.php "Symfony\Bridge\Doctrine\Types\UlidType")
Stores a [ULID](https://symfony.com/doc/current/components/uid.html#ulid) as a native GUID type if available, or as a 16-byte binary otherwise:
```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
```
```
// src/Entity/Product.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UlidType;
use Symfony\Component\Uid\Ulid;
#[ORM\Entity]
class Product
{
#[ORM\Column(type: UlidType::NAME)]
private Ulid $identifier;
// ...
}
```
See [Storing ULIDs in Databases](https://symfony.com/doc/current/components/uid.html#uid-ulid-doctrine) in the UID component documentation for more details, including how to use ULIDs as primary keys.
#### [DatePoint Types](https://symfony.com/doc/current/doctrine.html#datepoint-types "Permalink to this headline")
These types allow storing [DatePoint](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Component/Clock/DatePoint.php "Symfony\Component\Clock\DatePoint") objects from the [Clock component](https://symfony.com/doc/current/components/clock.html). They convert to/from `DatePoint` objects automatically.
| Type | Extends Doctrine type | Class |
|---|---|---|
| `date_point` | `datetime_immutable` | [DatePointType](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Bridge/Doctrine/Types/DatePointType.php "Symfony\Bridge\Doctrine\Types\DatePointType") |
| `day_point` | `date_immutable` | [DayPointType](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Bridge/Doctrine/Types/DayPointType.php "Symfony\Bridge\Doctrine\Types\DayPointType") |
| `time_point` | `time_immutable` | [TimePointType](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Bridge/Doctrine/Types/TimePointType.php "Symfony\Bridge\Doctrine\Types\TimePointType") |
Example usage:
```
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
```
```
// src/Entity/Product.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Clock\DatePoint;
#[ORM\Entity]
class Product
{
// Symfony autodetects the 'date_point' type when type-hinting with DatePoint
#[ORM\Column]
private DatePoint $createdAt;
// you can also set the type explicitly
#[ORM\Column(type: 'date_point')]
private DatePoint $updatedAt;
#[ORM\Column(type: 'day_point')]
public DatePoint $releaseDate;
#[ORM\Column(type: 'time_point')]
public DatePoint $openingTime;
// ...
}
```
Tip
Use `date_point` when you want to work with [DatePoint](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Component/Clock/DatePoint.php "Symfony\Component\Clock\DatePoint") objects, which makes your code easier to test with the [Clock component](https://symfony.com/doc/current/components/clock.html). Use `datetime_immutable` if you don't need the Clock component features.
## [Migrations: Creating the Database Tables/Schema](https://symfony.com/doc/current/doctrine.html#migrations-creating-the-database-tables-schema "Permalink to this headline")
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](https://github.com/doctrine/DoctrineMigrationsBundle), which is already installed:
Copy
```
1
```
```
$ php bin/console make:migration
```
Tip
Starting in [MakerBundle](https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html): v1.56.0 - Passing `--formatted` to `make:migration` generates a nice and tidy migration file.
If everything worked, you should see something like this:
```
1
2
3
4
```
```
SUCCESS!
Next: Review the new migration "migrations/Version20211116204726.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:
Copy
```
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](https://symfony.com/doc/current/doctrine.html#migrations-adding-more-fields "Permalink to this headline")
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
```
```
// src/Entity/Product.php
// ...
+ use Doctrine\DBAL\Types\Types;
class Product
{
// ...
+ #[ORM\Column(type: Types::TEXT)]
+ private string $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:
Copy
```
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:
Copy
```
1
```
```
$ php bin/console doctrine:migrations:migrate
```
Warning
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.
This will only execute the *one* new migration file, because DoctrineMigrationsBundle knows that the first migration was already executed earlier. Internally, 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:
Copy
```
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](https://symfony.com/doc/current/doctrine.html#persisting-objects-to-the-database "Permalink to this headline")
It's time to save a `Product` object to the database! Let's create a new controller to experiment:
Copy
```
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
```
```
// src/Controller/ProductController.php
namespace App\Controller;
// ...
use App\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ProductController extends AbstractController
{
#[Route('/product', name: 'create_product')]
public function createProduct(EntityManagerInterface $entityManager): Response
{
$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 dbal:run-sql 'SELECT * FROM product'
# on Windows systems not using Powershell, run this command instead:
# php bin/console dbal:run-sql "SELECT * FROM product"
```
Take a look at the previous example in more detail:
- **line 13** The `EntityManagerInterface $entityManager` argument tells Symfony to [inject the Entity Manager service](https://symfony.com/doc/current/service_container.html#services-constructor-injection) into the controller method. This object is responsible for saving objects to, and fetching objects from, the database.
- **lines 15-18** In this section, you instantiate and work with the `$product` object like any other normal PHP object.
- **line 21** The `persist($product)` call tells Doctrine to "manage" the `$product` object. This does **not** cause a query to be made to the database.
- **line 24** 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](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/transactions-and-concurrency.html).
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.
## [Validating Objects](https://symfony.com/doc/current/doctrine.html#validating-objects "Permalink to this headline")
[The Symfony validator](https://symfony.com/doc/current/validation.html) can reuse Doctrine metadata to perform some basic validation tasks. First, add or configure the [auto\_mapping option](https://symfony.com/doc/current/reference/configuration/framework.html#reference-validation-auto-mapping) to define which entities should be introspected by Symfony to add automatic validation constraints.
Consider the following controller code:
```
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
```
```
// src/Controller/ProductController.php
namespace App\Controller;
use App\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
// ...
class ProductController extends AbstractController
{
#[Route('/product', name: 'create_product')]
public function createProduct(ValidatorInterface $validator): Response
{
$product = new Product();
// ... update the product data somehow (e.g. with a form) ...
$errors = $validator->validate($product);
if (count($errors) > 0) {
return new Response((string) $errors, 400);
}
// ...
}
}
```
Although the `Product` entity doesn't define any explicit [validation configuration](https://symfony.com/doc/current/validation.html), if the `auto_mapping` option includes it in the list of entities to introspect, Symfony will infer some validation rules for it and will apply them.
For example, given that the `name` property can't be `null` in the database, a [NotNull constraint](https://symfony.com/doc/current/reference/constraints/NotNull.html) is added automatically to the property (if it doesn't contain that constraint already).
The following table summarizes the mapping between Doctrine metadata and the corresponding validation constraints added automatically by Symfony:
| Doctrine attribute | Validation constraint | Notes |
|---|---|---|
| `nullable=false` | [NotNull](https://symfony.com/doc/current/reference/constraints/NotNull.html) | Requires installing the [PropertyInfo component](https://symfony.com/doc/current/components/property_info.html) |
| `type` | [Type](https://symfony.com/doc/current/reference/constraints/Type.html) | Requires installing the [PropertyInfo component](https://symfony.com/doc/current/components/property_info.html) |
| `unique=true` | [UniqueEntity](https://symfony.com/doc/current/reference/constraints/UniqueEntity.html) | |
| `length` | [Length](https://symfony.com/doc/current/reference/constraints/Length.html) | |
Because [the Form component](https://symfony.com/doc/current/forms.html) as well as [API Platform](https://api-platform.com/docs/core/validation/) internally use the Validator component, all your forms and web APIs will also automatically benefit from these automatic validation constraints.
This automatic validation is a nice feature to improve your productivity, but it doesn't replace the validation configuration entirely. You still need to add some [validation constraints](https://symfony.com/doc/current/reference/constraints.html) to ensure that data provided by the user is correct.
## [Fetching Objects from the Database](https://symfony.com/doc/current/doctrine.html#fetching-objects-from-the-database "Permalink to this headline")
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
25
26
27
28
29
```
```
// src/Controller/ProductController.php
namespace App\Controller;
use App\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
// ...
class ProductController extends AbstractController
{
#[Route('/product/{id}', name: 'product_show')]
public function show(EntityManagerInterface $entityManager, int $id): Response
{
$product = $entityManager->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]);
}
}
```
Another possibility is to use the `ProductRepository` using Symfony's autowiring and injected by the dependency injection container:
```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
```
```
// src/Controller/ProductController.php
namespace App\Controller;
use App\Entity\Product;
use App\Repository\ProductRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
// ...
class ProductController extends AbstractController
{
#[Route('/product/{id}', name: 'product_show')]
public function show(ProductRepository $productRepository, int $id): Response
{
$product = $productRepository
->find($id);
// ...
}
}
```
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 = $entityManager->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](https://symfony.com/doc/current/doctrine.html#doctrine-queries) 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, install the `profiler` [Symfony pack](https://symfony.com/doc/current/setup.html#symfony-packs) by running this command: `composer require --dev symfony/profiler-pack`.
For more information, read the [Symfony profiler documentation](https://symfony.com/doc/current/profiler.html).
## [Automatically Fetching Objects (EntityValueResolver)](https://symfony.com/doc/current/doctrine.html#automatically-fetching-objects-entityvalueresolver "Permalink to this headline")
In many cases, you can use the `EntityValueResolver` to do the query for you automatically! You can simplify the controller to:
```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
```
```
// src/Controller/ProductController.php
namespace App\Controller;
use App\Entity\Product;
use App\Repository\ProductRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
// ...
class ProductController extends AbstractController
{
#[Route('/product/{id}')]
public function show(Product $product): Response
{
// use the Product!
// ...
}
}
```
That's it! The attribute uses the `{id}` from the route to query for the `Product` by the `id` column. If it's not found, a 404 error is thrown.
You can change this behavior by making the controller argument optional. In that case, no 404 is thrown automatically and you're free to handle the missing entity yourself:
```
1
2
3
4
5
6
7
8
9
```
```
#[Route('/product/{id}')]
public function show(?Product $product): Response
{
if (null === $product) {
// run your own logic to return a custom response
}
// ...
}
```
Tip
When enabled globally, it's possible to disable the behavior on a specific controller, by using the `MapEntity` set to `disabled`:
```
1
2
3
4
5
6
7
8
```
```
public function show(
#[CurrentUser]
#[MapEntity(disabled: true)]
User $user
): Response {
// User is not resolved by the EntityValueResolver
// ...
}
```
### [Fetch Automatically](https://symfony.com/doc/current/doctrine.html#fetch-automatically "Permalink to this headline")
By default, automatic fetching only works when your route contains an `{id}` wildcard. The resolver uses it to fetch the entity by its primary key via the `find()` method:
```
1
2
3
4
5
6
```
```
// performs a find($id) query to find the $product object
#[Route('/product/{id}')]
public function show(Product $product): Response
{
// ...
}
```
To fetch entities by other properties, use the `{param:argument}` route syntax. This maps a route parameter to a controller argument and tells the resolver to query the database using that property:
```
1
2
3
4
5
6
```
```
// performs a findOneBy(['slug' => $slug]) query to find the $product object
#[Route('/product/{slug:product}')]
public function show(Product $product): Response
{
// ...
}
```
Tip
If you set the `doctrine.orm.controller_resolver.auto_mapping` option to `true`, the resolver will attempt to do a `findOneBy()` using *all* route wildcards that match properties on your entity (non-properties are ignored). This removes the need for the `{param:argument}` syntax, but the behavior is less explicit and no longer recommended.
You can also configure the mapping explicitly for any controller argument using the `MapEntity` attribute. You can even control the behavior of the `EntityValueResolver` by using the [MapEntity options](https://symfony.com/doc/current/doctrine.html#mapentity-options):
```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
```
```
// src/Controller/ProductController.php
namespace App\Controller;
use App\Entity\Product;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
// ...
class ProductController extends AbstractController
{
#[Route('/product/{slug}')]
public function show(
#[MapEntity(mapping: ['slug' => 'slug'])]
Product $product
): Response {
// use the Product!
// ...
}
}
```
### [Fetch via an Expression](https://symfony.com/doc/current/doctrine.html#fetch-via-an-expression "Permalink to this headline")
If automatic fetching doesn't work for your use case, you can write an expression using the [ExpressionLanguage component](https://symfony.com/doc/current/components/expression_language.html):
```
1
2
3
4
5
6
```
```
#[Route('/product/{product_id}')]
public function show(
#[MapEntity(expr: 'repository.find(product_id)')]
Product $product
): Response {
}
```
In the expression, the `repository` variable will be your entity's Repository class and any route wildcards - like `{product_id}` are available as variables.
The repository method called in the expression can also return a list of entities. In that case, update the type of your controller argument:
```
1
2
3
4
5
6
```
```
#[Route('/posts_by/{author_id}')]
public function authorPosts(
#[MapEntity(class: Post::class, expr: 'repository.findBy({"author": author_id}, {}, 10)')]
iterable $posts
): Response {
}
```
This can also be used to help resolve multiple arguments:
```
1
2
3
4
5
6
7
```
```
#[Route('/product/{id}/comments/{comment_id}')]
public function show(
Product $product,
#[MapEntity(expr: 'repository.find(comment_id)')]
Comment $comment
): Response {
}
```
In the example above, the `$product` argument is handled automatically, but `$comment` is configured with the attribute since they cannot both follow the default convention.
If you need to get other information from the request to query the database, you can also access the request in your expression thanks to the `request` variable. Let's say you want the first or the last comment of a product depending on a query parameter named `sort`:
```
1
2
3
4
5
6
7
```
```
#[Route('/product/{id}/comments')]
public function show(
Product $product,
#[MapEntity(expr: 'repository.findOneBy({"product": id}, {"createdAt": request.query.get("sort", "DESC")})')]
Comment $comment
): Response {
}
```
### [Fetch via Interfaces](https://symfony.com/doc/current/doctrine.html#fetch-via-interfaces "Permalink to this headline")
Suppose your `Product` class implements an interface called `ProductInterface`. If you want to decouple your controllers from the concrete entity implementation, you can reference the entity by its interface instead.
To enable this, first configure the [resolve\_target\_entities option](https://symfony.com/doc/current/doctrine/resolve_target_entity.html). Then, your controller can type-hint the interface, and the entity will be resolved automatically:
```
1
2
3
4
5
6
```
```
public function show(
#[MapEntity]
ProductInterface $product
): Response {
// ...
}
```
### [MapEntity Options](https://symfony.com/doc/current/doctrine.html#mapentity-options "Permalink to this headline")
A number of options are available on the `MapEntity` attribute to control behavior:
`id`
If an `id` option is configured and matches a route parameter, then the resolver will find by the primary key:
```
1
2
3
4
5
6
```
```
#[Route('/product/{product_id}')]
public function show(
#[MapEntity(id: 'product_id')]
Product $product
): Response {
}
```
`mapping`
Configures the properties and values to use with the `findOneBy()` method: the key is the route placeholder name and the value is the Doctrine property name:
```
1
2
3
4
5
6
7
8
```
```
#[Route('/product/{category}/{slug}/comments/{comment_slug}')]
public function show(
#[MapEntity(mapping: ['category' => 'category', 'slug' => 'slug'])]
Product $product,
#[MapEntity(mapping: ['comment_slug' => 'slug'])]
Comment $comment
): Response {
}
```
`stripNull`
If true, then when `findOneBy()` is used, any values that are `null` will not be used for the query.
`objectManager`
By default, the `EntityValueResolver` uses the *default* object manager, but you can configure this:
```
1
2
3
4
5
6
```
```
#[Route('/product/{id}')]
public function show(
#[MapEntity(objectManager: 'foo')]
Product $product
): Response {
}
```
`evictCache`
If true, forces Doctrine to always fetch the entity from the database instead of cache.
`disabled`
If true, the `EntityValueResolver` will not try to replace the argument.
`message`
An optional custom message displayed when there's a [NotFoundHttpException](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php "Symfony\Component\HttpKernel\Exception\NotFoundHttpException"), but **only in the development environment** (you won't see this message in production):
```
1
2
3
4
5
6
```
```
#[Route('/product/{product_id}')]
public function show(
#[MapEntity(id: 'product_id', message: 'The product does not exist')]
Product $product
): Response {
}
```
## [Updating an Object](https://symfony.com/doc/current/doctrine.html#updating-an-object "Permalink to this headline")
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
22
23
24
25
26
27
28
29
30
31
```
```
// src/Controller/ProductController.php
namespace App\Controller;
use App\Entity\Product;
use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
// ...
class ProductController extends AbstractController
{
#[Route('/product/edit/{id}', name: 'product_edit')]
public function update(EntityManagerInterface $entityManager, int $id): Response
{
$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](https://symfony.com/doc/current/doctrine.html#deleting-an-object "Permalink to this headline")
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](https://symfony.com/doc/current/doctrine.html#querying-for-objects-the-repository "Permalink to this headline")
You've already seen how the repository object allows you to run basic queries without any work:
```
1
2
3
```
```
// from inside a controller
$repository = $entityManager->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 Doctrine\Persistence\ManagerRegistry;
class ProductRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $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
```
```
// src/Repository/ProductRepository.php
// ...
class ProductRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Product::class);
}
/**
* @return Product[]
*/
public function findAllGreaterThanPrice(int $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->getResult();
}
}
```
The string passed to `createQuery()` might look like SQL, but it is [Doctrine Query Language](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/dql-doctrine-query-language.html). This allows you to type queries using commonly known query language, but referencing PHP objects instead (i.e. in the `FROM` statement).
Now, you can call this method on the repository:
```
1
2
3
4
5
6
```
```
// from inside a controller
$minPrice = 1000;
$products = $entityManager->getRepository(Product::class)->findAllGreaterThanPrice($minPrice);
// ...
```
See [Service Container](https://symfony.com/doc/current/service_container.html#services-constructor-injection) for how to inject the repository into any service.
### [Querying with the Query Builder](https://symfony.com/doc/current/doctrine.html#querying-with-the-query-builder "Permalink to this headline")
Doctrine also provides a [Query Builder](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/query-builder.html), an object-oriented way to write queries. It is recommended to use this when queries are built dynamically (i.e. based on PHP conditions):
```
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
```
```
// src/Repository/ProductRepository.php
// ...
class ProductRepository extends ServiceEntityRepository
{
public function findAllGreaterThanPrice(int $price, bool $includeUnavailableProducts = false): array
{
// automatically knows to select Products
// the "p" is an alias you'll use in the rest of the query
$qb = $this->createQueryBuilder('p')
->where('p.price > :price')
->setParameter('price', $price)
->orderBy('p.price', 'ASC');
if (!$includeUnavailableProducts) {
$qb->andWhere('p.available = TRUE');
}
$query = $qb->getQuery();
return $query->execute();
// to get just one result:
// $product = $query->setMaxResults(1)->getOneOrNullResult();
}
}
```
### [Querying with SQL](https://symfony.com/doc/current/doctrine.html#querying-with-sql "Permalink to this headline")
In addition, you can query 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
19
20
21
```
```
// src/Repository/ProductRepository.php
// ...
class ProductRepository extends ServiceEntityRepository
{
public function findAllGreaterThanPrice(int $price): array
{
$conn = $this->getEntityManager()->getConnection();
$sql = '
SELECT * FROM product p
WHERE p.price > :price
ORDER BY p.price ASC
';
$resultSet = $conn->executeQuery($sql, ['price' => $price]);
// returns an array of arrays (i.e. a raw data set)
return $resultSet->fetchAllAssociative();
}
}
```
With SQL, you will get back raw data, not objects (unless you use the [NativeQuery](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/native-sql.html) functionality).
## [Configuration](https://symfony.com/doc/current/doctrine.html#configuration "Permalink to this headline")
See the [Doctrine config reference](https://symfony.com/doc/current/reference/configuration/doctrine.html).
## [Relationships and Associations](https://symfony.com/doc/current/doctrine.html#relationships-and-associations "Permalink to this headline")
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](https://symfony.com/doc/current/doctrine/associations.html).
## [Database Testing](https://symfony.com/doc/current/doctrine.html#database-testing "Permalink to this headline")
Read the article about [testing code that interacts with the database](https://symfony.com/doc/current/testing/database.html).
## [Doctrine Extensions (Timestampable, Translatable, etc.)](https://symfony.com/doc/current/doctrine.html#doctrine-extensions-timestampable-translatable-etc "Permalink to this headline")
Doctrine community has created some extensions to implement common needs such as *"set the value of the createdAt property automatically when creating an entity"*. Read more about the [available Doctrine extensions](https://github.com/doctrine-extensions/DoctrineExtensions) and use the [StofDoctrineExtensionsBundle](https://github.com/stof/StofDoctrineExtensionsBundle) to integrate them in your application.
## [Learn more](https://symfony.com/doc/current/doctrine.html#learn-more "Permalink to this headline")
- [How to Work with Doctrine Associations / Relations](https://symfony.com/doc/current/doctrine/associations.html)
- [Doctrine Events](https://symfony.com/doc/current/doctrine/events.html)
- [How to Register custom DQL Functions](https://symfony.com/doc/current/doctrine/custom_dql_functions.html)
- [How to Use Doctrine DBAL](https://symfony.com/doc/current/doctrine/dbal.html)
- [How to Work with Multiple Entity Managers and Connections](https://symfony.com/doc/current/doctrine/multiple_entity_managers.html)
- [Referencing Entities with Abstract Classes and Interfaces](https://symfony.com/doc/current/doctrine/resolve_target_entity.html)
- [How to Test a Doctrine Repository](https://symfony.com/doc/current/testing/database.html)
This work, including the code samples, is licensed under a [Creative Commons BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) license.
TOC
Search
Version
**Symfony 8.0** [backers](https://symfony.com/backers)
[](https://www.blackfire.io/profiler?utm_source=symfony&utm_medium=ad_visual&utm_campaign=profiler)
[Code consumes server resources. Blackfire tells you how](https://www.blackfire.io/profiler?utm_source=symfony&utm_medium=ad_visual&utm_campaign=profiler)
[](https://certification.symfony.com/?utm_source=ad&utm_medium=banner&utm_campaign=certification&utm_content=symfonycertified)
[Online exam, become Symfony certified today](https://certification.symfony.com/?utm_source=ad&utm_medium=banner&utm_campaign=certification&utm_content=symfonycertified)
## Symfony footer

Thanks **[Loïc Frémont](https://connect.symfony.com/profile/loic425)** (**@loic425**) for being a Symfony contributor
[**7** commits](https://github.com/symfony/symfony/commits?author=loic425) • **1K** lines changed
[View all contributors](https://symfony.com/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](https://symfony.com/doc/current/contributing/index.html)

[Celebrating 20 years of Symfony](https://symfony.com/20years)
**Symfony**™ is a trademark of Symfony SAS. [All rights reserved](https://symfony.com/trademark).
- [What is Symfony?](https://symfony.com/what-is-symfony)
- [What is Symfony?](https://symfony.com/what-is-symfony)
- [Symfony at a Glance](https://symfony.com/at-a-glance)
- [Symfony Packages](https://symfony.com/packages)
- [Symfony Releases](https://symfony.com/releases)
- [Security Policy](https://symfony.com/doc/current/contributing/code/security.html)
- [Logo & Screenshots](https://symfony.com/logo)
- [Trademark & Licenses](https://symfony.com/license)
- [symfony1 Legacy](https://symfony.com/legacy)
- [Learn Symfony](https://symfony.com/doc)
- [Symfony Docs](https://symfony.com/doc)
- [Symfony Book](https://symfony.com/book)
- [Reference](https://symfony.com/doc/current/reference/index.html)
- [Bundles](https://symfony.com/bundles)
- [Best Practices](https://symfony.com/doc/current/best_practices.html)
- [Training](https://sensiolabs.com/training/courses?utm_source=symfony&utm_medium=symfony_footer&utm_campaign=permanent_referral)
- [eLearning Platform](https://university.sensiolabs.com/e-learning-platform?utm_source=symfony&utm_medium=symfony_footer&utm_campaign=permanent_referral)
- [Certification](https://certification.symfony.com/)
- [Screencasts](https://symfonycasts.com/)
- [Learn Symfony](https://symfonycasts.com/tracks/symfony)
- [Learn PHP](https://symfonycasts.com/tracks/php)
- [Learn JavaScript](https://symfonycasts.com/tracks/javascript)
- [Learn Drupal](https://symfonycasts.com/tracks/drupal)
- [Learn RESTful APIs](https://symfonycasts.com/tracks/rest)
- [Community](https://symfony.com/community)
- [Symfony Community](https://symfony.com/community)
- [SymfonyConnect](https://connect.symfony.com/)
- [Events & Meetups](https://symfony.com/events/)
- [Projects using Symfony](https://symfony.com/projects)
- [Contributors](https://symfony.com/contributors)
- [Symfony Jobs](https://symfony.com/jobs)
- [Backers](https://symfony.com/backers)
- [Code of Conduct](https://symfony.com/doc/current/contributing/code_of_conduct/code_of_conduct.html)
- [Downloads Stats](https://symfony.com/stats/downloads)
- [Support](https://symfony.com/support)
- [Blog](https://symfony.com/blog/)
- [All Blog Posts](https://symfony.com/blog/)
- [A Week of Symfony](https://symfony.com/blog/category/a-week-of-symfony)
- [Case Studies](https://symfony.com/blog/category/case-studies)
- [Cloud](https://symfony.com/blog/category/cloud)
- [Community](https://symfony.com/blog/category/community)
- [Conferences](https://symfony.com/blog/category/conferences)
- [Diversity](https://symfony.com/blog/category/diversity)
- [Living on the edge](https://symfony.com/blog/category/living-on-the-edge)
- [Releases](https://symfony.com/blog/category/releases)
- [Security Advisories](https://symfony.com/blog/category/security-advisories)
- [Symfony Insight](https://symfony.com/blog/category/symfony-insight)
- [Twig](https://symfony.com/blog/category/twig)
- [SensioLabs Blog](https://sensiolabs.com/blog?utm_source=symfony&utm_medium=symfony_footer&utm_campaign=permanent_referral)
- [Services](https://sensiolabs.com/?utm_source=symfony&utm_medium=symfony_footer&utm_campaign=permanent_referral)
- [SensioLabs services](https://sensiolabs.com/?utm_source=symfony&utm_medium=symfony_footer&utm_campaign=permanent_referral)
- [Train developers](https://sensiolabs.com/training?utm_source=symfony&utm_medium=symfony_footer&utm_campaign=permanent_referral)
- [Manage your project quality](https://insight.symfony.com/)
- [Improve your project performance](https://www.blackfire.io/?utm_source=symfony&utm_medium=symfonycom_footer&utm_campaign=profiler)
- [Host Symfony projects](https://symfony.com/cloud/)
[Powered by](https://symfony.com/cloud/)
[Formerly Platform.sh](https://symfony.com/cloud/ "Upsun, a Platform-as-a-Service optimized for Symfony developers")
### Follow Symfony
CLOSE |
| Readable Markdown | Symfony provides all the tools you need to use databases in your applications thanks to [Doctrine](https://www.doctrine-project.org/), the best set of PHP libraries to work with databases. These tools support relational databases like MySQL and PostgreSQL and also NoSQL databases like MongoDB.
Databases are a broad topic, so the documentation is divided in three articles:
- This article explains the recommended way to work with **relational databases** in Symfony applications;
- Read [this other article](https://symfony.com/doc/current/doctrine/dbal.html) if you need **low-level access** to perform raw SQL queries to relational databases (similar to PHP's [PDO](https://www.php.net/pdo));
- Read [DoctrineMongoDBBundle docs](https://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html) if you are working with **MongoDB databases**.
## [Installing Doctrine](https://symfony.com/doc/current/doctrine.html#installing-doctrine "Permalink to this headline")
First, install Doctrine support via the `orm` [Symfony pack](https://symfony.com/doc/current/setup.html#symfony-packs), as well as the MakerBundle, which will help generate some code:
### [Configuring the Database](https://symfony.com/doc/current/doctrine.html#configuring-the-database "Permalink to this headline")
The database connection information is stored as an environment variable called `DATABASE_URL`. For development, you can find and customize this inside `.env`:
Warning
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](https://www.ietf.org/rfc/rfc3986.txt) for the full list of reserved characters. You can use the [urlencode](https://secure.php.net/manual/en/function.urlencode.php "urlencode") function to encode them or the [urlencode environment variable processor](https://symfony.com/doc/current/configuration/env_var_processors.html#urlencode_environment_variable_processor). In this case you need to remove the `resolve:` prefix in `config/packages/doctrine.yaml` to avoid errors: `url: '%env(DATABASE_URL)%'`
Tip
To avoid URL-encoding issues with special characters in credentials, you can use separate connection parameters instead of the URL format. Define each value as its own environment variable and wrap it in single quotes in the `.env` file to prevent characters like `$` and `#` from being interpreted:
Then configure Doctrine to use individual parameters:
Now that your connection parameters are setup, Doctrine can create the `db_name` database for you:
There are more options in `config/packages/doctrine.yaml` that you can configure, including your `server_version` (e.g. 8.0.37 if you're using MySQL 8.0.37), 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](https://symfony.com/doc/current/doctrine.html#creating-an-entity-class "Permalink to this headline")
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:
Whoa! You now have a new `src/Entity/Product.php` file:
Tip
Starting in [MakerBundle](https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html): v1.57.0 - You can pass either `--with-uuid` or `--with-ulid` to `make:entity`. Leveraging Symfony's [Uid Component](https://symfony.com/doc/current/components/uid.html), this generates an entity with the `id` type as [Uuid](https://symfony.com/doc/current/components/uid.html#uuid) or [Ulid](https://symfony.com/doc/current/components/uid.html#ulid) instead of `int`.
Note
Starting in v1.44.0 - [MakerBundle](https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html): only supports entities using PHP attributes.
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.
Warning
There is a [limit of 767 bytes for the index key prefix](https://dev.mysql.com/doc/refman/5.6/en/innodb-limits.html) 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 attributes: the `#[ORM\Column(...)]` 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.
Warning
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](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/basic-mapping.html#quoting-reserved-words) 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.
### [Entity Field Types](https://symfony.com/doc/current/doctrine.html#entity-field-types "Permalink to this headline")
Doctrine supports a wide variety of **field types** (numbers, strings, enums, binary, dates, JSON, etc.), each with their own options. Check out the [list of Doctrine mapping types](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/basic-mapping.html#reference-mapping-types) in the Doctrine documentation.
Symfony also provides the following **additional field types**:
#### [`uuid`](https://symfony.com/doc/current/doctrine.html#uuid "Permalink to this headline")
**Class:** [UuidType](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Bridge/Doctrine/Types/UuidType.php "Symfony\Bridge\Doctrine\Types\UuidType")
Stores a [UUID](https://symfony.com/doc/current/components/uid.html) as a native GUID type if available, or as a 16-byte binary otherwise:
See [Storing UUIDs in Databases](https://symfony.com/doc/current/components/uid.html#uid-uuid-doctrine) in the UID component documentation for more details, including how to use UUIDs as primary keys.
#### [`ulid`](https://symfony.com/doc/current/doctrine.html#ulid "Permalink to this headline")
**Class:** [UlidType](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Bridge/Doctrine/Types/UlidType.php "Symfony\Bridge\Doctrine\Types\UlidType")
Stores a [ULID](https://symfony.com/doc/current/components/uid.html#ulid) as a native GUID type if available, or as a 16-byte binary otherwise:
See [Storing ULIDs in Databases](https://symfony.com/doc/current/components/uid.html#uid-ulid-doctrine) in the UID component documentation for more details, including how to use ULIDs as primary keys.
#### [DatePoint Types](https://symfony.com/doc/current/doctrine.html#datepoint-types "Permalink to this headline")
These types allow storing [DatePoint](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Component/Clock/DatePoint.php "Symfony\Component\Clock\DatePoint") objects from the [Clock component](https://symfony.com/doc/current/components/clock.html). They convert to/from `DatePoint` objects automatically.
| Type | Extends Doctrine type | Class |
|---|---|---|
| `date_point` | `datetime_immutable` | [DatePointType](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Bridge/Doctrine/Types/DatePointType.php "Symfony\Bridge\Doctrine\Types\DatePointType") |
| `day_point` | `date_immutable` | [DayPointType](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Bridge/Doctrine/Types/DayPointType.php "Symfony\Bridge\Doctrine\Types\DayPointType") |
| `time_point` | `time_immutable` | [TimePointType](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Bridge/Doctrine/Types/TimePointType.php "Symfony\Bridge\Doctrine\Types\TimePointType") |
Example usage:
Tip
Use `date_point` when you want to work with [DatePoint](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Component/Clock/DatePoint.php "Symfony\Component\Clock\DatePoint") objects, which makes your code easier to test with the [Clock component](https://symfony.com/doc/current/components/clock.html). Use `datetime_immutable` if you don't need the Clock component features.
## [Migrations: Creating the Database Tables/Schema](https://symfony.com/doc/current/doctrine.html#migrations-creating-the-database-tables-schema "Permalink to this headline")
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](https://github.com/doctrine/DoctrineMigrationsBundle), which is already installed:
Tip
Starting in [MakerBundle](https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html): v1.56.0 - Passing `--formatted` to `make:migration` generates a nice and tidy migration file.
If everything worked, you should see something like this:
If you open this file, it contains the SQL needed to update your database! To run that SQL, execute your migrations:
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](https://symfony.com/doc/current/doctrine.html#migrations-adding-more-fields "Permalink to this headline")
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:
This adds the new `description` property and `getDescription()` and `setDescription()` methods:
The new property is mapped, but it doesn't exist yet in the `product` table. No problem! Generate a new migration:
This time, the SQL in the generated file will look like this:
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:
Warning
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.
This will only execute the *one* new migration file, because DoctrineMigrationsBundle knows that the first migration was already executed earlier. Internally, 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:
If you make some changes and want to regenerate *all* getter/setter methods, also pass `--overwrite`.
## [Persisting Objects to the Database](https://symfony.com/doc/current/doctrine.html#persisting-objects-to-the-database "Permalink to this headline")
It's time to save a `Product` object to the database! Let's create a new controller to experiment:
Inside the controller, you can create a new `Product` object, set data on it, and save it:
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:
Take a look at the previous example in more detail:
- **line 13** The `EntityManagerInterface $entityManager` argument tells Symfony to [inject the Entity Manager service](https://symfony.com/doc/current/service_container.html#services-constructor-injection) into the controller method. This object is responsible for saving objects to, and fetching objects from, the database.
- **lines 15-18** In this section, you instantiate and work with the `$product` object like any other normal PHP object.
- **line 21** The `persist($product)` call tells Doctrine to "manage" the `$product` object. This does **not** cause a query to be made to the database.
- **line 24** 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.
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.
## [Validating Objects](https://symfony.com/doc/current/doctrine.html#validating-objects "Permalink to this headline")
[The Symfony validator](https://symfony.com/doc/current/validation.html) can reuse Doctrine metadata to perform some basic validation tasks. First, add or configure the [auto\_mapping option](https://symfony.com/doc/current/reference/configuration/framework.html#reference-validation-auto-mapping) to define which entities should be introspected by Symfony to add automatic validation constraints.
Consider the following controller code:
Although the `Product` entity doesn't define any explicit [validation configuration](https://symfony.com/doc/current/validation.html), if the `auto_mapping` option includes it in the list of entities to introspect, Symfony will infer some validation rules for it and will apply them.
For example, given that the `name` property can't be `null` in the database, a [NotNull constraint](https://symfony.com/doc/current/reference/constraints/NotNull.html) is added automatically to the property (if it doesn't contain that constraint already).
The following table summarizes the mapping between Doctrine metadata and the corresponding validation constraints added automatically by Symfony:
| Doctrine attribute | Validation constraint | Notes |
|---|---|---|
| `nullable=false` | [NotNull](https://symfony.com/doc/current/reference/constraints/NotNull.html) | Requires installing the [PropertyInfo component](https://symfony.com/doc/current/components/property_info.html) |
| `type` | [Type](https://symfony.com/doc/current/reference/constraints/Type.html) | Requires installing the [PropertyInfo component](https://symfony.com/doc/current/components/property_info.html) |
| `unique=true` | [UniqueEntity](https://symfony.com/doc/current/reference/constraints/UniqueEntity.html) | |
| `length` | [Length](https://symfony.com/doc/current/reference/constraints/Length.html) | |
Because [the Form component](https://symfony.com/doc/current/forms.html) as well as [API Platform](https://api-platform.com/docs/core/validation/) internally use the Validator component, all your forms and web APIs will also automatically benefit from these automatic validation constraints.
This automatic validation is a nice feature to improve your productivity, but it doesn't replace the validation configuration entirely. You still need to add some [validation constraints](https://symfony.com/doc/current/reference/constraints.html) to ensure that data provided by the user is correct.
## [Fetching Objects from the Database](https://symfony.com/doc/current/doctrine.html#fetching-objects-from-the-database "Permalink to this headline")
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:
Another possibility is to use the `ProductRepository` using Symfony's autowiring and injected by the dependency injection container:
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:
You can also add *custom* methods for more complex queries! More on that later in the [Databases and the Doctrine ORM](https://symfony.com/doc/current/doctrine.html#doctrine-queries) 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, install the `profiler` [Symfony pack](https://symfony.com/doc/current/setup.html#symfony-packs) by running this command: `composer require --dev symfony/profiler-pack`.
For more information, read the [Symfony profiler documentation](https://symfony.com/doc/current/profiler.html).
## [Automatically Fetching Objects (EntityValueResolver)](https://symfony.com/doc/current/doctrine.html#automatically-fetching-objects-entityvalueresolver "Permalink to this headline")
In many cases, you can use the `EntityValueResolver` to do the query for you automatically! You can simplify the controller to:
That's it! The attribute uses the `{id}` from the route to query for the `Product` by the `id` column. If it's not found, a 404 error is thrown.
You can change this behavior by making the controller argument optional. In that case, no 404 is thrown automatically and you're free to handle the missing entity yourself:
Tip
When enabled globally, it's possible to disable the behavior on a specific controller, by using the `MapEntity` set to `disabled`:
### [Fetch Automatically](https://symfony.com/doc/current/doctrine.html#fetch-automatically "Permalink to this headline")
By default, automatic fetching only works when your route contains an `{id}` wildcard. The resolver uses it to fetch the entity by its primary key via the `find()` method:
To fetch entities by other properties, use the `{param:argument}` route syntax. This maps a route parameter to a controller argument and tells the resolver to query the database using that property:
Tip
If you set the `doctrine.orm.controller_resolver.auto_mapping` option to `true`, the resolver will attempt to do a `findOneBy()` using *all* route wildcards that match properties on your entity (non-properties are ignored). This removes the need for the `{param:argument}` syntax, but the behavior is less explicit and no longer recommended.
You can also configure the mapping explicitly for any controller argument using the `MapEntity` attribute. You can even control the behavior of the `EntityValueResolver` by using the [MapEntity options](https://symfony.com/doc/current/doctrine.html#mapentity-options):
### [Fetch via an Expression](https://symfony.com/doc/current/doctrine.html#fetch-via-an-expression "Permalink to this headline")
If automatic fetching doesn't work for your use case, you can write an expression using the [ExpressionLanguage component](https://symfony.com/doc/current/components/expression_language.html):
In the expression, the `repository` variable will be your entity's Repository class and any route wildcards - like `{product_id}` are available as variables.
The repository method called in the expression can also return a list of entities. In that case, update the type of your controller argument:
This can also be used to help resolve multiple arguments:
In the example above, the `$product` argument is handled automatically, but `$comment` is configured with the attribute since they cannot both follow the default convention.
If you need to get other information from the request to query the database, you can also access the request in your expression thanks to the `request` variable. Let's say you want the first or the last comment of a product depending on a query parameter named `sort`:
### [Fetch via Interfaces](https://symfony.com/doc/current/doctrine.html#fetch-via-interfaces "Permalink to this headline")
Suppose your `Product` class implements an interface called `ProductInterface`. If you want to decouple your controllers from the concrete entity implementation, you can reference the entity by its interface instead.
To enable this, first configure the [resolve\_target\_entities option](https://symfony.com/doc/current/doctrine/resolve_target_entity.html). Then, your controller can type-hint the interface, and the entity will be resolved automatically:
### [MapEntity Options](https://symfony.com/doc/current/doctrine.html#mapentity-options "Permalink to this headline")
A number of options are available on the `MapEntity` attribute to control behavior:
`id`
If an `id` option is configured and matches a route parameter, then the resolver will find by the primary key:
`mapping`
Configures the properties and values to use with the `findOneBy()` method: the key is the route placeholder name and the value is the Doctrine property name:
`stripNull`
If true, then when `findOneBy()` is used, any values that are `null` will not be used for the query.
`objectManager`
By default, the `EntityValueResolver` uses the *default* object manager, but you can configure this:
`evictCache`
If true, forces Doctrine to always fetch the entity from the database instead of cache.
`disabled`
If true, the `EntityValueResolver` will not try to replace the argument.
`message`
An optional custom message displayed when there's a [NotFoundHttpException](https://github.com/symfony/symfony/blob/8.0/src/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php "Symfony\Component\HttpKernel\Exception\NotFoundHttpException"), but **only in the development environment** (you won't see this message in production):
## [Updating an Object](https://symfony.com/doc/current/doctrine.html#updating-an-object "Permalink to this headline")
Once you've fetched an object from Doctrine, you interact with it the same as with any PHP model:
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](https://symfony.com/doc/current/doctrine.html#deleting-an-object "Permalink to this headline")
Deleting an object is very similar, but requires a call to the `remove()` method of the entity manager:
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](https://symfony.com/doc/current/doctrine.html#querying-for-objects-the-repository "Permalink to this headline")
You've already seen how the repository object allows you to run basic queries without any work:
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 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:
The string passed to `createQuery()` might look like SQL, but it is [Doctrine Query Language](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/dql-doctrine-query-language.html). This allows you to type queries using commonly known query language, but referencing PHP objects instead (i.e. in the `FROM` statement).
Now, you can call this method on the repository:
See [Service Container](https://symfony.com/doc/current/service_container.html#services-constructor-injection) for how to inject the repository into any service.
### [Querying with the Query Builder](https://symfony.com/doc/current/doctrine.html#querying-with-the-query-builder "Permalink to this headline")
Doctrine also provides a [Query Builder](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/query-builder.html), an object-oriented way to write queries. It is recommended to use this when queries are built dynamically (i.e. based on PHP conditions):
### [Querying with SQL](https://symfony.com/doc/current/doctrine.html#querying-with-sql "Permalink to this headline")
In addition, you can query directly with SQL if you need to:
With SQL, you will get back raw data, not objects (unless you use the [NativeQuery](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/native-sql.html) functionality).
## [Learn more](https://symfony.com/doc/current/doctrine.html#learn-more "Permalink to this headline")
- [How to Work with Doctrine Associations / Relations](https://symfony.com/doc/current/doctrine/associations.html)
- [Doctrine Events](https://symfony.com/doc/current/doctrine/events.html)
- [How to Register custom DQL Functions](https://symfony.com/doc/current/doctrine/custom_dql_functions.html)
- [How to Use Doctrine DBAL](https://symfony.com/doc/current/doctrine/dbal.html)
- [How to Work with Multiple Entity Managers and Connections](https://symfony.com/doc/current/doctrine/multiple_entity_managers.html)
- [Referencing Entities with Abstract Classes and Interfaces](https://symfony.com/doc/current/doctrine/resolve_target_entity.html)
- [How to Test a Doctrine Repository](https://symfony.com/doc/current/testing/database.html)
This work, including the code samples, is licensed under a [Creative Commons BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) license. |
| Shard | 148 (laksa) |
| Root Hash | 16968411856917244348 |
| Unparsed URL | com,symfony!/doc/current/doctrine.html s443 |