diff --git a/DependencyInjection/NelmioApiDocExtension.php b/DependencyInjection/NelmioApiDocExtension.php index 1ecae53..5aedd86 100644 --- a/DependencyInjection/NelmioApiDocExtension.php +++ b/DependencyInjection/NelmioApiDocExtension.php @@ -36,5 +36,15 @@ class NelmioApiDocExtension extends Extension $loader->load('formatters.xml'); $loader->load('request_listener.xml'); $loader->load('services.xml'); + + //JMS may or may not be installed, if it is, load that config as well + try { + if ($serializer = $container->findDefinition('serializer')) { + die(__METHOD__); + $loader->load('services.jms.xml'); + } + } catch (\Exception $e) { + + } } } diff --git a/Extractor/ApiDocExtractor.php b/Extractor/ApiDocExtractor.php index dcc8af1..2e0dae6 100644 --- a/Extractor/ApiDocExtractor.php +++ b/Extractor/ApiDocExtractor.php @@ -229,6 +229,7 @@ class ApiDocExtractor foreach ($this->parsers as $parser) { if ($parser->supports($input)) { $parameters = $parser->parse($input); + break; } } diff --git a/Parser/JmsMetadataParser.php b/Parser/JmsMetadataParser.php new file mode 100644 index 0000000..8978de2 --- /dev/null +++ b/Parser/JmsMetadataParser.php @@ -0,0 +1,93 @@ +<?php + +/* + * This file is part of the NelmioApiDocBundle. + * + * (c) Nelmio <hello@nelm.io> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Nelmio\ApiDocBundle\Parser; + +use Metadata\MetadataFactoryInterface; + +/** + * Uses the JMS metadata factory to extract input/output model information + */ +class JmsMetadataParser implements ParserInterface +{ + + /** + * Constructor, requires JMS Metadata factory + */ + public function __construct(MetadataFactoryInterface $factory) + { + $this->factory = $factory; + } + + /** + * {@inheritdoc} + */ + public function supports($input) + { + if($meta = $this->factory->getMetadataForClass($input)) { + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function parse($input) + { + die(__METHOD__); + + $meta = $this->factory->getMetadataForClass($input); + + if(is_null($meta)) { + throw new \InvalidArgumentException(sprintf("No metadata found for class %s", $input)); + } + + $params = array(); + $refClass = new \ReflectionClass($input); + + //iterate over property metadata + foreach ($meta->propertyMetadata as $item) { + $name = isset($item->serializedName) ? $item->serializedName : $item->name; + + $type = $this->getType($item->type); + + if (true) { + //TODO: check for nested type + } + + $params[$name] = array( + 'dataType' => $item->type, + 'required' => false, //can't think of a good way to specify this one, JMS doesn't have a setting for this + 'description' => $this->getDescription($refClass, $item->name), + 'readonly' => $item->readonly + ); + } + + return $params; + } + + protected function getDescription($ref, $nativePropertyName) + { + $description = "No description."; + + if (!$doc = $ref->getProperty($nativePropertyName)->getDocComment()) { + return $description; + } + + //TODO: regex comment to get description - or move doc comment parsing functionality from `ApiDocExtractor` to a new location + //in order to reuse it here + + return $description; + } + +} diff --git a/README.md b/README.md index ae19234..d011f4b 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ The following properties are available: * `filters`: an array of filters; -* `input`: the input type associated to the method, currently this only supports Form Types, useful for POST|PUT methods, either as FQCN or +* `input`: the input type associated to the method, currently this Form Types, and classes with JMS Serializer metadata, useful for POST|PUT methods, either as FQCN or as form type (if it is registered in the form factory in the container) Each _filter_ has to define a `name` parameter, but other parameters are free. Filters are often optional @@ -110,6 +110,8 @@ and determines for each parameter its data type, and if it's required or not. For Form Types, you can add an extra option named `description` on each field: +For classes parsed with JMS metadata, description will be taken from the properties doc comment, if available. + ``` php <?php diff --git a/Resources/config/services.jms.xml b/Resources/config/services.jms.xml new file mode 100644 index 0000000..cf61f8f --- /dev/null +++ b/Resources/config/services.jms.xml @@ -0,0 +1,16 @@ +<?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_doc.parser.jms_metadata_parser.class">Nelmio\ApiDocBundle\Parser\JmsMetadataParser</parameter> + </parameters> + + <services> + <service id="nelmio_api_doc.parser.jms_metadata_parser" class="%nelmio_api_doc.parser.jms_metadata_parser.class%"> + <argument type="service" id="jms_serializer.metadata_factory" /> + <tag name="nelmio_api_doc.extractor.parser" /> + </service> + </services> +</container> diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..f4d3408 --- /dev/null +++ b/TODO.md @@ -0,0 +1,5 @@ +# TODO # + +* Fix JMS registration by moving logic into a CompilerPass +* Finish `JmsMetadataParser` +* Implement new tests \ No newline at end of file diff --git a/Tests/Extractor/ApiDocExtratorTest.php b/Tests/Extractor/ApiDocExtratorTest.php index b092643..a15de3a 100644 --- a/Tests/Extractor/ApiDocExtratorTest.php +++ b/Tests/Extractor/ApiDocExtratorTest.php @@ -22,7 +22,7 @@ class ApiDocExtractorTest extends WebTestCase $data = $extractor->all(); $this->assertTrue(is_array($data)); - $this->assertCount(10, $data); + $this->assertCount(11, $data); foreach ($data as $d) { $this->assertTrue(is_array($d)); diff --git a/Tests/Fixtures/Controller/TestController.php b/Tests/Fixtures/Controller/TestController.php index d39a02a..e01189d 100644 --- a/Tests/Fixtures/Controller/TestController.php +++ b/Tests/Fixtures/Controller/TestController.php @@ -90,4 +90,14 @@ class TestController public function zActionWithQueryParamAction() { } + + /** + * @ApiDoc( + * description="Testing JMS", + * input="Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest" + * ) + */ + public function jmsInputTestAction() + { + } } diff --git a/Tests/Fixtures/Model/JmsTest.php b/Tests/Fixtures/Model/JmsTest.php new file mode 100644 index 0000000..343d936 --- /dev/null +++ b/Tests/Fixtures/Model/JmsTest.php @@ -0,0 +1,33 @@ +<?php + +namespace Nelmio\ApiDocBundle\Tests\Fixtures\Model; + +use JMS\SerializerBundle\Annotation as JMS; + +class JmsTest +{ + public $nothing; + + /** + * @JMS\Type("string"); + */ + public $foo; + + /** + * @JMS\Type("DateTime"); + * @JMS\ReadOnly + */ + public $bar; + + /** + * @JMS\Type("double"); + * @JMS\SerializedName("number"); + */ + public $baz; + + /** + * @JMS\Type("array"); + */ + public $arr; + +} diff --git a/Tests/Fixtures/app/AppKernel.php b/Tests/Fixtures/app/AppKernel.php index b8c28b7..0ebbbb0 100644 --- a/Tests/Fixtures/app/AppKernel.php +++ b/Tests/Fixtures/app/AppKernel.php @@ -48,6 +48,7 @@ class AppKernel extends Kernel return array( new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new \Symfony\Bundle\TwigBundle\TwigBundle(), + new \JMS\SerializerBundle\JMSSerializerBundle($this), new \Nelmio\ApiDocBundle\NelmioApiDocBundle(), new \Nelmio\ApiDocBundle\Tests\Fixtures\NelmioApiDocTestBundle(), ); diff --git a/Tests/Fixtures/app/config/default.yml b/Tests/Fixtures/app/config/default.yml index 04f029f..a046f02 100644 --- a/Tests/Fixtures/app/config/default.yml +++ b/Tests/Fixtures/app/config/default.yml @@ -26,3 +26,33 @@ services: - [foo, bar] tags: - { name: form.type, alias: dependency_type } + +#JMS Serializer config for testing JmsMetadataParser +jms_serializer: + handlers: + object_based: false + datetime: + format: "Y-m-dTH:i:s" # ISO8601 + default_timezone: "UTC" # defaults to whatever timezone set in php.ini or via date_default_timezone_set + array_collection: true + form_error: true + constraint_violation: true + + property_naming: + separator: _ + lower_case: true + + metadata: + cache: file + debug: "%kernel.debug%" + file_cache: + dir: "%kernel.cache_dir%/serializer" + + # Using auto-detection, the mapping files for each bundle will be + # expected in the Resources/config/serializer directory. + # + # Example: + # class: My\FooBundle\Entity\User + # expected path: @MyFooBundle/Resources/config/serializer/Entity.User.(yml|xml|php) + auto_detection: true + diff --git a/Tests/Fixtures/app/config/routing.yml b/Tests/Fixtures/app/config/routing.yml index 092dde5..f451e2b 100644 --- a/Tests/Fixtures/app/config/routing.yml +++ b/Tests/Fixtures/app/config/routing.yml @@ -40,6 +40,12 @@ test_route_8: requirements: _method: GET +test_route_9: + pattern: /jms-input-test + defaults: { _controller: NelmioApiDocTestBundle:Test:jmsInputTest } + requirements: + _method: POST + test_service_route_1: pattern: /tests defaults: { _controller: nemlio.test.controller:indexAction, _format: json } @@ -63,3 +69,4 @@ test_service_route_4: NelmioApiDocBundle: resource: "@NelmioApiDocBundle/Resources/config/routing.yml" prefix: / + \ No newline at end of file diff --git a/Tests/Formatter/MarkdownFormatterTest.php b/Tests/Formatter/MarkdownFormatterTest.php index f686655..3e5653b 100644 --- a/Tests/Formatter/MarkdownFormatterTest.php +++ b/Tests/Formatter/MarkdownFormatterTest.php @@ -135,6 +135,11 @@ _Action without HTTP verb_ +### `POST` /jms-input-test ### + +_Testing JMS_ + + ### `ANY` /my-commented/{id}/{page} ### _This method is useful to test if the getDocComment works._ diff --git a/Tests/Formatter/SimpleFormatterTest.php b/Tests/Formatter/SimpleFormatterTest.php index 9a1e93a..28a94f8 100644 --- a/Tests/Formatter/SimpleFormatterTest.php +++ b/Tests/Formatter/SimpleFormatterTest.php @@ -166,6 +166,43 @@ class SimpleFormatterTest extends WebTestCase 'description' => 'Action without HTTP verb', ), 3 => + array( + 'method' => 'POST', + 'uri' => '/jms-input-test', + 'parameters' => + array( + 'foo' => + array( + 'dataType' => 'string', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false + ), + 'bar' => + array( + 'dataType' => 'DateTime', + 'required' => false, + 'description' => 'No description.', + 'readonly' => true + ), + 'number' => + array( + 'dataType' => 'double', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false + ), + 'arr' => + array( + 'dataType' => 'array', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false + ) + ), + 'description' => 'Testing JMS' + ), + 4 => array( 'method' => 'ANY', 'uri' => '/my-commented/{id}/{page}', @@ -177,7 +214,7 @@ class SimpleFormatterTest extends WebTestCase 'description' => 'This method is useful to test if the getDocComment works.', 'documentation' => "This method is useful to test if the getDocComment works.\nAnd, it supports multilines until the first '@' char." ), - 4 => + 5 => array( 'method' => 'ANY', 'uri' => '/yet-another/{id}', @@ -186,7 +223,7 @@ class SimpleFormatterTest extends WebTestCase 'id' => array('type' => '', 'description' => '', 'requirement' => '\d+') ), ), - 5 => + 6 => array( 'method' => 'GET', 'uri' => '/z-action-with-query-param', diff --git a/composer.json b/composer.json index 2f8d743..80ed4d6 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "symfony/browser-kit": "2.1.*", "symfony/validator": "2.1.*", "symfony/yaml": "2.1.*", - "friendsofsymfony/rest-bundle": "dev-master" + "jms/serializer-bundle": "0.9.*" }, "minimum-stability": "dev", "autoload": {