Skip to content

Commit

Permalink
Introduced ApiBundle
Browse files Browse the repository at this point in the history
  • Loading branch information
willdurand committed Apr 11, 2012
0 parents commit 0dc8883
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 0 deletions.
61 changes: 61 additions & 0 deletions Annotation/ApiDoc.php
@@ -0,0 +1,61 @@
<?php

namespace Nelmio\ApiBundle\Annotation;

/**
* @Annotation
*/
class ApiDoc
{
/**
* @var array
*/
private $filters = array();

/**
* @var string
*/
private $formType = null;

/**
* @var string
*/
private $comment = null;

public function __construct(array $data)
{
if (isset($data['formType'])) {
$this->formType = $data['formType'];
} else if (isset($data['filters'])) {
foreach ($data['filters'] as $filter) {
if (!isset($filter['name'])) {
throw new \InvalidArgumentException('A "filter" element has to contain a "name" attribute');
}

$name = $filter['name'];
unset($filter['name']);

$this->filters[$name] = $filter;
}
}

if (isset($data['comment'])) {
$this->comment = $data['comment'];
}
}

public function getFilters()
{
return $this->filters;
}

public function getFormType()
{
return $this->formType;
}

public function getComment()
{
return $this->comment;
}
}
21 changes: 21 additions & 0 deletions DependencyInjection/NelmioApiExtension.php
@@ -0,0 +1,21 @@
<?php

namespace Nelmio\ApiBundle\DependencyInjection;

use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;

class NelmioApiExtension extends Extension
{
/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('request_listener.xml');
$loader->load('services.xml');
}
}
57 changes: 57 additions & 0 deletions EventListener/RequestListener.php
@@ -0,0 +1,57 @@
<?php

namespace Nelmio\ApiBundle\EventListener;

use Doctrine\Common\Annotations\Reader;
use Nelmio\ApiBundle\Formatter\ApiDocFormatter;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Routing\RouterInterface;

class RequestListener
{
protected $annotationClass = 'Nelmio\\ApiBundle\\Annotation\\ApiDoc';

protected $reader;

protected $router;

protected $formatter;

public function __construct(Reader $reader, RouterInterface $router, ApiDocFormatter $formatter)
{
$this->reader = $reader;
$this->router = $router;
$this->formatter = $formatter;
}

/**
* {@inheritdoc}
*/
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}

$request = $event->getRequest();

if (!$request->get('_doc')) {
return;
}

preg_match('#(.+)::([\w]+)#', $request->get('_controller'), $matches);
$method = new \ReflectionMethod($matches[1], $matches[2]);
$route = $request->get('_route');

if ($annot = $this->reader->getMethodAnnotation($method, $this->annotationClass)) {
if ($route = $this->router->getRouteCollection()->get($route)) {
$result = $this->formatter->format($annot, $route);

$event->setResponse(new JsonResponse($result));
}
}
}
}
53 changes: 53 additions & 0 deletions Formatter/ApiDocFormatter.php
@@ -0,0 +1,53 @@
<?php

namespace Nelmio\ApiBundle\Formatter;

use Nelmio\ApiBundle\Annotation\ApiDoc;
use Nelmio\ApiBundle\Parser\FormTypeParser;
use Symfony\Component\Routing\Route;

class ApiDocFormatter
{
/**
* @var \Nelmio\ApiBundle\Parser\FormTypeParser
*/
protected $parser;

public function __construct(FormTypeParser $parser)
{
$this->parser = $parser;
}

public function format(ApiDoc $apiDoc, Route $route)
{
$method = $route->getRequirement('_method');
$data = array(
'method' => $method,
'uri' => $route->compile()->getPattern(),
'requirements' => $route->compile()->getRequirements(),
);

unset($data['requirements']['_method']);

if (null !== $formType = $apiDoc->getFormType()) {
$data['parameters'] = $this->parser->parse(new $formType());

if ('PUT' === $method) {
// All parameters are optional with PUT (update)
array_walk($data['parameters'], function($val, $key) use (&$data) {
$data['parameters'][$key]['is_required'] = false;
});
}
}

if ($filters = $apiDoc->getFilters()) {
$data['filters'] = $filters;
}

if ($comment = $apiDoc->getComment()) {
$data['comment'] = $comment;
}

return $data;
}
}
9 changes: 9 additions & 0 deletions NelmioApiBundle.php
@@ -0,0 +1,9 @@
<?php

namespace Nelmio\ApiBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class NelmioApiBundle extends Bundle
{
}
53 changes: 53 additions & 0 deletions Parser/FormTypeParser.php
@@ -0,0 +1,53 @@
<?php

namespace Nelmio\ApiBundle\Parser;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormFactoryInterface;

class FormTypeParser
{
protected $formFactory;

protected $mapTypes = array(
'text' => 'string',
'date' => 'date',
'datetime' => 'datetime',
'checkbox' => 'boolean',
'time' => 'time',
'number' => 'float',
'integer' => 'int',
'textarea' => 'string',
);

public function __construct(FormFactoryInterface $formFactory)
{
$this->formFactory = $formFactory;
}

public function parse(AbstractType $type)
{
$builder = $this->formFactory->createBuilder($type);

$parameters = array();
foreach ($builder->all() as $name => $child) {
$b = $builder->create($name, $child['type'], $child['options']);

$bestType = '';
foreach ($b->getTypes() as $type) {
if (isset($this->mapTypes[$type->getName()])) {
$bestType = $this->mapTypes[$type->getName()];
}
}

$parameters[] = array(
'name' => $name,
'type' => $bestType,
'is_required' => $b->getRequired()
);
}

return $parameters;
}
}
19 changes: 19 additions & 0 deletions Resources/config/request_listener.xml
@@ -0,0 +1,19 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<parameters>
<parameter key="nelmio.api.event_listener.request.class">Nelmio\ApiBundle\EventListener\RequestListener</parameter>
</parameters>

<services>
<service id="nelmio.api.event_listener.request" class="%nelmio.api.event_listener.request.class%">
<argument type="service" id="annotation_reader" />
<argument type="service" id="router" />
<argument type="service" id="nelmio.api.formatter.api_doc_formatter" />
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" />
</service>
</services>

</container>
20 changes: 20 additions & 0 deletions Resources/config/services.xml
@@ -0,0 +1,20 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<parameters>
<parameter key="nelmio.api.parser.form_type_parser.class">Nelmio\ApiBundle\Parser\FormTypeParser</parameter>
<parameter key="nelmio.api.formatter.api_doc_formatter.class">Nelmio\ApiBundle\Formatter\ApiDocFormatter</parameter>
</parameters>

<services>
<service id="nelmio.api.parser.form_type_parser" class="%nelmio.api.parser.form_type_parser.class%">
<argument type="service" id="form.factory" />
</service>
<service id="nelmio.api.formatter.api_doc_formatter" class="%nelmio.api.formatter.api_doc_formatter.class%">
<argument type="service" id="nelmio.api.parser.form_type_parser" />
</service>
</services>

</container>

0 comments on commit 0dc8883

Please sign in to comment.