Skip to content

Commit

Permalink
Merge pull request #138 from zf-fr/repository-manager
Browse files Browse the repository at this point in the history
Add an object repository plugin manager
  • Loading branch information
bakura10 committed Mar 21, 2014
2 parents d14aef3 + ec44e7a commit ce3346e
Show file tree
Hide file tree
Showing 10 changed files with 307 additions and 58 deletions.
1 change: 1 addition & 0 deletions config/module.config.php
Expand Up @@ -28,6 +28,7 @@
'ZfrRest\Mvc\Controller\MethodHandler\MethodHandlerPluginManager' => 'ZfrRest\Factory\MethodHandlerPluginManagerFactory',
'ZfrRest\Options\ModuleOptions' => 'ZfrRest\Factory\ModuleOptionsFactory',
'ZfrRest\Resource\Metadata\ResourceMetadataFactory' => 'ZfrRest\Factory\ResourceMetadataFactoryFactory',
'ZfrRest\Resource\ResourcePluginManager' => 'ZfrRest\Factory\ResourcePluginManagerFactory',
'ZfrRest\Router\Http\Matcher\AssociationSubPathMatcher' => 'ZfrRest\Factory\AssociationSubPathMatcherFactory',
'ZfrRest\Router\Http\Matcher\BaseSubPathMatcher' => 'ZfrRest\Factory\BaseSubPathMatcherFactory',
'ZfrRest\View\Strategy\ResourceStrategy' => 'ZfrRest\Factory\ResourceStrategyFactory'
Expand Down
25 changes: 3 additions & 22 deletions docs/02. Quick Start.md
Expand Up @@ -122,7 +122,7 @@ return [
'type' => 'ResourceGraphRoute',
'options' => [
'route' => '/users',
'resource' => 'UserObjectRepository'
'resource' => 'User\Entity\User'
]
]
]
Expand All @@ -131,27 +131,8 @@ return [
```

This code says to ZfrRest that we have an entry point whose URL is `/users`, and that the initial resource is a
Doctrine object repository. This service is pulled from the global service manager. Internally, ZfrRest uses the
Doctrine Criteria API to traverse the hierarchy, that's why we need an object that implements
the `Doctrine\Common\Collections\Selectable` interface.

Add the following config, so that ZfrRest can properly retrieve the object repository:

```php
return [
'service_manager' => [
'factories' => [
'UserObjectRepository' => function($sm) {
$objectManager = $sm->get('doctrine.entitymanager.orm_default');
return $objectManager->getRepository('Application\Entity\User');
}
]
]
];
```

> If you don't want to define a factory for each repository, you can create an abstract factory, but this is beyond
the scope of this quick start.
User entity. Internally, ZfrRest automatically fetches a Doctrine object repository from the entity name, and uses
the Doctrine Criteria API to traverse the hierarchy.

## Adding controllers

Expand Down
16 changes: 12 additions & 4 deletions src/ZfrRest/Factory/ResourceGraphRouteFactory.php
Expand Up @@ -59,15 +59,23 @@ public function setCreationOptions(array $creationOptions)
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$parentLocator = $serviceLocator->getServiceLocator();
$resource = $parentLocator->get($this->creationOptions['resource']);
/* @var ServiceLocatorInterface $parentLocator */
$parentLocator = $serviceLocator->getServiceLocator();

/* @var \ZfrRest\Resource\Metadata\ResourceMetadataFactory $metadataFactory */
$metadataFactory = $parentLocator->get('ZfrRest\Resource\Metadata\ResourceMetadataFactory');
$matcher = $parentLocator->get('ZfrRest\Router\Http\Matcher\BaseSubPathMatcher');

/* @var \ZfrRest\Resource\ResourcePluginManager $pluginManager */
$pluginManager = $parentLocator->get('ZfrRest\Resource\ResourcePluginManager');

/** @var \ZfrRest\Router\Http\Matcher\BaseSubPathMatcher $matcher */
$matcher = $parentLocator->get('ZfrRest\Router\Http\Matcher\BaseSubPathMatcher');

return new ResourceGraphRoute(
$metadataFactory,
$pluginManager,
$matcher,
$resource,
$this->creationOptions['resource'],
$this->creationOptions['route']
);
}
Expand Down
49 changes: 49 additions & 0 deletions src/ZfrRest/Factory/ResourcePluginManagerFactory.php
@@ -0,0 +1,49 @@
<?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\Factory;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use ZfrRest\Resource\ResourcePluginManager;

/**
* Factory to create an ResourcePluginManager
*
* @author Michaël Gallego <mic.gallego@gmail.com>
* @licence MIT
*/
class ResourcePluginManagerFactory implements FactoryInterface
{
/**
* {@inheritDoc}
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
/* @var \ZfrRest\Options\ModuleOptions $moduleOptions */
$moduleOptions = $serviceLocator->get('ZfrRest\Options\ModuleOptions');

/* @var \Doctrine\Common\Persistence\ObjectManager $objectManager */
$objectManager = $serviceLocator->get($moduleOptions->getObjectManager());

$pluginManager = new ResourcePluginManager($objectManager);
$pluginManager->setServiceLocator($serviceLocator);

return $pluginManager;
}
}
88 changes: 88 additions & 0 deletions src/ZfrRest/Resource/ResourcePluginManager.php
@@ -0,0 +1,88 @@
<?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\Resource;

use Doctrine\Common\Persistence\ObjectManager;
use Zend\ServiceManager\AbstractPluginManager;
use Zend\ServiceManager\Exception;
use ZfrRest\Exception\RuntimeException;

/**
* Plugin manager that allows to create object repository, without having to define a factory
* for each entity. This is used also to improve performance, so that the router can lazy-load
* the creation of repositories
*
* @author Michaël Gallego <mic.gallego@gmail.com>
* @licence MIT
*/
class ResourcePluginManager extends AbstractPluginManager
{
/**
* @var ObjectManager
*/
protected $objectManager;

/**
* @param ObjectManager $objectManager
*/
public function __construct(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}

/**
* Get the repository
*
* @param string $name
* @return object
*/
public function get($name)
{
// First check if an explicit resource was set
if ($this->has($name)) {
return parent::get($name);
}

// Otherwise, fallback to getting an object repository
return $this->objectManager->getRepository($name);
}

/**
* {@inheritDoc}
*/
public function validatePlugin($plugin)
{
if (is_object($plugin)) {
return; // we're okay
}

throw new RuntimeException(sprintf(
'An object was expected; "%s" given',
gettype($plugin)
));
}

/**
* {@inheritDoc}
*/
protected function canonicalizeName($name)
{
return $name;
}
}
58 changes: 32 additions & 26 deletions src/ZfrRest/Router/Http/ResourceGraphRoute.php
Expand Up @@ -18,16 +18,14 @@

namespace ZfrRest\Router\Http;

use Doctrine\Common\Persistence\ObjectRepository;
use Metadata\MetadataFactory;
use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerAwareTrait;
use Zend\Http\Request as HttpRequest;
use Zend\Mvc\Router\Http\RouteInterface;
use Zend\Mvc\Router\Http\RouteMatch;
use Zend\Stdlib\RequestInterface;
use ZfrRest\Resource\Resource;
use ZfrRest\Resource\ResourceInterface;
use ZfrRest\Resource\ResourcePluginManager;
use ZfrRest\Router\Exception\RuntimeException;
use ZfrRest\Router\Http\Matcher\BaseSubPathMatcher;
use ZfrRest\Router\Http\Matcher\SubPathMatch;
Expand All @@ -37,15 +35,18 @@
* @author Marco Pivetta <ocramius@gmail.com>
* @author Michaël Gallego <mic.gallego@gmail.com>
*/
class ResourceGraphRoute implements RouteInterface, EventManagerAwareInterface
class ResourceGraphRoute implements RouteInterface
{
use EventManagerAwareTrait;

/**
* @var MetadataFactory
*/
protected $metadataFactory;

/**
* @var ResourcePluginManager
*/
protected $resourcePluginManager;

/**
* @var mixed
*/
Expand All @@ -64,17 +65,24 @@ class ResourceGraphRoute implements RouteInterface, EventManagerAwareInterface
/**
* Constructor
*
* @param MetadataFactory $metadataFactory
* @param BaseSubPathMatcher $matcher
* @param mixed $resource
* @param string $route
* @param MetadataFactory $metadataFactory
* @param ResourcePluginManager $resourcePluginManager
* @param BaseSubPathMatcher $matcher
* @param mixed $resource
* @param string $route
*/
public function __construct(MetadataFactory $metadataFactory, BaseSubPathMatcher $matcher, $resource, $route)
{
$this->metadataFactory = $metadataFactory;
$this->subPathMatcher = $matcher;
$this->resource = $resource;
$this->route = $route;
public function __construct(
MetadataFactory $metadataFactory,
ResourcePluginManager $resourcePluginManager,
BaseSubPathMatcher $matcher,
$resource,
$route
) {
$this->metadataFactory = $metadataFactory;
$this->resourcePluginManager = $resourcePluginManager;
$this->subPathMatcher = $matcher;
$this->resource = $resource;
$this->route = $route;
}

/**
Expand Down Expand Up @@ -211,9 +219,7 @@ protected function buildRouteMatch(SubPathMatch $match, $pathLength)
}

/**
* Initialize the resource to create an object implementing the ResourceInterface interface. A resource can
* be anything: an entity, a collection, a Selectable... However, any ResourceInterface object contains both
* the resource AND metadata associated to it. This metadata is usually extracted from the entity name
* Initialize the resource to create an object implementing the ResourceInterface interface
*
* @throws RuntimeException
* @return ResourceInterface
Expand All @@ -225,17 +231,17 @@ private function getResource()
return $this->resource;
}

if ($this->resource instanceof ObjectRepository) {
$metadata = $this->metadataFactory->getMetadataForClass($this->resource->getClassName());
} elseif (is_string($this->resource)) {
$metadata = $this->metadataFactory->getMetadataForClass($this->resource);
} else {
if (!is_string($this->resource)) {
throw new RuntimeException(sprintf(
'Resource "%s" is not supported: either specify an ObjectRepository instance, or an entity class name',
'Resource "%s" is not supported: you must specify an entity class name',
is_object($this->resource) ? get_class($this->resource) : gettype($this->resource)
));
}

return $this->resource = new Resource($this->resource, $metadata);
// Lazy-load the object repository for the resource class name
$repository = $this->resourcePluginManager->get($this->resource);
$metadata = $this->metadataFactory->getMetadataForClass($this->resource);

return $this->resource = new Resource($repository, $metadata);
}
}
8 changes: 4 additions & 4 deletions tests/ZfrRestTest/Factory/ResourceGraphRouteFactoryTest.php
Expand Up @@ -59,14 +59,14 @@ public function testCreateFromFactory()
$pluginManager = new RoutePluginManager();
$pluginManager->setServiceLocator($serviceManager);

$serviceManager->setService(
'resource',
$this->getMock('ZfrRest\Resource\ResourceInterface')
);
$serviceManager->setService(
'ZfrRest\Resource\Metadata\ResourceMetadataFactory',
$this->getMock('Metadata\MetadataFactory', [], [], '', false)
);
$serviceManager->setService(
'ZfrRest\Resource\ResourcePluginManager',
$this->getMock('ZfrRest\Resource\ResourcePluginManager', [], [], '', false)
);
$serviceManager->setService(
'ZfrRest\Router\Http\Matcher\BaseSubPathMatcher',
$this->getMock('ZfrRest\Router\Http\Matcher\BaseSubPathMatcher', [], [], '', false)
Expand Down
49 changes: 49 additions & 0 deletions tests/ZfrRestTest/Factory/ResourcePluginManagerFactoryTest.php
@@ -0,0 +1,49 @@
<?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 ZfrRestTest\Factory;

use PHPUnit_Framework_TestCase;
use Zend\ServiceManager\ServiceManager;
use ZfrRest\Factory\ResourcePluginManagerFactory;
use ZfrRest\Options\ModuleOptions;

/**
* @licence MIT
* @author Michaël Gallego <mic.gallego@gmail.com>
*
* @group Coverage
* @covers \ZfrRest\Factory\ResourcePluginManagerFactory
*/
class ResourcePluginManagerFactoryTest extends PHPUnit_Framework_TestCase
{
public function testCreateFromFactory()
{
$moduleOptions = new ModuleOptions(['object_manager' => 'my_object_manager']);

$serviceManager = new ServiceManager();
$serviceManager->setService('ZfrRest\Options\ModuleOptions', $moduleOptions);
$serviceManager->setService('my_object_manager', $this->getMock('Doctrine\Common\Persistence\ObjectManager'));

$factory = new ResourcePluginManagerFactory();
$result = $factory->createService($serviceManager);

$this->assertInstanceOf('ZfrRest\Resource\ResourcePluginManager', $result);
$this->assertSame($serviceManager, $result->getServiceLocator());
}
}

0 comments on commit ce3346e

Please sign in to comment.