diff --git a/.gitignore b/.gitignore index d938b52..d072ff3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /vendor/ composer.lock /nbproject/private/ -nbproject/ \ No newline at end of file +nbproject/ + +.idea/ \ No newline at end of file diff --git a/README.Symfony3.md b/README.Symfony3.md new file mode 100644 index 0000000..717d1a8 --- /dev/null +++ b/README.Symfony3.md @@ -0,0 +1,182 @@ +# Voryx REST Generator Bundle +[![SensioLabsInsight](https://insight.sensiolabs.com/projects/ac1842d9-4e36-45cc-8db1-b97e2e62540e/big.png)](https://insight.sensiolabs.com/projects/ac1842d9-4e36-45cc-8db1-b97e2e62540e) + +## About + +A CRUD like REST Generator + +## Features + +* Generators RESTful action from entity +* Simplifies setting up a RESTful Controller + + +## Installation +Require the "voryx/restgeneratorbundle" package in your composer.json and update your dependencies. + +```bash +$ php composer.phar require voryx/restgeneratorbundle dev-master +``` + +Add the VoryxRestGeneratorBundle to your application's kernel along with other dependencies: + +```php +public function registerBundles() +{ + $bundles = array( + //... + new Voryx\RESTGeneratorBundle\VoryxRESTGeneratorBundle(), + new FOS\RestBundle\FOSRestBundle(), + new JMS\SerializerBundle\JMSSerializerBundle($this), + new Nelmio\CorsBundle\NelmioCorsBundle(), + new Nelmio\ApiDocBundle\NelmioApiDocBundle(), + //... + ); + //... +} +``` + +## Configuration + +This bundle depends on a number of other Symfony bundles, so they need to be configured in order for the generator to work properly + +```yaml +framework: + csrf_protection: false #only use for public API + +fos_rest: + routing_loader: + default_format: json + param_fetcher_listener: true + body_listener: true + #disable_csrf_role: ROLE_USER + body_converter: + enabled: true + view: + view_response_listener: force + +nelmio_cors: + defaults: + allow_credentials: false + allow_origin: [] + allow_headers: [] + allow_methods: [] + expose_headers: [] + max_age: 0 + paths: + '^/api/': + allow_origin: ['*'] + allow_headers: ['*'] + allow_methods: ['POST', 'PUT', 'GET', 'DELETE'] + max_age: 3600 + +sensio_framework_extra: + request: { converters: true } + view: { annotations: false } + router: { annotations: true } + +nelmio_api_doc: ~ +``` + +## Generating the Controller + + +Generate the REST controller + +```bash +$ php bin/console voryx:generate:rest +``` + +This will guide you through the generator which will generate a RESTful controller for an entity. + + +## Example + +Create a new entity called 'Post': + +```bash +$ php bin/console doctrine:generate:entity --entity=AppBundle:Post --format=annotation --fields="name:string(255) description:string(255)" --no-interaction +``` + +Update the database schema: + +```bash +$ php bin/console doctrine:schema:update --force +``` + +Generate the API controller: + +```bash +$ php bin/console voryx:generate:rest --entity="AppBundle:Post" +``` + +Full example with all parameters + +```bash +$ php app/console voryx:generate:rest --entity="AppBundle:Post" --document --overwrite --route-prefix="api" --route-format="yml" --service-format="yml" --test="none" +``` + +possible values for all parameters + +| Parameter | Explanation | Values | +| --------- | ----------- | ------- | +| entity | The entity for which the REST api should be generated | **AppBundle:Post**, **AppBundle:Blog\Post** +| document | Whether or not you want API's documented by Nelmio | Yes if present, else No +| overwrite | Whether or not you want to overwrite existing generated files | Yes if present, else No +| resource | - | Yes if present, else No +| route-prefix | The route to prefix the generated Controller with (default api) | Any string +| route-format | The format that routing is generated in (default yml) | **yml** or **annotation** +| service-format | The format that the service is generated in (default yml) | **yml** or **xml** +| test | The type of test that should be generated | **none**, **oauth** or **no-authentication** + + +### Using the API +If you selected the default options you'll be able to start using the API like this: + +Creating a new post (`POST`) + +```bash +$ curl -i -H "Content-Type: application/json" -X POST -d '{"name" : "Test Post", "description" : "This is a test post"}' http://localhost/app_dev.php/api/posts +``` + +Updating (`PUT`) + +```bash +$ curl -i -H "Content-Type: application/json" -X PUT -d '{"name" : "Test Post 1", "description" : "This is an updated test post"}' http://localhost/app_dev.php/api/posts/1 +``` + +Get all posts (`GET`) + +```bash +$ curl http://localhost/app_dev.php/api/posts +``` + +Get one post (`GET`) + +```bash +$ curl http://localhost/app_dev.php/api/posts/1 +``` + + +Delete (`DELETE`) + +```bash +$ curl -X DELETE http://localhost/app_dev.php/api/posts/1 +``` + + +## Related Entities + +If you want the form to be able to convert related entities into the correct entity id on POST, PUT or PATCH, use the voryx_entity form type + +```php +use Voryx\RESTGeneratorBundle\Form\Type\VoryxEntityType; + +#Form/PostType() + + ->add( + 'user', VoryxEntityType:class, array( + 'class' => 'Acme\Bundle\Entity\User' + ) + ) +``` diff --git a/README.md b/README.md index a0f1227..d467b15 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 @@ -107,6 +110,27 @@ Generate the API controller: $ php app/console voryx:generate:rest --entity="AppBundle:Post" ``` +Full example with all parameters + +```bash +$ php app/console voryx:generate:rest --entity="AppBundle:Post" --document --resource --overwrite --route-prefix="api" --route-format="yml" --service-format="yml" --test="none" +$ php app/console voryx:generate:rest --entity="AppBundle:User/Domain" --document --overwrite --route-prefix="api" --route-format="annotation" --service-format="yml" --test="oauth2" +``` + +possible values for all parameters + +| Parameter | Explanation | Values | +| --------- | ----------- | ------- | +| entity | The entity for which the REST api should be generated | **AppBundle:Post**, **AppBundle:Blog\Post** +| document | Whether or not you want API's documented by Nelmio | Yes if present, else No +| overwrite | Whether or not you want to overwrite existing generated files | Yes if present, else No +| resource | Whether or not you want the resource name encapsulating the response | Yes if present, else No +| route-prefix | The route to prefix the generated Controller with (default api) | Any string +| route-format | The format that routing is generated in (default yml) | **yml** or **annotation** +| service-format | The format that the service is generated in (default yml) | **yml** or **xml** +| test | The type of test that should be generated | **none**, **oauth** or **no-authentication** + + ### Using the API If you selected the default options you'll be able to start using the API like this: @@ -147,10 +171,12 @@ $ curl -X DELETE http://localhost/app_dev.php/api/posts/1 If you want the form to be able to convert related entities into the correct entity id on POST, PUT or PATCH, use the voryx_entity form type ```php -#Form/PostType() +use Voryx\RESTGeneratorBundle\Form\Type\VoryxEntityType; + +// ... ->add( - 'user', 'voryx_entity', array( + 'user', VoryxEntityType:class, array( 'class' => 'Acme\Bundle\Entity\User' ) ) diff --git a/composer.json b/composer.json index 347beb1..3d6c22e 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "voryx/restgeneratorbundle", - "description": "REST API Generator for Symfony 2", + "description": "REST API Generator for Symfony", "type": "symfony-bundle", "license": "MIT", "authors": [ @@ -9,14 +9,20 @@ }, { "name": "David Dan" + }, + { + "name": "Maarten Sprakel" } ], "require": { - "php": ">=5.3.0", + "php": ">=5.5.9", + "symfony/symfony": ">=2.8", "sensio/generator-bundle": "~3.0", - "friendsofsymfony/rest-bundle": "~1.7", - "jms/serializer-bundle": "~1.1", - "nelmio/cors-bundle": "~1.4 " + "friendsofsymfony/rest-bundle": "^2.2", + "jms/serializer-bundle": "^2.0", + "nelmio/cors-bundle": "~1.5", + "nelmio/api-doc-bundle": "~2.7", + "doctrine/orm": ">=2.0" }, "autoload": { "psr-0": { diff --git a/src/Voryx/RESTGeneratorBundle/Command/GenerateDoctrineRESTCommand.php b/src/Voryx/RESTGeneratorBundle/Command/GenerateDoctrineRESTCommand.php index d90f461..6517eac 100644 --- a/src/Voryx/RESTGeneratorBundle/Command/GenerateDoctrineRESTCommand.php +++ b/src/Voryx/RESTGeneratorBundle/Command/GenerateDoctrineRESTCommand.php @@ -13,8 +13,13 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\Bundle\BundleInterface; use Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; use Voryx\RESTGeneratorBundle\Generator\DoctrineRESTGenerator; use Sensio\Bundle\GeneratorBundle\Command\Validators; use Voryx\RESTGeneratorBundle\Manipulator\RoutingManipulator; @@ -39,9 +44,12 @@ protected function configure() 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('route-format', '', InputOption::VALUE_REQUIRED, 'The format used for generation of routing (yml or annotation)', 'yml'), + new InputOption('service-format', '', InputOption::VALUE_REQUIRED, 'The format used for generation of services (yml or xml)', 'yml'), + new InputOption('test', '', InputOption::VALUE_REQUIRED, 'Generate a test for the given authentication mode (oauth2, no-authentication, none)', 'none'), 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'), + new InputOption('document', '', InputOption::VALUE_NONE, 'Use NelmioApiDocBundle to document the controller') ) ) ->setDescription('Generates a REST api based on a Doctrine entity') @@ -71,6 +79,9 @@ protected function configure() /** * @see Command + * @param InputInterface $input + * @param OutputInterface $output + * @return int|null */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -88,9 +99,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $entity = Validators::validateEntityName($input->getOption('entity')); list($bundle, $entity) = $this->parseShortcutNotation($entity); - $format = "rest"; + $format = $input->getOption('route-format'); + $service_format = $input->getOption('service-format'); $prefix = $this->getRoutePrefix($input, $entity); + /** @var bool $forceOverwrite */ $forceOverwrite = $input->getOption('overwrite'); + $test = $input->getOption('test'); $questionHelper->writeSection($output, 'REST api generation'); @@ -99,22 +113,61 @@ protected function execute(InputInterface $input, OutputInterface $output) $bundle = $this->getContainer()->get('kernel')->getBundle($bundle); $resource = $input->getOption('resource'); $document = $input->getOption('document'); + $constraints = array(); + + $constraintMetadata = null; + try + { + /** @var \Symfony\Component\Validator\Validator\RecursiveValidator $validator */ + $validator = $this->getContainer()->get('validator'); + + /** @var ClassMetadata $constraintMetadata */ + $constraintMetadata = $validator->getMetadataFor(new $entityClass); + foreach($constraintMetadata->getConstrainedProperties() as $property) + { + //var_dump($constraint_metadata->getPropertyMetadata($property)); + $constraints[$property] = $constraintMetadata->getPropertyMetadata($property)[0]->constraints; + } + } + catch(ServiceNotFoundException $snfex) + { + //no constraints are checked + $output->writeln($snfex->getMessage()); + } + catch(\Exception $ex) + { + $output->writeln($ex->getMessage()); + } + if ($constraintMetadata === null) + { + if ($test !== 'none') + { + $output->writeln('No class constraint metadata found for entity ' . $entityClass . ''); + } + } + + /** @var DoctrineRESTGenerator $generator */ $generator = $this->getGenerator($bundle); - $generator->generate($bundle, $entity, $metadata[0], $prefix, $forceOverwrite, $resource, $document); + $generator->generate($bundle, $entity, $metadata[0], $constraints, $prefix, $forceOverwrite, $resource, $document, $format, $service_format, $test); $output->writeln('Generating the REST api code: OK'); + if ($test === 'oauth2') + { + $output->writeln('Please make sure you check the Tests/oauthBase.php and fill in a correct username/password on line 17/18 before running the test.'); + } $errors = array(); $runner = $questionHelper->getRunner($output, $errors); // form - $this->generateForm($bundle, $entity, $metadata); + $this->generateForm($bundle, $entity, $metadata, $forceOverwrite); $output->writeln('Generating the Form code: OK'); // create route $runner($this->updateRouting($questionHelper, $input, $output, $bundle, $format, $entity, $prefix)); + $questionHelper->writeGeneratorSummary($output, $errors); } @@ -149,6 +202,21 @@ protected function interact(InputInterface $input, OutputInterface $output) $input->setOption('entity', $entity); list($bundle, $entity) = $this->parseShortcutNotation($entity); + //routing format + $format = $input->getOption('route-format'); + $output->writeln( + array( + '', + 'Determine the routing format (yml or annotation).', + '' + ) + ); + $question = new Question($questionHelper->getQuestion('Routing format', $format), $format); + $question->setValidator(array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateFormat')); + $format = $questionHelper->ask($input, $output, $question); + + $input->setOption('route-format',$format); + // route prefix $prefix = 'api'; $output->writeln( @@ -163,6 +231,36 @@ protected function interact(InputInterface $input, OutputInterface $output) $prefix = $questionHelper->ask($input, $output, new Question($questionHelper->getQuestion('Routes prefix', '/' . $prefix), '/' . $prefix)); $input->setOption('route-prefix', $prefix); + //service format + $serviceFormat = $input->getOption('service-format'); + $output->writeln( + array( + '', + 'Determine the service format (yml or xml).', + '' + ) + ); + $question = new Question($questionHelper->getQuestion('Service format', $serviceFormat), $serviceFormat); + $question->setValidator(array('Voryx\RESTGeneratorBundle\Command\Validators', 'validateServiceFormat')); + $serviceFormat = $questionHelper->ask($input, $output, $question); + + $input->setOption('service-format',$serviceFormat); + + //testing mode + $output->writeln( + array( + '', + 'Determine what kind of test you want to have generated (if any)', + 'Possible values are none (no tests), no-authentication and oauth2', + '' + ) + ); + $question = new Question($questionHelper->getQuestion('What type of tests do you want to generate?', $input->getOption('test')),$input->getOption('test')); + $question->setValidator(array('Voryx\RESTGeneratorBundle\Command\Validators', 'validateTestFormat')); + $test = $questionHelper->ask($input, $output, $question); + + $input->setOption('test',$test); + // summary $output->writeln( array( @@ -175,12 +273,12 @@ protected function interact(InputInterface $input, OutputInterface $output) ); } - /** * @param QuestionHelper $questionHelper * @param InputInterface $input * @param OutputInterface $output * @param BundleInterface $bundle + * @param $format * @param $entity * @param $prefix * @return array @@ -195,7 +293,59 @@ protected function updateRouting(QuestionHelper $questionHelper, InputInterface $output->write('Importing the REST api routes: '); $this->getContainer()->get('filesystem')->mkdir($bundle->getPath() . '/Resources/config/'); + + if ($format === 'annotation') + { + $bundle_name = str_replace("Bundle", "", $bundle->getName()); + $route_name = strtolower($bundle_name); + $yml_file_location = $this->getContainer()->getParameter('kernel.root_dir') . '/config/routing.yml'; + try + { + $yml_file = Yaml::parse(file_get_contents($yml_file_location)); + } + catch(ParseException $pex) + { + return array( + 'Could not read yaml file '.$yml_file_location.'', + 'On line', + 'parsed line: '.$pex->getParsedLine() . ' and current line '. $pex->getLine(), + 'With snippet '.$pex->getSnippet(), + 'Exception message:', + ''.$pex->getMessage().'' + ); + } + $resource_location = sprintf('@%s/Controller/', $bundle->getName()); + + $bundle_routing = null; + if (array_key_exists($route_name, $yml_file)) + { + $bundle_routing = $yml_file[$route_name]; + } + + if (is_array($bundle_routing)) + { + if (array_key_exists('type',$bundle_routing) && array_key_exists('resource',$bundle_routing) && $bundle_routing['type'] === $format && $bundle_routing['resource'] === $resource_location) + { + //all is good + return array(); + } + } + + $bundle_routing = array( + 'resource' => $resource_location, + 'type' => $format + ); + + $yml_file[$route_name] = $bundle_routing; + + $yml_content = Yaml::dump($yml_file, 2); + file_put_contents($yml_file_location, $yml_content); + + return array(); + } + $routing = new RoutingManipulator($this->getContainer()->getParameter('kernel.root_dir') . '/config/routing.yml'); + try { $ret = $auto ? $routing->addResource($bundle->getName(), '/' . $prefix, $entity) : false; } catch (\RuntimeException $exc) { @@ -210,7 +360,6 @@ protected function updateRouting(QuestionHelper $questionHelper, InputInterface ); $help .= sprintf(" type: %s\n", 'rest'); $help .= sprintf(" prefix: /%s\n", $prefix); - return array( '- Import this resource into the Apps routing file', sprintf(' (%s).', $this->getContainer()->getParameter('kernel.root_dir') . '/config/routing.yml'), @@ -232,7 +381,10 @@ protected function updateRouting(QuestionHelper $questionHelper, InputInterface */ protected function createGenerator($bundle = null) { - return new DoctrineRESTGenerator($this->getContainer()->get('filesystem')); + /** @var Filesystem $fileSystem */ + $fileSystem = $this->getContainer()->get('filesystem'); + + return new DoctrineRESTGenerator($fileSystem); } /** diff --git a/src/Voryx/RESTGeneratorBundle/Command/Validators.php b/src/Voryx/RESTGeneratorBundle/Command/Validators.php new file mode 100644 index 0000000..9451005 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Command/Validators.php @@ -0,0 +1,62 @@ + + */ +class Validators +{ + /** + * @param string $format + * @return string + * @throws \RuntimeException + */ + public static function validateTestFormat($format) + { + if (!$format) { + return 'none'; + } + + if ($format === 'oauth') { + $format = 'oauth2'; + } + + $format = strtolower($format); + + $supported = array('none', 'oauth2', 'no-authentication', 'csrf'); + + if (!in_array($format, $supported)) { + throw new \RuntimeException(sprintf('Test format "%s" is not supported, only '.implode(',',$supported).' are supported.', $format)); + } + + return $format; + } + + /** + * @param string $service_format + * @return string + * @throws \RuntimeException + */ + public static function validateServiceFormat($service_format) + { + if (!$service_format) + { + return 'xml'; + } + + $service_format = strtolower($service_format); + + $supported_service_formats = array('xml', 'yml'); + + if (!in_array($service_format, $supported_service_formats)) + { + throw new \RuntimeException(sprintf('Service format "%s" is not supported, only '.implode(',',$supported_service_formats).' are supported.', $service_format)); + } + + return $service_format; + } +} diff --git a/src/Voryx/RESTGeneratorBundle/Controller/VoryxController.php b/src/Voryx/RESTGeneratorBundle/Controller/VoryxController.php index ce6587d..cf53e47 100644 --- a/src/Voryx/RESTGeneratorBundle/Controller/VoryxController.php +++ b/src/Voryx/RESTGeneratorBundle/Controller/VoryxController.php @@ -1,15 +1,11 @@ setDefaults( array( @@ -58,14 +58,6 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) */ public function getParent() { - return 'entity'; - } - - /** - * @return string - */ - public function getName() - { - return 'voryx_entity'; + return EntityType::class; } } diff --git a/src/Voryx/RESTGeneratorBundle/Generator/DoctrineRESTGenerator.php b/src/Voryx/RESTGeneratorBundle/Generator/DoctrineRESTGenerator.php index f90caaf..f7f1a39 100644 --- a/src/Voryx/RESTGeneratorBundle/Generator/DoctrineRESTGenerator.php +++ b/src/Voryx/RESTGeneratorBundle/Generator/DoctrineRESTGenerator.php @@ -11,10 +11,15 @@ namespace Voryx\RESTGeneratorBundle\Generator; +use Doctrine\ORM\Mapping\ClassMetadataInfo; use Sensio\Bundle\GeneratorBundle\Generator\Generator; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\Bundle\BundleInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Yaml\Parser; +use Symfony\Component\Yaml\Yaml; /** * Generates a REST controller. @@ -23,12 +28,18 @@ */ class DoctrineRESTGenerator extends Generator { + /** @var Filesystem */ protected $filesystem; protected $routePrefix; protected $routeNamePrefix; + + /** @var BundleInterface */ protected $bundle; protected $entity; + + /** @var ClassMetadataInfo */ protected $metadata; + protected $entityConstraints; protected $format; protected $actions; @@ -48,32 +59,44 @@ public function __construct(Filesystem $filesystem) * @param BundleInterface $bundle A bundle object * @param string $entity The entity relative class name * @param ClassMetadataInfo $metadata The entity class metadata + * @param array $entityConstraints array of fields with constraints array('field' => array(Constraint, Constraint2),'field2' => array(Constraint, Constraint2)) * @param string $routePrefix The route name prefix - * @param array $forceOverwrite Whether or not to overwrite an existing controller - * - * @throws \RuntimeException + * @param bool $forceOverwrite Whether or not to overwrite an existing controller + * @param bool $resource + * @param bool $document Whether or not to use Nelmio api documentation + * @param string $format Format of routing + * @param string $service_format Format of service generation + * @param string $test Test-mode (none, oauth or no-authentication) */ - public function generate(BundleInterface $bundle, $entity, ClassMetadataInfo $metadata, $routePrefix, $forceOverwrite) + public function generate(BundleInterface $bundle,$entity,ClassMetadataInfo $metadata, $entityConstraints,$routePrefix,$forceOverwrite,$resource,$document,$format, $service_format, $test) { - $this->routePrefix = $routePrefix; + $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->entityConstraints = $entityConstraints; + $this->setFormat($format); + $this->generateControllerClass($forceOverwrite, $document, $resource); + $this->generateHandler($forceOverwrite, $document); + $this->generateExceptionClass(); + $this->declareService($service_format); + $this->generateTestClass($forceOverwrite, $test); } /** @@ -128,9 +151,11 @@ protected function generateConfiguration() /** * Generates the controller class only. - * + * @param bool $forceOverwrite + * @param bool $document + * @param bool $resource */ - protected function generateControllerClass($forceOverwrite) + protected function generateControllerClass($forceOverwrite, $document, $resource) { $dir = $this->bundle->getPath(); @@ -153,36 +178,377 @@ protected function generateControllerClass($forceOverwrite) 'rest/controller.php.twig', $target, array( - 'actions' => $this->actions, - 'route_prefix' => $this->routePrefix, + 'actions' => $this->actions, + 'route_prefix' => $this->routePrefix, 'route_name_prefix' => $this->routeNamePrefix, - 'bundle' => $this->bundle->getName(), - 'entity' => $this->entity, - 'entity_class' => $entityClass, + '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. + * @param bool $forceOverwrite + * @param bool $document + */ + protected function generateHandler($forceOverwrite, $document) + { + $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, + 'document' => $document, + ) + ); + } + + /** + * + */ + 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 + * @param $service_format + */ + public function declareService($service_format) + { + $dir = $this->bundle->getPath(); + + $parts = explode('\\', $this->entity); + $entityClass = array_pop($parts); + $entityNamespace = implode('\\', $parts); + if (strlen($entityNamespace) > 0) + { + $entityNamespace .= '\\'; + } + $namespace = $this->bundle->getNamespace(); + + $bundleName = strtolower($this->bundle->getName()); + $entityName = strtolower($this->entity); + $entityName = str_replace('\\','.',$entityName); + + $services = sprintf( + "%s/Resources/config/servicesREST.".$service_format, + $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.".$service_format.".twig", $services, array()); + } + + switch($service_format) + { + case 'xml': + $this->handleServiceDeclarationAsXML($services,$newId, $handlerClass,$namespace,$entityNamespace,$entityClass,$fileName); + break; + case 'yml': + default: + $this->handleServiceDeclarationAsYML($services,$newId, $handlerClass,$namespace,$entityNamespace,$entityClass,$fileName); + break; + + } + $this->updateDIFile($fileName,$service_format); + } + + private function handleServiceDeclarationAsXML($services, $newId, $handlerClass,$namespace,$entityNamespace,$entityClass,$fileName) + { + //this could be saved more readable by using dom_import_simplexml (http://stackoverflow.com/questions/1191167/format-output-of-simplexml-asxml) + $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); + } + + private function handleServiceDeclarationAsYML($services, $newId, $handlerClass,$namespace,$entityNamespace,$entityClass,$fileName) + { + $yml_file = Yaml::parse(file_get_contents($services)); + $params = $yml_file['parameters']; + if (!is_array($params)) + { + $params = array(); + } + $yml_file['parameters'] = + array_merge( + $params, + array( + $newId.'.handler_class' => $handlerClass, + $newId.'.entity_class' => sprintf( + "%s\\Entity\\%s%s", + $namespace, + $entityNamespace, + $entityClass + ), + ) + ); + $yml_services = $yml_file['services']; + if (!is_array($yml_services)) + { + $yml_services = array(); + } + $yml_file['services'] = + array_merge( + $yml_services, + array( + $newId => array( + 'class' => '%'.$newId.'.handler_class%', 'arguments' => array( + '@doctrine.orm.entity_manager', + '%'.$newId.'.entity_class%', + '@form.factory', + ), + ), + ) + ); + $yml_content = Yaml::dump($yml_file, 3); + file_put_contents($services, $yml_content); + } + + /** + * @param $fileName + * @param $serviceFormat + */ + private function updateDIFile($fileName, $serviceFormat) + { + $toInput = ''; + switch($serviceFormat) + { + case 'xml': + $toInput = PHP_EOL . "\t\t\$loader2 = new Loader\\XmlFileLoader(\$container, new FileLocator(__DIR__ . '/../Resources/config'));" . PHP_EOL . + "\t\t\$loader2->load('servicesREST.".$serviceFormat."');" . PHP_EOL . "\t"; + break; + case 'yml': + default: + $toInput = PHP_EOL . "\t\t\$loader2 = new Loader\\YamlFileLoader(\$container, new FileLocator(__DIR__ . '/../Resources/config'));" . PHP_EOL . + "\t\t\$loader2->load('servicesREST.".$serviceFormat."');" . PHP_EOL . "\t"; + break; + + } + + if (!file_exists(dirname($fileName))) + { + mkdir(dirname($fileName), 0777, true); + } + if (!file_exists($fileName)) + { + $this->handleExtensionFileCreation($fileName); + } + $text = file_get_contents($fileName); + + if (strpos($text, "servicesREST.".$serviceFormat) == false) { + $position = strpos($text, "}", strpos($text, "function load(")); + + $newContent = substr_replace($text, $toInput, $position, 0); + file_put_contents($fileName, $newContent); + } + } + + /** + * @param $fileName + */ + private function handleExtensionFileCreation($fileName) + { + $parts = explode('\\', $this->entity); + $entityNamespace = implode('\\', $parts); + + $this->renderFile( + 'rest/extension.php.twig', + $fileName, + array( + 'class_name' => str_replace("Bundle", "Extension", $this->bundle->getName()), 'namespace' => $this->bundle->getNamespace(), 'entity_namespace' => $entityNamespace, - 'format' => $this->format, ) ); } + private function makeFormatUserFriendly($format) + { + $returnFormat = ''; + $parts = explode('-',$format); + foreach($parts as $part) + { + $returnFormat .= ucfirst($part); + } + + return $returnFormat; + } + /** * Generates the functional test class only. - * + * @param boolean $forceOverwrite whether or not to force overwriting or not + * @param string $format either none, no-authentication or oauth */ - protected function generateTestClass() + protected function generateTestClass($forceOverwrite, $format) { + if ($format === 'none') + { + return; + } + + $friendlyFormat = $this->makeFormatUserFriendly($format); + + $base_dir = $this->bundle->getPath() . '/Tests/Base'; + $dir = $this->bundle->getPath() . '/Tests/Controller'; + $parts = explode('\\', $this->entity); $entityClass = array_pop($parts); $entityNamespace = implode('\\', $parts); - $dir = $this->bundle->getPath() . '/Tests/Controller'; $target = $dir . '/' . str_replace('\\', '/', $entityNamespace) . '/' . $entityClass . 'RESTControllerTest.php'; + $base_target = $base_dir . '/' . $friendlyFormat . 'BaseCase.php'; + + if ($forceOverwrite === false && file_exists($target)) + { + throw new \RuntimeException('Unable to generate the test as it already exists.'); + } + + $this->generateBaseTestCaseIfNotExists($forceOverwrite, $format, $friendlyFormat, $base_target); + + $this->renderFile( + 'rest/test.php.twig', + $target, + array( + 'format' => $format, + 'friendly_format' => $friendlyFormat, + 'fields' => $this->metadata->fieldMappings, + 'assoc_mapping' => $this->metadata->associationMappings, + 'entity_constraints' => $this->entityConstraints, + 'base_file' => $base_target, + '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'), + ) + ); + } + + /** + * @param $overwrite + * @param $format + * @param $friendlyFormat + * @param $target + */ + protected function generateBaseTestCaseIfNotExists($overwrite, $format, $friendlyFormat, $target) + { + $parts = explode('\\', $this->entity); + $entityClass = array_pop($parts); + $entityNamespace = implode('\\', $parts); + + if (file_exists($target)) + { + return; + } $this->renderFile( - 'rest/tests/test.php.twig', + 'rest/tests/base/'.$format.'.php.twig', $target, array( + 'format' => $format, + 'friendly_format' => $friendlyFormat, + 'fields' => $this->metadata->fieldMappings, 'route_prefix' => $this->routePrefix, 'route_name_prefix' => $this->routeNamePrefix, 'entity' => $this->entity, @@ -191,7 +557,6 @@ protected function generateTestClass() '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 index 8e17c61..39bb2e0 100644 --- a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/delete.php.twig +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/delete.php.twig @@ -2,32 +2,45 @@ {% block phpdoc_method_header %} * 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 $entity + * @param $id * * @return Response {% endblock phpdoc_method_header %} {% block phpdoc_method_annotations %} -{% if 'annotation' == format %}{% endif %} +{% if 'annotation' == format %} + * + * @RESTDelete("/{id}.{_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_class|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|replace({"\\":'.'}) }}.handler')->delete(${{ entity_class|lower }}); + } catch (\Exception $exception) { + throw new \RuntimeException("Exclusion not allowed"); } {% endblock method_body %} {% block method_return '' %} } {% block form '' %} + 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..813f72d --- a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getAll.php.twig +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getAll.php.twig @@ -2,6 +2,18 @@ {% block phpdoc_method_header %} * 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 @@ -9,12 +21,16 @@ * @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="limit", requirements="\d+", default="20", description="How many {{ entity_class }} 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 %} + * + * @RESTGet(".{_format}") + * +{% endif %} {% endblock phpdoc_method_annotations %} */ {% block method_definition %} @@ -22,23 +38,18 @@ {% 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|replace({"\\":'.'}) }}.handler')->getAll($filters, $order_by, $limit, $offset); + if ($answer{{ resource ? "['" ~ entity|lower ~ "']" }}) { + return $answer; } + return null; {% endblock method_body %} {% block method_return '' %} } {% block form '' %} + 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..2a444db --- a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getById.php.twig +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/getById.php.twig @@ -2,23 +2,40 @@ {% block phpdoc_method_header %} * 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 %} + * + * @RESTGet("/{id}.{_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_class|lower ~ "']" }} = $this->getOr404($id); + return $answer; {% endblock method_body %} {% block method_return '' %} } {% block form '' %} + 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..19d2cc6 --- /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_class }} $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|replace({"\\":'.'}) }}.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 0a9b710..8788060 --- a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/patch.php.twig +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/patch.php.twig @@ -2,24 +2,43 @@ {% block phpdoc_method_header %} * 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 $entity + * @param $id * * @return Response {% endblock phpdoc_method_header %} {% block phpdoc_method_annotations %} -{% if 'annotation' == format %}{% endif %} +{% if 'annotation' == format %} + * + * @RESTPatch("/{id}.{_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|replace({"\\":'.'}) }}.handler')->patch($this->getOr404($id), $request->request->all()); + return $answer; {% endblock method_body %} {% block method_return '' %} } {% block form '' %} + 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 945a687..9a3be5a --- a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/post.php.twig +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/post.php.twig @@ -2,15 +2,31 @@ {% block phpdoc_method_header %} * 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 %} +{% if 'annotation' == format %} + * + * @RESTPost(".{_format}") + * +{% endif %} {% endblock phpdoc_method_annotations %} */ {% block method_definition %} @@ -18,22 +34,15 @@ {% endblock method_definition %} { {% block method_body %} - $entity = new {{ entity }}(); - $form = $this->createForm(get_class(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|replace({"\\":'.'}) }}.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 7597729..b887391 --- a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/put.php.twig +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/actions/put.php.twig @@ -2,39 +2,56 @@ {% block phpdoc_method_header %} * 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 $entity + * @param $id * * @return Response {% endblock phpdoc_method_header %} {% block phpdoc_method_annotations %} -{% if 'annotation' == format %}{% endif %} +{% if 'annotation' == format %} + * + * @RESTPut("/{id}.{_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(get_class(new {{ entity }}Type()), $entity, array("method" => $request->getMethod())); - $this->removeExtraFields($request, $form); - $form->handleRequest($request); - if ($form->isValid()) { - $em->flush(); - - return $entity; + if (${{ entity_class|lower }} = $this->container->get('{{ bundle|replace({'Bundle': ''})|lower}}.{{ entity|lower|replace({"\\":'.'}) }}.handler')->get($id)) { + $answer{{ resource ? "['" ~ entity|lower ~ "']" }}= $this->container->get('{{ bundle|replace({'Bundle': ''})|lower}}.{{ entity|lower|replace({"\\":'.'}) }}.handler')->put(${{ entity_class|lower }}, $request->request->all()); + $code = Codes::HTTP_OK; + } else { + $answer{{ resource ? "['" ~ entity|lower ~ "']" }} = $this->container->get('{{ bundle|replace({'Bundle': ''})|lower}}.{{ entity|lower|replace({"\\":'.'}) }}.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 '' %} } {% block form '' %} + 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..57638d5 --- 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; @@ -17,28 +18,38 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use FOS\RestBundle\Controller\Annotations\Get as RESTGet; +use FOS\RestBundle\Controller\Annotations\Put as RESTPut; +use FOS\RestBundle\Controller\Annotations\Patch as RESTPatch; +use FOS\RestBundle\Controller\Annotations\Post as RESTPost; +use FOS\RestBundle\Controller\Annotations\Delete as RESTDelete; {%- endif %} + 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 %} /** {% block phpdoc_class_header %} - * {{ entity }} controller. + * {{ entity|replace({"\\":'/'}) }} controller. {% endblock phpdoc_class_header %} * @RouteResource("{{ entity }}") {% block phpdoc_class_annotations %} {% if 'annotation' == format %} - * @Route("/{{ route_prefix }}") + * @Route("/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}") {% endif %} {% 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 +64,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/extension.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/extension.php.twig new file mode 100644 index 0000000..0748229 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/extension.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..bdeeef3 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler.php.twig @@ -0,0 +1,49 @@ +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..aa1acbe --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/delete.php.twig @@ -0,0 +1,23 @@ + /** +{% block phpdoc_method_header %} + * @param {{ entity_class }} ${{ entity_class|lower }} + * @return null + * @throws \RuntimeException +{% endblock phpdoc_method_header %} + */ +{% block method_definition %} + public function delete({{ entity_class }} ${{ entity_class|lower }}) +{% endblock method_definition %} + { +{% block method_body %} + try { + $this->om->remove(${{ entity_class|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..2972cd3 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/extras.php.twig @@ -0,0 +1,34 @@ +{% block extras_methods %} + /** +{% block phpdoc_method_header %} + * @param {{ entity_class }} ${{ entity_class|lower }} + * @param array $parameters + * @param string $method + * @return {{ entity_class }} ${{ entity_class|lower }} + * @throws InvalidFormException +{% endblock phpdoc_method_header %} + */ + private function processForm({{ entity_class }} ${{ entity_class|lower }}, array $parameters, $method = "PUT") + { + $form = $this->formFactory->create(get_class(new {{ entity_class }}Type()), ${{ entity_class|lower }}, array('method' => $method)); + $form->submit($parameters, 'PATCH' !== $method); + if ($form->isValid()) { + ${{ entity_class|lower }} = $form->getData(); + $this->om->persist(${{ entity_class|lower }}); + $this->om->flush(); + + return ${{ entity_class|lower }}; + } + throw new InvalidFormException('Invalid submitted data', $form); + } + +{% if document %} + /** + * @return {{ entity_class }} ${{ entity_class|lower }} + */ +{% endif %} + private function create{{ entity_class }}() + { + 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..f7adfb5 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/get.php.twig @@ -0,0 +1,15 @@ + /** +{% block phpdoc_method_header %} + * @param int $id + * @return {{ entity_class }} ${{ entity_class|lower }} + {% endblock phpdoc_method_header %} + */ +{% 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..b1f1dad --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/getAll.php.twig @@ -0,0 +1,18 @@ + /** +{% block phpdoc_method_header %} + * @param array $filters + * @param string|null $order_by The field to order by + * @param int|null $limit The limit + * @param int|null $offset The offset from the first record + * @return {{ entity_class }}[] ${{ entity_class|lower }}s +{% endblock phpdoc_method_header %} + */ +{% 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..8dce3d7 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/patch.php.twig @@ -0,0 +1,17 @@ + /** +{% block phpdoc_method_header %} + * @param {{ entity_class }} ${{ entity_class|lower }} + * @param array $parameters + * @return {{ entity_class }} ${{ entity_class|lower }} + * @throws InvalidFormException +{% endblock phpdoc_method_header %} + */ +{% block method_definition %} + public function patch({{ entity_class }} ${{ entity_class|lower }}, array $parameters) +{% endblock method_definition %} + { +{% block method_body %} + return $this->processForm(${{ entity_class|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..6b1d84d --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/post.php.twig @@ -0,0 +1,18 @@ + /** +{% block phpdoc_method_header %} + * @param array $parameters + * @return {{ entity_class }} ${{ entity_class|lower }} + * @throws InvalidFormException +{% endblock phpdoc_method_header %} + */ +{% block method_definition %} + public function post($parameters) +{% endblock method_definition %} + { +{% block method_body %} + ${{ entity_class|lower }} = $this->create{{ entity_class }}(); + + return $this->processForm(${{ entity_class|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..7c5eddf --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/handler/put.php.twig @@ -0,0 +1,17 @@ + /** +{% block phpdoc_method_header %} + * @param {{ entity_class }} ${{ entity_class|lower }} + * @param array $parameters + * @return {{ entity_class }} ${{ entity_class|lower }} + * @throws InvalidFormException +{% endblock phpdoc_method_header %} + */ +{% block method_definition %} + public function put({{ entity_class }} ${{ entity_class|lower }}, array $parameters) +{% endblock method_definition %} + { +{% block method_body %} + return $this->processForm(${{ entity_class|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 diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/service/services.yml.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/service/services.yml.twig new file mode 100644 index 0000000..12b6ebf --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/service/services.yml.twig @@ -0,0 +1,3 @@ +parameters: + +services: diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/test.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/test.php.twig new file mode 100644 index 0000000..c126411 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/test.php.twig @@ -0,0 +1,40 @@ +username === null || $this->password === null) + { + die('Please make sure to fill in an API user username/password in this file (path/to/Tests/oauthBase.php)'); + } + parent::__construct(); + $client = static::createClient(); + $clientManager = $client->getContainer()->get('fos_oauth_server.client_manager.default'); + /** @var OAuthClient $oauth_client */ + $oauth_client = $clientManager->createClient(); + $oauth_client->setRedirectUris(array()); + $oauth_client->setAllowedGrantTypes(array('authorization_code', 'password', 'refresh_token', 'client_credentials')); + $clientManager->updateClient($oauth_client); + + $public_key_split = $oauth_client->getPublicId(); + $secret_key_split = $oauth_client->getSecret(); + + self::$client_id = $public_key_split; + self::$client_secret = $secret_key_split; + $this->assertNotEquals(self::$client_id, null); + $this->assertNotEquals(self::$client_secret, null); + + $url = "/oauth/v2/token?client_id=".self::$client_id.'&client_secret='.self::$client_secret.'&grant_type=password'. + '&username='.$this->username.'&password='.$this->password; + $crawler = $client->request('GET', $url, array(), array(), array('HTTP_ACCEPT' => 'application/json', 'HTTP_CONTENT_TYPE' => 'application/json')); + $json_object = json_decode($client->getResponse()->getContent()); + self::$access_token = $json_object->access_token; + } +} diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/delete.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/delete.php.twig new file mode 100644 index 0000000..5dae00d --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/delete.php.twig @@ -0,0 +1,6 @@ + public function testDelete() + { + $crawler = self::$client->request('DELETE', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json'); + $this->assertEquals(204, self::$client->getResponse()->getStatusCode(), 'Expected a 204 response status after requesting DELETE /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json, got '.self::$client->getResponse()->getStatusCode()); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/get.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/get.php.twig new file mode 100644 index 0000000..3b829be --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/get.php.twig @@ -0,0 +1,6 @@ + public function testGet() + { + $crawler = self::$client->request('GET', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json'); + $this->assertEquals(200, self::$client->getResponse()->getStatusCode(), 'Expected a 200 response status after requesting /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json, got '.self::$client->getResponse()->getStatusCode()); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/getAfterDelete.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/getAfterDelete.php.twig new file mode 100644 index 0000000..a49ddb1 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/getAfterDelete.php.twig @@ -0,0 +1,6 @@ + public function testGetOnDeletedItem() + { + $crawler = self::$client->request('GET', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json'); + $this->assertEquals(200, self::$client->getResponse()->getStatusCode(), 'Expected a 200 response status after requesting /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json, got '.self::$client->getResponse()->getStatusCode()); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/getAll.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/getAll.php.twig new file mode 100644 index 0000000..c90c8d5 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/getAll.php.twig @@ -0,0 +1,13 @@ + public function testGetAllWithContent() + { + $crawler = self::$client->request('GET', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json'); + $this->assertEquals(200, self::$client->getResponse()->getStatusCode(), 'Expected a 200 response status after requesting /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json, got '.self::$client->getResponse()->getStatusCode()); + + $obj = json_decode(self::$client->getResponse()->getContent()); + $this->assertTrue(is_array($obj), 'expected decoded response content to be array, got '.gettype($obj)); + $this->assertTrue(count($obj) > 0, 'expected decoded response content to be an array with more then 0 items, got '.count($obj)); + $first = reset($obj); + $this->assertTrue(array_key_exists('id', $first), 'expected first array item to have a key id'); + self::$id = $first->id; + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/getAllWithoutContent.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/getAllWithoutContent.php.twig new file mode 100644 index 0000000..c722193 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/getAllWithoutContent.php.twig @@ -0,0 +1,6 @@ + public function testGetAllWithoutContent() + { + $crawler = self::$client->request('GET', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json'); + $this->assertEquals(204, self::$client->getResponse()->getStatusCode(), 'Expected a 204 response status after requesting /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json, got '.self::$client->getResponse()->getStatusCode()); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/patch.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/patch.php.twig new file mode 100644 index 0000000..cef4543 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/patch.php.twig @@ -0,0 +1,13 @@ + public function testPatch() + { + $json = json_encode(self::$sample_object); + + $crawler = self::$client->request('PATCH', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json', array(), array(), ['HTTP_CONTENT_TYPE' => 'application/json', 'CONTENT_TYPE' => 'application/json'], $json); + $this->assertEquals(200, self::$client->getResponse()->getStatusCode(), 'Expected a 200 response status after requesting PATCH /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json, got '.self::$client->getResponse()->getStatusCode()); + + $obj = json_decode(self::$client->getResponse()->getContent()); + //test response content + //$this->assertEquals($put_name, $obj->name, 'name did not change after PUT'); + //$this->assertEquals($put_description, $obj->description, 'description did not change after PUT'); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/post.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/post.php.twig new file mode 100644 index 0000000..f87aa36 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/post.php.twig @@ -0,0 +1,7 @@ + public function testPost() + { + $json = json_encode(self::$sample_object); + $crawler = self::$client->request('POST', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json', array(), array(), ['HTTP_CONTENT_TYPE' => 'application/json', 'CONTENT_TYPE' => 'application/json'], $json); + $this->assertEquals(201, self::$client->getResponse()->getStatusCode(), "Expected a 201 response status after posting to /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json with content, but got ".self::$client->getResponse()->getStatusCode()); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/put.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/put.php.twig new file mode 100644 index 0000000..39a8c9b --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/put.php.twig @@ -0,0 +1,13 @@ + public function testPut() + { + $json = json_encode(self::$sample_object); + + $crawler = self::$client->request('PUT', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json', array(), array(), ['HTTP_CONTENT_TYPE' => 'application/json', 'CONTENT_TYPE' => 'application/json'], $json); + $this->assertEquals(200, self::$client->getResponse()->getStatusCode(), 'Expected a 200 response status after requesting PUT /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json, got '.self::$client->getResponse()->getStatusCode()); + + $obj = json_decode(self::$client->getResponse()->getContent()); + //test response content + //$this->assertEquals($put_name, $obj->name, 'name did not change after PUT'); + //$this->assertEquals($put_description, $obj->description, 'description did not change after PUT'); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/setup.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/setup.php.twig new file mode 100644 index 0000000..373e5bf --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/no-authentication/setup.php.twig @@ -0,0 +1,86 @@ +/** @var Client */ +private static $client; + +private static $id; + +private static $sample_object; + +public function setUp() +{ +parent::setUp(); +self::$client = static::createClient(); +//$em = self::$client->getContainer()->get('doctrine'); + +//create assocation fields (relations from this entity) +{%- for key, assoc in assoc_mapping %} + {%- if (assoc['type'] == 4 or assoc['type'] == 8) and assoc['targetEntity'] is defined and assoc['targetEntity'] is not null %} {# type 4 == OneToMany, type 8 == ManyToMany #} + + $assoc_{{ key }} = array(); + $field_{{ assoc['fieldName']|capitalize }} = new \{{ assoc['targetEntity'] }}; + //set fields of $field_{{ assoc['fieldName']|capitalize }}. + $assoc_{{ key }}[] = $field_{{ assoc['fieldName']|capitalize }}; + + + {%- elseif (assoc['type'] == 1 or assoc['type'] == 2) and assoc['targetEntity'] is defined and assoc['targetEntity'] is not null %} {# type 1 == OneToOne, type 2 == ManyToOne #} + + $assoc_{{ key }} = new \{{ assoc['targetEntity'] }}; + + + {%- endif %} +{%- endfor %} + +//create main object +${{ entity_class|lower }}_object = array(); +{%- for field, info in fields if info['id'] is not defined or info['id'] == false -%} + {%- if info['type'] == 'string' %} + + ${{ entity_class|lower }}_object['{{ field }}'] = 'test_{{ field }}'; + + {%- elseif info['type'] == 'text' %} + + ${{ entity_class|lower }}_object['{{ field }}'] = 'test_{{ field }}_'.substr(str_shuffle(md5(time())),0,255); + + {%- elseif info['type'] == 'integer' or info['type'] == 'smallint' or info['type'] == 'bigint' %} + + ${{ entity_class|lower }}_object['{{ field }}'] = 1; + + {%- elseif info['type'] == 'decimal' or info['type'] == 'float' %} + + ${{ entity_class|lower }}_object['{{ field }}'] = 1.11; + + {%- elseif info['type'] == 'boolean' %} + + ${{ entity_class|lower }}_object['{{ field }}'] = true; + + {%- elseif info['type'] == 'date' or info['type'] == 'datetime' or info['type'] == 'datetimetz' or info['type'] == 'time' %} + + ${{ entity_class|lower }}_object['{{ field }}'] = new \DateTime('now'); + + {%- elseif info['type'] == 'dateinterval' %} + + ${{ entity_class|lower }}_object['{{ field }}'] = new \DateInterval('P2Y4DT6H8M'); + + {%- elseif info['type'] == 'array' or info['type'] == 'simple_array' or info['type'] == 'json_array' %} + + ${{ entity_class|lower }}_object['{{ field }}'] = array('{{ field }}_1','{{ field }}_2'); + + {%- else %} + + ${{ entity_class|lower }}_object['{{ field }}'] = 'unknown_field_value'; + + {%- endif -%} +{%- endfor %} + + +//add associations to mapping +{%- for key, assoc in assoc_mapping %} + {%- if (assoc['type'] == 4 or assoc['type'] == 2) and assoc['targetEntity'] is defined and assoc['targetEntity'] is not null %} {# type 2 == ManyToOne, type 4 == OneToMany #} + + ${{ entity_class|lower }}_object['{{ assoc['fieldName'] }}'] = $assoc_{{ key }}; + + {%- endif %} +{%- endfor %} + +self::$sample_object = ${{ entity_class|lower }}_object; +} + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/delete.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/delete.php.twig new file mode 100644 index 0000000..6142af9 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/delete.php.twig @@ -0,0 +1,12 @@ + public function testDelete() + { + $crawler = self::$client->request('DELETE', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json'); + $this->assertEquals(401, self::$client->getResponse()->getStatusCode(), 'Expected a 401 response status after requesting DELETE /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json without access token, but got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $crawler = self::$client->request('DELETE', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/999.json?access_token='.self::$access_token); + $this->assertEquals(404, self::$client->getResponse()->getStatusCode(), 'Expected a 404 response status after requesting DELETE /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/999.json, got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $crawler = self::$client->request('DELETE', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json?access_token='.self::$access_token); + $this->assertEquals(204, self::$client->getResponse()->getStatusCode(), 'Expected a 204 response status after requesting DELETE /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json, got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/get.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/get.php.twig new file mode 100644 index 0000000..ed5239f --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/get.php.twig @@ -0,0 +1,12 @@ + public function testGet() + { + $crawler = self::$client->request('GET', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json'); + $this->assertEquals(401, self::$client->getResponse()->getStatusCode(), 'Expected a 401 response status after requesting /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json without access token, but got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $crawler = self::$client->request('GET', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/999.json?access_token='.self::$access_token); + $this->assertEquals(404, self::$client->getResponse()->getStatusCode(), 'Expected a 404 response status after requesting /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/999.json, got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $crawler = self::$client->request('GET', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json?access_token='.self::$access_token); + $this->assertEquals(200, self::$client->getResponse()->getStatusCode(), 'Expected a 200 response status after requesting /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json, got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/getAfterDelete.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/getAfterDelete.php.twig new file mode 100644 index 0000000..81cce64 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/getAfterDelete.php.twig @@ -0,0 +1,6 @@ + public function testGetOnDeletedItem() + { + $crawler = self::$client->request('GET', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json?access_token='.self::$access_token); + $this->assertEquals(404, self::$client->getResponse()->getStatusCode(), 'Expected a 404 response status after requesting /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json, got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/getAll.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/getAll.php.twig new file mode 100644 index 0000000..91eb314 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/getAll.php.twig @@ -0,0 +1,16 @@ + public function testGetAllWithContent() + { + $crawler = self::$client->request('GET', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json'); + $this->assertEquals(401, self::$client->getResponse()->getStatusCode(), 'Expected a 401 response status after requesting /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json without access token, but got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $crawler = self::$client->request('GET', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json?access_token='.self::$access_token); + $this->assertEquals(200, self::$client->getResponse()->getStatusCode(), 'Expected a 200 response status after requesting /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json, got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $obj = json_decode(self::$client->getResponse()->getContent()); + $this->assertTrue(is_array($obj), 'expected decoded response content to be array, got '.gettype($obj)); + $this->assertTrue(count($obj) > 0, 'expected decoded response content to be an array with more then 0 items, got '.count($obj)); + $first = reset($obj); + $this->assertTrue(array_key_exists('id', $first), 'expected first array item to have a key id'); + self::$id = $first->id; + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/getAllWithoutContent.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/getAllWithoutContent.php.twig new file mode 100644 index 0000000..aaa5e31 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/getAllWithoutContent.php.twig @@ -0,0 +1,9 @@ + public function testGetAllWithoutContent() + { + $crawler = self::$client->request('GET', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json'); + $this->assertEquals(401, self::$client->getResponse()->getStatusCode(), 'Expected a 401 response status after requesting /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json without access token, but got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $crawler = self::$client->request('GET', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json?access_token='.self::$access_token); + $this->assertEquals(204, self::$client->getResponse()->getStatusCode(), 'Expected a 204 response status after requesting /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json, got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/patch.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/patch.php.twig new file mode 100644 index 0000000..4d58134 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/patch.php.twig @@ -0,0 +1,16 @@ + public function testPatch() + { + $crawler = self::$client->request('PATCH', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json'); + $this->assertEquals(401, self::$client->getResponse()->getStatusCode(), 'Expected a 401 response status after requesting PATCH /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json without access token, but got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $crawler = self::$client->request('PATCH', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/999.json?access_token='.self::$access_token); + $this->assertEquals(404, self::$client->getResponse()->getStatusCode(), 'Expected a 404 response status after requesting PATCH /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/999.json, got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $json = json_encode(self::$sample_object); + + $crawler = self::$client->request('PATCH', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json?access_token='.self::$access_token, array(), array(), ['HTTP_CONTENT_TYPE' => 'application/json', 'CONTENT_TYPE' => 'application/json'], $json); + $this->assertEquals(200, self::$client->getResponse()->getStatusCode(), 'Expected a 200 response status after requesting PATCH /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json, got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $obj = json_decode(self::$client->getResponse()->getContent()); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/post.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/post.php.twig new file mode 100644 index 0000000..8c7b81b --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/post.php.twig @@ -0,0 +1,14 @@ + public function testPost() + { + $crawler = self::$client->request('POST', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json'); + $this->assertEquals(401, self::$client->getResponse()->getStatusCode(), 'Expected a 401 response status after posting to api/post.json without access token, but got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $extra_help = 'If this test fails with a 500 status code, make sure the properties in the entity that should be required are annotated with @Symfony\Component\Validator\Constraints\NotBlank (or @Constraints\NotBlank and use Symfony\Component\Validator\Constraints;), remove this test if it doesn\'t apply to u.'; + $crawler = self::$client->request('POST', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json?access_token='.self::$access_token); + $this->assertEquals(400, self::$client->getResponse()->getStatusCode(), "Expected a 400 response status after posting to /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json without content, but got ".self::$client->getResponse()->getStatusCode().".\r\n".$extra_help."\r\n\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $json = json_encode(self::$sample_object); + $crawler = self::$client->request('POST', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json?access_token='.self::$access_token, array(), array(), ['HTTP_CONTENT_TYPE' => 'application/json', 'CONTENT_TYPE' => 'application/json'], $json); + $this->assertEquals(201, self::$client->getResponse()->getStatusCode(), "Expected a 201 response status after posting to /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}.json with content, but got ".self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/put.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/put.php.twig new file mode 100644 index 0000000..6534038 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/put.php.twig @@ -0,0 +1,16 @@ + public function testPut() + { + $crawler = self::$client->request('PUT', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json'); + $this->assertEquals(401, self::$client->getResponse()->getStatusCode(), 'Expected a 401 response status after requesting PUT /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json without access token, but got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $crawler = self::$client->request('PUT', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/999.json?access_token='.self::$access_token); + $this->assertEquals(400, self::$client->getResponse()->getStatusCode(), 'Expected a 400 response status after requesting PUT /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/999.json, got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $json = json_encode(self::$sample_object); + + $crawler = self::$client->request('PUT', '/{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json?access_token='.self::$access_token, array(), array(), ['HTTP_CONTENT_TYPE' => 'application/json', 'CONTENT_TYPE' => 'application/json'], $json); + $this->assertEquals(200, self::$client->getResponse()->getStatusCode(), 'Expected a 200 response status after requesting PUT /{{ route_prefix }}/{{ entity|lower|replace({"\\":'/'}) }}/'.self::$id.'.json, got '.self::$client->getResponse()->getStatusCode()."\r\nResponse:\r\n".self::$client->getResponse()->getContent()); + + $obj = json_decode(self::$client->getResponse()->getContent()); + } + diff --git a/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/setup.php.twig b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/setup.php.twig new file mode 100644 index 0000000..176a5b2 --- /dev/null +++ b/src/Voryx/RESTGeneratorBundle/Resources/skeleton/rest/tests/oauth2/setup.php.twig @@ -0,0 +1,206 @@ + /** @var Client */ + private static $client; + + private static $id; + + private static $sample_object; + + public function setUp() + { + parent::setUp(); + self::$client = static::createClient(); + //$em = self::$client->getContainer()->get('doctrine'); + + //create assocation fields (relations from this entity) + {%- for key, assoc in assoc_mapping %} + {%- if (assoc['type'] == 4 or assoc['type'] == 8) and assoc['targetEntity'] is defined and assoc['targetEntity'] is not null %} {# type 4 == OneToMany, type 8 == ManyToMany #} + + $assoc_{{ key }} = array(); + $field_{{ assoc['fieldName']|capitalize }} = new \{{ assoc['targetEntity'] }}; + //set fields of $field_{{ assoc['fieldName']|capitalize }}. + $assoc_{{ key }}[] = $field_{{ assoc['fieldName']|capitalize }}; + + + {%- elseif (assoc['type'] == 1 or assoc['type'] == 2) and assoc['targetEntity'] is defined and assoc['targetEntity'] is not null %} {# type 1 == OneToOne, type 2 == ManyToOne #} + + $assoc_{{ key }} = new \{{ assoc['targetEntity'] }}; + + + {%- endif %} + {%- endfor %} + + //create main object + ${{ entity_class|lower }}_object = array(); + {%- for field, info in fields if info['id'] is not defined or info['id'] == false -%} + {%- if entity_constraints[field] is defined and entity_constraints[field]|length > 0 %} + {# Check basic constraints #} + {%- for constraint in entity_constraints[field] %} + {%- if 'NotBlankValidator' in constraint.validatedBy() %} + {# do nothing, value will be set on type checking #} + {%- elseif 'BlankValidator' in constraint.validatedBy() %} + {%- set test_data = null %} + {%- elseif 'NotNullValidator' in constraint.validatedBy() %} + {# do nothing #} + {%- elseif 'IsNullValidator' in constraint.validatedBy() %} + {%- set test_data = null %} + {%- endif %} + {%- endfor %} + {% endif %} + + {%- if info['type'] == 'string' %} + {%- set test_data = 'test_' ~ field %} + {%- if entity_constraints[field] is defined and entity_constraints[field]|length > 0 %} + {%- for constraint in entity_constraints[field] %} + {%- if 'NotBlankValidator' in constraint.validatedBy() %} + {%- set test_data = 'test_required_field_' ~ field %} + {%- elseif 'EmailValidator' in constraint.validatedBy() %} + {%- set test_data = field ~ '@domain.com' %} + {%- elseif 'UrlValidator' in constraint.validatedBy() %} + {%- set test_data = 'http://www.' ~ field ~ '.com' %} + {%- elseif 'IpValidator' in constraint.validatedBy() %} + {%- set test_data = '192.168.1.1' %} + {%- elseif 'RegexValidator' in constraint.validatedBy() %} + {%- set test_data = 'RegexValidator_test_data' %} + {%- elseif 'IsTrueValidator' in constraint.validatedBy() %} + {%- set test_data = '1' %} + {%- elseif 'IsFalseValidator' in constraint.validatedBy() %} + {%- set test_data = '0' %} + {%- elseif 'BicValidator' in constraint.validatedBy() %} + {%- set test_data = 'RABONL2U' %} + {%- elseif 'IbanValidator' in constraint.validatedBy() %} + {%- set test_data = 'NL10RABO1234556789' %} + {%- elseif 'CountryValidator' in constraint.validatedBy() %} + {%- set test_data = 'NL' %} + {%- elseif 'LocaleValidator' in constraint.validatedBy() %} + {%- set test_data = 'nl_NL' %} + {%- elseif 'LanguageValidator' in constraint.validatedBy() %} + {%- set test_data = 'nl' %} + {%- elseif 'CurrencyValidator' in constraint.validatedBy() %} + {%- set test_data = 'EUR' %} + {%- elseif 'LengthValidator' in constraint.validatedBy() %} + {% set random_number = random(constraint.max - constraint.min) + constraint.min %} + {% set test_data = '' %} + {% for i in 1..random_number %} + {% set test_data = test_data ~ random('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890') %} + {% endfor %} + {%- elseif 'UuidValidator' in constraint.validatedBy() %} + {#- might want to check uuid versions and strict mode? #} + {%- set test_data = '123e4567-e89b-12d3-a456-426655440000' %} + {%- endif %} + {%- endfor %} + {%- endif %} + + ${{ entity_class|lower }}_object['{{ field }}'] = '{{ test_data }}'; + + {%- elseif info['type'] == 'text' %} + + ${{ entity_class|lower }}_object['{{ field }}'] = 'test_{{ field }}_'.substr(str_shuffle(md5(time())),0,255); + + {%- elseif info['type'] == 'integer' or info['type'] == 'smallint' or info['type'] == 'bigint' %} + {%- set test_data = 1 %} + {%- if entity_constraints[field] is defined and entity_constraints[field]|length > 0 %} + {%- for constraint in entity_constraints[field] %} + {%- if 'RangeValidator' in constraint.validatedBy() %} + {%- set test_data = random(constraint.max - constraint.min) + constraint.min %} + {%- elseif 'IsTrueValidator' in constraint.validatedBy() %} + {%- set test_data = 1 %} + {%- elseif 'IsFalseValidator' in constraint.validatedBy() %} + {%- set test_data = 0 %} + {%- endif %} + {%- endfor %} + {% endif %} + + ${{ entity_class|lower }}_object['{{ field }}'] = {{ test_data }}; + + {%- elseif info['type'] == 'decimal' or info['type'] == 'float' %} + {%- set test_data = 1.11 %} + {%- if entity_constraints[field] is defined and entity_constraints[field]|length > 0 %} + {%- for constraint in entity_constraints[field] %} + {%- if 'RangeValidator' in constraint.validatedBy() %} + {%- set test_data = random(constraint.max - constraint.min) + constraint.min %} + {%- elseif 'IsTrueValidator' in constraint.validatedBy() %} + {%- set test_data = 1 %} + {%- elseif 'IsFalseValidator' in constraint.validatedBy() %} + {%- set test_data = 0 %} + {%- endif %} + {%- endfor %} + {% endif %} + + ${{ entity_class|lower }}_object['{{ field }}'] = {{ test_data }}; + + {%- elseif info['type'] == 'boolean' %} + {%- set test_data = true %} + {%- if entity_constraints[field] is defined and entity_constraints[field]|length > 0 %} + {%- for constraint in entity_constraints[field] %} + {%- if 'RangeValidator' in constraint.validatedBy() %} + {%- set test_data = random(constraint.max - constraint.min) + constraint.min %} + {%- elseif 'IsFalseValidator' in constraint.validatedBy() %} + {%- set test_data = false %} + {%- endif %} + {%- endfor %} + {% endif %} + + ${{ entity_class|lower }}_object['{{ field }}'] = {{ test_data }}; + + {%- elseif info['type'] == 'date' or info['type'] == 'datetime' or info['type'] == 'datetimetz' or info['type'] == 'time' %} + {%- set test_data = 'now' %} + {%- if entity_constraints[field] is defined and entity_constraints[field]|length > 0 %} + {%- for constraint in entity_constraints[field] %} + {%- if 'RangeValidator' in constraint.validatedBy() %} + {%- set test_data = constraint.min %} + {%- endif %} + {%- endfor %} + {% endif %} + + ${{ entity_class|lower }}_object['{{ field }}'] = new \DateTime('{{ test_data }}'); + + {%- elseif info['type'] == 'dateinterval' %} + + ${{ entity_class|lower }}_object['{{ field }}'] = new \DateInterval('P2Y4DT6H8M'); + + {%- elseif info['type'] == 'array' or info['type'] == 'simple_array' or info['type'] == 'json_array' %} + {%- set plain_test_data = 'array(' %} + {%- set test_data = [ field ~ '_1' ] %} + {%- if entity_constraints[field] is defined and entity_constraints[field]|length > 0 %} + {%- for constraint in entity_constraints[field] %} + {# the following 2 validators are incompatible at the moment #} + {%- if 'ChoiceValidator' in constraint.validatedBy() %} + {% if constraint.choices is defined and constraint.choices|length > 0 %} + {%- set test_data = constraint.choices %} + {% else %} + {# check the callback here #} + {% endif %} + {%- elseif 'CountValidator' in constraint.validatedBy() %} + {% for i in 1..constraint.min%} + {{ test_data|merge([ field ~ '_' ~ i+1 ]) }} + {% endfor %} + {%- endif %} + {%- endfor %} + {%- endif %} + {%- for array_item in test_data %} + {%- set plain_test_data = plain_test_data ~ "'" ~ array_item ~ "'," %} + {%- endfor %} + {%- set plain_test_data = plain_test_data|trim(',') ~ ')'%} + + ${{ entity_class|lower }}_object['{{ field }}'] = {{ plain_test_data }}; + + {%- else %} + + ${{ entity_class|lower }}_object['{{ field }}'] = 'unknown_field_value'; + + {%- endif -%} + {%- endfor %} + + + //add associations to mapping + {%- for key, assoc in assoc_mapping %} + {%- if (assoc['type'] == 4 or assoc['type'] == 2) and assoc['targetEntity'] is defined and assoc['targetEntity'] is not null %} {# type 2 == ManyToOne, type 4 == OneToMany #} + + ${{ entity_class|lower }}_object['{{ assoc['fieldName'] }}'] = $assoc_{{ key }}; + + {%- endif %} + {%- endfor %} + + self::$sample_object = ${{ entity_class|lower }}_object; + } +