diff --git a/api-bundle/angular-integration.md b/api-bundle/angular-integration.md
new file mode 100644
index 00000000000..8e9fdedb95f
--- /dev/null
+++ b/api-bundle/angular-integration.md
@@ -0,0 +1,61 @@
+# AngularJS integration
+
+DunglasApiBundle works fine with [AngularJS](http://angularjs.org). The popular [Restangular](https://github.com/mgonto/restangular)
+REST client library for Angular can easily be configured to handle the API format.
+
+Here is a working Restangular config:
+
+```javascript
+'use strict';
+
+var app = angular
+ .module('myAngularjsApp')
+ .config(['RestangularProvider', function (RestangularProvider) {
+ // The URL of the API endpoint
+ RestangularProvider.setBaseUrl('http://localhost:8000');
+
+ // JSON-LD @id support
+ RestangularProvider.setRestangularFields({
+ id: '@id'
+ });
+ RestangularProvider.setSelfLinkAbsoluteUrl(false);
+
+ // Hydra collections support
+ RestangularProvider.addResponseInterceptor(function (data, operation) {
+ // Remove trailing slash to make Restangular working
+ function populateHref(data) {
+ if (data['@id']) {
+ data.href = data['@id'].substring(1);
+ }
+ }
+
+ // Populate href property for the collection
+ populateHref(data);
+
+ if ('getList' === operation) {
+ var collectionResponse = data['hydra:member'];
+ collectionResponse.metadata = {};
+
+ // Put metadata in a property of the collection
+ angular.forEach(data, function (value, key) {
+ if ('hydra:member' !== key) {
+ collectionResponse.metadata[key] = value;
+ }
+ });
+
+ // Populate href property for all elements of the collection
+ angular.forEach(collectionResponse, function (value) {
+ populateHref(value);
+ });
+
+ return collectionResponse;
+ }
+
+ return data;
+ });
+ }])
+;
+
+```
+
+Previous chapter: [Performances](performances.md)
diff --git a/api-bundle/controllers.md b/api-bundle/controllers.md
new file mode 100644
index 00000000000..f2d76ccab3c
--- /dev/null
+++ b/api-bundle/controllers.md
@@ -0,0 +1,45 @@
+# Controllers
+
+The bundle provide a default controller class implementing CRUD operations: `Dunglas\ApiBundle\Controller\ResourceController`.
+Basically this controller class extends the default controller class of the FrameworkBundle of Symfony providing implementations
+of CRUD actions. It also provides convenient methods to retrieve the `Resource` class associated with the current request
+and to serialize entities using normalizers provided by the bundle.
+
+## Using a custom controller
+
+When [the event system](the-event-system.md) is not enough, it's possible to use custom controllers.
+
+Your custom controller should extend the `ResourceController` provided by this bundle.
+
+Example of custom controller:
+
+```php
+get('logger')->info('This is my custom controller.');
+
+ return parent::getAction($request, $id);
+ }
+}
+```
+
+Custom controllers are often used with [custom operations](operations.md). If you don't create a custom operation
+for your custom controller, you need to register yourself that controller in the Symfony routing system and it will
+appear in documentations.
+
+Note that you shouldn't use `@Route` annotations, as this will cause bugs. The bundle auto-registers routes within Symfony2, so you don't need to use `@Route` annotations.
+
+Previous chapter: [Controllers](controllers.md)
+Next chapter: [Using external (JSON-LD) vocabularies](external-vocabularies.md)
diff --git a/api-bundle/data-providers.md b/api-bundle/data-providers.md
new file mode 100644
index 00000000000..45532b78cf0
--- /dev/null
+++ b/api-bundle/data-providers.md
@@ -0,0 +1,140 @@
+# Data providers
+
+To retrieve data that will be exposed by the API, DunglasApiBundle use classes called data providers. A data provider
+using [Doctrine ORM](http://www.doctrine-project.org/projects/orm.html) to retrieve data from a database is included with the bundle and enabled by default. This data provider
+natively supports paged collection and filters. It can be used as is and fits perfectly with common usages.
+
+But sometime, you want to retrieve data from other sources such as a webservice, ElasticSearch, MongoDB or another ORM.
+Custom data providers can be used to do so. A project can include as much data providers as it needs. The first able to
+retrieve data for a given resource will be used.
+
+## Creating a custom data provider
+
+Data providers must return collection of items and specific items for a given resource when requested. In the following
+example, we will create a custom provider returning data from a static list of object. Fell free to adapt it to match your
+own needs.
+
+Let's start with the data provider itself:
+
+```php
+data = [
+ 'a1' => new MyEntity('a1', 1871),
+ 'a2' => new MyEntity('a2', 1936),
+ ];
+ }
+
+ public function getItem(ResourceInterface $resource, $id, $fetchData = false)
+ {
+ return isset($this->data[$id]) ? $this->data[$id] : null;
+ }
+
+ public function getCollection(ResourceInterface $resource, Request $request)
+ {
+ return $this->data;
+ }
+
+ public function supports(ResourceInterface $resource)
+ {
+ return 'AppBundle\Entity\MyEntity' === $resource->getEntityClass();
+ }
+}
+```
+
+Then register that provider with a priority higher than the Doctrine ORM data provider:
+
+```yaml
+
+# app/config/services.yml
+
+services:
+ my_custom_data_provider:
+ class: AppBundle\DataProvider\StaticDataProvider
+ tags: [ { name: "api.data_provider", priority: 1 } ]
+```
+
+This data provider is now up and running. It will take precedence over the default Doctrine ORM data provider for each resources
+it supports (here, resources managing `AppBundle\Entity\MyEntity` entities).
+
+## Returning a paged collection
+
+The previous custom data provider return only full, non paged, collection. However for large collections, returning all
+the data set in one response is often not possible.
+The `getCollection()` method of data providers supporting paged collections must returns an instance of `Dunglas\ApiBundle\Model\PaginatorInterface`
+instead of a standard array.
+
+To create your own paginators, take a look at the Doctrine ORM paginator bridge: [`Dunglas\ApiBundle\Doctrine\Orm\Paginator`](/Doctrine/Orm/Paginator.php).
+
+## Supporting filters
+
+To be able [to filter collections](filters.md), the Data Provider must be aware of registered filters to the given resource.
+The best way to learn how to create filter aware data provider is too look at the default Doctrine ORM dataprovider: [`Dunglas\ApiBundle\Doctrine\Orm\DataProvider`](/Doctrine/Orm/DataProvider.php).
+
+## Extending the Doctrine Data Provider
+
+The bundle is provided with a data provider leveraging the Doctrine ORM. This default data provider can be extended.
+
+For performance reasons, [custom output walkers for the Doctrine ORM Paginator](http://www.doctrine-project.org/jira/browse/DDC-3282)
+are disabled. It drastically improves performance when dealing with large collections. However it prevents advanced [filters](filters.md)
+adding `HAVING` and `GROUP BY` clauses to DQL queries to work properly.
+
+To enable custom output walkers, start by creating a custom data provider supporting the `AppBundle\Entity\MyEntity` class:
+
+```php
+setUseOutputWalkers(true);
+
+ return new Paginator($doctrineOrmPaginator);
+ }
+
+ public function supports(ResourceInterface $resource)
+ {
+ return 'AppBundle\Entity\MyEntity' === $resource->getEntityClass();
+ }
+}
+```
+
+Then register the data provider:
+
+```yaml
+
+# app/config/services.yml
+
+services:
+ my_entity_data_provider:
+ parent: "api.doctrine.orm.data_provider"
+ class: AppBundle\DataProvider\MyEntityDataProvider
+ tags: [ { name: "api.data_provider", priority: 1 } ]
+```
+
+Previous chapter: [Operations](operations.md)
+Next chapter: [Filters](filters.md)
diff --git a/api-bundle/external-vocabularies.md b/api-bundle/external-vocabularies.md
new file mode 100644
index 00000000000..4b8022503af
--- /dev/null
+++ b/api-bundle/external-vocabularies.md
@@ -0,0 +1,64 @@
+# Using external (JSON-LD) vocabularies
+
+JSON-LD allows to define classes and properties of your API with open vocabularies such as [Schema.org](https://schema.org)
+and [Good Relations](http://www.heppnetz.de/projects/goodrelations/).
+
+DunglasApiBundle provides annotations usable on PHP classes and properties to specify a related external [IRI](http://en.wikipedia.org/wiki/Internationalized_resource_identifier).
+
+
+```php
+
+Next chapter: [Performances](performances.md)
diff --git a/api-bundle/filters.md b/api-bundle/filters.md
new file mode 100644
index 00000000000..fe509d72f10
--- /dev/null
+++ b/api-bundle/filters.md
@@ -0,0 +1,299 @@
+# Filters
+
+The bundle provides a generic system to apply filters on collections. Useful filters
+for the Doctrine ORM are provided with the bundle. However the filter system is
+extensible enough to let you create custom filters fitting your specific needs
+and for any data provider.
+
+By default, all filters are disabled. They must be enabled explicitly.
+
+When a filter is enabled, it is automatically documented as a `hydra:search` property
+in collection returns. It also automatically appears in the NelmioApiDoc documentation
+if this bundle is active.
+
+## Search filter
+
+If Doctrine ORM support is enabled, adding filters is as easy as adding an entry
+in your `app/config/services.yml` file.
+
+The search filter supports exact and partial matching strategies.
+If the partial strategy is specified, a SQL query with a `WHERE` clause similar
+to `LIKE %text to search%` will be automatically issued.
+
+In the following, we will see how to allow filtering a list of e-commerce offers:
+
+```yaml
+
+# app/config/services.yml
+
+services:
+ resource.offer.search_filter:
+ parent: "api.doctrine.orm.search_filter"
+ arguments: [ { id: "exact", price: "exact", name: "partial" } ]
+
+ resource.offer:
+ parent: "api.resource"
+ arguments: [ "AppBundle\Entity\Offer" ]
+ calls:
+ - method: "initFilters"
+ arguments: [ [ "@resource.offer.search_filter" ] ]
+ tags: [ { name: "api.resource" } ]
+```
+
+`http://localhost:8000/api/offers?price=10` will return all offers with a price being exactly `10`.
+`http://localhost:8000/api/offers?name=shirt` will returns all offer with a description containing the word "shirt".
+
+Filters can be combined together: `http://localhost:8000/api/offers?price=10&name=shirt`
+
+It is possible to filter on relations too:
+
+```yaml
+
+# app/config/services.yml
+
+services:
+ resource.offer.search_filter:
+ parent: "api.doctrine.orm.search_filter"
+ arguments: [ { "product": "exact" } ]
+
+ resource.offer:
+ parent: "api.resource"
+ arguments: [ "AppBundle\Entity\Offer"]
+ calls:
+ - method: "initFilters"
+ arguments: [ [ "@resource.offer.search_filter" ] ]
+ tags: [ { name: "api.resource" } ]
+```
+
+With this service definition, it is possible to find all offers belonging to the
+product identified by a given IRI.
+Try the following: `http://localhost:8000/api/offers?product=/api/products/12`
+Using a numeric ID is also supported: `http://localhost:8000/api/offers?product=12`
+
+Previous URLs will return all offers for the product having the following IRI as
+JSON-LD identifier (`@id`): `http://localhost:8000/api/products/12`.
+
+## Date filter
+
+The date filter allows to filter a collection by date intervals.
+
+Syntax: `?property[]=value`
+
+The value can take any date format supported by the [`\DateTime()`](http://php.net/manual/en/datetime.construct.php)
+class.
+
+As others filters, the date filter must be explicitly enabled:
+
+```yaml
+
+# app/config/services.yml
+
+services:
+ # Enable date filter for for the property "dateProperty" of the resource "resource.offer"
+ resource.date_filter:
+ parent: "api.doctrine.orm.date_filter"
+ arguments: [ { "dateProperty": ~ } ]
+
+ resource.offer:
+ parent: "api.resource"
+ arguments: [ "AppBundle\Entity\Offer"]
+ calls:
+ - method: "initFilters"
+ arguments: [ [ "@resource.offer.date_filter" ] ]
+ tags: [ { name: "api.resource" } ]
+```
+
+### Managing `null` values
+
+The date filter is able to deal with date properties having `null` values.
+Four behaviors are available at the property level of the filter:
+
+| Description | Strategy to set |
+|--------------------------------------|-------------------------------------------------------------------------------|
+| Use the default behavior of the DBMS | `null` |
+| Exclude items | `Dunglas\ApiBundle\Doctrine\Orm\Filter\DateFilter::EXCLUDE_NULL` (`0`) |
+| Consider items as oldest | `Dunglas\ApiBundle\Doctrine\Orm\Filter\DateFilter::INCLUDE_NULL_BEFORE` (`1`) |
+| Consider items as youngest | `Dunglas\ApiBundle\Doctrine\Orm\Filter\DateFilter::INCLUDE_NULL_AFTER` (`2`) |
+
+For instance, exclude entries with a property value of `null`, with the following service definition:
+
+```yaml
+
+# app/config/services.yml
+
+services:
+ resource.date_filter:
+ parent: "api.doctrine.orm.date_filter"
+ arguments: [ { "dateProperty": ~ } ]
+
+ resource.offer:
+ parent: "api.resource"
+ arguments: [ "AppBundle\Entity\Offer"]
+ calls:
+ - method: "initFilters"
+ arguments: [ [ "@resource.offer.date_filter" ] ]
+ tags: [ { name: "api.resource" } ]
+```
+
+If you use another service definition format than YAML, you can use the
+`Dunglas\ApiBundle\Doctrine\Orm\Filter\DateFilter::EXCLUDE_NULL` constant directly.
+
+## Order filter
+
+The order filter allows to order a collection by given properties.
+
+Syntax: `?order[property]=`
+
+Enable the filter:
+
+```yaml
+
+# app/config/services.yml
+
+services:
+ resource.offer.order_filter:
+ parent: "api.doctrine.orm.order_filter"
+ arguments: [ { "id": ~, "name": ~ } ]
+
+ resource.offer:
+ parent: "api.resource"
+ arguments: [ "AppBundle\Entity\Offer"]
+ calls:
+ - method: "initFilters"
+ arguments: [ [ "@resource.offer.order_filter" ] ]
+ tags: [ { name: "api.resource" } ]
+```
+
+Given that the collection endpoint is `/offers`, you can filter offers by name in
+ascending order and then by ID on descending order with the following query: `/offers?order[name]=desc&order[id]=asc`.
+
+By default, whenever the query does not specify explicitly the direction (e.g: `/offers?order[name]&order[id]`), filters will not be applied, unless you configure a default order direction to use:
+
+```yaml
+
+# app/config/services.yml
+
+services:
+ resource.offer.order_filter:
+ parent: "api.doctrine.orm.order_filter"
+ arguments: [ { "id": ASC, "name": DESC } ]
+
+ [...]
+```
+
+### Using a custom order query parameter name
+
+A conflict will occur if `order` is also the name of a property with the search filter enabled.
+Hopefully, the query parameter name to use is configurable:
+
+```yaml
+
+# app/config/config.yml
+
+dunglas_api:
+ collection:
+ filter_name:
+ order: "_order" # the URL query parameter to use is now "_order"
+```
+
+## Enabling a filter for all properties of a resource
+
+As we seen in previous examples, properties where filters can be applied must be
+explicitly declared. But if you don't care about security and performances (ex:
+an API with restricted access), it's also possible to enable builtin filters for
+all properties:
+
+```yaml
+
+# app/config/services.yml
+
+services:
+ # Filter enabled for all properties
+ resource.offer.order_filter:
+ parent: "api.doctrine.orm.order_filter"
+ arguments: [ ~ ] # This line can also be omitted
+```
+
+Regardless of this option, filters can by applied on a property only if:
+- the property exists
+- the value is supported (ex: `asc` or `desc` for the order filters).
+
+It means that the filter will be **silently** ignored if the property:
+- does not exist
+- is not enabled
+- has an invalid value
+
+
+## Creating custom filters
+
+Custom filters can be written by implementing the `Dunglas\ApiBundle\Api\Filter\FilterInterface`
+interface.
+
+Don't forget to register your custom filters with the `Dunglas\ApiBundle\Api\Resource::initFilters()` method.
+
+If you use [custom data providers](data-providers.md), they must support filtering and be aware of actives filters to
+work properly.
+
+### Creating custom Doctrine ORM filters
+
+Doctrine ORM filters must implement the `Dunglas\ApiBundle\Doctrine\Orm\FilterInterface`.
+They can interact directly with the Doctrine `QueryBuilder`.
+
+A convenient abstract class is also shipped with the bundle: `Dunglas\ApiBundle\Doctrine\Orm\AbstractFilter`
+
+### Overriding extraction of properties from the request
+
+How filters data are extracted from the request can be changed for all built-in
+filters by extending the parent filter class an overriding the `extractProperties(\Symfony\Component\HttpFoundation\Request $request)`
+method.
+
+In the following example, we will completely change the syntax of the order filter
+to be the following: `?filter[order][property]`
+
+```php
+
+// src/AppBundle/Filter/CustomOrderFilter.php
+
+namespace AppBundle\Filter;
+
+use Dunglas\ApiBundle\Doctrine\Orm\OrderFilter;
+use Symfony\Component\HttpFoundation\Request;
+
+class CustomOrderFilter extends OrderFilter
+{
+ protected function extractProperties(Request $request)
+ {
+ $filter = $request->query->get('filter[order]', []);
+ }
+}
+```
+
+Finally, register the custom filter:
+
+```yaml
+
+# app/config/services.yml
+
+services:
+ resource.offer.custom_order_filter:
+ class: "AppBundle\Filter\CustomOrderFilter"
+
+ resource.offer:
+ parent: "api.resource"
+ arguments: [ "AppBundle\Entity\Offer" ]
+ calls:
+ - method: "initFilters"
+ arguments: [ [ "@resource.offer.custom_order_filter" ] ]
+ tags: [ { name: "api.resource" } ]
+```
+
+Beware: in [some cases](https://github.com/dunglas/DunglasApiBundle/issues/157#issuecomment-119576010) you may have to use double slashes in the class path to make it work:
+
+```
+services:
+ resource.offer.custom_order_filter:
+ class: "AppBundle\\Filter\\CustomOrderFilter"
+```
+
+Previous chapter: [Data providers](data-providers.md)
+Next chapter: [Serialization groups and relations](serialization-groups-and-relations.md)
diff --git a/api-bundle/fosuser-bundle.md b/api-bundle/fosuser-bundle.md
new file mode 100644
index 00000000000..348ac2488d2
--- /dev/null
+++ b/api-bundle/fosuser-bundle.md
@@ -0,0 +1,87 @@
+# FOSUser Bundle integration
+
+This bundle is shipped with a bridge for the [FOSUserBundle](https://github.com/FriendsOfSymfony/FOSUserBundle). If the FOSUserBundle is enabled, this bridges registers to the persist, update and delete events to pass user objects to the UserManager, before redispatching the event.
+
+## Creating a `User` entity with serialization groups
+
+Here's an example of declaration of a [doctrine ORM User class](https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/index.md#a-doctrine-orm-user-class). As shown you can use serialization groups to hide properties like `plainPassword` (only in read) and `password`. The properties shown are handled with the [`normalizationContext`](serialization-groups-and-relations.md#normalization), while the properties you can modify are handled with [`denormalizationContext`](serialization-groups-and-relations.md#denormalization).
+
+First register the following service:
+
+```yaml
+# app/config/services.yml
+
+resource.user:
+ parent: "api.resource"
+ arguments: [ "AppBundle\Entity\User" ]
+ calls:
+ - method: "initNormalizationContext"
+ arguments: [ { groups: [ "user_read" ] } ]
+ - method: "initDenormalizationContext"
+ arguments: [ { groups: [ "user_write" ] } ]
+ tags: [ { name: "api.resource" } ]
+```
+
+Then create your User entity with serialization groups:
+
+```php
+
+Next chapter: [Operations](operations.md)
diff --git a/api-bundle/operations.md b/api-bundle/operations.md
new file mode 100644
index 00000000000..9466075730d
--- /dev/null
+++ b/api-bundle/operations.md
@@ -0,0 +1,116 @@
+# Operations
+
+By default, the following operations are automatically enabled:
+
+*Collection*
+
+| Method | Description |
+|--------|-------------------------------------------|
+| `GET` | Retrieve the (paginated) list of elements |
+| `POST` | Create a new element |
+
+*Item*
+
+| Method | Description |
+|----------|-------------------------------------------|
+| `GET` | Retrieve element (mandatory operation) |
+| `PUT` | Update an element |
+| `DELETE` | Delete an element |
+
+
+## Disabling operations
+
+If you want to disable some operations (e.g. the `DELETE` operation), you must create manually applicable operations using
+the operation factory class, `Dunglas\ApiBundle\Api\Operation\OperationFactory::createCollectionOperation()` or/and `Dunglas\ApiBundle\Api\Operation\OperationFactory::createItemOperation()` methods and then, register it in the resource with `Dunglas\ApiBundle\Api/Resource::initCollectionOperations` or/and `Dunglas\ApiBundle\Api/Resource::initItemOperations`.
+
+The following `Resource` definition exposes a `GET` operation for it's collection but not the `POST` one:
+
+```yaml
+services:
+ resource.product.collection_operation.get:
+ class: "Dunglas\ApiBundle\Api\Operation\Operation"
+ public: false
+ factory: [ "@api.operation_factory", "createCollectionOperation" ]
+ arguments: [ "@resource.product", "GET" ]
+
+ resource.product:
+ parent: "api.resource"
+ arguments: [ "AppBundle\Entity\Product" ]
+ calls:
+ - [ "initCollectionOperations", [ [ "@resource.product.collection_operation.get" ] ] ]
+ tags: [ { name: "api.resource" } ]
+```
+
+However, in the following example items operations will still be automatically registered. To disable them, call `initItemOperations`
+with an empty array as first parameter:
+
+```yaml
+# ...
+
+ resource.product:
+ parent: "api.resource"
+ arguments: [ "AppBundle\Entity\Product" ]
+ calls:
+ - [ "initItemOperations", [ [ ] ] ]
+ - [ "initCollectionOperations", [ [ "@resource.product.collection_operation.get" ] ] ]
+ tags: [ { name: "api.resource" } ]
+```
+
+## Creating custom operations
+
+DunglasApiBundle allows to register custom operations for collections and items.
+Custom operations allow to customize routing information (like the URL and the HTTP method),
+the controller to use (default to the built-in action of the `ResourceController` applicable
+for the given HTTP method) and a context that will be passed to documentation generators.
+
+A convenient factory is provided to build `Dunglas\ApiBundle\Api\Operation\Operation` instances.
+This factory guesses good default values for options such as the route name and its associated URL
+by inspecting the given `Resource` instance. All guessed values can be override.
+
+If you want to use custom controller action, [refer to the dedicated documentation](controllers.md).
+
+DunglasApiBundle is smart enough to automatically register routes in the Symfony routing system and to document
+operations in the Hydra vocab.
+
+```yaml
+ resource.product.item_operation.get:
+ class: "Dunglas\ApiBundle\Api\Operation\Operation"
+ public: false
+ factory: [ "@api.operation_factory", "createItemOperation" ]
+ arguments: [ "@resource.product", "GET" ]
+
+ resource.product.item_operation.put:
+ class: "Dunglas\ApiBundle\Api\Operation\Operation"
+ public: false
+ factory: [ "@api.operation_factory", "createItemOperation" ]
+ arguments: [ "@resource.product", "PUT" ]
+
+ resource.product.item_operation.custom_get:
+ class: "Dunglas\ApiBundle\Api\Operation\Operation"
+ public: false
+ factory: [ "@api.operation_factory", "createItemOperation" ]
+ arguments:
+ - "@resource.product" # Resource
+ - [ "GET", "HEAD" ] # Methods
+ - "/products/{id}/custom" # Path
+ - "AppBundle:Custom:custom" # Controller
+ - "my_custom_route" # Route name
+ - # Context (will be present in Hydra documentation)
+ "@type": "hydra:Operation"
+ "hydra:title": "A custom operation"
+ "returns": "xmls:string"
+
+ resource.product:
+ parent: "api.resource"
+ arguments: [ "AppBundle\Entity\Product" ]
+ calls:
+ - method: "initItemOperations"
+ arguments: [ [ "@resource.product.item_operation.get", "@resource.product.item_operation.put", "@resource.product.item_operation.custom_get" ] ]
+ tags: [ { name: "api.resource" } ]
+```
+
+Additionally to the default generated `GET` and `PUT` operations, this definition will expose a new `GET` operation for
+the `/products/{id}/custom` URL. When this URL is opened, the `AppBundle:Custom:custom` controller is called.
+
+Previous chapter: [NelmioApiDocBundle integration](nelmio-api-doc.md)
+Next chapter: [Data providers](data-providers.md)
diff --git a/api-bundle/performances.md b/api-bundle/performances.md
new file mode 100644
index 00000000000..31624ec0065
--- /dev/null
+++ b/api-bundle/performances.md
@@ -0,0 +1,21 @@
+# Performances
+
+## Enabling the metadata cache
+
+Computing metadata used by the bundle is a costly operation. Fortunately, metadata can be computed once then cached. The
+bundle provides a built-in cache service using [APCu](https://github.com/krakjoe/apcu).
+To enable it in the prod environment (requires APCu to be installed), add the following lines to `app/config/config_prod.yml`:
+
+```yaml
+dunglas_api:
+ cache: api.mapping.cache.apc
+```
+
+DunglasApiBundle leverages [Doctrine Cache](https://github.com/doctrine/cache) to abstract the cache backend. If
+you want to use a custom cache backend such as Redis, Memcache or MongoDB, register a Doctrine Cache provider as a service
+and set the `cache` config key to the id of the custom service you created.
+
+A built-in cache warmer will be automatically executed every time you clear or warmup the cache if a cache service is configured.
+
+Previous chapter: [Using external (JSON-LD) vocabularies](external-vocabularies.md)
+Next chapter: [AngularJS Integration](angular-integration.md)
diff --git a/api-bundle/resources.md b/api-bundle/resources.md
new file mode 100644
index 00000000000..8b3dc8bdea5
--- /dev/null
+++ b/api-bundle/resources.md
@@ -0,0 +1,88 @@
+# Resources
+
+The default `Resource` class provided by the bundle is sufficient for small projects. If your app grows, using custom resources
+can become necessary.
+
+## Using a custom `Resource` class
+
+When the size of your services definition start to grow, it is useful to create custom resources instead of using the default
+one. To do so, the `Dunglas\ApiBundle\Api\ResourceInterface` interface must be implemented.
+
+```php
+
+Next chapter: [Controllers](controllers.md)
diff --git a/api-bundle/serialization-groups-and-relations.md b/api-bundle/serialization-groups-and-relations.md
new file mode 100644
index 00000000000..c0102fefa7a
--- /dev/null
+++ b/api-bundle/serialization-groups-and-relations.md
@@ -0,0 +1,236 @@
+# Serialization groups and relations
+
+## Using serialization groups
+
+Symfony 2.7 introduced [serialization (and deserialization) groups support](http://symfony.com/blog/new-in-symfony-2-7-serialization-groups)
+in the Serializer component. Specifying to the API system the groups to use is damn easy:
+
+```yaml
+# app/config/services.yml
+services:
+ resource.product:
+ parent: "api.resource"
+ arguments: [ "AppBundle\Entity\Product" ]
+ calls:
+ - method: "initNormalizationContext"
+ arguments: [ { groups: [ "serialization_group1", "serialization_group2" ] } ]
+ - method: "initDenormalizationContext"
+ arguments: [ { groups: [ "deserialization_group1", "deserialization_group2" ] } ]
+ tags: [ { name: "api.resource" } ]
+```
+
+The built-in controller and the Hydra documentation generator will leverage specified serialization and deserialization
+to give access only to exposed properties and to guess if they are readable or/and writable.
+
+## Annotations
+
+The Symfony serializer allows to specify the definition of serialization using XML, YAML, or annotations. As annotations are really easy to understand, we'll use them in following examples.
+However, in order to use annotations, don't forget to enable it in the serializer configuration:
+```yaml
+# app/config/config.yml
+framework:
+ # ...
+ serializer: { enable_annotations: true }
+```
+
+## Embedding relations
+
+By default, the serializer provided with DunglasApiBundle will represent relations between objects by dereferenceables
+URIs. They allow to retrieve details of related objects by issuing an extra HTTP request.
+
+In the following JSON document, the relation from an offer to a product is represented by an URI:
+
+```json
+{
+ "@context": "/contexts/Offer",
+ "@id": "/offer/62",
+ "@type": "Offer",
+ "price": 31.2,
+ "product": "/products/59"
+}
+```
+
+## Embedding the context
+
+By default, the context attribute (`@context`) will be serialized as an URI:
+```json
+{
+ "@context": "/contexts/Offer"
+}
+```
+
+You can also decide to embed it:
+```json
+{
+ "@context": {
+ "@vocab": "http://localhost/vocab#",
+ "hydra": "http://www.w3.org/ns/hydra/core#",
+ "name": "#Offer/name"
+ }
+}
+```
+
+For this, register the following services (for example in `app/config/services.yml`):
+
+```yaml
+services:
+ # ...
+
+ resource.offer:
+ parent: "api.resource"
+ arguments: [ "AppBundle\Entity\Offer" ]
+ calls:
+ - method: "initNormalizationContext"
+ arguments: [ { json_ld_context_embedded: true } ]
+ tags: [ { name: "api.resource" } ]
+```
+
+### Normalization
+
+From a performance point of view, it's sometimes necessary to avoid extra HTTP requests. It is possible to embed related
+objects (or only some of their properties) directly in the parent response trough serialization groups.
+By using the following serizalization groups annotations (`@Groups`) and this updated service definition, a JSON representation
+of the product is embedded in the offer response:
+
+```php
+
+Next chapter: [Validation](validation.md)
diff --git a/api-bundle/the-event-system.md b/api-bundle/the-event-system.md
new file mode 100644
index 00000000000..0333a170652
--- /dev/null
+++ b/api-bundle/the-event-system.md
@@ -0,0 +1,82 @@
+# The event system
+
+DunglasApiBundle leverages the [Symfony Event Dispatcher Component](http://symfony.com/doc/current/components/event_dispatcher/index.html)
+to provide a powerful event system triggered in the object lifecycle.
+
+Additionally to [events triggered by the Symfony HTTP Kernel](http://symfony.com/doc/current/components/http_kernel/introduction.html#creating-an-event-listener)
+and [by Doctrine ORM](http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#reference-events-lifecycle-events)
+(if you use it), DunglasApiBundle comes with its own set of events occurring during the handling of the request:
+
+## Retrieving list
+
+- `api.retrieve_list` (`Dunglas\ApiBundle\Event\Events::RETRIEVE_LIST`): occurs after the retrieving of an object list during a `GET` request on a collection.
+
+## Retrieving item
+
+- `api.retrieve` (`Dunglas\ApiBundle\Event\Events::RETRIEVE_LIST`): after the retrieving of an object during a `GET` request on an item.
+
+## Creating item
+
+- `api.pre_create_validation` (`Dunglas\ApiBundle\Event\Events::PRE_CREATE_VALIDATION`): occurs before the object validation during a `POST` request.
+- `api.pre_create` (`Dunglas\ApiBundle\Event\Events::PRE_CREATE`): occurs after the object validation and before its persistence during a `POST` request.
+- `api.post_create` (`Dunglas\ApiBundle\Event\Events::POST_CREATE`): event occurs after the object persistence during `POST` request.
+
+## Updating item
+
+- `api.pre_update_validation` (`Dunglas\ApiBundle\Event\Events::PRE_UPDATE_VALIDATION`): event occurs before the object validation during a `PUT` request.
+- `api.pre_update` (`Dunglas\ApiBundle\Event\Events::PRE_UPDATE`): occurs after the object validation and before its persistence during a `PUT` request.
+- `api.post_update` (`Dunglas\ApiBundle\Event\Events::POST_UPDATE`): event occurs after the object persistence during a `PUT` request.
+
+## Deleting item
+
+- `api.pre_delete` (`Dunglas\ApiBundle\Event\Events::PRE_DELETE`): event occurs before the object deletion during a `DELETE` request.
+- `api.post_delete` (`Dunglas\ApiBundle\Event\Events::POST_DELETE`): occurs after the object deletion during a `DELETE` request.
+
+## JSON-LD context builder
+
+- `api.json_ld.context_builder` (`Dunglas\ApiBundle\JsonLd\Event\Events::CONTEXT_BUILDER`): occurs after the initialization of the context and lets you extend the context. Event object is an instance of Dunglas\ApiBundle\JsonLd\Event\ContextBuilderEvent.
+
+## Registering an event listener
+
+In the following example, we register an event listener that will be called every time after the creation of an object:
+
+```php
+getData();
+
+ if ($data instanceof MyObject) {
+ $resource = $event->getResource(); // Get the related instance of Dunglas\ApiBundle\Api\ResourceInterface
+
+ // Do something awesome here
+ }
+ }
+}
+```
+
+```yaml
+# app/config/services.yml
+
+services:
+ my_event_listener:
+ class: "AppBundle\EventListener\MyEventListener"
+ tags: [ { name: "kernel.event_listener", event: "api.post_create", method: "onPostCreate" } ]
+```
+
+Previous chapter: [Validation](validation.md)
+Next chapter: [Resources](resources.md)
diff --git a/api-bundle/validation.md b/api-bundle/validation.md
new file mode 100644
index 00000000000..996a31e8f74
--- /dev/null
+++ b/api-bundle/validation.md
@@ -0,0 +1,25 @@
+# Validation
+
+DunglasApiBundle use the Symfony validator to validate entities.
+By default, it uses the default validation group, but this behavior is customizable.
+
+## Using validation groups
+The built-in controller is able to leverage Symfony's [validation groups](http://symfony.com/doc/current/book/validation.html#validation-groups).
+
+To take care of them, edit your service declaration and add groups you want to use when the validation occurs:
+
+```yaml
+services:
+ resource.product:
+ parent: "api.resource"
+ arguments: [ "AppBundle\Entity\Product" ]
+ calls:
+ - method: "initValidationGroups"
+ arguments: [ [ "group1", "group2" ] ]
+ tags: [ { name: "api.resource" } ]
+```
+
+With the previous definition, the validations groups `group1` and `group2` will be used when the validation occurs.
+
+Previous chapter: [Serialization groups and relations](serialization-groups-and-relations.md)
+Next chapter: [The event system](the-event-system.md)
diff --git a/getting-started/docker.md b/deployment/docker.md
similarity index 83%
rename from getting-started/docker.md
rename to deployment/docker.md
index 0718318dc65..954fcde1158 100644
--- a/getting-started/docker.md
+++ b/deployment/docker.md
@@ -1,6 +1,6 @@
-# Use it with docker !
+# Using API Platform with Docker
-Project can be run through [Docker](https://www.docker.com/) &
+Projects using API Platform can be run through [Docker](https://www.docker.com/) and
[Docker compose](https://docs.docker.com/compose/). Run `docker-compose up -d`,
your project will be accessible at [http://127.0.0.1](http://127.0.0.1).
diff --git a/deployment/heroku.md b/deployment/heroku.md
index 03400b09f24..76ef5f3281e 100644
--- a/deployment/heroku.md
+++ b/deployment/heroku.md
@@ -166,4 +166,3 @@ can scale it in one click from the Heroku interface.
To see your logs, run `heroku logs --tail`.
Can it be easier? Yes it can: we are preparing an API Platform edition preconfigured to run on Heroku! Stay tuned.
-
diff --git a/deployment/index.md b/deployment/index.md
new file mode 100644
index 00000000000..c1937b16ec5
--- /dev/null
+++ b/deployment/index.md
@@ -0,0 +1,9 @@
+# Deploying API Platform applications
+
+As an API Platform application is basically a standard Symfony application, [all Symfony deployment cookbooks](http://symfony.com/doc/current/cookbook/deployment/index.html)
+apply.
+
+However, API Platform also provide facilities to deploy applications trough containers and Platforms as a Service (Paas):
+
+* [Deploying an API Platform app on Heroku](heroku.md)
+* [Using API Platform with Docker](docker.md)
diff --git a/getting-started/api.md b/getting-started/api.md
index 3222ee9b6f9..49b43f90268 100644
--- a/getting-started/api.md
+++ b/getting-started/api.md
@@ -8,7 +8,7 @@ If you are in a hurry, a demo is available online and all sources created during
/ [sources](https://github.com/dunglas/blog-api)
* the Angular client: [demo](https://dunglas.github.io/blog-client/) / [sources](https://github.com/dunglas/blog-client)
-[](http://dunglas.fr/wp-content/uploads/2015/06/blog-api-platform.png)
+[](images/blog-api-platform.png)
To create the API-side of our project we will:
@@ -26,7 +26,7 @@ any language able to send HTTP requests).
## Prerequisites
-Only PHP 5.5+ must be installed to run Dunglas's API Platform. A built-in web server is shipped with the framework for the
+Only PHP 5.5+ must be installed to run API Platform. A built-in web server is shipped with the framework for the
development environment.
To follow this tutorial a database must be installed (but its not a strong dependency of the framework). We recommend MySQL
@@ -59,13 +59,13 @@ The installer will ask for:
* a secret token (choose a long one) for cryptographic features
Take a look at [the content of the generated directory](https://github.com/dunglas/blog-api). You should recognize a [Symfony
-application directory structure](http://symfony.com/doc/current/quick_tour/the_architecture.html). It's fine and intended:
-**the generated skeleton is a perfectly valid Symfony full-stack application** that follows [Symfony Best Practices](http://symfony.com/doc/current/best_practices/index.html).
+application directory structure](https://symfony.com/doc/current/quick_tour/the_architecture.html). It's fine and intended:
+**the generated skeleton is a perfectly valid Symfony full-stack application** that follows [Symfony Best Practices](https://symfony.com/doc/current/best_practices/index.html).
It means that you can:
* [use thousands of exiting Symfony bundles](http://knpbundles.com) with API Platform
* use API Platform in any existing Symfony application
-* reuse all your Symfony skills and benefit of the high quality [Symfony documentation](http://symfony.com/doc/current/index.html)
+* reuse all your Symfony skills and benefit of the high quality [Symfony documentation](https://symfony.com/doc/current/index.html)
The skeleton comes with a demonstration bookstore API. Remove it:
@@ -74,7 +74,7 @@ The skeleton comes with a demonstration bookstore API. Remove it:
## Generating the data model
-The first incredibly useful tool provided by Dunglas's API platform is it's data model generator also know as **[PHP Schema](https://github.com/dunglas/php-schema)**.
+The first incredibly useful tool provided by API platform is [its data model generator](../shema-generator/index.md).
This API Platform component can also be used standalone to bootstrap any PHP data model.
To kickstart our blog data model we browse [Schema.org](http://schema.org) and find an existing schema that describe perfectly
@@ -90,7 +90,7 @@ Browse Schema.org, choose the types and properties you need (there is a bunch of
* Full high-quality PHPDoc for classes, properties, constants and methods extracted from Schema.org.
* Doctrine ORM annotation mapping including database columns with type guessing, relations with cardinality guessing, class
inheritance (through the `@AbstractSuperclass` annotation).
-* Data validation through [Symfony Validator](http://symfony.com/doc/current/book/validation.html) annotations including
+* Data validation through [Symfony Validator](https://symfony.com/doc/current/book/validation.html) annotations including
data type validation, enum support (choices) and check for required properties.
* Interfaces and [Doctrine `ResolveTargetEntityListener`](http://doctrine-orm.readthedocs.org/en/latest/cookbook/resolve-target-entity-listener.html)
support.
@@ -180,7 +180,7 @@ Then generate database tables related to the generated entities:
app/console doctrine:schema:create
-PHP Schema provides a lot of configuration options. Take a look at [its dedicated documentation](https://github.com/dunglas/php-schema).
+PHP Schema provides a lot of configuration options. Take a look at [its dedicated documentation](../schema-generator/index.md).
Keep in mind that PHP Schema is also available as a standalone tool (and a PHAR will be available soon) and can be used
to bootstrap any PHP project (works fine with raw PHP, API Platform and Symfony but has an extension mechanism allowing
to use it with other technologies such as Zend Framework and Laravel).
@@ -199,11 +199,11 @@ remove `dunglas/php-schema` from your `composer.json` file.
## Exposing the API
We have a working data model backed by a database. Now we will create a hypermedia REST API thanks to another component
-of Dunglas's API Platform: **[DunglasApiBundle](https://github.com/dunglas/DunglasApiBundle)**.
+of API Platform: **[ApiBundle](../api-bundle/index.md)**.
As PHP Schema, it is already preinstalled and properly configured. We just need to declare resources we want to expose.
-Exposing a resource collection basically consist to register a new [Symfony service](http://symfony.com/doc/current/book/service_container.html).
+Exposing a resource collection basically consist to register a new [Symfony service](https://symfony.com/doc/current/book/service_container.html).
For our blog app we will expose trough the API the two entity classes generated by PHP Schema: `BlogPosting` (blog post)
and `Person` (author of the post):
@@ -228,9 +228,9 @@ Start the integrated development web server: `app/console server:start`
Then open `http://localhost:8000/doc` with a web browser:
-[](http://dunglas.fr/wp-content/uploads/2015/06/api-platform-nelmio-api-doc.png)
+[](images/api-doc.png)
-Thanks to [NelmioApiDocBundle](https://github.com/nelmio/NelmioApiDocBundle) support of DunglasApiBundle and its integration
+Thanks to [NelmioApiDocBundle](https://github.com/nelmio/NelmioApiDocBundle) support of ApiBundle and its integration
with API Platform, you get for a free **an automatically generated human-readable documentation** of the API (Swagger-like).
The doc also **includes a sandbox** to try the API.
@@ -264,7 +264,7 @@ The data is inserted in database. The server replies with a JSON-LD representati
The JSON-LD spec is fully supported by the bundle. Want a proof? Browse `http://localhost:8000/contexts/Person`.
By default, the API allows `GET` (retrieve, on collections and items), `POST` (create), `PUT` (update) and `DELETE` (self-explaining)
-HTTP methods. [You can add and remove any other operation you want](https://github.com/dunglas/DunglasApiBundle/blob/master/Resources/doc/operations.md).
+HTTP methods. [You can add and remove any other operation you want](../api-bundle/operations.md).
Try it!
Now, browse `http://localhost:8000/people`:
@@ -295,7 +295,7 @@ as body:
```json
{
- "name": "Dunglas's API Platform is great",
+ "name": "API Platform is great",
"headline": "You'll love that framework!",
"articleBody": "The body of my article.",
"articleSection": "technology",
@@ -328,7 +328,7 @@ Correct the body and send the request again:
```json
{
- "name": "Dunglas's API Platform is great",
+ "name": "API Platform is great",
"headline": "You'll love that framework!",
"articleBody": "The body of my article.",
"articleSection": "technology",
@@ -356,21 +356,21 @@ an open web standard for describing hypermedia REST APIs in JSON-LD. Any Hydra-c
with the API without knowing anything about it! The most popular Hydra client is [Hydra Console](http://www.markus-lanthaler.com/hydra/console/).
Open an URL of the API with it you'll get a nice management interface.
-[](http://dunglas.fr/wp-content/uploads/2015/06/api-platform-hydra-console.png)
+[](console.png)
You can also give a try to the brand new [hydra-core Javascript library](https://github.com/bergos/hydra-core).
-DunglasApiBundle offers a lot of other features including:
+ApiBundle offers a lot of other features including:
-* [filters](https://github.com/dunglas/DunglasApiBundle/blob/master/Resources/doc/filters.md)
-* [serialization groups and child resource embedding](https://github.com/dunglas/DunglasApiBundle/blob/master/Resources/doc/serialization-groups-and-relations.md)
-* [custom operations](https://github.com/dunglas/DunglasApiBundle/blob/master/Resources/doc/operations.md): deactivate
+* [filters](../api-bundle/filters.md)
+* [serialization groups and child resource embedding](../api-bundle/serialization-groups-and-relations.md)
+* [custom operations](../api-bundle/operations.md): deactivate
some methods, create custom operations, URL and controllers
-* [data providers](https://github.com/dunglas/DunglasApiBundle/blob/master/Resources/doc/data-providers.md): retrieve and
+* [data providers](../api-bundle/data-providers.md): retrieve and
modify data trough a web-service or a MongoDB database or anything else instead of Doctrine ORM
-* a powerful [event system](https://github.com/dunglas/DunglasApiBundle/blob/master/Resources/doc/the-event-system.md)
+* a powerful [event system](../api-bundle/the-event-system.md)
-Read [its dedicated documentation](https://github.com/dunglas/DunglasApiBundle/) to see how to leverage them and how to
+Read [its dedicated documentation](../api-bundle/index.md) to see how to leverage them and how to
hook your own code everywhere into it.
## Specifying and testing the API
@@ -439,7 +439,7 @@ Feature: Blog
When I send a "POST" request to "/blog_postings" with body:
"""
{
- "name": "Dunglas's API Platform is great",
+ "name": "API Platform is great",
"headline": "You'll that framework!",
"articleBody": "The body of my article.",
"articleSection": "technology",
@@ -473,7 +473,7 @@ Feature: Blog
When I send a "POST" request to "/blog_postings" with body:
"""
{
- "name": "Dunglas's API Platform is great",
+ "name": "API Platform is great",
"headline": "You'll that framework!",
"articleBody": "The body of my article.",
"articleSection": "technology",
@@ -498,7 +498,7 @@ Feature: Blog
"datePublished": "2015-05-11T00:00:00+02:00",
"headline": "You'll that framework!",
"isFamilyFriendly": true,
- "name": "Dunglas's API Platform is great"
+ "name": "API Platform is great"
}
"""
```
@@ -524,7 +524,7 @@ more documentation and cookbooks are coming!
Here is a non exhaustive list of what you can do with API Platform:
-* Add [a user management system](https://github.com/dunglas/DunglasApiBundle/blob/master/Resources/doc/fosuser-bundle.md)
+* Add [a user management system](../api-bundle/fosuser-bundle.md)
(FOSUser integration)
* [Secure the API with JWT](https://github.com/lexik/LexikJWTAuthenticationBundle) (LexikJwtAuthenticationBundle) or [OAuth](https://github.com/FriendsOfSymfony/FOSOAuthServerBundle)
(FosOAuthServer)
@@ -533,7 +533,7 @@ Here is a non exhaustive list of what you can do with API Platform:
* [Add CSRF protection when the API authentication relies on cookies](https://github.com/dunglas/DunglasAngularCsrfBundle)
(DunglasAngularCsrfBundle – you should prefer using a stateless authentication mode such as a JWT token stored in the
browser session storage when possible)
-* [Send mails](http://symfony.com/doc/current/cookbook/email/email.html) (Swift Mailer)
-* [Use Apache or Nginx](http://symfony.com/doc/current/cookbook/configuration/web_server_configuration.html), or even [Heroku](../deployment/heroku.md)
+* [Send mails](https://symfony.com/doc/current/cookbook/email/email.html) (Swift Mailer)
+* [Deploy](../deployment/index.md)
The next step? [Learn how to create an AngularJS client for the API](angularjs.md).
diff --git a/getting-started/images/api-doc.png b/getting-started/images/api-doc.png
new file mode 100644
index 00000000000..e7656033514
Binary files /dev/null and b/getting-started/images/api-doc.png differ
diff --git a/getting-started/images/blog-api-platform.png b/getting-started/images/blog-api-platform.png
new file mode 100644
index 00000000000..f52f745eb35
Binary files /dev/null and b/getting-started/images/blog-api-platform.png differ
diff --git a/getting-started/images/console.png b/getting-started/images/console.png
new file mode 100644
index 00000000000..b1abf9e0237
Binary files /dev/null and b/getting-started/images/console.png differ
diff --git a/README.md b/getting-started/index.md
similarity index 82%
rename from README.md
rename to getting-started/index.md
index fcb3e83fd8c..6387a47bbb3 100644
--- a/README.md
+++ b/getting-started/index.md
@@ -1,6 +1,4 @@
-# API Platform
-
-
+# Getting started with the API Platform framework
In 20 years of PHP, the web changed dramatically and is now evolving faster than ever:
@@ -28,17 +26,9 @@ full-stack all-in-one framework and a set of independent PHP components and bund
The architecture promoted by the framework will distrust many of you but read this tutorial until the end and you will
see how API Platform make modern development easy and fun again:
-* Start by **creating [a hypermedia REST API](http://en.wikipedia.org/wiki/HATEOAS)** exposing structured data that can
+* [Start by **creating a hypermedia REST API**](api.md) exposing structured data that can
be understood by any compliant client such your apps but also as search engines (JSON-LD with Schema.org vocabulary).
This API is the central and unique entry point to access and modify data. It also encapsulates the whole business logic.
-* Then **create as many clients as you want using frontend technologies you love**: an HTML5/Javascript webapp querying
+* [Then **create as many clients as you want using frontend technologies you love**](angularjs.md): an HTML5/Javascript webapp querying
the API in AJAX (of course) but also a native iOS or Android app, or even a desktop application. Clients only display
data and forms.
-
-## Resources
-
-* [Getting started](getting-started/api.md)
-* [The model generator](http://php-schema.dunglas.fr)
-* [The API system](https://github.com/dunglas/DunglasApiBundle)
-* [Deploying on Heroku](deployment/heroku.md)
-* [Use it with docker !](getting-started/docker.md)
diff --git a/index.md b/index.md
new file mode 100644
index 00000000000..9233864c087
--- /dev/null
+++ b/index.md
@@ -0,0 +1,6 @@
+# API Platform documentation
+
+* [Getting started with API Platform](getting-started/index.md)
+* [The schema generator](schema-generator/index.md)
+* [The API bundle](api-bundle/index.md)
+* [Deploying API Platform applications](deployment/index.md)
diff --git a/schema-generator/configuration.md b/schema-generator/configuration.md
new file mode 100644
index 00000000000..8bd32f48e03
--- /dev/null
+++ b/schema-generator/configuration.md
@@ -0,0 +1,349 @@
+# Configuration
+
+The following options can be used in the configuration file.
+
+## Customizing PHP namespaces
+
+Namespaces of generated PHP classes can be set globally, respectively for entities, enumerations and interfaces (if used
+with Doctrine Resolve Target Entity Listener option).
+
+Example:
+
+```yaml
+namespaces:
+ entity: "Dunglas\EcommerceBundle\Entity"
+ enum: "Dunglas\EcommerceBundle\Enum"
+ interface: "Dunglas\EcommerceBundle\Model"
+```
+
+Namespaces can also be specified for a specific type. It will take precedence over any globally configured namespace.
+
+Example:
+
+```yaml
+types:
+ Thing:
+ namespaces:
+ entity: "Dunglas\CoreBundle\Entity" # Namespace for the Thing entity (works for enumerations too)
+ interface: "Schema\Model" # Namespace of the related interface
+```
+
+## Forcing a field range
+
+Schema.org allows a property to have several types. However, the generator allows only one type by property. If not configured,
+it will use the first defined type.
+The `range` option is useful to set the type of a given property. It can also be used to force a type (even if not in the
+Schema.org definition).
+
+Example:
+
+```yaml
+types:
+ Brand:
+ properties:
+ logo: { range: "ImageObject" } # Force the range of the logo propery to ImageObject (can also be URL according to Schema.org)
+
+ PostalAddress:
+ properties:
+ addressCountry: { range: "Text" } # Force the type to Text instead of Country
+```
+
+## Forcing a field cardinality
+
+The cardinality of a property is automatically guessed. The `cardinality` option allows to override the guessed value.
+Supported cardinalities are:
+* `(0..1)`: scalar, not required
+* `(0..*)`: array, not required
+* `(1..1)`: scalar, required
+* `(1..*)`: array, required
+
+Cardinalities are enforced by the class generator, the Doctrine ORM generator and the Symfony validation generator.
+
+Example:
+
+```yaml
+types:
+ Product:
+ properties:
+ sku:
+ cardinality: "(0..1)"
+```
+
+## Forcing a relation table name
+
+The relation table name between two entities is automatically guessed by Doctrine. The `relationTableName` option allows
+to override the default value.
+
+This is useful when you need two entities to have more than one relation.
+
+Example:
+
+```yaml
+ Organization:
+ properties:
+ contactPoint: { range: Person, relationTableName: organization_contactPoint }
+ member: { range: Person, cardinality: (1..*) } ## Will be default value : organization_person
+```
+
+## Forcing (or disabling) a class parent
+
+Override the guessed class hierarchy of a given type with this option.
+
+Example:
+
+```yaml
+ ImageObject:
+ parent: Thing # Force the parent to be Thing instead of CreativeWork > MediaObject
+ properties: ~
+ Drug:
+ parent: false # No parent
+```
+
+## Forcing a class to be abstract
+
+Force a class to be `abstract` (or to be not).
+
+Example:
+
+```yaml
+ Person:
+ abstract: true
+```
+
+## Author PHPDoc
+
+Add a `@author` PHPDoc annotation to class' DocBlock.
+
+Example:
+
+```yaml
+author: "Kévin Dunglas "
+```
+
+## Disabling generators and creating custom ones
+
+By default, all generators except the `DunglasJsonLdApi` one are enabled. You can specify the list of generators to use
+with the `generators` option.
+
+Example (enabling only the PHPDoc generator):
+
+```yaml
+annotationGenerators:
+ - SchemaOrgModel\AnnotationGenerator\PhpDocAnnotationGenerator
+```
+
+You can write your generators by implementing the [AnnotationGeneratorInterface](src/SchemaOrgModel/AnnotationGenerator/AnnotationGeneratorInterface).
+The [AbstractAnnotationGenerator](src/SchemaOrgModel/AnnotationGenerator/AbstractAnnotationGenerator) provides helper methods
+useful when creating your own generators.
+
+Enabling a custom generator and the PHPDoc generator:
+
+```yaml
+annotationGenerators:
+ - SchemaOrgModel\AnnotationGenerator\PhpDocAnnotationGenerator
+ - Acme\Generators\MyGenerator
+```
+
+## Disabling `id` generator
+
+By default, the generator add a property called `id` not provided by Schema.org. This useful when using generated entity
+with an ORM or an ODM.
+This behavior can be disabled with the following setting:
+
+```yaml
+generateId: false
+```
+
+## Disabling usage of Doctrine collection
+
+By default, the generator use classes provided by the [Doctrine Collections](https://github.com/doctrine/collections) library
+to store collections of entities. This is useful (and required) when using Doctrine ORM or Doctrine ODM.
+This behavior can be disabled (to fallback to standard arrays) with the following setting:
+
+```yaml
+doctrine:
+ useCollection: false
+```
+
+## Custom field visibility
+
+Generated fields have a `private` visibility and are exposed through getters and setters.
+The default visibility can be changed with the `fieldVisibility` otion.
+
+Example:
+
+```yaml
+fieldVisibility: "protected"
+```
+
+## Forcing Doctrine inheritance mapping annotation
+
+The standard behavior of the generator is to use the `@MappedSuperclass` Doctrine annotation for classes with children and
+`@Entity` for classes with no child.
+
+The inheritance annotation can be forced for a given type like the following:
+
+```yaml
+types:
+ Product:
+ doctrine:
+ inheritanceMapping: "@MappedSuperclass"
+```
+
+*This setting is only relevant when using the Doctrine ORM generator.*
+
+## Interfaces and Doctrine Resolve Target Entity Listener
+
+[`ResolveTargetEntityListener`](http://doctrine-orm.readthedocs.org/en/latest/cookbook/resolve-target-entity-listener.html)
+is a feature of Doctrine to keep modules independent. It allows to specify interfaces and `abstract` classes in relation
+mappings.
+
+If you set the option `useInterface` to true, the generator will generate an interface corresponding to each generated
+entity and will use them in relation mappings.
+
+To let PHP Schema generating the XML mapping file usable with Symfony add the following to your config file:
+
+```yaml
+doctrine:
+ resolveTargetEntityConfigPath: path/to/doctrine.xml
+```
+
+## Custom schemas
+
+The generator can use your own schema definitions. They must be wrote in RDFa and follow the format of the [Schema.org's
+definition](http://schema.org/docs/schema_org_rdfa.html). This is useful to document your [Schema.org extensions](http://schema.org/docs/extension.html) and use them
+to generate the PHP data model of your application.
+
+Example:
+
+```yaml
+rdfa:
+ - https://raw.githubusercontent.com/rvguha/schemaorg/master/data/schema.rdfa # Experimental version of Schema.org
+ - http://example.com/data/myschema.rfa # Additional types
+```
+
+*Support for other namespaces than `http://schema.org` is planned for future versions but not currently available.*
+
+## Checking GoodRelation compatibility
+
+If the `checkIsGoodRelations` option is set to `true`, the generator will emit a warning if an encountered property is not
+par of the [GoodRelations](http://www.heppnetz.de/projects/goodrelations/) schema.
+
+This is useful when generating e-commerce data model.
+
+## PHP file header
+
+Prepend all generated PHP files with a custom comment.
+
+Example:
+
+```yaml
+header: |
+ /*
+ * This file is part of the Ecommerce package.
+ *
+ * (c) Kévin Dunglas
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+```
+
+
+## Full configuration reference
+
+```yaml
+# RDFa files to use
+rdfa:
+
+ # Default:
+ - http://schema.org/docs/schema_org_rdfa.html
+
+# OWL relation files to use
+relations:
+
+ # Default:
+ - http://purl.org/goodrelations/v1.owl
+
+# Debug mode
+debug: false
+
+# Automatically add an id field to entities
+generateId: true
+
+# Generate interfaces and use Doctrine's Resolve Target Entity feature
+useInterface: false
+
+# Emit a warning if a property is not derived from GoodRelations
+checkIsGoodRelations: false
+
+# A license or any text to use as header of generated files
+header: false # Example: // (c) Kévin Dunglas
+
+# PHP namespaces
+namespaces:
+
+ # The namespace of the generated entities
+ entity: SchemaOrg\Entity # Example: Acme\Entity
+
+ # The namespace of the generated enumerations
+ enum: SchemaOrg\Enum # Example: Acme\Enum
+
+ # The namespace of the generated interfaces
+ interface: SchemaOrg\Model # Example: Acme\Model
+
+# Doctrine
+doctrine:
+
+ # Use Doctrine's ArrayCollection instead of standard arrays
+ useCollection: true
+
+ # The Resolve Target Entity Listener config file pass
+ resolveTargetEntityConfigPath: null
+
+# The value of the phpDoc's @author annotation
+author: false # Example: Kévin Dunglas
+
+# Visibility of entities fields
+fieldVisibility: ~ # One of "private"; "protected"; "public"
+
+# Schema.org's types to use
+types:
+
+ # Prototype
+ id:
+
+ # Type namespaces
+ namespaces:
+
+ # The namespace for the generated class (override any other defined namespace)
+ class: null
+
+ # The namespace for the generated interface (override any other defined namespace)
+ interface: null
+ doctrine:
+
+ # The Doctrine inheritance mapping type (override the guessed one)
+ inheritanceMapping: null
+
+ # The parent class, set to false for a top level class
+ parent: null
+
+ # Properties of this type to use
+ properties:
+
+ # Prototype
+ id:
+
+ # The property range
+ range: null # Example: Offer
+ cardinality: ~ # One of "(0..1)"; "(0..*)"; "(1..1)"; "(1..*)"; "unknown"
+
+# Annotation generators to use
+annotationGenerators:
+
+ # Defaults:
+ - SchemaOrgModel\AnnotationGenerator\PhpDocAnnotationGenerator
+ - SchemaOrgModel\AnnotationGenerator\ConstraintAnnotationGenerator
+ - SchemaOrgModel\AnnotationGenerator\DoctrineOrmAnnotationGenerator
+```
\ No newline at end of file
diff --git a/schema-generator/getting-started.md b/schema-generator/getting-started.md
new file mode 100644
index 00000000000..797eab80252
--- /dev/null
+++ b/schema-generator/getting-started.md
@@ -0,0 +1,749 @@
+# Usage
+
+## Installation
+
+Use [Composer](http://getcomposer.org) to install the generator. In standalone mode:
+
+ composer create-project dunglas/php-schema
+
+Or directly as a development dependency of your project:
+
+ composer require --dev dunglas/php-schema
+
+If you want to create an API exposing Schema.org types, take a look at [API platform](https://api-platform.com),
+a all-in-one skeleton including PHP Schema and integrated with a ton of other useful packages allowing to generate JSON-LD
+REST API in minutes.
+
+## Model scaffolding
+
+Start by browsing [Schema.org](http://schema.org) and pick types applicable to your application. The website provides
+tons of schemas including (but not limited too) representations of people, organization, event, postal address, creative
+work and e-commerce structures.
+Then, write a simple YAML config file like the following (here we will generate a data model for an address book):
+
+`address-book.yml`:
+
+```yml
+rdfa:
+ - tests/data/schema.rdfa
+relations:
+ - tests/data/v1.owl
+# The PHP namespace of generated entities
+namespaces:
+ entity: "AddressBook\Entity"
+# The list of types and properties we want to use
+types:
+ # Parent class of Person
+ Thing:
+ properties:
+ name: ~
+ Person:
+ properties:
+ familyName: ~
+ givenName: ~
+ additionalName: ~
+ gender: ~
+ address: ~
+ birthDate: ~
+ telephone: ~
+ email: ~
+ url: ~
+ jobTitle: ~
+ PostalAddress:
+ # Disable the generation of the class hierarchy for this type
+ parent: false
+ properties:
+ # Force the type of the addressCountry property to text
+ addressCountry: { range: "Text" }
+ addressLocality: ~
+ addressRegion: ~
+ postOfficeBoxNumber: ~
+ postalCode: ~
+ streetAddress: ~
+```
+
+Run the generator with this config file as parameter:
+
+ bin/schema generate-types output-directory/ address-book.yml
+
+The following classes will be generated:
+
+`output-directory/AddressBook/Entity/Thing.php`:
+
+```php
+name = $name;
+
+ return $this;
+ }
+
+ /**
+ * Gets name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+}
+
+```
+
+`output-directory/AddressBook/Entity/Person.php`:
+
+```php
+id = $id;
+
+ return $this;
+ }
+
+ /**
+ * Gets id.
+ *
+ * @return integer
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * Sets additionalName.
+ *
+ * @param string $additionalName
+ * @return $this
+ */
+ public function setAdditionalName($additionalName)
+ {
+ $this->additionalName = $additionalName;
+
+ return $this;
+ }
+
+ /**
+ * Gets additionalName.
+ *
+ * @return string
+ */
+ public function getAdditionalName()
+ {
+ return $this->additionalName;
+ }
+
+ /**
+ * Sets address.
+ *
+ * @param PostalAddress $address
+ * @return $this
+ */
+ public function setAddress(PostalAddress $address)
+ {
+ $this->address = $address;
+
+ return $this;
+ }
+
+ /**
+ * Gets address.
+ *
+ * @return PostalAddress
+ */
+ public function getAddress()
+ {
+ return $this->address;
+ }
+
+ /**
+ * Sets birthDate.
+ *
+ * @param \DateTime $birthDate
+ * @return $this
+ */
+ public function setBirthDate(\DateTime $birthDate)
+ {
+ $this->birthDate = $birthDate;
+
+ return $this;
+ }
+
+ /**
+ * Gets birthDate.
+ *
+ * @return \DateTime
+ */
+ public function getBirthDate()
+ {
+ return $this->birthDate;
+ }
+
+ /**
+ * Sets email.
+ *
+ * @param string $email
+ * @return $this
+ */
+ public function setEmail($email)
+ {
+ $this->email = $email;
+
+ return $this;
+ }
+
+ /**
+ * Gets email.
+ *
+ * @return string
+ */
+ public function getEmail()
+ {
+ return $this->email;
+ }
+
+ /**
+ * Sets familyName.
+ *
+ * @param string $familyName
+ * @return $this
+ */
+ public function setFamilyName($familyName)
+ {
+ $this->familyName = $familyName;
+
+ return $this;
+ }
+
+ /**
+ * Gets familyName.
+ *
+ * @return string
+ */
+ public function getFamilyName()
+ {
+ return $this->familyName;
+ }
+
+ /**
+ * Sets gender.
+ *
+ * @param string $gender
+ * @return $this
+ */
+ public function setGender($gender)
+ {
+ $this->gender = $gender;
+
+ return $this;
+ }
+
+ /**
+ * Gets gender.
+ *
+ * @return string
+ */
+ public function getGender()
+ {
+ return $this->gender;
+ }
+
+ /**
+ * Sets givenName.
+ *
+ * @param string $givenName
+ * @return $this
+ */
+ public function setGivenName($givenName)
+ {
+ $this->givenName = $givenName;
+
+ return $this;
+ }
+
+ /**
+ * Gets givenName.
+ *
+ * @return string
+ */
+ public function getGivenName()
+ {
+ return $this->givenName;
+ }
+
+ /**
+ * Sets jobTitle.
+ *
+ * @param string $jobTitle
+ * @return $this
+ */
+ public function setJobTitle($jobTitle)
+ {
+ $this->jobTitle = $jobTitle;
+
+ return $this;
+ }
+
+ /**
+ * Gets jobTitle.
+ *
+ * @return string
+ */
+ public function getJobTitle()
+ {
+ return $this->jobTitle;
+ }
+
+ /**
+ * Sets telephone.
+ *
+ * @param string $telephone
+ * @return $this
+ */
+ public function setTelephone($telephone)
+ {
+ $this->telephone = $telephone;
+
+ return $this;
+ }
+
+ /**
+ * Gets telephone.
+ *
+ * @return string
+ */
+ public function getTelephone()
+ {
+ return $this->telephone;
+ }
+}
+
+```
+
+`output-directory/AddressBook/Entity/PostalAddress.php`:
+
+```php
+id = $id;
+
+ return $this;
+ }
+
+ /**
+ * Gets id.
+ *
+ * @return integer
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * Sets addressCountry.
+ *
+ * @param string $addressCountry
+ * @return $this
+ */
+ public function setAddressCountry($addressCountry)
+ {
+ $this->addressCountry = $addressCountry;
+
+ return $this;
+ }
+
+ /**
+ * Gets addressCountry.
+ *
+ * @return string
+ */
+ public function getAddressCountry()
+ {
+ return $this->addressCountry;
+ }
+
+ /**
+ * Sets addressLocality.
+ *
+ * @param string $addressLocality
+ * @return $this
+ */
+ public function setAddressLocality($addressLocality)
+ {
+ $this->addressLocality = $addressLocality;
+
+ return $this;
+ }
+
+ /**
+ * Gets addressLocality.
+ *
+ * @return string
+ */
+ public function getAddressLocality()
+ {
+ return $this->addressLocality;
+ }
+
+ /**
+ * Sets addressRegion.
+ *
+ * @param string $addressRegion
+ * @return $this
+ */
+ public function setAddressRegion($addressRegion)
+ {
+ $this->addressRegion = $addressRegion;
+
+ return $this;
+ }
+
+ /**
+ * Gets addressRegion.
+ *
+ * @return string
+ */
+ public function getAddressRegion()
+ {
+ return $this->addressRegion;
+ }
+
+ /**
+ * Sets postalCode.
+ *
+ * @param string $postalCode
+ * @return $this
+ */
+ public function setPostalCode($postalCode)
+ {
+ $this->postalCode = $postalCode;
+
+ return $this;
+ }
+
+ /**
+ * Gets postalCode.
+ *
+ * @return string
+ */
+ public function getPostalCode()
+ {
+ return $this->postalCode;
+ }
+
+ /**
+ * Sets postOfficeBoxNumber.
+ *
+ * @param string $postOfficeBoxNumber
+ * @return $this
+ */
+ public function setPostOfficeBoxNumber($postOfficeBoxNumber)
+ {
+ $this->postOfficeBoxNumber = $postOfficeBoxNumber;
+
+ return $this;
+ }
+
+ /**
+ * Gets postOfficeBoxNumber.
+ *
+ * @return string
+ */
+ public function getPostOfficeBoxNumber()
+ {
+ return $this->postOfficeBoxNumber;
+ }
+
+ /**
+ * Sets streetAddress.
+ *
+ * @param string $streetAddress
+ * @return $this
+ */
+ public function setStreetAddress($streetAddress)
+ {
+ $this->streetAddress = $streetAddress;
+
+ return $this;
+ }
+
+ /**
+ * Gets streetAddress.
+ *
+ * @return string
+ */
+ public function getStreetAddress()
+ {
+ return $this->streetAddress;
+ }
+}
+
+```
+
+Note that the generator take care of creating directories corresponding to the namespace structure.
+
+Without configuration file, the tool will build the entire Schema.org vocabulary. If no properties are specified for a given
+type, all its properties will be generated.
+
+The generator also support enumerations generation. For subclasses of [`Enumeration`](https://schema.org/Enumeration), the
+generator will automatically create a class extending the Enum type provided by [myclabs/php-enum](https://github.com/myclabs/php-enum).
+Don't forget to install this library in your project. Refer you to PHP Enum documentation to see how to use it. The Symfony
+validation annotation generator automatically takes care of enumerations to validate choices values.
+
+A config file generating an enum class:
+
+```yml
+types:
+ OfferItemCondition: ~ # The generator will automatically guess that OfferItemCondition is subclass of Enum
+```
+
+The associated PHP class:
+
+```php
+