Skip to content

Commit

Permalink
Offer post, put, delete, patch subresource routes
Browse files Browse the repository at this point in the history
  • Loading branch information
torreytsui committed Mar 10, 2019
1 parent 5af79dc commit 69fe295
Show file tree
Hide file tree
Showing 7 changed files with 1,186 additions and 34 deletions.
5 changes: 5 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/api.xml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@
<service id="api_platform.action.put_item" alias="api_platform.action.placeholder" public="true" />
<service id="api_platform.action.delete_item" alias="api_platform.action.placeholder" public="true" />
<service id="api_platform.action.get_subresource" alias="api_platform.action.placeholder" public="true" />
<service id="api_platform.action.post_subresource" alias="api_platform.action.placeholder" public="true" />
<service id="api_platform.action.put_subresource" alias="api_platform.action.placeholder" public="true" />
<service id="api_platform.action.delete_subresource" alias="api_platform.action.placeholder" public="true" />
<service id="api_platform.action.patch_subresource" alias="api_platform.action.placeholder" public="true" />

<service id="api_platform.action.entrypoint" class="ApiPlatform\Core\Action\EntrypointAction" public="true">
<argument type="service" id="api_platform.metadata.resource.name_collection_factory" />
Expand Down Expand Up @@ -268,6 +272,7 @@
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
<argument type="service" id="api_platform.path_segment_name_generator" />
<argument>%api_platform.formats%</argument>
</service>

<service id="api_platform.subresource_operation_factory.cached" class="ApiPlatform\Core\Operation\Factory\CachedSubresourceOperationFactory" decorates="api_platform.subresource_operation_factory" decoration-priority="-10" public="false">
Expand Down
42 changes: 25 additions & 17 deletions src/Bridge/Symfony/Routing/ApiLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,9 @@ public function load($data, $type = null): RouteCollection
}

foreach ($this->subresourceOperationFactory->create($resourceClass) as $operationId => $operation) {
if (null === $controller = $operation['controller'] ?? null) {
$controller = self::DEFAULT_ACTION_PATTERN.'get_subresource';
$this->assertOperationMethod($resourceClass, $operationId, $operation);

if (!$this->container->has($controller)) {
throw new RuntimeException(sprintf('There is no builtin action for the %s %s operation. You need to define the controller yourself.', OperationType::SUBRESOURCE, 'GET'));
}
}
$controller = $this->resolveOperationController($operation, OperationType::SUBRESOURCE);

$routeCollection->add($operation['route_name'], new Route(
$operation['path'],
Expand All @@ -135,7 +131,7 @@ public function load($data, $type = null): RouteCollection
$operation['options'] ?? [],
$operation['host'] ?? '',
$operation['schemes'] ?? [],
['GET'],
[$operation['method']],
$operation['condition'] ?? ''
));
}
Expand Down Expand Up @@ -189,17 +185,9 @@ private function addRoute(RouteCollection $routeCollection, string $resourceClas
return;
}

if (!isset($operation['method'])) {
throw new RuntimeException(sprintf('Either a "route_name" or a "method" operation attribute must exist for the operation "%s" of the resource "%s".', $operationName, $resourceClass));
}

if (null === $controller = $operation['controller'] ?? null) {
$controller = sprintf('%s%s_%s', self::DEFAULT_ACTION_PATTERN, strtolower($operation['method']), $operationType);
$this->assertOperationMethod($resourceClass, $operationName, $operation);

if (!$this->container->has($controller)) {
throw new RuntimeException(sprintf('There is no builtin action for the %s %s operation. You need to define the controller yourself.', $operationType, $operation['method']));
}
}
$controller = $this->resolveOperationController($operation, $operationType);

$path = trim(trim($resourceMetadata->getAttribute('route_prefix', '')), '/');
$path .= $this->operationPathResolver->resolveOperationPath($resourceShortName, $operation, $operationType, $operationName);
Expand All @@ -222,4 +210,24 @@ private function addRoute(RouteCollection $routeCollection, string $resourceClas

$routeCollection->add(RouteNameGenerator::generate($operationName, $resourceShortName, $operationType), $route);
}

private function assertOperationMethod(string $resourceClass, string $operationName, array $operation)
{
if (!isset($operation['method'])) {
throw new RuntimeException(sprintf('Either a "route_name" or a "method" operation attribute must exist for the operation "%s" of the resource "%s".', $operationName, $resourceClass));
}
}

private function resolveOperationController(array $operation, string $operationType): string
{
if (null === $controller = $operation['controller'] ?? null) {
$controller = sprintf('%s%s_%s', self::DEFAULT_ACTION_PATTERN, strtolower($operation['method']), $operationType);

if (!$this->container->has($controller)) {
throw new RuntimeException(sprintf('There is no builtin action for the %s %s operation. You need to define the controller yourself.', $operationType, $operation['method']));
}
}

return $controller;
}
}
33 changes: 22 additions & 11 deletions src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,19 @@ public function __construct(ResourceMetadataFactoryInterface $decorated, array $
}

/**
* {@inheritdoc}
* @internal
*/
public function create(string $resourceClass): ResourceMetadata
public static function populateOperations(string $resourceClass, ResourceMetadata $resourceMetadata, array $formats): ResourceMetadata
{
$resourceMetadata = $this->decorated->create($resourceClass);
$isAbstract = (new \ReflectionClass($resourceClass))->isAbstract();

$collectionOperations = $resourceMetadata->getCollectionOperations();
if (null === $collectionOperations) {
$resourceMetadata = $resourceMetadata->withCollectionOperations($this->createOperations(
$resourceMetadata = $resourceMetadata->withCollectionOperations(static::createOperations(
$isAbstract ? ['GET'] : ['GET', 'POST']
));
} else {
$resourceMetadata = $this->normalize(true, $resourceMetadata, $collectionOperations);
$resourceMetadata = static::normalize(true, $resourceMetadata, $collectionOperations, $formats);
}

$itemOperations = $resourceMetadata->getItemOperations();
Expand All @@ -72,16 +71,28 @@ public function create(string $resourceClass): ResourceMetadata
if (!$isAbstract) {
$methods[] = 'PUT';

if (isset($this->formats['jsonapi'])) {
if (isset($formats['jsonapi'])) {
$methods[] = 'PATCH';
}
}

$resourceMetadata = $resourceMetadata->withItemOperations($this->createOperations($methods));
$resourceMetadata = $resourceMetadata->withItemOperations(static::createOperations($methods));
} else {
$resourceMetadata = $this->normalize(false, $resourceMetadata, $itemOperations);
$resourceMetadata = static::normalize(false, $resourceMetadata, $itemOperations, $formats);
}

return $resourceMetadata;
}

/**
* {@inheritdoc}
*/
public function create(string $resourceClass): ResourceMetadata
{
$resourceMetadata = $this->decorated->create($resourceClass);
$formats = $this->formats;
$resourceMetadata = self::populateOperations($resourceClass, $resourceMetadata, $formats);

$graphql = $resourceMetadata->getGraphql();
if (null === $graphql) {
$resourceMetadata = $resourceMetadata->withGraphql(['query' => [], 'delete' => [], 'update' => [], 'create' => []]);
Expand All @@ -92,7 +103,7 @@ public function create(string $resourceClass): ResourceMetadata
return $resourceMetadata;
}

private function createOperations(array $methods): array
private static function createOperations(array $methods): array
{
$operations = [];
foreach ($methods as $method) {
Expand All @@ -102,7 +113,7 @@ private function createOperations(array $methods): array
return $operations;
}

private function normalize(bool $collection, ResourceMetadata $resourceMetadata, array $operations): ResourceMetadata
private static function normalize(bool $collection, ResourceMetadata $resourceMetadata, array $operations, array $formats): ResourceMetadata
{
$newOperations = [];
foreach ($operations as $operationName => $operation) {
Expand All @@ -116,7 +127,7 @@ private function normalize(bool $collection, ResourceMetadata $resourceMetadata,
if ($collection) {
$supported = isset(self::SUPPORTED_COLLECTION_OPERATION_METHODS[$upperOperationName]);
} else {
$supported = isset(self::SUPPORTED_ITEM_OPERATION_METHODS[$upperOperationName]) || (isset($this->formats['jsonapi']) && 'PATCH' === $upperOperationName);
$supported = isset(self::SUPPORTED_ITEM_OPERATION_METHODS[$upperOperationName]) || (isset($formats['jsonapi']) && 'PATCH' === $upperOperationName);
}

if (!isset($operation['method']) && !isset($operation['route_name'])) {
Expand Down
Loading

0 comments on commit 69fe295

Please sign in to comment.