diff --git a/README.md b/README.md index af64c7c..2e0b399 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ public function registerBundles() new FOS\RestBundle\FOSRestBundle(), new JMS\SerializerBundle\JMSSerializerBundle($this), new Nelmio\CorsBundle\NelmioCorsBundle(), + new Nelmio\ApiDocBundle\NelmioApiDocBundle(), //... ); //... @@ -73,6 +74,8 @@ sensio_framework_extra: request: { converters: true } view: { annotations: false } router: { annotations: true } + +nelmio_api_doc: ~ ``` ## Generating the Controller diff --git a/composer.json b/composer.json index 1aab61a..533e3cb 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "php": ">=5.3.0", "friendsofsymfony/rest-bundle": "1.4.*", "jms/serializer-bundle": "0.13.*", - "nelmio/cors-bundle": "1.3.*" + "nelmio/cors-bundle": "1.3.*", + "nelmio/api-doc-bundle": "~2.7" }, "autoload": { "psr-0": { diff --git a/src/Voryx/RESTGeneratorBundle/Command/GenerateDoctrineRESTCommand.php b/src/Voryx/RESTGeneratorBundle/Command/GenerateDoctrineRESTCommand.php index 65ed376..568a4bc 100644 --- a/src/Voryx/RESTGeneratorBundle/Command/GenerateDoctrineRESTCommand.php +++ b/src/Voryx/RESTGeneratorBundle/Command/GenerateDoctrineRESTCommand.php @@ -32,14 +32,39 @@ class GenerateDoctrineRESTCommand extends GenerateDoctrineCommand protected function configure() { $this - ->setDefinition(array( - new InputOption('entity', '', InputOption::VALUE_REQUIRED, 'The entity class name to initialize (shortcut notation)'), - new InputOption('route-prefix', '', InputOption::VALUE_REQUIRED, 'The route prefix'), - new InputOption('overwrite', '', InputOption::VALUE_NONE, 'Do not stop the generation if rest api controller already exist, thus overwriting all generated files'), - )) + ->setDefinition( + array( + new InputOption( + 'entity', + '', + InputOption::VALUE_REQUIRED, + 'The entity class name to initialize (shortcut notation)' + ), + new InputOption('route-prefix', '', InputOption::VALUE_REQUIRED, 'The route prefix'), + new InputOption( + 'overwrite', + '', + InputOption::VALUE_NONE, + 'Do not stop the generation if rest api controller already exist, thus overwriting all generated files' + ), + new InputOption( + 'resource', + '', + InputOption::VALUE_NONE, + 'The object will return with the resource name' + ), + new InputOption( + 'document', + '', + InputOption::VALUE_NONE, + 'Use NelmioApiDocBundle to document the controller' + ), + ) + ) ->setDescription('Generates a REST api based on a Doctrine entity') - ->setHelp(<<voryx:generate:rest command generates a REST api based on a Doctrine entity. + ->setHelp( + <<voryx:generate:rest command generates a REST api based on a Doctrine entity. php app/console voryx:generate:rest --entity=AcmeBlogBundle:Post --route-prefix=post_admin @@ -58,8 +83,7 @@ protected function configure() EOT ) ->setName('voryx:generate:rest') - ->setAliases(array('generate:voryx:rest')) - ; + ->setAliases(array('generate:voryx:rest')); } /** @@ -70,7 +94,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $dialog = $this->getDialogHelper(); if ($input->isInteractive()) { - if (!$dialog->askConfirmation($output, $dialog->getQuestion('Do you confirm generation', 'yes', '?'), true)) { + if (!$dialog->askConfirmation( + $output, + $dialog->getQuestion('Do you confirm generation', 'yes', '?'), + true + ) + ) { $output->writeln('Command aborted'); return 1; @@ -85,12 +114,14 @@ protected function execute(InputInterface $input, OutputInterface $output) $dialog->writeSection($output, 'REST api generation'); - $entityClass = $this->getContainer()->get('doctrine')->getAliasNamespace($bundle).'\\'.$entity; - $metadata = $this->getEntityMetadata($entityClass); - $bundle = $this->getContainer()->get('kernel')->getBundle($bundle); + $entityClass = $this->getContainer()->get('doctrine')->getAliasNamespace($bundle) . '\\' . $entity; + $metadata = $this->getEntityMetadata($entityClass); + $bundle = $this->getContainer()->get('kernel')->getBundle($bundle); + $resource = $input->getOption('resource'); + $document = $input->getOption('document'); $generator = $this->getGenerator($bundle); - $generator->generate($bundle, $entity, $metadata[0], $prefix, $forceOverwrite); + $generator->generate($bundle, $entity, $metadata[0], $prefix, $forceOverwrite, $resource, $document); $output->writeln('Generating the REST api code: OK'); @@ -115,41 +146,53 @@ protected function interact(InputInterface $input, OutputInterface $output) $dialog->writeSection($output, 'Welcome to the Doctrine2 REST api generator'); // namespace - $output->writeln(array( - '', - 'This command helps you generate a REST api controller.', - '', - 'First, you need to give the entity for which you want to generate a REST api.', - 'You can give an entity that does not exist yet and the wizard will help', - 'you defining it.', - '', - 'You must use the shortcut notation like AcmeBlogBundle:Post.', - '', - )); - - $entity = $dialog->askAndValidate($output, $dialog->getQuestion('The Entity shortcut name', $input->getOption('entity')), array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateEntityName'), false, $input->getOption('entity')); + $output->writeln( + array( + '', + 'This command helps you generate a REST api controller.', + '', + 'First, you need to give the entity for which you want to generate a REST api.', + 'You can give an entity that does not exist yet and the wizard will help', + 'you defining it.', + '', + 'You must use the shortcut notation like AcmeBlogBundle:Post.', + '', + ) + ); + + $entity = $dialog->askAndValidate( + $output, + $dialog->getQuestion('The Entity shortcut name', $input->getOption('entity')), + array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateEntityName'), + false, + $input->getOption('entity') + ); $input->setOption('entity', $entity); list($bundle, $entity) = $this->parseShortcutNotation($entity); // route prefix $prefix = $this->getRoutePrefix($input, $entity); - $output->writeln(array( - '', - 'Determine the routes prefix (all the routes will be "mounted" under this', - 'prefix: /prefix/, /prefix/new, ...).', - '', - )); - $prefix = $dialog->ask($output, $dialog->getQuestion('Routes prefix', '/'.$prefix), '/'.$prefix); + $output->writeln( + array( + '', + 'Determine the routes prefix (all the routes will be "mounted" under this', + 'prefix: /prefix/, /prefix/new, ...).', + '', + ) + ); + $prefix = $dialog->ask($output, $dialog->getQuestion('Routes prefix', '/' . $prefix), '/' . $prefix); $input->setOption('route-prefix', $prefix); // summary - $output->writeln(array( - '', - $this->getHelper('formatter')->formatBlock('Summary before generation', 'bg=blue;fg=white', true), - '', - sprintf("You are going to generate a REST api controller for \"%s:%s\"", $bundle, $entity), - '', - )); + $output->writeln( + array( + '', + $this->getHelper('formatter')->formatBlock('Summary before generation', 'bg=blue;fg=white', true), + '', + sprintf("You are going to generate a REST api controller for \"%s:%s\"", $bundle, $entity), + '', + ) + ); } /** @@ -159,38 +202,61 @@ protected function generateForm($bundle, $entity, $metadata) { try { $this->getFormGenerator($bundle)->generate($bundle, $entity, $metadata[0]); - } catch (\RuntimeException $e ) { + } catch (\RuntimeException $e) { // form already exists } } - protected function updateRouting(DialogHelper $dialog, InputInterface $input, OutputInterface $output, BundleInterface $bundle, $entity, $prefix) - { + protected function updateRouting( + DialogHelper $dialog, + InputInterface $input, + OutputInterface $output, + BundleInterface $bundle, + $entity, + $prefix + ) { $auto = true; if ($input->isInteractive()) { - $auto = $dialog->askConfirmation($output, $dialog->getQuestion('Confirm automatic update of the Routing', 'yes', '?'), true); + $auto = $dialog->askConfirmation( + $output, + $dialog->getQuestion('Confirm automatic update of the Routing', 'yes', '?'), + true + ); } $output->write('Importing the REST api routes: '); - $this->getContainer()->get('filesystem')->mkdir($bundle->getPath().'/Resources/config/'); - $routing = new RoutingManipulator($bundle->getPath().'/Resources/config/routing.yml'); + $this->getContainer()->get('filesystem')->mkdir($bundle->getPath() . '/Resources/config/'); + $routing = new RoutingManipulator($bundle->getPath() . '/Resources/config/routing.yml'); try { // TODO: fix the format parameter - leaving it for now $format = "annotation"; - $ret = $auto ? $routing->addResource($bundle->getName(), $format, '/'.$prefix, 'routing/'.strtolower(str_replace('\\', '_', $entity))) : false; + $ret = $auto ? $routing->addResource( + $bundle->getName(), + $format, + '/' . $prefix, + 'routing/' . strtolower(str_replace('\\', '_', $entity)) + ) : false; } catch (\RuntimeException $exc) { $ret = false; } if (!$ret) { - $help = sprintf(" resource: \"@%s/Resources/config/routing/%s.%s\"\n", $bundle->getName(), strtolower(str_replace('\\', '_', $entity)), $format); + $help = sprintf( + " resource: \"@%s/Resources/config/routing/%s.%s\"\n", + $bundle->getName(), + strtolower(str_replace('\\', '_', $entity)), + $format + ); $help .= sprintf(" prefix: /%s\n", $prefix); return array( '- Import the bundle\'s routing resource in the bundle routing file', - sprintf(' (%s).', $bundle->getPath().'/Resources/config/routing.yml'), + sprintf(' (%s).', $bundle->getPath() . '/Resources/config/routing.yml'), '', - sprintf(' %s:', $bundle->getName().('' !== $prefix ? '_'.str_replace('/', '_', $prefix) : '')), + sprintf( + ' %s:', + $bundle->getName() . ('' !== $prefix ? '_' . str_replace('/', '_', $prefix) : '') + ), $help, '', ); diff --git a/src/Voryx/RESTGeneratorBundle/Controller/VoryxController.php b/src/Voryx/RESTGeneratorBundle/Controller/VoryxController.php index 51cca5c..95847dd 100644 --- a/src/Voryx/RESTGeneratorBundle/Controller/VoryxController.php +++ b/src/Voryx/RESTGeneratorBundle/Controller/VoryxController.php @@ -1,15 +1,11 @@ request->replace($data); } -} +} \ No newline at end of file diff --git a/src/Voryx/RESTGeneratorBundle/Generator/DoctrineRESTGenerator.php b/src/Voryx/RESTGeneratorBundle/Generator/DoctrineRESTGenerator.php index d664e2e..64ff329 100644 --- a/src/Voryx/RESTGeneratorBundle/Generator/DoctrineRESTGenerator.php +++ b/src/Voryx/RESTGeneratorBundle/Generator/DoctrineRESTGenerator.php @@ -11,6 +11,7 @@ namespace Voryx\RESTGeneratorBundle\Generator; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\Bundle\BundleInterface; use Doctrine\ORM\Mapping\ClassMetadataInfo; @@ -38,41 +39,54 @@ class DoctrineRESTGenerator extends Generator */ public function __construct(Filesystem $filesystem) { - $this->filesystem = $filesystem; + $this->filesystem = $filesystem; } /** * Generate the REST controller. * - * @param BundleInterface $bundle A bundle object - * @param string $entity The entity relative class name - * @param ClassMetadataInfo $metadata The entity class metadata - * @param string $routePrefix The route name prefix - * @param array $forceOverwrite Whether or not to overwrite an existing controller + * @param BundleInterface $bundle A bundle object + * @param string $entity The entity relative class name + * @param ClassMetadataInfo $metadata The entity class metadata + * @param string $routePrefix The route name prefix + * @param array $forceOverwrite Whether or not to overwrite an existing controller * * @throws \RuntimeException */ - public function generate(BundleInterface $bundle, $entity, ClassMetadataInfo $metadata, $routePrefix, $forceOverwrite) - { + public function generate( + BundleInterface $bundle, + $entity, + ClassMetadataInfo $metadata, + $routePrefix, + $forceOverwrite, + $resource, + $document + ) { $this->routePrefix = $routePrefix; $this->routeNamePrefix = str_replace('/', '_', $routePrefix); $this->actions = array('getById', 'getAll', 'post', 'put', 'delete'); if (count($metadata->identifier) > 1) { - throw new \RuntimeException('The REST api generator does not support entity classes with multiple primary keys.'); + throw new \RuntimeException( + 'The REST api generator does not support entity classes with multiple primary keys.' + ); } if (!in_array('id', $metadata->identifier)) { - throw new \RuntimeException('The REST api generator expects the entity object has a primary key field named "id" with a getId() method.'); + throw new \RuntimeException( + 'The REST api generator expects the entity object has a primary key field named "id" with a getId() method.' + ); } - $this->entity = $entity; - $this->bundle = $bundle; + $this->entity = $entity; + $this->bundle = $bundle; $this->metadata = $metadata; $this->setFormat('yml'); - $this->generateControllerClass($forceOverwrite); - + $this->generateControllerClass($forceOverwrite, $document, $resource); + $this->generateHandler($forceOverwrite); + $this->generateExceptionClass(); + $this->declareService(); } /** @@ -112,20 +126,24 @@ protected function generateConfiguration() $this->format ); - $this->renderFile('rest/config/routing.'.$this->format.'.twig', $target, array( - 'actions' => $this->actions, - 'route_prefix' => $this->routePrefix, - 'route_name_prefix' => $this->routeNamePrefix, - 'bundle' => $this->bundle->getName(), - 'entity' => $this->entity, - )); + $this->renderFile( + 'rest/config/routing.' . $this->format . '.twig', + $target, + array( + 'actions' => $this->actions, + 'route_prefix' => $this->routePrefix, + 'route_name_prefix' => $this->routeNamePrefix, + 'bundle' => $this->bundle->getName(), + 'entity' => $this->entity, + ) + ); } /** * Generates the controller class only. * */ - protected function generateControllerClass($forceOverwrite) + protected function generateControllerClass($forceOverwrite, $document, $resource) { $dir = $this->bundle->getPath(); @@ -144,19 +162,177 @@ protected function generateControllerClass($forceOverwrite) throw new \RuntimeException('Unable to generate the controller as it already exists.'); } - $this->renderFile('rest/controller.php.twig', $target, array( - 'actions' => $this->actions, - 'route_prefix' => $this->routePrefix, - 'route_name_prefix' => $this->routeNamePrefix, - 'bundle' => $this->bundle->getName(), - 'entity' => $this->entity, - 'entity_class' => $entityClass, - 'namespace' => $this->bundle->getNamespace(), - 'entity_namespace' => $entityNamespace, - 'format' => $this->format, - )); + $this->renderFile( + 'rest/controller.php.twig', + $target, + array( + 'route_prefix' => $this->routePrefix, + 'route_name_prefix' => $this->routeNamePrefix, + 'bundle' => $this->bundle->getName(), + 'entity' => $this->entity, + 'entity_class' => $entityClass, + 'namespace' => $this->bundle->getNamespace(), + 'entity_namespace' => $entityNamespace, + 'format' => $this->format, + 'resource' => $resource, + 'document' => $document, + ) + ); + } + + /** + * Generates the Handle only. + */ + protected function generateHandler($forceOverwrite) + { + $dir = $this->bundle->getPath(); + + $parts = explode('\\', $this->entity); + $entityClass = array_pop($parts); + $entityNamespace = implode('\\', $parts); + + $target = sprintf( + '%s/Handler/%s/%sRESTHandler.php', + $dir, + str_replace('\\', '/', $entityNamespace), + $entityClass + ); + + if (!is_dir(dirname($target))) { + mkdir(dirname($target), 0777, true); + } + + if (!$forceOverwrite && file_exists($target)) { + throw new \RuntimeException('Unable to generate the controller as it already exists.'); + } + + $this->renderFile( + 'rest/handler.php.twig', + $target, + array( + 'route_prefix' => $this->routePrefix, + 'route_name_prefix' => $this->routeNamePrefix, + 'bundle' => $this->bundle->getName(), + 'entity' => $this->entity, + 'entity_class' => $entityClass, + 'namespace' => $this->bundle->getNamespace(), + 'entity_namespace' => $entityNamespace, + 'format' => $this->format, + ) + ); + } + + public function generateExceptionClass() + { + $dir = $this->bundle->getPath(); + + $target = sprintf('%s/Exception/InvalidFormException.php', $dir); + + if (!is_dir(dirname($target))) { + mkdir(dirname($target), 0777, true); + } + + $this->renderFile( + 'rest/form_exception.php.twig', + $target, + array('namespace' => $this->bundle->getNamespace()) + ); } + /** + * Declares the handler as a service + */ + public function declareService() + { + $dir = $this->bundle->getPath(); + + $parts = explode('\\', $this->entity); + $entityClass = array_pop($parts); + $entityNamespace = implode('\\', $parts); + $namespace = $this->bundle->getNamespace(); + + $bundleName = strtolower($this->bundle->getName()); + $entityName = strtolower($this->entity); + + $services = sprintf( + "%s/Resources/config/servicesREST.xml", + $dir + ); + + $handlerClass = sprintf( + "%s\\Handler\\%s%sRESTHandler", + $namespace, + $entityNamespace, + $entityClass + ); + + $newId = sprintf( + "%s.%s.handler", + str_replace("bundle", "", $bundleName), + $entityName + ); + + $fileName = sprintf( + "%s/DependencyInjection/%s.php", + $dir, + str_replace("Bundle", "Extension", $this->bundle->getName()) + ); + + if (!is_file($services)) { + $this->renderFile("rest/service/services.xml.twig", $services, array()); + } + + $newXML = simplexml_load_file($services); + + if (!($servicesTag = $newXML->services)) { + $servicesTag = $newXML->addChild("services"); + } + + $search = $newXML->xpath("//*[@id='$newId']"); + if (!$search) { + $newServiceTag = $servicesTag->addChild("service"); + $newServiceTag->addAttribute("id", $newId); + $newServiceTag->addAttribute("class", $handlerClass); + + $entityManagerTag = $newServiceTag->addChild("argument"); + $entityManagerTag->addAttribute("type", "service"); + $entityManagerTag->addAttribute("id", "doctrine.orm.entity_manager"); + + $newServiceTag->addChild( + "argument", + sprintf( + "%s\\Entity\\%s%s", + $namespace, + $entityNamespace, + $entityClass + ) + ); + + $formFactoryTag = $newServiceTag->addChild("argument"); + $formFactoryTag->addAttribute("type", "service"); + $formFactoryTag->addAttribute("id", "form.factory"); + } + + $newXML->saveXML($services); + $this->updateDIFile($fileName); + } + + private function updateDIFile($fileName) + { + $toInput = PHP_EOL . "\t\t\$loader2 = new Loader\\XmlFileLoader(\$container, new FileLocator(__DIR__ . '/../Resources/config'));" . PHP_EOL . + "\t\t\$loader2->load('servicesREST.xml');" . PHP_EOL . "\t"; + + $text = file_get_contents($fileName); + + if (strpos($text, "servicesREST.xml") == false) { + $position = strpos($text, "}", strpos($text, "function load(")); + + $newContent = substr_replace($text, $toInput, $position, 0); + file_put_contents($fileName, $newContent); + } + } + + /** * Generates the functional test class only. * @@ -167,20 +343,29 @@ protected function generateTestClass() $entityClass = array_pop($parts); $entityNamespace = implode('\\', $parts); - $dir = $this->bundle->getPath() .'/Tests/Controller'; - $target = $dir .'/'. str_replace('\\', '/', $entityNamespace).'/'. $entityClass .'RESTControllerTest.php'; - - $this->renderFile('rest/tests/test.php.twig', $target, array( - 'route_prefix' => $this->routePrefix, - 'route_name_prefix' => $this->routeNamePrefix, - 'entity' => $this->entity, - 'bundle' => $this->bundle->getName(), - 'entity_class' => $entityClass, - 'namespace' => $this->bundle->getNamespace(), - 'entity_namespace' => $entityNamespace, - 'actions' => $this->actions, - 'form_type_name' => strtolower(str_replace('\\', '_', $this->bundle->getNamespace()).($parts ? '_' : '').implode('_', $parts).'_'.$entityClass.'Type'), - )); + $dir = $this->bundle->getPath() . '/Tests/Controller'; + $target = $dir . '/' . str_replace('\\', '/', $entityNamespace) . '/' . $entityClass . 'RESTControllerTest.php'; + + $this->renderFile( + 'rest/tests/test.php.twig', + $target, + array( + 'route_prefix' => $this->routePrefix, + 'route_name_prefix' => $this->routeNamePrefix, + 'entity' => $this->entity, + 'bundle' => $this->bundle->getName(), + 'entity_class' => $entityClass, + 'namespace' => $this->bundle->getNamespace(), + 'entity_namespace' => $entityNamespace, + 'actions' => $this->actions, + 'form_type_name' => strtolower( + str_replace('\\', '_', $this->bundle->getNamespace()) . ($parts ? '_' : '') . implode( + '_', + $parts + ) . '_' . $entityClass . 'Type' + ), + ) + ); } } diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/delete.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/delete.php.twig old mode 100644 new mode 100755 index 11bce1f..b1ab62e --- a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/delete.php.twig +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/delete.php.twig @@ -1,32 +1,40 @@ - /** + /** {% block phpdoc_method_header %} - * Delete a {{ entity }} entity. - * - * @View(statusCode=204) - * - * @param Request $request - * @param $entity - * @internal param $id - * - * @return Response + * Delete a {{ entity }} entity. + * +{% block documentation %} +{% if document %} + * @ApiDoc( + * resource = true, + * description = "Delete a {{ entity }} entity.", + * statusCodes = { + * 204 = "No content. Successfully excluded.", + * 404 = "Not found." + * } + * ) +{% endif %} +{% endblock documentation %} + * @View(statusCode=204) + * + * @param Request $request + * @param $id + * + * @return Response {% endblock phpdoc_method_header %} {% block phpdoc_method_annotations %} -{% if 'annotation' == format %}{% endif %} + {% if 'annotation' == format %}{% endif %} {% endblock phpdoc_method_annotations %} - */ +*/ {% block method_definition %} - public function deleteAction(Request $request, {{ entity }} $entity) + public function deleteAction($id) {% endblock method_definition %} { {% block method_body %} + ${{ entity|lower }} = $this->getOr404($id); try { - $em = $this->getDoctrine()->getManager(); - $em->remove($entity); - $em->flush(); - - return null; - } catch (\Exception $e) { - return FOSView::create($e->getMessage(), Codes::HTTP_INTERNAL_SERVER_ERROR); + return $this->container->get('{{ bundle|replace({'Bundle': ''})|lower}}.{{ entity|lower }}.handler')->delete(${{ entity|lower }}); + } catch (\Exception $exception) { + throw new \RuntimeException("Exclusion not allowed"); } {% endblock method_body %} {% block method_return '' %} diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getAll.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getAll.php.twig old mode 100644 new mode 100755 index 0acdc63..50e3245 --- a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getAll.php.twig +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getAll.php.twig @@ -1,43 +1,49 @@ - /** + /** {% block phpdoc_method_header %} - * Get all {{ entity }} entities. - * - * @View(serializerEnableMaxDepthChecks=true) - * - * @param ParamFetcherInterface $paramFetcher - * - * @return Response - * - * @QueryParam(name="offset", requirements="\d+", nullable=true, description="Offset from which to start listing notes.") - * @QueryParam(name="limit", requirements="\d+", default="20", description="How many notes to return.") - * @QueryParam(name="order_by", nullable=true, array=true, description="Order by fields. Must be an array ie. &order_by[name]=ASC&order_by[description]=DESC") - * @QueryParam(name="filters", nullable=true, array=true, description="Filter by fields. Must be an array ie. &filters[id]=3") + * Get all {{ entity }} entities. + * +{% block documentation %} +{% if document %} + * @ApiDoc( + * resource = true, + * description = "Get all {{ entity }} entities.", + * statusCodes = { + * 200 = "List of {{ entity }}", + * 204 = "No content. Nothing to list." + * } + * ) +{% endif %} +{% endblock documentation %} + * @View(serializerEnableMaxDepthChecks=true) + * + * @param ParamFetcherInterface $paramFetcher + * + * @return Response + * + * @QueryParam(name="offset", requirements="\d+", nullable=true, description="Offset from which to start listing notes.") + * @QueryParam(name="limit", requirements="\d+", default="20", description="How many notes to return.") + * @QueryParam(name="order_by", nullable=true, array=true, description="Order by fields. Must be an array ie. &order_by[name]=ASC&order_by[description]=DESC") + * @QueryParam(name="filters", nullable=true, array=true, description="Filter by fields. Must be an array ie. &filters[id]=3") {% endblock phpdoc_method_header %} {% block phpdoc_method_annotations %} -{% if 'annotation' == format %}{% endif %} + {% if 'annotation' == format %}{% endif %} {% endblock phpdoc_method_annotations %} - */ +*/ {% block method_definition %} public function cgetAction(ParamFetcherInterface $paramFetcher) {% endblock method_definition %} { {% block method_body %} - try { - $offset = $paramFetcher->get('offset'); - $limit = $paramFetcher->get('limit'); - $order_by = $paramFetcher->get('order_by'); - $filters = !is_null($paramFetcher->get('filters')) ? $paramFetcher->get('filters') : array(); + $offset = $paramFetcher->get('offset'); + $limit = $paramFetcher->get('limit'); + $order_by = $paramFetcher->get('order_by'); + $filters = !is_null($paramFetcher->get('filters')) ? $paramFetcher->get('filters') : array(); - $em = $this->getDoctrine()->getManager(); - $entities = $em->getRepository('{{ bundle }}:{{ entity }}')->findBy($filters, $order_by, $limit, $offset); - if ($entities) { - return $entities; - } - - return FOSView::create('Not Found', Codes::HTTP_NO_CONTENT); - } catch (\Exception $e) { - return FOSView::create($e->getMessage(), Codes::HTTP_INTERNAL_SERVER_ERROR); + $answer{{ resource ? "['" ~ entity|lower ~ "']" }} = $this->container->get('{{ bundle|replace({'Bundle': ''})|lower}}.{{ entity|lower }}.handler')->getAll($filters, $order_by, $limit, $offset); + if ($answer{{ resource ? "['" ~ entity|lower ~ "']" }}) { + return $answer; } + return null; {% endblock method_body %} {% block method_return '' %} } diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getById.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getById.php.twig old mode 100644 new mode 100755 index 848910e..f6f1b42 --- a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getById.php.twig +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getById.php.twig @@ -1,23 +1,36 @@ - /** + /** {% block phpdoc_method_header %} - * Get a {{ entity }} entity - * - * @View(serializerEnableMaxDepthChecks=true) - * - * @return Response + * Get a {{ entity }} entity + * +{% block documentation %} +{% if document %} + * @ApiDoc( + * resource = true, + * description = "Get a {{ entity }} entity.", + * statusCodes = { + * 200 = "{{ entity }}'s object.", + * 404 = "Not Found." + * } + * ) +{% endif %} +{% endblock documentation %} + * @View(serializerEnableMaxDepthChecks=true) + * + * @return Response + * @param $id {% endblock phpdoc_method_header %} - * {% block phpdoc_method_annotations %} -{% if 'annotation' == format %} -{% endif %} + {% if 'annotation' == format %} + {% endif %} {% endblock phpdoc_method_annotations %} - */ +*/ {% block method_definition %} - public function getAction({{ entity }} $entity) + public function getAction($id) {% endblock method_definition %} { {% block method_body %} - return $entity; + $answer{{ resource ? "['" ~ entity|lower ~ "']" }} = $this->getOr404($id); + return $answer; {% endblock method_body %} {% block method_return '' %} } diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getOr404.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getOr404.php.twig new file mode 100755 index 0000000..ffbea13 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getOr404.php.twig @@ -0,0 +1,21 @@ + /** +{% block phpdoc_method_header %} + * Get a entity or throw a exception + * + * @param $id + * @return $entity +{% endblock phpdoc_method_header %} + */ +{% block method_definition %} + protected function getOr404($id) +{% endblock method_definition %} + { +{% block method_body %} + if (!($entity = $this->container->get('{{ bundle|replace({'Bundle': ''})|lower}}.{{ entity|lower }}.handler')->get($id))) { + throw new NotFoundHttpException(sprintf('The resource \'%s\' was not found.',$id)); + } + + return $entity; +{% endblock method_body %} + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/patch.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/patch.php.twig old mode 100644 new mode 100755 index 323cb7a..4a7c498 --- a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/patch.php.twig +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/patch.php.twig @@ -1,24 +1,38 @@ - /** + /** {% block phpdoc_method_header %} - * Partial Update to a {{ entity }} entity. - * - * @View(serializerEnableMaxDepthChecks=true) - * - * @param Request $request - * @param $entity - * - * @return Response + * Partial Update to a {{ entity }} entity. + * +{% block documentation %} +{% if document %} + * @ApiDoc( + * resource = true, + * description = "Partial Update to a {{ entity }} entity.", + * statusCodes = { + * 200 = "Updated object.", + * 400 = "Bad Request. Verify your params.", + * 404 = "Not Found." + * } + * ) +{% endif %} +{% endblock documentation %} + * @View(serializerEnableMaxDepthChecks=true) + * + * @param Request $request + * @param $id + * + * @return Response {% endblock phpdoc_method_header %} {% block phpdoc_method_annotations %} -{% if 'annotation' == format %}{% endif %} + {% if 'annotation' == format %}{% endif %} {% endblock phpdoc_method_annotations %} */ {% block method_definition %} - public function patchAction(Request $request, {{ entity }} $entity) + public function patchAction(Request $request, $id) {% endblock method_definition %} { {% block method_body %} - return $this->putAction($request, $entity); + $answer{{ resource ? "['" ~ entity|lower ~ "']" }} = $this->container->get('{{ bundle|replace({'Bundle': ''})|lower}}.{{ entity|lower }}.handler')->patch($this->getOr404($id), $request->request->all()); + return $answer; {% endblock method_body %} {% block method_return '' %} } diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/post.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/post.php.twig old mode 100644 new mode 100755 index 54beeec..8cadcd4 --- a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/post.php.twig +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/post.php.twig @@ -1,39 +1,43 @@ - /** + /** {% block phpdoc_method_header %} - * Create a {{ entity }} entity. - * - * @View(statusCode=201, serializerEnableMaxDepthChecks=true) - * - * @param Request $request - * - * @return Response + * Create a {{ entity }} entity. + * +{% block documentation %} +{% if document %} + * @ApiDoc( + * resource = true, + * description = "Create a {{ entity }} entity.", + * statusCodes = { + * 201 = "Created object.", + * 400 = "Bad Request. Verify your params.", + * 404 = "Not Found." + * } + * ) +{% endif %} +{% endblock documentation %} + * @View(statusCode=201, serializerEnableMaxDepthChecks=true) + * + * @param Request $request + * + * @return Response {% endblock phpdoc_method_header %} - * {% block phpdoc_method_annotations %} {% if 'annotation' == format %}{% endif %} {% endblock phpdoc_method_annotations %} - */ + */ {% block method_definition %} public function postAction(Request $request) {% endblock method_definition %} { {% block method_body %} - $entity = new {{ entity }}(); - $form = $this->createForm(new {{ entity }}Type(), $entity, array("method" => $request->getMethod())); - $this->removeExtraFields($request, $form); - $form->handleRequest($request); + try { + $new = $this->container->get('{{ bundle|replace({'Bundle': ''})|lower}}.{{ entity|lower }}.handler')->post($request->request->all()); + $answer{{ resource ? "['" ~ entity|lower ~ "']" }} = $new; - if ($form->isValid()) { - $em = $this->getDoctrine()->getManager(); - $em->persist($entity); - $em->flush(); - - return $entity; + return $answer; + } catch (InvalidFormException $exception) { + return $exception->getForm(); } - {% endblock method_body %} -{% block method_return %} - return FOSView::create(array('errors' => $form->getErrors()), Codes::HTTP_INTERNAL_SERVER_ERROR); -{% endblock method_return %} } {% block form '' %} diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/put.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/put.php.twig old mode 100644 new mode 100755 index 31cf008..eeb7447 --- a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/put.php.twig +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/put.php.twig @@ -1,39 +1,51 @@ - /** + /** {% block phpdoc_method_header %} - * Update a {{ entity }} entity. - * - * @View(serializerEnableMaxDepthChecks=true) - * - * @param Request $request - * @param $entity - * - * @return Response + * Update a {{ entity }} entity. + * +{% block documentation %} +{% if document %} + * @ApiDoc( + * resource = true, + * description = "Update a {{ entity }} entity.", + * statusCodes = { + * 200 = "Updated object.", + * 201 = "Created object.", + * 400 = "Bad Request. Verify your params.", + * 404 = "Not Found." + * } + * ) +{% endif %} +{% endblock documentation %} + * @View(serializerEnableMaxDepthChecks=true) + * + * @param Request $request + * @param $id + * + * @return Response {% endblock phpdoc_method_header %} {% block phpdoc_method_annotations %} -{% if 'annotation' == format %}{% endif %} + {% if 'annotation' == format %}{% endif %} {% endblock phpdoc_method_annotations %} - */ +*/ {% block method_definition %} - public function putAction(Request $request, {{ entity }} $entity) + public function putAction(Request $request, $id) {% endblock method_definition %} { {% block method_body %} try { - $em = $this->getDoctrine()->getManager(); - $request->setMethod('PATCH'); //Treat all PUTs as PATCH - $form = $this->createForm(new {{ entity }}Type(), $entity, array("method" => $request->getMethod())); - $this->removeExtraFields($request, $form); - $form->handleRequest($request); - if ($form->isValid()) { - $em->flush(); - - return $entity; + if (${{ entity|lower }} = $this->container->get('{{ bundle|replace({'Bundle': ''})|lower}}.{{ entity|lower }}.handler')->get($id)) { + $answer{{ resource ? "['" ~ entity|lower ~ "']" }}= $this->container->get('{{ bundle|replace({'Bundle': ''})|lower}}.{{ entity|lower }}.handler')->put(${{ entity|lower }}, $request->request->all()); + $code = Codes::HTTP_OK; + } else { + $answer{{ resource ? "['" ~ entity|lower ~ "']" }} = $this->container->get('{{ bundle|replace({'Bundle': ''})|lower}}.{{ entity|lower }}.handler')->post($request->request->all()); + $code = Codes::HTTP_CREATED; } - - return FOSView::create(array('errors' => $form->getErrors()), Codes::HTTP_INTERNAL_SERVER_ERROR); - } catch (\Exception $e) { - return FOSView::create($e->getMessage(), Codes::HTTP_INTERNAL_SERVER_ERROR); + } catch (InvalidFormException $exception) { + return $exception->getForm(); } + + $view = $this->view($answer, $code); + return $this->handleView($view); {% endblock method_body %} {% block method_return '' %} } diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/controller.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/controller.php.twig old mode 100644 new mode 100755 index 6cf716a..624a63c --- a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/controller.php.twig +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/controller.php.twig @@ -5,6 +5,7 @@ namespace {{ namespace }}\Controller{{ entity_namespace ? '\\' ~ entity_namespac {% block use_statements %} use {{ namespace }}\Entity\{{ entity }}; use {{ namespace }}\Form\{{ entity }}Type; +use {{ namespace }}\Exception\InvalidFormException; use FOS\RestBundle\Controller\Annotations\QueryParam; use FOS\RestBundle\Controller\Annotations\RouteResource; @@ -22,8 +23,12 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\Form\Form; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Voryx\RESTGeneratorBundle\Controller\VoryxController; +use FOS\RestBundle\Controller\FOSRestController; +{% if document %} +use Nelmio\ApiDocBundle\Annotation\ApiDoc; +{% endif %} {% endblock use_statements %} /** @@ -38,7 +43,7 @@ use Voryx\RESTGeneratorBundle\Controller\VoryxController; {% endblock phpdoc_class_annotations %} */ {% block class_definition %} -class {{ entity_class }}RESTController extends VoryxController +class {{ entity_class }}RESTController extends FOSRestController {% endblock class_definition %} { {% block class_body %} @@ -53,5 +58,7 @@ class {{ entity_class }}RESTController extends VoryxController {%- include 'rest/actions/patch.php.twig' %} {%- include 'rest/actions/delete.php.twig' %} + + {%- include 'rest/actions/getOr404.php.twig' %} {% endblock class_body %} } diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/form_exception.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/form_exception.php.twig new file mode 100644 index 0000000..14a0b4c --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/form_exception.php.twig @@ -0,0 +1,29 @@ +form = $form; + } + + public function getForm() + { + return $this->form; + } +{% endblock class_body %} +} \ No newline at end of file diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler.php.twig new file mode 100644 index 0000000..0eaeace --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler.php.twig @@ -0,0 +1,43 @@ +om = $om; + $this->entityClass = $entityClass; + $this->repository = $this->om->getRepository($this->entityClass); + $this->formFactory = $formFactory; +{% endblock method_body %} + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/delete.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/delete.php.twig new file mode 100644 index 0000000..8824fc8 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/delete.php.twig @@ -0,0 +1,16 @@ +{% block method_definition %} + public function delete({{ entity }} ${{ entity|lower }}) +{% endblock method_definition %} + { +{% block method_body %} + try { + $this->om->remove(${{ entity|lower }}); + $this->om->flush(); + + return null; + } catch (\Exception $e) { + throw new \RuntimeException(); + } +{% endblock method_body %} + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/extras.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/extras.php.twig new file mode 100644 index 0000000..903caf5 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/extras.php.twig @@ -0,0 +1,20 @@ +{% block extras_methods %} + private function processForm({{ entity }} ${{ entity|lower }}, array $parameters, $method = "PUT") + { + $form = $this->formFactory->create(new {{ entity }}Type(), ${{ entity|lower }}, array('method' => $method)); + $form->submit($parameters, 'PATCH' !== $method); + if ($form->isValid()) { + ${{ entity|lower }} = $form->getData(); + $this->om->persist(${{ entity|lower }}); + $this->om->flush(${{ entity|lower }}); + + return ${{ entity|lower }}; + } + throw new InvalidFormException('Invalid submitted data', $form); + } + + private function create{{ entity }}() + { + return new $this->entityClass(); + } +{% endblock extras_methods %} diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/get.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/get.php.twig new file mode 100644 index 0000000..202e2e7 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/get.php.twig @@ -0,0 +1,9 @@ +{% block method_definition %} + public function get($id) +{% endblock method_definition %} + { +{% block method_body %} + return $this->repository->find($id); +{% endblock method_body %} + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/getAll.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/getAll.php.twig new file mode 100644 index 0000000..d8d5bc8 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/getAll.php.twig @@ -0,0 +1,9 @@ +{% block method_definition %} + public function getAll($filters = array(), $order_by = null, $limit = null, $offset = null) +{% endblock method_definition %} + { +{% block method_body %} + return $this->repository->findBy($filters, $order_by, $limit, $offset); +{% endblock method_body %} + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/patch.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/patch.php.twig new file mode 100644 index 0000000..fbe9b54 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/patch.php.twig @@ -0,0 +1,9 @@ +{% block method_definition %} + public function patch({{ entity }} ${{ entity|lower }}, array $parameters) +{% endblock method_definition %} + { +{% block method_body %} + return $this->processForm(${{ entity|lower }}, $parameters, 'PATCH'); +{% endblock method_body %} + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/post.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/post.php.twig new file mode 100644 index 0000000..7cfa712 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/post.php.twig @@ -0,0 +1,11 @@ +{% block method_definition %} + public function post($parameters) +{% endblock method_definition %} + { +{% block method_body %} + ${{ entity|lower }} = $this->create{{ entity }}(); + + return $this->processForm(${{ entity|lower }}, $parameters, 'POST'); +{% endblock method_body %} + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/put.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/put.php.twig new file mode 100644 index 0000000..5c44191 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/put.php.twig @@ -0,0 +1,9 @@ +{% block method_definition %} + public function put({{ entity }} ${{ entity|lower }}, array $parameters) +{% endblock method_definition %} + { +{% block method_body %} + return $this->processForm(${{ entity|lower }}, $parameters, 'PUT'); +{% endblock method_body %} + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/service/services.xml.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/service/services.xml.twig new file mode 100644 index 0000000..124152f --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/service/services.xml.twig @@ -0,0 +1,6 @@ + + + + \ No newline at end of file