Skip to content

Commit

Permalink
Merge pull request #2051 from sryabov/fix-existsfilter-onetoone-inverse
Browse files Browse the repository at this point in the history
Fix ExistsFilter for inverse side of OneToOne association
  • Loading branch information
dunglas committed Jun 29, 2018
2 parents 7c38133 + e166e1b commit 96a2b2d
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 4 deletions.
10 changes: 6 additions & 4 deletions features/main/content_negotiation.feature
Expand Up @@ -18,7 +18,7 @@ Feature: Content Negotiation support
And the response should be equal to
"""
<?xml version="1.0"?>
<response><description/><dummy/><dummyBoolean/><dummyDate/><dummyFloat/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><arrayData/><name_converted/><id>1</id><name>XML!</name><alias/><foo/></response>
<response><description/><dummy/><dummyBoolean/><dummyDate/><dummyFloat/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><arrayData/><name_converted/><relatedOwnedDummy/><relatedOwningDummy/><id>1</id><name>XML!</name><alias/><foo/></response>
"""

Scenario: Retrieve a collection in XML
Expand All @@ -29,7 +29,7 @@ Feature: Content Negotiation support
And the response should be equal to
"""
<?xml version="1.0"?>
<response><item key="0"><description/><dummy/><dummyBoolean/><dummyDate/><dummyFloat/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><arrayData/><name_converted/><id>1</id><name>XML!</name><alias/><foo/></item></response>
<response><item key="0"><description/><dummy/><dummyBoolean/><dummyDate/><dummyFloat/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><arrayData/><name_converted/><relatedOwnedDummy/><relatedOwningDummy/><id>1</id><name>XML!</name><alias/><foo/></item></response>
"""

Scenario: Retrieve a collection in XML using the .xml URL
Expand All @@ -39,7 +39,7 @@ Feature: Content Negotiation support
And the response should be equal to
"""
<?xml version="1.0"?>
<response><item key="0"><description/><dummy/><dummyBoolean/><dummyDate/><dummyFloat/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><arrayData/><name_converted/><id>1</id><name>XML!</name><alias/><foo/></item></response>
<response><item key="0"><description/><dummy/><dummyBoolean/><dummyDate/><dummyFloat/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><arrayData/><name_converted/><relatedOwnedDummy/><relatedOwningDummy/><id>1</id><name>XML!</name><alias/><foo/></item></response>
"""

Scenario: Retrieve a collection in JSON
Expand All @@ -63,6 +63,8 @@ Feature: Content Negotiation support
"jsonData": [],
"arrayData": [],
"name_converted": null,
"relatedOwnedDummy": null,
"relatedOwningDummy": null,
"id": 1,
"name": "XML!",
"alias": null,
Expand All @@ -83,7 +85,7 @@ Feature: Content Negotiation support
And the response should be equal to
"""
<?xml version="1.0"?>
<response><description/><dummy/><dummyBoolean/><dummyDate/><dummyFloat/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><arrayData/><name_converted/><id>2</id><name>Sent in JSON</name><alias/><foo/></response>
<response><description/><dummy/><dummyBoolean/><dummyDate/><dummyFloat/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><arrayData/><name_converted/><relatedOwnedDummy/><relatedOwningDummy/><id>2</id><name>Sent in JSON</name><alias/><foo/></response>
"""

Scenario: Requesting the same format in the Accept header and in the URL should work
Expand Down
10 changes: 10 additions & 0 deletions features/main/crud.feature
Expand Up @@ -44,6 +44,8 @@ Feature: Create-Retrieve-Update-Delete
},
"arrayData": [],
"name_converted": null,
"relatedOwnedDummy": null,
"relatedOwningDummy": null,
"id": 1,
"name": "My Dummy",
"alias": null,
Expand Down Expand Up @@ -78,6 +80,8 @@ Feature: Create-Retrieve-Update-Delete
},
"arrayData": [],
"name_converted": null,
"relatedOwnedDummy": null,
"relatedOwningDummy": null,
"id": 1,
"name": "My Dummy",
"alias": null,
Expand Down Expand Up @@ -120,6 +124,8 @@ Feature: Create-Retrieve-Update-Delete
},
"arrayData": [],
"name_converted": null,
"relatedOwnedDummy": null,
"relatedOwningDummy": null,
"id": 1,
"name": "My Dummy",
"alias": null,
Expand Down Expand Up @@ -444,6 +450,8 @@ Feature: Create-Retrieve-Update-Delete
],
"arrayData": [],
"name_converted": null,
"relatedOwnedDummy": null,
"relatedOwningDummy": null,
"id": 1,
"name": "A nice dummy",
"alias": null,
Expand Down Expand Up @@ -481,6 +489,8 @@ Feature: Create-Retrieve-Update-Delete
],
"arrayData": [],
"name_converted": null,
"relatedOwnedDummy": null,
"relatedOwningDummy": null,
"id": 1,
"name": "A nice dummy",
"alias": null,
Expand Down
4 changes: 4 additions & 0 deletions features/main/relation.feature
Expand Up @@ -162,6 +162,8 @@ Feature: Relations support
"jsonData": [],
"arrayData": [],
"name_converted": null,
"relatedOwnedDummy": null,
"relatedOwningDummy": null,
"id": 1,
"name": "Dummy with relations",
"alias": null,
Expand Down Expand Up @@ -544,6 +546,8 @@ Feature: Relations support
"jsonData":[],
"arrayData":[],
"name_converted":null,
"relatedOwnedDummy": null,
"relatedOwningDummy": null,
"id":2,
"name":"Dummy with plain relations",
"alias":null,
Expand Down
2 changes: 2 additions & 0 deletions features/security/strong_typing.feature
Expand Up @@ -33,6 +33,8 @@ Feature: Handle properly invalid data submitted to the API
"jsonData": [],
"arrayData": [],
"name_converted": null,
"relatedOwnedDummy": null,
"relatedOwningDummy": null,
"id": 1,
"name": "Not existing",
"alias": null,
Expand Down
2 changes: 2 additions & 0 deletions features/security/unknown_attributes.feature
Expand Up @@ -34,6 +34,8 @@ Feature: Ignore unknown attributes
"jsonData": [],
"arrayData": [],
"name_converted": null,
"relatedOwnedDummy": null,
"relatedOwningDummy": null,
"id": 1,
"name": "Not existing",
"alias": null,
Expand Down
11 changes: 11 additions & 0 deletions src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php
Expand Up @@ -13,9 +13,11 @@

namespace ApiPlatform\Core\Bridge\Doctrine\Orm\Filter;

use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryBuilderHelper;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Core\Exception\InvalidArgumentException;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;

/**
Expand Down Expand Up @@ -110,6 +112,15 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
return;
}

if ($metadata->isAssociationInverseSide($field)) {
$alias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $alias, $field, Join::LEFT_JOIN);

$queryBuilder
->andWhere(sprintf('%s %s NULL', $alias, $value ? 'IS NOT' : 'IS'));

return;
}

$queryBuilder
->andWhere(sprintf('%s.%s %s NULL', $alias, $field, $value ? 'IS NOT' : 'IS'));

Expand Down
48 changes: 48 additions & 0 deletions tests/Bridge/Doctrine/Orm/Filter/ExistsFilterTest.php
Expand Up @@ -265,6 +265,54 @@ public function provideApplyTestData(): array
],
sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummy IS NULL', Dummy::class),
],

'related owned association does not exist' => [
[
'relatedOwnedDummy' => null,
],
[
'relatedOwnedDummy' => [
'exists' => '0',
],
],
sprintf('SELECT o FROM %s o LEFT JOIN o.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1 IS NULL', Dummy::class),
],

'related owned association exists' => [
[
'relatedOwnedDummy' => null,
],
[
'relatedOwnedDummy' => [
'exists' => '1',
],
],
sprintf('SELECT o FROM %s o LEFT JOIN o.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1 IS NOT NULL', Dummy::class),
],

'related owning association does not exist' => [
[
'relatedOwningDummy' => null,
],
[
'relatedOwningDummy' => [
'exists' => '0',
],
],
sprintf('SELECT o FROM %s o WHERE o.relatedOwningDummy IS NULL', Dummy::class),
],

'related owning association exists' => [
[
'relatedOwningDummy' => null,
],
[
'relatedOwningDummy' => [
'exists' => '1',
],
],
sprintf('SELECT o FROM %s o WHERE o.relatedOwningDummy IS NOT NULL', Dummy::class),
],
];
}
}
38 changes: 38 additions & 0 deletions tests/Fixtures/TestBundle/Entity/Dummy.php
Expand Up @@ -152,6 +152,20 @@ class Dummy
*/
public $nameConverted;

/**
* @var RelatedOwnedDummy
*
* @ORM\OneToOne(targetEntity="RelatedOwnedDummy", cascade={"persist"}, mappedBy="owningDummy")
*/
public $relatedOwnedDummy;

/**
* @var RelatedOwningDummy
*
* @ORM\OneToOne(targetEntity="RelatedOwningDummy", cascade={"persist"}, inversedBy="ownedDummy")
*/
public $relatedOwningDummy;

public static function staticMethod()
{
}
Expand Down Expand Up @@ -274,6 +288,30 @@ public function addRelatedDummy(RelatedDummy $relatedDummy)
$this->relatedDummies->add($relatedDummy);
}

public function getRelatedOwnedDummy()
{
return $this->relatedOwnedDummy;
}

public function setRelatedOwnedDummy(RelatedOwnedDummy $relatedOwnedDummy)
{
$this->relatedOwnedDummy = $relatedOwnedDummy;

if ($this !== $this->relatedOwnedDummy->getOwningDummy()) {
$this->relatedOwnedDummy->setOwningDummy($this);
}
}

public function getRelatedOwningDummy()
{
return $this->relatedOwningDummy;
}

public function setRelatedOwningDummy(RelatedOwningDummy $relatedOwningDummy)
{
$this->relatedOwningDummy = $relatedOwningDummy;
}

/**
* @return bool
*/
Expand Down
90 changes: 90 additions & 0 deletions tests/Fixtures/TestBundle/Entity/RelatedOwnedDummy.php
@@ -0,0 +1,90 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;

/**
* Related Owned Dummy.
*
* @author Sergey V. Ryabov <sryabov@mhds.ru>
*
* @ApiResource(iri="https://schema.org/Product")
* @ORM\Entity
*/
class RelatedOwnedDummy
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

/**
* @var string A name
*
* @ORM\Column(nullable=true)
*/
public $name;

/**
* @var Dummy
*
* @ORM\OneToOne(targetEntity="Dummy", cascade={"persist"}, inversedBy="relatedOwnedDummy")
* @ORM\JoinColumn(nullable=false)
*/
public $owningDummy;

public function getId()
{
return $this->id;
}

public function setId($id)
{
$this->id = $id;
}

public function setName($name)
{
$this->name = $name;
}

public function getName()
{
return $this->name;
}

/**
* Get owning dummy.
*
* @return Dummy
*/
public function getOwningDummy()
{
return $this->owningDummy;
}

/**
* Set owning dummy.
*
* @param Dummy $owningDummy the value to set
*/
public function setOwningDummy(Dummy $owningDummy)
{
$this->owningDummy = $owningDummy;
}
}

0 comments on commit 96a2b2d

Please sign in to comment.