Skip to content

Commit

Permalink
Add the ability to join to a strongly typed document without includin…
Browse files Browse the repository at this point in the history
…g its class or classparents in the query -- doctrine#658.
  • Loading branch information
sarcher committed Nov 1, 2015
1 parent 9ae7031 commit 8b31a12
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 17 deletions.
11 changes: 10 additions & 1 deletion lib/Doctrine/ODM/PHPCR/Query/Builder/ConverterPhpcr.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ public function getQuery(QueryBuilder $builder)
);
}

// be explicit in what we select
if (empty($this->columns)) {
foreach (array_keys($this->sourceDocumentNodes) as $selectorName) {
$this->columns[] = $this->qomf->column($selectorName);
}
}

// for each document source add phpcr:{class,classparents} restrictions
foreach ($this->sourceDocumentNodes as $sourceNode) {
$documentFqn = $this->aliasMetadata[$sourceNode->getAlias()]->getName();
Expand Down Expand Up @@ -265,7 +272,9 @@ protected function walkSourceDocument(SourceDocument $node)
// make sure we add the phpcr:{class,classparents} constraints
// From is dispatched first, so these will always be the primary
// constraints.
$this->sourceDocumentNodes[$alias] = $node;
if (!$node->getStronglyTyped()) {
$this->sourceDocumentNodes[$alias] = $node;
}

// cache the metadata for this document
/** @var $meta ClassMetadata */
Expand Down
9 changes: 8 additions & 1 deletion lib/Doctrine/ODM/PHPCR/Query/Builder/SourceDocument.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ class SourceDocument extends AbstractLeafNode
{
protected $documentFqn;
protected $alias;
protected $stronglyTyped;

public function __construct(AbstractNode $parent, $documentFqn, $alias)
public function __construct(AbstractNode $parent, $documentFqn, $alias, $stronglyTyped = false)
{
if (!is_string($alias) || empty($alias)) {
throw new InvalidArgumentException(sprintf(
Expand All @@ -20,6 +21,7 @@ public function __construct(AbstractNode $parent, $documentFqn, $alias)

$this->documentFqn = $documentFqn;
$this->alias = $alias;
$this->stronglyTyped = $stronglyTyped;
parent::__construct($parent);
}

Expand All @@ -33,6 +35,11 @@ public function getAlias()
return $this->alias;
}

public function getStronglyTyped()
{
return $this->stronglyTyped;
}

public function getNodeType()
{
return self::NT_SOURCE;
Expand Down
4 changes: 2 additions & 2 deletions lib/Doctrine/ODM/PHPCR/Query/Builder/SourceFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ public function getCardinalityMap()
* @factoryMethod SourceDocument
* @return SourceDocument
*/
public function document($documentFqn, $alias)
public function document($documentFqn, $alias, $stronglyTyped = false)
{
return $this->addChild(new SourceDocument($this, $documentFqn, $alias));
return $this->addChild(new SourceDocument($this, $documentFqn, $alias, $stronglyTyped));
}

/**
Expand Down
73 changes: 73 additions & 0 deletions tests/Doctrine/Tests/Models/CMS/CmsProfile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace Doctrine\Tests\Models\CMS;

use Doctrine\ODM\PHPCR\DocumentRepository;
use Doctrine\ODM\PHPCR\Id\RepositoryIdInterface;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;

/**
* @PHPCRODM\Document(
* nodeType="phpcr:cms_profile",
* referenceable=true,
* repositoryClass="Doctrine\Tests\Models\CMS\CmsProfileRepository"
* )
*/
class CmsProfile
{
/** @PHPCRODM\Id(strategy="repository") */
public $id;

/** @PHPCRODM\Uuid */
public $uuid;

/** @PHPCRODM\Field(type="string") */
public $data;

/** @PHPCRODM\ReferenceOne(targetDocument="CmsUser", cascade="persist") */
public $user;

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

public function getUuid()
{
return $this->uuid;
}

public function setData($data)
{
$this->data = $data;
}

public function getData()
{
return $this->data;
}

public function setUser(CmsUser $user)
{
$this->user = $user;
}

public function getUser()
{
return $this->user;
}
}

class CmsProfileRepository extends DocumentRepository implements RepositoryIdInterface
{
/**
* Generate a document id
*
* @param object $document
* @return string
*/
public function generateId($document, $parent = null)
{
return '/functional/' . $document->user->username . '/' . $document->data;
}
}
13 changes: 13 additions & 0 deletions tests/Doctrine/Tests/Models/CMS/CmsUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class CmsUser
public $articles;
/** @PHPCRODM\ReferenceMany(targetDocument="CmsGroup") */
public $groups;
/** @PHPCRODM\ReferenceMany(targetDocument="CmsProfile") */
public $profiles;
/** @PHPCRODM\Children() */
public $children;
/** @PHPCRODM\Child(nodeName="assistant", cascade="persist") */
Expand Down Expand Up @@ -82,6 +84,17 @@ public function getGroups()
{
return $this->groups;
}

public function addProfile(CmsProfile $profile)
{
$this->profiles[] = $profile;
$profile->setUser($this);
}

public function getProfiles()
{
return $this->profiles;
}
}

class CmsUserRepository extends DocumentRepository implements RepositoryIdInterface
Expand Down
46 changes: 37 additions & 9 deletions tests/Doctrine/Tests/ODM/PHPCR/Functional/QueryBuilderJoinTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Doctrine\ODM\PHPCR\Query\Builder\AbstractNode as QBConstants;
use Doctrine\Tests\ODM\PHPCR\PHPCRFunctionalTestCase;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsProfile;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\CMS\CmsAuditItem;

Expand All @@ -23,6 +24,9 @@ public function setUp()

$this->resetFunctionalNode($this->dm);

$ntm = $this->dm->getPhpcrSession()->getWorkspace()->getNodeTypeManager();
$ntm->registerNodeTypesCnd('[phpcr:cms_profile] > nt:unstructured', true);

$address1 = new CmsAddress;
$address1->country = 'France';
$address1->city = 'Lyon';
Expand All @@ -40,6 +44,11 @@ public function setUp()
$user->address = $address1;
$this->dm->persist($user);

$profile = new CmsProfile();
$profile->setData('testdata');
$user->addProfile($profile);
$this->dm->persist($profile);

$user = new CmsUser;
$user->username = 'winstonsmith';
$user->address = $address2;
Expand Down Expand Up @@ -123,15 +132,6 @@ public function provideEquiJoinInner()
),
),

array(
'LeftOuter',
'CmsUser', 'CmsAuditItem',
array('a.username' => 'anonymous'), array(
array(
'a' => '/functional/anonymous'
),
),
),
array(
'LeftOuter',
'CmsAuditItem', 'CmsUser',
Expand Down Expand Up @@ -207,4 +207,32 @@ public function testEquiJoinInner($joinType, $leftClass, $rightClass, $criteria
}
}
}

/**
* Verify that using an outer join on a document that is strongly typed
* results in the full expected result set.
*/
public function testStronglyTypedOuterJoin()
{
$qb = $this->dm->createQueryBuilder();
->fromDocument('Doctrine\Tests\Models\CMS\CmsUser', 'u')
->addJoinLeftOuter()
->right()->document('Doctrine\Tests\Models\CMS\CmsProfile', 'p', true)->end()
->condition()->equi('u.profiles', 'p.uuid')
->where()->orX()
->eq()->field('u.username')->literal('winstonsmith')->end()
->eq()->field('p.data')->literal('testdata')->end()
->end()->end()
->orderBy()->asc()->field('u.username');

$q = $qb->getQuery();
$phpcrQuery = $q->getPhpcrQuery();
$phpcrRes = $phpcrQuery->execute();
$phpcrRows = $phpcrRes->getRows();

$this->assertCount(2, $phpcrRows);
foreach ($phpcrRows as $key => $row) {
$this->assertEquals($key == 0 ? '/functional/dantleech' : '/functional/winstonsmith', $row->getPath('u'));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public function testComposite()

switch ($qb->getQuery()->getLanguage()) {
case 'JCR-SQL2':
$query = "SELECT * FROM [nt:unstructured] AS a WHERE ((a.username = 'dtl' OR a.username = 'js') AND (a.[phpcr:class] = 'Doctrine\Tests\Models\Blog\User' OR a.[phpcr:classparents] = 'Doctrine\Tests\Models\Blog\User'))";
$query = "SELECT a.* FROM [nt:unstructured] AS a WHERE ((a.username = 'dtl' OR a.username = 'js') AND (a.[phpcr:class] = 'Doctrine\Tests\Models\Blog\User' OR a.[phpcr:classparents] = 'Doctrine\Tests\Models\Blog\User'))";
break;
case 'sql':
$query = "SELECT s FROM nt:unstructured AS a WHERE (a.username = 'dtl' OR a,username = 'js')";
Expand All @@ -193,7 +193,7 @@ public function testComposite()

switch ($qb->getQuery()->getLanguage()) {
case 'JCR-SQL2':
$query = "SELECT * FROM [nt:unstructured] AS a WHERE (((a.username = 'dtl' OR a.username = 'js') AND a.name = 'foobar') AND (a.[phpcr:class] = 'Doctrine\Tests\Models\Blog\User' OR a.[phpcr:classparents] = 'Doctrine\Tests\Models\Blog\User'))";
$query = "SELECT a.* FROM [nt:unstructured] AS a WHERE (((a.username = 'dtl' OR a.username = 'js') AND a.name = 'foobar') AND (a.[phpcr:class] = 'Doctrine\Tests\Models\Blog\User' OR a.[phpcr:classparents] = 'Doctrine\Tests\Models\Blog\User'))";
break;
case 'sql':
$this->markTestIncomplete('Not testing SQL for sql query language');
Expand All @@ -212,7 +212,7 @@ public function testComposite()

switch ($qb->getQuery()->getLanguage()) {
case 'JCR-SQL2':
$query = "SELECT * FROM [nt:unstructured] AS a WHERE ((((a.username = 'dtl' OR a.username = 'js') AND a.name = 'foobar') OR a.name = 'johnsmith') AND (a.[phpcr:class] = 'Doctrine\Tests\Models\Blog\User' OR a.[phpcr:classparents] = 'Doctrine\Tests\Models\Blog\User'))";
$query = "SELECT a.* FROM [nt:unstructured] AS a WHERE ((((a.username = 'dtl' OR a.username = 'js') AND a.name = 'foobar') OR a.name = 'johnsmith') AND (a.[phpcr:class] = 'Doctrine\Tests\Models\Blog\User' OR a.[phpcr:classparents] = 'Doctrine\Tests\Models\Blog\User'))";
break;
case 'sql':
$this->markTestIncomplete('Not testing SQL for sql query language');
Expand Down Expand Up @@ -399,7 +399,7 @@ public function testSameNode()
}

/**
* @expectedException InvalidArgumentException
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Alias name "a" is not known
*/
public function testConditionWithNonExistingAlias()
Expand Down

0 comments on commit 8b31a12

Please sign in to comment.