Skip to content

Commit 8fe1200

Browse files
authored
Merge pull request #11895 from mpdude/fix-many-to-many-in-expression
Fix `IN`/`NOT IN` expression handling and support enums when matching on to-many-collections
2 parents 397358c + 9bf407f commit 8fe1200

File tree

10 files changed

+378
-164
lines changed

10 files changed

+378
-164
lines changed

phpstan-baseline.neon

Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2742,18 +2742,6 @@ parameters:
27422742
count: 1
27432743
path: src/Persisters/Entity/BasicEntityPersister.php
27442744

2745-
-
2746-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:expandCriteriaParameters\(\) should return array\{list\<mixed\>, list\<int\|string\|null\>\} but returns array\{array\<mixed\>, list\<int\|string\|null\>\}\.$#'
2747-
identifier: return.type
2748-
count: 1
2749-
path: src/Persisters/Entity/BasicEntityPersister.php
2750-
2751-
-
2752-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:expandParameters\(\) should return array\{list\<mixed\>, list\<int\|string\|null\>\} but returns array\{array\<mixed\>, list\<int\|string\|null\>\}\.$#'
2753-
identifier: return.type
2754-
count: 1
2755-
path: src/Persisters/Entity/BasicEntityPersister.php
2756-
27572745
-
27582746
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:expandToManyParameters\(\) return type has no value type specified in iterable type array\.$#'
27592747
identifier: missingType.iterableValue
@@ -2790,12 +2778,6 @@ parameters:
27902778
count: 1
27912779
path: src/Persisters/Entity/BasicEntityPersister.php
27922780

2793-
-
2794-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getIndividualValue\(\) should return list\<mixed\> but returns array\<mixed\>\.$#'
2795-
identifier: return.type
2796-
count: 1
2797-
path: src/Persisters/Entity/BasicEntityPersister.php
2798-
27992781
-
28002782
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getManyToManyCollection\(\) has parameter \$assoc with no value type specified in iterable type array\.$#'
28012783
identifier: missingType.iterableValue
@@ -2856,12 +2838,6 @@ parameters:
28562838
count: 5
28572839
path: src/Persisters/Entity/BasicEntityPersister.php
28582840

2859-
-
2860-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getTypes\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
2861-
identifier: missingType.generics
2862-
count: 1
2863-
path: src/Persisters/Entity/BasicEntityPersister.php
2864-
28652841
-
28662842
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:load\(\) has parameter \$assoc with no value type specified in iterable type array\.$#'
28672843
identifier: missingType.iterableValue
@@ -2922,18 +2898,6 @@ parameters:
29222898
count: 8
29232899
path: src/Persisters/Entity/BasicEntityPersister.php
29242900

2925-
-
2926-
message: '#^Offset ''relationToTargetKeyColumns'' might not exist on array\{cache\?\: array, cascade\: array\<string\>, declared\?\: class\-string, fetch\: mixed, fieldName\: string, id\?\: bool, inherited\?\: class\-string, indexBy\?\: string, \.\.\.\}\.$#'
2927-
identifier: offsetAccess.notFound
2928-
count: 1
2929-
path: src/Persisters/Entity/BasicEntityPersister.php
2930-
2931-
-
2932-
message: '#^Offset ''sourceToTargetKeyColumns'' might not exist on array\{cache\?\: array, cascade\: array\<string\>, declared\?\: class\-string, fetch\: mixed, fieldName\: string, id\?\: bool, inherited\?\: class\-string, indexBy\?\: string, \.\.\.\}\.$#'
2933-
identifier: offsetAccess.notFound
2934-
count: 1
2935-
path: src/Persisters/Entity/BasicEntityPersister.php
2936-
29372901
-
29382902
message: '#^Offset ''targetToSourceKeyColumns'' might not exist on array\{cache\?\: array, cascade\: array\<string\>, declared\?\: class\-string, fetch\: mixed, fieldName\: string, id\?\: bool, inherited\?\: class\-string, indexBy\?\: string, \.\.\.\}\.$#'
29392903
identifier: offsetAccess.notFound
@@ -5553,8 +5517,26 @@ parameters:
55535517
count: 1
55545518
path: src/Utility/PersisterHelper.php
55555519

5520+
-
5521+
message: '#^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:inferParameterTypes\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
5522+
identifier: missingType.generics
5523+
count: 1
5524+
path: src/Utility/PersisterHelper.php
5525+
55565526
-
55575527
message: '#^Offset ''joinTable'' might not exist on array\{cache\?\: array, cascade\: array\<string\>, declared\?\: class\-string, fetch\: mixed, fieldName\: string, id\?\: bool, inherited\?\: class\-string, indexBy\?\: string, \.\.\.\}\.$#'
55585528
identifier: offsetAccess.notFound
55595529
count: 1
55605530
path: src/Utility/PersisterHelper.php
5531+
5532+
-
5533+
message: '#^Offset ''relationToTargetKeyColumns'' might not exist on array\{cache\?\: array, cascade\: array\<string\>, declared\?\: class\-string, fetch\: mixed, fieldName\: string, id\?\: bool, inherited\?\: class\-string, indexBy\?\: string, \.\.\.\}\.$#'
5534+
identifier: offsetAccess.notFound
5535+
count: 1
5536+
path: src/Utility/PersisterHelper.php
5537+
5538+
-
5539+
message: '#^Offset ''sourceToTargetKeyColumns'' might not exist on array\{cache\?\: array, cascade\: array\<string\>, declared\?\: class\-string, fetch\: mixed, fieldName\: string, id\?\: bool, inherited\?\: class\-string, indexBy\?\: string, \.\.\.\}\.$#'
5540+
identifier: offsetAccess.notFound
5541+
count: 1
5542+
path: src/Utility/PersisterHelper.php

src/Persisters/Collection/ManyToManyPersister.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Doctrine\ORM\Utility\PersisterHelper;
1717

1818
use function array_fill;
19+
use function array_merge;
1920
use function array_pop;
2021
use function count;
2122
use function get_class;
@@ -257,9 +258,16 @@ public function loadCriteria(PersistentCollection $collection, Criteria $criteri
257258
if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) {
258259
$whereClauses[] = sprintf('te.%s %s NULL', $field, $operator === Comparison::EQ ? 'IS' : 'IS NOT');
259260
} else {
260-
$whereClauses[] = sprintf('te.%s %s ?', $field, $operator);
261-
$params[] = $value;
262-
$paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0];
261+
if ($operator === Comparison::IN) {
262+
$whereClauses[] = sprintf('te.%s IN (?)', $field);
263+
} elseif ($operator === Comparison::NIN) {
264+
$whereClauses[] = sprintf('te.%s NOT IN (?)', $field);
265+
} else {
266+
$whereClauses[] = sprintf('te.%s %s ?', $field, $operator);
267+
}
268+
269+
$params = array_merge($params, PersisterHelper::convertToParameterValue($value, $this->em));
270+
$paramTypes = array_merge($paramTypes, PersisterHelper::inferParameterTypes($name, $value, $targetClass, $this->em));
263271
}
264272
}
265273

src/Persisters/Entity/BasicEntityPersister.php

Lines changed: 7 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace Doctrine\ORM\Persisters\Entity;
66

7-
use BackedEnum;
87
use Doctrine\Common\Collections\Criteria;
98
use Doctrine\Common\Collections\Expr\Comparison;
109
use Doctrine\DBAL\Connection;
@@ -26,9 +25,7 @@
2625
use Doctrine\ORM\Persisters\Exception\UnrecognizedField;
2726
use Doctrine\ORM\Persisters\SqlExpressionVisitor;
2827
use Doctrine\ORM\Persisters\SqlValueVisitor;
29-
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
3028
use Doctrine\ORM\Query;
31-
use Doctrine\ORM\Query\QueryException;
3229
use Doctrine\ORM\Repository\Exception\InvalidFindByCall;
3330
use Doctrine\ORM\UnitOfWork;
3431
use Doctrine\ORM\Utility\IdentifierFlattener;
@@ -47,7 +44,6 @@
4744
use function count;
4845
use function implode;
4946
use function is_array;
50-
use function is_object;
5147
use function reset;
5248
use function spl_object_id;
5349
use function sprintf;
@@ -394,7 +390,7 @@ final protected function extractIdentifierTypes(array $id, ClassMetadata $versio
394390
$types = [];
395391

396392
foreach ($id as $field => $value) {
397-
$types = array_merge($types, $this->getTypes($field, $value, $versionedClass));
393+
$types = array_merge($types, PersisterHelper::inferParameterTypes($field, $value, $versionedClass, $this->em));
398394
}
399395

400396
return $types;
@@ -955,8 +951,8 @@ public function expandCriteriaParameters(Criteria $criteria)
955951
continue;
956952
}
957953

958-
$sqlParams = array_merge($sqlParams, $this->getValues($value));
959-
$sqlTypes = array_merge($sqlTypes, $this->getTypes($field, $value, $this->class));
954+
$sqlParams = array_merge($sqlParams, PersisterHelper::convertToParameterValue($value, $this->em));
955+
$sqlTypes = array_merge($sqlTypes, PersisterHelper::inferParameterTypes($field, $value, $this->class, $this->em));
960956
}
961957

962958
return [$sqlParams, $sqlTypes];
@@ -1942,8 +1938,8 @@ public function expandParameters($criteria)
19421938
continue; // skip null values.
19431939
}
19441940

1945-
$types = array_merge($types, $this->getTypes($field, $value, $this->class));
1946-
$params = array_merge($params, $this->getValues($value));
1941+
$types = array_merge($types, PersisterHelper::inferParameterTypes($field, $value, $this->class, $this->em));
1942+
$params = array_merge($params, PersisterHelper::convertToParameterValue($value, $this->em));
19471943
}
19481944

19491945
return [$params, $types];
@@ -1971,127 +1967,13 @@ private function expandToManyParameters(array $criteria): array
19711967
continue; // skip null values.
19721968
}
19731969

1974-
$types = array_merge($types, $this->getTypes($criterion['field'], $criterion['value'], $criterion['class']));
1975-
$params = array_merge($params, $this->getValues($criterion['value']));
1970+
$types = array_merge($types, PersisterHelper::inferParameterTypes($criterion['field'], $criterion['value'], $criterion['class'], $this->em));
1971+
$params = array_merge($params, PersisterHelper::convertToParameterValue($criterion['value'], $this->em));
19761972
}
19771973

19781974
return [$params, $types];
19791975
}
19801976

1981-
/**
1982-
* Infers field types to be used by parameter type casting.
1983-
*
1984-
* @param mixed $value
1985-
*
1986-
* @return int[]|null[]|string[]
1987-
* @phpstan-return list<int|string|null>
1988-
*
1989-
* @throws QueryException
1990-
*/
1991-
private function getTypes(string $field, $value, ClassMetadata $class): array
1992-
{
1993-
$types = [];
1994-
1995-
switch (true) {
1996-
case isset($class->fieldMappings[$field]):
1997-
$types = array_merge($types, [$class->fieldMappings[$field]['type']]);
1998-
break;
1999-
2000-
case isset($class->associationMappings[$field]):
2001-
$assoc = $class->associationMappings[$field];
2002-
$class = $this->em->getClassMetadata($assoc['targetEntity']);
2003-
2004-
if (! $assoc['isOwningSide']) {
2005-
$assoc = $class->associationMappings[$assoc['mappedBy']];
2006-
$class = $this->em->getClassMetadata($assoc['targetEntity']);
2007-
}
2008-
2009-
$columns = $assoc['type'] === ClassMetadata::MANY_TO_MANY
2010-
? $assoc['relationToTargetKeyColumns']
2011-
: $assoc['sourceToTargetKeyColumns'];
2012-
2013-
foreach ($columns as $column) {
2014-
$types[] = PersisterHelper::getTypeOfColumn($column, $class, $this->em);
2015-
}
2016-
2017-
break;
2018-
2019-
default:
2020-
$types[] = null;
2021-
break;
2022-
}
2023-
2024-
if (is_array($value)) {
2025-
return array_map(static function ($type) {
2026-
$type = Type::getType($type);
2027-
2028-
return $type->getBindingType() + Connection::ARRAY_PARAM_OFFSET;
2029-
}, $types);
2030-
}
2031-
2032-
return $types;
2033-
}
2034-
2035-
/**
2036-
* Retrieves the parameters that identifies a value.
2037-
*
2038-
* @param mixed $value
2039-
*
2040-
* @return mixed[]
2041-
*/
2042-
private function getValues($value): array
2043-
{
2044-
if (is_array($value)) {
2045-
$newValue = [];
2046-
2047-
foreach ($value as $itemValue) {
2048-
$newValue = array_merge($newValue, $this->getValues($itemValue));
2049-
}
2050-
2051-
return [$newValue];
2052-
}
2053-
2054-
return $this->getIndividualValue($value);
2055-
}
2056-
2057-
/**
2058-
* Retrieves an individual parameter value.
2059-
*
2060-
* @param mixed $value
2061-
*
2062-
* @phpstan-return list<mixed>
2063-
*/
2064-
private function getIndividualValue($value): array
2065-
{
2066-
if (! is_object($value)) {
2067-
return [$value];
2068-
}
2069-
2070-
if ($value instanceof BackedEnum) {
2071-
return [$value->value];
2072-
}
2073-
2074-
$valueClass = DefaultProxyClassNameResolver::getClass($value);
2075-
2076-
if ($this->em->getMetadataFactory()->isTransient($valueClass)) {
2077-
return [$value];
2078-
}
2079-
2080-
$class = $this->em->getClassMetadata($valueClass);
2081-
2082-
if ($class->isIdentifierComposite) {
2083-
$newValue = [];
2084-
2085-
foreach ($class->getIdentifierValues($value) as $innerValue) {
2086-
$newValue = array_merge($newValue, $this->getValues($innerValue));
2087-
}
2088-
2089-
return $newValue;
2090-
}
2091-
2092-
return [$this->em->getUnitOfWork()->getSingleIdentifierValue($value)];
2093-
}
2094-
20951977
/**
20961978
* {@inheritDoc}
20971979
*/

0 commit comments

Comments
 (0)