Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3e34b8e
Add a test to reproduce the issue
mpdude Oct 1, 2025
e7e2fef
Fix creation of the count query, to that a new RSM is being created
mpdude Oct 1, 2025
4372595
Fix CS
mpdude Oct 1, 2025
048e308
Fix DQL JOIN syntax in two test cases
mpdude Oct 7, 2025
f8186b1
Merge pull request #12200 from mpdude/fix-invalid-dql-test-case
stof Oct 8, 2025
a939dc2
[GH-9219] Add support for toIterable over mixed or scalar results. (#…
beberlei Oct 8, 2025
2ad720b
Revert "Fix fields of transient classes being considered duplicate wi…
mpdude Oct 9, 2025
8afaa63
Add a recommendation not to use multiple private fields of the same n…
mpdude Oct 9, 2025
3b7de17
add jobs using PHP 8.5 in the CI (#12180)
xabbuh Oct 9, 2025
930a790
Merge pull request #12212 from mpdude/revert-11769
greg0ire Oct 10, 2025
5f6896a
Avoid using LIBRARY as table name (#12170)
mbeccati Oct 11, 2025
c1ce2bb
Merge pull request #12220 from mbeccati/fix-mysql9-library
greg0ire Oct 11, 2025
693acbf
Bump doctrine/.github from 8.0.0 to 10.1.0
dependabot[bot] Oct 13, 2025
5def068
Merge pull request #12223 from doctrine/dependabot/github_actions/2.2…
greg0ire Oct 13, 2025
7a59281
Fix collection filtering API for IN/NOT IN comparisons that require t…
mpdude Mar 30, 2025
9d680a6
Do not eagerly set metadata from `ResolveTargetEntityListener` (#12174)
mpdude Oct 15, 2025
9a55cf4
Merge pull request #12190 from mpdude/criteria-matching-custom-type-r…
greg0ire Oct 16, 2025
96f9b29
Merge pull request #12233 from alisolphp/fix-docs-typos-2-20
alisolphp Oct 21, 2025
f71956f
Use `docs-builder` to generate ORM docs
paulinevos Oct 9, 2025
aa62efa
Adapt to latest coding standard
greg0ire Oct 22, 2025
c1047b3
Merge pull request #12216 from paulinevos/docs-builder
greg0ire Oct 23, 2025
b45d532
Bump doctrine/.github from 10.1.0 to 12.0.0 (#12227)
dependabot[bot] Oct 23, 2025
c2b844d
Update src/Tools/Pagination/Paginator.php
mpdude Oct 26, 2025
828b06e
Update the DQL walker cookbook example
mpdude Oct 26, 2025
1865717
Avoid triggering a deprecation notice in doctrine/collections
mpdude Oct 26, 2025
3304290
Bump actions/download-artifact from 5 to 6
dependabot[bot] Oct 27, 2025
c64dcb4
Bump actions/upload-artifact from 4 to 5
dependabot[bot] Oct 27, 2025
6acbadf
Merge pull request #12240 from doctrine/dependabot/github_actions/2.2…
greg0ire Oct 27, 2025
a0d401b
Merge pull request #12239 from doctrine/dependabot/github_actions/2.2…
greg0ire Oct 27, 2025
63635ca
Remove PHPStan error suppressions
mpdude Oct 27, 2025
da67f32
Add PHPStan errors to persistence2 baseline file
mpdude Oct 27, 2025
298dc9b
Merge pull request #12183 from mpdude/paginator-confused-result-set-m…
greg0ire Oct 27, 2025
59938ca
Merge pull request #12238 from mpdude/fix-collections-deprecation
greg0ire Oct 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/coding-standards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ on:

jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@8.0.0"
uses: "doctrine/.github/.github/workflows/coding-standards.yml@12.1.0"
14 changes: 9 additions & 5 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
extension:
Expand Down Expand Up @@ -98,7 +99,7 @@ jobs:
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"

- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
uses: "actions/upload-artifact@v5"
with:
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.proxy }}-coverage"
path: "coverage*.xml"
Expand All @@ -115,6 +116,7 @@ jobs:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3@dev"
Expand Down Expand Up @@ -172,7 +174,7 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"

- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
uses: "actions/upload-artifact@v5"
with:
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
path: "coverage.xml"
Expand All @@ -189,6 +191,7 @@ jobs:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3@dev"
Expand Down Expand Up @@ -243,7 +246,7 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"

- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
uses: "actions/upload-artifact@v5"
with:
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
Expand All @@ -260,6 +263,7 @@ jobs:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3@dev"
Expand Down Expand Up @@ -321,7 +325,7 @@ jobs:
ENABLE_SECOND_LEVEL_CACHE: 1

- name: "Upload coverage files"
uses: "actions/upload-artifact@v4"
uses: "actions/upload-artifact@v5"
with:
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
Expand Down Expand Up @@ -378,7 +382,7 @@ jobs:
fetch-depth: 2

- name: "Download coverage files"
uses: "actions/download-artifact@v5"
uses: "actions/download-artifact@v6"
with:
path: "reports"

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ on:
jobs:
documentation:
name: "Documentation"
uses: "doctrine/.github/.github/workflows/documentation.yml@8.0.0"
uses: "doctrine/.github/.github/workflows/documentation.yml@12.1.0"
2 changes: 1 addition & 1 deletion .github/workflows/release-on-milestone-closed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:

jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@8.0.0"
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@12.1.0"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
{"name": "Jonathan Wage", "email": "[email protected]"},
{"name": "Marco Pivetta", "email": "[email protected]"}
],
"scripts": {
"docs": "composer --working-dir docs update && ./docs/vendor/bin/build-docs.sh @additional_args"
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
Expand Down
2 changes: 1 addition & 1 deletion docs/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
composer.lock
vendor/
build/
output/
24 changes: 0 additions & 24 deletions docs/Makefile

This file was deleted.

12 changes: 4 additions & 8 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,15 @@ The documentation is written in [ReStructured Text](https://docutils.sourceforge

## How to Generate:

In the `docs/` folder, run
In the project root, run

composer update
composer docs

Then compile the documentation with:

make html

This will generate the documentation into the `build` subdirectory.
This will generate the documentation into the `docs/output` subdirectory.

To browse the documentation, you need to run a webserver:

cd build/html
cd docs/output
php -S localhost:8000

Now the documentation is available at [http://localhost:8000](http://localhost:8000).
5 changes: 2 additions & 3 deletions docs/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
"description": "Documentation for the Object-Relational Mapper\"",
"type": "library",
"license": "MIT",
"require": {
"phpdocumentor/guides-cli": "1.7.1",
"phpdocumentor/filesystem": "1.7.1"
"require-dev": {
"doctrine/docs-builder": "^1.0"
}
}
14 changes: 11 additions & 3 deletions docs/en/cookbook/dql-custom-walkers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,16 @@ The ``Paginate::count(Query $query)`` looks like:
{
static public function count(Query $query)
{
/** @var Query $countQuery */
$countQuery = clone $query;
/*
To avoid changing the $query passed into the method and to make sure a possibly existing
ResultSetMapping is discarded, we create a new query object any copy relevant data over.
*/
$countQuery = new Query($query->getEntityManager());
$countQuery->setDQL($query->getDQL());
$countQuery->setParameters(clone $query->getParameters());
foreach ($query->getHints() as $name => $value) {
$countQuery->setHint($name, $value);
}

$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
$countQuery->setFirstResult(null)->setMaxResults(null);
Expand All @@ -111,7 +119,7 @@ The ``Paginate::count(Query $query)`` looks like:
}
}

It clones the query, resets the limit clause first and max results
This resets the limit clause first and max results
and registers the ``CountSqlWalker`` custom tree walker which
will modify the AST to execute a count query. The walkers
implementation is:
Expand Down
2 changes: 1 addition & 1 deletion docs/en/reference/advanced-configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ requests.
Connection
----------

The ``$connection`` passed as the first argument to he constructor of
The ``$connection`` passed as the first argument to the constructor of
``EntityManager`` has to be an instance of ``Doctrine\DBAL\Connection``.
You can use the factory ``Doctrine\DBAL\DriverManager::getConnection()``
to create such a connection. The DBAL configuration is explained in the
Expand Down
2 changes: 1 addition & 1 deletion docs/en/reference/architecture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ recommended, at least not as long as an entity instance still holds
references to proxy objects or is still managed by an EntityManager.
By default, serializing proxy objects does not initialize them. On
unserialization, resulting objects are detached from the entity
manager and cannot be initialiazed anymore. You can implement the
manager and cannot be initialized anymore. You can implement the
``__serialize()`` method if you want to change that behavior, but
then you need to ensure that you won't generate large serialized
object graphs and take care of circular associations.
Expand Down
2 changes: 1 addition & 1 deletion docs/en/reference/filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ table alias of the SQL table of the entity.

For the filter to correctly function, the following rules must be followed. Failure to do so will lead to unexpected results from the query cache.
1. Parameters for the query should be set on the filter object by ``SQLFilter#setParameter()`` before the filter is used by the ORM ( i.e. do not set parameters inside ``SQLFilter#addFilterConstraint()`` function ).
2. The filter must be deterministic. Don't change the values base on external inputs.
2. The filter must be deterministic. Don't change the values based on external inputs.

The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters.

Expand Down
8 changes: 8 additions & 0 deletions docs/en/reference/limitations-and-known-issues.rst
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,14 @@ internally by the ORM currently refers to fields by their name only, without tak
class containing the field into consideration. This makes it impossible to keep separate
mapping configuration for both fields.

Apart from that, in the case of having multiple ``private`` fields of the same name within
the class hierarchy an entity or mapped superclass, the Collection filtering API cannot determine
the right field to look at. Even if only one of these fields is actually mapped, the ``ArrayCollection``
will not be able to tell, since it does not have access to any metadata.

Thus, to avoid problems in this regard, it is best to avoid having multiple ``private`` fields of the
same name in class hierarchies containing entity and mapped superclasses.

Known Issues
------------

Expand Down
4 changes: 2 additions & 2 deletions docs/en/reference/second-level-cache.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Something like below for an entity region:


If the entity holds a collection that also needs to be cached.
An collection region could look something like:
A collection region could look something like:

.. code-block:: php

Expand Down Expand Up @@ -630,7 +630,7 @@ DELETE / UPDATE queries
DQL UPDATE / DELETE statements are ported directly into a database and bypass
the second-level cache.
Entities that are already cached will NOT be invalidated.
However the cached data could be evicted using the cache API or an special query hint.
However the cached data could be evicted using the cache API or a special query hint.


Execute the ``UPDATE`` and invalidate ``all cache entries`` using ``Query::HINT_CACHE_EVICT``
Expand Down
2 changes: 1 addition & 1 deletion docs/en/reference/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ entity might look like this:
}
}

Now the possiblity of mass-assignment exists on this entity and can
Now the possibility of mass-assignment exists on this entity and can
be exploited by attackers to set the "isAdmin" flag to true on any
object when you pass the whole request data to this method like:

Expand Down
12 changes: 0 additions & 12 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2946,12 +2946,6 @@ parameters:
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php

-
message: '#^Strict comparison using \=\=\= between string and null will always evaluate to false\.$#'
identifier: identical.alwaysFalse
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php

-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\CachedPersisterContext\:\:__construct\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
Expand Down Expand Up @@ -5007,12 +5001,6 @@ parameters:
count: 5
path: src/Tools/ResolveTargetEntityListener.php

-
message: '#^Parameter \#1 \$className of method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory\<Doctrine\\ORM\\Mapping\\ClassMetadata\>\:\:setMetadataFor\(\) expects class\-string, \(int\|string\) given\.$#'
identifier: argument.type
count: 1
path: src/Tools/ResolveTargetEntityListener.php

-
message: '#^Call to function is_numeric\(\) with int\<0, max\> will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
Expand Down
6 changes: 6 additions & 0 deletions phpstan-persistence2.neon
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,9 @@ parameters:
message: '#injectObjectManager#'
identifier: 'method.deprecatedInterface'
path: src/UnitOfWork.php

-
message: '#^Static method Doctrine\\Common\\Collections\\Criteria\:\:create\(\) invoked with 1 parameter, 0 required\.$#'
identifier: arguments.count
count: 3
path: src/Persisters/Collection/OneToManyPersister.php
5 changes: 0 additions & 5 deletions src/AbstractQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\Mapping\MappingException;
use LogicException;
Expand Down Expand Up @@ -1144,10 +1143,6 @@ public function toIterable(iterable $parameters = [], $hydrationMode = null): it
throw new LogicException('Uninitialized result set mapping.');
}

if ($rsm->isMixed && count($rsm->scalarMappings) > 0) {
throw QueryException::iterateWithMixedResultNotAllowed();
}

$stmt = $this->_doExecute();

return $this->_em->newHydrator($this->_hydrationMode)->toIterable($stmt, $rsm, $this->_hints);
Expand Down
6 changes: 5 additions & 1 deletion src/Internal/Hydration/AbstractHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
use function array_map;
use function array_merge;
use function count;
use function current;
use function end;
use function get_debug_type;
use function in_array;
use function is_array;
use function is_object;
use function sprintf;

/**
Expand Down Expand Up @@ -201,8 +203,10 @@ public function toIterable($stmt, ResultSetMapping $resultSetMapping, array $hin
} else {
yield from $result;
}
} else {
} elseif (is_object(current($result))) {
yield $result;
} else {
yield array_merge(...$result);
}
}
} finally {
Expand Down
5 changes: 0 additions & 5 deletions src/Mapping/Driver/ReflectionBasedDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,8 @@ private function isRepeatedPropertyDeclaration(ReflectionProperty $property, Cla
|| $metadata->isInheritedEmbeddedClass($property->name);
}

/** @var class-string $declaringClass */
$declaringClass = $property->class;

if ($this->isTransient($declaringClass)) {
return isset($metadata->fieldMappings[$property->name]);
}

if (
isset($metadata->fieldMappings[$property->name]['declared'])
&& $metadata->fieldMappings[$property->name]['declared'] === $declaringClass
Expand Down
14 changes: 7 additions & 7 deletions src/Persisters/Collection/ManyToManyPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,14 +257,14 @@ public function loadCriteria(PersistentCollection $collection, Criteria $criteri

if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) {
$whereClauses[] = sprintf('te.%s %s NULL', $field, $operator === Comparison::EQ ? 'IS' : 'IS NOT');
} else {
if ($operator === Comparison::IN) {
$whereClauses[] = sprintf('te.%s IN (?)', $field);
} elseif ($operator === Comparison::NIN) {
$whereClauses[] = sprintf('te.%s NOT IN (?)', $field);
} else {
$whereClauses[] = sprintf('te.%s %s ?', $field, $operator);
} elseif ($operator === Comparison::IN || $operator === Comparison::NIN) {
$whereClauses[] = sprintf('te.%s %s (%s)', $field, $operator === Comparison::IN ? 'IN' : 'NOT IN', implode(', ', array_fill(0, count($value), '?')));
foreach ($value as $item) {
$params = array_merge($params, PersisterHelper::convertToParameterValue($item, $this->em));
$paramTypes = array_merge($paramTypes, PersisterHelper::inferParameterTypes($name, $item, $targetClass, $this->em));
}
} else {
$whereClauses[] = sprintf('te.%s %s ?', $field, $operator);

$params = array_merge($params, PersisterHelper::convertToParameterValue($value, $this->em));
$paramTypes = array_merge($paramTypes, PersisterHelper::inferParameterTypes($name, $value, $targetClass, $this->em));
Expand Down
Loading