Skip to content

Commit

Permalink
Merge pull request api-platform#547 from polc/master
Browse files Browse the repository at this point in the history
Add configurable naming strategy
  • Loading branch information
dunglas committed May 31, 2016
2 parents 649a2f2 + 9c945b3 commit 3bf6671
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ public function load(array $configs, ContainerBuilder $container)
}
}

$container->setAlias('api_platform.routing.resource_path_generator', $config['routing']['resource_path_generator']);

if ($config['name_converter']) {
$container->setAlias('api_platform.name_converter', $config['name_converter']);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ public function getConfigTreeBuilder()
->end()
->end()
->end()
->arrayNode('routing')
->addDefaultsIfNotSet()
->children()
->scalarNode('resource_path_generator')->defaultValue('api_platform.routing.resource_path_generator.underscore')->info('Specify the strategy to use for generating resource paths.')->end()
->end()
->end()
->scalarNode('name_converter')->defaultNull()->info('Specify a name converter to use.')->end()
->booleanNode('enable_fos_user')->defaultValue(false)->info('Enable the FOSUserBundle integration.')->end()
->booleanNode('enable_nelmio_api_doc')->defaultTrue()->info('Enable the Nelmio Api doc integration.')->end()
Expand Down
7 changes: 7 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/api.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<argument type="service" id="kernel" />
<argument type="service" id="api_platform.metadata.resource.name_collection_factory" />
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument type="service" id="api_platform.routing.resource_path_generator" />

<tag name="routing.loader" />
</service>
Expand All @@ -44,6 +45,12 @@

<service id="api_platform.negotiator" class="Negotiation\Negotiator" public="false" />

<!-- Resource path generators -->

<service id="api_platform.routing.resource_path_generator.underscore" class="ApiPlatform\Core\Routing\UnderscoreResourcePathGenerator" public="false" />

<service id="api_platform.routing.resource_path_generator.dash" class="ApiPlatform\Core\Routing\DashResourcePathGenerator" public="false" />

<!-- Event listeners -->

<service id="api_platform.listener.request.format" class="ApiPlatform\Core\EventListener\FormatRequestListener">
Expand Down
25 changes: 17 additions & 8 deletions src/Bridge/Symfony/Routing/ApiLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@

namespace ApiPlatform\Core\Bridge\Symfony\Routing;

use ApiPlatform\Core\Exception\InvalidResourceException;
use ApiPlatform\Core\Exception\RuntimeException;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
use ApiPlatform\Core\Routing\ResourcePathGeneratorInterface;
use Doctrine\Common\Inflector\Inflector;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\Loader;
Expand All @@ -35,12 +37,14 @@ final class ApiLoader extends Loader
private $fileLoader;
private $resourceNameCollectionFactory;
private $resourceMetadataFactory;
private $resourcePathGenerator;

public function __construct(KernelInterface $kernel, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory)
public function __construct(KernelInterface $kernel, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, ResourcePathGeneratorInterface $resourcePathGenerator)
{
$this->fileLoader = new XmlFileLoader(new FileLocator($kernel->locateResource('@ApiPlatformBundle/Resources/config/routing')));
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
$this->resourceMetadataFactory = $resourceMetadataFactory;
$this->resourcePathGenerator = $resourcePathGenerator;
}

/**
Expand All @@ -55,14 +59,18 @@ public function load($data, $type = null)

foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
$normalizedShortName = Inflector::pluralize(Inflector::tableize($resourceMetadata->getShortName()));
$resourceShortName = $resourceMetadata->getShortName();

if (null === $resourceShortName) {
throw new InvalidResourceException(sprintf('Resource %s has no short name defined.', $resourceClass));
}

foreach ($resourceMetadata->getCollectionOperations() as $operationName => $operation) {
$this->addRoute($routeCollection, $resourceClass, $operationName, $operation, $normalizedShortName, true);
$this->addRoute($routeCollection, $resourceClass, $operationName, $operation, $resourceShortName, true);
}

foreach ($resourceMetadata->getItemOperations() as $operationName => $operation) {
$this->addRoute($routeCollection, $resourceClass, $operationName, $operation, $normalizedShortName, false);
$this->addRoute($routeCollection, $resourceClass, $operationName, $operation, $resourceShortName, false);
}
}

Expand All @@ -84,12 +92,12 @@ public function supports($resource, $type = null)
* @param string $resourceClass
* @param string $operationName
* @param array $operation
* @param string $normalizedShortName
* @param string $resourceShortName
* @param bool $collection
*
* @throws RuntimeException
*/
private function addRoute(RouteCollection $routeCollection, string $resourceClass, string $operationName, array $operation, string $normalizedShortName, bool $collection)
private function addRoute(RouteCollection $routeCollection, string $resourceClass, string $operationName, array $operation, string $resourceShortName, bool $collection)
{
if (isset($operation['route_name'])) {
return;
Expand All @@ -113,14 +121,15 @@ private function addRoute(RouteCollection $routeCollection, string $resourceClas
$path = $operation['path'] ?? null;

if (null === $path) {
$path = '/'.$normalizedShortName;
$path = '/'.$this->resourcePathGenerator->generateResourceBasePath($resourceShortName);

if (!$collection) {
$path .= '/{id}';
}
}

$routeName = sprintf('%s%s_%s', self::ROUTE_NAME_PREFIX, $normalizedShortName, $actionName);
$resourceRouteName = Inflector::pluralize(Inflector::tableize($resourceShortName));
$routeName = sprintf('%s%s_%s', self::ROUTE_NAME_PREFIX, $resourceRouteName, $actionName);

$route = new Route(
$path,
Expand Down
21 changes: 21 additions & 0 deletions src/Exception/InvalidResourceException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?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.
*/

namespace ApiPlatform\Core\Exception;

/**
* Invalid resource exception.
*
* @author Paul Le Corre <paul@lecorre.me>
*/
class InvalidResourceException extends \Exception implements ExceptionInterface
{
}
27 changes: 27 additions & 0 deletions src/Routing/DashResourcePathGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?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.
*/

namespace ApiPlatform\Core\Routing;

use Doctrine\Common\Inflector\Inflector;

/**
* @author Paul Le Corre <paul@lecorre.me>
*/
class DashResourcePathGenerator implements ResourcePathGeneratorInterface
{
public function generateResourceBasePath(string $resourceShortName) : string
{
$pathName = strtolower(preg_replace('~(?<=\\w)([A-Z])~', '-$1', $resourceShortName));

return Inflector::pluralize($pathName);
}
}
20 changes: 20 additions & 0 deletions src/Routing/ResourcePathGeneratorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?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.
*/

namespace ApiPlatform\Core\Routing;

/**
* @author Paul Le Corre <paul@lecorre.me>
*/
interface ResourcePathGeneratorInterface
{
public function generateResourceBasePath(string $resourceShortName) : string;
}
27 changes: 27 additions & 0 deletions src/Routing/UnderscoreResourcePathGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?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.
*/

namespace ApiPlatform\Core\Routing;

use Doctrine\Common\Inflector\Inflector;

/**
* @author Paul Le Corre <paul@lecorre.me>
*/
class UnderscoreResourcePathGenerator implements ResourcePathGeneratorInterface
{
public function generateResourceBasePath(string $resourceShortName) : string
{
$pathName = Inflector::tableize($resourceShortName);

return Inflector::pluralize($pathName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ private function getContainerBuilderProphecy()
'api_platform.route_loader',
'api_platform.router',
'api_platform.iri_converter',
'api_platform.routing.resource_path_generator.underscore',
'api_platform.routing.resource_path_generator.dash',
'api_platform.listener.request.format',
'api_platform.listener.view.validation',
'api_platform.listener.request.format',
Expand Down Expand Up @@ -324,6 +326,7 @@ private function getContainerBuilderProphecy()
}

$aliases = [
'api_platform.routing.resource_path_generator' => 'api_platform.routing.resource_path_generator.underscore',
'api_platform.metadata.resource.name_collection_factory' => 'api_platform.metadata.resource.name_collection_factory.annotation',
'api_platform.metadata.resource.cache' => 'api_platform.metadata.resource.cache.array',
'api_platform.metadata.property.cache' => 'api_platform.metadata.property.cache.array',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public function testDefaultConfig()
'title' => 'title',
'description' => 'description',
'supported_formats' => ['jsonld' => ['mime_types' => ['application/ld+json']]],
'routing' => [
'resource_path_generator' => 'api_platform.routing.resource_path_generator.underscore',
],
'name_converter' => null,
'enable_fos_user' => false,
'enable_nelmio_api_doc' => true,
Expand Down
16 changes: 15 additions & 1 deletion tests/Bridge/Symfony/Routing/ApiLoaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection;
use ApiPlatform\Core\Routing\ResourcePathGeneratorInterface;
use ApiPlatform\Core\Tests\Fixtures\DummyEntity;
use Prophecy\Argument;
use Symfony\Component\HttpKernel\KernelInterface;
Expand Down Expand Up @@ -85,6 +86,8 @@ public function testApiLoader()
public function testNoMethodApiLoader()
{
$resourceMetadata = new ResourceMetadata();
$resourceMetadata = $resourceMetadata->withShortName('dummy');

$resourceMetadata = $resourceMetadata->withItemOperations([
'get' => [],
]);
Expand All @@ -96,6 +99,14 @@ public function testNoMethodApiLoader()
$routeCollection = $this->getApiLoaderWithResourceMetadata($resourceMetadata)->load(null);
}

/**
* @expectedException \ApiPlatform\Core\Exception\InvalidResourceException
*/
public function testNoShortNameApiLoader()
{
$this->getApiLoaderWithResourceMetadata(new ResourceMetadata())->load(null);
}

private function getApiLoaderWithResourceMetadata(ResourceMetadata $resourceMetadata): ApiLoader
{
$routingConfig = __DIR__.'/../../../../src/Bridge/Symfony/Bundle/Resources/config/routing';
Expand All @@ -109,7 +120,10 @@ private function getApiLoaderWithResourceMetadata(ResourceMetadata $resourceMeta
$resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class);
$resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([DummyEntity::class]));

$apiLoader = new ApiLoader($kernelProphecy->reveal(), $resourceNameCollectionFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal());
$resourcePathGeneratorProphecy = $this->prophesize(ResourcePathGeneratorInterface::class);
$resourcePathGeneratorProphecy->generateResourceBasePath('dummy')->willReturn('dummies');

$apiLoader = new ApiLoader($kernelProphecy->reveal(), $resourceNameCollectionFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), $resourcePathGeneratorProphecy->reveal());

return $apiLoader;
}
Expand Down
24 changes: 24 additions & 0 deletions tests/Routing/DashResourcePathGeneratorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?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.
*/

namespace ApiPlatform\Core\Tests\Routing;

use ApiPlatform\Core\Routing\DashResourcePathGenerator;

class DashResourcePathGeneratorTest extends \PHPUnit_Framework_TestCase
{
public function testGenerateResourceBasePath()
{
$dashResourcePathGenerator = new DashResourcePathGenerator();

$this->assertSame('short-names', $dashResourcePathGenerator->generateResourceBasePath('ShortName'));
}
}
24 changes: 24 additions & 0 deletions tests/Routing/UnderscoreResourcePathGeneratorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?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.
*/

namespace ApiPlatform\Core\Tests\Routing;

use ApiPlatform\Core\Routing\UnderscoreResourcePathGenerator;

class UnderscoreResourcePathGeneratorTest extends \PHPUnit_Framework_TestCase
{
public function testGenerateResourceBasePath()
{
$underscoreResourcePathGenerator = new UnderscoreResourcePathGenerator();

$this->assertSame('short_names', $underscoreResourcePathGenerator->generateResourceBasePath('ShortName'));
}
}

0 comments on commit 3bf6671

Please sign in to comment.