Skip to content

Commit

Permalink
Import AnnotationsProvider & Parser from NelmioApiDocBundle
Browse files Browse the repository at this point in the history
  • Loading branch information
teohhanhui committed Mar 9, 2016
1 parent f2e6594 commit e052628
Show file tree
Hide file tree
Showing 7 changed files with 470 additions and 2 deletions.
6 changes: 4 additions & 2 deletions composer.json
Expand Up @@ -42,10 +42,12 @@
"phpdocumentor/reflection-docblock": "~3.0",
"doctrine/orm": "~2.2,>=2.2.3",
"doctrine/doctrine-bundle": "dev-property_info",
"php-mock/php-mock-phpunit": "~1.1"
"php-mock/php-mock-phpunit": "~1.1",
"nelmio/api-doc-bundle": "^2.11.2"
},
"suggest": {
"friendsofsymfony/user-bundle": "To use the FOSUserBundle bridge."
"friendsofsymfony/user-bundle": "To use the FOSUserBundle bridge.",
"nelmio/api-doc-bundle": "To have the api sandbox & documentation."
},
"autoload": {
"psr-4": { "ApiPlatform\\Core\\": "src/" }
Expand Down
@@ -0,0 +1,226 @@
<?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\Bridge\NelmioApiDoc\Extractor\AnnotationsProvider;

use ApiPlatform\Core\Api\FilterCollection;
use ApiPlatform\Core\Api\OperationMethodResolverInterface;
use ApiPlatform\Core\Bridge\NelmioApiDoc\ApiPlatformParser;
use ApiPlatform\Core\Hydra\ApiDocumentationBuilderInterface;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Extractor\AnnotationsProviderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouterInterface;

/**
* Creates Nelmio ApiDoc annotations for the api platform.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
final class ApiPlatformProvider implements AnnotationsProviderInterface
{
private $resourceNameCollectionFactory;
private $apiDocumentationBuilder;
private $resourceMetadataFactory;
private $filters;
private $operationMethodResolver;
private $router;

public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ApiDocumentationBuilderInterface $apiDocumentationBuilder, ResourceMetadataFactoryInterface $resourceMetadataFactory, FilterCollection $filters, OperationMethodResolverInterface $operationMethodResolver, RouterInterface $router)
{
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
$this->apiDocumentationBuilder = $apiDocumentationBuilder;
$this->resourceMetadataFactory = $resourceMetadataFactory;
$this->filters = $filters;
$this->operationMethodResolver = $operationMethodResolver;
$this->router = $router;
}

/**
* {@inheritdoc}
*/
public function getAnnotations() : array
{
$annotations = [];
$hydraDoc = $this->apiDocumentationBuilder->getApiDocumentation();
$entrypointHydraDoc = $this->getResourceHydraDoc($hydraDoc, '#Entrypoint');

foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);

$prefixedShortName = ($iri = $resourceMetadata->getIri()) ? $iri : '#'.$resourceMetadata->getShortName();
$resourceHydraDoc = $this->getResourceHydraDoc($hydraDoc, $prefixedShortName);

if ($hydraDoc) {
foreach ($resourceMetadata->getCollectionOperations() as $operationName => $operation) {
$annotations[] = $this->getApiDoc(true, $resourceClass, $resourceMetadata, $operationName, $operation, $resourceHydraDoc, $entrypointHydraDoc);
}

foreach ($resourceMetadata->getItemOperations() as $operationName => $operation) {
$annotations[] = $this->getApiDoc(false, $resourceClass, $resourceMetadata, $operationName, $operation, $resourceHydraDoc);
}
}
}

return $annotations;
}

/**
* Builds ApiDoc annotation from ApiPlatform data.
*
* @param bool $collection
* @param string $resourceClass
* @param ResourceMetadata $resourceMetadata
* @param string $operationName
* @param array $operation
* @param array $resourceHydraDoc
* @param array $entrypointHydraDoc
*
* @return ApiDoc
*/
private function getApiDoc(bool $collection, string $resourceClass, ResourceMetadata $resourceMetadata, string $operationName, array $operation, array $resourceHydraDoc, array $entrypointHydraDoc = []) : ApiDoc
{
if ($collection) {
$method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName);
$operationHydraDoc = $this->getCollectionOperationHydraDoc($resourceMetadata->getShortName(), $method, $entrypointHydraDoc);
} else {
$method = $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName);
$operationHydraDoc = $this->getOperationHydraDoc($method, $resourceHydraDoc);
}

$route = $this->getRoute($resourceClass, $collection, $operationName);

$data = [
'resource' => $route->getPath(),
'description' => $operationHydraDoc['hydra:title'],
'resourceDescription' => $resourceHydraDoc['hydra:title'],
'section' => $resourceHydraDoc['hydra:title'],
];

if (isset($operationHydraDoc['expects']) && 'owl:Nothing' !== $operationHydraDoc['expects']) {
$data['input'] = sprintf('%s:%s', ApiPlatformParser::IN_PREFIX, $resourceClass);
}

if (isset($operationHydraDoc['returns']) && 'owl:Nothing' !== $operationHydraDoc['returns']) {
$data['output'] = sprintf('%s:%s', ApiPlatformParser::OUT_PREFIX, $resourceClass);
}

if ($collection && Request::METHOD_GET === $method) {
$resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true);

$data['filters'] = [];
foreach ($this->filters as $filterName => $filter) {
if (in_array($filterName, $resourceFilters)) {
foreach ($filter->getDescription($resource) as $name => $definition) {
$data['filters'][] = ['name' => $name] + $definition;
}
}
}
}

$apiDoc = new ApiDoc($data);
$apiDoc->setRoute($route);

return $apiDoc;
}

/**
* Gets Hydra documentation for the given resource.
*
* @param array $hydraApiDoc
* @param string $prefixedShortName
*
* @return array|null
*/
private function getResourceHydraDoc(array $hydraApiDoc, string $prefixedShortName)
{
foreach ($hydraApiDoc['hydra:supportedClass'] as $supportedClass) {
if ($supportedClass['@id'] === $prefixedShortName) {
return $supportedClass;
}
}
}

/**
* Gets the Hydra documentation of a given operation.
*
* @param string $method
* @param array $hydraDoc
*
* @return array|null
*/
private function getOperationHydraDoc(string $method, array $hydraDoc)
{
foreach ($hydraDoc['hydra:supportedOperation'] as $supportedOperation) {
if ($supportedOperation['hydra:method'] === $method) {
return $supportedOperation;
}
}
}

/**
* Gets the Hydra documentation for the collection operation.
*
* @param string $shortName
* @param string $method
* @param array $hydraEntrypointDoc
*
* @return array|null
*/
private function getCollectionOperationHydraDoc(string $shortName, string $method, array $hydraEntrypointDoc)
{
$propertyName = '#Entrypoint/'.lcfirst($shortName);

foreach ($hydraEntrypointDoc['hydra:supportedProperty'] as $supportedProperty) {
$hydraProperty = $supportedProperty['hydra:property'];
if ($hydraProperty['@id'] === $propertyName) {
return $this->getOperationHydraDoc($method, $hydraProperty);
}
}
}

/**
* Finds the route for an operation on a resource.
*
* @param string $resourceClass
* @param bool $collection
* @param string $operationName
*
* @return Route
*
* @throws \InvalidArgumentException
*/
private function getRoute(string $resourceClass, bool $collection, string $operationName) : Route
{
$operationNameKey = sprintf('_%s_operation_name', $collection ? 'collection' : 'item');
$found = false;

foreach ($this->router->getRouteCollection()->all() as $routeName => $route) {
$currentResourceClass = $route->getDefault('_resource_class');
$currentOperationName = $route->getDefault($operationNameKey);

if ($resourceClass === $currentResourceClass && $operationName === $currentOperationName) {
$found = true;
break;
}
}

if (!$found) {
throw new \InvalidArgumentException(sprintf('No route found for operation "%s" for type "%s".', $operationName, $resourceClass));
}

return $route;
}
}

0 comments on commit e052628

Please sign in to comment.