mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
121 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a541b8b3a | ||
|
|
9fb9cc46e4 | ||
|
|
c322c71cd4 | ||
|
|
3c733a2fee | ||
|
|
5984ad586a | ||
|
|
ee2c3a506b | ||
|
|
781ed30926 | ||
|
|
04694a9f7b | ||
|
|
257c5094c4 | ||
|
|
66e0e92816 | ||
|
|
5b2060e25f | ||
|
|
64444dcfd5 | ||
|
|
eb2cd5375c | ||
|
|
de7140e105 | ||
|
|
39e35fc06c | ||
|
|
7f061c3870 | ||
|
|
74495711fb | ||
|
|
97a7cb8d2f | ||
|
|
85d66de9df | ||
|
|
1b98be31ce | ||
|
|
61f2752a80 | ||
|
|
f3371e1773 | ||
|
|
6476894dc4 | ||
|
|
e0052390e1 | ||
|
|
8c6419e0e0 | ||
|
|
6f5ce1aca2 | ||
|
|
98e7a53b42 | ||
|
|
3aaaf37dfb | ||
|
|
154a4652ee | ||
|
|
ae7489ff19 | ||
|
|
0f32569a7a | ||
|
|
d99f74c704 | ||
|
|
62ca8424d8 | ||
|
|
3f2209a571 | ||
|
|
1ee01f4473 | ||
|
|
8a9ed138a8 | ||
|
|
e714b1a2fc | ||
|
|
ec0bf05853 | ||
|
|
dc58aa3ea1 | ||
|
|
23b74e4f8b | ||
|
|
d2b699e6f5 | ||
|
|
0338d69324 | ||
|
|
2c01dac173 | ||
|
|
137ecb491a | ||
|
|
f38ee09082 | ||
|
|
6ab858a5c5 | ||
|
|
3dca27ce0d | ||
|
|
e19704e1f8 | ||
|
|
41ea59ac66 | ||
|
|
e605e6d569 | ||
|
|
9437675d3b | ||
|
|
63409d638c | ||
|
|
9b9160b206 | ||
|
|
6deec3655b | ||
|
|
7f40422d21 | ||
|
|
e67fa5388b | ||
|
|
80053336c9 | ||
|
|
dddcc507ef | ||
|
|
b41d9da88d | ||
|
|
c04bfb78b7 | ||
|
|
8a5dfc86d4 | ||
|
|
79e103c07e | ||
|
|
5afadf163a | ||
|
|
edfaa37228 | ||
|
|
ea056e98ba | ||
|
|
bab5771e98 | ||
|
|
ee919d6231 | ||
|
|
04c390693a | ||
|
|
49293c4d48 | ||
|
|
8d9e2e7d4e | ||
|
|
ef607f26c2 | ||
|
|
ed543a205c | ||
|
|
de1c28bb16 | ||
|
|
60ff966d54 | ||
|
|
33684253c3 | ||
|
|
b4ca0cd5fb | ||
|
|
a49c1beb93 | ||
|
|
76852cfef3 | ||
|
|
3bd89caf36 | ||
|
|
eb2e7d959c | ||
|
|
a4b20356f4 | ||
|
|
2550b2d1de | ||
|
|
e94e1ab126 | ||
|
|
6307b4fa7d | ||
|
|
19e1a64a91 | ||
|
|
082e776e91 | ||
|
|
bbde41f712 | ||
|
|
8c0994f35f | ||
|
|
3d390bc053 | ||
|
|
16f1be7f10 | ||
|
|
1334162a56 | ||
|
|
68ec3ebaa3 | ||
|
|
4f4ed2f242 | ||
|
|
79cc70a62f | ||
|
|
4e6b5a1b0b | ||
|
|
21b144fff9 | ||
|
|
658940de38 | ||
|
|
ad487370f5 | ||
|
|
259f83b549 | ||
|
|
4a24860dcf | ||
|
|
116cdf8661 | ||
|
|
528b8837e1 | ||
|
|
067ad51b3f | ||
|
|
00c77213fb | ||
|
|
c68b8f90b3 | ||
|
|
aa4f9ce9e9 | ||
|
|
d96fc23327 | ||
|
|
4fb044d5f6 | ||
|
|
2a953c5e2b | ||
|
|
abc6a40ccb | ||
|
|
73e68f3c7d | ||
|
|
73777d0bd4 | ||
|
|
ec6d1b9f72 | ||
|
|
d809fed52a | ||
|
|
0e4786dfa8 | ||
|
|
c429262f02 | ||
|
|
f4fdcbcdcb | ||
|
|
b0806469d5 | ||
|
|
e89b58a13f | ||
|
|
2b94ec18b9 | ||
|
|
2a662149f4 |
@@ -12,38 +12,39 @@
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.5",
|
||||
"branchName": "3.5.x",
|
||||
"slug": "3.5",
|
||||
"name": "3.6",
|
||||
"branchName": "3.6.x",
|
||||
"slug": "3.6",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.4",
|
||||
"branchName": "3.4.x",
|
||||
"slug": "3.4",
|
||||
"name": "3.5",
|
||||
"branchName": "3.5.x",
|
||||
"slug": "3.5",
|
||||
"current": true
|
||||
},
|
||||
{
|
||||
"name": "3.4",
|
||||
"slug": "3.4",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "3.3",
|
||||
"branchName": "3.3.x",
|
||||
"slug": "3.3",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "3.2",
|
||||
"branchName": "3.2.x",
|
||||
"slug": "3.2",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "3.1",
|
||||
"branchName": "3.1.x",
|
||||
"slug": "3.1",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "3.0",
|
||||
"branchName": "3.0.x",
|
||||
"slug": "3.0",
|
||||
"maintained": false
|
||||
},
|
||||
@@ -61,61 +62,51 @@
|
||||
},
|
||||
{
|
||||
"name": "2.19",
|
||||
"branchName": "2.19.x",
|
||||
"slug": "2.19",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.18",
|
||||
"branchName": "2.18.x",
|
||||
"slug": "2.18",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.17",
|
||||
"branchName": "2.17.x",
|
||||
"slug": "2.17",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.16",
|
||||
"branchName": "2.16.x",
|
||||
"slug": "2.16",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.15",
|
||||
"branchName": "2.15.x",
|
||||
"slug": "2.15",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.14",
|
||||
"branchName": "2.14.x",
|
||||
"slug": "2.14",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.13",
|
||||
"branchName": "2.13.x",
|
||||
"slug": "2.13",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.12",
|
||||
"branchName": "2.12.x",
|
||||
"slug": "2.12",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.11",
|
||||
"branchName": "2.11.x",
|
||||
"slug": "2.11",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.10",
|
||||
"branchName": "2.10.x",
|
||||
"slug": "2.10",
|
||||
"maintained": false
|
||||
}
|
||||
|
||||
2
.github/workflows/continuous-integration.yml
vendored
2
.github/workflows/continuous-integration.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Continuous Integration"
|
||||
name: "CI"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
19
README.md
19
README.md
@@ -1,7 +1,7 @@
|
||||
| [4.0.x][4.0] | [3.5.x][3.5] | [3.4.x][3.4] | [2.21.x][2.21] | [2.20.x][2.20] |
|
||||
| [4.0.x][4.0] | [3.6.x][3.6] | [3.5.x][3.5] | [2.21.x][2.21] | [2.20.x][2.20] |
|
||||
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
|
||||
| [![Build status][4.0 image]][4.0] | [![Build status][3.5 image]][3.5] | [![Build status][3.4 image]][3.4] | [![Build status][2.21 image]][2.21] | [![Build status][2.20 image]][2.20] |
|
||||
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.5 coverage image]][3.5 coverage] | [![Coverage Status][3.4 coverage image]][3.4 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] |
|
||||
| [![Build status][4.0 image]][4.0 workflow] | [![Build status][3.6 image]][3.6 workflow] | [![Build status][3.5 image]][3.5 workflow] | [![Build status][2.21 image]][2.21 workflow] | [![Build status][2.20 image]][2.20 workflow] |
|
||||
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.6 coverage image]][3.6 coverage] | [![Coverage Status][3.5 coverage image]][3.5 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] |
|
||||
|
||||
Doctrine ORM is an object-relational mapper for PHP 8.1+ that provides transparent persistence
|
||||
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
|
||||
@@ -18,21 +18,26 @@ without requiring unnecessary code duplication.
|
||||
|
||||
[4.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0.x
|
||||
[4.0]: https://github.com/doctrine/orm/tree/4.0.x
|
||||
[4.0 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A4.0.x
|
||||
[4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg
|
||||
[4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x
|
||||
[3.6 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.6.x
|
||||
[3.6]: https://github.com/doctrine/orm/tree/3.6.x
|
||||
[3.6 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A3.6.x
|
||||
[3.6 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.6.x/graph/badge.svg
|
||||
[3.6 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.6.x
|
||||
[3.5 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.5.x
|
||||
[3.5]: https://github.com/doctrine/orm/tree/3.5.x
|
||||
[3.5 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A3.5.x
|
||||
[3.5 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.5.x/graph/badge.svg
|
||||
[3.5 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.5.x
|
||||
[3.4 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.4.x
|
||||
[3.4]: https://github.com/doctrine/orm/tree/3.4.x
|
||||
[3.4 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.4.x/graph/badge.svg
|
||||
[3.4 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.4.x
|
||||
[2.21 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.21.x
|
||||
[2.21]: https://github.com/doctrine/orm/tree/2.21.x
|
||||
[2.21 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A2.21.x
|
||||
[2.21 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.21.x/graph/badge.svg
|
||||
[2.21 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.21.x
|
||||
[2.20 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.20.x
|
||||
[2.20]: https://github.com/doctrine/orm/tree/2.20.x
|
||||
[2.20 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A2.20.x
|
||||
[2.20 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg
|
||||
[2.20 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.20.x
|
||||
|
||||
48
UPGRADE.md
48
UPGRADE.md
@@ -1,3 +1,49 @@
|
||||
# Upgrade to 3.5
|
||||
|
||||
## Deprecate not using native lazy objects on PHP 8.4+
|
||||
|
||||
Having native lazy objects disabled on PHP 8.4+ is deprecated and will not be
|
||||
possible in 4.0.
|
||||
|
||||
You can enable them through configuration:
|
||||
|
||||
```php
|
||||
$config->enableNativeLazyObjects(true);
|
||||
```
|
||||
|
||||
As a consequence, methods, parameters and commands related to userland lazy
|
||||
objects have been deprecated on PHP 8.4+:
|
||||
|
||||
- `Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand`
|
||||
- `Doctrine\ORM\Configuration::getAutoGenerateProxyClasses()`
|
||||
- `Doctrine\ORM\Configuration::getProxyDir()`
|
||||
- `Doctrine\ORM\Configuration::getProxyNamespace()`
|
||||
- `Doctrine\ORM\Configuration::setAutoGenerateProxyClasses()`
|
||||
- `Doctrine\ORM\Configuration::setProxyDir()`
|
||||
- `Doctrine\ORM\Configuration::setProxyNamespace()`
|
||||
- Passing more than one argument to `Doctrine\ORM\Proxy\ProxyFactory::__construct()`
|
||||
|
||||
Additionally, some methods of ORMSetup have been deprecated in favor of a new
|
||||
counterpart.
|
||||
|
||||
- `Doctrine\ORM\ORMSetup::createAttributeMetadataConfiguration()` is deprecated in favor of
|
||||
`Doctrine\ORM\ORMSetup::createAttributeMetadataConfig()`
|
||||
- `Doctrine\ORM\ORMSetup::createXMLMetadataConfiguration()` is deprecated in favor of
|
||||
`Doctrine\ORM\ORMSetup::createXMLMetadataConfig()`
|
||||
- `Doctrine\ORM\ORMSetup::createConfiguration()` is deprecated in favor of
|
||||
`Doctrine\ORM\ORMSetup::createConfig()`
|
||||
|
||||
## Deprecate methods for configuring no longer configurable features
|
||||
|
||||
Since 3.0, lazy ghosts are enabled unconditionally, and so is rejecting ID
|
||||
collisions in the identity map.
|
||||
|
||||
As a consequence, the following methods are deprecated and will be removed in 4.0:
|
||||
* `Doctrine\ORM\Configuration::setLazyGhostObjectEnabled()`
|
||||
* `Doctrine\ORM\Configuration::isLazyGhostObjectEnabled()`
|
||||
* `Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()`
|
||||
* `Doctrine\ORM\Configuration::isRejectIdCollisionInIdentityMapEnabled()`
|
||||
|
||||
# Upgrade to 3.4.1
|
||||
|
||||
## BC BREAK: You can no longer use the `.*` notation to get all fields of an entity in a DTO
|
||||
@@ -23,6 +69,8 @@ that implements `ArrayAccess`.
|
||||
Use the new `Doctrine\ORM\Mapping\PropertyAccessors\PropertyAccessor` API and access
|
||||
through `Doctrine\ORM\Mapping\ClassMetadata::$propertyAccessors` instead.
|
||||
|
||||
Companion accessor methods are deprecated as well.
|
||||
|
||||
# Upgrade to 3.3
|
||||
|
||||
## Deprecate `DatabaseDriver`
|
||||
|
||||
@@ -46,17 +46,18 @@ entities:
|
||||
#[Entity]
|
||||
class Article
|
||||
{
|
||||
const STATUS_VISIBLE = 'visible';
|
||||
const STATUS_INVISIBLE = 'invisible';
|
||||
public const STATUS_VISIBLE = 'visible';
|
||||
public const STATUS_INVISIBLE = 'invisible';
|
||||
|
||||
#[Column(type: "string")]
|
||||
private $status;
|
||||
|
||||
public function setStatus($status)
|
||||
public function setStatus(string $status): void
|
||||
{
|
||||
if (!in_array($status, array(self::STATUS_VISIBLE, self::STATUS_INVISIBLE))) {
|
||||
if (!in_array($status, [self::STATUS_VISIBLE, self::STATUS_INVISIBLE], true)) {
|
||||
throw new \InvalidArgumentException("Invalid status");
|
||||
}
|
||||
|
||||
$this->status = $status;
|
||||
}
|
||||
}
|
||||
@@ -92,37 +93,33 @@ For example for the previous enum type:
|
||||
|
||||
class EnumVisibilityType extends Type
|
||||
{
|
||||
const ENUM_VISIBILITY = 'enumvisibility';
|
||||
const STATUS_VISIBLE = 'visible';
|
||||
const STATUS_INVISIBLE = 'invisible';
|
||||
private const ENUM_VISIBILITY = 'enumvisibility';
|
||||
private const STATUS_VISIBLE = 'visible';
|
||||
private const STATUS_INVISIBLE = 'invisible';
|
||||
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
|
||||
{
|
||||
return "ENUM('visible', 'invisible')";
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): string
|
||||
{
|
||||
if (!in_array($value, array(self::STATUS_VISIBLE, self::STATUS_INVISIBLE))) {
|
||||
if (!in_array($value, [self::STATUS_VISIBLE, self::STATUS_INVISIBLE], true)) {
|
||||
throw new \InvalidArgumentException("Invalid status");
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return self::ENUM_VISIBILITY;
|
||||
}
|
||||
|
||||
public function requiresSQLCommentHint(AbstractPlatform $platform)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
You can register this type with ``Type::addType('enumvisibility', 'MyProject\DBAL\EnumVisibilityType');``.
|
||||
@@ -151,37 +148,33 @@ You can generalize this approach easily to create a base class for enums:
|
||||
abstract class EnumType extends Type
|
||||
{
|
||||
protected $name;
|
||||
protected $values = array();
|
||||
protected $values = [];
|
||||
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
|
||||
{
|
||||
$values = array_map(function($val) { return "'".$val."'"; }, $this->values);
|
||||
$values = array_map(fn($val) => "'".$val."'", $this->values);
|
||||
|
||||
return "ENUM(".implode(", ", $values).")";
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): mixed
|
||||
{
|
||||
if (!in_array($value, $this->values)) {
|
||||
if (!in_array($value, $this->values, true)) {
|
||||
throw new \InvalidArgumentException("Invalid '".$this->name."' value.");
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function requiresSQLCommentHint(AbstractPlatform $platform)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
With this base class you can define an enum as easily as:
|
||||
@@ -194,5 +187,5 @@ With this base class you can define an enum as easily as:
|
||||
class EnumVisibilityType extends EnumType
|
||||
{
|
||||
protected $name = 'enumvisibility';
|
||||
protected $values = array('visible', 'invisible');
|
||||
protected $values = ['visible', 'invisible'];
|
||||
}
|
||||
|
||||
@@ -175,6 +175,10 @@ Optional parameters:
|
||||
- **unique**: Boolean value to determine if the value of the column
|
||||
should be unique across all rows of the underlying entities table.
|
||||
|
||||
- **index**: Boolean value to generate an index for this column.
|
||||
For more advanced usages, take a look at :ref:`#[Index] <attrref_index>`.
|
||||
If not specified, default value is ``false``.
|
||||
|
||||
- **nullable**: Determines if NULL values allowed for this column.
|
||||
If not specified, default value is ``false``.
|
||||
|
||||
@@ -245,6 +249,9 @@ Examples:
|
||||
#[Column(type: "string", length: 32, unique: true, nullable: false)]
|
||||
protected $username;
|
||||
|
||||
#[Column(type: "string", index: true)]
|
||||
protected $firstName;
|
||||
|
||||
#[Column(type: "string", columnDefinition: "CHAR(2) NOT NULL")]
|
||||
protected $country;
|
||||
|
||||
|
||||
@@ -56,7 +56,8 @@ access point to ORM functionality provided by Doctrine.
|
||||
'dbname' => 'foo',
|
||||
];
|
||||
|
||||
$config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);
|
||||
$config = ORMSetup::createAttributeMetadataConfig($paths, $isDevMode);
|
||||
// on PHP < 8.4, use ORMSetup::createAttributeMetadataConfiguration() instead
|
||||
$connection = DriverManager::getConnection($dbParams, $config);
|
||||
$entityManager = new EntityManager($connection, $config);
|
||||
|
||||
@@ -66,7 +67,8 @@ Or if you prefer XML:
|
||||
|
||||
<?php
|
||||
$paths = ['/path/to/xml-mappings'];
|
||||
$config = ORMSetup::createXMLMetadataConfiguration($paths, $isDevMode);
|
||||
$config = ORMSetup::createXMLMetadataConfig($paths, $isDevMode);
|
||||
// on PHP < 8.4, use ORMSetup::createXMLMetadataConfiguration() instead
|
||||
$connection = DriverManager::getConnection($dbParams, $config);
|
||||
$entityManager = new EntityManager($connection, $config);
|
||||
|
||||
|
||||
@@ -209,6 +209,7 @@ Field & Association Getters
|
||||
|
||||
- ``isUniqueField($fieldName)``
|
||||
- ``isNullable($fieldName)``
|
||||
- ``isIndexed($fieldName)``
|
||||
- ``getColumnName($fieldName)``
|
||||
- ``getFieldMapping($fieldName)``
|
||||
- ``getAssociationMapping($fieldName)``
|
||||
|
||||
@@ -84,7 +84,7 @@ The following Commands are currently available:
|
||||
- ``orm:clear-cache:result`` Clear result cache of the various
|
||||
cache drivers.
|
||||
- ``orm:generate-proxies`` Generates proxy classes for entity
|
||||
classes.
|
||||
classes. Deprecated in favor of using native lazy objects.
|
||||
- ``orm:run-dql`` Executes arbitrary DQL directly from the command
|
||||
line.
|
||||
- ``orm:schema-tool:create`` Processes the schema and either
|
||||
|
||||
@@ -112,7 +112,6 @@ of several common elements:
|
||||
|
||||
<indexes>
|
||||
<index name="name_idx" columns="name"/>
|
||||
<index columns="user_email"/>
|
||||
</indexes>
|
||||
|
||||
<unique-constraints>
|
||||
@@ -131,7 +130,7 @@ of several common elements:
|
||||
</id>
|
||||
|
||||
<field name="name" column="name" type="string" length="50" nullable="true" unique="true" />
|
||||
<field name="email" column="user_email" type="string" column-definition="CHAR(32) NOT NULL" />
|
||||
<field name="email" column="user_email" type="string" index="true" column-definition="CHAR(32) NOT NULL" />
|
||||
|
||||
<one-to-one field="address" target-entity="Address" inversed-by="user">
|
||||
<cascade><cascade-remove /></cascade>
|
||||
@@ -255,6 +254,8 @@ Optional attributes:
|
||||
only.
|
||||
- unique - Should this field contain a unique value across the
|
||||
table? Defaults to false.
|
||||
- index - Should an index be created for this column? Defaults to
|
||||
false.
|
||||
- nullable - Should this field allow NULL as a value? Defaults to
|
||||
false.
|
||||
- insertable - Should this field be inserted? Defaults to true.
|
||||
|
||||
@@ -138,12 +138,12 @@ step:
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
// Create a simple "default" Doctrine ORM configuration for Attributes
|
||||
$config = ORMSetup::createAttributeMetadataConfiguration(
|
||||
$config = ORMSetup::createAttributeMetadataConfig( // on PHP < 8.4, use ORMSetup::createAttributeMetadataConfiguration()
|
||||
paths: [__DIR__ . '/src'],
|
||||
isDevMode: true,
|
||||
);
|
||||
// or if you prefer XML
|
||||
// $config = ORMSetup::createXMLMetadataConfiguration(
|
||||
// $config = ORMSetup::createXMLMetadataConfig( // on PHP < 8.4, use ORMSetup::createXMLMetadataConfiguration()
|
||||
// paths: [__DIR__ . '/config/xml'],
|
||||
// isDevMode: true,
|
||||
//);
|
||||
|
||||
@@ -243,6 +243,7 @@
|
||||
<xs:attribute name="length" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="unique" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="nullable" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="index" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="insertable" type="xs:boolean" default="true" />
|
||||
<xs:attribute name="updatable" type="xs:boolean" default="true" />
|
||||
<xs:attribute name="generated" type="orm:generated-type" default="NEVER" />
|
||||
|
||||
@@ -1333,51 +1333,45 @@ parameters:
|
||||
path: src/Mapping/Driver/AttributeDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencedColumnNames\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencedColumn…'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencedTableName\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencedTableN…'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencingColumnNames\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencingColum…'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\Index\:\:getIndexedColumns\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\Index'' and ''getIndexedColumns'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\Table\:\:getPrimaryKeyConstraint\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\Table'' and ''getPrimaryKeyConstr…'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to method getColumnName\(\) on an unknown class Doctrine\\DBAL\\Schema\\Index\\IndexedColumn\.$#'
|
||||
identifier: class.notFound
|
||||
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Schema\\Index and ''getType'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to method getColumnNames\(\) on an unknown class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to method toString\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\.$#'
|
||||
identifier: class.notFound
|
||||
count: 5
|
||||
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Schema\\Table and ''getPrimaryKeyConstr…'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 2
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
@@ -1386,18 +1380,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Class Doctrine\\DBAL\\Schema\\Index\\IndexType not found\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint not found\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Instanceof between Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\> and Doctrine\\ORM\\Mapping\\ClassMetadata will always evaluate to true\.$#'
|
||||
identifier: instanceof.alwaysTrue
|
||||
@@ -1434,30 +1416,12 @@ parameters:
|
||||
count: 2
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$array of function sort contains unresolvable type\.$#'
|
||||
identifier: argument.unresolvableType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$columnName of method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getFieldNameForColumn\(\) expects string, string\|false given\.$#'
|
||||
identifier: argument.type
|
||||
count: 4
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \$indexedColumn of anonymous function has invalid type Doctrine\\DBAL\\Schema\\Index\\IndexedColumn\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \$name of anonymous function has invalid type Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\.$#'
|
||||
identifier: class.notFound
|
||||
count: 5
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\SimplifiedXmlDriver\:\:__construct\(\) has parameter \$fileExtension with no type specified\.$#'
|
||||
identifier: missingType.parameter
|
||||
@@ -2022,6 +1986,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/Persisters/Collection/OneToManyPersister.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$columns of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:getColumnDeclarationListSQL\(\) expects list\<array\{name\: string, type\: Doctrine\\DBAL\\Types\\Type, default\: mixed, notnull\?\: bool, autoincrement\: bool, columnDefinition\: non\-empty\-string\|null, comment\: string, charset\?\: non\-empty\-string\|null, \.\.\.\}\>, array\<string, array\{name\: string, notnull\: true, type\: Doctrine\\DBAL\\Types\\Type\}\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Persisters/Collection/OneToManyPersister.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\AbstractEntityInheritancePersister\:\:getSelectColumnSQL\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
@@ -2610,6 +2580,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/Query/Exec/MultiTableDeleteExecutor.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$columns of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:getColumnDeclarationListSQL\(\) expects list\<array\{name\: string, type\: Doctrine\\DBAL\\Types\\Type, default\: mixed, notnull\?\: bool, autoincrement\: bool, columnDefinition\: non\-empty\-string\|null, comment\: string, charset\?\: non\-empty\-string\|null, \.\.\.\}\>, array\<string, array\{name\: string, notnull\: true, type\: Doctrine\\DBAL\\Types\\Type\}\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Query/Exec/MultiTableDeleteExecutor.php
|
||||
|
||||
-
|
||||
message: '#^Argument of an invalid type list\<string\>\|string supplied for foreach, only iterables are supported\.$#'
|
||||
identifier: foreach.nonIterable
|
||||
@@ -2622,6 +2598,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/Query/Exec/MultiTableUpdateExecutor.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$columns of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:getColumnDeclarationListSQL\(\) expects list\<array\{name\: string, type\: Doctrine\\DBAL\\Types\\Type, default\: mixed, notnull\?\: bool, autoincrement\: bool, columnDefinition\: non\-empty\-string\|null, comment\: string, charset\?\: non\-empty\-string\|null, \.\.\.\}\>, array\<string, array\{name\: string, notnull\: true, type\: Doctrine\\DBAL\\Types\\Type\}\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Query/Exec/MultiTableUpdateExecutor.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#3 \$types of method Doctrine\\DBAL\\Connection\:\:executeStatement\(\) expects array\<int\<0, max\>\|string, Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|Doctrine\\DBAL\\Types\\Type\|string\>, list\<Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|Doctrine\\DBAL\\Types\\Type\|int\|string\> given\.$#'
|
||||
identifier: argument.type
|
||||
@@ -3121,32 +3103,71 @@ parameters:
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencedColumnNames\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '''
|
||||
#^Call to deprecated method getColumns\(\) of class Doctrine\\DBAL\\Schema\\Index\:
|
||||
Use \{@see getIndexedColumns\(\)\} instead\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencedTableName\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '''
|
||||
#^Call to deprecated method getForeignColumns\(\) of class Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:
|
||||
Use \{@see getReferencedColumnNames\(\)\} instead\.
|
||||
|
||||
Returns the names of the referenced table columns
|
||||
the foreign key constraint is associated with\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencingColumnNames\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '''
|
||||
#^Call to deprecated method getForeignTableName\(\) of class Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:
|
||||
Use \{@see getReferencedTableName\(\)\} instead\.
|
||||
|
||||
Returns the name of the referenced table
|
||||
the foreign key constraint is associated with\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\Index\:\:getIndexedColumns\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '''
|
||||
#^Call to deprecated method getLocalColumns\(\) of class Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:
|
||||
Use \{@see getReferencingColumnNames\(\)\} instead\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\Table\:\:dropForeignKey\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '''
|
||||
#^Call to deprecated method getPrimaryKey\(\) of class Doctrine\\DBAL\\Schema\\Table\:
|
||||
Use \{@see getPrimaryKeyConstraint\(\)\} instead\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '''
|
||||
#^Call to deprecated method removeForeignKey\(\) of class Doctrine\\DBAL\\Schema\\Table\:
|
||||
Use \{@link dropForeignKey\(\)\} instead\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '''
|
||||
#^Call to deprecated method setPrimaryKey\(\) of class Doctrine\\DBAL\\Schema\\Table\:
|
||||
Use \{@see addPrimaryKeyConstraint\(\)\} instead\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
@@ -3157,32 +3178,14 @@ parameters:
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to method getColumnName\(\) on an unknown class Doctrine\\DBAL\\Schema\\Index\\IndexedColumn\.$#'
|
||||
identifier: class.notFound
|
||||
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\Index'' and ''getIndexedColumns'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to method getColumnNames\(\) on an unknown class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to method toString\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\.$#'
|
||||
identifier: class.notFound
|
||||
count: 3
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint not found\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\DBAL\\Schema\\AbstractSchemaManager\<Doctrine\\DBAL\\Platforms\\AbstractPlatform\>\:\:createComparator\(\) invoked with 1 parameter, 0 required\.$#'
|
||||
identifier: arguments.count
|
||||
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Schema\\Table and ''getPrimaryKeyConstr…'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
@@ -3289,15 +3292,51 @@ parameters:
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \$indexedColumn of anonymous function has invalid type Doctrine\\DBAL\\Schema\\Index\\IndexedColumn\.$#'
|
||||
identifier: class.notFound
|
||||
message: '#^Parameter \#1 \$columnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addIndex\(\) expects non\-empty\-list\<string\>, list\<string\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \$name of anonymous function has invalid type Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\.$#'
|
||||
identifier: class.notFound
|
||||
count: 3
|
||||
message: '#^Parameter \#1 \$columnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addUniqueIndex\(\) expects non\-empty\-list\<string\>, array\<string\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$columnNames of method Doctrine\\DBAL\\Schema\\Table\:\:setPrimaryKey\(\) expects non\-empty\-list\<string\>, array\<string\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$value of static method Doctrine\\DBAL\\Schema\\Name\\Identifier\:\:unquoted\(\) expects non\-empty\-string, string given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$columnNames of class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint constructor expects non\-empty\-list\<Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\>, list\<Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$columns of class Doctrine\\DBAL\\Schema\\Index constructor expects non\-empty\-list\<string\>, list\<string\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$localColumnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addForeignKeyConstraint\(\) expects non\-empty\-list\<string\>, list\<string\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#3 \$foreignColumnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addForeignKeyConstraint\(\) expects non\-empty\-list\<string\>, list\<string\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
@@ -3450,12 +3489,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @phpstan\-assert\-if\-true for \$obj contains generic interface Doctrine\\ORM\\Proxy\\InternalProxy but does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$assoc of method Doctrine\\ORM\\PersistentCollection\<\(int\|string\),mixed\>\:\:setOwner\(\) expects Doctrine\\ORM\\Mapping\\AssociationMapping&Doctrine\\ORM\\Mapping\\ToManyAssociationMapping, Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping given\.$#'
|
||||
identifier: argument.type
|
||||
@@ -3498,6 +3531,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^Unable to resolve the template type T in call to method static method Symfony\\Component\\VarExporter\\Hydrator\:\:hydrate\(\)$#'
|
||||
identifier: argument.templateType
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$name\.$#'
|
||||
identifier: property.notFound
|
||||
|
||||
@@ -34,8 +34,58 @@ parameters:
|
||||
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table::addPrimaryKeyConstraint\(\)\.$~'
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint::get.*\.$~'
|
||||
identifier: method.notFound
|
||||
|
||||
-
|
||||
message: '~createComparator~'
|
||||
identifier: arguments.count
|
||||
|
||||
-
|
||||
message: '~UnqualifiedName~'
|
||||
identifier: class.notFound
|
||||
|
||||
-
|
||||
message: '~IndexedColumn~'
|
||||
identifier: class.notFound
|
||||
|
||||
-
|
||||
message: '~PrimaryKeyConstraint~'
|
||||
identifier: class.notFound
|
||||
|
||||
-
|
||||
message: '~IndexType~'
|
||||
identifier: class.notFound
|
||||
|
||||
-
|
||||
message: '~dropForeignKey~'
|
||||
identifier: method.notFound
|
||||
|
||||
-
|
||||
message: '~getIndexedColumns~'
|
||||
identifier: method.notFound
|
||||
|
||||
-
|
||||
message: '~getPrimaryKeyConstraint~'
|
||||
identifier: method.notFound
|
||||
|
||||
-
|
||||
message: '~PrimaryKeyConstraint~'
|
||||
identifier: class.notFound
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '~^Call to method toString.*UnqualifiedName\.$~'
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
- '~^Class Doctrine\\DBAL\\Platforms\\SQLitePlatform not found\.$~'
|
||||
|
||||
-
|
||||
message: '~sort~'
|
||||
identifier: argument.unresolvableType
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
# To be removed in 4.0
|
||||
-
|
||||
message: '#Negated boolean expression is always false\.#'
|
||||
|
||||
12
phpstan.neon
12
phpstan.neon
@@ -20,18 +20,6 @@ parameters:
|
||||
message: '~^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '~^Call to static method unquoted\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\Identifier\.$~'
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '~^Instantiated class Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName not found\.$~'
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table::addPrimaryKeyConstraint\(\)\.$~'
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
# Compatibility with DBAL 3
|
||||
# See https://github.com/doctrine/dbal/pull/3480
|
||||
-
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Cache\CacheConfiguration;
|
||||
use Doctrine\ORM\Exception\InvalidEntityRepository;
|
||||
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
|
||||
@@ -63,6 +64,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function setProxyDir(string $dir): void
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
$this->attributes['proxyDir'] = $dir;
|
||||
}
|
||||
|
||||
@@ -71,6 +81,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function getProxyDir(): string|null
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
return $this->attributes['proxyDir'] ?? null;
|
||||
}
|
||||
|
||||
@@ -81,6 +100,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function getAutoGenerateProxyClasses(): int
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
return $this->attributes['autoGenerateProxyClasses'] ?? ProxyFactory::AUTOGENERATE_ALWAYS;
|
||||
}
|
||||
|
||||
@@ -91,6 +119,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function setAutoGenerateProxyClasses(bool|int $autoGenerate): void
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
$this->attributes['autoGenerateProxyClasses'] = (int) $autoGenerate;
|
||||
}
|
||||
|
||||
@@ -99,6 +136,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function getProxyNamespace(): string|null
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
return $this->attributes['proxyNamespace'] ?? null;
|
||||
}
|
||||
|
||||
@@ -107,6 +153,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function setProxyNamespace(string $ns): void
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
$this->attributes['proxyNamespace'] = $ns;
|
||||
}
|
||||
|
||||
@@ -602,7 +657,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
public function enableNativeLazyObjects(bool $nativeLazyObjects): void
|
||||
{
|
||||
if (PHP_VERSION_ID < 80400) {
|
||||
if (PHP_VERSION_ID >= 80400 && ! $nativeLazyObjects) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Disabling native lazy objects is deprecated and will be impossible in Doctrine ORM 4.0.',
|
||||
);
|
||||
}
|
||||
|
||||
if (PHP_VERSION_ID < 80400 && $nativeLazyObjects) {
|
||||
throw new LogicException('Lazy loading proxies require PHP 8.4 or higher.');
|
||||
}
|
||||
|
||||
@@ -610,7 +673,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* To be deprecated in 3.1.0
|
||||
* @deprecated lazy ghost objects are always enabled
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
@@ -619,7 +682,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
return true;
|
||||
}
|
||||
|
||||
/** To be deprecated in 3.1.0 */
|
||||
/** @deprecated lazy ghost objects cannot be disabled */
|
||||
public function setLazyGhostObjectEnabled(bool $flag): void
|
||||
{
|
||||
if (! $flag) {
|
||||
@@ -630,7 +693,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
}
|
||||
|
||||
/** To be deprecated in 3.1.0 */
|
||||
/** @deprecated rejecting ID collisions in the identity map cannot be disabled */
|
||||
public function setRejectIdCollisionInIdentityMap(bool $flag): void
|
||||
{
|
||||
if (! $flag) {
|
||||
@@ -642,7 +705,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* To be deprecated in 3.1.0
|
||||
* @deprecated rejecting ID collisions in the identity map is always enabled
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
|
||||
@@ -43,7 +43,7 @@ use function method_exists;
|
||||
*
|
||||
* $paths = ['/path/to/entity/mapping/files'];
|
||||
*
|
||||
* $config = ORMSetup::createAttributeMetadataConfiguration($paths);
|
||||
* $config = ORMSetup::createAttributeMetadataConfig($paths);
|
||||
* $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true], $config);
|
||||
* $entityManager = new EntityManager($connection, $config);
|
||||
*
|
||||
@@ -134,12 +134,16 @@ class EntityManager implements EntityManagerInterface
|
||||
|
||||
$this->repositoryFactory = $config->getRepositoryFactory();
|
||||
$this->unitOfWork = new UnitOfWork($this);
|
||||
$this->proxyFactory = new ProxyFactory(
|
||||
$this,
|
||||
$config->getProxyDir(),
|
||||
$config->getProxyNamespace(),
|
||||
$config->getAutoGenerateProxyClasses(),
|
||||
);
|
||||
if ($config->isNativeLazyObjectsEnabled()) {
|
||||
$this->proxyFactory = new ProxyFactory($this);
|
||||
} else {
|
||||
$this->proxyFactory = new ProxyFactory(
|
||||
$this,
|
||||
$config->getProxyDir(),
|
||||
$config->getProxyNamespace(),
|
||||
$config->getAutoGenerateProxyClasses(),
|
||||
);
|
||||
}
|
||||
|
||||
if ($config->isSecondLevelCacheEnabled()) {
|
||||
$cacheConfig = $config->getSecondLevelCacheConfiguration();
|
||||
|
||||
79
src/Internal/UnitOfWork/InsertBatch.php
Normal file
79
src/Internal/UnitOfWork/InsertBatch.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal\UnitOfWork;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Id\AssignedGenerator;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* An {@see InsertBatch} represents a set of entities that are safe to be batched
|
||||
* together in a single query.
|
||||
*
|
||||
* These entities are only those that have all fields already assigned, including the
|
||||
* identifier field(s).
|
||||
*
|
||||
* This data structure only exists for internal {@see UnitOfWork} optimisations, and
|
||||
* should not be relied upon outside the ORM.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @template TEntity of object
|
||||
*/
|
||||
final class InsertBatch
|
||||
{
|
||||
/**
|
||||
* @param ClassMetadata<TEntity> $class
|
||||
* @param non-empty-list<TEntity> $entities
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly ClassMetadata $class,
|
||||
public array $entities,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Code in here is procedural/ugly due to it being in a hot path of the {@see UnitOfWork}
|
||||
*
|
||||
* This method will batch the given entity set by type, preserving their order. For example,
|
||||
* given an input [A1, A2, A3, B1, B2, A4, A5], it will create an [[A1, A2, A3], [B1, B2], [A4, A5]] batch.
|
||||
*
|
||||
* Entities for which the identifier needs to be generated or fetched by a sequence are put as single
|
||||
* items in a batch of their own, since it is unsafe to batch-insert them.
|
||||
*
|
||||
* @param list<TEntities> $entities
|
||||
*
|
||||
* @return list<self<TEntities>>
|
||||
*
|
||||
* @template TEntities of object
|
||||
*/
|
||||
public static function batchByEntityType(
|
||||
EntityManagerInterface $entityManager,
|
||||
array $entities,
|
||||
): array {
|
||||
$currentClass = null;
|
||||
$batches = [];
|
||||
$batchIndex = -1;
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$entityClass = $entityManager->getClassMetadata($entity::class);
|
||||
|
||||
if (
|
||||
$currentClass?->name !== $entityClass->name
|
||||
|| ! $entityClass->idGenerator instanceof AssignedGenerator
|
||||
) {
|
||||
$currentClass = $entityClass;
|
||||
$batches[] = new InsertBatch($entityClass, [$entity]);
|
||||
$batchIndex += 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$batches[$batchIndex]->entities[] = $entity;
|
||||
}
|
||||
|
||||
return $batches;
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,18 @@ class FieldBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets indexed.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function index(bool $flag = true): static
|
||||
{
|
||||
$this->mapping['index'] = $flag;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets column name.
|
||||
*
|
||||
|
||||
@@ -400,13 +400,9 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
public DiscriminatorColumnMapping|null $discriminatorColumn = null;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The primary table definition. The definition is an array with the
|
||||
* following entries:
|
||||
* READ-ONLY: The primary table definition.
|
||||
*
|
||||
* name => <tableName>
|
||||
* schema => <schemaName>
|
||||
* indexes => array
|
||||
* uniqueConstraints => array
|
||||
* "quoted" indicates whether the table name is quoted (with backticks) or not
|
||||
*
|
||||
* @var mixed[]
|
||||
* @phpstan-var array{
|
||||
@@ -544,6 +540,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
/**
|
||||
* The ReflectionProperty instances of the mapped class.
|
||||
*
|
||||
* @deprecated Use $propertyAccessors instead.
|
||||
*
|
||||
* @var LegacyReflectionFields|array<string, ReflectionProperty>
|
||||
*/
|
||||
public LegacyReflectionFields|array $reflFields = [];
|
||||
@@ -573,6 +571,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
/**
|
||||
* Gets the ReflectionProperties of the mapped class.
|
||||
*
|
||||
* @deprecated Use getPropertyAccessors() instead.
|
||||
*
|
||||
* @return LegacyReflectionFields|ReflectionProperty[] An array of ReflectionProperty instances.
|
||||
* @phpstan-return LegacyReflectionFields|array<string, ReflectionProperty>
|
||||
*/
|
||||
@@ -593,6 +593,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
|
||||
/**
|
||||
* Gets a ReflectionProperty for a specific field of the mapped class.
|
||||
*
|
||||
* @deprecated Use getPropertyAccessor() instead.
|
||||
*/
|
||||
public function getReflectionProperty(string $name): ReflectionProperty|null
|
||||
{
|
||||
@@ -604,7 +606,11 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
return $this->propertyAccessors[$name] ?? null;
|
||||
}
|
||||
|
||||
/** @throws BadMethodCallException If the class has a composite identifier. */
|
||||
/**
|
||||
* @deprecated Use getPropertyAccessor() instead.
|
||||
*
|
||||
* @throws BadMethodCallException If the class has a composite identifier.
|
||||
*/
|
||||
public function getSingleIdReflectionProperty(): ReflectionProperty|null
|
||||
{
|
||||
if ($this->isIdentifierComposite) {
|
||||
@@ -818,7 +824,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
public function wakeupReflection(ReflectionService $reflService): void
|
||||
{
|
||||
// Restore ReflectionClass and properties
|
||||
$this->reflClass = $reflService->getClass($this->name);
|
||||
$this->reflClass = $reflService->getClass($this->name);
|
||||
/** @phpstan-ignore property.deprecated */
|
||||
$this->reflFields = new LegacyReflectionFields($this, $reflService);
|
||||
$this->instantiator = $this->instantiator ?: new Instantiator();
|
||||
|
||||
@@ -1056,6 +1063,13 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
return $mapping !== false && isset($mapping->nullable) && $mapping->nullable;
|
||||
}
|
||||
|
||||
public function isIndexed(string $fieldName): bool
|
||||
{
|
||||
$mapping = $this->getFieldMapping($fieldName);
|
||||
|
||||
return isset($mapping->index) && $mapping->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a column name for a field name.
|
||||
* If the column name for the field cannot be found, the given field name
|
||||
|
||||
@@ -66,7 +66,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
|
||||
public function setEntityManager(EntityManagerInterface $em): void
|
||||
{
|
||||
parent::setProxyClassNameResolver(new DefaultProxyClassNameResolver());
|
||||
if (! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
parent::setProxyClassNameResolver(new DefaultProxyClassNameResolver());
|
||||
}
|
||||
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ final class Column implements MappingAttribute
|
||||
public readonly array $options = [],
|
||||
public readonly string|null $columnDefinition = null,
|
||||
public readonly string|null $generated = null,
|
||||
public readonly bool $index = false,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,11 @@ use Doctrine\ORM\Internal\SQLResultCasing;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function assert;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function is_numeric;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
@@ -38,7 +41,13 @@ class DefaultQuoteStrategy implements QuoteStrategy
|
||||
$tableName = $class->table['name'];
|
||||
|
||||
if (! empty($class->table['schema'])) {
|
||||
$tableName = $class->table['schema'] . '.' . $class->table['name'];
|
||||
return isset($class->table['quoted'])
|
||||
? sprintf(
|
||||
'%s.%s',
|
||||
$platform->quoteSingleIdentifier($class->table['schema']),
|
||||
$platform->quoteSingleIdentifier($tableName),
|
||||
)
|
||||
: $class->table['schema'] . '.' . $class->table['name'];
|
||||
}
|
||||
|
||||
return isset($class->table['quoted'])
|
||||
@@ -52,7 +61,10 @@ class DefaultQuoteStrategy implements QuoteStrategy
|
||||
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string
|
||||
{
|
||||
return isset($definition['quoted'])
|
||||
? $platform->quoteSingleIdentifier($definition['sequenceName'])
|
||||
? implode('.', array_map(
|
||||
static fn (string $part) => $platform->quoteSingleIdentifier($part),
|
||||
explode('.', $definition['sequenceName']),
|
||||
))
|
||||
: $definition['sequenceName'];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use BackedEnum;
|
||||
use BcMath\Number;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
@@ -40,7 +41,12 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
|
||||
/** @param array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */
|
||||
public function __construct(array $typedFieldMappings = [])
|
||||
{
|
||||
$this->typedFieldMappings = array_merge(self::DEFAULT_TYPED_FIELD_MAPPINGS, $typedFieldMappings);
|
||||
$defaultMappings = self::DEFAULT_TYPED_FIELD_MAPPINGS;
|
||||
if (defined(Types::class . '::NUMBER')) { // DBAL 4.3+
|
||||
$defaultMappings[Number::class] = Types::NUMBER;
|
||||
}
|
||||
|
||||
$this->typedFieldMappings = array_merge($defaultMappings, $typedFieldMappings);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -710,6 +710,7 @@ class AttributeDriver implements MappingDriver
|
||||
* length: int,
|
||||
* unique: bool,
|
||||
* nullable: bool,
|
||||
* index: bool,
|
||||
* precision: int,
|
||||
* enumType?: class-string,
|
||||
* options?: mixed[],
|
||||
@@ -726,6 +727,7 @@ class AttributeDriver implements MappingDriver
|
||||
'length' => $column->length,
|
||||
'unique' => $column->unique,
|
||||
'nullable' => $column->nullable,
|
||||
'index' => $column->index,
|
||||
'precision' => $column->precision,
|
||||
];
|
||||
|
||||
|
||||
@@ -754,6 +754,7 @@ class XmlDriver extends FileDriver
|
||||
* scale?: int,
|
||||
* unique?: bool,
|
||||
* nullable?: bool,
|
||||
* index?: bool,
|
||||
* notInsertable?: bool,
|
||||
* notUpdatable?: bool,
|
||||
* enumType?: string,
|
||||
@@ -792,6 +793,10 @@ class XmlDriver extends FileDriver
|
||||
$mapping['unique'] = $this->evaluateBoolean($fieldMapping['unique']);
|
||||
}
|
||||
|
||||
if (isset($fieldMapping['index'])) {
|
||||
$mapping['index'] = $this->evaluateBoolean($fieldMapping['index']);
|
||||
}
|
||||
|
||||
if (isset($fieldMapping['nullable'])) {
|
||||
$mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ final class FieldMapping implements ArrayAccess
|
||||
public int|null $scale = null;
|
||||
/** Whether a unique constraint should be generated for the column. */
|
||||
public bool|null $unique = null;
|
||||
/** Whether an index should be generated for the column. */
|
||||
public bool|null $index = null;
|
||||
/**
|
||||
* @var class-string|null This is set when the field is inherited by this
|
||||
* class from another (inheritance) parent <em>entity</em> class. The value
|
||||
@@ -93,6 +95,7 @@ final class FieldMapping implements ArrayAccess
|
||||
* length?: int|null,
|
||||
* id?: bool|null,
|
||||
* nullable?: bool|null,
|
||||
* index?: bool|null,
|
||||
* notInsertable?: bool|null,
|
||||
* notUpdatable?: bool|null,
|
||||
* columnDefinition?: string|null,
|
||||
|
||||
@@ -12,7 +12,7 @@ trait JoinColumnProperties
|
||||
public readonly string|null $referencedColumnName = null,
|
||||
public readonly bool $deferrable = false,
|
||||
public readonly bool $unique = false,
|
||||
public readonly bool $nullable = true,
|
||||
public readonly bool|null $nullable = null,
|
||||
public readonly mixed $onDelete = null,
|
||||
public readonly string|null $columnDefinition = null,
|
||||
public readonly string|null $fieldName = null,
|
||||
|
||||
@@ -127,6 +127,8 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
|
||||
$mapping->joinTableColumns = [];
|
||||
|
||||
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
|
||||
$joinColumn->nullable = false;
|
||||
|
||||
if (empty($joinColumn->referencedColumnName)) {
|
||||
$joinColumn->referencedColumnName = $namingStrategy->referenceColumnName();
|
||||
}
|
||||
@@ -150,6 +152,8 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
|
||||
}
|
||||
|
||||
foreach ($mapping->joinTable->inverseJoinColumns as $inverseJoinColumn) {
|
||||
$inverseJoinColumn->nullable = false;
|
||||
|
||||
if (empty($inverseJoinColumn->referencedColumnName)) {
|
||||
$inverseJoinColumn->referencedColumnName = $namingStrategy->referenceColumnName();
|
||||
}
|
||||
|
||||
@@ -130,6 +130,12 @@ abstract class ToOneOwningSideMapping extends OwningSideMapping implements ToOne
|
||||
$uniqueConstraintColumns = [];
|
||||
|
||||
foreach ($mapping->joinColumns as $joinColumn) {
|
||||
if ($mapping->id) {
|
||||
$joinColumn->nullable = false;
|
||||
} elseif ($joinColumn->nullable === null) {
|
||||
$joinColumn->nullable = true;
|
||||
}
|
||||
|
||||
if ($mapping->isOneToOne() && ! $isInheritanceTypeSingleTable) {
|
||||
if (count($mapping->joinColumns) === 1) {
|
||||
if (empty($mapping->id)) {
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
|
||||
use Doctrine\ORM\Mapping\Driver\XmlDriver;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
@@ -20,6 +21,8 @@ use function extension_loaded;
|
||||
use function md5;
|
||||
use function sys_get_temp_dir;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
final class ORMSetup
|
||||
{
|
||||
/**
|
||||
@@ -33,12 +36,39 @@ final class ORMSetup
|
||||
string|null $proxyDir = null,
|
||||
CacheItemPoolInterface|null $cache = null,
|
||||
): Configuration {
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'%s is deprecated in favor of %s, and will be removed in 4.0.',
|
||||
__METHOD__,
|
||||
self::class . '::createAttributeMetadataConfig()',
|
||||
);
|
||||
}
|
||||
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl(new AttributeDriver($paths));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration with an attribute metadata driver.
|
||||
*
|
||||
* @param string[] $paths
|
||||
*/
|
||||
public static function createAttributeMetadataConfig(
|
||||
array $paths,
|
||||
bool $isDevMode = false,
|
||||
string|null $cacheNamespaceSeed = null,
|
||||
CacheItemPoolInterface|null $cache = null,
|
||||
): Configuration {
|
||||
$config = self::createConfig($isDevMode, $cacheNamespaceSeed, $cache);
|
||||
$config->setMetadataDriverImpl(new AttributeDriver($paths));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration with an XML metadata driver.
|
||||
*
|
||||
@@ -51,12 +81,44 @@ final class ORMSetup
|
||||
CacheItemPoolInterface|null $cache = null,
|
||||
bool $isXsdValidationEnabled = true,
|
||||
): Configuration {
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'%s is deprecated in favor of %s, and will be removed in 4.0.',
|
||||
__METHOD__,
|
||||
self::class . '::createXMLMetadataConfig()',
|
||||
);
|
||||
}
|
||||
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl(new XmlDriver($paths, XmlDriver::DEFAULT_FILE_EXTENSION, $isXsdValidationEnabled));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration with an XML metadata driver.
|
||||
*
|
||||
* @param string[] $paths
|
||||
*/
|
||||
public static function createXMLMetadataConfig(
|
||||
array $paths,
|
||||
bool $isDevMode = false,
|
||||
string|null $cacheNamespaceSeed = null,
|
||||
CacheItemPoolInterface|null $cache = null,
|
||||
bool $isXsdValidationEnabled = true,
|
||||
): Configuration {
|
||||
$config = self::createConfig($isDevMode, $cacheNamespaceSeed, $cache);
|
||||
$config->setMetadataDriverImpl(new XmlDriver(
|
||||
$paths,
|
||||
XmlDriver::DEFAULT_FILE_EXTENSION,
|
||||
$isXsdValidationEnabled,
|
||||
));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration without a metadata driver.
|
||||
*/
|
||||
@@ -65,6 +127,16 @@ final class ORMSetup
|
||||
string|null $proxyDir = null,
|
||||
CacheItemPoolInterface|null $cache = null,
|
||||
): Configuration {
|
||||
if (PHP_VERSION_ID >= 80400 && $proxyDir !== null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'%s is deprecated in favor of %s, and will be removed in 4.0.',
|
||||
__METHOD__,
|
||||
self::class . '::createConfig()',
|
||||
);
|
||||
}
|
||||
|
||||
$proxyDir = $proxyDir ?: sys_get_temp_dir();
|
||||
|
||||
$cache = self::createCacheInstance($isDevMode, $proxyDir, $cache);
|
||||
@@ -81,9 +153,23 @@ final class ORMSetup
|
||||
return $config;
|
||||
}
|
||||
|
||||
public static function createConfig(
|
||||
bool $isDevMode = false,
|
||||
string|null $cacheNamespaceSeed = null,
|
||||
CacheItemPoolInterface|null $cache = null,
|
||||
): Configuration {
|
||||
$cache = self::createCacheInstance($isDevMode, $cacheNamespaceSeed, $cache);
|
||||
$config = new Configuration();
|
||||
$config->setMetadataCache($cache);
|
||||
$config->setQueryCache($cache);
|
||||
$config->setResultCache($cache);
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private static function createCacheInstance(
|
||||
bool $isDevMode,
|
||||
string $proxyDir,
|
||||
string|null $cacheNamespaceSeed,
|
||||
CacheItemPoolInterface|null $cache,
|
||||
): CacheItemPoolInterface {
|
||||
if ($cache !== null) {
|
||||
@@ -101,7 +187,7 @@ final class ORMSetup
|
||||
return new ArrayAdapter();
|
||||
}
|
||||
|
||||
$namespace = 'dc2_' . md5($proxyDir);
|
||||
$namespace = 'dc2_' . md5($cacheNamespaceSeed ?? 'default');
|
||||
|
||||
if (extension_loaded('apcu') && apcu_enabled()) {
|
||||
return new ApcuAdapter($namespace);
|
||||
|
||||
@@ -153,12 +153,6 @@ class BasicEntityPersister implements EntityPersister
|
||||
*/
|
||||
protected array $quotedColumns = [];
|
||||
|
||||
/**
|
||||
* The INSERT SQL statement used for entities handled by this persister.
|
||||
* This SQL is only generated once per request, if at all.
|
||||
*/
|
||||
private string|null $insertSql = null;
|
||||
|
||||
/**
|
||||
* The quote strategy.
|
||||
*/
|
||||
@@ -273,8 +267,8 @@ class BasicEntityPersister implements EntityPersister
|
||||
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
|
||||
}
|
||||
|
||||
// Unset this queued insert, so that the prepareUpdateData() method knows right away
|
||||
// (for the next entity already) that the current entity has been written to the database
|
||||
// Unset this queued insert, so that the prepareUpdateData() method (called via prepareInsertData() method)
|
||||
// knows right away (for the next entity already) that the current entity has been written to the database
|
||||
// and no extra updates need to be scheduled to refer to it.
|
||||
//
|
||||
// In \Doctrine\ORM\UnitOfWork::executeInserts(), the UoW already removed entities
|
||||
@@ -693,11 +687,11 @@ class BasicEntityPersister implements EntityPersister
|
||||
$targetColumn = $joinColumn->referencedColumnName;
|
||||
$quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
|
||||
|
||||
$this->quotedColumns[$sourceColumn] = $quotedColumn;
|
||||
$this->columnTypes[$sourceColumn] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em);
|
||||
$result[$owningTable][$sourceColumn] = $newValId
|
||||
? $newValId[$targetClass->getFieldForColumn($targetColumn)]
|
||||
: null;
|
||||
$this->quotedColumns[$sourceColumn] = $quotedColumn;
|
||||
$this->columnTypes[$sourceColumn] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em);
|
||||
|
||||
$newValue = $newValId ? $newValId[$targetClass->getFieldForColumn($targetColumn)] : null;
|
||||
$result[$owningTable][$sourceColumn] = $newValue instanceof BackedEnum ? $newValue->value : $newValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1418,22 +1412,17 @@ class BasicEntityPersister implements EntityPersister
|
||||
|
||||
public function getInsertSQL(): string
|
||||
{
|
||||
if ($this->insertSql !== null) {
|
||||
return $this->insertSql;
|
||||
}
|
||||
|
||||
$columns = $this->getInsertColumnList();
|
||||
$tableName = $this->quoteStrategy->getTableName($this->class, $this->platform);
|
||||
|
||||
if (empty($columns)) {
|
||||
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
|
||||
$this->insertSql = $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
|
||||
if ($columns === []) {
|
||||
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
|
||||
|
||||
return $this->insertSql;
|
||||
return $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
|
||||
}
|
||||
|
||||
$values = [];
|
||||
$columns = array_unique($columns);
|
||||
$placeholders = [];
|
||||
$columns = array_unique($columns);
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$placeholder = '?';
|
||||
@@ -1447,15 +1436,13 @@ class BasicEntityPersister implements EntityPersister
|
||||
$placeholder = $type->convertToDatabaseValueSQL('?', $this->platform);
|
||||
}
|
||||
|
||||
$values[] = $placeholder;
|
||||
$placeholders[] = $placeholder;
|
||||
}
|
||||
|
||||
$columns = implode(', ', $columns);
|
||||
$values = implode(', ', $values);
|
||||
$columns = implode(', ', $columns);
|
||||
$placeholders = implode(', ', $placeholders);
|
||||
|
||||
$this->insertSql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $values);
|
||||
|
||||
return $this->insertSql;
|
||||
return sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $placeholders);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1964,6 +1951,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
ParameterType::STRING => ArrayParameterType::STRING,
|
||||
ParameterType::INTEGER => ArrayParameterType::INTEGER,
|
||||
ParameterType::ASCII => ArrayParameterType::ASCII,
|
||||
ParameterType::BINARY => ArrayParameterType::BINARY,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
// Execute all inserts. For each entity:
|
||||
// 1) Insert on root table
|
||||
// 2) Insert on sub tables
|
||||
foreach ($this->queuedInserts as $entity) {
|
||||
foreach ($this->queuedInserts as $key => $entity) {
|
||||
$insertData = $this->prepareInsertData($entity);
|
||||
|
||||
// Execute insert on root table
|
||||
@@ -179,9 +179,16 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
if ($this->class->requiresFetchAfterChange) {
|
||||
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedInserts = [];
|
||||
// Unset this queued insert, so that the prepareUpdateData() method (called via prepareInsertData() method)
|
||||
// knows right away (for the next entity already) that the current entity has been written to the database
|
||||
// and no extra updates need to be scheduled to refer to it.
|
||||
//
|
||||
// In \Doctrine\ORM\UnitOfWork::executeInserts(), the UoW already removed entities
|
||||
// from its own list (\Doctrine\ORM\UnitOfWork::$entityInsertions) right after they
|
||||
// were given to our addInsert() method.
|
||||
unset($this->queuedInserts[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(object $entity): void
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Proxy;
|
||||
|
||||
use Closure;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function file_exists;
|
||||
use function ltrim;
|
||||
@@ -15,6 +16,7 @@ use function strlen;
|
||||
use function substr;
|
||||
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
/**
|
||||
* Special Autoloader for Proxy classes, which are not PSR-0 compliant.
|
||||
@@ -34,6 +36,15 @@ final class Autoloader
|
||||
*/
|
||||
public static function resolveFile(string $proxyDir, string $proxyNamespace, string $className): string
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Class "%s" is deprecated. Use native lazy objects instead.',
|
||||
self::class,
|
||||
);
|
||||
}
|
||||
|
||||
if (! str_starts_with($className, $proxyNamespace)) {
|
||||
throw new NotAProxyClass($className, $proxyNamespace);
|
||||
}
|
||||
@@ -59,6 +70,15 @@ final class Autoloader
|
||||
string $proxyNamespace,
|
||||
Closure|null $notFoundCallback = null,
|
||||
): Closure {
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Class "%s" is deprecated. Use native lazy objects instead.',
|
||||
self::class,
|
||||
);
|
||||
}
|
||||
|
||||
$proxyNamespace = ltrim($proxyNamespace, '\\');
|
||||
|
||||
$autoloader = /** @param class-string $className */ static function (string $className) use ($proxyDir, $proxyNamespace, $notFoundCallback): void {
|
||||
|
||||
@@ -4,12 +4,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Proxy;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\Persistence\Mapping\ProxyClassNameResolver;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
|
||||
use function strrpos;
|
||||
use function substr;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
/**
|
||||
* Class-related functionality for objects that might or not be proxy objects
|
||||
* at the moment.
|
||||
@@ -18,6 +21,15 @@ final class DefaultProxyClassNameResolver implements ProxyClassNameResolver
|
||||
{
|
||||
public function resolveClassName(string $className): string
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Class "%s" is deprecated. Use native lazy objects instead.',
|
||||
self::class,
|
||||
);
|
||||
}
|
||||
|
||||
$pos = strrpos($className, '\\' . Proxy::MARKER . '\\');
|
||||
|
||||
if ($pos === false) {
|
||||
@@ -30,6 +42,15 @@ final class DefaultProxyClassNameResolver implements ProxyClassNameResolver
|
||||
/** @return class-string */
|
||||
public static function getClass(object $object): string
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Class "%s" is deprecated. Use native lazy objects instead.',
|
||||
self::class,
|
||||
);
|
||||
}
|
||||
|
||||
return (new self())->resolveClassName($object::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Proxy;
|
||||
|
||||
use Closure;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityNotFoundException;
|
||||
use Doctrine\ORM\ORMInvalidArgumentException;
|
||||
@@ -30,6 +31,7 @@ use function dirname;
|
||||
use function file_exists;
|
||||
use function file_put_contents;
|
||||
use function filemtime;
|
||||
use function func_num_args;
|
||||
use function is_bool;
|
||||
use function is_dir;
|
||||
use function is_int;
|
||||
@@ -150,12 +152,29 @@ EOPHP;
|
||||
string|null $proxyNs = null,
|
||||
bool|int $autoGenerate = self::AUTOGENERATE_NEVER,
|
||||
) {
|
||||
if (! $proxyDir && ! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
throw ORMInvalidArgumentException::proxyDirectoryRequired();
|
||||
}
|
||||
if (! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Not enabling native lazy objects is deprecated and will be impossible in Doctrine ORM 4.0.',
|
||||
);
|
||||
}
|
||||
|
||||
if (! $proxyNs && ! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
throw ORMInvalidArgumentException::proxyNamespaceRequired();
|
||||
if (! $proxyDir) {
|
||||
throw ORMInvalidArgumentException::proxyDirectoryRequired();
|
||||
}
|
||||
|
||||
if (! $proxyNs) {
|
||||
throw ORMInvalidArgumentException::proxyNamespaceRequired();
|
||||
}
|
||||
} elseif (PHP_VERSION_ID >= 80400 && func_num_args() > 1) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Passing more than just the EntityManager to the %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
if (is_int($autoGenerate) ? $autoGenerate < 0 || $autoGenerate > 4 : ! is_bool($autoGenerate)) {
|
||||
|
||||
@@ -1464,7 +1464,7 @@ final class Parser
|
||||
|
||||
assert($this->lexer->lookahead !== null);
|
||||
$expr = match (true) {
|
||||
$this->isMathOperator($peek) => $this->SimpleArithmeticExpression(),
|
||||
$this->isMathOperator($peek) || $this->isMathOperator($glimpse) => $this->SimpleArithmeticExpression(),
|
||||
$glimpse !== null && $glimpse->type === TokenType::T_DOT => $this->SingleValuedPathExpression(),
|
||||
$this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis()) => $this->ScalarExpression(),
|
||||
$this->lexer->lookahead->type === TokenType::T_CASE => $this->CaseExpression(),
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Tools\Console\MetadataFilter;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@@ -19,6 +20,8 @@ use function mkdir;
|
||||
use function realpath;
|
||||
use function sprintf;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
/**
|
||||
* Command to (re)generate the proxy classes used by doctrine.
|
||||
*
|
||||
@@ -39,6 +42,14 @@ class GenerateProxiesCommand extends AbstractEntityManagerCommand
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Generating proxies is deprecated and will be impossible in Doctrine ORM 4.0.',
|
||||
);
|
||||
}
|
||||
|
||||
$ui = (new SymfonyStyle($input, $output))->getErrorStyle();
|
||||
|
||||
$em = $this->getEntityManager($input);
|
||||
|
||||
@@ -10,6 +10,8 @@ use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\FieldMapping;
|
||||
use Doctrine\Persistence\Mapping\MappingException;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
@@ -19,6 +21,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function current;
|
||||
use function get_debug_type;
|
||||
@@ -32,6 +35,7 @@ use function preg_match;
|
||||
use function preg_quote;
|
||||
use function print_r;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
|
||||
use const JSON_PRETTY_PRINT;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
@@ -73,6 +77,20 @@ EOT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestArgumentValuesFor('entityName')) {
|
||||
$entityManager = $this->getEntityManager($input);
|
||||
|
||||
$entities = array_map(
|
||||
static fn (string $fqcn) => str_replace('\\', '\\\\', $fqcn),
|
||||
$this->getMappedEntities($entityManager),
|
||||
);
|
||||
|
||||
$suggestions->suggestValues(array_values($entities));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display all the mapping information for a single Entity.
|
||||
*
|
||||
@@ -97,7 +115,7 @@ EOT);
|
||||
$this->formatField('Embedded class?', $metadata->isEmbeddedClass),
|
||||
$this->formatField('Parent classes', $metadata->parentClasses),
|
||||
$this->formatField('Sub classes', $metadata->subClasses),
|
||||
$this->formatField('Embedded classes', $metadata->subClasses),
|
||||
$this->formatField('Embedded classes', $metadata->embeddedClasses),
|
||||
$this->formatField('Identifier', $metadata->identifier),
|
||||
$this->formatField('Inheritance type', $metadata->inheritanceType),
|
||||
$this->formatField('Discriminator column', $metadata->discriminatorColumn),
|
||||
|
||||
@@ -505,6 +505,11 @@ class SchemaTool
|
||||
if ($isUnique) {
|
||||
$table->addUniqueIndex([$columnName]);
|
||||
}
|
||||
|
||||
$isIndex = $mapping->index ?? false;
|
||||
if ($isIndex) {
|
||||
$table->addIndex([$columnName]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -153,7 +153,7 @@ class SchemaValidator
|
||||
$ce[] = 'The field ' . $class->name . '#' . $fieldName . ' is on the inverse side of a ' .
|
||||
'bi-directional relationship, but the specified mappedBy association on the target-entity ' .
|
||||
$assoc->targetEntity . '#' . $assoc->mappedBy . ' does not contain the required ' .
|
||||
"'inversedBy=\"" . $fieldName . "\"' attribute.";
|
||||
"'inversedBy: \"" . $fieldName . "\"' attribute.";
|
||||
} elseif ($targetMetadata->associationMappings[$assoc->mappedBy]->inversedBy !== $fieldName) {
|
||||
$ce[] = 'The mappings ' . $class->name . '#' . $fieldName . ' and ' .
|
||||
$assoc->targetEntity . '#' . $assoc->mappedBy . ' are ' .
|
||||
@@ -174,7 +174,7 @@ class SchemaValidator
|
||||
$ce[] = 'The field ' . $class->name . '#' . $fieldName . ' is on the owning side of a ' .
|
||||
'bi-directional relationship, but the specified inversedBy association on the target-entity ' .
|
||||
$assoc->targetEntity . '#' . $assoc->inversedBy . ' does not contain the required ' .
|
||||
"'mappedBy=\"" . $fieldName . "\"' attribute.";
|
||||
"'mappedBy: \"" . $fieldName . "\"' attribute.";
|
||||
} elseif ($targetMetadata->associationMappings[$assoc->inversedBy]->mappedBy !== $fieldName) {
|
||||
$ce[] = 'The mappings ' . $class->name . '#' . $fieldName . ' and ' .
|
||||
$assoc->targetEntity . '#' . $assoc->inversedBy . ' are ' .
|
||||
|
||||
@@ -31,6 +31,7 @@ use Doctrine\ORM\Id\AssignedGenerator;
|
||||
use Doctrine\ORM\Internal\HydrationCompleteHandler;
|
||||
use Doctrine\ORM\Internal\StronglyConnectedComponents;
|
||||
use Doctrine\ORM\Internal\TopologicalSort;
|
||||
use Doctrine\ORM\Internal\UnitOfWork\InsertBatch;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
@@ -933,7 +934,9 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
$this->entityStates[$oid] = self::STATE_MANAGED;
|
||||
|
||||
$this->scheduleForInsert($entity);
|
||||
if (! isset($this->entityInsertions[$oid])) {
|
||||
$this->scheduleForInsert($entity);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param mixed[] $idValue */
|
||||
@@ -1037,30 +1040,36 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*/
|
||||
private function executeInserts(): void
|
||||
{
|
||||
$entities = $this->computeInsertExecutionOrder();
|
||||
$batchedByType = InsertBatch::batchByEntityType($this->em, $this->computeInsertExecutionOrder());
|
||||
$eventsToDispatch = [];
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$oid = spl_object_id($entity);
|
||||
$class = $this->em->getClassMetadata($entity::class);
|
||||
foreach ($batchedByType as $batch) {
|
||||
$class = $batch->class;
|
||||
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
|
||||
$persister = $this->getEntityPersister($class->name);
|
||||
|
||||
$persister->addInsert($entity);
|
||||
foreach ($batch->entities as $entity) {
|
||||
$oid = spl_object_id($entity);
|
||||
|
||||
unset($this->entityInsertions[$oid]);
|
||||
$persister->addInsert($entity);
|
||||
|
||||
unset($this->entityInsertions[$oid]);
|
||||
}
|
||||
|
||||
$persister->executeInserts();
|
||||
|
||||
if (! isset($this->entityIdentifiers[$oid])) {
|
||||
//entity was not added to identity map because some identifiers are foreign keys to new entities.
|
||||
//add it now
|
||||
$this->addToEntityIdentifiersAndEntityMap($class, $oid, $entity);
|
||||
}
|
||||
foreach ($batch->entities as $entity) {
|
||||
$oid = spl_object_id($entity);
|
||||
|
||||
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
|
||||
if (! isset($this->entityIdentifiers[$oid])) {
|
||||
//entity was not added to identity map because some identifiers are foreign keys to new entities.
|
||||
//add it now
|
||||
$this->addToEntityIdentifiersAndEntityMap($class, $oid, $entity);
|
||||
}
|
||||
|
||||
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
|
||||
$eventsToDispatch[] = ['class' => $class, 'entity' => $entity, 'invoke' => $invoke];
|
||||
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
|
||||
$eventsToDispatch[] = ['class' => $class, 'entity' => $entity, 'invoke' => $invoke];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2383,9 +2392,9 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$class->reflClass->markLazyObjectAsInitialized($entity);
|
||||
} else {
|
||||
$entity->__setInitialized(true);
|
||||
}
|
||||
|
||||
Hydrator::hydrate($entity, (array) $class->reflClass->newInstanceWithoutConstructor());
|
||||
Hydrator::hydrate($entity, (array) $class->reflClass->newInstanceWithoutConstructor());
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
! isset($hints[Query::HINT_REFRESH])
|
||||
@@ -3050,11 +3059,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a value is an uninitialized entity.
|
||||
*
|
||||
* @phpstan-assert-if-true InternalProxy $obj
|
||||
*/
|
||||
/** Tests if a value is an uninitialized entity. */
|
||||
public function isUninitializedObject(mixed $obj): bool
|
||||
{
|
||||
if ($this->em->getConfiguration()->isNativeLazyObjectsEnabled() && ! ($obj instanceof Collection) && is_object($obj)) {
|
||||
|
||||
238
tests/Doctrine/Tests/ORM/Functional/GH8011Test.php
Normal file
238
tests/Doctrine/Tests/ORM/Functional/GH8011Test.php
Normal file
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||
use Doctrine\Tests\Models\Company\CompanyEmployee;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Functional tests for ordering with arithmetic expression.
|
||||
*/
|
||||
#[Group('GH8011')]
|
||||
class GH8011Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->useModelSet('company');
|
||||
|
||||
parent::setUp();
|
||||
|
||||
$this->generateFixture();
|
||||
}
|
||||
|
||||
private function skipIfPostgres(string $test): void
|
||||
{
|
||||
$platform = $this->_em->getConnection()->getDatabasePlatform();
|
||||
if ($platform instanceof PostgreSQLPlatform) {
|
||||
self::markTestSkipped(
|
||||
'The ' . $test . ' test does not work on postgresql (see https://github.com/doctrine/orm/pull/8012).',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testOrderWithArithmeticExpressionWithSingleValuedPathExpression(): void
|
||||
{
|
||||
$dql = 'SELECT p ' .
|
||||
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
|
||||
'ORDER BY p.id + p.id ASC';
|
||||
|
||||
/** @var CompanyEmployee[] $result */
|
||||
$result = $this->_em->createQuery($dql)->getResult();
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals('Benjamin E.', $result[0]->getName());
|
||||
$this->assertEquals('Guilherme B.', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function testOrderWithArithmeticExpressionWithLiteralAndSingleValuedPathExpression(): void
|
||||
{
|
||||
$dql = 'SELECT p ' .
|
||||
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
|
||||
'ORDER BY 1 + p.id ASC';
|
||||
|
||||
/** @var CompanyEmployee[] $result */
|
||||
$result = $this->_em->createQuery($dql)->getResult();
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals('Benjamin E.', $result[0]->getName());
|
||||
$this->assertEquals('Guilherme B.', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function testOrderWithArithmeticExpressionWithLiteralAndSingleValuedPathExpression2(): void
|
||||
{
|
||||
$dql = 'SELECT p ' .
|
||||
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
|
||||
'ORDER BY ((1 + p.id)) ASC';
|
||||
|
||||
/** @var CompanyEmployee[] $result */
|
||||
$result = $this->_em->createQuery($dql)->getResult();
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals('Benjamin E.', $result[0]->getName());
|
||||
$this->assertEquals('Guilherme B.', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function testOrderWithArithmeticExpressionWithSingleValuedPathExpressionAndLiteral(): void
|
||||
{
|
||||
$dql = 'SELECT p ' .
|
||||
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
|
||||
'ORDER BY p.id + 1 ASC';
|
||||
|
||||
/** @var CompanyEmployee[] $result */
|
||||
$result = $this->_em->createQuery($dql)->getResult();
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals('Benjamin E.', $result[0]->getName());
|
||||
$this->assertEquals('Guilherme B.', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function testOrderWithArithmeticExpressionWithResultVariableAndLiteral(): void
|
||||
{
|
||||
$this->skipIfPostgres(__FUNCTION__);
|
||||
|
||||
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
|
||||
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
|
||||
'ORDER BY s + 1 DESC';
|
||||
|
||||
/** @var CompanyEmployee[] $result */
|
||||
$result = $this->_em->createQuery($dql)->getResult();
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals('Guilherme B.', $result[0]->getName());
|
||||
$this->assertEquals('Benjamin E.', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function testOrderWithArithmeticExpressionWithResultVariableAndLiteral2(): void
|
||||
{
|
||||
$this->skipIfPostgres(__FUNCTION__);
|
||||
|
||||
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
|
||||
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
|
||||
'ORDER BY ((s + 1)) DESC';
|
||||
|
||||
/** @var CompanyEmployee[] $result */
|
||||
$result = $this->_em->createQuery($dql)->getResult();
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals('Guilherme B.', $result[0]->getName());
|
||||
$this->assertEquals('Benjamin E.', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function testOrderWithArithmeticExpressionWithLiteralAndResultVariable(): void
|
||||
{
|
||||
$this->skipIfPostgres(__FUNCTION__);
|
||||
|
||||
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
|
||||
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
|
||||
'ORDER BY 1 + s DESC';
|
||||
|
||||
/** @var CompanyEmployee[] $result */
|
||||
$result = $this->_em->createQuery($dql)->getResult();
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals('Guilherme B.', $result[0]->getName());
|
||||
$this->assertEquals('Benjamin E.', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function testOrderWithArithmeticExpressionWithLiteralAndResultVariable2(): void
|
||||
{
|
||||
$this->skipIfPostgres(__FUNCTION__);
|
||||
|
||||
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
|
||||
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
|
||||
'ORDER BY ((1 + s)) DESC';
|
||||
|
||||
/** @var CompanyEmployee[] $result */
|
||||
$result = $this->_em->createQuery($dql)->getResult();
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals('Guilherme B.', $result[0]->getName());
|
||||
$this->assertEquals('Benjamin E.', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function testOrderWithArithmeticExpressionWithResultVariableAndSingleValuedPathExpression(): void
|
||||
{
|
||||
$this->skipIfPostgres(__FUNCTION__);
|
||||
|
||||
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
|
||||
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
|
||||
'ORDER BY s + p.id DESC';
|
||||
|
||||
/** @var CompanyEmployee[] $result */
|
||||
$result = $this->_em->createQuery($dql)->getResult();
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals('Guilherme B.', $result[0]->getName());
|
||||
$this->assertEquals('Benjamin E.', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function testOrderWithArithmeticExpressionWithResultVariableAndSingleValuedPathExpression2(): void
|
||||
{
|
||||
$this->skipIfPostgres(__FUNCTION__);
|
||||
|
||||
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
|
||||
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
|
||||
'ORDER BY ((s + p.id)) DESC';
|
||||
|
||||
/** @var CompanyEmployee[] $result */
|
||||
$result = $this->_em->createQuery($dql)->getResult();
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals('Guilherme B.', $result[0]->getName());
|
||||
$this->assertEquals('Benjamin E.', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function testOrderWithArithmeticExpressionWithSingleValuedPathExpressionAndResultVariable(): void
|
||||
{
|
||||
$this->skipIfPostgres(__FUNCTION__);
|
||||
|
||||
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
|
||||
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
|
||||
'ORDER BY p.id + s DESC';
|
||||
|
||||
/** @var CompanyEmployee[] $result */
|
||||
$result = $this->_em->createQuery($dql)->getResult();
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals('Guilherme B.', $result[0]->getName());
|
||||
$this->assertEquals('Benjamin E.', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function testOrderWithArithmeticExpressionWithLiteralAndResultVariableUsingHiddenResultVariable(): void
|
||||
{
|
||||
$dql = 'SELECT p, 1 + p.salary AS HIDDEN _order ' .
|
||||
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
|
||||
'ORDER BY _order DESC';
|
||||
|
||||
/** @var CompanyEmployee[] $result */
|
||||
$result = $this->_em->createQuery($dql)->getResult();
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals('Guilherme B.', $result[0]->getName());
|
||||
$this->assertEquals('Benjamin E.', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function generateFixture(): void
|
||||
{
|
||||
$person1 = new CompanyEmployee();
|
||||
$person1->setName('Benjamin E.');
|
||||
$person1->setDepartment('IT');
|
||||
$person1->setSalary(200000);
|
||||
|
||||
$person2 = new CompanyEmployee();
|
||||
$person2->setName('Guilherme B.');
|
||||
$person2->setDepartment('IT2');
|
||||
$person2->setSalary(400000);
|
||||
|
||||
$this->_em->persist($person1);
|
||||
$this->_em->persist($person2);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
}
|
||||
}
|
||||
85
tests/Doctrine/Tests/ORM/Functional/Ticket/GH12063.php
Normal file
85
tests/Doctrine/Tests/ORM/Functional/Ticket/GH12063.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?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 GH12063 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]
|
||||
public GH12063Code $code;
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class GH12063Entity
|
||||
{
|
||||
#[Id]
|
||||
#[Column]
|
||||
#[GeneratedValue]
|
||||
public int|null $id = null;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(referencedColumnName: 'code')]
|
||||
public GH12063Association $association;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Performance\LazyLoading;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Proxy\InternalProxy as Proxy;
|
||||
use Doctrine\Performance\EntityManagerFactory;
|
||||
use Doctrine\Performance\Mock\NonProxyLoadingEntityManager;
|
||||
@@ -25,9 +26,11 @@ final class ProxyInitializationTimeBench
|
||||
/** @var Proxy[] */
|
||||
private array|null $initializedEmployees = null;
|
||||
|
||||
private EntityManager $em;
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$proxyFactory = (new NonProxyLoadingEntityManager(EntityManagerFactory::getEntityManager([])))
|
||||
$proxyFactory = (new NonProxyLoadingEntityManager($this->em = EntityManagerFactory::getEntityManager([])))
|
||||
->getProxyFactory();
|
||||
|
||||
for ($i = 0; $i < 10000; ++$i) {
|
||||
@@ -36,36 +39,36 @@ final class ProxyInitializationTimeBench
|
||||
$this->initializedUsers[$i] = $proxyFactory->getProxy(CmsUser::class, ['id' => $i]);
|
||||
$this->initializedEmployees[$i] = $proxyFactory->getProxy(CmsEmployee::class, ['id' => $i]);
|
||||
|
||||
$this->initializedUsers[$i]->__load();
|
||||
$this->initializedEmployees[$i]->__load();
|
||||
$this->em->getUnitOfWork()->initializeObject($this->initializedUsers[$i]);
|
||||
$this->em->getUnitOfWork()->initializeObject($this->initializedEmployees[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
public function benchCmsUserInitialization(): void
|
||||
{
|
||||
foreach ($this->cmsUsers as $proxy) {
|
||||
$proxy->__load();
|
||||
$this->em->getUnitOfWork()->initializeObject($proxy);
|
||||
}
|
||||
}
|
||||
|
||||
public function benchCmsEmployeeInitialization(): void
|
||||
{
|
||||
foreach ($this->cmsEmployees as $proxy) {
|
||||
$proxy->__load();
|
||||
$this->em->getUnitOfWork()->initializeObject($proxy);
|
||||
}
|
||||
}
|
||||
|
||||
public function benchInitializationOfAlreadyInitializedCmsUsers(): void
|
||||
{
|
||||
foreach ($this->initializedUsers as $proxy) {
|
||||
$proxy->__load();
|
||||
$this->em->getUnitOfWork()->initializeObject($proxy);
|
||||
}
|
||||
}
|
||||
|
||||
public function benchInitializationOfAlreadyInitializedCmsEmployees(): void
|
||||
{
|
||||
foreach ($this->initializedEmployees as $proxy) {
|
||||
$proxy->__load();
|
||||
$this->em->getUnitOfWork()->initializeObject($proxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
|
||||
*/
|
||||
class EntityPersisterMock extends BasicEntityPersister
|
||||
{
|
||||
/** @var int<0, max> */
|
||||
private int $countOfExecuteInsertCalls = 0;
|
||||
private array $inserts = [];
|
||||
private array $updates = [];
|
||||
private array $deletes = [];
|
||||
@@ -40,6 +42,8 @@ class EntityPersisterMock extends BasicEntityPersister
|
||||
|
||||
public function executeInserts(): void
|
||||
{
|
||||
$this->countOfExecuteInsertCalls += 1;
|
||||
|
||||
foreach ($this->postInsertIds as $item) {
|
||||
$this->em->getUnitOfWork()->assignPostInsertId($item['entity'], $item['generatedId']);
|
||||
}
|
||||
@@ -86,6 +90,7 @@ class EntityPersisterMock extends BasicEntityPersister
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->countOfExecuteInsertCalls = 0;
|
||||
$this->existsCalled = false;
|
||||
$this->identityColumnValueCounter = 0;
|
||||
$this->inserts = [];
|
||||
@@ -97,4 +102,10 @@ class EntityPersisterMock extends BasicEntityPersister
|
||||
{
|
||||
return $this->existsCalled;
|
||||
}
|
||||
|
||||
/** @return int<0, max> */
|
||||
public function countOfExecuteInsertCalls(): int
|
||||
{
|
||||
return $this->countOfExecuteInsertCalls;
|
||||
}
|
||||
}
|
||||
|
||||
48
tests/Tests/Models/BinaryPrimaryKey/BinaryId.php
Normal file
48
tests/Tests/Models/BinaryPrimaryKey/BinaryId.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\BinaryPrimaryKey;
|
||||
|
||||
use LogicException;
|
||||
|
||||
use function bin2hex;
|
||||
use function hex2bin;
|
||||
use function random_bytes;
|
||||
|
||||
class BinaryId
|
||||
{
|
||||
public const LENGTH = 6;
|
||||
|
||||
private string $hexId;
|
||||
|
||||
private function __construct(string $data)
|
||||
{
|
||||
$this->hexId = $data;
|
||||
}
|
||||
|
||||
public static function new(): self
|
||||
{
|
||||
return new self(bin2hex(random_bytes(self::LENGTH)));
|
||||
}
|
||||
|
||||
public static function fromBytes(string $value): self
|
||||
{
|
||||
return new self(bin2hex($value));
|
||||
}
|
||||
|
||||
public function getBytes(): string
|
||||
{
|
||||
$binary = hex2bin($this->hexId);
|
||||
if ($binary === false) {
|
||||
throw new LogicException('Cannot convert hex to binary: ' . $this->hexId);
|
||||
}
|
||||
|
||||
return $binary;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getBytes();
|
||||
}
|
||||
}
|
||||
66
tests/Tests/Models/BinaryPrimaryKey/BinaryIdType.php
Normal file
66
tests/Tests/Models/BinaryPrimaryKey/BinaryIdType.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\BinaryPrimaryKey;
|
||||
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\Tests\Mocks\CompatibilityType;
|
||||
use LogicException;
|
||||
|
||||
final class BinaryIdType extends Type
|
||||
{
|
||||
use CompatibilityType;
|
||||
|
||||
public const NAME = 'binary_id';
|
||||
|
||||
public function convertToPHPValue(
|
||||
mixed $value,
|
||||
AbstractPlatform $platform,
|
||||
): BinaryId|null {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($value instanceof BinaryId) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return BinaryId::fromBytes($value);
|
||||
}
|
||||
|
||||
public function convertToDatabaseValue(
|
||||
mixed $value,
|
||||
AbstractPlatform $platform,
|
||||
): string|null {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
} elseif ($value instanceof BinaryId) {
|
||||
return $value->getBytes();
|
||||
} else {
|
||||
throw new LogicException('Unexpected value: ' . $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function getSQLDeclaration(
|
||||
array $column,
|
||||
AbstractPlatform $platform,
|
||||
): string {
|
||||
return $platform->getBinaryTypeDeclarationSQL([
|
||||
'length' => BinaryId::LENGTH,
|
||||
'fixed' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
private function doGetBindingType(): ParameterType|int
|
||||
{
|
||||
return ParameterType::BINARY;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
}
|
||||
73
tests/Tests/Models/BinaryPrimaryKey/Category.php
Normal file
73
tests/Tests/Models/BinaryPrimaryKey/Category.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\BinaryPrimaryKey;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\ReadableCollection;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
use Doctrine\ORM\Mapping\OneToMany;
|
||||
|
||||
#[Entity]
|
||||
class Category
|
||||
{
|
||||
#[Id]
|
||||
#[Column(type: BinaryIdType::NAME, nullable: false)]
|
||||
private BinaryId $id;
|
||||
|
||||
#[Column]
|
||||
private string $name;
|
||||
|
||||
#[ManyToOne(targetEntity: self::class, inversedBy: 'children')]
|
||||
private self|null $parent;
|
||||
|
||||
/** @var Collection<int, Category> */
|
||||
#[OneToMany(targetEntity: self::class, mappedBy: 'parent')]
|
||||
private Collection $children;
|
||||
|
||||
public function __construct(
|
||||
string $name,
|
||||
self|null $parent = null,
|
||||
) {
|
||||
$this->id = BinaryId::new();
|
||||
$this->name = $name;
|
||||
$this->parent = $parent;
|
||||
$this->children = new ArrayCollection();
|
||||
|
||||
$parent?->addChild($this);
|
||||
}
|
||||
|
||||
public function getId(): BinaryId
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getParent(): self|null
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/** @return ReadableCollection<int, Category> */
|
||||
public function getChildren(): ReadableCollection
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function addChild(self $category): void
|
||||
{
|
||||
if (! $this->children->contains($category)) {
|
||||
$this->children->add($category);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\TypedProperties;
|
||||
|
||||
use BcMath\Number;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
@@ -54,6 +55,9 @@ class UserTyped
|
||||
#[ORM\Embedded]
|
||||
public Contact|null $contact = null;
|
||||
|
||||
#[ORM\Column(precision: 5, scale: 2)]
|
||||
public Number|null $bodyHeight = null;
|
||||
|
||||
public static function loadMetadata(ClassMetadata $metadata): void
|
||||
{
|
||||
$metadata->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_NONE);
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM;
|
||||
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Cache\CacheConfiguration;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
@@ -17,6 +18,8 @@ use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
|
||||
use Doctrine\Tests\Models\DDC753\DDC753CustomRepository;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\RequiresPhp;
|
||||
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
|
||||
@@ -25,6 +28,8 @@ use Psr\Cache\CacheItemPoolInterface;
|
||||
*/
|
||||
class ConfigurationTest extends TestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
private Configuration $configuration;
|
||||
|
||||
protected function setUp(): void
|
||||
@@ -34,6 +39,7 @@ class ConfigurationTest extends TestCase
|
||||
$this->configuration = new Configuration();
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function testSetGetProxyDir(): void
|
||||
{
|
||||
self::assertNull($this->configuration->getProxyDir()); // defaults
|
||||
@@ -42,6 +48,7 @@ class ConfigurationTest extends TestCase
|
||||
self::assertSame(__DIR__, $this->configuration->getProxyDir());
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function testSetGetAutoGenerateProxyClasses(): void
|
||||
{
|
||||
self::assertSame(ProxyFactory::AUTOGENERATE_ALWAYS, $this->configuration->getAutoGenerateProxyClasses()); // defaults
|
||||
@@ -56,6 +63,7 @@ class ConfigurationTest extends TestCase
|
||||
self::assertSame(ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS, $this->configuration->getAutoGenerateProxyClasses());
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function testSetGetProxyNamespace(): void
|
||||
{
|
||||
self::assertNull($this->configuration->getProxyNamespace()); // defaults
|
||||
@@ -212,4 +220,13 @@ class ConfigurationTest extends TestCase
|
||||
$this->configuration->setTypedFieldMapper($defaultTypedFieldMapper);
|
||||
self::assertSame($defaultTypedFieldMapper, $this->configuration->getTypedFieldMapper());
|
||||
}
|
||||
|
||||
#[RequiresPhp('8.4')]
|
||||
#[WithoutErrorHandler]
|
||||
public function testDisablingNativeLazyObjectsIsDeprecated(): void
|
||||
{
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12005');
|
||||
|
||||
$this->configuration->enableNativeLazyObjects(false);
|
||||
}
|
||||
}
|
||||
|
||||
93
tests/Tests/ORM/Functional/PrePersistEventTest.php
Normal file
93
tests/Tests/ORM/Functional/PrePersistEventTest.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\ORM\Event\PrePersistEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
use function uniqid;
|
||||
|
||||
class PrePersistEventTest extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->createSchemaForModels(
|
||||
EntityWithUnmappedEntity::class,
|
||||
EntityWithCascadeAssociation::class,
|
||||
);
|
||||
}
|
||||
|
||||
public function testCallingPersistInPrePersistHook(): void
|
||||
{
|
||||
$entityWithUnmapped = new EntityWithUnmappedEntity();
|
||||
$entityWithCascade = new EntityWithCascadeAssociation();
|
||||
|
||||
$entityWithUnmapped->unmapped = $entityWithCascade;
|
||||
$entityWithCascade->cascaded = $entityWithUnmapped;
|
||||
|
||||
$this->_em->getEventManager()->addEventListener(Events::prePersist, new PrePersistUnmappedPersistListener());
|
||||
$this->_em->persist($entityWithUnmapped);
|
||||
|
||||
$this->assertTrue($this->_em->getUnitOfWork()->isScheduledForInsert($entityWithCascade));
|
||||
$this->assertTrue($this->_em->getUnitOfWork()->isScheduledForInsert($entityWithUnmapped));
|
||||
}
|
||||
}
|
||||
|
||||
class PrePersistUnmappedPersistListener
|
||||
{
|
||||
public function prePersist(PrePersistEventArgs $args): void
|
||||
{
|
||||
$object = $args->getObject();
|
||||
|
||||
if ($object instanceof EntityWithUnmappedEntity) {
|
||||
$uow = $args->getObjectManager()->getUnitOfWork();
|
||||
|
||||
if ($object->unmapped && ! $uow->isInIdentityMap($object->unmapped) && ! $uow->isScheduledForInsert($object->unmapped)) {
|
||||
$args->getObjectManager()->persist($object->unmapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class EntityWithUnmappedEntity
|
||||
{
|
||||
#[Id]
|
||||
#[Column(type: 'string', length: 255)]
|
||||
#[GeneratedValue(strategy: 'NONE')]
|
||||
public string $id;
|
||||
|
||||
public EntityWithCascadeAssociation|null $unmapped = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = uniqid(self::class, true);
|
||||
}
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class EntityWithCascadeAssociation
|
||||
{
|
||||
#[Id]
|
||||
#[Column(type: 'string', length: 255)]
|
||||
#[GeneratedValue(strategy: 'NONE')]
|
||||
public string $id;
|
||||
|
||||
#[ManyToOne(targetEntity: EntityWithUnmappedEntity::class, cascade: ['persist'])]
|
||||
public EntityWithUnmappedEntity|null $cascaded = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = uniqid(self::class, true);
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,6 @@ use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use Doctrine\Tests\Proxies\__CG__\Doctrine\Tests\Models\CMS\CmsUser as CmsUserProxy;
|
||||
|
||||
use function assert;
|
||||
|
||||
/**
|
||||
* Test that Doctrine ORM correctly works with proxy instances exactly like with ordinary Entities
|
||||
*
|
||||
@@ -34,10 +32,6 @@ class ProxiesLikeEntitiesTest extends OrmFunctionalTestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if ($this->_em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
self::markTestSkipped('This test is not applicable when lazy proxy is enabled.');
|
||||
}
|
||||
|
||||
$this->createSchemaForModels(
|
||||
CmsUser::class,
|
||||
CmsTag::class,
|
||||
@@ -83,8 +77,7 @@ class ProxiesLikeEntitiesTest extends OrmFunctionalTestCase
|
||||
{
|
||||
$userId = $this->user->getId();
|
||||
$uninitializedProxy = $this->_em->getReference(CmsUser::class, $userId);
|
||||
assert($uninitializedProxy instanceof CmsUserProxy);
|
||||
self::assertInstanceOf(CmsUserProxy::class, $uninitializedProxy);
|
||||
$this->assertTrue($this->isUninitializedObject($uninitializedProxy));
|
||||
|
||||
$this->_em->persist($uninitializedProxy);
|
||||
$this->_em->flush();
|
||||
@@ -116,6 +109,10 @@ class ProxiesLikeEntitiesTest extends OrmFunctionalTestCase
|
||||
*/
|
||||
public function testFindWithProxyName(): void
|
||||
{
|
||||
if ($this->_em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
self::markTestSkipped('There is no such thing as a proxy class name when native lazy objects are enabled.');
|
||||
}
|
||||
|
||||
$result = $this->_em->find(CmsUserProxy::class, $this->user->getId());
|
||||
self::assertSame($this->user->getId(), $result->getId());
|
||||
$this->_em->clear();
|
||||
|
||||
167
tests/Tests/ORM/Functional/SecondLevelCacheCountQueriesTest.php
Normal file
167
tests/Tests/ORM/Functional/SecondLevelCacheCountQueriesTest.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\ORM\Id\AssignedGenerator;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\Tests\Models\Cache\Country;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use ReflectionProperty;
|
||||
|
||||
use function array_diff;
|
||||
use function array_filter;
|
||||
use function file_exists;
|
||||
use function rmdir;
|
||||
use function scandir;
|
||||
use function strpos;
|
||||
use function sys_get_temp_dir;
|
||||
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
|
||||
/** @phpstan-type SupportedCacheUsage 0|ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE|ClassMetadata::CACHE_USAGE_READ_WRITE */
|
||||
#[Group('DDC-2183')]
|
||||
class SecondLevelCacheCountQueriesTest extends SecondLevelCacheFunctionalTestCase
|
||||
{
|
||||
/** @var string */
|
||||
private $tmpDir;
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if ($this->tmpDir !== null && file_exists($this->tmpDir)) {
|
||||
foreach (array_diff(scandir($this->tmpDir), ['.', '..']) as $f) {
|
||||
rmdir($this->tmpDir . DIRECTORY_SEPARATOR . $f);
|
||||
}
|
||||
|
||||
rmdir($this->tmpDir);
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/** @param SupportedCacheUsage $cacheUsage */
|
||||
private function setupCountryModel(int $cacheUsage): void
|
||||
{
|
||||
$metadata = $this->_em->getClassMetaData(Country::class);
|
||||
|
||||
if ($cacheUsage === 0) {
|
||||
$metadataCacheReflection = new ReflectionProperty(ClassMetadata::class, 'cache');
|
||||
$metadataCacheReflection->setAccessible(true);
|
||||
$metadataCacheReflection->setValue($metadata, null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($cacheUsage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
|
||||
$this->tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::class;
|
||||
$this->secondLevelCacheFactory->setFileLockRegionDirectory($this->tmpDir);
|
||||
}
|
||||
|
||||
$metadata->enableCache(['usage' => $cacheUsage]);
|
||||
}
|
||||
|
||||
private function loadFixturesCountriesWithoutPostInsertIdentifier(): void
|
||||
{
|
||||
$metadata = $this->_em->getClassMetaData(Country::class);
|
||||
$metadata->setIdGenerator(new AssignedGenerator());
|
||||
|
||||
$c1 = new Country('Brazil');
|
||||
$c1->setId(10);
|
||||
$c2 = new Country('Germany');
|
||||
$c2->setId(20);
|
||||
|
||||
$this->countries[] = $c1;
|
||||
$this->countries[] = $c2;
|
||||
|
||||
$this->_em->persist($c1);
|
||||
$this->_em->persist($c2);
|
||||
$this->_em->flush();
|
||||
}
|
||||
|
||||
/** @param 'INSERT'|'UPDATE'|'DELETE' $type */
|
||||
private function assertQueryCountByType(string $type, int $expectedCount): void
|
||||
{
|
||||
$queries = array_filter($this->getQueryLog()->queries, static function (array $entry) use ($type): bool {
|
||||
return strpos($entry['sql'], $type) === 0;
|
||||
});
|
||||
|
||||
self::assertCount($expectedCount, $queries);
|
||||
}
|
||||
|
||||
/** @param SupportedCacheUsage $cacheUsage */
|
||||
#[DataProvider('cacheUsageProvider')]
|
||||
public function testInsertWithPostInsertIdentifier(int $cacheUsage): void
|
||||
{
|
||||
$this->setupCountryModel($cacheUsage);
|
||||
|
||||
self::assertQueryCountByType('INSERT', 0);
|
||||
|
||||
$this->loadFixturesCountries();
|
||||
|
||||
self::assertCount(2, $this->countries);
|
||||
self::assertQueryCountByType('INSERT', 2);
|
||||
}
|
||||
|
||||
/** @param SupportedCacheUsage $cacheUsage */
|
||||
#[DataProvider('cacheUsageProvider')]
|
||||
public function testInsertWithoutPostInsertIdentifier(int $cacheUsage): void
|
||||
{
|
||||
$this->setupCountryModel($cacheUsage);
|
||||
|
||||
self::assertQueryCountByType('INSERT', 0);
|
||||
|
||||
$this->loadFixturesCountriesWithoutPostInsertIdentifier();
|
||||
|
||||
self::assertCount(2, $this->countries);
|
||||
self::assertQueryCountByType('INSERT', 2);
|
||||
}
|
||||
|
||||
/** @param SupportedCacheUsage $cacheUsage */
|
||||
#[DataProvider('cacheUsageProvider')]
|
||||
public function testDelete(int $cacheUsage): void
|
||||
{
|
||||
$this->setupCountryModel($cacheUsage);
|
||||
$this->loadFixturesCountries();
|
||||
|
||||
$c1 = $this->_em->find(Country::class, $this->countries[0]->getId());
|
||||
$c2 = $this->_em->find(Country::class, $this->countries[1]->getId());
|
||||
|
||||
$this->_em->remove($c1);
|
||||
$this->_em->remove($c2);
|
||||
$this->_em->flush();
|
||||
|
||||
self::assertQueryCountByType('DELETE', 2);
|
||||
}
|
||||
|
||||
/** @param SupportedCacheUsage $cacheUsage */
|
||||
#[DataProvider('cacheUsageProvider')]
|
||||
public function testUpdate(int $cacheUsage): void
|
||||
{
|
||||
$this->setupCountryModel($cacheUsage);
|
||||
$this->loadFixturesCountries();
|
||||
|
||||
$c1 = $this->_em->find(Country::class, $this->countries[0]->getId());
|
||||
$c2 = $this->_em->find(Country::class, $this->countries[1]->getId());
|
||||
|
||||
$c1->setName('Czech Republic');
|
||||
$c2->setName('Hungary');
|
||||
|
||||
$this->_em->persist($c1);
|
||||
$this->_em->persist($c2);
|
||||
$this->_em->flush();
|
||||
|
||||
self::assertQueryCountByType('UPDATE', 2);
|
||||
}
|
||||
|
||||
/** @return list<array{SupportedCacheUsage}> */
|
||||
public static function cacheUsageProvider(): array
|
||||
{
|
||||
return [
|
||||
[0],
|
||||
[ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE],
|
||||
[ClassMetadata::CACHE_USAGE_READ_WRITE],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,6 @@ use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* This class makes tests on the correct use of a database schema when entities are stored
|
||||
*/
|
||||
@@ -30,32 +28,7 @@ class DDC2825Test extends OrmFunctionalTestCase
|
||||
}
|
||||
}
|
||||
|
||||
#[DataProvider('getTestedClasses')]
|
||||
public function testClassSchemaMappingsValidity(string $className, string $expectedSchemaName, string $expectedTableName): void
|
||||
{
|
||||
$classMetadata = $this->_em->getClassMetadata($className);
|
||||
$platform = $this->_em->getConnection()->getDatabasePlatform();
|
||||
$quotedTableName = $this->_em->getConfiguration()->getQuoteStrategy()->getTableName($classMetadata, $platform);
|
||||
|
||||
// Check if table name and schema properties are defined in the class metadata
|
||||
self::assertEquals($expectedTableName, $classMetadata->table['name']);
|
||||
self::assertEquals($expectedSchemaName, $classMetadata->table['schema']);
|
||||
|
||||
if ($this->_em->getConnection()->getDatabasePlatform()->supportsSchemas()) {
|
||||
$fullTableName = sprintf('%s.%s', $expectedSchemaName, $expectedTableName);
|
||||
} else {
|
||||
$fullTableName = sprintf('%s__%s', $expectedSchemaName, $expectedTableName);
|
||||
}
|
||||
|
||||
self::assertEquals($fullTableName, $quotedTableName);
|
||||
|
||||
// Checks sequence name validity
|
||||
self::assertEquals(
|
||||
$fullTableName . '_' . $classMetadata->getSingleIdentifierColumnName() . '_seq',
|
||||
$classMetadata->getSequenceName($platform),
|
||||
);
|
||||
}
|
||||
|
||||
/** @param class-string $className */
|
||||
#[DataProvider('getTestedClasses')]
|
||||
public function testPersistenceOfEntityWithSchemaMapping(string $className): void
|
||||
{
|
||||
@@ -68,17 +41,14 @@ class DDC2825Test extends OrmFunctionalTestCase
|
||||
self::assertCount(1, $this->_em->getRepository($className)->findAll());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider
|
||||
*
|
||||
* @return string[][]
|
||||
*/
|
||||
/** @return list<array{class-string}> */
|
||||
public static function getTestedClasses(): array
|
||||
{
|
||||
return [
|
||||
[ExplicitSchemaAndTable::class, 'explicit_schema', 'explicit_table'],
|
||||
[SchemaAndTableInTableName::class, 'implicit_schema', 'implicit_table'],
|
||||
[DDC2825ClassWithImplicitlyDefinedSchemaAndQuotedTableName::class, 'myschema', 'order'],
|
||||
[ExplicitSchemaAndTable::class],
|
||||
[SchemaAndTableInTableName::class],
|
||||
[DDC2825ClassWithImplicitlyDefinedSchemaAndQuotedTableName::class],
|
||||
[File::class],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -93,3 +63,13 @@ class DDC2825ClassWithImplicitlyDefinedSchemaAndQuotedTableName
|
||||
#[ORM\Column(type: 'integer')]
|
||||
public $id;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: '`file`', schema: 'yourschema')]
|
||||
class File
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
#[ORM\GeneratedValue]
|
||||
public int $id;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ use Doctrine\ORM\Mapping\Table;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function get_class;
|
||||
use ReflectionClass;
|
||||
|
||||
#[Group('GH10808')]
|
||||
class GH10808Test extends OrmFunctionalTestCase
|
||||
@@ -32,10 +31,6 @@ class GH10808Test extends OrmFunctionalTestCase
|
||||
|
||||
public function testDQLDeferredEagerLoad(): void
|
||||
{
|
||||
if ($this->_em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
self::markTestSkipped('Test requires lazy loading to be disabled');
|
||||
}
|
||||
|
||||
$appointment = new GH10808Appointment();
|
||||
|
||||
$this->_em->persist($appointment);
|
||||
@@ -55,14 +50,13 @@ class GH10808Test extends OrmFunctionalTestCase
|
||||
|
||||
$eagerLoadResult = $query->setHint(UnitOfWork::HINT_DEFEREAGERLOAD, false)->getSingleResult();
|
||||
|
||||
self::assertNotEquals(
|
||||
GH10808AppointmentChild::class,
|
||||
get_class($deferredLoadResult->child),
|
||||
'$deferredLoadResult->child should be a proxy',
|
||||
$reflector = new ReflectionClass(GH10808AppointmentChild::class);
|
||||
self::assertFalse(
|
||||
$this->isUninitializedObject($deferredLoadResult->child),
|
||||
'$deferredLoadResult->child should be a native lazy object',
|
||||
);
|
||||
self::assertEquals(
|
||||
GH10808AppointmentChild::class,
|
||||
get_class($eagerLoadResult->child),
|
||||
self::assertFalse(
|
||||
$this->isUninitializedObject($deferredLoadResult->child),
|
||||
'$eagerLoadResult->child should not be a proxy',
|
||||
);
|
||||
}
|
||||
|
||||
99
tests/Tests/ORM/Functional/Ticket/GH11982Test.php
Normal file
99
tests/Tests/ORM/Functional/Ticket/GH11982Test.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function method_exists;
|
||||
use function reset;
|
||||
|
||||
class GH11982Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([
|
||||
GH11982ColumnIndex::class,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Group('GH-11982')]
|
||||
public function testSchema(): void
|
||||
{
|
||||
if ($this->_em->getConnection()->getDatabasePlatform() instanceof PostgreSQLPlatform) {
|
||||
self::markTestSkipped('This test does not work on psql.');
|
||||
}
|
||||
|
||||
if (! method_exists(Index::class, 'getIndexedColumns')) {
|
||||
self::markTestSkipped('This test requires doctrine/dbal >=4.3');
|
||||
}
|
||||
|
||||
$indexes = $this->createSchemaManager()
|
||||
->introspectTable('GH11982ColumnIndex')
|
||||
->getIndexes();
|
||||
|
||||
self::assertCount(3, $indexes); // primary + 2 custom indexes
|
||||
self::assertArrayHasKey('class_idx', $indexes);
|
||||
|
||||
unset($indexes['primary']);
|
||||
unset($indexes['class_idx']);
|
||||
$unnamedIndexColumns = reset($indexes)->getIndexedColumns();
|
||||
self::assertCount(1, $unnamedIndexColumns);
|
||||
self::assertEquals(
|
||||
'indexTrue',
|
||||
$unnamedIndexColumns[0]->getColumnName()->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
#[Group('GH-11982')]
|
||||
public function testSchemaLegacyDbal(): void
|
||||
{
|
||||
if ($this->_em->getConnection()->getDatabasePlatform() instanceof PostgreSQLPlatform) {
|
||||
self::markTestSkipped('This test does not work on psql.');
|
||||
}
|
||||
|
||||
if (method_exists(Index::class, 'getIndexedColumns')) {
|
||||
self::markTestSkipped('This test requires doctrine/dbal <4.3');
|
||||
}
|
||||
|
||||
$indexes = $this->createSchemaManager()
|
||||
->introspectTable('GH11982ColumnIndex')
|
||||
->getIndexes();
|
||||
|
||||
self::assertCount(3, $indexes); // primary + 2 custom indexes
|
||||
self::assertArrayHasKey('class_idx', $indexes);
|
||||
|
||||
unset($indexes['primary']);
|
||||
unset($indexes['class_idx']);
|
||||
$unnamedIndexColumns = reset($indexes)->getColumns();
|
||||
self::assertCount(1, $unnamedIndexColumns);
|
||||
self::assertEquals('indexTrue', $unnamedIndexColumns[0]);
|
||||
}
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Index(
|
||||
name: 'class_idx',
|
||||
fields: ['classIndex'],
|
||||
flags: ['fulltext'],
|
||||
options: ['test'],
|
||||
)]
|
||||
class GH11982ColumnIndex
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
public string $noIndex;
|
||||
|
||||
#[ORM\Column(index: true)]
|
||||
public string $indexTrue;
|
||||
|
||||
#[ORM\Column]
|
||||
public string $classIndex;
|
||||
}
|
||||
143
tests/Tests/ORM/Internal/UnitOfWork/InsertBatchTest.php
Normal file
143
tests/Tests/ORM/Internal/UnitOfWork/InsertBatchTest.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Internal\UnitOfWork;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Id\AssignedGenerator;
|
||||
use Doctrine\ORM\Id\IdentityGenerator;
|
||||
use Doctrine\ORM\Internal\UnitOfWork\InsertBatch;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\MockObject\Stub;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(InsertBatch::class)]
|
||||
#[Group('#11977')]
|
||||
final class InsertBatchTest extends TestCase
|
||||
{
|
||||
private EntityManagerInterface&Stub $entityManager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->entityManager = $this->createStub(EntityManagerInterface::class);
|
||||
|
||||
$entityAMetadata = new ClassMetadata(EntityA::class);
|
||||
$entityBMetadata = new ClassMetadata(EntityB::class);
|
||||
$entityCMetadata = new ClassMetadata(EntityC::class);
|
||||
|
||||
$entityAMetadata->idGenerator = new AssignedGenerator();
|
||||
$entityBMetadata->idGenerator = new AssignedGenerator();
|
||||
$entityCMetadata->idGenerator = new IdentityGenerator();
|
||||
|
||||
$this->entityManager->method('getClassMetadata')
|
||||
->willReturnMap([
|
||||
[EntityA::class, $entityAMetadata],
|
||||
[EntityB::class, $entityBMetadata],
|
||||
[EntityC::class, $entityCMetadata],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testWillProduceEmptyBatchOnNoGivenEntities(): void
|
||||
{
|
||||
self::assertEmpty(InsertBatch::batchByEntityType($this->entityManager, []));
|
||||
}
|
||||
|
||||
public function testWillBatchSameEntityOperationsInSingleBatch(): void
|
||||
{
|
||||
$batches = InsertBatch::batchByEntityType(
|
||||
$this->entityManager,
|
||||
[
|
||||
new EntityA(),
|
||||
new EntityA(),
|
||||
new EntityA(),
|
||||
],
|
||||
);
|
||||
|
||||
self::assertCount(1, $batches);
|
||||
self::assertSame(EntityA::class, $batches[0]->class->name);
|
||||
self::assertCount(3, $batches[0]->entities);
|
||||
}
|
||||
|
||||
public function testWillBatchInterleavedEntityOperationsInGroups(): void
|
||||
{
|
||||
$batches = InsertBatch::batchByEntityType(
|
||||
$this->entityManager,
|
||||
[
|
||||
new EntityA(),
|
||||
new EntityA(),
|
||||
new EntityB(),
|
||||
new EntityB(),
|
||||
new EntityA(),
|
||||
new EntityA(),
|
||||
],
|
||||
);
|
||||
|
||||
self::assertCount(3, $batches);
|
||||
self::assertSame(EntityA::class, $batches[0]->class->name);
|
||||
self::assertCount(2, $batches[0]->entities);
|
||||
self::assertSame(EntityB::class, $batches[1]->class->name);
|
||||
self::assertCount(2, $batches[1]->entities);
|
||||
self::assertSame(EntityA::class, $batches[2]->class->name);
|
||||
self::assertCount(2, $batches[2]->entities);
|
||||
}
|
||||
|
||||
public function testWillNotBatchOperationsForAGeneratedIdentifierEntity(): void
|
||||
{
|
||||
$batches = InsertBatch::batchByEntityType(
|
||||
$this->entityManager,
|
||||
[
|
||||
new EntityC(),
|
||||
new EntityC(),
|
||||
new EntityC(),
|
||||
],
|
||||
);
|
||||
|
||||
self::assertCount(3, $batches);
|
||||
self::assertSame(EntityC::class, $batches[0]->class->name);
|
||||
self::assertCount(1, $batches[0]->entities);
|
||||
self::assertSame(EntityC::class, $batches[1]->class->name);
|
||||
self::assertCount(1, $batches[1]->entities);
|
||||
self::assertSame(EntityC::class, $batches[2]->class->name);
|
||||
self::assertCount(1, $batches[2]->entities);
|
||||
}
|
||||
|
||||
public function testWillIsolateBatchesForEntitiesWithGeneratedIdentifiers(): void
|
||||
{
|
||||
$batches = InsertBatch::batchByEntityType(
|
||||
$this->entityManager,
|
||||
[
|
||||
new EntityA(),
|
||||
new EntityA(),
|
||||
new EntityC(),
|
||||
new EntityC(),
|
||||
new EntityA(),
|
||||
new EntityA(),
|
||||
],
|
||||
);
|
||||
|
||||
self::assertCount(4, $batches);
|
||||
self::assertSame(EntityA::class, $batches[0]->class->name);
|
||||
self::assertCount(2, $batches[0]->entities);
|
||||
self::assertSame(EntityC::class, $batches[1]->class->name);
|
||||
self::assertCount(1, $batches[1]->entities);
|
||||
self::assertSame(EntityC::class, $batches[2]->class->name);
|
||||
self::assertCount(1, $batches[2]->entities);
|
||||
self::assertSame(EntityA::class, $batches[3]->class->name);
|
||||
self::assertCount(2, $batches[3]->entities);
|
||||
}
|
||||
}
|
||||
|
||||
class EntityA
|
||||
{
|
||||
}
|
||||
|
||||
class EntityB
|
||||
{
|
||||
}
|
||||
|
||||
class EntityC
|
||||
{
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class AttributeDriverTest extends MappingDriverTestCase
|
||||
'name' => 'assoz_id',
|
||||
'referencedColumnName' => 'assoz_id',
|
||||
'unique' => false,
|
||||
'nullable' => true,
|
||||
'nullable' => null,
|
||||
'onDelete' => null,
|
||||
'columnDefinition' => null,
|
||||
]),
|
||||
@@ -80,7 +80,7 @@ class AttributeDriverTest extends MappingDriverTestCase
|
||||
'name' => 'inverse_assoz_id',
|
||||
'referencedColumnName' => 'inverse_assoz_id',
|
||||
'unique' => false,
|
||||
'nullable' => true,
|
||||
'nullable' => null,
|
||||
'onDelete' => null,
|
||||
'columnDefinition' => null,
|
||||
]),
|
||||
|
||||
@@ -364,7 +364,7 @@ class ClassMetadataBuilderTest extends OrmTestCase
|
||||
[
|
||||
'name' => 'group_id',
|
||||
'referencedColumnName' => 'id',
|
||||
'nullable' => true,
|
||||
'nullable' => false,
|
||||
'unique' => false,
|
||||
'onDelete' => 'CASCADE',
|
||||
'columnDefinition' => null,
|
||||
@@ -469,7 +469,7 @@ class ClassMetadataBuilderTest extends OrmTestCase
|
||||
[
|
||||
'name' => 'group_id',
|
||||
'referencedColumnName' => 'id',
|
||||
'nullable' => true,
|
||||
'nullable' => false,
|
||||
'unique' => false,
|
||||
'onDelete' => 'CASCADE',
|
||||
'columnDefinition' => null,
|
||||
@@ -539,7 +539,7 @@ class ClassMetadataBuilderTest extends OrmTestCase
|
||||
[
|
||||
'name' => 'group_id',
|
||||
'referencedColumnName' => 'id',
|
||||
'nullable' => true,
|
||||
'nullable' => false,
|
||||
'unique' => false,
|
||||
'onDelete' => 'CASCADE',
|
||||
'columnDefinition' => null,
|
||||
@@ -551,7 +551,7 @@ class ClassMetadataBuilderTest extends OrmTestCase
|
||||
[
|
||||
'name' => 'user_id',
|
||||
'referencedColumnName' => 'id',
|
||||
'nullable' => true,
|
||||
'nullable' => false,
|
||||
'unique' => false,
|
||||
'onDelete' => null,
|
||||
'columnDefinition' => null,
|
||||
@@ -742,7 +742,7 @@ class ClassMetadataBuilderTest extends OrmTestCase
|
||||
0 => [
|
||||
'name' => 'group_id',
|
||||
'referencedColumnName' => 'id',
|
||||
'nullable' => true,
|
||||
'nullable' => false,
|
||||
'unique' => false,
|
||||
'onDelete' => 'CASCADE',
|
||||
'columnDefinition' => null,
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\Tests\ORM\Mapping;
|
||||
|
||||
use ArrayObject;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Events;
|
||||
@@ -51,6 +52,7 @@ use Doctrine\Tests\ORM\Mapping\TypedFieldMapper\CustomIntAsStringTypedFieldMappe
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use DoctrineGlobalArticle;
|
||||
use LogicException;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group as TestGroup;
|
||||
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
|
||||
use ReflectionClass;
|
||||
@@ -60,6 +62,7 @@ use function array_keys;
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function serialize;
|
||||
use function str_contains;
|
||||
use function str_replace;
|
||||
@@ -199,6 +202,12 @@ class ClassMetadataTest extends OrmTestCase
|
||||
// float
|
||||
$cm->mapField(['fieldName' => 'float']);
|
||||
self::assertEquals('float', $cm->getTypeOfField('float'));
|
||||
|
||||
// number, requires DBAL 4.3+
|
||||
if (defined(Types::class . '::NUMBER')) {
|
||||
$cm->mapField(['fieldName' => 'bodyHeight']);
|
||||
self::assertEquals('number', $cm->getTypeOfField('bodyHeight'));
|
||||
}
|
||||
}
|
||||
|
||||
#[TestGroup('GH10313')]
|
||||
@@ -963,6 +972,47 @@ class ClassMetadataTest extends OrmTestCase
|
||||
);
|
||||
}
|
||||
|
||||
#[DataProvider('fullTableNameProvider')]
|
||||
public function testGetSequenceName(
|
||||
string $expectedSequenceName,
|
||||
string $fullTableName,
|
||||
): void {
|
||||
$cm = new ClassMetadata(self::class);
|
||||
$cm->setIdentifier(['id']);
|
||||
$cm->setPrimaryTable(['name' => $fullTableName]);
|
||||
|
||||
$platform = $this->createStub(AbstractPlatform::class);
|
||||
|
||||
self::assertSame(
|
||||
$expectedSequenceName,
|
||||
$cm->getSequenceName($platform),
|
||||
);
|
||||
}
|
||||
|
||||
/** @return iterable<string, array{string, string}> */
|
||||
public static function fullTableNameProvider(): iterable
|
||||
{
|
||||
yield 'quoted table name with schema' => [
|
||||
'custom.reserved_id_seq',
|
||||
'custom.`reserved`',
|
||||
];
|
||||
|
||||
yield 'unquoted table name with schema' => [
|
||||
'custom.non_reserved_id_seq',
|
||||
'custom.non_reserved',
|
||||
];
|
||||
|
||||
yield 'quoted table name without schema' => [
|
||||
'reserved_id_seq',
|
||||
'`reserved`',
|
||||
];
|
||||
|
||||
yield 'unquoted table name without schema' => [
|
||||
'non_reserved_id_seq',
|
||||
'non_reserved',
|
||||
];
|
||||
}
|
||||
|
||||
#[TestGroup('DDC-2700')]
|
||||
public function testIsIdentifierMappedSuperClass(): void
|
||||
{
|
||||
|
||||
@@ -6,13 +6,16 @@ namespace Doctrine\Tests\ORM\Mapping;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Schema\Name\UnquotedIdentifierFolding;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\DefaultQuoteStrategy;
|
||||
use Doctrine\Tests\Models\NonPublicSchemaJoins\User as NonPublicSchemaUser;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function assert;
|
||||
use function enum_exists;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Doctrine\Tests\ORM\Mapping\DefaultQuoteStrategyTest
|
||||
@@ -34,4 +37,48 @@ class DefaultQuoteStrategyTest extends OrmTestCase
|
||||
$strategy->getJoinTableName($metadata->associationMappings['readers'], $metadata, $platform),
|
||||
);
|
||||
}
|
||||
|
||||
#[DataProvider('fullTableNameProvider')]
|
||||
public function testGetTableName(string $expectedFullTableName, string $tableName): void
|
||||
{
|
||||
$classMetadata = new ClassMetadata(self::class);
|
||||
$classMetadata->setPrimaryTable(['name' => $tableName]);
|
||||
|
||||
$platform = $this->createStub(AbstractPlatform::class);
|
||||
$platform->method('quoteSingleIdentifier')
|
||||
->willReturnCallback(
|
||||
static fn (string $identifier): string => sprintf('✌️%s✌️', $identifier),
|
||||
);
|
||||
|
||||
$quotedTableName = (new DefaultQuoteStrategy())->getTableName(
|
||||
$classMetadata,
|
||||
$platform,
|
||||
);
|
||||
|
||||
self::assertSame($expectedFullTableName, $quotedTableName);
|
||||
}
|
||||
|
||||
/** @return iterable<string, array{string, string}> */
|
||||
public static function fullTableNameProvider(): iterable
|
||||
{
|
||||
yield 'quoted table name with schema' => [
|
||||
'✌️custom✌️.✌️reserved✌️',
|
||||
'custom.`reserved`',
|
||||
];
|
||||
|
||||
yield 'unquoted table name with schema' => [
|
||||
'custom.non_reserved',
|
||||
'custom.non_reserved',
|
||||
];
|
||||
|
||||
yield 'quoted table name without schema' => [
|
||||
'✌️reserved✌️',
|
||||
'`reserved`',
|
||||
];
|
||||
|
||||
yield 'unquoted table name without schema' => [
|
||||
'non_reserved',
|
||||
'non_reserved',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Mapping;
|
||||
|
||||
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
|
||||
use Doctrine\ORM\Mapping\JoinTableMapping;
|
||||
use Doctrine\ORM\Mapping\ManyToManyOwningSideMapping;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
use function assert;
|
||||
@@ -35,4 +37,57 @@ final class ManyToManyOwningSideMappingTest extends TestCase
|
||||
self::assertSame(['foo' => 'bar'], $resurrectedMapping->relationToSourceKeyColumns);
|
||||
self::assertSame(['bar' => 'baz'], $resurrectedMapping->relationToTargetKeyColumns);
|
||||
}
|
||||
|
||||
#[DataProvider('mappingsProvider')]
|
||||
public function testNullableDefaults(bool $expectedValue, ManyToManyOwningSideMapping $mapping): void
|
||||
{
|
||||
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
|
||||
self::assertSame($expectedValue, $joinColumn->nullable);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return iterable<string, array{bool, ManyToManyOwningSideMapping}> */
|
||||
public static function mappingsProvider(): iterable
|
||||
{
|
||||
$namingStrategy = new DefaultNamingStrategy();
|
||||
|
||||
yield 'defaults to false' => [
|
||||
false,
|
||||
ManyToManyOwningSideMapping::fromMappingArrayAndNamingStrategy([
|
||||
'fieldName' => 'foo',
|
||||
'sourceEntity' => self::class,
|
||||
'targetEntity' => self::class,
|
||||
'isOwningSide' => true,
|
||||
'joinTable' => [
|
||||
'name' => 'bar',
|
||||
'joinColumns' => [
|
||||
['name' => 'bar_id', 'referencedColumnName' => 'id'],
|
||||
],
|
||||
'inverseJoinColumns' => [
|
||||
['name' => 'foo_id', 'referencedColumnName' => 'id'],
|
||||
],
|
||||
],
|
||||
], $namingStrategy),
|
||||
];
|
||||
|
||||
yield 'explicitly marked as nullable' => [
|
||||
false, // user's intent is ignored at the ORM level
|
||||
ManyToManyOwningSideMapping::fromMappingArrayAndNamingStrategy([
|
||||
'fieldName' => 'foo',
|
||||
'sourceEntity' => self::class,
|
||||
'targetEntity' => self::class,
|
||||
'isOwningSide' => true,
|
||||
'joinTable' => [
|
||||
'name' => 'bar',
|
||||
'joinColumns' => [
|
||||
['name' => 'bar_id', 'referencedColumnName' => 'id', 'nullable' => true],
|
||||
],
|
||||
'inverseJoinColumns' => [
|
||||
['name' => 'foo_id', 'referencedColumnName' => 'id', 'nullable' => true],
|
||||
],
|
||||
],
|
||||
'id' => true,
|
||||
], $namingStrategy),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Mapping;
|
||||
|
||||
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
|
||||
use Doctrine\ORM\Mapping\JoinColumnMapping;
|
||||
use Doctrine\ORM\Mapping\ManyToOneAssociationMapping;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
use function assert;
|
||||
@@ -35,4 +37,60 @@ final class ManyToOneAssociationMappingTest extends TestCase
|
||||
self::assertSame(['foo' => 'bar'], $resurrectedMapping->sourceToTargetKeyColumns);
|
||||
self::assertSame(['bar' => 'foo'], $resurrectedMapping->targetToSourceKeyColumns);
|
||||
}
|
||||
|
||||
#[DataProvider('mappingsProvider')]
|
||||
public function testNullableDefaults(bool $expectedValue, ManyToOneAssociationMapping $mapping): void
|
||||
{
|
||||
foreach ($mapping->joinColumns as $joinColumn) {
|
||||
self::assertSame($expectedValue, $joinColumn->nullable);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return iterable<string, array{bool, ManyToOneAssociationMapping}> */
|
||||
public static function mappingsProvider(): iterable
|
||||
{
|
||||
$namingStrategy = new DefaultNamingStrategy();
|
||||
|
||||
yield 'not part of the identifier' => [
|
||||
true,
|
||||
ManyToOneAssociationMapping::fromMappingArrayAndName([
|
||||
'fieldName' => 'foo',
|
||||
'sourceEntity' => self::class,
|
||||
'targetEntity' => self::class,
|
||||
'isOwningSide' => true,
|
||||
'joinColumns' => [
|
||||
['name' => 'foo_id', 'referencedColumnName' => 'id'],
|
||||
],
|
||||
'id' => false,
|
||||
], $namingStrategy, self::class, null, false),
|
||||
];
|
||||
|
||||
yield 'part of the identifier' => [
|
||||
false,
|
||||
ManyToOneAssociationMapping::fromMappingArrayAndName([
|
||||
'fieldName' => 'foo',
|
||||
'sourceEntity' => self::class,
|
||||
'targetEntity' => self::class,
|
||||
'isOwningSide' => true,
|
||||
'joinColumns' => [
|
||||
['name' => 'foo_id', 'referencedColumnName' => 'id'],
|
||||
],
|
||||
'id' => true,
|
||||
], $namingStrategy, self::class, null, false),
|
||||
];
|
||||
|
||||
yield 'part of the identifier, but explicitly marked as nullable' => [
|
||||
false, // user's intent is ignored at the ORM level
|
||||
ManyToOneAssociationMapping::fromMappingArrayAndName([
|
||||
'fieldName' => 'foo',
|
||||
'sourceEntity' => self::class,
|
||||
'targetEntity' => self::class,
|
||||
'isOwningSide' => true,
|
||||
'joinColumns' => [
|
||||
['name' => 'foo_id', 'referencedColumnName' => 'id', 'nullable' => true],
|
||||
],
|
||||
'id' => true,
|
||||
], $namingStrategy, self::class, null, false),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,11 +227,12 @@ abstract class MappingDriverTestCase extends OrmTestCase
|
||||
#[Depends('testEntityTableNameAndInheritance')]
|
||||
public function testFieldMappings(ClassMetadata $class): ClassMetadata
|
||||
{
|
||||
self::assertEquals(4, count($class->fieldMappings));
|
||||
self::assertEquals(5, count($class->fieldMappings));
|
||||
self::assertTrue(isset($class->fieldMappings['id']));
|
||||
self::assertTrue(isset($class->fieldMappings['name']));
|
||||
self::assertTrue(isset($class->fieldMappings['email']));
|
||||
self::assertTrue(isset($class->fieldMappings['version']));
|
||||
self::assertTrue(isset($class->fieldMappings['indexed']));
|
||||
|
||||
return $class;
|
||||
}
|
||||
@@ -262,6 +263,7 @@ abstract class MappingDriverTestCase extends OrmTestCase
|
||||
self::assertEquals(50, $class->fieldMappings['name']->length);
|
||||
self::assertTrue($class->fieldMappings['name']->nullable);
|
||||
self::assertTrue($class->fieldMappings['name']->unique);
|
||||
self::assertTrue($class->fieldMappings['indexed']->index);
|
||||
|
||||
return $class;
|
||||
}
|
||||
@@ -1006,6 +1008,10 @@ class User
|
||||
#[ORM\Version]
|
||||
public $version;
|
||||
|
||||
/** @var string */
|
||||
#[ORM\Column(index: true)]
|
||||
public $indexed;
|
||||
|
||||
#[ORM\PrePersist]
|
||||
public function doStuffOnPrePersist(): void
|
||||
{
|
||||
@@ -1065,6 +1071,12 @@ class User
|
||||
$mapping = ['fieldName' => 'version', 'type' => 'integer'];
|
||||
$metadata->setVersionMapping($mapping);
|
||||
$metadata->mapField($mapping);
|
||||
$metadata->mapField([
|
||||
'fieldName' => 'indexed',
|
||||
'type' => 'string',
|
||||
'columnName' => 'indexed',
|
||||
'index' => true,
|
||||
]);
|
||||
$metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO);
|
||||
$metadata->mapOneToOne(
|
||||
[
|
||||
|
||||
@@ -4,8 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Mapping;
|
||||
|
||||
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
|
||||
use Doctrine\ORM\Mapping\JoinColumnMapping;
|
||||
use Doctrine\ORM\Mapping\OneToOneOwningSideMapping;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
use function assert;
|
||||
@@ -35,4 +37,60 @@ final class OneToOneOwningSideMappingTest extends TestCase
|
||||
self::assertSame(['foo' => 'bar'], $resurrectedMapping->sourceToTargetKeyColumns);
|
||||
self::assertSame(['bar' => 'foo'], $resurrectedMapping->targetToSourceKeyColumns);
|
||||
}
|
||||
|
||||
#[DataProvider('mappingsProvider')]
|
||||
public function testNullableDefaults(bool $expectedValue, OneToOneOwningSideMapping $mapping): void
|
||||
{
|
||||
foreach ($mapping->joinColumns as $joinColumn) {
|
||||
self::assertSame($expectedValue, $joinColumn->nullable);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return iterable<string, array{bool, OneToOneOwningSideMapping}> */
|
||||
public static function mappingsProvider(): iterable
|
||||
{
|
||||
$namingStrategy = new DefaultNamingStrategy();
|
||||
|
||||
yield 'not part of the identifier' => [
|
||||
true,
|
||||
OneToOneOwningSideMapping::fromMappingArrayAndName([
|
||||
'fieldName' => 'foo',
|
||||
'sourceEntity' => self::class,
|
||||
'targetEntity' => self::class,
|
||||
'isOwningSide' => true,
|
||||
'joinColumns' => [
|
||||
['name' => 'foo_id', 'referencedColumnName' => 'id'],
|
||||
],
|
||||
'id' => false,
|
||||
], $namingStrategy, self::class, null, false),
|
||||
];
|
||||
|
||||
yield 'part of the identifier' => [
|
||||
false,
|
||||
OneToOneOwningSideMapping::fromMappingArrayAndName([
|
||||
'fieldName' => 'foo',
|
||||
'sourceEntity' => self::class,
|
||||
'targetEntity' => self::class,
|
||||
'isOwningSide' => true,
|
||||
'joinColumns' => [
|
||||
['name' => 'foo_id', 'referencedColumnName' => 'id'],
|
||||
],
|
||||
'id' => true,
|
||||
], $namingStrategy, self::class, null, false),
|
||||
];
|
||||
|
||||
yield 'part of the identifier, but explicitly marked as nullable' => [
|
||||
false, // user's intent ignored at the ORM level
|
||||
OneToOneOwningSideMapping::fromMappingArrayAndName([
|
||||
'fieldName' => 'foo',
|
||||
'sourceEntity' => self::class,
|
||||
'targetEntity' => self::class,
|
||||
'isOwningSide' => true,
|
||||
'joinColumns' => [
|
||||
['name' => 'foo_id', 'referencedColumnName' => 'id', 'nullable' => true],
|
||||
],
|
||||
'id' => true,
|
||||
], $namingStrategy, self::class, null, false),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,13 @@ use Doctrine\ORM\Mapping\TypedFieldMapper;
|
||||
use Doctrine\Tests\Models\TypedProperties\UserTyped;
|
||||
use Doctrine\Tests\ORM\Mapping\TypedFieldMapper\CustomIntAsStringTypedFieldMapper;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use Generator;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use ReflectionClass;
|
||||
|
||||
use function defined;
|
||||
|
||||
#[Group('GH10313')]
|
||||
class TypedFieldMapperTest extends OrmTestCase
|
||||
{
|
||||
@@ -36,7 +39,7 @@ class TypedFieldMapperTest extends OrmTestCase
|
||||
/**
|
||||
* Data Provider for NamingStrategy#classToTableName
|
||||
*
|
||||
* @return array<
|
||||
* @return Generator<
|
||||
* array{
|
||||
* TypedFieldMapper,
|
||||
* ReflectionClass,
|
||||
@@ -44,28 +47,30 @@ class TypedFieldMapperTest extends OrmTestCase
|
||||
* array{fieldName: string, enumType?: string, type?: mixed}
|
||||
* }>
|
||||
*/
|
||||
public static function dataFieldToMappedField(): array
|
||||
public static function dataFieldToMappedField(): Generator
|
||||
{
|
||||
$reflectionClass = new ReflectionClass(UserTyped::class);
|
||||
|
||||
return [
|
||||
// DefaultTypedFieldMapper
|
||||
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'id'], ['fieldName' => 'id', 'type' => Types::INTEGER]],
|
||||
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'username'], ['fieldName' => 'username', 'type' => Types::STRING]],
|
||||
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'dateInterval'], ['fieldName' => 'dateInterval', 'type' => Types::DATEINTERVAL]],
|
||||
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'dateTime'], ['fieldName' => 'dateTime', 'type' => Types::DATETIME_MUTABLE]],
|
||||
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'dateTimeImmutable'], ['fieldName' => 'dateTimeImmutable', 'type' => Types::DATETIME_IMMUTABLE]],
|
||||
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'array'], ['fieldName' => 'array', 'type' => Types::JSON]],
|
||||
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'boolean'], ['fieldName' => 'boolean', 'type' => Types::BOOLEAN]],
|
||||
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'float'], ['fieldName' => 'float', 'type' => Types::FLOAT]],
|
||||
// DefaultTypedFieldMapper
|
||||
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'id'], ['fieldName' => 'id', 'type' => Types::INTEGER]];
|
||||
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'username'], ['fieldName' => 'username', 'type' => Types::STRING]];
|
||||
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'dateInterval'], ['fieldName' => 'dateInterval', 'type' => Types::DATEINTERVAL]];
|
||||
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'dateTime'], ['fieldName' => 'dateTime', 'type' => Types::DATETIME_MUTABLE]];
|
||||
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'dateTimeImmutable'], ['fieldName' => 'dateTimeImmutable', 'type' => Types::DATETIME_IMMUTABLE]];
|
||||
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'array'], ['fieldName' => 'array', 'type' => Types::JSON]];
|
||||
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'boolean'], ['fieldName' => 'boolean', 'type' => Types::BOOLEAN]];
|
||||
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'float'], ['fieldName' => 'float', 'type' => Types::FLOAT]];
|
||||
|
||||
// CustomIntAsStringTypedFieldMapper
|
||||
[self::customTypedFieldMapper(), $reflectionClass, ['fieldName' => 'id'], ['fieldName' => 'id', 'type' => Types::STRING]],
|
||||
if (defined(Types::class . '::NUMBER')) {
|
||||
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'bodyHeight'], ['fieldName' => 'bodyHeight', 'type' => Types::NUMBER]];
|
||||
}
|
||||
|
||||
// ChainTypedFieldMapper
|
||||
[self::chainTypedFieldMapper(), $reflectionClass, ['fieldName' => 'id'], ['fieldName' => 'id', 'type' => Types::STRING]],
|
||||
[self::chainTypedFieldMapper(), $reflectionClass, ['fieldName' => 'username'], ['fieldName' => 'username', 'type' => Types::STRING]],
|
||||
];
|
||||
// CustomIntAsStringTypedFieldMapper
|
||||
yield [self::customTypedFieldMapper(), $reflectionClass, ['fieldName' => 'id'], ['fieldName' => 'id', 'type' => Types::STRING]];
|
||||
|
||||
// ChainTypedFieldMapper
|
||||
yield [self::chainTypedFieldMapper(), $reflectionClass, ['fieldName' => 'id'], ['fieldName' => 'id', 'type' => Types::STRING]];
|
||||
yield [self::chainTypedFieldMapper(), $reflectionClass, ['fieldName' => 'username'], ['fieldName' => 'username', 'type' => Types::STRING]];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -183,7 +183,7 @@ class XmlMappingDriverTest extends MappingDriverTestCase
|
||||
[
|
||||
User::class,
|
||||
'cms_users',
|
||||
['name', 'email', 'version', 'id'],
|
||||
['name', 'email', 'version', 'indexed', 'id'],
|
||||
['address', 'phonenumbers', 'groups'],
|
||||
],
|
||||
[
|
||||
|
||||
@@ -56,6 +56,8 @@
|
||||
|
||||
<field name="version" type="integer" version="true" />
|
||||
|
||||
<field name="indexed" type="string" index="true" />
|
||||
|
||||
<one-to-one field="address" target-entity="Address" inversed-by="user">
|
||||
<cascade><cascade-remove /></cascade>
|
||||
<join-column name="address_id" referenced-column-name="id" on-delete="CASCADE" />
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM;
|
||||
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\Mapping as MappingNamespace;
|
||||
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
|
||||
@@ -12,6 +13,7 @@ use Doctrine\ORM\ORMSetup;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
|
||||
use PHPUnit\Framework\Attributes\RequiresSetting;
|
||||
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionProperty;
|
||||
use Symfony\Component\Cache\Adapter\AbstractAdapter;
|
||||
@@ -20,10 +22,19 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
|
||||
use function sys_get_temp_dir;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
class ORMSetupTest extends TestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function testAttributeConfiguration(): void
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12005');
|
||||
}
|
||||
|
||||
$config = ORMSetup::createAttributeMetadataConfiguration([], true);
|
||||
|
||||
self::assertInstanceOf(Configuration::class, $config);
|
||||
@@ -32,8 +43,21 @@ class ORMSetupTest extends TestCase
|
||||
self::assertInstanceOf(AttributeDriver::class, $config->getMetadataDriverImpl());
|
||||
}
|
||||
|
||||
public function testAttributeConfig(): void
|
||||
{
|
||||
$config = ORMSetup::createAttributeMetadataConfig([], true);
|
||||
|
||||
self::assertInstanceOf(Configuration::class, $config);
|
||||
self::assertInstanceOf(AttributeDriver::class, $config->getMetadataDriverImpl());
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function testXMLConfiguration(): void
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12005');
|
||||
}
|
||||
|
||||
$config = ORMSetup::createXMLMetadataConfiguration([], true);
|
||||
|
||||
self::assertInstanceOf(Configuration::class, $config);
|
||||
@@ -44,14 +68,18 @@ class ORMSetupTest extends TestCase
|
||||
{
|
||||
$this->expectNotToPerformAssertions();
|
||||
|
||||
ORMSetup::createXMLMetadataConfiguration(paths: [], isXsdValidationEnabled: false);
|
||||
ORMSetup::createXMLMetadataConfig(paths: [], isXsdValidationEnabled: false);
|
||||
}
|
||||
|
||||
#[RequiresPhpExtension('apcu')]
|
||||
#[RequiresSetting('apc.enable_cli', '1')]
|
||||
#[RequiresSetting('apc.enabled', '1')]
|
||||
public function testCacheNamespaceShouldBeGeneratedForApcu(): void
|
||||
public function testCacheNamespaceShouldBeGeneratedForApcuWhenUsingLegacyConstructor(): void
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12005');
|
||||
}
|
||||
|
||||
$config = ORMSetup::createConfiguration(false, '/foo');
|
||||
$cache = $config->getMetadataCache();
|
||||
|
||||
@@ -61,7 +89,22 @@ class ORMSetupTest extends TestCase
|
||||
self::assertSame('dc2_1effb2475fcfba4f9e8b8a1dbc8f3caf:', $namespaceProperty->getValue($cache));
|
||||
}
|
||||
|
||||
#[RequiresPhpExtension('apcu')]
|
||||
#[RequiresSetting('apc.enable_cli', '1')]
|
||||
#[RequiresSetting('apc.enabled', '1')]
|
||||
public function testCacheNamespaceShouldBeGeneratedForApcu(): void
|
||||
{
|
||||
$config = ORMSetup::createConfig(false, '/foo');
|
||||
$cache = $config->getMetadataCache();
|
||||
|
||||
$namespaceProperty = new ReflectionProperty(AbstractAdapter::class, 'namespace');
|
||||
|
||||
self::assertInstanceOf(ApcuAdapter::class, $cache);
|
||||
self::assertSame('dc2_1effb2475fcfba4f9e8b8a1dbc8f3caf:', $namespaceProperty->getValue($cache));
|
||||
}
|
||||
|
||||
#[Group('DDC-1350')]
|
||||
#[WithoutErrorHandler]
|
||||
public function testConfigureProxyDir(): void
|
||||
{
|
||||
$config = ORMSetup::createAttributeMetadataConfiguration([], true, '/foo');
|
||||
@@ -72,7 +115,7 @@ class ORMSetupTest extends TestCase
|
||||
public function testConfigureCache(): void
|
||||
{
|
||||
$cache = new ArrayAdapter();
|
||||
$config = ORMSetup::createAttributeMetadataConfiguration([], true, null, $cache);
|
||||
$config = ORMSetup::createAttributeMetadataConfig([], true, null, $cache);
|
||||
|
||||
self::assertSame($cache, $config->getResultCache());
|
||||
self::assertSame($cache, $config->getQueryCache());
|
||||
@@ -83,7 +126,7 @@ class ORMSetupTest extends TestCase
|
||||
public function testConfigureCacheCustomInstance(): void
|
||||
{
|
||||
$cache = new ArrayAdapter();
|
||||
$config = ORMSetup::createConfiguration(true, null, $cache);
|
||||
$config = ORMSetup::createConfig(true, null, $cache);
|
||||
|
||||
self::assertSame($cache, $config->getResultCache());
|
||||
self::assertSame($cache, $config->getQueryCache());
|
||||
|
||||
85
tests/Tests/ORM/Persisters/BinaryIdPersisterTest.php
Normal file
85
tests/Tests/ORM/Persisters/BinaryIdPersisterTest.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Persisters;
|
||||
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\Types\Type as DbalType;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Doctrine\ORM\Tools\SchemaValidator;
|
||||
use Doctrine\Tests\Mocks\EntityManagerMock;
|
||||
use Doctrine\Tests\Models\BinaryPrimaryKey\BinaryIdType;
|
||||
use Doctrine\Tests\Models\BinaryPrimaryKey\Category;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
|
||||
final class BinaryIdPersisterTest extends OrmTestCase
|
||||
{
|
||||
private EntityManager|null $entityManager = null;
|
||||
|
||||
public function testOneToManyWithEagerFetchMode(): void
|
||||
{
|
||||
$entityManager = $this->createEntityManager();
|
||||
|
||||
$this->createDummyBlogData($entityManager, 3);
|
||||
|
||||
$categories = $entityManager->createQueryBuilder()
|
||||
->select('category')
|
||||
->from(Category::class, 'category')
|
||||
->getQuery()
|
||||
->setFetchMode(Category::class, 'children', ClassMetadata::FETCH_EAGER)
|
||||
->getResult();
|
||||
|
||||
self::assertCount(3, $categories);
|
||||
}
|
||||
|
||||
private function createDummyBlogData(
|
||||
EntityManager $entityManager,
|
||||
int $categoryCount = 1,
|
||||
int $categoryParentsCount = 0,
|
||||
): void {
|
||||
for ($h = 0; $h < $categoryCount; $h++) {
|
||||
$categoryParent = null;
|
||||
|
||||
for ($i = 0; $i < $categoryParentsCount; $i++) {
|
||||
$categoryParent = new Category('CategoryParent#' . $i, $categoryParent);
|
||||
$entityManager->persist($categoryParent);
|
||||
}
|
||||
|
||||
$category = new Category('Category#' . $h, $categoryParent);
|
||||
$entityManager->persist($category);
|
||||
}
|
||||
|
||||
$entityManager->flush();
|
||||
$entityManager->clear();
|
||||
}
|
||||
|
||||
private function createEntityManager(): EntityManager
|
||||
{
|
||||
if ($this->entityManager !== null) {
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
$config = ORMSetup::createAttributeMetadataConfiguration([__DIR__ . '/../../Models/BinaryPrimaryKey'], isDevMode: true);
|
||||
|
||||
if (! DbalType::hasType(BinaryIdType::NAME)) {
|
||||
DbalType::addType(BinaryIdType::NAME, BinaryIdType::class);
|
||||
}
|
||||
|
||||
$connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true], $config);
|
||||
$entityManager = new EntityManagerMock($connection, $config);
|
||||
|
||||
$schemaTool = new SchemaTool($entityManager);
|
||||
$schemaTool->createSchema($entityManager->getMetadataFactory()->getAllMetadata());
|
||||
|
||||
$schemaValidator = new SchemaValidator($entityManager);
|
||||
$schemaValidator->validateMapping();
|
||||
|
||||
$this->entityManager = $entityManager;
|
||||
|
||||
return $entityManager;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ namespace Doctrine\Tests\ORM\Proxy;
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\EntityNotFoundException;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
|
||||
@@ -21,6 +22,7 @@ use Doctrine\Tests\Models\ECommerce\ECommerceFeature;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\RequiresPhp;
|
||||
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
use stdClass;
|
||||
@@ -34,10 +36,10 @@ use function sys_get_temp_dir;
|
||||
*/
|
||||
class ProxyFactoryTest extends OrmTestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
private UnitOfWorkMock $uowMock;
|
||||
|
||||
private EntityManagerMock $emMock;
|
||||
|
||||
private ProxyFactory $proxyFactory;
|
||||
|
||||
protected function setUp(): void
|
||||
@@ -243,6 +245,37 @@ class ProxyFactoryTest extends OrmTestCase
|
||||
|
||||
self::assertTrue($reflection->isUninitializedLazyObject($proxy));
|
||||
}
|
||||
|
||||
#[RequiresPhp('8.4')]
|
||||
#[WithoutErrorHandler]
|
||||
public function testProxyFactoryTriggersDeprecationWhenNativeLazyObjectsAreDisabled(): void
|
||||
{
|
||||
$this->emMock->getConfiguration()->enableNativeLazyObjects(false);
|
||||
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12005');
|
||||
|
||||
$this->proxyFactory = new ProxyFactory(
|
||||
$this->emMock,
|
||||
sys_get_temp_dir(),
|
||||
'Proxies',
|
||||
ProxyFactory::AUTOGENERATE_ALWAYS,
|
||||
);
|
||||
}
|
||||
|
||||
#[RequiresPhp('< 8.4')]
|
||||
public function testProxyFactoryDoesNotTriggerDeprecationWhenNativeLazyObjectsAreDisabled(): void
|
||||
{
|
||||
$this->emMock->getConfiguration()->enableNativeLazyObjects(false);
|
||||
|
||||
$this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12005');
|
||||
|
||||
$this->proxyFactory = new ProxyFactory(
|
||||
$this->emMock,
|
||||
sys_get_temp_dir(),
|
||||
'Proxies',
|
||||
ProxyFactory::AUTOGENERATE_ALWAYS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractClass
|
||||
|
||||
@@ -452,6 +452,8 @@ class SelectSqlGenerationTest extends OrmTestCase
|
||||
|
||||
public function testSupportsTrimFunction(): void
|
||||
{
|
||||
$this->entityManager = $this->createTestEntityManagerWithPlatform(new MySQLPlatform());
|
||||
|
||||
$this->assertSqlGeneration(
|
||||
"SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE TRIM(TRAILING ' ' FROM u.name) = 'someone'",
|
||||
"SELECT c0_.name AS name_0 FROM cms_users c0_ WHERE TRIM(TRAILING ' ' FROM c0_.name) = 'someone'",
|
||||
@@ -461,6 +463,8 @@ class SelectSqlGenerationTest extends OrmTestCase
|
||||
#[Group('DDC-2668')]
|
||||
public function testSupportsTrimLeadingZeroString(): void
|
||||
{
|
||||
$this->entityManager = $this->createTestEntityManagerWithPlatform(new MySQLPlatform());
|
||||
|
||||
$this->assertSqlGeneration(
|
||||
"SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE TRIM(TRAILING '0' FROM u.name) != ''",
|
||||
"SELECT c0_.name AS name_0 FROM cms_users c0_ WHERE TRIM(TRAILING '0' FROM c0_.name) <> ''",
|
||||
@@ -584,6 +588,8 @@ class SelectSqlGenerationTest extends OrmTestCase
|
||||
#[Group('DDC-1802')]
|
||||
public function testSupportsNotInExpressionForModFunction(): void
|
||||
{
|
||||
$this->entityManager = $this->createTestEntityManagerWithPlatform(new MySQLPlatform());
|
||||
|
||||
$this->assertSqlGeneration(
|
||||
'SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE MOD(u.id, 5) NOT IN(1,3,4)',
|
||||
'SELECT c0_.name AS name_0 FROM cms_users c0_ WHERE MOD(c0_.id, 5) NOT IN (1, 3, 4)',
|
||||
@@ -1925,6 +1931,8 @@ SQL,
|
||||
#[Group('DDC-2268')]
|
||||
public function testCaseThenFunction(): void
|
||||
{
|
||||
$this->entityManager = $this->createTestEntityManagerWithPlatform(new PostgreSQLPlatform());
|
||||
|
||||
$this->assertSqlGeneration(
|
||||
'SELECT CASE WHEN LENGTH(u.name) <> 0 THEN CONCAT(u.id, u.name) ELSE u.id END AS name FROM Doctrine\Tests\Models\CMS\CmsUser u',
|
||||
'SELECT CASE WHEN LENGTH(c0_.name) <> 0 THEN c0_.id || c0_.name ELSE c0_.id END AS sclr_0 FROM cms_users c0_',
|
||||
|
||||
@@ -11,7 +11,9 @@ use Doctrine\Tests\Models\Cache\AttractionInfo;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandCompletionTester;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
/**
|
||||
@@ -77,4 +79,37 @@ class MappingDescribeCommandTest extends OrmFunctionalTestCase
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $input
|
||||
* @param string[] $expectedSuggestions
|
||||
*/
|
||||
#[DataProvider('provideCompletionSuggestions')]
|
||||
public function testComplete(array $input, array $expectedSuggestions): void
|
||||
{
|
||||
$this->useModelSet('cache');
|
||||
|
||||
parent::setUp();
|
||||
|
||||
$completionTester = new CommandCompletionTester(new MappingDescribeCommand(new SingleManagerProvider($this->_em)));
|
||||
|
||||
$suggestions = $completionTester->complete($input);
|
||||
|
||||
foreach ($expectedSuggestions as $expected) {
|
||||
self::assertContains($expected, $suggestions);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return iterable<string, array{string[], string[]}> */
|
||||
public static function provideCompletionSuggestions(): iterable
|
||||
{
|
||||
yield 'entityName' => [
|
||||
[''],
|
||||
[
|
||||
'Doctrine\\\\Tests\\\\Models\\\\Cache\\\\Restaurant',
|
||||
'Doctrine\\\\Tests\\\\Models\\\\Cache\\\\Beach',
|
||||
'Doctrine\\\\Tests\\\\Models\\\\Cache\\\\Bar',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ class SchemaValidatorTest extends OrmTestCase
|
||||
[
|
||||
'The field Doctrine\Tests\ORM\Tools\DDC3274One#two is on the inverse side of a bi-directional ' .
|
||||
'relationship, but the specified mappedBy association on the target-entity ' .
|
||||
"Doctrine\Tests\ORM\Tools\DDC3274Two#one does not contain the required 'inversedBy=\"two\"' attribute.",
|
||||
"Doctrine\Tests\ORM\Tools\DDC3274Two#one does not contain the required 'inversedBy: \"two\"' attribute.",
|
||||
],
|
||||
$ce,
|
||||
);
|
||||
@@ -151,7 +151,7 @@ class SchemaValidatorTest extends OrmTestCase
|
||||
[
|
||||
'The field Doctrine\Tests\ORM\Tools\Issue9536Owner#one is on the owning side of a bi-directional ' .
|
||||
'relationship, but the specified inversedBy association on the target-entity ' .
|
||||
"Doctrine\Tests\ORM\Tools\Issue9536Target#two does not contain the required 'mappedBy=\"one\"' " .
|
||||
"Doctrine\Tests\ORM\Tools\Issue9536Target#two does not contain the required 'mappedBy: \"one\"' " .
|
||||
'attribute.',
|
||||
],
|
||||
$ce,
|
||||
|
||||
@@ -33,6 +33,7 @@ use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\Models\Forum\ForumAvatar;
|
||||
use Doctrine\Tests\Models\Forum\ForumUser;
|
||||
use Doctrine\Tests\Models\MixedToOneIdentity\Country;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use Exception as BaseException;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
@@ -143,6 +144,25 @@ class UnitOfWorkTest extends OrmTestCase
|
||||
self::assertIsNumeric($user->id);
|
||||
}
|
||||
|
||||
#[Group('#11977')]
|
||||
public function testMultipleInsertsAreBatchedInThePersister(): void
|
||||
{
|
||||
$userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(Country::class));
|
||||
$this->_unitOfWork->setEntityPersister(Country::class, $userPersister);
|
||||
|
||||
$country1 = new Country();
|
||||
$country1->country = 'Italy';
|
||||
$country2 = new Country();
|
||||
$country2->country = 'Germany';
|
||||
|
||||
$this->_unitOfWork->persist($country1);
|
||||
$this->_unitOfWork->persist($country2);
|
||||
$this->_unitOfWork->commit();
|
||||
|
||||
self::assertCount(2, $userPersister->getInserts());
|
||||
self::assertSame(1, $userPersister->countOfExecuteInsertCalls());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a scenario where a save() operation is cascaded from a ForumUser
|
||||
* to its associated ForumAvatar, both entities using IDENTITY id generation.
|
||||
|
||||
@@ -8,9 +8,7 @@ use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Driver\Result;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Schema\AbstractSchemaManager;
|
||||
use Doctrine\DBAL\Schema\Name\UnquotedIdentifierFolding;
|
||||
use Doctrine\DBAL\Schema\SchemaConfig;
|
||||
use Doctrine\DBAL\Platforms\SQLitePlatform;
|
||||
use Doctrine\ORM\Cache\CacheConfiguration;
|
||||
use Doctrine\ORM\Cache\CacheFactory;
|
||||
use Doctrine\ORM\Cache\DefaultCacheFactory;
|
||||
@@ -22,11 +20,14 @@ use PHPUnit\Framework\TestCase;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
|
||||
use function enum_exists;
|
||||
use function class_exists;
|
||||
use function method_exists;
|
||||
use function realpath;
|
||||
use function sprintf;
|
||||
|
||||
// DBAL 3 compatibility
|
||||
class_exists('Doctrine\\DBAL\\Platforms\\SqlitePlatform');
|
||||
|
||||
/**
|
||||
* Base testcase class for all ORM testcases.
|
||||
*/
|
||||
@@ -71,9 +72,7 @@ abstract class OrmTestCase extends TestCase
|
||||
*/
|
||||
protected function getTestEntityManager(): EntityManagerMock
|
||||
{
|
||||
return $this->buildTestEntityManagerWithPlatform(
|
||||
$this->createConnectionMock($this->createPlatformMock()),
|
||||
);
|
||||
return $this->createTestEntityManagerWithPlatform(new SQLitePlatform());
|
||||
}
|
||||
|
||||
protected function createTestEntityManagerWithConnection(Connection $connection): EntityManagerMock
|
||||
@@ -153,24 +152,6 @@ abstract class OrmTestCase extends TestCase
|
||||
return $connection;
|
||||
}
|
||||
|
||||
private function createPlatformMock(): AbstractPlatform
|
||||
{
|
||||
$schemaManager = $this->createMock(AbstractSchemaManager::class);
|
||||
$schemaManager->method('createSchemaConfig')
|
||||
->willReturn(new SchemaConfig());
|
||||
|
||||
$platform = $this->getMockBuilder(AbstractPlatform::class)
|
||||
->setConstructorArgs(enum_exists(UnquotedIdentifierFolding::class) ? [UnquotedIdentifierFolding::UPPER] : [])
|
||||
->onlyMethods(['supportsIdentityColumns', 'createSchemaManager'])
|
||||
->getMockForAbstractClass();
|
||||
$platform->method('supportsIdentityColumns')
|
||||
->willReturn(true);
|
||||
$platform->method('createSchemaManager')
|
||||
->willReturn($schemaManager);
|
||||
|
||||
return $platform;
|
||||
}
|
||||
|
||||
private function createDriverMock(AbstractPlatform $platform): Driver
|
||||
{
|
||||
$result = $this->createMock(Result::class);
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Doctrine\Tests\Proxy;
|
||||
|
||||
use Doctrine\ORM\Proxy\Autoloader;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
use function class_exists;
|
||||
@@ -31,6 +32,7 @@ class AutoloaderTest extends TestCase
|
||||
|
||||
/** @param class-string $className */
|
||||
#[DataProvider('dataResolveFile')]
|
||||
#[WithoutErrorHandler]
|
||||
public function testResolveFile(
|
||||
string $proxyDir,
|
||||
string $proxyNamespace,
|
||||
@@ -41,6 +43,7 @@ class AutoloaderTest extends TestCase
|
||||
self::assertEquals($expectedProxyFile, $actualProxyFile);
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
public function testAutoload(): void
|
||||
{
|
||||
if (file_exists(sys_get_temp_dir() . '/AutoloaderTestClass.php')) {
|
||||
|
||||
@@ -100,14 +100,14 @@ class TestUtil
|
||||
$enableNativeLazyObjects = true;
|
||||
}
|
||||
|
||||
$configuration->setProxyDir(__DIR__ . '/Proxies');
|
||||
$configuration->setProxyNamespace('Doctrine\Tests\Proxies');
|
||||
|
||||
if (PHP_VERSION_ID >= 80400 && $enableNativeLazyObjects) {
|
||||
$configuration->enableNativeLazyObjects(true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$configuration->setProxyDir(__DIR__ . '/Proxies');
|
||||
$configuration->setProxyNamespace('Doctrine\Tests\Proxies');
|
||||
}
|
||||
|
||||
private static function initializeDatabase(): void
|
||||
|
||||
Reference in New Issue
Block a user