mirror of
https://github.com/doctrine/orm.git
synced 2026-04-26 16:08:03 +02:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99b56af279 | |||
| 62e6fb5856 | |||
| be6ddf001e | |||
| 08a930a424 | |||
| a381af4424 | |||
| 81f6bb4d31 | |||
| eca39efc6e | |||
| 65f5f49809 | |||
| 11d7a91e62 | |||
| f414d89b05 | |||
| aa2eb71555 | |||
| eecb1d8efd | |||
| b9dd4fc963 | |||
| e88cd591f0 | |||
| 88a8f75f62 | |||
| 9fe8ce4bf7 | |||
| a46ff16339 | |||
| 94e60e4318 | |||
| f59cd4019a | |||
| 81558a8b2a | |||
| e431ee113d | |||
| 9b226894e7 | |||
| de4ec208fd | |||
| df014a74c9 | |||
| 2f46d95028 | |||
| 3fda5629f6 | |||
| 6b273234d6 | |||
| 580a95ce3f | |||
| 8b91e248eb | |||
| d2266c7d0c | |||
| eb0485869a | |||
| 8372d600c6 | |||
| dde1d71b34 | |||
| 0d03255061 | |||
| 75a18090d9 | |||
| 77ffd2ab68 | |||
| 401cc06d71 | |||
| 5029b193ee | |||
| 27c33cf88d | |||
| 6068b61a0d | |||
| 00024f7d88 | |||
| 331f8b52cb | |||
| b2faba62b7 | |||
| da426a0036 | |||
| 1891a76f13 |
@@ -7,3 +7,7 @@ updates:
|
||||
labels:
|
||||
- "CI"
|
||||
target-branch: "2.20.x"
|
||||
groups:
|
||||
doctrine:
|
||||
patterns:
|
||||
- "doctrine/*"
|
||||
|
||||
@@ -24,4 +24,4 @@ on:
|
||||
|
||||
jobs:
|
||||
coding-standards:
|
||||
uses: "doctrine/.github/.github/workflows/coding-standards.yml@13.1.0"
|
||||
uses: "doctrine/.github/.github/workflows/coding-standards.yml@15.0.0"
|
||||
|
||||
@@ -17,4 +17,4 @@ on:
|
||||
jobs:
|
||||
composer-lint:
|
||||
name: "Composer Lint"
|
||||
uses: "doctrine/.github/.github/workflows/composer-lint.yml@13.1.0"
|
||||
uses: "doctrine/.github/.github/workflows/composer-lint.yml@15.0.0"
|
||||
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
if: "${{ matrix.native_lazy == '0' }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v3"
|
||||
uses: "ramsey/composer-install@v4"
|
||||
with:
|
||||
composer-options: "--ignore-platform-req=php+"
|
||||
dependency-versions: "${{ matrix.deps }}"
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }}
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v6"
|
||||
uses: "actions/upload-artifact@v7"
|
||||
with:
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.stability }}-${{ matrix.native_lazy }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
@@ -257,7 +257,7 @@ jobs:
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v3"
|
||||
uses: "ramsey/composer-install@v4"
|
||||
with:
|
||||
composer-options: "--ignore-platform-req=php+"
|
||||
|
||||
@@ -265,7 +265,7 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v6"
|
||||
uses: "actions/upload-artifact@v7"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
|
||||
path: "coverage.xml"
|
||||
@@ -331,7 +331,7 @@ jobs:
|
||||
extensions: "${{ matrix.extension }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v3"
|
||||
uses: "ramsey/composer-install@v4"
|
||||
with:
|
||||
composer-options: "--ignore-platform-req=php+"
|
||||
|
||||
@@ -339,7 +339,7 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v6"
|
||||
uses: "actions/upload-artifact@v7"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage.xml"
|
||||
@@ -413,7 +413,7 @@ jobs:
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v3"
|
||||
uses: "ramsey/composer-install@v4"
|
||||
with:
|
||||
composer-options: "--ignore-platform-req=php+"
|
||||
|
||||
@@ -442,7 +442,7 @@ jobs:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
|
||||
- name: "Upload coverage files"
|
||||
uses: "actions/upload-artifact@v6"
|
||||
uses: "actions/upload-artifact@v7"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
@@ -465,12 +465,12 @@ jobs:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Download coverage files"
|
||||
uses: "actions/download-artifact@v7"
|
||||
uses: "actions/download-artifact@v8"
|
||||
with:
|
||||
path: "reports"
|
||||
|
||||
- name: "Upload to Codecov"
|
||||
uses: "codecov/codecov-action@v5"
|
||||
uses: "codecov/codecov-action@v6"
|
||||
with:
|
||||
directory: reports
|
||||
env:
|
||||
|
||||
@@ -17,4 +17,4 @@ on:
|
||||
jobs:
|
||||
documentation:
|
||||
name: "Documentation"
|
||||
uses: "doctrine/.github/.github/workflows/documentation.yml@13.1.0"
|
||||
uses: "doctrine/.github/.github/workflows/documentation.yml@15.0.0"
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
ini-values: "zend.assertions=1, apc.enable_cli=1"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v3"
|
||||
uses: "ramsey/composer-install@v4"
|
||||
|
||||
- name: "Run PHPBench"
|
||||
run: "vendor/bin/phpbench run --report=default"
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@13.1.0"
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@15.0.0"
|
||||
secrets:
|
||||
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
|
||||
- name: Install dependencies with Composer
|
||||
uses: ramsey/composer-install@v2
|
||||
uses: ramsey/composer-install@v4
|
||||
|
||||
- name: Run static analysis with phpstan/phpstan
|
||||
run: "vendor/bin/phpstan analyse -c ${{ matrix.config }} --error-format=checkstyle | cs2pr"
|
||||
|
||||
@@ -30,6 +30,13 @@ a property referring to the other side.
|
||||
To gain a full understanding of associations you should also read about :doc:`owning and
|
||||
inverse sides of associations <unitofwork-associations>`
|
||||
|
||||
Composite Foreign Keys
|
||||
----------------------
|
||||
|
||||
When the target entity has a composite primary key, you need to use multiple
|
||||
join column mappings, one for each column of the composite key. See the
|
||||
:doc:`Composite and Foreign Keys <../tutorials/composite-primary-keys>` tutorial for details.
|
||||
|
||||
Many-To-One, Unidirectional
|
||||
---------------------------
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ Here is a complete list of ``Column``s attributes (all optional):
|
||||
- ``insertable`` (default: ``true``): Whether the column should be inserted.
|
||||
- ``updatable`` (default: ``true``): Whether the column should be updated.
|
||||
- ``generated`` (default: ``null``): Whether the generated strategy should be ``'NEVER'``, ``'INSERT'`` and ``ALWAYS``.
|
||||
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into.
|
||||
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into. See :ref:`reference-enum-mapping`.
|
||||
- ``precision`` (default: 0): The precision for a decimal (exact numeric) column
|
||||
(applies only for decimal column),
|
||||
which is the maximum number of digits that are stored for the values.
|
||||
@@ -321,6 +321,160 @@ that value and raw value are different, you have to use the raw value representa
|
||||
$messageRepository = $entityManager->getRepository(Message::class);
|
||||
$deMessages = $messageRepository->findBy(['language' => 'de']); // Use lower case here for raw value representation
|
||||
|
||||
.. _reference-enum-mapping:
|
||||
|
||||
Mapping PHP Enums
|
||||
-----------------
|
||||
|
||||
.. versionadded:: 2.11
|
||||
|
||||
Doctrine natively supports mapping PHP backed enums to database columns.
|
||||
A backed enum is a PHP enum that the same scalar type (``string`` or ``int``)
|
||||
assigned to each case. Doctrine stores the scalar value in the database and
|
||||
converts it back to the enum instance when hydrating the entity.
|
||||
|
||||
Using ``enumType`` provides three main benefits:
|
||||
|
||||
- **Automatic conversion**: Doctrine handles the conversion in both directions
|
||||
transparently. When loading an entity, scalar values from the database are
|
||||
converted into enum instances. When persisting, enum instances are reduced
|
||||
to their scalar ``->value`` before being sent to the database.
|
||||
- **Type-safety**: Entity properties contain enum instances directly. Your
|
||||
getters return ``Suit`` instead of ``string``, removing the need to call
|
||||
``Suit::from()`` manually.
|
||||
- **Validation**: When a database value does not match any enum case, Doctrine
|
||||
throws a ``MappingException`` during hydration instead of silently returning
|
||||
an invalid value.
|
||||
|
||||
This feature works with all database platforms supported by Doctrine (MySQL,
|
||||
PostgreSQL, SQLite, etc.) as it relies on standard column types (``string``,
|
||||
``integer``, ``json``, ``simple_array``) rather than any vendor-specific enum
|
||||
type.
|
||||
|
||||
.. note::
|
||||
|
||||
This is unrelated to the MySQL-specific ``ENUM`` column type covered in
|
||||
:doc:`the MySQL Enums cookbook entry </cookbook/mysql-enums>`.
|
||||
|
||||
Defining an Enum
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. literalinclude:: basic-mapping/Suit.php
|
||||
:language: php
|
||||
|
||||
Only backed enums (``string`` or ``int``) are supported. Unit enums (without
|
||||
a scalar value) cannot be mapped.
|
||||
|
||||
Single-Value Columns
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Use the ``enumType`` option on ``#[Column]`` to map a property to a backed enum.
|
||||
The underlying database column stores the enum's scalar value (``string`` or ``int``).
|
||||
|
||||
.. literalinclude:: basic-mapping/EnumMapping.php
|
||||
:language: php
|
||||
|
||||
When the PHP property is typed with the enum class, Doctrine automatically
|
||||
infers the appropriate column type (``string`` for string-backed enums,
|
||||
``integer`` for int-backed enums) and sets ``enumType``. You can also specify
|
||||
the column ``type`` explicitly.
|
||||
|
||||
Storing Collections of Enums
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can store multiple enum values in a single column by combining ``enumType``
|
||||
with a collection column type: ``json`` or ``simple_array``.
|
||||
|
||||
.. note::
|
||||
|
||||
Automatic type inference does not apply to collection columns. When the
|
||||
PHP property is typed as ``array``, Doctrine cannot detect the enum class.
|
||||
You must specify both ``type`` and ``enumType`` explicitly.
|
||||
|
||||
.. literalinclude:: basic-mapping/EnumCollectionMapping.php
|
||||
:language: php
|
||||
|
||||
With ``json``, the values are stored as a JSON array (e.g. ``["hearts","spades"]``).
|
||||
With ``simple_array``, the values are stored as a comma-separated string
|
||||
(e.g. ``hearts,spades``).
|
||||
|
||||
In both cases, Doctrine converts each element to and from the enum
|
||||
automatically during hydration and persistence.
|
||||
|
||||
.. tip::
|
||||
|
||||
Use ``json`` when enum values may contain commas, when you need to store
|
||||
int-backed enums (as it preserves value types), when the column also
|
||||
stores complex/nested data structures, or when you want to query individual
|
||||
values using database-native JSON operators (e.g. PostgreSQL ``jsonb``).
|
||||
Prefer ``simple_array`` for a compact, human-readable storage of
|
||||
string-backed enums whose values do not contain commas.
|
||||
|
||||
+-------------------+-----------------------------+-------------------------------+
|
||||
| Column type | Database storage | PHP type |
|
||||
+===================+=============================+===============================+
|
||||
| ``string`` | ``hearts`` | ``Suit`` |
|
||||
+-------------------+-----------------------------+-------------------------------+
|
||||
| ``integer`` | ``1`` | ``Priority`` |
|
||||
+-------------------+-----------------------------+-------------------------------+
|
||||
| ``json`` | ``["hearts","spades"]`` | ``array<Suit>`` |
|
||||
+-------------------+-----------------------------+-------------------------------+
|
||||
| ``simple_array`` | ``hearts,spades`` | ``array<Suit>`` |
|
||||
+-------------------+-----------------------------+-------------------------------+
|
||||
|
||||
Nullable Enums
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Enum columns can be nullable. When the database value is ``NULL``, Doctrine
|
||||
preserves it as ``null`` without triggering any validation error.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
#[ORM\Column(type: 'string', nullable: true, enumType: Suit::class)]
|
||||
private Suit|null $suit = null;
|
||||
|
||||
Default Values
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
You can specify a database-level default using an enum case directly in the
|
||||
column options:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
#[ORM\Column(options: ['default' => Suit::Hearts])]
|
||||
public Suit $suit;
|
||||
|
||||
Using Enums in Queries
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Enum instances can be used directly as parameters in DQL, QueryBuilder, and
|
||||
repository methods. Doctrine converts them to their scalar value automatically.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// QueryBuilder
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select('c')
|
||||
->from(Card::class, 'c')
|
||||
->where('c.suit = :suit')
|
||||
->setParameter('suit', Suit::Clubs);
|
||||
|
||||
// Repository
|
||||
$cards = $em->getRepository(Card::class)->findBy(['suit' => Suit::Clubs]);
|
||||
|
||||
XML Mapping
|
||||
~~~~~~~~~~~
|
||||
|
||||
When using XML mapping, the ``enum-type`` attribute is used on ``<field>``
|
||||
elements:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<field name="suit" type="string" enum-type="App\Entity\Suit" />
|
||||
|
||||
.. _reference-mapping-types:
|
||||
|
||||
Doctrine Mapping Types
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
class Player
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private int $id;
|
||||
|
||||
/** @var list<Suit> */
|
||||
#[ORM\Column(type: 'json', enumType: Suit::class)]
|
||||
private array $favouriteSuits = [];
|
||||
|
||||
/** @var list<Suit> */
|
||||
#[ORM\Column(type: 'simple_array', enumType: Suit::class)]
|
||||
private array $allowedSuits = [];
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
class Card
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private int $id;
|
||||
|
||||
#[ORM\Column(enumType: Suit::class)]
|
||||
private Suit $suit;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
enum Suit: string
|
||||
{
|
||||
case Hearts = 'hearts';
|
||||
case Diamonds = 'diamonds';
|
||||
case Clubs = 'clubs';
|
||||
case Spades = 'spades';
|
||||
}
|
||||
@@ -1610,16 +1610,16 @@ Identifiers
|
||||
IdentificationVariable ::= identifier
|
||||
|
||||
/* Alias Identification declaration (the "u" of "FROM User u") */
|
||||
AliasIdentificationVariable :: = identifier
|
||||
AliasIdentificationVariable ::= identifier
|
||||
|
||||
/* identifier that must be a class name (the "User" of "FROM User u"), possibly as a fully qualified class name */
|
||||
AbstractSchemaName ::= fully_qualified_name | identifier
|
||||
|
||||
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
|
||||
AliasResultVariable = identifier
|
||||
AliasResultVariable ::= identifier
|
||||
|
||||
/* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */
|
||||
ResultVariable = identifier
|
||||
ResultVariable ::= identifier
|
||||
|
||||
/* identifier that must be a field (the "name" of "u.name") */
|
||||
/* This is responsible to know if the field exists in Object, no matter if it's a relation or a simple field */
|
||||
@@ -1780,7 +1780,7 @@ Scalar and Type Expressions
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression | BooleanPrimary | CaseExpression | InstanceOfExpression
|
||||
ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | StateFieldPathExpression | BooleanPrimary | CaseExpression | InstanceOfExpression
|
||||
StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
|
||||
StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
|
||||
BooleanExpression ::= BooleanPrimary | "(" Subselect ")"
|
||||
@@ -1806,14 +1806,14 @@ Case Expressions
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
|
||||
CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullIfExpression
|
||||
GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
|
||||
WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
|
||||
SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
|
||||
CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
|
||||
CaseOperand ::= StateFieldPathExpression
|
||||
SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
|
||||
CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
|
||||
NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
|
||||
NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
|
||||
|
||||
Other Expressions
|
||||
~~~~~~~~~~~~~~~~~
|
||||
@@ -1838,7 +1838,7 @@ Functions
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime
|
||||
FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
|
||||
|
||||
FunctionsReturningNumerics ::=
|
||||
"LENGTH" "(" StringPrimary ")" |
|
||||
@@ -1851,7 +1851,7 @@ Functions
|
||||
"BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
|
||||
"BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
|
||||
|
||||
FunctionsReturningDateTime ::=
|
||||
FunctionsReturningDatetime ::=
|
||||
"CURRENT_DATE" |
|
||||
"CURRENT_TIME" |
|
||||
"CURRENT_TIMESTAMP" |
|
||||
|
||||
@@ -114,7 +114,7 @@ functionally equivalent to the previously shown code looks as follows:
|
||||
});
|
||||
|
||||
The difference between ``Connection#transactional($func)`` and
|
||||
``EntityManager#transactional($func)`` is that the latter
|
||||
``EntityManager#wrapInTransaction($func)`` is that the latter
|
||||
abstraction flushes the ``EntityManager`` prior to transaction
|
||||
commit and in case of an exception the ``EntityManager`` gets closed
|
||||
in addition to the transaction rollback.
|
||||
|
||||
@@ -93,14 +93,75 @@ And for querying you can use arrays to both DQL and EntityRepositories:
|
||||
->setParameter(2, 2010)
|
||||
->getSingleResult();
|
||||
|
||||
You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name``
|
||||
and to ``year`` to the related entities.
|
||||
|
||||
.. note::
|
||||
|
||||
This example shows how you can nicely solve the requirement for existing
|
||||
values before ``EntityManager#persist()``: By adding them as mandatory values for the constructor.
|
||||
|
||||
You can also use this entity in associations. Doctrine will then generate a composite foreign key
|
||||
using the ``name`` and ``year`` columns on the related entities.
|
||||
|
||||
To define such an association, you need to use multiple join column mappings, one for each
|
||||
column of the composite primary key:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
namespace VehicleCatalogue\Model;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\JoinColumn;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
|
||||
#[Entity]
|
||||
class Registration
|
||||
{
|
||||
#[Id, Column, GeneratedValue]
|
||||
private int|null $id = null;
|
||||
|
||||
#[ManyToOne(targetEntity: Car::class)]
|
||||
#[JoinColumn(name: 'car_name', referencedColumnName: 'name')]
|
||||
#[JoinColumn(name: 'car_year', referencedColumnName: 'year')]
|
||||
private Car|null $car = null;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="VehicleCatalogue\Model\Registration">
|
||||
<id name="id" type="integer">
|
||||
<generator strategy="AUTO" />
|
||||
</id>
|
||||
|
||||
<many-to-one field="car" target-entity="Car">
|
||||
<join-column name="car_name" referenced-column-name="name" />
|
||||
<join-column name="car_year" referenced-column-name="year" />
|
||||
</many-to-one>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
This generates the following SQL:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE Registration (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
car_name VARCHAR(255) DEFAULT NULL,
|
||||
car_year INT DEFAULT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
ALTER TABLE Registration ADD FOREIGN KEY (car_name, car_year) REFERENCES Car(name, year);
|
||||
|
||||
Identity through foreign Entities
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -18,8 +18,7 @@ before. There are some prerequisites for the tutorial that have to be
|
||||
installed:
|
||||
|
||||
- PHP (latest stable version)
|
||||
- Composer Package Manager (\ `Install Composer
|
||||
<https://getcomposer.org/doc/00-intro.md>`_)
|
||||
- Composer Package Manager (\ `Install Composer <https://getcomposer.org/doc/00-intro.md>`_)
|
||||
|
||||
The code of this tutorial is `available on Github <https://github.com/doctrine/doctrine2-orm-tutorial>`_.
|
||||
|
||||
|
||||
@@ -2580,30 +2580,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/Query/Exec/SingleTableDeleteUpdateExecutor.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc type array\<string\> of property Doctrine\\ORM\\Query\\Expr\\Andx\:\:\$allowedClasses is not covariant with PHPDoc type list\<class\-string\> of overridden property Doctrine\\ORM\\Query\\Expr\\Base\:\:\$allowedClasses\.$#'
|
||||
identifier: property.phpDocType
|
||||
count: 1
|
||||
path: src/Query/Expr/Andx.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Query\\Expr\\Func\:\:getArguments\(\) should return list\<mixed\> but returns array\<mixed\>\.$#'
|
||||
identifier: return.type
|
||||
count: 1
|
||||
path: src/Query/Expr/Func.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc type array\<string\> of property Doctrine\\ORM\\Query\\Expr\\Orx\:\:\$allowedClasses is not covariant with PHPDoc type list\<class\-string\> of overridden property Doctrine\\ORM\\Query\\Expr\\Base\:\:\$allowedClasses\.$#'
|
||||
identifier: property.phpDocType
|
||||
count: 1
|
||||
path: src/Query/Expr/Orx.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc type array\<string\> of property Doctrine\\ORM\\Query\\Expr\\Select\:\:\$allowedClasses is not covariant with PHPDoc type list\<class\-string\> of overridden property Doctrine\\ORM\\Query\\Expr\\Base\:\:\$allowedClasses\.$#'
|
||||
identifier: property.phpDocType
|
||||
count: 1
|
||||
path: src/Query/Expr/Select.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns int so it can be removed from the return type\.$#'
|
||||
identifier: return.unusedType
|
||||
|
||||
@@ -40,13 +40,21 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
|
||||
{
|
||||
$tableAlias = $alias === 'r' ? '' : $alias;
|
||||
$fieldMapping = $class->fieldMappings[$field];
|
||||
$columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName);
|
||||
$sql = sprintf(
|
||||
'%s.%s',
|
||||
$this->getSQLTableAlias($class->name, $tableAlias),
|
||||
$this->quoteStrategy->getColumnName($field, $class, $this->platform),
|
||||
);
|
||||
|
||||
$columnAlias = null;
|
||||
if ($this->currentPersisterContext->rsm->hasColumnAliasByField($alias, $field)) {
|
||||
$columnAlias = $this->currentPersisterContext->rsm->getColumnAliasByField($alias, $field);
|
||||
}
|
||||
|
||||
if ($columnAlias === null) {
|
||||
$columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName);
|
||||
}
|
||||
|
||||
$this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field, $class->name);
|
||||
|
||||
$type = Type::getType($fieldMapping->type);
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Query\Expr;
|
||||
|
||||
use Stringable;
|
||||
|
||||
/**
|
||||
* Expression class for building DQL and parts.
|
||||
*
|
||||
@@ -13,7 +15,7 @@ class Andx extends Composite
|
||||
{
|
||||
protected string $separator = ' AND ';
|
||||
|
||||
/** @var string[] */
|
||||
/** @var list<class-string<Stringable>> */
|
||||
protected array $allowedClasses = [
|
||||
Comparison::class,
|
||||
Func::class,
|
||||
|
||||
@@ -13,6 +13,7 @@ use function get_debug_type;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_object;
|
||||
use function is_string;
|
||||
use function sprintf;
|
||||
|
||||
@@ -27,7 +28,7 @@ abstract class Base implements Stringable
|
||||
protected string $separator = ', ';
|
||||
protected string $postSeparator = ')';
|
||||
|
||||
/** @var list<class-string> */
|
||||
/** @var list<class-string<Stringable>> */
|
||||
protected array $allowedClasses = [];
|
||||
|
||||
/** @var list<string|Stringable> */
|
||||
@@ -58,6 +59,8 @@ abstract class Base implements Stringable
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|Stringable|null $arg
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
@@ -66,7 +69,8 @@ abstract class Base implements Stringable
|
||||
{
|
||||
if ($arg !== null && (! $arg instanceof self || $arg->count() > 0)) {
|
||||
// If we decide to keep Expr\Base instances, we can use this check
|
||||
if (! is_string($arg) && ! in_array($arg::class, $this->allowedClasses, true)) {
|
||||
// @phpstan-ignore function.alreadyNarrowedType (input validation)
|
||||
if (! is_string($arg) && ! (is_object($arg) && in_array($arg::class, $this->allowedClasses, true))) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
"Expression of type '%s' not allowed in this context.",
|
||||
get_debug_type($arg),
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Query\Expr;
|
||||
|
||||
use Stringable;
|
||||
|
||||
/**
|
||||
* Expression class for building DQL OR clauses.
|
||||
*
|
||||
@@ -13,7 +15,7 @@ class Orx extends Composite
|
||||
{
|
||||
protected string $separator = ' OR ';
|
||||
|
||||
/** @var string[] */
|
||||
/** @var list<class-string<Stringable>> */
|
||||
protected array $allowedClasses = [
|
||||
Comparison::class,
|
||||
Func::class,
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Query\Expr;
|
||||
|
||||
use Stringable;
|
||||
|
||||
/**
|
||||
* Expression class for building DQL select statements.
|
||||
*
|
||||
@@ -14,7 +16,7 @@ class Select extends Base
|
||||
protected string $preSeparator = '';
|
||||
protected string $postSeparator = '';
|
||||
|
||||
/** @var string[] */
|
||||
/** @var list<class-string<Stringable>> */
|
||||
protected array $allowedClasses = [Func::class];
|
||||
|
||||
/** @phpstan-var list<string|Func> */
|
||||
|
||||
@@ -1937,7 +1937,7 @@ final class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
|
||||
* ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary |
|
||||
* StateFieldPathExpression | BooleanPrimary | CaseExpression |
|
||||
* InstanceOfExpression
|
||||
*
|
||||
@@ -2011,14 +2011,14 @@ final class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
|
||||
* CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullIfExpression
|
||||
* GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
|
||||
* WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
|
||||
* SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
|
||||
* CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
|
||||
* CaseOperand ::= StateFieldPathExpression
|
||||
* SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
|
||||
* CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
|
||||
* NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
|
||||
* NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
|
||||
*
|
||||
* @return mixed One of the possible expressions or subexpressions.
|
||||
*/
|
||||
@@ -2116,7 +2116,7 @@ final class Parser
|
||||
|
||||
/**
|
||||
* SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
|
||||
* CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
|
||||
* CaseOperand ::= StateFieldPathExpression
|
||||
*/
|
||||
public function SimpleCaseExpression(): AST\SimpleCaseExpression
|
||||
{
|
||||
@@ -3435,7 +3435,7 @@ final class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* FunctionsReturningDateTime ::=
|
||||
* FunctionsReturningDatetime ::=
|
||||
* "CURRENT_DATE" |
|
||||
* "CURRENT_TIME" |
|
||||
* "CURRENT_TIMESTAMP" |
|
||||
|
||||
@@ -363,6 +363,10 @@ class ResultSetMapping
|
||||
|
||||
public function hasColumnAliasByField(string $alias, string $fieldName): bool
|
||||
{
|
||||
if (! isset($this->aliasMap[$alias])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$declaringClass = $this->aliasMap[$alias];
|
||||
|
||||
return isset($this->columnAliasMappings[$declaringClass][$alias][$fieldName]);
|
||||
|
||||
+5
-1
@@ -35,6 +35,7 @@ use Doctrine\ORM\Internal\UnitOfWork\InsertBatch;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\ORM\Mapping\PropertyAccessors\ReadonlyAccessor;
|
||||
use Doctrine\ORM\Mapping\ToManyInverseSideMapping;
|
||||
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
|
||||
use Doctrine\ORM\Persisters\Collection\ManyToManyPersister;
|
||||
@@ -1176,7 +1177,10 @@ class UnitOfWork implements PropertyChangedListener
|
||||
// Entity with this $oid after deletion treated as NEW, even if the $oid
|
||||
// is obtained by a new entity because the old one went out of scope.
|
||||
//$this->entityStates[$oid] = self::STATE_NEW;
|
||||
if (! $class->isIdentifierNatural()) {
|
||||
if (
|
||||
! $class->isIdentifierNatural() &&
|
||||
! $class->propertyAccessors[$class->identifier[0]] instanceof ReadonlyAccessor
|
||||
) {
|
||||
$class->propertyAccessors[$class->identifier[0]]->setValue($entity, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH12063Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->createSchemaForModels(GH12063Association::class, GH12063Entity::class);
|
||||
}
|
||||
|
||||
public function testLoadedAssociationWithBackedEnum(): void
|
||||
{
|
||||
$association = new GH12063Association();
|
||||
$association->code = GH12063Code::One;
|
||||
|
||||
$this->_em->persist($association);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$entity = new GH12063Entity();
|
||||
$entity->association = $this->_em->find(GH12063Association::class, GH12063Code::One);
|
||||
|
||||
$this->_em->persist($entity);
|
||||
$this->_em->flush();
|
||||
|
||||
$this->assertNotNull($entity->id);
|
||||
}
|
||||
|
||||
public function testProxyAssociationWithBackedEnum(): void
|
||||
{
|
||||
$association = new GH12063Association();
|
||||
$association->code = GH12063Code::Two;
|
||||
|
||||
$this->_em->persist($association);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$entity = new GH12063Entity();
|
||||
$entity->association = $this->_em->getReference(GH12063Association::class, GH12063Code::Two);
|
||||
|
||||
$this->_em->persist($entity);
|
||||
$this->_em->flush();
|
||||
|
||||
$this->assertNotNull($entity->id);
|
||||
}
|
||||
}
|
||||
|
||||
enum GH12063Code: string
|
||||
{
|
||||
case One = 'one';
|
||||
case Two = 'two';
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class GH12063Association
|
||||
{
|
||||
#[Id]
|
||||
#[Column(length: 3)]
|
||||
public GH12063Code $code;
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class GH12063Entity
|
||||
{
|
||||
#[Id]
|
||||
#[Column]
|
||||
#[GeneratedValue]
|
||||
public int|null $id = null;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(referencedColumnName: 'code', options: ['length' => 3])]
|
||||
public GH12063Association $association;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH12225;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\DiscriminatorColumn;
|
||||
use Doctrine\ORM\Mapping\DiscriminatorMap;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\Index;
|
||||
use Doctrine\ORM\Mapping\InheritanceType;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
use Doctrine\ORM\Mapping\OneToMany;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
|
||||
#[Entity]
|
||||
#[Table(name: 'gh_12225_directory')]
|
||||
#[Index(columns: ['dir_key'])]
|
||||
#[InheritanceType('SINGLE_TABLE')]
|
||||
#[DiscriminatorColumn(name: 'type', type: 'string')]
|
||||
#[DiscriminatorMap(['main' => ConcreteDirectory::class])]
|
||||
class AbstractDirectory
|
||||
{
|
||||
#[Id]
|
||||
#[GeneratedValue]
|
||||
#[Column(name: 'id', type: 'integer')]
|
||||
private int $id;
|
||||
|
||||
#[Column(name: 'dir_key', type: 'string')]
|
||||
private string $dirKey;
|
||||
|
||||
#[Column(name: 'deleted_at', type: 'datetime_immutable', nullable: true)]
|
||||
private DateTimeImmutable|null $deletedAt = null;
|
||||
|
||||
#[ManyToOne(targetEntity: self::class, fetch: 'LAZY', inversedBy: 'directories')]
|
||||
private AbstractDirectory|null $parent = null;
|
||||
|
||||
/** @var Collection<string, self> */
|
||||
#[OneToMany(mappedBy: 'parent', targetEntity: self::class, fetch: 'EXTRA_LAZY', indexBy: 'dirKey')]
|
||||
private Collection $children;
|
||||
|
||||
public function __construct(string $dirKey)
|
||||
{
|
||||
$this->dirKey = $dirKey;
|
||||
$this->children = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getDirKey(): string
|
||||
{
|
||||
return $this->dirKey;
|
||||
}
|
||||
|
||||
public function getDeletedAt(): DateTimeImmutable|null
|
||||
{
|
||||
return $this->deletedAt;
|
||||
}
|
||||
|
||||
public function setDeletedAt(DateTimeImmutable|null $deletedAt): AbstractDirectory
|
||||
{
|
||||
$this->deletedAt = $deletedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParent(): AbstractDirectory|null
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function setParent(AbstractDirectory|null $parent): AbstractDirectory
|
||||
{
|
||||
$this->parent = $parent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return Collection<string, self> */
|
||||
public function getChildren(): Collection
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH12225;
|
||||
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
|
||||
#[Entity]
|
||||
class ConcreteDirectory extends AbstractDirectory
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH12225;
|
||||
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH12225Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([
|
||||
AbstractDirectory::class,
|
||||
ConcreteDirectory::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testHydrateWithIndexByFilterAndInheritanceMapping(): void
|
||||
{
|
||||
// Enable the filter
|
||||
$this->_em->getConfiguration()->addFilter('my_filter', MyFilter::class);
|
||||
$this->_em->getFilters()->enable('my_filter');
|
||||
|
||||
// Load entities into database
|
||||
$parent = new ConcreteDirectory('parent');
|
||||
$child = (new ConcreteDirectory('child'))->setParent($parent);
|
||||
$this->_em->persist($parent);
|
||||
$this->_em->persist($child);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$repository = $this->_em->getRepository(AbstractDirectory::class);
|
||||
|
||||
// Fetch entities from database while changing filters
|
||||
$this->_em->getFilters()->suspend('my_filter');
|
||||
$directories = $repository->findBy(['parent' => null]);
|
||||
$this->_em->getFilters()->restore('my_filter');
|
||||
|
||||
// Ensure we got the parent directory
|
||||
self::assertCount(1, $directories);
|
||||
self::assertEquals('parent', $directories[0]->getDirKey());
|
||||
|
||||
// Try to hydrate all children of the parent directory (toArray is important here to initialize the collection)
|
||||
self::assertCount(1, $directories[0]->getChildren()->toArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH12225;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query\Filter\SQLFilter;
|
||||
|
||||
class MyFilter extends SQLFilter
|
||||
{
|
||||
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
|
||||
{
|
||||
return $targetTableAlias . '.deleted_at IS NULL';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH9538Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([
|
||||
GH9538EntityA::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testCanRemoveEntityWithReadonlyId(): void
|
||||
{
|
||||
$this->_em->persist($entity = new GH9538EntityA());
|
||||
$this->_em->flush();
|
||||
$this->_em->remove($entity);
|
||||
$this->_em->flush();
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class GH9538EntityA
|
||||
{
|
||||
#[Column(type: 'integer')]
|
||||
#[Id]
|
||||
#[GeneratedValue(strategy: 'AUTO')]
|
||||
public readonly int $id;
|
||||
}
|
||||
@@ -374,11 +374,27 @@ class ExprTest extends OrmTestCase
|
||||
|
||||
public function testAddThrowsException(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$orExpr = $this->expr->orX();
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$orExpr->add($this->expr->quot(5, 2));
|
||||
}
|
||||
|
||||
#[DataProvider('provideInvalidTypesForAdd')]
|
||||
public function testAddThrowsExceptionOnInvalidType(mixed $arg): void
|
||||
{
|
||||
$orExpr = $this->expr->orX();
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$orExpr->add($arg);
|
||||
}
|
||||
|
||||
/** @return Generator<string, array{mixed}> */
|
||||
public static function provideInvalidTypesForAdd(): Generator
|
||||
{
|
||||
yield 'integer 1' => [1];
|
||||
yield 'object' => [(object) ['foo' => 'bar']];
|
||||
yield 'array' => [['foo' => 'bar']];
|
||||
}
|
||||
|
||||
#[Group('DDC-1683')]
|
||||
public function testBooleanLiteral(): void
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user