Compare commits

..

33 Commits
3.2.1 ... 3.2.3

Author SHA1 Message Date
Grégoire Paris
522863116a Update branch metadata (#11663)
2.20.0 just got released
2024-10-11 20:23:33 +02:00
Alexander M. Turek
b44774285b Merge branch '2.19.x' into 3.2.x
* 2.19.x:
  PHPStan 1.12.6 (#11635)
2024-10-09 11:09:19 +02:00
Alexander M. Turek
bc37f75b41 PHPStan 1.12.6 (#11635) 2024-10-09 11:08:02 +02:00
Grégoire Paris
191a5366b1 Merge pull request #11629 from greg0ire/3.2.x
Merge 2.19.x up into 3.2.x
2024-10-08 17:57:30 +02:00
Grégoire Paris
44dddb2eee Merge remote-tracking branch 'origin/2.19.x' into 3.2.x 2024-10-08 15:37:53 +02:00
Grégoire Paris
0c0c61c51b Merge pull request #11627 from greg0ire/no-custom-directives
Replace custom directives with native option
2024-10-08 15:26:44 +02:00
Grégoire Paris
cc28fed9f5 Replace custom directives with native option 2024-10-08 14:43:18 +02:00
Alexander M. Turek
b13564c6c0 Make nullable parameters explicit in generated entities (#11625) 2024-10-08 12:25:31 +02:00
Grégoire Paris
d18126aac5 Merge pull request #11618 from n0099/patch-1
unclosed `]` in attributes-reference.rst
2024-10-01 17:27:04 +02:00
n0099
b7fd8241cf Update attributes-reference.rst 2024-10-01 21:19:44 +08:00
dependabot[bot]
2432939e4f Bump doctrine/.github from 5.0.1 to 5.1.0 (#11616)
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 5.0.1 to 5.1.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/5.0.1...5.1.0)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 09:04:06 +02:00
Grégoire Paris
93ce84fa6e Merge pull request #11614 from greg0ire/guides-as-dep
Guides as dev dependency
2024-09-29 09:23:14 +02:00
Grégoire Paris
1bf4603422 Merge pull request #11615 from greg0ire/move-orphan
Move orphan metadata to where it belongs
2024-09-29 09:22:54 +02:00
Grégoire Paris
e6961bd968 Install guides-cli as a dev requirement
It is better if contributors can check the docs by themselves.
2024-09-27 19:44:34 +02:00
Grégoire Paris
25d5bc5b46 Move orphan metadata to where it belongs
The goal here was to retain compatibility with doctrine/rst-parser,
which is no longer in use in the website.
2024-09-27 19:42:13 +02:00
Alexander M. Turek
cfc0655a1c Fix compatibility with DBAL 4.2 (#11592) 2024-09-03 18:44:06 +02:00
Alexander M. Turek
6cde337777 PHPStan 1.12 (#11585) 2024-08-27 12:10:07 +02:00
Grégoire Paris
831a1eb7d2 Merge pull request #11581 from greg0ire/3.2.x
Merge 2.19.x up into 3.2.x
2024-08-23 12:03:52 +02:00
Grégoire Paris
3a82b153f3 Merge remote-tracking branch 'origin/2.19.x' into 3.2.x 2024-08-23 09:07:18 +02:00
Grégoire Paris
168ac31084 Merge pull request #11109 from mcurland/Fix11108
Original entity data resolves inverse 1-1 joins
2024-08-23 08:54:57 +02:00
Matthew Curland
fe4a2e83cf Original entity data resolves inverse 1-1 joins
If the source entity for an inverse (non-owning) 1-1 relationship is
identified by an association then the identifying association may not
be set when an inverse one-to-one association is resolved. This means
that no data is available in the entity to resolve the needed column
value for the join query.

The original entity data can be retrieved from the unit of work and
is used as a fallback to populate the query condition.

Fixes #11108
2024-08-17 11:50:56 +02:00
Grégoire Paris
205b2f5f20 Merge pull request #11550 from janedbal/patch-1
DQL custom functions: document TypedExpression
2024-08-09 22:50:14 +02:00
Jan Nedbal
3f550c19e3 DQL custom functions: document TypedExpression
Partially related to https://github.com/doctrine/orm/issues/11537

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>
2024-08-06 09:16:45 +02:00
Grégoire Paris
8ac6a13ca0 Merge pull request #11564 from gitbugr/GH11501_fix_o2m_persister_single_inheritence_parent_relation_bugfix
GH11551 - fix OneToManyPersister::deleteEntityCollection case where single-inheritence table parent entity is targetEntity.
2024-08-05 07:47:46 +02:00
gitbugr
2707b09a07 fix spacing
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2024-08-03 21:38:49 +01:00
Kyron Taylor
121158f92c GH11551 - fix OneToManyPersister::deleteEntityCollection when using
single-inheritence entity parent as targetEntity.

When using the parent entity for a single-inheritence table as the
targetEntity for a property, the discriminator value should be all
of the values in the discriminator map.
OneToManyPersister::deleteEntityCollection has been amended to
reflect this.
2024-08-03 16:55:14 +01:00
Grégoire Paris
51ad860a25 Merge pull request #11543 from stof/fix_native_query_parameter_type
Fix the support for custom parameter types in native queries
2024-07-04 20:12:59 +02:00
Christophe Coevoet
9bd51aaeb6 Fix the support for custom parameter types in native queries
The Query class (used for DQL queries) takes care of using the value and
type as is when a type was specified for a parameter instead of going
through the default processing of values.
The NativeQuery class was missing the equivalent check, making the
custom type work only if the default processing of values does not
convert the value to a different one.
2024-07-04 16:25:34 +02:00
Xesau
1fe1a6a048 Fix incorrect exception message for ManyToOne attribute in embeddable class (#11536)
When a ManyToOne attribute is encountered on an Embeddable class, the exception message reads "Attribute "Doctrine\ORM\Mapping\OneToMany" on embeddable [class] is not allowed.". This should be "Doctrine\ORM\Mapping\ManyToOne" on embeddable [class] is not allowed.".
2024-07-01 21:57:36 +02:00
Grégoire Paris
c37b115450 Merge pull request #11534 from k00ni/patch-1
working-with-objects.rst: added missing white space
2024-06-28 09:03:54 +02:00
Konrad Abicht
19129e9f8a working-with-objects.rst: added missing white space 2024-06-28 09:00:12 +02:00
Grégoire Paris
c1bb2ccf4b Merge pull request #11526 from GromNaN/patch-1
doc: Use modern array syntax in getting started
2024-06-26 19:24:40 +02:00
Jérôme Tamarelle
e3d7c6076c Use modern array syntax in the doc 2024-06-26 19:18:32 +02:00
34 changed files with 536 additions and 181 deletions

View File

@@ -35,17 +35,23 @@
"slug": "3.0",
"maintained": false
},
{
"name": "2.21",
"branchName": "2.21.x",
"slug": "2.21",
"upcoming": true
},
{
"name": "2.20",
"branchName": "2.20.x",
"slug": "2.20",
"upcoming": true
"maintained": true
},
{
"name": "2.19",
"branchName": "2.19.x",
"slug": "2.19",
"maintained": true
"maintained": false
},
{
"name": "2.18",

View File

@@ -24,4 +24,4 @@ on:
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@5.0.1"
uses: "doctrine/.github/.github/workflows/coding-standards.yml@5.1.0"

View File

@@ -5,45 +5,16 @@ on:
branches:
- "*.x"
paths:
- .github/workflows/documentation.yml
- docs/**
- ".github/workflows/documentation.yml"
- "docs/**"
push:
branches:
- "*.x"
paths:
- .github/workflows/documentation.yml
- docs/**
- ".github/workflows/documentation.yml"
- "docs/**"
jobs:
validate-with-guides:
name: "Validate documentation with phpDocumentor/guides"
runs-on: "ubuntu-22.04"
steps:
- name: "Checkout code"
uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.3"
- name: "Remove existing composer file"
run: "rm composer.json"
- name: "Require phpdocumentor/guides-cli"
run: "composer require --dev phpdocumentor/guides-cli --no-update"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
dependency-versions: "highest"
- name: "Add orphan metadata where needed"
run: |
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/sidebar.rst)" > docs/en/sidebar.rst
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/reference/installation.rst)" > docs/en/reference/installation.rst
- name: "Run guides-cli"
run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'No template found for rendering directive' | ( ! grep WARNING )"
documentation:
name: "Documentation"
uses: "doctrine/.github/.github/workflows/documentation.yml@5.1.0"

View File

@@ -7,7 +7,7 @@ on:
jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@5.0.1"
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@5.1.0"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}

View File

@@ -38,7 +38,8 @@
"require-dev": {
"doctrine/coding-standard": "^12.0",
"phpbench/phpbench": "^1.0",
"phpstan/phpstan": "1.11.1",
"phpdocumentor/guides-cli": "^1.4",
"phpstan/phpstan": "1.12.6",
"phpunit/phpunit": "^10.4.0",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.2",

View File

@@ -232,6 +232,33 @@ vendors SQL parser to show us further errors in the parsing
process, for example if the Unit would not be one of the supported
values by MySql.
Typed functions
---------------
By default, result of custom functions is fetched as-is from the database driver.
If you want to be sure that the type is always the same, then your custom function needs to
implement ``Doctrine\ORM\Query\AST\TypedExpression``. Then, the result is wired
through ``Doctrine\DBAL\Types\Type::convertToPhpValue()`` of the ``Type`` returned in ``getReturnType()``.
.. code-block:: php
<?php
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\TypedExpression;
class DateDiff extends FunctionNode implements TypedExpression
{
// ...
public function getReturnType(): Type
{
return Type::getType(Types::INTEGER);
}
}
Conclusion
----------

View File

@@ -15,7 +15,7 @@ Index
- :ref:`#[AttributeOverride] <attrref_attributeoverride>`
- :ref:`#[Column] <attrref_column>`
- :ref:`#[Cache] <attrref_cache>`
- :ref:`#[ChangeTrackingPolicy <attrref_changetrackingpolicy>`
- :ref:`#[ChangeTrackingPolicy] <attrref_changetrackingpolicy>`
- :ref:`#[CustomIdGenerator] <attrref_customidgenerator>`
- :ref:`#[DiscriminatorColumn] <attrref_discriminatorcolumn>`
- :ref:`#[DiscriminatorMap] <attrref_discriminatormap>`

View File

@@ -1,3 +1,5 @@
:orphan:
Installation
============

View File

@@ -338,10 +338,11 @@ Performance of different deletion strategies
Deleting an object with all its associated objects can be achieved
in multiple ways with very different performance impacts.
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine ORM
will fetch this association. If its a Single association it will
pass this entity to
``EntityManager#remove()``. If the association is a collection, Doctrine will loop over all its elements and pass them to``EntityManager#remove()``.
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine ORM will
fetch this association. If it's a Single association it will pass
this entity to ``EntityManager#remove()``. If the association is a
collection, Doctrine will loop over all its elements and pass them to
``EntityManager#remove()``.
In both cases the cascade remove semantics are applied recursively.
For large object graphs this removal strategy can be very costly.
2. Using a DQL ``DELETE`` statement allows you to delete multiple

View File

@@ -1,80 +1,73 @@
.. toc::
:orphan:
.. tocheader:: Tutorials
.. toctree::
:caption: Tutorials
:depth: 3
.. toctree::
:depth: 3
tutorials/getting-started
tutorials/getting-started-database
tutorials/getting-started-models
tutorials/working-with-indexed-associations
tutorials/extra-lazy-associations
tutorials/composite-primary-keys
tutorials/ordered-associations
tutorials/override-field-association-mappings-in-subclasses
tutorials/pagination
tutorials/embeddables
tutorials/getting-started
tutorials/getting-started-database
tutorials/getting-started-models
tutorials/working-with-indexed-associations
tutorials/extra-lazy-associations
tutorials/composite-primary-keys
tutorials/ordered-associations
tutorials/override-field-association-mappings-in-subclasses
tutorials/pagination
tutorials/embeddables
.. toctree::
:caption: Reference
:depth: 3
.. toc::
reference/architecture
reference/configuration
reference/faq
reference/basic-mapping
reference/association-mapping
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/typedfieldmapper
reference/events
reference/unitofwork
reference/unitofwork-associations
reference/transactions-and-concurrency
reference/batch-processing
reference/dql-doctrine-query-language
reference/query-builder
reference/native-sql
reference/change-tracking-policies
reference/attributes-reference
reference/xml-mapping
reference/php-mapping
reference/caching
reference/improving-performance
reference/tools
reference/metadata-drivers
reference/best-practices
reference/limitations-and-known-issues
tutorials/pagination
reference/filters
reference/namingstrategy
reference/advanced-configuration
reference/second-level-cache
reference/security
.. tocheader:: Reference
.. toctree::
:caption: Cookbook
:depth: 3
.. toctree::
:depth: 3
reference/architecture
reference/configuration
reference/faq
reference/basic-mapping
reference/association-mapping
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/typedfieldmapper
reference/events
reference/unitofwork
reference/unitofwork-associations
reference/transactions-and-concurrency
reference/batch-processing
reference/dql-doctrine-query-language
reference/query-builder
reference/native-sql
reference/change-tracking-policies
reference/attributes-reference
reference/xml-mapping
reference/php-mapping
reference/caching
reference/improving-performance
reference/tools
reference/metadata-drivers
reference/best-practices
reference/limitations-and-known-issues
tutorials/pagination
reference/filters
reference/namingstrategy
reference/advanced-configuration
reference/second-level-cache
reference/security
.. toc::
.. tocheader:: Cookbook
.. toctree::
:depth: 3
cookbook/aggregate-fields
cookbook/custom-mapping-types
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction
cookbook/validation-of-entities
cookbook/working-with-datetime
cookbook/mysql-enums
cookbook/advanced-field-value-conversion-using-custom-mapping-types
cookbook/entities-in-session
cookbook/aggregate-fields
cookbook/custom-mapping-types
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction
cookbook/validation-of-entities
cookbook/working-with-datetime
cookbook/mysql-enums
cookbook/advanced-field-value-conversion-using-custom-mapping-types
cookbook/entities-in-session

View File

@@ -139,12 +139,12 @@ step:
// Create a simple "default" Doctrine ORM configuration for Attributes
$config = ORMSetup::createAttributeMetadataConfiguration(
paths: array(__DIR__."/src"),
paths: [__DIR__ . '/src'],
isDevMode: true,
);
// or if you prefer XML
// $config = ORMSetup::createXMLMetadataConfiguration(
// paths: array(__DIR__."/config/xml"),
// paths: [__DIR__ . '/config/xml'],
// isDevMode: true,
//);

View File

@@ -390,7 +390,7 @@ class AttributeDriver implements MappingDriver
$metadata->mapOneToMany($mapping);
} elseif ($manyToOneAttribute !== null) {
if ($metadata->isEmbeddedClass) {
throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToMany::class);
throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\ManyToOne::class);
}
$idAttribute = $this->reader->getPropertyAttribute($property, Mapping\Id::class);

View File

@@ -40,7 +40,15 @@ class NativeQuery extends AbstractQuery
$types = [];
foreach ($this->getParameters() as $parameter) {
$name = $parameter->getName();
$name = $parameter->getName();
if ($parameter->typeWasSpecified()) {
$parameters[$name] = $parameter->getValue();
$types[$name] = $parameter->getType();
continue;
}
$value = $this->processParameterValue($parameter->getValue());
$type = $parameter->getValue() === $value
? $parameter->getType()

View File

@@ -14,9 +14,12 @@ use Doctrine\ORM\Mapping\OneToManyAssociationMapping;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Utility\PersisterHelper;
use function array_fill;
use function array_keys;
use function array_reverse;
use function array_values;
use function assert;
use function count;
use function implode;
use function is_int;
use function is_string;
@@ -174,9 +177,12 @@ class OneToManyPersister extends AbstractCollectionPersister
if ($targetClass->isInheritanceTypeSingleTable()) {
$discriminatorColumn = $targetClass->getDiscriminatorColumn();
$statement .= ' AND ' . $discriminatorColumn->name . ' = ?';
$parameters[] = $targetClass->discriminatorValue;
$types[] = $discriminatorColumn->type;
$discriminatorValues = $targetClass->discriminatorValue ? [$targetClass->discriminatorValue] : array_keys($targetClass->discriminatorMap);
$statement .= ' AND ' . $discriminatorColumn->name . ' IN (' . implode(', ', array_fill(0, count($discriminatorValues), '?')) . ')';
foreach ($discriminatorValues as $discriminatorValue) {
$parameters[] = $discriminatorValue;
$types[] = $discriminatorColumn->type;
}
}
$numAffected = $this->conn->executeStatement($statement, $parameters, $types);

View File

@@ -792,17 +792,42 @@ class BasicEntityPersister implements EntityPersister
$computedIdentifier = [];
/** @var array<string,mixed>|null $sourceEntityData */
$sourceEntityData = null;
// TRICKY: since the association is specular source and target are flipped
foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) {
if (! isset($sourceClass->fieldNames[$sourceKeyColumn])) {
throw MappingException::joinColumnMustPointToMappedField(
$sourceClass->name,
$sourceKeyColumn,
);
}
// The likely case here is that the column is a join column
// in an association mapping. However, there is no guarantee
// at this point that a corresponding (generally identifying)
// association has been mapped in the source entity. To handle
// this case we directly reference the column-keyed data used
// to initialize the source entity before throwing an exception.
$resolvedSourceData = false;
if (! isset($sourceEntityData)) {
$sourceEntityData = $this->em->getUnitOfWork()->getOriginalEntityData($sourceEntity);
}
$computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] =
$sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
if (isset($sourceEntityData[$sourceKeyColumn])) {
$dataValue = $sourceEntityData[$sourceKeyColumn];
if ($dataValue !== null) {
$resolvedSourceData = true;
$computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] =
$dataValue;
}
}
if (! $resolvedSourceData) {
throw MappingException::joinColumnMustPointToMappedField(
$sourceClass->name,
$sourceKeyColumn,
);
}
} else {
$computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] =
$sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
}
}
$targetEntity = $this->load($computedIdentifier, null, $assoc);

View File

@@ -1,18 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\Performance;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\PDO\SQLite\Driver;
use Doctrine\DBAL\Result;
final class ArrayResultFactory
{
public static function createFromArray(array $resultSet): Result
{
return new Result(new ArrayResult($resultSet), new Connection([], new Driver()));
}
}

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\Performance;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\PDO\SQLite\Driver;
@@ -17,6 +16,7 @@ use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\TestUtil;
use function array_map;
@@ -67,7 +67,7 @@ final class EntityManagerFactory
/** {@inheritDoc} */
public function executeQuery(string $sql, array $params = [], $types = [], QueryCacheProfile|null $qcp = null): Result
{
return new Result(new ArrayResult([]), $this);
return ArrayResultFactory::createWrapperResultFromArray([], $this);
}
};

View File

@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ArrayHydrator;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Performance\ArrayResultFactory;
use Doctrine\Performance\EntityManagerFactory;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsUser;
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
@@ -62,7 +62,7 @@ final class MixedQueryFetchJoinArrayHydrationPerformanceBench
];
}
$this->result = ArrayResultFactory::createFromArray($resultSet);
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
$this->hydrator = new ArrayHydrator(EntityManagerFactory::getEntityManager([]));
$this->rsm = new ResultSetMapping();

View File

@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Performance\ArrayResultFactory;
use Doctrine\Performance\EntityManagerFactory;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsUser;
@@ -49,7 +49,7 @@ final class MixedQueryFetchJoinFullObjectHydrationPerformanceBench
];
}
$this->result = ArrayResultFactory::createFromArray($resultSet);
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
$this->hydrator = new ObjectHydrator(EntityManagerFactory::getEntityManager([]));
$this->rsm = new ResultSetMapping();

View File

@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ArrayHydrator;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Performance\ArrayResultFactory;
use Doctrine\Performance\EntityManagerFactory;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Models\CMS\CmsUser;
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
@@ -53,7 +53,7 @@ final class SimpleQueryArrayHydrationPerformanceBench
];
}
$this->result = ArrayResultFactory::createFromArray($resultSet);
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
$this->hydrator = new ArrayHydrator(EntityManagerFactory::getEntityManager([]));
$this->rsm = new ResultSetMapping();

View File

@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Performance\ArrayResultFactory;
use Doctrine\Performance\EntityManagerFactory;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsUser;
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
@@ -44,7 +44,7 @@ final class SimpleQueryFullObjectHydrationPerformanceBench
];
}
$this->result = ArrayResultFactory::createFromArray($resultSet);
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
$this->hydrator = new ObjectHydrator(EntityManagerFactory::getEntityManager([]));
$this->rsm = new ResultSetMapping();

View File

@@ -7,8 +7,8 @@ namespace Doctrine\Performance\Hydration;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ScalarHydrator;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Performance\ArrayResultFactory;
use Doctrine\Performance\EntityManagerFactory;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Models\CMS\CmsUser;
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
@@ -53,7 +53,7 @@ final class SimpleQueryScalarHydrationPerformanceBench
];
}
$this->result = ArrayResultFactory::createFromArray($resultSet);
$this->result = ArrayResultFactory::createWrapperResultFromArray($resultSet);
$this->hydrator = new ScalarHydrator(EntityManagerFactory::getEntityManager([]));
$this->rsm = new ResultSetMapping();

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Mocks;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\PDO\SQLite\Driver;
use Doctrine\DBAL\Result;
use ReflectionMethod;
use function array_keys;
use function array_map;
use function array_values;
final class ArrayResultFactory
{
/** @param list<array<string, mixed>> $resultSet */
public static function createDriverResultFromArray(array $resultSet): ArrayResult
{
if ((new ReflectionMethod(ArrayResult::class, '__construct'))->getNumberOfRequiredParameters() < 2) {
// DBAL < 4.2
return new ArrayResult($resultSet);
}
// DBAL 4.2+
return new ArrayResult(
array_keys($resultSet[0] ?? []),
array_map(array_values(...), $resultSet),
);
}
/** @param list<array<string, mixed>> $resultSet */
public static function createWrapperResultFromArray(array $resultSet, Connection|null $connection = null): Result
{
return new Result(
self::createDriverResultFromArray($resultSet),
$connection ?? new Connection([], new Driver()),
);
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\OneToOne;
use Doctrine\ORM\Mapping\Table;
#[Entity]
#[Table(name: 'one_to_one_inverse_side_assoc_id_load_inverse')]
class InverseSide
{
/** Associative id (owning identifier) */
#[Id]
#[OneToOne(targetEntity: InverseSideIdTarget::class, inversedBy: 'inverseSide')]
#[JoinColumn(nullable: false, name: 'associativeId')]
public InverseSideIdTarget $associativeId;
#[OneToOne(targetEntity: OwningSide::class, mappedBy: 'inverse')]
public OwningSide $owning;
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToOne;
use Doctrine\ORM\Mapping\Table;
#[Entity]
#[Table(name: 'one_to_one_inverse_side_assoc_id_load_inverse_id_target')]
class InverseSideIdTarget
{
#[Id]
#[Column(type: 'string', length: 255)]
#[GeneratedValue(strategy: 'NONE')]
public string $id;
#[OneToOne(targetEntity: InverseSide::class, mappedBy: 'associativeId')]
public InverseSide $inverseSide;
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\OneToOne;
use Doctrine\ORM\Mapping\Table;
#[Entity]
#[Table(name: 'one_to_one_inverse_side_assoc_id_load_owning')]
class OwningSide
{
#[Id]
#[Column(type: 'string', length: 255)]
#[GeneratedValue(strategy: 'NONE')]
public string $id;
/** Owning side */
#[OneToOne(targetEntity: InverseSide::class, inversedBy: 'owning')]
#[JoinColumn(name: 'inverse', referencedColumnName: 'associativeId')]
public InverseSide $inverse;
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad\InverseSide;
use Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad\InverseSideIdTarget;
use Doctrine\Tests\Models\OneToOneInverseSideWithAssociativeIdLoad\OwningSide;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
use function assert;
class OneToOneInverseSideWithAssociativeIdLoadAfterDqlQueryTest extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->createSchemaForModels(OwningSide::class, InverseSideIdTarget::class, InverseSide::class);
}
#[Group('GH-11108')]
public function testInverseSideWithAssociativeIdOneToOneLoadedAfterDqlQuery(): void
{
$owner = new OwningSide();
$inverseId = new InverseSideIdTarget();
$inverse = new InverseSide();
$owner->id = 'owner';
$inverseId->id = 'inverseId';
$inverseId->inverseSide = $inverse;
$inverse->associativeId = $inverseId;
$owner->inverse = $inverse;
$inverse->owning = $owner;
$this->_em->persist($owner);
$this->_em->persist($inverseId);
$this->_em->persist($inverse);
$this->_em->flush();
$this->_em->clear();
$fetchedInverse = $this
->_em
->createQueryBuilder()
->select('inverse')
->from(InverseSide::class, 'inverse')
->andWhere('inverse.associativeId = :associativeId')
->setParameter('associativeId', 'inverseId')
->getQuery()
->getSingleResult();
assert($fetchedInverse instanceof InverseSide);
self::assertInstanceOf(InverseSide::class, $fetchedInverse);
self::assertInstanceOf(InverseSideIdTarget::class, $fetchedInverse->associativeId);
self::assertInstanceOf(OwningSide::class, $fetchedInverse->owning);
$this->assertSQLEquals(
'select o0_.associativeid as associativeid_0 from one_to_one_inverse_side_assoc_id_load_inverse o0_ where o0_.associativeid = ?',
$this->getLastLoggedQuery(1)['sql'],
);
$this->assertSQLEquals(
'select t0.id as id_1, t0.inverse as inverse_2 from one_to_one_inverse_side_assoc_id_load_owning t0 where t0.inverse = ?',
$this->getLastLoggedQuery()['sql'],
);
}
}

View File

@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Exception\ORMException;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH11501Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->setUpEntitySchema([
GH11501AbstractTestEntity::class,
GH11501TestEntityOne::class,
GH11501TestEntityTwo::class,
GH11501TestEntityHolder::class,
]);
}
/** @throws ORMException */
public function testDeleteOneToManyCollectionWithSingleTableInheritance(): void
{
$testEntityOne = new GH11501TestEntityOne();
$testEntityTwo = new GH11501TestEntityTwo();
$testEntityHolder = new GH11501TestEntityHolder();
$testEntityOne->testEntityHolder = $testEntityHolder;
$testEntityHolder->testEntities->add($testEntityOne);
$testEntityTwo->testEntityHolder = $testEntityHolder;
$testEntityHolder->testEntities->add($testEntityTwo);
$em = $this->getEntityManager();
$em->persist($testEntityOne);
$em->persist($testEntityTwo);
$em->persist($testEntityHolder);
$em->flush();
$testEntityHolder->testEntities = new ArrayCollection();
$em->persist($testEntityHolder);
$em->flush();
$em->refresh($testEntityHolder);
static::assertEmpty($testEntityHolder->testEntities->toArray(), 'All records should have been deleted');
}
}
#[ORM\Entity]
#[ORM\Table(name: 'one_to_many_single_table_inheritance_test_entities_parent_join')]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap([
'test_entity_one' => 'GH11501TestEntityOne',
'test_entity_two' => 'GH11501TestEntityTwo',
])]
class GH11501AbstractTestEntity
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
public int $id;
#[ORM\ManyToOne(targetEntity: 'GH11501TestEntityHolder', inversedBy: 'testEntities')]
#[ORM\JoinColumn(name: 'test_entity_holder_id', referencedColumnName: 'id')]
public GH11501TestEntityHolder $testEntityHolder;
}
#[ORM\Entity]
class GH11501TestEntityOne extends GH11501AbstractTestEntity
{
}
#[ORM\Entity]
class GH11501TestEntityTwo extends GH11501AbstractTestEntity
{
}
#[ORM\Entity]
class GH11501TestEntityHolder
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
public int $id;
#[ORM\OneToMany(
targetEntity: 'GH11501AbstractTestEntity',
mappedBy: 'testEntityHolder',
orphanRemoval: true,
)]
public Collection $testEntities;
public function __construct()
{
$this->testEntities = new ArrayCollection();
}
}

View File

@@ -5,9 +5,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\DiscriminatorColumn;
@@ -19,6 +17,7 @@ use Doctrine\ORM\Mapping\InheritanceType;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
@@ -78,7 +77,7 @@ final class GH6362Test extends OrmFunctionalTestCase
],
];
$stmt = new Result(new ArrayResult($resultSet), $this->createMock(Connection::class));
$stmt = ArrayResultFactory::createWrapperResultFromArray($resultSet, $this->createMock(Connection::class));
$hydrator = new ObjectHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);

View File

@@ -5,9 +5,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Column;
@@ -15,6 +13,7 @@ use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\OrmFunctionalTestCase;
final class GH9807Test extends OrmFunctionalTestCase
@@ -63,7 +62,7 @@ final class GH9807Test extends OrmFunctionalTestCase
],
];
$stmt = new Result(new ArrayResult($resultSet), $this->createMock(Connection::class));
$stmt = ArrayResultFactory::createWrapperResultFromArray($resultSet, $this->createMock(Connection::class));
/** @var GH9807Main[] $result */
$result = $hydrator->hydrateAll($stmt, $rsm);

View File

@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Hydration;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Result;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\OrmTestCase;
class HydrationTestCase extends OrmTestCase
@@ -22,6 +22,6 @@ class HydrationTestCase extends OrmTestCase
protected function createResultMock(array $resultSet): Result
{
return new Result(new ArrayResult($resultSet), $this->entityManager->getConnection());
return ArrayResultFactory::createWrapperResultFromArray($resultSet, $this->entityManager->getConnection());
}
}

View File

@@ -4,9 +4,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Hydration;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Result;
use Doctrine\ORM\Internal\Hydration\HydrationException;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
use Doctrine\ORM\Mapping\ClassMetadata;
@@ -14,6 +12,7 @@ use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Proxy\InternalProxy;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsArticle;
use Doctrine\Tests\Models\CMS\CmsComment;
@@ -1337,7 +1336,7 @@ class ObjectHydratorTest extends HydrationTestCase
$hydrator = new ObjectHydrator($this->entityManager);
$rowNum = 0;
$iterableResult = $hydrator->toIterable(
new Result(new ArrayResult($resultSet), $this->createMock(Connection::class)),
ArrayResultFactory::createWrapperResultFromArray($resultSet, $this->createMock(Connection::class)),
$rsm,
);

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Query;
use DateTime;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\Mocks\EntityManagerMock;
use Doctrine\Tests\OrmTestCase;
class NativeQueryTest extends OrmTestCase
{
/** @var EntityManagerMock */
protected $entityManager;
protected function setUp(): void
{
$this->entityManager = $this->getTestEntityManager();
}
public function testValuesAreNotBeingResolvedForSpecifiedParameterTypes(): void
{
$unitOfWork = $this->createMock(UnitOfWork::class);
$this->entityManager->setUnitOfWork($unitOfWork);
$unitOfWork
->expects(self::never())
->method('getSingleIdentifierValue');
$rsm = new ResultSetMapping();
$query = $this->entityManager->createNativeQuery('SELECT d.* FROM date_time_model d WHERE d.datetime = :value', $rsm);
$query->setParameter('value', new DateTime(), Types::DATETIME_MUTABLE);
self::assertEmpty($query->getResult());
}
}

View File

@@ -8,7 +8,6 @@ use DateTime;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Result;
@@ -22,6 +21,7 @@ use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Mocks\EntityManagerMock;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsGroup;
@@ -361,10 +361,10 @@ class QueryTest extends OrmTestCase
{
$entityManager = $this->createTestEntityManagerWithConnection(
$this->createConnection(
new ArrayResult([
ArrayResultFactory::createDriverResultFromArray([
['id_0' => 1],
]),
new ArrayResult([]),
ArrayResultFactory::createDriverResultFromArray([]),
),
);
@@ -399,14 +399,14 @@ class QueryTest extends OrmTestCase
{
$entityManager = $this->createTestEntityManagerWithConnection(
$this->createConnection(
new ArrayResult([
ArrayResultFactory::createDriverResultFromArray([
['id_0' => 1],
]),
new ArrayResult([
ArrayResultFactory::createDriverResultFromArray([
['id_0' => 1],
['id_0' => 2],
]),
new ArrayResult([
ArrayResultFactory::createDriverResultFromArray([
['id_0' => 1],
]),
),