diff --git a/docs/en/reference/attributes-reference.rst b/docs/en/reference/attributes-reference.rst
index 3452f78acb5..8190c28c8ef 100644
--- a/docs/en/reference/attributes-reference.rst
+++ b/docs/en/reference/attributes-reference.rst
@@ -576,7 +576,7 @@ Example with partial indexes:
#[Index(name: "search_idx", columns: ["category"],
options: [
- "where": "((category IS NOT NULL))"
+ "where" => "((category IS NOT NULL))"
]
)]
class ECommerceProduct
diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd
index 1965c8f4561..a9b1a367e67 100644
--- a/doctrine-mapping.xsd
+++ b/doctrine-mapping.xsd
@@ -302,7 +302,7 @@
-
+
@@ -415,7 +415,7 @@
-
+
@@ -447,6 +447,13 @@
+
+
+
+
+
+
+
@@ -631,7 +638,7 @@
-
+
diff --git a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php
index 2ca06847236..393255c0c50 100644
--- a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php
+++ b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php
@@ -51,7 +51,7 @@ public function deferPostLoadInvoking(ClassMetadata $class, $entity): void
}
/**
- * This method should me called after any hydration cycle completed.
+ * This method should be called after any hydration cycle completed.
*
* Method fires all deferred invocations of postLoad events
*/
diff --git a/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php
index 1813319e75c..2dccf6707e8 100644
--- a/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php
+++ b/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php
@@ -6,6 +6,7 @@
use BadMethodCallException;
use Doctrine\Common\Collections\Criteria;
+use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
@@ -248,10 +249,15 @@ public function loadCriteria(PersistentCollection $collection, Criteria $criteri
foreach ($parameters as $parameter) {
[$name, $value, $operator] = $parameter;
- $field = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform);
- $whereClauses[] = sprintf('te.%s %s ?', $field, $operator);
- $params[] = $value;
- $paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0];
+ $field = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform);
+
+ if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) {
+ $whereClauses[] = sprintf('te.%s %s NULL', $field, $operator === Comparison::EQ ? 'IS' : 'IS NOT');
+ } else {
+ $whereClauses[] = sprintf('te.%s %s ?', $field, $operator);
+ $params[] = $value;
+ $paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0];
+ }
}
$tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
diff --git a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php
index 1cc8e57db66..c3b70e41f68 100644
--- a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php
+++ b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php
@@ -892,15 +892,17 @@ public function expandCriteriaParameters(Criteria $criteria)
$valueVisitor->dispatch($expression);
- [$params, $types] = $valueVisitor->getParamsAndTypes();
-
- foreach ($params as $param) {
- $sqlParams = array_merge($sqlParams, $this->getValues($param));
- }
+ [, $types] = $valueVisitor->getParamsAndTypes();
foreach ($types as $type) {
- [$field, $value] = $type;
- $sqlTypes = array_merge($sqlTypes, $this->getTypes($field, $value, $this->class));
+ [$field, $value, $operator] = $type;
+
+ if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) {
+ continue;
+ }
+
+ $sqlParams = array_merge($sqlParams, $this->getValues($value));
+ $sqlTypes = array_merge($sqlTypes, $this->getTypes($field, $value, $this->class));
}
return [$sqlParams, $sqlTypes];
diff --git a/lib/Doctrine/ORM/Persisters/SqlValueVisitor.php b/lib/Doctrine/ORM/Persisters/SqlValueVisitor.php
index a61d0a25f86..999746faaa9 100644
--- a/lib/Doctrine/ORM/Persisters/SqlValueVisitor.php
+++ b/lib/Doctrine/ORM/Persisters/SqlValueVisitor.php
@@ -27,18 +27,10 @@ class SqlValueVisitor extends ExpressionVisitor
*/
public function walkComparison(Comparison $comparison)
{
- $value = $this->getValueFromComparison($comparison);
- $field = $comparison->getField();
- $operator = $comparison->getOperator();
-
- if (($operator === Comparison::EQ || $operator === Comparison::IS) && $value === null) {
- return null;
- } elseif ($operator === Comparison::NEQ && $value === null) {
- return null;
- }
+ $value = $this->getValueFromComparison($comparison);
$this->values[] = $value;
- $this->types[] = [$field, $value, $operator];
+ $this->types[] = [$comparison->getField(), $value, $comparison->getOperator()];
return null;
}
diff --git a/tests/Doctrine/Tests/Models/GH7717/GH7717Child.php b/tests/Doctrine/Tests/Models/GH7717/GH7717Child.php
new file mode 100644
index 00000000000..0ea30c8437f
--- /dev/null
+++ b/tests/Doctrine/Tests/Models/GH7717/GH7717Child.php
@@ -0,0 +1,26 @@
+
+ */
+ public Selectable $children;
+}
diff --git a/tests/Doctrine/Tests/Models/Project/Project.php b/tests/Doctrine/Tests/Models/Project/Project.php
new file mode 100644
index 00000000000..fc64b43561a
--- /dev/null
+++ b/tests/Doctrine/Tests/Models/Project/Project.php
@@ -0,0 +1,24 @@
+id = $id;
+ $this->name = $name;
+ }
+}
diff --git a/tests/Doctrine/Tests/Models/Project/ProjectId.php b/tests/Doctrine/Tests/Models/Project/ProjectId.php
new file mode 100644
index 00000000000..cedc0e5bd9d
--- /dev/null
+++ b/tests/Doctrine/Tests/Models/Project/ProjectId.php
@@ -0,0 +1,18 @@
+id = $id;
+ }
+}
diff --git a/tests/Doctrine/Tests/Models/Project/ProjectInvalidMapping.php b/tests/Doctrine/Tests/Models/Project/ProjectInvalidMapping.php
new file mode 100644
index 00000000000..ceecd48f754
--- /dev/null
+++ b/tests/Doctrine/Tests/Models/Project/ProjectInvalidMapping.php
@@ -0,0 +1,24 @@
+id = $id;
+ $this->name = $name;
+ }
+}
diff --git a/tests/Doctrine/Tests/Models/Project/ProjectName.php b/tests/Doctrine/Tests/Models/Project/ProjectName.php
new file mode 100644
index 00000000000..ea2177996d6
--- /dev/null
+++ b/tests/Doctrine/Tests/Models/Project/ProjectName.php
@@ -0,0 +1,18 @@
+name = $name;
+ }
+}
diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7717Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7717Test.php
new file mode 100755
index 00000000000..96c519e8624
--- /dev/null
+++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7717Test.php
@@ -0,0 +1,45 @@
+createSchemaForModels(
+ GH7717Parent::class,
+ GH7717Child::class
+ );
+ }
+
+ public function testManyToManyPersisterIsNullComparison(): void
+ {
+ $childWithNullProperty = new GH7717Child();
+ $childWithoutNullProperty = new GH7717Child();
+ $childWithoutNullProperty->nullableProperty = 'nope';
+
+ $parent = new GH7717Parent();
+ $parent->children = new ArrayCollection([$childWithNullProperty, $childWithoutNullProperty]);
+
+ $this->_em->persist($parent);
+ $this->_em->flush();
+ $this->_em->clear();
+
+ $parent = $this->_em->find(GH7717Parent::class, 1);
+
+ $this->assertCount(1, $parent->children->matching(new Criteria(Criteria::expr()->isNull('nullableProperty'))));
+ }
+}
diff --git a/tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php
index df09f9613d2..090a9d17693 100644
--- a/tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php
+++ b/tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php
@@ -22,6 +22,10 @@
use Doctrine\Tests\Models\Generic\BooleanModel;
use Doctrine\Tests\Models\GH7141\GH7141Article;
use Doctrine\Tests\Models\GH7316\GH7316Article;
+use Doctrine\Tests\Models\Project\Project;
+use Doctrine\Tests\Models\Project\ProjectId;
+use Doctrine\Tests\Models\Project\ProjectInvalidMapping;
+use Doctrine\Tests\Models\Project\ProjectName;
use Doctrine\Tests\Models\ValueObjects\Name;
use Doctrine\Tests\Models\ValueObjects\Person;
@@ -239,6 +243,10 @@ public static function dataInvalidSchema(): array
UserMissingAttributes::class,
['The attribute \'name\' is required but missing' => 1],
],
+ [
+ ProjectInvalidMapping::class,
+ ['attribute \'type\': [facet \'pattern\'] The value' => 2],
+ ],
];
}
@@ -279,6 +287,23 @@ public function testInvalidEntityOrMappedSuperClassShouldMentionParentClasses():
$this->createClassMetadata(DDC889Class::class);
}
+
+ public function testClassNameInFieldOrId(): void
+ {
+ $class = new ClassMetadata(Project::class);
+ $class->initializeReflection(new RuntimeReflectionService());
+
+ $driver = $this->loadDriver();
+ $driver->loadMetadataForClass(Project::class, $class);
+
+ /** @var array{type: string} $id */
+ $id = $class->getFieldMapping('id');
+ /** @var array{type: string} $name */
+ $name = $class->getFieldMapping('name');
+
+ self::assertEquals(ProjectId::class, $id['type']);
+ self::assertEquals(ProjectName::class, $name['type']);
+ }
}
class CTI
diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Project.Project.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Project.Project.dcm.xml
new file mode 100644
index 00000000000..05ad513b7b6
--- /dev/null
+++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Project.Project.dcm.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Project.ProjectInvalidMapping.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Project.ProjectInvalidMapping.dcm.xml
new file mode 100644
index 00000000000..d70800a3552
--- /dev/null
+++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Project.ProjectInvalidMapping.dcm.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/tests/Doctrine/Tests/TestUtil.php b/tests/Doctrine/Tests/TestUtil.php
index 28b3185c5fa..4eb0afe22c6 100644
--- a/tests/Doctrine/Tests/TestUtil.php
+++ b/tests/Doctrine/Tests/TestUtil.php
@@ -6,6 +6,7 @@
use Doctrine\Common\EventSubscriber;
use Doctrine\DBAL\Connection;
+use Doctrine\DBAL\Driver\AbstractSQLiteDriver\Middleware\EnableForeignKeys;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Platforms\SqlitePlatform;
@@ -16,10 +17,12 @@
use UnexpectedValueException;
use function assert;
+use function class_exists;
use function explode;
use function fwrite;
use function get_debug_type;
use function getenv;
+use function in_array;
use function method_exists;
use function sprintf;
use function str_starts_with;
@@ -63,7 +66,13 @@ public static function getConnection(): DbalExtensions\Connection
self::$initialized = true;
}
- $connection = DriverManager::getConnection(self::getTestConnectionParameters());
+ $connectionParameters = self::getTestConnectionParameters();
+ $configuration = new Configuration();
+ if (in_array($connectionParameters['driver'], ['pdo_sqlite', 'sqlite3'], true) && class_exists(EnableForeignKeys::class)) {
+ $configuration->setMiddlewares([new EnableForeignKeys()]);
+ }
+
+ $connection = DriverManager::getConnection($connectionParameters, $configuration);
assert($connection instanceof DbalExtensions\Connection);
self::addDbEventSubscribers($connection);
@@ -212,6 +221,7 @@ private static function mapConnectionParameters(array $configuration, string $pr
'port',
'server',
'memory',
+ 'path',
'ssl_key',
'ssl_cert',
'ssl_ca',