Skip to content

Commit

Permalink
Adding first stub of sub-path matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
Ocramius committed Jun 5, 2013
1 parent 63c20f0 commit ddcc94f
Show file tree
Hide file tree
Showing 5 changed files with 340 additions and 0 deletions.
79 changes: 79 additions & 0 deletions src/ZfrRest/Mvc/Router/Http/Matcher/AssociationSubPathMatcher.php
@@ -0,0 +1,79 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license.
*/

namespace ZfrRest\Mvc\Router\Http\Matcher;

use Doctrine\Common\Collections\Criteria;
use Zend\Http\Request;
use ZfrRest\Mvc\Exception;
use ZfrRest\Mvc\Exception\RuntimeException;
use ZfrRest\Resource\Resource;
use ZfrRest\Resource\ResourceInterface;

/**
* {@inheritDoc}
*
* @license MIT
* @author Marco Pivetta <ocramius@gmail.com>
*/
class AssociationSubPathMatcher implements SubPathMatcherInterface
{
public function matchSubPath(
ResourceInterface $resource,
$subPath,
Request $request
) {
if ($resource->isCollection()) {
return null;
}

$data = $resource->getData();

// @todo cannot handle non-object resources because of this hardcoding
if (! is_object($data)) {
return null;
}

$resourceMetadata = $resource->getMetadata();
$associationName = array_shift(explode('/', trim($subPath, '/')));

if (! $resourceMetadata->hasAssociation($associationName)) {
return null;
}

$classMetadata = $resourceMetadata->getClassMetadata();
$reflectionClass = $classMetadata->getReflectionClass();
// @todo Using reflection directly? Maybe should be coded in the metadata interface instead?
$reflectionProperty = $reflectionClass->getProperty($associationName);
$associationMetadata = $resourceMetadata->getAssociationMetadata($associationName);

$reflectionProperty->setAccessible(true);

$associationData = $reflectionProperty->getValue($data);

// @todo null and scalars won't work as a resource here!
if (! $associationMetadata->getClassMetadata()->getReflectionClass()->isInstance($associationData)) {
return null;
}

return new SubPathMatch(
new Resource($associationData, $associationMetadata),
substr($subPath, strpos($subPath, $associationName), strlen($associationName))
);
}
}
71 changes: 71 additions & 0 deletions src/ZfrRest/Mvc/Router/Http/Matcher/BaseSubPathMatcher.php
@@ -0,0 +1,71 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license.
*/

namespace ZfrRest\Mvc\Router\Http\Matcher;

use Doctrine\Common\Collections\Criteria;
use Zend\Http\Request;
use ZfrRest\Mvc\Exception;
use ZfrRest\Mvc\Exception\RuntimeException;
use ZfrRest\Resource\ResourceInterface;

/**
* {@inheritDoc}
*
* Base sub-path matcher - passes the sub-path to either an association or
* a collection matcher depending on the case
*
* @license MIT
* @author Marco Pivetta <ocramius@gmail.com>
*/
class BaseSubPathMatcher implements SubPathMatcherInterface
{
public function __construct()
{
$this->collectionMatcher = new CollectionSubPathMatcher();
$this->associationMatcher = new AssociationSubPathMatcher();
}

public function matchSubPath(
ResourceInterface $resource,
$subPath,
Request $request
) {
$path = trim($subPath, '/');

if (empty($path)) {
return new SubPathMatch($resource, $subPath);
}

if ($resource->isCollection()) {
$match = $this->collectionMatcher->matchSubPath($resource, $path, $request);
} else {
$match = $this->associationMatcher->matchSubPath($resource, $path, $request);
}

if (! $match) {
return null;
}

return $this->matchSubPath(
$match->matchedResource,
substr($path, strlen($match->matchedPath)),
$request
);
}
}
100 changes: 100 additions & 0 deletions src/ZfrRest/Mvc/Router/Http/Matcher/CollectionSubPathMatcher.php
@@ -0,0 +1,100 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license.
*/

namespace ZfrRest\Mvc\Router\Http\Matcher;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Selectable;
use Zend\Http\Request;
use Zend\Stdlib\ArrayUtils;
use ZfrRest\Mvc\Exception;
use ZfrRest\Mvc\Exception\RuntimeException;
use ZfrRest\Resource\Resource;
use ZfrRest\Resource\ResourceInterface;

/**
* {@inheritDoc}
*
* @license MIT
* @author Marco Pivetta <ocramius@gmail.com>
*/
class CollectionSubPathMatcher implements SubPathMatcherInterface
{
public function matchSubPath(
ResourceInterface $resource,
$subPath,
Request $request
) {
if (! $resource->isCollection()) {
return null;
}

$path = trim($subPath, '/');

if (empty($path)) {
return new SubPathMatch($this->filterAssociation($resource, $request), $subPath);
}

$identifier = array_shift(explode('/', trim($subPath, '/')));
$classMetadata = $resource->getMetadata()->getClassMetadata();
$data = $this->findItem($resource->getData(), $classMetadata->getIdentifierFieldNames(), $identifier);

if (null === $data) {
// @todo is a null value actually valid?
return null;
}

return new SubPathMatch(
new Resource($data, $resource->getMetadata()),
substr($subPath, strpos($subPath, $identifier), strlen($identifier))
);
}

protected function findItem($data, array $identifierNames, $identifier)
{
if (count($identifierNames) > 1) {
// @todo Cannot match multiple identifiers for now
return null;
}

if (!$data instanceof Selectable) {
if (! ($data instanceof \Traversable || is_array($data))) {
// @todo cannot match on non-selectables?
return null;
}

$data = new ArrayCollection(ArrayUtils::iteratorToArray($data));
}

if (! $data instanceof Selectable) {
// @todo should probably also handle repositories with no Selectable API
return null;
}

return $data->matching(
new Criteria(Criteria::expr()->eq(current($identifierNames), $identifier))
)->first();
}

protected function filterAssociation(ResourceInterface $resource, Request $request)
{
// @todo add collection filtering via GET parameters
return $resource;
}
}
43 changes: 43 additions & 0 deletions src/ZfrRest/Mvc/Router/Http/Matcher/SubPathMatch.php
@@ -0,0 +1,43 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license.
*/

namespace ZfrRest\Mvc\Router\Http\Matcher;

use Doctrine\Common\Collections\Criteria;
use Zend\Http\Request;
use ZfrRest\Mvc\Exception;
use ZfrRest\Mvc\Exception\RuntimeException;
use ZfrRest\Resource\ResourceInterface;

/**
* {@inheritDoc}
*
* @license MIT
* @author Marco Pivetta <ocramius@gmail.com>
*/
class SubPathMatch
{
public $matchedPath;
public $matchedResource;

public function __construct(ResourceInterface $matchedResource = null, $matchedPath = null)
{
$this->matchedResource = $matchedResource;
$this->matchedPath = $matchedPath;
}
}
47 changes: 47 additions & 0 deletions src/ZfrRest/Mvc/Router/Http/Matcher/SubPathMatcherInterface.php
@@ -0,0 +1,47 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license.
*/

namespace ZfrRest\Mvc\Router\Http\Matcher;

use Doctrine\Common\Collections\Criteria;
use Zend\Http\Request;
use ZfrRest\Mvc\Exception;
use ZfrRest\Mvc\Exception\RuntimeException;
use ZfrRest\Resource\ResourceInterface;

/**
* Association matcher - builds a resource from a given resource and an association
*
* @license MIT
* @author Marco Pivetta <ocramius@gmail.com>
*/
interface SubPathMatcherInterface
{
/**
* @param ResourceInterface $resource
* @param $subPath
* @param Request $request
*
* @return SubPathMatch|null
*/
public function matchSubPath(
ResourceInterface $resource,
$subPath,
Request $request
);
}

0 comments on commit ddcc94f

Please sign in to comment.