mirror of
https://github.com/doctrine/orm.git
synced 2026-04-27 16:33:40 +02:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99b56af279 | |||
| 62e6fb5856 | |||
| be6ddf001e | |||
| 08a930a424 | |||
| a381af4424 | |||
| 81f6bb4d31 | |||
| eca39efc6e | |||
| 65f5f49809 | |||
| 11d7a91e62 | |||
| f414d89b05 | |||
| aa2eb71555 | |||
| eecb1d8efd | |||
| b9dd4fc963 | |||
| 9b226894e7 |
@@ -24,4 +24,4 @@ on:
|
||||
|
||||
jobs:
|
||||
coding-standards:
|
||||
uses: "doctrine/.github/.github/workflows/coding-standards.yml@14.0.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@14.0.0"
|
||||
uses: "doctrine/.github/.github/workflows/composer-lint.yml@15.0.0"
|
||||
|
||||
@@ -17,4 +17,4 @@ on:
|
||||
jobs:
|
||||
documentation:
|
||||
name: "Documentation"
|
||||
uses: "doctrine/.github/.github/workflows/documentation.yml@14.0.0"
|
||||
uses: "doctrine/.github/.github/workflows/documentation.yml@15.0.0"
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@14.0.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 }}
|
||||
|
||||
@@ -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
|
||||
---------------------------
|
||||
|
||||
|
||||
@@ -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>`_.
|
||||
|
||||
|
||||
@@ -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" |
|
||||
|
||||
+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,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;
|
||||
}
|
||||
Reference in New Issue
Block a user