{"action":"created","comment":{"url":"https://api.github.com/repos/api-platform/docs/pulls/comments/79981155","id":79981155,"diff_hunk":"@@ -1,608 +1,613 @@\n # Creating your First API with API Platform, in 5 Minutes\n \n-In this tutorial, we will create a typical blog application with API Platform.\n-And it will take us less than 5 minutes to create the API, promised!\n-\n-All sources created during this tutorial are available on GitHub:\n-\n-* the blog API : [demo](https://api-platform-demo-blog-api.herokuapp.com) (we recommend to browse it with [Postman](http://getpostman.com))\n-  / [sources](https://github.com/dunglas/blog-api)\n-* the Angular client: [demo](https://dunglas.github.io/blog-client/) / [sources](https://github.com/dunglas/blog-client)\n-\n-![Screenshot of the blog](images/blog-api-platform.png)\n-\n-To create the API-side of our project we will:\n-\n-* Bootstrap a fully featured and working data model including ORM mapping, validation rules and semantic metadata with the\n-  generator provided by API platform (of course you can also handcraft your data model or modify the generated one to fit\n-  your needs).\n-* Expose this data model trough a read/write (CRUD) web API having a ton of features out of the box: hypermedia, pagination,\n-  validation, filtering, sorting... ; supporting [JSON-LD](http://json-ld.org), [Schema.org](https://schema.org) and [Hydra](http://www.hydra-cg.com/)\n-  open standards and being easily extensible thanks to strong [OOP](https://en.wikipedia.org/wiki/Object-oriented_programming).\n-\n-In [a second part](angularjs.md), we will develop a tiny AngularJS webapp to illustrate how to create and consume data from the API. Keep\n-in mind that you can use your preferred client-side technology (tested and approved with Angular, React, Ionic, Swift but\n-can work with any language able to send HTTP requests).\n-\n-## Prerequisites\n-\n-PHP 7 must be installed to run API Platform. A built-in web server is shipped with the framework for the development environment.\n-\n-To follow this tutorial a database must be installed (but its not a strong dependency of the framework). We recommend MySQL\n-or MariaDB but other major DBMS are supported including PostgreSQL and SQLite.\n+[API Platform](https://api-platform.com) is the most efficient framework out there to create web APIs. It makes it\n+easy to create simple APIs with support of industry leading open standards while giving you the ability to create complex\n+features with style. To discover the basics, we will create an API to manage a bookshop.\n+\n+In a few minutes and just 2 steps, we will create a fully featured API:\n+\n+1. Install API Platform\n+2. Handcraft the API data model as *Plain Old PHP Objects*\n+\n+API Platform uses these model classes to expose a web API having a ton of built-in features:\n+\n+* creating, retrieving, updating and deleting (CRUD) resources\n+* data validation\n+* pagination\n+* filtering\n+* sorting\n+* content negotiation\n+* a nice UI and machine-readable documentations ([Swagger/OpenAPI](https://swagger.io), [Hydra](http://hydra-cg.com))\n+* hypermedia/[HATEOAS](https://en.wikipedia.org/wiki/HATEOAS) and content negotiation support ([JSON-LD](http://json-ld.org),\n+[HAL](http://blog.stateless.co/post/13296666138/json-linking-with-hal))\n+* authentication ([Basic HTTP](https://en.wikipedia.org/wiki/Basic_access_authentication), cookies as well as [JWT](https://jwt.io/)\n+  and [OAuth](https://oauth.net/) through extensions)\n+* [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)\n+* security (tested against [OWASP recommendations](https://www.owasp.org/index.php/REST_Security_Cheat_Sheet))\n+* HTTP caching\n+* and basically everything needed to build modern APIs.\n+\n+One more thing, before we start: API Platform is built on top of [the Symfony framework](https://symfony.com). API Platform\n+is compatible with most [Symfony bundles](https://symfony.com/blog/the-30-most-useful-symfony-bundles-and-making-them-even-better)\n+(plugins) and benefits from the numerous extensions points provided by this rock-solid foundation (events, DIC...).\n+Adding features like custom, service-oriented, API endpoints, JWT or OAuth authentication, HTTP caching, mail sending or\n+asynchronous jobs to your APIs is very straightforward.\n \n ## Installing the framework\n \n-Let's start our new blog API project. The easiest way to create a new project is to use [Composer](https://getcomposer.org/)\n-(you need to have it installed on your box):\n+API Platform is shipped with a entire [Docker](https://docker.com) setup that makes it easy to get a containerized development\n+environment up and running. This setup contains an image pre-configured with PHP 7, Apache and everything needed to run API\n+Platform and a MySQL image to host the database.\n \n-    composer create-project api-platform/api-platform:dev-master blog-api\n+*Alternatively to using Docker, API Platform can also be installed using [Composer](https://getcomposer.org/):\n+`composer create-project api-platform/api-platform bookshop-api`.*\n \n-Composer creates the skeleton of the new blog API then retrieve the framework and all its dependencies.\n+Start by [downloading the API Platform Standard Edition](https://api-platform.com/download) and extract the content of the\n+archive.\n+The resulting directory contains an empty API Platform project structure. You will add your own code and configuration inside\n+it.\n+Then, if you do not already have Docker on your computer, [it's the right time to install it](https://www.docker.com/products/overview#/install_the_platform).\n \n-At the end of the installation, you will be prompted for some configuration parameters including database credentials.\n-All configuration parameters can be changed later by editing the `app/config/parameters.yml` file.\n+Open a terminal, go inside the directory containing your project skeleton, and run the following command to start the Apache\n+and the MySQL servers using [Docker Compose](https://docs.docker.com/compose/):\n \n-API Platform is pre-configured to use the popular and powerful [Doctrine ORM](http://www.doctrine-project.org/projects/orm.html).\n-It's supported natively by all API Platform components. However the Doctrine ORM is fully optional: you can replace it\n-by your favorite ORM, no ORM at all and even no database.\n+    $ docker-compose up\n \n-The installer will also ask you for some configuration parameters:\n+The first time you start the containers, Docker downloads and builds images for you. It will take some time, but don't worry,\n+this operation is done only one time. Starting servers will then be lightning fast.\n \n-* `database_*`: database credentials (MySQL is configured by default, but other popular RDBMS are supported)\n-* `mailer_*`: mail server credentials (to send mails)\n-* `cors_allow_origin`: the URL of your default web client application to automatically set appropriate [CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing)\n-  headers, **set it to `http://locahost:9000` (the default URL of the built-in Grunt server of our AngularJS client) to follow this tutorial**\n-* `api_*`: a name and a description of the API that will be used in the generated documentation\n-* `secret`: a secret token (choose a long one) for cryptographic features\n+Project's files are automatically shared between your local host machine and the container thanks to a pre-configured [Docker\n+volume](https://docs.docker.com/engine/tutorials/dockervolumes/). It means that you can edit files of your project locally\n+using your preferred IDE or code editor, they will be transparently taken into account in the container.\n+Speaking about IDEs, our preferred software to develop API Platform apps is [PHPStorm](https://www.jetbrains.com/phpstorm/)\n+with its awesome [Symfony](https://confluence.jetbrains.com/display/PhpStorm/Getting+Started+-+Symfony+Development+using+PhpStorm)\n+and [PHP annotations](https://plugins.jetbrains.com/plugin/7320) plugins. Give them a try, you'll got auto-completion for\n+almost everything.\n \n-Finally, the installer will ask if you want to remove the existing VCS history, type `Y`.\n+Now, in another shell, install the PHP dependencies of the project:\n \n-Take a look at [the content of the generated directory](https://github.com/dunglas/blog-api). You maybe recognize a [Symfony\n-application directory structure](https://symfony.com/doc/current/quick_tour/the_architecture.html). It's fine and intended:\n-**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).\n+    $ docker-compose run web composer install --no-interaction\n \n-It means that with this default API Platform setup, you can:\n+The `web` container is where your project belongs. Prefixing a command by `docker-compose run web` allows to execute this\n+given command in the container. You may want [to create an alias](http://www.linfo.org/alias.html) to run easily commands\n+in the container. Here, we installed libraries required by the project using the `composer` tool included in the API Platform\n+image.\n \n-* [use thousands of exiting Symfony bundles](http://knpbundles.com)\n-* reuse all your Symfony skills and benefit of the high quality [Symfony documentation](https://symfony.com/doc/current/index.html)\n+The API Platform Standard Edition comes with a dummy entity for test purpose: `src/AppBundle/Entity/Foo.php`. We will remove\n+it later, but for now, create the related database table:\n \n-You can also use API Platform in any existing Symfony application.\n+    docker-compose run web bin/console doctrine:schema:create\n \n-While API Platform is perfectly integrated with Symfony, keep in mind that it's basically a set of standalone PHP components.\n-You can also use those components in raw PHP (without framework) as well as with other frameworks.\n+If you're used to the PHP ecosystem, you probably guessed that this test entity uses the industry leading [Doctrine ORM](http://www.doctrine-project.org/projects/orm.html)\n+library as persistence system.\n+API Platform is 100% independent of the persistence system and you can use the one(s) that best suit(s) your needs (like\n+a NoSQL database or a remote web service).\n+API Platform even supports using several persistence systems together in the same project.\n \n-The skeleton comes with a demonstration bookstore API. You can play with it by running the following commands:\n+However, Doctrine ORM is definitely the easiest way to persist and query data in an API Platform project thanks to a bridge\n+included in the Standard Edition. This Doctrine ORM bridge is optimized for performance and development convenience. Doctrine\n+ORM and its bridge support major RDBMS including MySQL, PostgreSQL, SQLite, SQL Server and MariaDB.\n \n-Create the database:\n+Open `http://localhost` with your favorite web browser:\n \n-    $ bin/console doctrine:database:create\n+![Swagger UI integration in API Platform](images/swagger-ui-1.png)\n \n-Create the database schema:\n+API Platform exposes a description of the API in the Swagger format. It also integrates Swagger UI, a nice interface rendering\n+the API documentation. Click on an operation to display its details. You can also send requests to the API directly from the UI.\n+Try to create a new *Foo* resource using the `POST` operation, then access it using the `GET` operation and, finally, delete\n+it by executing the `DELETE` operation.\n+If you access any API URL using a web browser, API Platform detects it (using the `Accept` HTTP header) and displays the\n+corresponding API request in the UI. Open `http://localhost/foos`:\n \n-    $ bin/console doctrine:schema:create\n+![Request detail in the UI](images/swagger-ui-2.png)\n \n-Run the built-in web server:\n+If you want to access the raw data, you have two alternatives:\n \n-    $ bin/console server:start\n+* Add the correct `Accept` header (or don't set any `Accept` header at all and API Platform will default to JSON-LD) - preferred\n+  when writing API clients\n+* Add the format format you want as the extension of the resource - for debug purpose only\n \n-You can open `http://localhost:8000` with you preferred REST client.\n-We recommend [Postman](https://www.getpostman.com), and you will see later that API Platform is nicely integrated with it.\n+For instance, go to `http://localhost/foos.jsonld` to retrieve the list of `Foo` resources in JSON-LD or `http://localhost/foos.json`\n+to retrieve data in raw JSON.\n \n-When you're done with the demo app and want to create your own API:\n+Of course, you can also use your favorite HTTP client to query the API. We strongly recommend to use [Postman](https://www.getpostman.com/).\n+It works perfectly well with API Platform, also has native Swagger support, allows to easily write functional tests and\n+has very good team collaboration features.\n \n-* empty the `app/config/schema.yml` file\n-* delete all PHP files in the `src/AppBundle/Entity/` directory\n+## Creating the model\n \n-## Generating the data model\n+API Platform is now 100% functional. Let's create our own data model.\n+Our bookshop API will start simple. It will be composed of a `Book` resource type and a `Review` one.\n \n-The first incredibly useful tool provided by API platform is [its data model generator](../schema-generator/index.md).\n+Books have an id, an ISBN number, a title, a description, an author, a publication date and are related to a list of reviews.\n+Reviews have an id, a rating (between 0 and 5), a body, an author, a publication date and are related to one book.\n \n-It is 100% independent of other components but fits well with them: you can use this generator to scaffold the data model\n-of any PHP application. But you can also expose any hand-crafted PHP data model with the API system.\n-**Those components are not coupled together.**\n+Let's describe this data model as a set of Plain Old PHP Objects (POPO) and map it to database's tables using annotations\n+provided by the Doctrine ORM:\n \n-To scaffold our blog data model we'll browse [Schema.org](https://schema.org) and find types matching our needs.\n-We're lucky, the [https://schema.org/BlogPosting](https://schema.org/BlogPosting) describes exactly the data model we want\n-for our blog. As you can see, there is a bunch of schemas available.\n \n-Report types you're interested in a YAML configuration file like in the following snippet: \n+```php\n+// src/AppBundle/Entity/Book.php\n \n-```yaml\n-# app/config/schema.yml\n- \n-types:                      # The list of type to generated (a PHP entity class by type will be generated)\n-    BlogPosting:\n-        parent: false           # It's a best practice to have entity without parents\n-        properties:             # The list of properties we want to use\n-            name: ~               # You can include properties from the current type and of all these parents\n-            articleBody: ~\n-            articleSection: ~\n-            headline: ~\n-            isFamilyFriendly: ~\n-            datePublished: ~\n-            author:\n-                range: Person       # You can specify relations, here we force the type of the property to Person\n-                cardinality: (*..0) # We also the cardinality of the relation\n-            kevinReview:          # You can also define custom properties, not available in Schema.org\n-                range: Text         # For custom properties, type must always be specified\n-                cardinality: (*..0) # Using the cardinality here (not a relation) allows to deal with the nullable option\n-    Person:                   # Person is a relation of the BlogPosting type (author property), relations will be automatically generated\n-        parent: false\n-        properties:\n-            familyName: ~         # We add some common properties defined by Schema.org\n-            givenName: ~\n-            description: ~\n-            birthDate: ~\n-            deathDate: ~\n+namespace AppBundle\\Entity;\n \n-namespaces:\n-    entity: AppBundle\\Entity # The default namespace for entities, following API Platform and Symfony best practices\n+use Doctrine\\ORM\\Mapping as ORM;\n \n-annotationGenerators: # Enabled generators\n-    - ApiPlatform\\SchemaGenerator\\AnnotationGenerator\\PhpDocAnnotationGenerator          # PHPDoc\n-    - ApiPlatform\\SchemaGenerator\\AnnotationGenerator\\DoctrineOrmAnnotationGenerator     # Doctrine ORM mapping\n-    - ApiPlatform\\SchemaGenerator\\AnnotationGenerator\\ConstraintAnnotationGenerator      # Symfony Validation Constraints\n-    - ApiPlatform\\SchemaGenerator\\AnnotationGenerator\\ApiPlatformCoreAnnotationGenerator # API Platform resource mapping\n+/**\n+ * A book.\n+ *\n+ * @ORM\\Entity\n+ */\n+class Book\n+{\n+    /**\n+     * @var string An UUID for this book.\n+     *\n+     * @ORM\\Id\n+     * @ORM\\GeneratedValue(strategy=\"UUID\")\n+     * @ORM\\Column(type=\"guid\")\n+     */\n+    private $id;\n+\n+    /**\n+     * @var string|null The ISBN number if this book (or null if doesn't have one).\n+     *\n+     * @ORM\\Column(nullable=true)\n+     */\n+    private $isbn;\n+\n+    /**\n+     * @var string The title of this book.\n+     *\n+     * @ORM\\Column\n+     */\n+    private $title;\n+\n+    /**\n+     * @var string The description of this book.\n+     *\n+     * @ORM\\Column(type=\"text\")\n+     */\n+    private $description;\n+\n+    /**\n+     * @var string The author of this book.\n+     *\n+     * @ORM\\Column\n+     */\n+    private $author;\n+\n+    /**\n+     * @var \\DateTimeInterface The publication date of this book.\n+     *\n+     * @ORM\\Column(type=\"datetime\")\n+     */\n+    private $publicationDate;\n+\n+    /**\n+     * @var Review[] Available reviews for this book.\n+     *\n+     * @ORM\\OneToMany(targetEntity=\"Review\", mappedBy=\"book\")\n+     */\n+    private $reviews;\n+}\n ```\n \n-The `types` key contains the list of classes we want to generate. Each class will be generated from the corresponding\n-Schema.org type.\n-\n-Report properties of the class you want to generate in the `properties` key of the type. Similarly PHP properties are\n-generated using properties coming from Schema.org. If the value of the `properties` key is null (`~`), all properties\n-of the Schema.org type will be generated.\n-As you can see with `kevinReview`, it's also possible to define custom properties.\n-\n-The schema generator is smart enough to guess types (`range` in the Schema.org terminology) and cardinalities of properties.\n-Use the `range` and `cardinality` keys if you want to override those values.\n-\n-The `namespaces` key contain the namespace where generates entities belong. The generator is also able to generate enums,\n-interfaces and abstract class. Here we use the default Symfony directory for entities.\n+```php\n+// src/AppBundle/Entity/Review.php\n \n-Finally, the `annotationGenerators` key contains the list of annotation generators we want to register. With those settings\n-it will generate the PHPDoc, Doctrine ORM mappings, Symfony Validation annotations and API Platform annotations.\n-The last one is only useful to generate Schema.org IRI when exposing the API instead of custom ones. If you don't want to\n-expose a Schema.org enabled API, you can remove this generator. You can also create you own annotation generators and register them in this configuration section.\n+namespace AppBundle\\Entity;\n \n-If you don't find types or properties matching your specific needs, it's not a big deal. You can create entity classes\n-by yourself (directly in PHP) and still benefit from the bunch of other API Platform features.\n-You can also pick some classes and properties from Schema.org then add more custom types to your model.\n+use Doctrine\\ORM\\Mapping as ORM;\n \n-It's time to run the model generator:\n-\n-    $ vendor/bin/schema generate-types src/ app/config/schema.yml\n-\n-Take a look at the content of the [src/AppBundle/Entity/](https://github.com/dunglas/blog-api/tree/master/src/AppBundle/Entity) directory.\n-We generated a set of Plain-Old-PHP entities representing our data model. As promised we generated:\n-\n-* A set of PHP entities with properties, constants (enum values), getters, setters, adders and removers. The class hierarchy\n-  provided by Schema.org is translated to a PHP class hierarchy with parents as `abstract` classes. The generated code\n-  complies with [PSR](http://www.php-fig.org/) coding standards.\n-* [Doctrine ORM mapping annotations](https://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html)\n-  including database columns with type guessing, relations with cardinality guessing and class inheritance (through the\n-  `@AbstractSuperclass` annotation).\n-* [Symfony validation annotations](https://symfony.com/doc/current/book/validation.html#constraints)\n-* API Platform `@ApiResource` and `@ApiProperty` annotations\n-* Full high-quality [PHPDoc](https://www.phpdoc.org) for classes, properties, constants and methods extracted from Schema.org\n-\n-The data model is fully functional. You can hack it (modify entities, properties, indexes, validation rules...), or use it\n-as is!\n+/**\n+ * A review of a book.\n+ *\n+ * @ORM\\Entity\n+ */\n+class Review\n+{\n+    /**\n+     * @var string The UUID of this review.\n+     *\n+     * @ORM\\Id\n+     * @ORM\\GeneratedValue(strategy=\"UUID\")\n+     * @ORM\\Column(type=\"guid\")\n+     */\n+    private $id;\n+\n+    /**\n+     * @var int The rating of this review (between 0 and 5).\n+     *\n+     * @ORM\\Column(type=\"smallint\")\n+     */\n+    private $rating;\n+\n+    /**\n+     * @var string the body of the review.\n+     *\n+     * @ORM\\Column(type=\"text\")\n+     */\n+    private $body;\n+\n+    /**\n+     * @var string The author of the review.\n+     *\n+     * @ORM\\Column\n+     */\n+    private $author;\n+\n+    /**\n+     * @var \\DateTimeInterface The date of publication of this review.\n+     *\n+     * @ORM\\Column(type=\"datetime\")\n+     */\n+    private $publicationDate;\n+\n+    /**\n+     * @var Book The book this review is about.\n+     *\n+     * @ORM\\ManyToOne(targetEntity=\"Book\", inversedBy=\"reviews\")\n+     */\n+    private $book;\n+}\n+```\n \n-Reusing an existing semantic schema like we've done has many advantages:\n+As you can see there are just 2 typical PHP objects with the corresponding PHPDoc (you will see that the PHPDoc of entities\n+and properties will appear in the API documentation).\n \n-**Don't Reinvent The Wheel**\n+Doctrine's annotations map these entities to tables in the MySQL database. Annotations are convenient as\n+they allow grouping the code and the configuration but, if you want to decouple classes from their metadata, you can switch\n+to XML or YAML mappings. They are supported as well.\n \n-Data models provided by Schema.org are popular and have been proved efficient. They cover a broad spectrum of topics including\n-creative work, e-commerce, event, medicine, social networking, people, postal address, organization, place or review. Schema.org\n-has its root in [a ton of preexisting well designed vocabularies](http://schema.rdfs.org/mappings.html) and is successfully\n-used by more and more website and applications.\n+Learn more about how to map entities with the Doctrine ORM in [the project's official documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html)\n+or in Kévin's book \"[Persistence in PHP with the Doctrine ORM](https://www.amazon.fr/gp/product/B00HEGSKYQ/ref=as_li_tl?ie=UTF8&camp=1642&creative=6746&creativeASIN=B00HEGSKYQ&linkCode=as2&tag=kevidung-21)\".\n \n-Pick up schemas applicable to your application, generate your PHP model, then customize and specialize it to fit your needs.\n+As we used private properties (but API Platform as well as Doctrine can also work with public ones), we need to create the\n+corresponding accessor methods. Run the following command or use the code generation feature of your IDE to generate them:\n \n-**Improve SEO and user experience**\n+    $ docker-compose run web bin/console doctrine:generate:entities AppBundle\n \n-Adding Schema.org markup to websites and apps increase their ranking in search engines results and enable awesome features\n-such as [Google Rich Snippets](https://support.google.com/webmasters/answer/99170?hl=en) and [Gmail markup](https://developers.google.com/gmail/markup/overview).\n+Then, delete the file `src/AppBundle/Entity/Foo.php`, this demo entity isn't useful anymore.\n+Finally, tell Doctrine to sync the database's tables structure with our new data model:\n \n-Mapping your app data model to Schema.org structures can be a tedious task. Using the generator, your data model will be\n-a derived from Schema.org. Serializing your data as JSON-LD will not require specific mapping nor adaptation. It's a matter\n-of minutes.\n+    $ docker-compose run web bin/console doctrine:schema:update --force\n \n-**Ready for the future**\n+We now have a working data model that you can persist and query. To create an API endpoint with CRUD capabilities corresponding\n+to an entity class, we just have to mark it with an annotation called `@ApiResource`:\n \n-Schema.org improves the interoperability of your applications. Used with hypermedia technologies such as [Hydra](http://www.hydra-cg.com/)\n-it's a big step towards the semantic and machine readable web. It opens the way to generic web API clients able to extract\n-and process data from any website or app using such technologies.\n+```php\n+// src/AppBundle/Entity/Book.php\n \n-Ask Doctrine to create the database of the project:\n+namespace AppBundle\\Entity;\n \n-    $ bin/console doctrine:database:drop --force # Just in case you created the DB to play with the bookstore app, be careful with this command it deletes data permanently\n-    $ bin/console doctrine:database:create\n+use ApiPlatform\\Core\\Annotation\\ApiResource;\n+use Doctrine\\ORM\\Mapping as ORM;\n \n-Then generate database tables related to the generated entities:\n+/**\n+ * A book.\n+ *\n+ * @ApiResource\n+ * @ORM\\Entity\n+ */\n+class Book\n+{\n+    // ...\n+}\n+```\n \n-    $ bin/console doctrine:schema:create\n+```php\n+// src/AppBundle/Entity/Entity.php\n \n-The schema generator provides a lot of configuration options. Take a look at [its dedicated documentation](../schema-generator/index.md).\n-Keep in mind that it is also available as a standalone tool (PHAR) and can be used to bootstrap any PHP project (works fine\n-with raw PHP, API Platform and Symfony but also has an extension mechanism allowing to use it with other technologies.\n+namespace AppBundle\\Entity;\n \n-You can always create your very own data model from scratch. It's perfectly OK and you can still use API Platform without\n-the generator.\n+use ApiPlatform\\Core\\Annotation\\ApiResource;\n+use Doctrine\\ORM\\Mapping as ORM;\n \n-Sometimes we will have to make a data model with very specific business types, not available in Schema.org. Sometimes we\n-will find Schema.org types that partially matches what we want but needs to be adapted.\n+/**\n+ * A review of a book.\n+ *\n+ * @ApiResource\n+ * @ORM\\Entity\n+ */\n+class Review\n+{\n+    // ...\n+}\n+```\n \n-Anyway, the schema generator is a tool intended **to bootstrap** the data model. You can and **you will** edit manually\n-generated PHP entities. When you start to edit manually the generated files, be careful to not run the generator again,\n-it will overwrite your changes (this behavior will be enhanced in future versions). When you do such things, the best to\n-do is to remove `api-platform/schema-generator` from your `composer.json` file.\n+**Our API is (almost) ready!**\n+Browse `http://localhost/app_dev.php` to load the development environment (including the awesome [Symfony profiler](https://symfony.com/blog/new-in-symfony-2-8-redesigned-profiler)).\n \n-## Exposing the API\n+Operations available for our 2 resources types appear in the UI.\n \n-We have a working data model backed by a database. But we also got a working hypermedia REST API thanks to **[API Platform Core](../core/index.md)**.\n+Click on the `POST` operation of the `Book` resource type and send the following JSON document as request body:\n \n-The core, like the schema generator, is already pre-installed and properly configured.\n-We just need to mark resources we want to expose with an `@ApiResource` annotation. Open any of the generated entities, and\n-you'll see that the schema generator already added this annotation for us.\n+```json\n+{\n+  \"isbn\": \"9781782164104\",\n+  \"title\": \"Persistence in PHP with the Doctrine ORM\",\n+  \"description\": \"This book is designed for PHP developers and architects who want to modernize their skills through better understanding of Persistence and ORM.\",\n+  \"author\": \"Kévin Dunglas\",\n+  \"publicationDate\": \"2013-12-01\"\n+}\n+```\n \n-And our API is already finished! How would it be easier?\n+You just saved a new book resource through the bookshop API! API Platform automatically transforms the JSON document to\n+an instance of the corresponding PHP entity class and uses Doctrine ORM to persist it in the database.\n \n-When you create new entities, you need to clear the cache:\n+By default, the API supports `GET` (retrieve, on collections and items), `POST` (create), `PUT` (update) and `DELETE` (self-explaining)\n+HTTP methods. You are not limited to the built-in operations. You can [add new custom operations](../core/operations.md#enabling-and-disabling-operations)\n+(`PATCH` operations, sub-resources...) or [disable the ones you don't want](../core/operations.md#creating-custom-operations-and-controllers).\n \n-    $ bin/console cache:clear\n+Try the `GET` operation on the collection. The book we added appears. When the collection will contain more than 30 items,\n+the pagination will automatically show up, [and this is entirely configurable](../core/pagination.md). You may be interested\n+in [adding some filters and in sorting the collection](../core/filters.md) as well.\n \n-Start the integrated development web server:\n+Maybe did you notice that some keys start by the `@` symbol in the generated JSON response (`@id`, `@type`, `@context`...)?\n+API Platform comes with a full support of the [JSON-LD](http://json-ld.org/) format (and its [Hydra](http://www.hydra-cg.com/)\n+extension). It allows to build smart clients, with auto-discoverability capabilities (take a look at [Hydra console](http://www.markus-lanthaler.com/hydra/console/))\n+and is very useful for open data, SEO and interoperability when [used with open vocabularies such as Schema.org](http://blog.schema.org/2013/06/schemaorg-and-json-ld.html).\n+JSON-LD enables a lot of awesome advanced features (like [giving access to Google to your structured data](https://developers.google.com/search/docs/guides/intro-structured-data)\n+or consuming APIs with [Apache Jena](https://jena.apache.org/documentation/io/#formats)).\n+Wwe think that it's the best default for a new API. However, API Platform natively [supports many other formats](../core/content-negotiation.md)\n+including [HAL](http://stateless.co/hal_specification.html), raw [JSON](http://www.json.org/), [XML](https://www.w3.org/XML/)\n+(experimental) and even [YAML](http://yaml.org/) and [CSV](https://en.wikipedia.org/wiki/Comma-separated_values) (if you\n+use Symfony 3.2+).\n+It's up to you to choose which format to enable and to use by default. You can also easily [add support for other formats](../core/content-negotiation.md)\n+if you need to.\n \n-    $ bin/console server:start\n+Now, add a review for this book using the `POST` operation for the `Review` resource:\n \n-Then open `http://localhost:8000/doc` with a web browser:\n+```json\n+{\n+    \"book\": \"/books/<THE-UUID-OF-THE-BOOK>\"\n+    \"rating\": 5,\n+    \"body\": \"Interesting book!\",\n+    \"author\": \"Kévin\",\n+    \"publicationDate\": \"September 21, 2016\",\n+}\n+```\n \n-![API doc](images/api-doc.png)]\n+Be sure to replace `<THE-UUID-OF-THE-BOOK>` by... The UUID of the book we previously created.\n \n-Thanks to [NelmioApiDocBundle](https://github.com/nelmio/NelmioApiDocBundle) support of ApiBundle and its integration\n-with API Platform, you get for a free **an automatically generated human-readable documentation** of the API (Swagger-like).\n-The doc also **includes a sandbox** to try the API.\n+There is 2 interesting things to mention about this request:","path":"tutorial/api.md","position":572,"original_position":572,"commit_id":"0a048edcf7ee0b07b75019cbdb515493b13370f7","original_commit_id":"0a048edcf7ee0b07b75019cbdb515493b13370f7","user":{"login":"soyuka","id":1321971,"avatar_url":"https://avatars.githubusercontent.com/u/1321971?v=3","gravatar_id":"","url":"https://api.github.com/users/soyuka","html_url":"https://github.com/soyuka","followers_url":"https://api.github.com/users/soyuka/followers","following_url":"https://api.github.com/users/soyuka/following{/other_user}","gists_url":"https://api.github.com/users/soyuka/gists{/gist_id}","starred_url":"https://api.github.com/users/soyuka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/soyuka/subscriptions","organizations_url":"https://api.github.com/users/soyuka/orgs","repos_url":"https://api.github.com/users/soyuka/repos","events_url":"https://api.github.com/users/soyuka/events{/privacy}","received_events_url":"https://api.github.com/users/soyuka/received_events","type":"User","site_admin":false},"body":"There are","created_at":"2016-09-22T06:25:18Z","updated_at":"2016-09-22T06:35:11Z","html_url":"https://github.com/api-platform/docs/pull/109#discussion_r79981155","pull_request_url":"https://api.github.com/repos/api-platform/docs/pulls/109","_links":{"self":{"href":"https://api.github.com/repos/api-platform/docs/pulls/comments/79981155"},"html":{"href":"https://github.com/api-platform/docs/pull/109#discussion_r79981155"},"pull_request":{"href":"https://api.github.com/repos/api-platform/docs/pulls/109"}}},"pull_request":{"url":"https://api.github.com/repos/api-platform/docs/pulls/109","id":85815993,"html_url":"https://github.com/api-platform/docs/pull/109","diff_url":"https://github.com/api-platform/docs/pull/109.diff","patch_url":"https://github.com/api-platform/docs/pull/109.patch","issue_url":"https://api.github.com/repos/api-platform/docs/issues/109","number":109,"state":"open","locked":false,"title":"[WIP] New getting started","user":{"login":"dunglas","id":57224,"avatar_url":"https://avatars.githubusercontent.com/u/57224?v=3","gravatar_id":"","url":"https://api.github.com/users/dunglas","html_url":"https://github.com/dunglas","followers_url":"https://api.github.com/users/dunglas/followers","following_url":"https://api.github.com/users/dunglas/following{/other_user}","gists_url":"https://api.github.com/users/dunglas/gists{/gist_id}","starred_url":"https://api.github.com/users/dunglas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dunglas/subscriptions","organizations_url":"https://api.github.com/users/dunglas/orgs","repos_url":"https://api.github.com/users/dunglas/repos","events_url":"https://api.github.com/users/dunglas/events{/privacy}","received_events_url":"https://api.github.com/users/dunglas/received_events","type":"User","site_admin":false},"body":"WIP","created_at":"2016-09-19T15:31:47Z","updated_at":"2016-09-22T06:35:11Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d4a3359a1f91813c9989b59076915eaa0ee191ea","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/api-platform/docs/pulls/109/commits","review_comments_url":"https://api.github.com/repos/api-platform/docs/pulls/109/comments","review_comment_url":"https://api.github.com/repos/api-platform/docs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/api-platform/docs/issues/109/comments","statuses_url":"https://api.github.com/repos/api-platform/docs/statuses/0a048edcf7ee0b07b75019cbdb515493b13370f7","head":{"label":"dunglas:new_getting_started","ref":"new_getting_started","sha":"0a048edcf7ee0b07b75019cbdb515493b13370f7","user":{"login":"dunglas","id":57224,"avatar_url":"https://avatars.githubusercontent.com/u/57224?v=3","gravatar_id":"","url":"https://api.github.com/users/dunglas","html_url":"https://github.com/dunglas","followers_url":"https://api.github.com/users/dunglas/followers","following_url":"https://api.github.com/users/dunglas/following{/other_user}","gists_url":"https://api.github.com/users/dunglas/gists{/gist_id}","starred_url":"https://api.github.com/users/dunglas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dunglas/subscriptions","organizations_url":"https://api.github.com/users/dunglas/orgs","repos_url":"https://api.github.com/users/dunglas/repos","events_url":"https://api.github.com/users/dunglas/events{/privacy}","received_events_url":"https://api.github.com/users/dunglas/received_events","type":"User","site_admin":false},"repo":{"id":53446876,"name":"doc","full_name":"dunglas/doc","owner":{"login":"dunglas","id":57224,"avatar_url":"https://avatars.githubusercontent.com/u/57224?v=3","gravatar_id":"","url":"https://api.github.com/users/dunglas","html_url":"https://github.com/dunglas","followers_url":"https://api.github.com/users/dunglas/followers","following_url":"https://api.github.com/users/dunglas/following{/other_user}","gists_url":"https://api.github.com/users/dunglas/gists{/gist_id}","starred_url":"https://api.github.com/users/dunglas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dunglas/subscriptions","organizations_url":"https://api.github.com/users/dunglas/orgs","repos_url":"https://api.github.com/users/dunglas/repos","events_url":"https://api.github.com/users/dunglas/events{/privacy}","received_events_url":"https://api.github.com/users/dunglas/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/dunglas/doc","description":"API Platform documentation","fork":true,"url":"https://api.github.com/repos/dunglas/doc","forks_url":"https://api.github.com/repos/dunglas/doc/forks","keys_url":"https://api.github.com/repos/dunglas/doc/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dunglas/doc/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dunglas/doc/teams","hooks_url":"https://api.github.com/repos/dunglas/doc/hooks","issue_events_url":"https://api.github.com/repos/dunglas/doc/issues/events{/number}","events_url":"https://api.github.com/repos/dunglas/doc/events","assignees_url":"https://api.github.com/repos/dunglas/doc/assignees{/user}","branches_url":"https://api.github.com/repos/dunglas/doc/branches{/branch}","tags_url":"https://api.github.com/repos/dunglas/doc/tags","blobs_url":"https://api.github.com/repos/dunglas/doc/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dunglas/doc/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dunglas/doc/git/refs{/sha}","trees_url":"https://api.github.com/repos/dunglas/doc/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dunglas/doc/statuses/{sha}","languages_url":"https://api.github.com/repos/dunglas/doc/languages","stargazers_url":"https://api.github.com/repos/dunglas/doc/stargazers","contributors_url":"https://api.github.com/repos/dunglas/doc/contributors","subscribers_url":"https://api.github.com/repos/dunglas/doc/subscribers","subscription_url":"https://api.github.com/repos/dunglas/doc/subscription","commits_url":"https://api.github.com/repos/dunglas/doc/commits{/sha}","git_commits_url":"https://api.github.com/repos/dunglas/doc/git/commits{/sha}","comments_url":"https://api.github.com/repos/dunglas/doc/comments{/number}","issue_comment_url":"https://api.github.com/repos/dunglas/doc/issues/comments{/number}","contents_url":"https://api.github.com/repos/dunglas/doc/contents/{+path}","compare_url":"https://api.github.com/repos/dunglas/doc/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dunglas/doc/merges","archive_url":"https://api.github.com/repos/dunglas/doc/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dunglas/doc/downloads","issues_url":"https://api.github.com/repos/dunglas/doc/issues{/number}","pulls_url":"https://api.github.com/repos/dunglas/doc/pulls{/number}","milestones_url":"https://api.github.com/repos/dunglas/doc/milestones{/number}","notifications_url":"https://api.github.com/repos/dunglas/doc/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dunglas/doc/labels{/name}","releases_url":"https://api.github.com/repos/dunglas/doc/releases{/id}","deployments_url":"https://api.github.com/repos/dunglas/doc/deployments","created_at":"2016-03-08T21:35:56Z","updated_at":"2016-09-06T12:26:16Z","pushed_at":"2016-09-21T20:49:22Z","git_url":"git://github.com/dunglas/doc.git","ssh_url":"git@github.com:dunglas/doc.git","clone_url":"https://github.com/dunglas/doc.git","svn_url":"https://github.com/dunglas/doc","homepage":"https://api-platform.com/doc/","size":1213,"stargazers_count":1,"watchers_count":1,"language":null,"has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":3,"forks":1,"open_issues":3,"watchers":1,"default_branch":"1.0"}},"base":{"label":"api-platform:master","ref":"master","sha":"20b576b60d23fc13734f4b7d4fa04b1fc607e474","user":{"login":"api-platform","id":13420081,"avatar_url":"https://avatars.githubusercontent.com/u/13420081?v=3","gravatar_id":"","url":"https://api.github.com/users/api-platform","html_url":"https://github.com/api-platform","followers_url":"https://api.github.com/users/api-platform/followers","following_url":"https://api.github.com/users/api-platform/following{/other_user}","gists_url":"https://api.github.com/users/api-platform/gists{/gist_id}","starred_url":"https://api.github.com/users/api-platform/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/api-platform/subscriptions","organizations_url":"https://api.github.com/users/api-platform/orgs","repos_url":"https://api.github.com/users/api-platform/repos","events_url":"https://api.github.com/users/api-platform/events{/privacy}","received_events_url":"https://api.github.com/users/api-platform/received_events","type":"Organization","site_admin":false},"repo":{"id":38251565,"name":"docs","full_name":"api-platform/docs","owner":{"login":"api-platform","id":13420081,"avatar_url":"https://avatars.githubusercontent.com/u/13420081?v=3","gravatar_id":"","url":"https://api.github.com/users/api-platform","html_url":"https://github.com/api-platform","followers_url":"https://api.github.com/users/api-platform/followers","following_url":"https://api.github.com/users/api-platform/following{/other_user}","gists_url":"https://api.github.com/users/api-platform/gists{/gist_id}","starred_url":"https://api.github.com/users/api-platform/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/api-platform/subscriptions","organizations_url":"https://api.github.com/users/api-platform/orgs","repos_url":"https://api.github.com/users/api-platform/repos","events_url":"https://api.github.com/users/api-platform/events{/privacy}","received_events_url":"https://api.github.com/users/api-platform/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/api-platform/docs","description":"API Platform documentation","fork":false,"url":"https://api.github.com/repos/api-platform/docs","forks_url":"https://api.github.com/repos/api-platform/docs/forks","keys_url":"https://api.github.com/repos/api-platform/docs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/api-platform/docs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/api-platform/docs/teams","hooks_url":"https://api.github.com/repos/api-platform/docs/hooks","issue_events_url":"https://api.github.com/repos/api-platform/docs/issues/events{/number}","events_url":"https://api.github.com/repos/api-platform/docs/events","assignees_url":"https://api.github.com/repos/api-platform/docs/assignees{/user}","branches_url":"https://api.github.com/repos/api-platform/docs/branches{/branch}","tags_url":"https://api.github.com/repos/api-platform/docs/tags","blobs_url":"https://api.github.com/repos/api-platform/docs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/api-platform/docs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/api-platform/docs/git/refs{/sha}","trees_url":"https://api.github.com/repos/api-platform/docs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/api-platform/docs/statuses/{sha}","languages_url":"https://api.github.com/repos/api-platform/docs/languages","stargazers_url":"https://api.github.com/repos/api-platform/docs/stargazers","contributors_url":"https://api.github.com/repos/api-platform/docs/contributors","subscribers_url":"https://api.github.com/repos/api-platform/docs/subscribers","subscription_url":"https://api.github.com/repos/api-platform/docs/subscription","commits_url":"https://api.github.com/repos/api-platform/docs/commits{/sha}","git_commits_url":"https://api.github.com/repos/api-platform/docs/git/commits{/sha}","comments_url":"https://api.github.com/repos/api-platform/docs/comments{/number}","issue_comment_url":"https://api.github.com/repos/api-platform/docs/issues/comments{/number}","contents_url":"https://api.github.com/repos/api-platform/docs/contents/{+path}","compare_url":"https://api.github.com/repos/api-platform/docs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/api-platform/docs/merges","archive_url":"https://api.github.com/repos/api-platform/docs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/api-platform/docs/downloads","issues_url":"https://api.github.com/repos/api-platform/docs/issues{/number}","pulls_url":"https://api.github.com/repos/api-platform/docs/pulls{/number}","milestones_url":"https://api.github.com/repos/api-platform/docs/milestones{/number}","notifications_url":"https://api.github.com/repos/api-platform/docs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/api-platform/docs/labels{/name}","releases_url":"https://api.github.com/repos/api-platform/docs/releases{/id}","deployments_url":"https://api.github.com/repos/api-platform/docs/deployments","created_at":"2015-06-29T14:19:34Z","updated_at":"2016-09-05T15:27:37Z","pushed_at":"2016-09-21T20:49:22Z","git_url":"git://github.com/api-platform/docs.git","ssh_url":"git@github.com:api-platform/docs.git","clone_url":"https://github.com/api-platform/docs.git","svn_url":"https://github.com/api-platform/docs","homepage":"https://api-platform.com/doc/","size":1217,"stargazers_count":21,"watchers_count":21,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":37,"mirror_url":null,"open_issues_count":8,"forks":37,"open_issues":8,"watchers":21,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/api-platform/docs/pulls/109"},"html":{"href":"https://github.com/api-platform/docs/pull/109"},"issue":{"href":"https://api.github.com/repos/api-platform/docs/issues/109"},"comments":{"href":"https://api.github.com/repos/api-platform/docs/issues/109/comments"},"review_comments":{"href":"https://api.github.com/repos/api-platform/docs/pulls/109/comments"},"review_comment":{"href":"https://api.github.com/repos/api-platform/docs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/api-platform/docs/pulls/109/commits"},"statuses":{"href":"https://api.github.com/repos/api-platform/docs/statuses/0a048edcf7ee0b07b75019cbdb515493b13370f7"}}}}
{"actor":{"display_login":"soyuka"}}