diff --git a/.gitignore b/.gitignore index 035a7a7..9f4eb18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ composer.lock vendor .php_cs.cache -tests/TestApplication/var \ No newline at end of file +tests/TestApplication/var +.couscous diff --git a/README.md b/README.md index b573354..bf0bdef 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,18 @@ Melodiia Finally some competitor to ApiPlatform. +Melodiia has been designed to do exactly what you want it does. No magic here. It's just a set of tools that +work nicely with Symfony. + Features -------- - Adds a documentation endpoint with help of swagger - Set of api responses -- Uses Symfony Form as input, because Symfony Serializer is not ready for that +- Uses Symfony Form as input +- CRUD controllers +- Error management +- Output format as [json-api](https://jsonapi.org/), a format that has 1.x version Install ------- @@ -19,32 +25,18 @@ Install composer require swag-industries/melodiia ``` -### Symfony setup - -Add the bundle to your bundle list: - -```php -SwagIndustries\Melodiia\Bridge\Symfony\MelodiiaBundle::class => ['all' => true] -``` - -Setup the configuration: - -```yaml -# config/packages/melodiia.yaml - -melodiia: - apis: - main: - base_path: / -``` +The recipe will automatically create the configuration file `melodiia.yaml`. If you decided to not execute this recipe, +please refer to the +[recipe repository of Symfony](https://github.com/symfony/recipes-contrib/tree/master/swagindustries/melodiia). -### Documentation +### Learn more - - [Configure my first crud operations !](./docs/Crud.md) - - [Integration with Symfony ](./docs/Symfony.md) - - [Presentation of Melodiia @Biig-io](https://docs.google.com/presentation/d/1dtxUOzZFGRq7Ar5YV5aZ6AN60RhDbf_0OcXKj5iiDS8/edit?usp=sharing) +- [Getting Started](./docs/getting-started.md) +- [Configure my first crud operations !](./docs/Crud.md) +- [Integration with Symfony ](./docs/Symfony.md) +- [Presentation of Melodiia @Biig-io](https://docs.google.com/presentation/d/1dtxUOzZFGRq7Ar5YV5aZ6AN60RhDbf_0OcXKj5iiDS8/edit?usp=sharing) -Feel free to open an issue, if you encounter problems implementing Melodiia. +Feel free to open an issue, if you encounter problems while implementing Melodiia. FAQ --- diff --git a/couscous.yml b/couscous.yml new file mode 100644 index 0000000..3eac37a --- /dev/null +++ b/couscous.yml @@ -0,0 +1,34 @@ +template: + url: https://github.com/Nek-/Template-Light + +github: + user: SwagIndustries + repo: Melodiia + +exclude: + - vendor + - %gitignore% +cname: melodiia.swag.industries + +branch: gh-pages + + +title: Melodiia Documentation +subTitle: Take control of your API +menu: + items: + home: + text: Home page + relativeUrl: index.html + getting_started: + text: Getting Started + relativeUrl: docs/getting-started.html + filters: + text: Using filters + relativeUrl: docs/filters.html + config: + text: Configuration Reference + relativeUrl: docs/config-reference.html + cruds: + text: CRUD Reference + relativeUrl: docs/crud-controllers.html diff --git a/docs/Crud.md b/docs/Crud.md deleted file mode 100644 index c974ac4..0000000 --- a/docs/Crud.md +++ /dev/null @@ -1,138 +0,0 @@ -CRUDs with Melodiia -=================== - -Requirements ------------- - -The CRUD of Melodiia is based on the following components of Symfony. This means you need to use install & enable them: -- **The Serializer Component** - - Melodiia uses the serializer of Symfony to render responses. -- **The Form Component** - - Melodiia uses the form component to manage input. - - -Register your first CRUD ------------------------- - -Melodiia CRUDs are **simple** controllers that you can configure in the routing. - -For any further usage, consider using your own controller. - -Here is an example of configuration: - -#### Simple operation - -```yaml -acme_article_create: - path: /api/v1/articles - controller: 'melodiia.crud.controller.create' - methods: ['POST'] - defaults: - melodiia_model: App\Entity\Article - melodiia_form: App\Form\ArticleType - -acme_article_update: - path: /api/v1/articles/{id} - controller: 'melodiia.crud.controller.update' - methods: ['PATCH'] - defaults: - melodiia_model: App\Entity\Article - melodiia_form: App\Form\ArticleType - -acme_article_get: - path: /api/v1/articles/{id} - controller: 'melodiia.crud.controller.get' - methods: ['GET'] - defaults: - melodiia_model: App\Entity\Article - melodiia_security_check: 'some content runnable in AuthorizationChecker class' -``` - -#### Collection - -The collection configuration is more advanced due to the use case, please consider following examples. - -The key "melodiia_allow_user_define_max_per_page" default value is "false" - -```yaml -# The user can specify in query parameter under "max_per_page" key a maximum of 50 items per page -acme_article_get_collection: - path: /api/v1/articles - controller: 'melodiia.crud.controller.get_all' - methods: ['GET'] - defaults: - melodiia_model: App\Entity\Article - melodiia_serialization_group: "list-article" - melodiia_security_check: 'some content runnable in AuthorizationChecker class' - melodiia_allow_user_define_max_per_page: true - melodiia_max_per_page_allowed: 50 - - -# Static use case the user can't ask for a specific count of items per page. The max count of items returned will be 25 -acme_article_get_collection2: - path: /api/v1/articles - controller: 'melodiia.crud.controller.get_all' - methods: ['GET'] - defaults: - melodiia_model: App\Entity\Article - melodiia_serialization_group: "list-article" - melodiia_max_per_page: 25 -``` - -Learn more about available options in `CrudControllerInterface`. - -Filters -------- - -Filters are enabled for `GetAll` controllers. You can use them by yourself -if you consider using the `FilterCollectionFactory`. - -All you have to do is to implement the interface `FilterInterface`. - -Currently Doctrine query is the only type of query managed by the filters. - -Here is an example of filter you may want to do: - -```php -types = $types; - } - - public function filter($query, Symfony\Component\Form\FormInterface $form): void - { - $type = $form->get('type')->getData(); - if ($type !== null) { - $query - ->andWhere($query->expr()->eq('item.type', ':filterType')) - ->setParameter('filterType', $type) - ; - } - } - - public function supports(string $class): bool - { - return $class === MyEntity::class; - } - - public function buildForm(Symfony\Component\Form\FormBuilderInterface $formBuilder): void - { - $formBuilder->add('type', ChoiceType::class, [ - 'choices' => $this->types - ]); - } -} - -``` - -> Note: the given query builder name the entity `item` no matter what kind of entity it is. diff --git a/docs/Symfony.md b/docs/Symfony.md deleted file mode 100644 index 5a52993..0000000 --- a/docs/Symfony.md +++ /dev/null @@ -1,62 +0,0 @@ -Symfony integration -=================== - -I. Dependencies ---------------- - -Melodiia does not provide Symfony dependencies. If you are **not** using the -Symfony full edition (which is recommended) here are the components you're -going to need to install. - -```bash -composer req symfony/form symfony/asset -``` - - -II. Configure -------------- - -Add it as bundle. Modify `bundles.php` file and add the following: - -```php -// config/bundles.php -return [ - // Some other bundles already registered here - // ... - - SwagIndustries\Melodiia\Bridge\Symfony\MelodiiaBundle::class => ['all' => true], -]; -``` - -By default it comes with a configuration that will consider `/` is your API. -It's recommended to change it. - -```yaml -melodiia: - apis: - main: - # All those options are used for documentation purpose. - paths: ['%kernel.project_dir%/src'] # List of path where melodiia will look for documentation blocks - base_path: / # Path to your API - pagination: - max_per_page_attribute: 'max_per_page' - # Melodiia comes with some form extensions, it makes your life easier - # but in some case this could break some parts of an existing application - # or just not act like you expect. That's why you can't disable extensions. - form_extensions: - # enabled by default, set false to disable. - datetime: true -``` - -III. Override Melodiia services -------------- - -The below configuration override the default pagination request factory. -So Melodiia will execute `Your\FQCN\YourClass` instead of `SwagIndustries\Melodiia\Crud\Pagination\PaginationRequestFactory` to create PaginationRequest object - -```yaml -#services.yaml - -SwagIndustries\Melodiia\Crud\Pagination\PaginationRequestFactoryInterface: - alias: Namespace\To\Your\Class -``` diff --git a/docs/config-reference.md b/docs/config-reference.md new file mode 100644 index 0000000..625d895 --- /dev/null +++ b/docs/config-reference.md @@ -0,0 +1,17 @@ +Configuration Reference of Melodiia +=================================== + +```yaml +melodiia: + apis: + # Define as much APIs you want here. + main: + base_path: /api/v1 + pagination: + # Using this query attribute you can change the max per page + max_per_page_attribute: max_per_page + # Melodiia comes with some form types to help you build your application. You can enable them here. + # This is the default configuration + form_extensions: + datetime: true +``` diff --git a/docs/crud-controllers.md b/docs/crud-controllers.md new file mode 100644 index 0000000..518c606 --- /dev/null +++ b/docs/crud-controllers.md @@ -0,0 +1,38 @@ +About CRUD Controllers +====================== + +Here are all the controllers for your CRUD. + +| CRUD action | Service name | +|-------------------|----------------------------------| +| Create | melodiia.crud.controller.create | +| Read (collection) | melodiia.crud.controller.get_all | +| Read (item) | melodiia.crud.controller.get | +| Update | melodiia.crud.controller.update | +| Delete | melodiia.crud.controller.delete | + +CRUD Controllers have options, here are a list of them and their availability by controller: + +| Option name | Create | Read | Update | Delete | +|-----------------------------------------|--------|------|--------|--------| +| melodiia_model | x | x | x | x | +| melodiia_form | x | | x | | +| melodiia_clear_missing | x | | x | | +| melodiia_serialization_group | | x | | | +| melodiia_security_check | x | x | x | x | +| melodiia_max_per_page | | x | | | +| melodiia_max_per_page_allowed | | x | | | +| melodiia_allow_user_define_max_per_page | | x | | | + +Example of route using options: + +```yaml +acme_article_get_collection: + path: /api/v1/articles + controller: 'melodiia.crud.controller.get_all' + methods: 'GET' + defaults: + melodiia_model: App\Entity\Article + melodiia_serialization_group: "list-article" + melodiia_max_per_page: 25 +``` diff --git a/docs/filters.md b/docs/filters.md new file mode 100644 index 0000000..16ae7fa --- /dev/null +++ b/docs/filters.md @@ -0,0 +1,43 @@ +Using filters +============= + +To make filters you must implement the interface `FilterInterface`. Because Melodiia validates **any** input to avoid +inconsistent API, filters also uses form types. + +If it's not done automatically, you must register your filter as a service: + +```yaml +services: + App\Filters\Todos\TodoContainsFilter: + # This tag is autoconfigured in standard installations + tags: ['melodiia.crud_filter'] +``` + +Here is how may look this filter: + +```php +class TodoContainFilter implements FilterInterface +{ + /** + * @param QueryBuilder $queryBuilder + */ + public function filter($queryBuilder, FormInterface $form): void + { + $q = $form->get('q')->getData(); + if (!empty($q)) { + $queryBuilder->andWhere($queryBuilder->expr()->like('item.content', ':like')); + $queryBuilder->setParameter('like', '%' . $q . '%'); + } + } + + public function supports(string $class): bool + { + return Todo::class === $class; + } + + public function buildForm(FormBuilderInterface $formBuilder): void + { + $formBuilder->add('q', TextType::class); + } +} +``` diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..6a73a17 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,133 @@ +Getting Started +=============== + +_Notice: if you want to see a complete example, you can see the folder test/TestApplication that contains an app of test we +use for functional testing_ + +Installation +------------ + +Run the following command and you're all set: + +```bash +composer require swag-industries/melodiia +``` + +Step 1: your model +------------------ + +To work with Melodiia, your model must implement the interface `MelodiiaModel`. + +```php +/** + * @ORM\Entity() + */ +class Todo implements MelodiiaModel +{ + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + private $id; + + /** + * @ORM\Column(type="text") + */ + private $content; +} +``` + +Step 2: CRUD +------------ + +Because a lot of what you do is actually CRUD, Melodiia provides controllers to make a CRUD. You just have to use +your routing to make it work. + +```yaml +# routing.yaml +get_todos: + path: /todos + controller: 'melodiia.crud.controller.get_all' + methods: GET + defaults: + melodiia_model: App\Entity\Todo +``` + +This will generate a paginated result. You may also want to add [filters](filters.md) to the output. + +Learn more about CRUD controllers on [related documentation page](crud-controllers.md). + +Step 3: Form +------------ + +Melodiia uses form types for input data. That's how you manage your input validation and errors in the context of Melodiia. + +```php +use SwagIndustries\Melodiia\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\NotBlank; +use App\Entity\Todo; + +class TodoType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->add('content', TextType::class, ['constraints' => new NotBlank()]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefault('data_class', Todo::class); + } +} +``` + +You can now use the `Create` controller from the CRUD controllers. + +By default Symfony Forms are not well designed for API. That's why we created a new `AbstractType` that you should +extends. Melodiia also provide a set of type for APIs needs: https://github.com/swagindustries/Melodiia/tree/master/src/Form/Type + +```yaml +# routing.yaml +post_todos: + path: /todos + controller: 'melodiia.crud.controller.create' + methods: POST + defaults: + melodiia_model: App\Entity\Todo + melodiia_form: App\Form\TodoType +``` + +Step 4: write documentation +--------------------------- + +_Disclaimer: Melodiia was originally generating the API documentation. I though this is an expected behavior from an API library. +But I quickly figured out that even if it was generating valid documentation (unless some other frameworks), using docblocks +to configure the OpenAPI documentation in a good way was a serious pain and was just making things complicated._ + +**Melodiia doesn't generates automatically documentation.** + +But it comes with some help to build your documentation: +1. Run `bin/console assets:insall` +2. Create a file `documentation.yml` (the `config` folder seems like a good location) +2. Add the following routing to your configuration + +```yaml +# The documentation should be available only in dev environment +# routing_dev.yaml +documentation: + path: /documentation + controller: 'melodiia.documentation' + defaults: + documentation_file_path: '%kernel.project_dir%/config/documentation.yaml' +``` + +Step 5: do what you want +------------------------ + +Just like the readme says. Melodiia is a set of tools. Feel free to make your own controllers, but if you want that +Melodiia process your content (to ensure your API always answer with the same format), you must return a response +that implements `SwagIndustries\Melodiia\Response\ApiResponse`.