bentools / api-first-bundle
Provides classes for API-first designed projects with Symfony.
Installs: 97
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 3
Forks: 0
Open Issues: 0
Type:symfony-bundle
Requires
- php: >=7
- bentools/helpfultraits: ^1.0
- dunglas/action-bundle: ^0.3.0
- friendsofsymfony/rest-bundle: ^2.0
- jms/serializer-bundle: ^1.1
- m6web/api-exception-bundle: ^1.1
- sylius/resource: 0.19.*
Requires (Dev)
- doctrine/doctrine-fixtures-bundle: ^2.3
- guzzlehttp/guzzle: ^6.2
- liip/functional-test-bundle: ^1.6
- sensio/distribution-bundle: ~5.0
- sensio/framework-extra-bundle: ^3.0.2
- sensio/generator-bundle: ~3.0
- symfony/phpunit-bridge: ~2.7
- symfony/symfony: ~2.8|~3.0
README
Provides classes for API-first designed projects with Symfony.
For personnal purposes for the moment.
The goal of this bundle is to help in designing Symfony applications that can be consumed with an API and with an UI as well.
Important
This repository is no longer maintained and may be removed in a near future. You may consider creating a fork if you still require it.
Concept
A Symfony entity is a Resource that has an id. It should implement ResourceInterface
which just requires implementing a getId()
method.
Several classes interacts with this resource:
- ORM / ODM classes (EntityManager, Repository)
- Form classes
- Action classes (GET, POST, PUT, PATCH, DELETE, related resources, etc)
A AbstractResourceHandler
is a service that gives access to the corresponding classes of a specific Resource.
Here's the flow:
- The
AbstractResourceHandler
provides form handling. It is HTTP agnostic: you can submit a form from aRequest
or from raw data (array). You can use it in cron jobs, bulk actions, its role is not to send aResponse
but aResource
. When the form fails it throws aValidationFormException
. - The Action classes calls the
AbstractResourceHandler
to transform aResource
with aRequest
. - The Action classes can generate a pre-response, in which they can define:
- What to do on success (redirect to an URL, add flashes for instance, in case of an UI request)
- Which HTTP status code to reply (in case of an API submission)
- An event-listener will transform this
PreResponse
to the correct response with content-negociation (redirect + flash if the request came from an UI, status code in case of an API request) - When a
ValidationFormException
is thrown from theAbstractResourceHandler
, the Action class should:- Return a HTTP 200 response code with the form and the errors in case of an UI request
- Return a HTTP 400 response code with the serialized form errors in case of an API request
- The
BenTools\ApiFirstBundle\Model\AbstractCRUDAction::submitForm()
method will return the resolved callable$success
on success; the Form object otherwise.
CSRF Protection
- FOSRESTBundle can disable CSRF protection on a specific role.
- This is not the best solution since the same User can use both the API and the UI. This means if they have the ROLE_API and logs in on the UI, they won't be CSRF-protected.
APIFirstBundle provides another solution:
- When you create your Form Types, don't extend
Symfony\Component\Form\AbstractType
butBenTools\ApiFirstBundle\Form\ApiFirstAbstractType
instead - Setup your form with automatic CSRF protection enabling:
/** * @param OptionsResolver $resolver */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => MyResource::class, 'csrf_protection' => $this->shouldEnableCSRFProtection(), ]); }
- Declare your form type as a service since its constructor has a dependency on
api_first.api_consumer_detector
Form Handling
When you extend the BenTools\ApiFirstBundle\Model\AbstractResourceHandler
class, you can call the getCreationForm
, getEditionForm
and the getDeletionForm
methods.
If you're using an UI, it will create a named form. On the contrary, if you're posting data on the API, the keys won't be prefixed in the form.
For instance, if you're creating a new Contact resource with the UI, the app will expect the following form params:
[
'contact' => [
'firstname' => 'John',
'lastname' => 'Doe',
],
'_token' => 'zef6rq1g6er8g1re6g81e6fertjh4yu6j4'
];
If you're using the API, the app will expect this:
[
'firstname' => 'John',
'lastname' => 'Doe',
];
Of course we could use non-named forms only. But this leads to issues with Symfony's _token and _method hidden fields that are misunderstood as forms extra fields.