Skip to content

Commit 9ccca55

Browse files
committed
Deprecate string default expressions
Right now, the ORM handles the conversion of strings that happen to be default expressions for date, time and datetime columns into the corresponding value objects. Let us allow users to specify these value objects directly, and deprecate relying on the aforementioned conversion.
1 parent 458b040 commit 9ccca55

File tree

12 files changed

+499
-9
lines changed

12 files changed

+499
-9
lines changed

UPGRADE.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,95 @@ and directly start using native lazy objects.
2929

3030
# Upgrade to 3.6
3131

32+
## Deprecate using string expression for default values in mappings
33+
34+
Using a string expression for default values in field mappings is deprecated.
35+
Use `Doctrine\DBAL\Schema\DefaultExpression` instances instead.
36+
37+
Here is how to address this deprecation when mapping entities using PHP attributes:
38+
39+
```diff
40+
use DateTime;
41+
+use Doctrine\DBAL\Schema\DefaultExpression\CurrentDate;
42+
+use Doctrine\DBAL\Schema\DefaultExpression\CurrentTime;
43+
+use Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp;
44+
use Doctrine\ORM\Mapping as ORM;
45+
46+
#[ORM\Entity]
47+
final class TimeEntity
48+
{
49+
#[ORM\Id]
50+
#[ORM\Column]
51+
public int $id;
52+
53+
- #[ORM\Column(options: ['default' => 'CURRENT_TIMESTAMP'], insertable: false, updatable: false)]
54+
+ #[ORM\Column(options: ['default' => new CurrentTimestamp()], insertable: false, updatable: false)]
55+
public DateTime $createdAt;
56+
57+
- #[ORM\Column(options: ['default' => 'CURRENT_TIME'], insertable: false, updatable: false)]
58+
+ #[ORM\Column(options: ['default' => new CurrentTime()], insertable: false, updatable: false)]
59+
public DateTime $createdTime;
60+
61+
- #[ORM\Column(options: ['default' => 'CURRENT_DATE'], insertable: false, updatable: false)]
62+
+ #[ORM\Column(options: ['default' => new CurrentDate()], insertable: false, updatable: false)]
63+
public DateTime $createdDate;
64+
}
65+
```
66+
67+
Here is how to do the same when mapping entities using XML:
68+
69+
```diff
70+
<?xml version="1.0" encoding="UTF-8"?>
71+
72+
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
73+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
74+
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
75+
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
76+
77+
<entity name="Doctrine\Tests\ORM\Functional\XmlTimeEntity">
78+
<id name="id" type="integer" column="id">
79+
<generator strategy="AUTO"/>
80+
</id>
81+
82+
<field name="createdAt" type="datetime" insertable="false" updatable="false">
83+
<options>
84+
- <option name="default">CURRENT_TIMESTAMP</option>
85+
+ <option name="default">
86+
+ <object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp"/>
87+
+ </option>
88+
</options>
89+
</field>
90+
91+
<field name="createdAtImmutable" type="datetime_immutable" insertable="false" updatable="false">
92+
<options>
93+
- <option name="default">CURRENT_TIMESTAMP</option>
94+
+ <option name="default">
95+
+ <object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp"/>
96+
+ </option>
97+
</options>
98+
</field>
99+
100+
<field name="createdTime" type="time" insertable="false" updatable="false">
101+
<options>
102+
- <option name="default">CURRENT_TIME</option>
103+
+ <option name="default">
104+
+ <object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTime"/>
105+
+ </option>
106+
</options>
107+
</field>
108+
<field name="createdDate" type="date" insertable="false" updatable="false">
109+
<options>
110+
- <option name="default">CURRENT_DATE</option>
111+
+ <option name="default">
112+
+ <object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentDate"/>
113+
+ </option>
114+
</options>
115+
</field>
116+
</entity>
117+
</doctrine-mapping>
118+
```
119+
120+
32121
## Deprecate `FieldMapping::$default`
33122

34123
The `default` property of `Doctrine\ORM\Mapping\FieldMapping` is deprecated and

docs/en/reference/basic-mapping.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,22 @@ PHP class, Doctrine also allows you to specify default values for
190190
database columns using the ``default`` key in the ``options`` array of
191191
the ``Column`` attribute.
192192

193+
When using XML, you can specify object instances using the ``<object>``
194+
element:
195+
196+
.. code-block:: xml
197+
198+
<field name="createdAt" type="datetime" insertable="false" updatable="false">
199+
<options>
200+
<option name="default">
201+
<object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp"/>
202+
</option>
203+
</options>
204+
</field>
205+
206+
The ``<object>`` element requires a ``class`` attribute specifying the
207+
fully qualified class name to instantiate.
208+
193209
.. configuration-block::
194210
.. literalinclude:: basic-mapping/DefaultValues.php
195211
:language: attribute

docs/en/reference/basic-mapping/DefaultValues.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace App\Entity;
66

7+
use DateTime;
8+
use Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp;
79
use Doctrine\ORM\Mapping\Column;
810
use Doctrine\ORM\Mapping\Entity;
911

@@ -12,4 +14,7 @@ class Message
1214
{
1315
#[Column(options: ['default' => 'Hello World!'])]
1416
private string $text;
17+
18+
#[Column(options: ['default' => new CurrentTimestamp()], insertable: false, updatable: false)]
19+
private DateTime $createdAt;
1520
}

docs/en/reference/basic-mapping/default-values.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,12 @@
55
<option name="default">Hello World!</option>
66
</options>
77
</field>
8+
<field name="createdAt" insertable="false" updatable="false">
9+
<options>
10+
<option name="default">
11+
<object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp"/>
12+
</option>
13+
</options>
14+
</field>
815
</entity>
916
</doctrine-mapping>

doctrine-mapping.xsd

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,15 @@
155155
</xs:restriction>
156156
</xs:simpleType>
157157

158+
<xs:complexType name="object">
159+
<xs:attribute name="class" type="xs:string" use="required"/>
160+
<xs:anyAttribute namespace="##other"/>
161+
</xs:complexType>
162+
158163
<xs:complexType name="option" mixed="true">
159164
<xs:choice minOccurs="0" maxOccurs="unbounded">
160165
<xs:element name="option" type="orm:option"/>
166+
<xs:element name="object" type="orm:object"/>
161167
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
162168
</xs:choice>
163169
<xs:attribute name="name" type="xs:NMTOKEN" use="required"/>

phpstan-baseline.neon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,7 +1441,7 @@ parameters:
14411441
path: src/Mapping/Driver/XmlDriver.php
14421442

14431443
-
1444-
message: '#^Parameter \#1 \$columnDef of method Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\>\:\:setDiscriminatorColumn\(\) expects array\{name\: string\|null, fieldName\?\: string\|null, type\?\: string\|null, length\?\: int\|null, columnDefinition\?\: string\|null, enumType\?\: class\-string\<BackedEnum\>\|null, options\?\: array\<string, mixed\>\|null\}\|Doctrine\\ORM\\Mapping\\DiscriminatorColumnMapping\|null, array\{name\: string\|null, type\: string, length\: int, columnDefinition\: string\|null, enumType\: string\|null, options\?\: array\<int\|string, array\<int\|string, mixed\>\|bool\|string\>\} given\.$#'
1444+
message: '#^Parameter \#1 \$columnDef of method Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\>\:\:setDiscriminatorColumn\(\) expects array\{name\: string\|null, fieldName\?\: string\|null, type\?\: string\|null, length\?\: int\|null, columnDefinition\?\: string\|null, enumType\?\: class\-string\<BackedEnum\>\|null, options\?\: array\<string, mixed\>\|null\}\|Doctrine\\ORM\\Mapping\\DiscriminatorColumnMapping\|null, array\{name\: string\|null, type\: string, length\: int, columnDefinition\: string\|null, enumType\: string\|null, options\?\: array\<int\|string, array\<int\|string, mixed\>\|bool\|object\|string\>\} given\.$#'
14451445
identifier: argument.type
14461446
count: 1
14471447
path: src/Mapping/Driver/XmlDriver.php
@@ -1471,7 +1471,7 @@ parameters:
14711471
path: src/Mapping/Driver/XmlDriver.php
14721472

14731473
-
1474-
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\>\:\:\$table \(array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\?\: array\<string, mixed\>, quoted\?\: bool\}\) does not accept array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\: array\<int\|string, array\<int\|string, mixed\>\|bool\|string\>, quoted\?\: bool\}\.$#'
1474+
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\>\:\:\$table \(array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\?\: array\<string, mixed\>, quoted\?\: bool\}\) does not accept array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\: array\<int\|string, array\<int\|string, mixed\>\|bool\|object\|string\>, quoted\?\: bool\}\.$#'
14751475
identifier: assign.propertyType
14761476
count: 1
14771477
path: src/Mapping/Driver/XmlDriver.php

src/Mapping/Driver/XmlDriver.php

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use SimpleXMLElement;
1717

1818
use function assert;
19+
use function class_exists;
1920
use function constant;
2021
use function count;
2122
use function defined;
@@ -656,15 +657,30 @@ public function loadMetadataForClass($className, PersistenceClassMetadata $metad
656657
* Parses (nested) option elements.
657658
*
658659
* @return mixed[] The options array.
659-
* @phpstan-return array<int|string, array<int|string, mixed|string>|bool|string>
660+
* @phpstan-return array<int|string, array<int|string, mixed|string>|bool|string|object>
660661
*/
661662
private function parseOptions(SimpleXMLElement|null $options): array
662663
{
663664
$array = [];
664665

665666
foreach ($options ?? [] as $option) {
667+
$value = null;
666668
if ($option->count()) {
667-
$value = $this->parseOptions($option->children());
669+
// Check if this option contains an <object> element
670+
$children = $option->children();
671+
$hasObjectElement = false;
672+
673+
foreach ($children as $child) {
674+
if ($child->getName() === 'object') {
675+
$value = $this->parseObjectElement($child);
676+
$hasObjectElement = true;
677+
break;
678+
}
679+
}
680+
681+
if (! $hasObjectElement) {
682+
$value = $this->parseOptions($children);
683+
}
668684
} else {
669685
$value = (string) $option;
670686
}
@@ -684,6 +700,33 @@ private function parseOptions(SimpleXMLElement|null $options): array
684700
return $array;
685701
}
686702

703+
/**
704+
* Parses an <object> element and returns the instantiated object.
705+
*
706+
* @param SimpleXMLElement $objectElement The XML element.
707+
*
708+
* @return object The instantiated object.
709+
*
710+
* @throws MappingException If the object specification is invalid.
711+
* @throws InvalidArgumentException If the class does not exist.
712+
*/
713+
private function parseObjectElement(SimpleXMLElement $objectElement): object
714+
{
715+
$attributes = $objectElement->attributes();
716+
717+
if (! isset($attributes->class)) {
718+
throw MappingException::missingRequiredOption('object', 'class');
719+
}
720+
721+
$className = (string) $attributes->class;
722+
723+
if (! class_exists($className)) {
724+
throw new InvalidArgumentException(sprintf('Class "%s" does not exist', $className));
725+
}
726+
727+
return new $className();
728+
}
729+
687730
/**
688731
* Constructs a joinColumn mapping array based on the information
689732
* found in the given SimpleXMLElement.

src/Tools/SchemaTool.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Doctrine\DBAL\Schema\Schema;
2424
use Doctrine\DBAL\Schema\Table;
2525
use Doctrine\DBAL\Types\Types;
26+
use Doctrine\Deprecations\Deprecation;
2627
use Doctrine\ORM\EntityManagerInterface;
2728
use Doctrine\ORM\Mapping\AssociationMapping;
2829
use Doctrine\ORM\Mapping\ClassMetadata;
@@ -507,20 +508,50 @@ private function gatherColumn(
507508
], true)
508509
&& $options['default'] === $this->platform->getCurrentTimestampSQL()
509510
) {
511+
Deprecation::trigger(
512+
'doctrine/orm',
513+
'https://github.com/doctrine/orm/issues/12252',
514+
<<<'DEPRECATION'
515+
Using "%s" as a default value for datetime fields is deprecated and
516+
will not be supported in Doctrine ORM 4.0.
517+
Pass a `Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp` instance instead.
518+
DEPRECATION,
519+
$this->platform->getCurrentTimestampSQL(),
520+
);
510521
$options['default'] = new CurrentTimestamp();
511522
}
512523

513524
if (
514525
in_array($mapping->type, [Types::TIME_MUTABLE, Types::TIME_IMMUTABLE], true)
515526
&& $options['default'] === $this->platform->getCurrentTimeSQL()
516527
) {
528+
Deprecation::trigger(
529+
'doctrine/orm',
530+
'https://github.com/doctrine/orm/issues/12252',
531+
<<<'DEPRECATION'
532+
Using "%s" as a default value for time fields is deprecated and
533+
will not be supported in Doctrine ORM 4.0.
534+
Pass a `Doctrine\DBAL\Schema\DefaultExpression\CurrentTime` instance instead.
535+
DEPRECATION,
536+
$this->platform->getCurrentTimeSQL(),
537+
);
517538
$options['default'] = new CurrentTime();
518539
}
519540

520541
if (
521542
in_array($mapping->type, [Types::DATE_MUTABLE, Types::DATE_IMMUTABLE], true)
522543
&& $options['default'] === $this->platform->getCurrentDateSQL()
523544
) {
545+
Deprecation::trigger(
546+
'doctrine/orm',
547+
'https://github.com/doctrine/orm/issues/12252',
548+
<<<'DEPRECATION'
549+
Using "%s" as a default value for date fields is deprecated and
550+
will not be supported in Doctrine ORM 4.0.
551+
Pass a `Doctrine\DBAL\Schema\DefaultExpression\CurrentDate` instance instead.
552+
DEPRECATION,
553+
$this->platform->getCurrentDateSQL(),
554+
);
524555
$options['default'] = new CurrentDate();
525556
}
526557
}

0 commit comments

Comments
 (0)