Skip to content

document how to work with generated columns #11834

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 2.20.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions docs/en/cookbook/generated-columns.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Generated Columns
=================

Generated columns, sometimes also called virtual columns, are populated by
the database engine itself. They are a tool for performance optimization, to
avoid calculating a value on each query.

You can define generated columns on entities and have Doctrine map the values
to your entity.

Declaring a generated column
----------------------------

There is no explicit mapping instruction for generated columns. Instead, you
specify that the column should not be written to, and define a custom column
definition.

.. code-block:: php
.. literalinclude:: generated-columns/Person.php

* ``insertable``, ``updatable``: Setting these to false tells Doctrine to never
write this column - writing to a generated column would result in an error
from the database.
* ``columnDefinition``: We specify the full DDL to create the column. To allow
to use database specific features, this attribute does not use Doctrine Query
Language but native SQL. Note that you need to reference columns by their
database name (either explicitly set in the mapping or per the current
:doc:`naming strategy <../reference/namingstrategy>`).
Be aware that specifying a column definition makes the ``SchemaTool``
completely ignore all other configuration for this column. See also
:ref:`#[Column] <attrref_column>`
* ``generated``: Specifying that this column is always generated tells Doctrine
to update the field on the entity with the value from the database after
every write operation.

Advanced example: Extracting a value from a JSON structure
----------------------------------------------------------

Lets assume we have an entity that stores a blogpost as structured JSON.
To avoid extracting all titles on the fly when listing the posts, we create a
generated column with the field.

.. code-block:: php
.. literalinclude:: generated-columns/Article.php
33 changes: 33 additions & 0 deletions docs/en/cookbook/generated-columns/Article.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Article
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private int $id;

/**
* When working with Postgres, it is recommended to use the jsonb
* format for better performance.
*/
#[ORM\Column(options: ['jsonb' => true])]
private array $content;

/**
* Because we specify NOT NULL, inserting will fail if the content does
* not have a string in the title field.
*/
#[ORM\Column(
insertable: false,
updatable: false,
columnDefinition: "VARCHAR(255) generated always as (content->>'title') stored NOT NULL",
generated: 'ALWAYS',
)]
private string $title;
}
24 changes: 24 additions & 0 deletions docs/en/cookbook/generated-columns/Person.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Person
{
#[ORM\Column(type: 'string')]
private string $firstName;

#[ORM\Column(type: 'string', name: 'name')]
private string $lastName;

#[ORM\Column(
type: 'string',
insertable: false,
updatable: false,
columnDefinition: "VARCHAR(255) GENERATED ALWAYS AS (concat(firstName, ' ', name) stored NOT NULL",
generated: 'ALWAYS',
)]
private string $fullName;
}
2 changes: 2 additions & 0 deletions docs/en/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Cookbook

* **Patterns**:
:doc:`Aggregate Fields <cookbook/aggregate-fields>` \|
:doc:`Generated/Virtual Columns <cookbook/generated-columns>` \|
:doc:`Decorator Pattern <cookbook/decorator-pattern>` \|
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`

Expand All @@ -123,4 +124,5 @@ Cookbook

* **Custom Datatypes**
:doc:`MySQL Enums <cookbook/mysql-enums>`
:doc:`Custom Mapping Types <cookbook/custom-mapping-types>`
:doc:`Advanced Field Value Conversion <cookbook/advanced-field-value-conversion-using-custom-mapping-types>`
22 changes: 14 additions & 8 deletions docs/en/reference/attributes-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,15 @@ Optional parameters:
- ``check``: Adds a check constraint type to the column (might not
be supported by all vendors).

- **columnDefinition**: DDL SQL snippet that starts after the column
- **columnDefinition**: Specify the DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
This attribute allows to make use of advanced RMDBS features.
However you should make careful use of this feature and the
consequences. ``SchemaTool`` will not detect changes on the column correctly
anymore if you use ``columnDefinition``.
However, as this needs to be specified in the DDL native to the database,
the resulting schema changes are no longer portable. If you specify a
``columnDefinition``, the ``SchemaTool`` ignores all other attributes
that are normally used to build the definition DDL. Changes to the
``columnDefinition`` are not detected, you will need to manually create a
migration to apply changes.

Additionally you should remember that the ``type``
attribute still handles the conversion between PHP and Database
Expand Down Expand Up @@ -261,10 +264,11 @@ Examples:
)]
protected $loginCount;

// MySQL example: full_name char(41) GENERATED ALWAYS AS (concat(firstname,' ',lastname)),
// columnDefinition is raw SQL, not DQL. This example works for MySQL:
#[Column(
type: "string",
name: "user_fullname",
columnDefinition: "VARCHAR(255) GENERATED ALWAYS AS (concat(firstname,' ',lastname))",
insertable: false,
updatable: false
)]
Expand Down Expand Up @@ -366,7 +370,7 @@ Optional parameters:

- **type**: By default this is string.
- **length**: By default this is 255.
- **columnDefinition**: By default this is null the definition according to the type will be used. This option allows to override it.
- **columnDefinition**: Allows to override how the column is generated. See the "columnDefinition" attribute on :ref:`#[Column] <attrref_column>`
- **enumType**: By default this is `null`. Allows to map discriminatorColumn value to PHP enum
- **options**: See "options" attribute on :ref:`#[Column] <attrref_column>`.

Expand Down Expand Up @@ -678,8 +682,10 @@ Optional parameters:
- **onDelete**: Cascade Action (Database-level)
- **columnDefinition**: DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
This attribute enables the use of advanced RMDBS features. Using
this attribute on ``#[JoinColumn]`` is necessary if you need slightly
This attribute enables the use of advanced RMDBS features. Note that you
need to reference columns by their database name (either explicitly set in
the mapping or per the current :doc:`naming strategy <namingstrategy>`).
Using this attribute on ``#[JoinColumn]`` is necessary if you need
different column definitions for joining columns, for example
regarding NULL/NOT NULL defaults. However by default a
"columnDefinition" attribute on :ref:`#[Column] <attrref_column>` also sets
Expand Down
1 change: 1 addition & 0 deletions docs/en/sidebar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/generated-columns
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/resolve-target-entity-listener
Expand Down
Loading