Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge release 2.14.3 into 2.15.x #10648

Merged
merged 8 commits into from
Apr 22, 2023
Merged
2 changes: 1 addition & 1 deletion docs/en/reference/attributes-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ Example with partial indexes:

#[Index(name: "search_idx", columns: ["category"],
options: [
"where": "((category IS NOT NULL))"
"where" => "((category IS NOT NULL))"
]
)]
class ECommerceProduct
Expand Down
13 changes: 10 additions & 3 deletions doctrine-mapping.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="type" type="xs:NMTOKEN" default="string" />
<xs:attribute name="type" type="orm:type" default="string" />
<xs:attribute name="column" type="orm:columntoken" />
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
Expand Down Expand Up @@ -414,7 +414,7 @@
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="type" type="xs:NMTOKEN" />
<xs:attribute name="type" type="orm:type" />
<xs:attribute name="column" type="orm:columntoken" />
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="association-key" type="xs:boolean" default="false" />
Expand Down Expand Up @@ -446,6 +446,13 @@
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="type" id="type">
<xs:restriction base="xs:token">
<xs:pattern value="([a-zA-Z_u01-uff][a-zA-Z0-9_u01-uff]+)|(\c+)" id="type.class.pattern">
</xs:pattern>
</xs:restriction>
</xs:simpleType>

<xs:complexType name="inverse-join-columns">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
Expand Down Expand Up @@ -630,7 +637,7 @@
<xs:element name="options" type="orm:options" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:attribute name="type" type="xs:NMTOKEN" default="string" />
<xs:attribute name="type" type="orm:type" default="string" />
<xs:attribute name="column" type="orm:columntoken" />
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
Expand Down
2 changes: 1 addition & 1 deletion lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
14 changes: 10 additions & 4 deletions lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -246,10 +247,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);
Expand Down
16 changes: 9 additions & 7 deletions lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -890,15 +890,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];
Expand Down
12 changes: 2 additions & 10 deletions lib/Doctrine/ORM/Persisters/SqlValueVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
26 changes: 26 additions & 0 deletions tests/Doctrine/Tests/Models/GH7717/GH7717Child.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\GH7717;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name="gh7717_children")
*/
class GH7717Child
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
public ?int $id = null;

/**
* @ORM\Column(type="string", nullable=true)
*/
public ?string $nullableProperty = null;
}
29 changes: 29 additions & 0 deletions tests/Doctrine/Tests/Models/GH7717/GH7717Parent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\GH7717;

use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name="gh7717_parents")
*/
class GH7717Parent
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
public ?int $id = null;

/**
* @ORM\ManyToMany(targetEntity="GH7717Child", cascade={"persist"})
*
* @var Selectable<int, GH7717Child>
*/
public Selectable $children;
}
24 changes: 24 additions & 0 deletions tests/Doctrine/Tests/Models/Project/Project.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\Project;

class Project
{
/**
* @var string
*/
private $id;

/**
* @var string
*/
private $name;

public function __construct(string $id, string $name)
{
$this->id = $id;
$this->name = $name;
}
}
18 changes: 18 additions & 0 deletions tests/Doctrine/Tests/Models/Project/ProjectId.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\Project;

class ProjectId
{
/**
* @var string
*/
private $id;

public function __construct(string $id)
{
$this->id = $id;
}
}
24 changes: 24 additions & 0 deletions tests/Doctrine/Tests/Models/Project/ProjectInvalidMapping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\Project;

class ProjectInvalidMapping
{
/**
* @var string
*/
private $id;

/**
* @var string
*/
private $name;

public function __construct(string $id, string $name)
{
$this->id = $id;
$this->name = $name;
}
}
18 changes: 18 additions & 0 deletions tests/Doctrine/Tests/Models/Project/ProjectName.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\Project;

final class ProjectName
{
/**
* @var string
*/
private $name;

public function __construct(string $name)
{
$this->name = $name;
}
}
45 changes: 45 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/Ticket/GH7717Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Tests\Models\GH7717\GH7717Child;
use Doctrine\Tests\Models\GH7717\GH7717Parent;
use Doctrine\Tests\OrmFunctionalTestCase;

/**
* @requires PHP 7.4
*/
final class GH7717Test extends OrmFunctionalTestCase
{
public function setUp(): void
{
parent::setUp();

$this->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'))));
}
}
25 changes: 25 additions & 0 deletions tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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],
],
];
}

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\Project\Project" table="project">
<id name="id" type="Doctrine\Tests\Models\Project\ProjectId" column="id">
<generator strategy="NONE"/>
</id>
<field name="name" type="Doctrine\Tests\Models\Project\ProjectName" column="name"/>
</entity>
</doctrine-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\Project\ProjectInvalidMapping" table="project">
<id name="id" type="Doctrine/Tests/Models/Project/Project/ProjectId" column="id">
<generator strategy="NONE"/>
</id>
<field name="name" type="Doctrine/Tests/Models/Project/Project/ProjectName" column="name"/>
</entity>
</doctrine-mapping>
Loading