Skip to content

Commit

Permalink
Merge pull request #167 from zf-fr/one-to-one
Browse files Browse the repository at this point in the history
Add support for OneToOne
  • Loading branch information
bakura10 committed May 29, 2014
2 parents 3000c37 + 202976c commit b0103a3
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 8 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,10 @@
# CHANGELOG

## 0.3.2

* Added support for OneToOne association (assuming you have a "User" entity with a OneToOne association to
a "Card" entity, you can now do a POST request like "/users/4/card")

## 0.3.1

* ZfrRest now returns input errors correctly if no data was given in the body
Expand Down
17 changes: 12 additions & 5 deletions src/ZfrRest/Router/Http/Matcher/AssociationSubPathMatcher.php
Expand Up @@ -82,11 +82,18 @@ public function matchSubPath(ResourceInterface $resource, $subPath, SubPathMatch
$reflectionProperty->setAccessible(true);

$associationData = $reflectionProperty->getValue($resource->getData());
$terminal = false;

return new SubPathMatch(
new Resource($associationData, $associationResourceMetadata),
$associationPath,
$previousMatch
);
if ($associationData === null && $classMetadata->isSingleValuedAssociation($associationName)) {
$resource = $associationResourceMetadata->createResource();

// We set this match as terminal, so that paths like "/user/4/twitter/tweets/123" don't end
// up creating a lot of "non-existant" resources, that would be very strange to handle
$terminal = true;
} else {
$resource = new Resource($associationData, $associationResourceMetadata);
}

return new SubPathMatch($resource, $associationPath, $previousMatch, $terminal);
}
}
2 changes: 1 addition & 1 deletion src/ZfrRest/Router/Http/Matcher/BaseSubPathMatcher.php
Expand Up @@ -59,7 +59,7 @@ public function matchSubPath(ResourceInterface $resource, $subPath, SubPathMatch
$subPath = trim($subPath, '/');

// We have traversed the whole path, return the last matched path!
if (empty($subPath)) {
if (empty($subPath) || ($previousMatch && $previousMatch->isTerminal())) {
return $previousMatch ?: new SubPathMatch($resource, $subPath);
}

Expand Down
25 changes: 23 additions & 2 deletions src/ZfrRest/Router/Http/Matcher/SubPathMatch.php
Expand Up @@ -44,16 +44,27 @@ class SubPathMatch
*/
protected $previousMatch;

/**
* @var bool
*/
protected $terminal;

/**
* @param ResourceInterface $matchedResource
* @param string $matchedPath
* @param SubPathMatch|null $previousMatch
* @param bool $terminal
*/
public function __construct(ResourceInterface $matchedResource, $matchedPath, SubPathMatch $previousMatch = null)
{
public function __construct(
ResourceInterface $matchedResource,
$matchedPath,
SubPathMatch $previousMatch = null,
$terminal = false
) {
$this->matchedResource = $matchedResource;
$this->matchedPath = $matchedPath;
$this->previousMatch = $previousMatch;
$this->terminal = (bool) $terminal;
}

/**
Expand Down Expand Up @@ -85,4 +96,14 @@ public function getPreviousMatch()
{
return $this->previousMatch;
}

/**
* Get if this sub path match is a terminal path
*
* @return bool
*/
public function isTerminal()
{
return $this->terminal;
}
}
Expand Up @@ -160,6 +160,7 @@ public function testCanMatchAssociation($subPath, $associationPath, $propertyNam
$this->assertSame($associationMetadata, $result->getMatchedResource()->getMetadata());
$this->assertEquals($associationPath, $result->getMatchedPath());
$this->assertNull($result->getPreviousMatch());
$this->assertFalse($result->isTerminal());
}

public function testWontMatchWhenRoutableIsSetToFalse()
Expand All @@ -180,4 +181,66 @@ public function testWontMatchWhenRoutableIsSetToFalse()

$this->assertNull($this->associationMatcher->matchSubPath($resource, 'bar'));
}

public function testCanCreateEmptyResourceForSingleValuedAssociation()
{
$resource = $this->getMock('ZfrRest\Resource\ResourceInterface');
$metadata = $this->getMock('ZfrRest\Resource\Metadata\ResourceMetadataInterface');

$data = new AssociationMatcherEntity();

$resource->expects($this->once())->method('getData')->will($this->returnValue($data));
$resource->expects($this->once())->method('getMetadata')->will($this->returnValue($metadata));
$metadata->expects($this->once())
->method('hasAssociationMetadata')
->with('card')
->will($this->returnValue(true));

$metadata->expects($this->once())
->method('getAssociationMetadata')
->with('card')
->will($this->returnValue([
'routable' => true,
'propertyName' => 'card',
'path' => 'card'
]));

$classMetadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata');
$classMetadata->expects($this->once())
->method('getAssociationTargetClass')
->with('card')
->will($this->returnValue('Card'));

$associationMetadata = $this->getMock('ZfrRest\Resource\Metadata\ResourceMetadataInterface');
$associationMetadata->expects($this->once())
->method('createResource')
->will($this->returnValue($this->getMock('ZfrRest\Resource\ResourceInterface')));

$this->metadataFactory->expects($this->once())
->method('getMetadataForClass')
->with('Card')
->will($this->returnValue($associationMetadata));

$reflectionProperty = $this->getMock('ReflectionProperty', [], [], '', false);
$reflectionProperty->expects($this->once())->method('getValue')->will($this->returnValue(null));

$reflectionClass = $this->getMock('ReflectionClass', [], [], '', false);
$reflectionClass->expects($this->once())->method('getProperty')->will($this->returnValue($reflectionProperty));

$metadata->expects($this->once())
->method('getReflectionClass')
->will($this->returnValue($reflectionClass));

$metadata->expects($this->once())->method('getClassMetadata')->will($this->returnValue($classMetadata));
$classMetadata->expects($this->once())
->method('isSingleValuedAssociation')
->with('card')
->will($this->returnValue(true));

$result = $this->associationMatcher->matchSubPath($resource, 'card');

$this->assertInstanceOf('ZfrRest\Router\Http\Matcher\SubPathMatch', $result);
$this->assertInstanceOf('ZfrRest\Resource\ResourceInterface', $result->getMatchedResource());
$this->assertTrue($result->isTerminal());
}
}
14 changes: 14 additions & 0 deletions tests/ZfrRestTest/Router/Http/Matcher/BaseSubPathMatcherTest.php
Expand Up @@ -129,4 +129,18 @@ public function testCanMatch($subPath)
$this->assertSame($secondMatch, $result);
$this->assertSame($secondMatchedResource, $result->getMatchedResource());
}

public function testReturnsMatchIfTerminalEvenIfPathIsNotFullyConsumed()
{
$baseResource = $this->getMock('ZfrRest\Resource\ResourceInterface');
$previousMatch = $this->getMock('ZfrRest\Router\Http\Matcher\SubPathMatch', [], [], '', false);
$previousMatch->expects($this->once())->method('isTerminal')->will($this->returnValue(true));

$this->associationMatcher->expects($this->never())->method('matchSubPath');
$this->collectionMatcher->expects($this->never())->method('matchSubPath');

$result = $this->baseMatcher->matchSubPath($baseResource, '/tweets/123', $previousMatch);

$this->assertSame($result, $previousMatch);
}
}

0 comments on commit b0103a3

Please sign in to comment.