├── admin ├── images │ └── admin-demo.gif ├── schema.org.md ├── index.md ├── authentication-support.md ├── getting-started.md ├── handling-relations-to-collections.md └── customizing.md ├── core ├── images │ ├── JWTAuthorizeButton.png │ ├── JWTConfigureApiKey.png │ ├── NelmioApiDocBundle.png │ ├── SerializerWorkflow.png │ ├── deprecated-graphiql.png │ ├── mercure-discovery.png │ ├── deprecated-swagger-ui.png │ ├── mercure-subscriptions.png │ └── diagrams │ │ ├── api-platform-get-i-o.dia │ │ ├── api-platform-get-i-o.png │ │ ├── api-platform-put-i-o.dia │ │ ├── api-platform-put-i-o.png │ │ ├── api-platform-post-i-o.dia │ │ └── api-platform-post-i-o.png ├── nelmio-api-doc.md ├── json-schema.md ├── default-order.md ├── push-relations.md ├── index.md ├── operation-path-naming.md ├── external-vocabularies.md ├── data-persisters.md ├── angularjs-integration.md ├── design.md ├── extending-jsonld-context.md ├── form-data.md ├── identifiers.md ├── errors.md ├── fosuser-bundle.md ├── testing.md ├── mercure.md ├── deprecations.md ├── extending.md ├── mongodb.md ├── messenger.md ├── elasticsearch.md ├── extensions.md ├── subresources.md ├── configuration.md └── events.md ├── distribution ├── images │ ├── swagger-ui-1.png │ ├── swagger-ui-2.png │ ├── api-platform-2.5-api.png │ ├── symfonycasts-player.png │ ├── api-platform-2.5-admin.png │ ├── api-platform-2.5-graphql.png │ ├── api-platform-2.5-pwa-react.png │ ├── api-platform-2.5-welcome.png │ └── api-platform-2.5-bookshop-api.png └── debugging.md ├── .github ├── ISSUE_TEMPLATE │ ├── 2_Documentation_issue.md │ └── 1_Support_question.md └── PULL_REQUEST_TEMPLATE.md ├── client-generator ├── images │ ├── client-generator-demo.gif │ ├── react │ │ ├── client-generator-react-edit.png │ │ ├── client-generator-react-list.png │ │ ├── client-generator-react-show.png │ │ ├── client-generator-react-delete.png │ │ └── client-generator-react-list-pagination.png │ ├── nextjs │ │ ├── client-generator-nextjs-list.png │ │ └── client-generator-nextjs-show.png │ └── react-native │ │ ├── client-generator-react-native-add.png │ │ ├── client-generator-react-native-list.png │ │ ├── client-generator-react-native-show.png │ │ └── client-generator-react-native-delete.png ├── typescript.md ├── nextjs.md ├── index.md ├── quasar.md ├── vuejs.md ├── troubleshooting.md ├── react-native.md ├── react.md └── vuetify.md ├── .proselintrc ├── extra ├── contribution-guides.md ├── releases.md ├── conduct.md ├── philosophy.md └── troubleshooting.md ├── .editorconfig ├── deployment ├── index.md ├── heroku.md ├── kubernetes.md └── docker-compose.md ├── .travis.yml ├── outline.yaml └── schema-generator └── index.md /admin/images/admin-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/admin/images/admin-demo.gif -------------------------------------------------------------------------------- /core/images/JWTAuthorizeButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/JWTAuthorizeButton.png -------------------------------------------------------------------------------- /core/images/JWTConfigureApiKey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/JWTConfigureApiKey.png -------------------------------------------------------------------------------- /core/images/NelmioApiDocBundle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/NelmioApiDocBundle.png -------------------------------------------------------------------------------- /core/images/SerializerWorkflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/SerializerWorkflow.png -------------------------------------------------------------------------------- /core/images/deprecated-graphiql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/deprecated-graphiql.png -------------------------------------------------------------------------------- /core/images/mercure-discovery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/mercure-discovery.png -------------------------------------------------------------------------------- /core/images/deprecated-swagger-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/deprecated-swagger-ui.png -------------------------------------------------------------------------------- /core/images/mercure-subscriptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/mercure-subscriptions.png -------------------------------------------------------------------------------- /distribution/images/swagger-ui-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/distribution/images/swagger-ui-1.png -------------------------------------------------------------------------------- /distribution/images/swagger-ui-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/distribution/images/swagger-ui-2.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2_Documentation_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 📄 Documentation issue 3 | about: Report a documentation issue 4 | 5 | --- 6 | -------------------------------------------------------------------------------- /core/images/diagrams/api-platform-get-i-o.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/diagrams/api-platform-get-i-o.dia -------------------------------------------------------------------------------- /core/images/diagrams/api-platform-get-i-o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/diagrams/api-platform-get-i-o.png -------------------------------------------------------------------------------- /core/images/diagrams/api-platform-put-i-o.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/diagrams/api-platform-put-i-o.dia -------------------------------------------------------------------------------- /core/images/diagrams/api-platform-put-i-o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/diagrams/api-platform-put-i-o.png -------------------------------------------------------------------------------- /distribution/images/api-platform-2.5-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/distribution/images/api-platform-2.5-api.png -------------------------------------------------------------------------------- /distribution/images/symfonycasts-player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/distribution/images/symfonycasts-player.png -------------------------------------------------------------------------------- /core/images/diagrams/api-platform-post-i-o.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/diagrams/api-platform-post-i-o.dia -------------------------------------------------------------------------------- /core/images/diagrams/api-platform-post-i-o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/core/images/diagrams/api-platform-post-i-o.png -------------------------------------------------------------------------------- /distribution/images/api-platform-2.5-admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/distribution/images/api-platform-2.5-admin.png -------------------------------------------------------------------------------- /client-generator/images/client-generator-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/client-generator/images/client-generator-demo.gif -------------------------------------------------------------------------------- /distribution/images/api-platform-2.5-graphql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/distribution/images/api-platform-2.5-graphql.png -------------------------------------------------------------------------------- /distribution/images/api-platform-2.5-pwa-react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/distribution/images/api-platform-2.5-pwa-react.png -------------------------------------------------------------------------------- /distribution/images/api-platform-2.5-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/distribution/images/api-platform-2.5-welcome.png -------------------------------------------------------------------------------- /distribution/images/api-platform-2.5-bookshop-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/distribution/images/api-platform-2.5-bookshop-api.png -------------------------------------------------------------------------------- /client-generator/images/react/client-generator-react-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/client-generator/images/react/client-generator-react-edit.png -------------------------------------------------------------------------------- /client-generator/images/react/client-generator-react-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/client-generator/images/react/client-generator-react-list.png -------------------------------------------------------------------------------- /client-generator/images/react/client-generator-react-show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/client-generator/images/react/client-generator-react-show.png -------------------------------------------------------------------------------- /client-generator/images/nextjs/client-generator-nextjs-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/client-generator/images/nextjs/client-generator-nextjs-list.png -------------------------------------------------------------------------------- /client-generator/images/nextjs/client-generator-nextjs-show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/client-generator/images/nextjs/client-generator-nextjs-show.png -------------------------------------------------------------------------------- /client-generator/images/react/client-generator-react-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/client-generator/images/react/client-generator-react-delete.png -------------------------------------------------------------------------------- /.proselintrc: -------------------------------------------------------------------------------- 1 | { 2 | "checks": { 3 | "typography.symbols": false, 4 | "typography.exclamation": false, 5 | "hyperbole.misc": false, 6 | "cliches.misc": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client-generator/images/react/client-generator-react-list-pagination.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/client-generator/images/react/client-generator-react-list-pagination.png -------------------------------------------------------------------------------- /client-generator/images/react-native/client-generator-react-native-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/client-generator/images/react-native/client-generator-react-native-add.png -------------------------------------------------------------------------------- /client-generator/images/react-native/client-generator-react-native-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/client-generator/images/react-native/client-generator-react-native-list.png -------------------------------------------------------------------------------- /client-generator/images/react-native/client-generator-react-native-show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/client-generator/images/react-native/client-generator-react-native-show.png -------------------------------------------------------------------------------- /client-generator/images/react-native/client-generator-react-native-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredericbarthelet/docs/HEAD/client-generator/images/react-native/client-generator-react-native-delete.png -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1_Support_question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ⛔ Support Question 3 | about: See https://api-platform.com/support/ for questions about using API Platform 4 | 5 | --- 6 | 7 | We use GitHub issues only to discuss about bugs and new features. 8 | For this kind of questions about using API Platform, please use 9 | any of the support alternatives shown in https://api-platform.com/support/ 10 | 11 | Thanks! 12 | -------------------------------------------------------------------------------- /extra/contribution-guides.md: -------------------------------------------------------------------------------- 1 | # Contribution guides 2 | 3 | 1. [API Platform Core Library](https://github.com/api-platform/core/blob/master/CONTRIBUTING.md) 4 | 2. [API Platform Schema Generator](https://github.com/api-platform/schema-generator/blob/master/CONTRIBUTING.md) 5 | 3. [API Platform Admin](https://github.com/api-platform/admin/blob/master/CONTRIBUTING.md) 6 | 4. [API Platform CRUD Generator](https://github.com/api-platform/client-generator/blob/master/CONTRIBUTING.md) 7 | 8 |

JWT screencast
Watch the Contributing back to Symfony screencast (free-

9 | -------------------------------------------------------------------------------- /extra/releases.md: -------------------------------------------------------------------------------- 1 | # The Release Process 2 | 3 | API Platform follows the [Semantic Versioning](https://semver.org) strategy. 4 | 5 | Only 3 versions are maintained at the same time: 6 | 7 | * **stable** (currently the **2.5** branch): regular bug fixes are integrated in this version 8 | * **old-stable** (currently **2.4** branch): security fixes are integrated in this version, regular bug fixes are **not** backported in it 9 | * **development** (**master** branch): new features target this branch 10 | 11 | Older versions (1.x, 2.0...) **are not maintained**. If you still use them, you must upgrade as soon as possible. 12 | 13 | The **old-stable** branch is merged in the **stable** branch on a regular basis to propagate security fixes. 14 | The **stable** branch is merged in the **development** branch on a regular basis to propagate security and regular bug fixes. 15 | 16 | New versions of API Platform are released **when they are ready**, on the behalf of the API Platform Core Team. 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | # Change these settings to your own preference 9 | indent_style = space 10 | indent_size = 4 11 | 12 | # We recommend you to keep these unchanged 13 | end_of_line = lf 14 | charset = utf-8 15 | trim_trailing_whitespace = true 16 | insert_final_newline = true 17 | 18 | [*.json] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | [*.md] 23 | trim_trailing_whitespace = false 24 | 25 | [*.neon] 26 | indent_style = tab 27 | indent_size = 4 28 | 29 | [*.xml] 30 | indent_style = space 31 | indent_size = 4 32 | 33 | [*.{yaml,yml}] 34 | indent_style = space 35 | indent_size = 2 36 | trim_trailing_whitespace = false 37 | 38 | [.circleci/config.yml] 39 | indent_style = space 40 | indent_size = 2 41 | 42 | [.github/workflows/*.yml] 43 | indent_style = space 44 | indent_size = 2 45 | 46 | [.gitmodules] 47 | indent_style = tab 48 | 49 | [.proselintrc] 50 | indent_style = space 51 | indent_size = 2 52 | 53 | [.travis.yml] 54 | indent_style = space 55 | indent_size = 2 56 | -------------------------------------------------------------------------------- /client-generator/typescript.md: -------------------------------------------------------------------------------- 1 | # TypeScript Interfaces 2 | 3 | The TypeScript Generator allows you to create [TypeScript interfaces](https://www.typescriptlang.org/docs/handbook/interfaces.html) that you can embed in any TypeScript-enabled project (React, Vue.js, Angular..) 4 | 5 | To do so, run the client generator: 6 | 7 | $ npx @api-platform/client-generator --generator typescript https://demo.api-platform.com src/ --resource foo 8 | # Replace the URL with the entrypoint of your Hydra-enabled API 9 | # "src/" represents where the interfaces will be generated 10 | # Omit the resource flag to generate files for all resource types exposed by the API 11 | 12 | This command parses the Hydra documentation and creates one `.ts` file for each API Resource you have defined in your application, in the `interfaces` subfolder. 13 | 14 | NOTE: If you are not sure what the entrypoint is, see [Troubleshooting](troubleshooting.md) 15 | 16 | ## Example 17 | 18 | Assuming you have 2 resources in your application, `Foo` and `Bar`, when you run 19 | 20 | $ npx @api-platform/client-generator --generator typescript https://demo.api-platform.com src/ 21 | 22 | you will obtain 2 `.ts` files arranged as following: 23 | 24 | * src/ 25 | * interfaces/ 26 | * foo.ts 27 | * bar.ts 28 | -------------------------------------------------------------------------------- /core/nelmio-api-doc.md: -------------------------------------------------------------------------------- 1 | # NelmioApiDocBundle Integration 2 | 3 | NelmioApiDoc provides an alternative to [the native Swagger/Open API support](swagger.md) provided by API Platform. 4 | 5 | As NelmioApiDocBundle 3+ has built-in support for API Platform, this documentation is only relevant for people using 6 | NelmioApiDocBundle between version 2.9 and 3.0. 7 | 8 | For new projects, prefer using the built-in Swagger support and/or NelmioApiDoc 3. 9 | 10 | ![Screenshot of API Platform integrated with NelmioApiDocBundle](images/NelmioApiDocBundle.png) 11 | 12 | [NelmioApiDocBundle](https://github.com/nelmio/NelmioApiDocBundle) is supported by API Platform since version 2.9. 13 | 14 | To enable the NelmioApiDoc integration, copy the following configuration: 15 | 16 | ```yaml 17 | # api/config/packages/api_platform.yaml 18 | api_platform: 19 | # ... 20 | 21 | enable_nelmio_api_doc: true 22 | 23 | nelmio_api_doc: 24 | sandbox: 25 | accept_type: 'application/json' 26 | body_format: 27 | formats: ['json'] 28 | default_format: 'json' 29 | request_format: 30 | formats: 31 | json: 'application/json' 32 | ``` 33 | 34 | Please note that NelmioApiDocBundle has a sandbox limitation where you cannot pass a JSON array as parameter, so you cannot 35 | use it to deserialize nested objects. 36 | -------------------------------------------------------------------------------- /core/json-schema.md: -------------------------------------------------------------------------------- 1 | # JSON Schema Support 2 | 3 | [JSON Schema](https://json-schema.org/) is a popular vocabulary to describe the shape of JSON documents. A variant of JSON Schema is also used [in OpenAPI specifications](swagger.md). 4 | 5 | API Platform provides an infrastructure to generate JSON Schemas for any resource, represented in any format (including JSON-LD). 6 | The generated schema can be used with libraries such as [react-json-schema-form](https://github.com/rjsf-team/react-jsonschema-form) to build forms for the documented resources, or to [be used for validation](https://json-schema.org/implementations.html#validators). 7 | 8 | ## Generating a JSON Schema 9 | 10 | To export the schema corresponding to an API Resource, run the following command: 11 | 12 | $ docker-compose exec php bin/console api:json-schema:generate 'App\Entity\Book' 13 | 14 | To see all options available, try: 15 | 16 | $ docker-compose exec php bin/console help api:json-schema:generate 17 | 18 | ## Generating a JSON Schema Programmatically 19 | 20 | To generate JSON Schemas programmatically, use the `api_platform.json_schema.schema_factory` [service](https://symfony.com/doc/current/service_container.html#fetching-and-using-services). 21 | 22 | ## Testing 23 | 24 | API Platform provides a PHPUnit assertion to test if a response is valid according to a given Schema: `assertMatchesJsonSchema()`. 25 | Refer to [the testing documentation](testing.md) for more details. 26 | -------------------------------------------------------------------------------- /client-generator/nextjs.md: -------------------------------------------------------------------------------- 1 | # Next.js Generator 2 | 3 | ![List screenshot](images/nextjs/client-generator-nextjs-list.png) 4 | 5 | The Next.js Client Generator generates components for Server Side Rendered applications using [Next.js](https://zeit.co/blog/next) 6 | 7 | ## Install 8 | 9 | ### Next + Express Server 10 | 11 | Create a [Next.js application with express server](https://github.com/zeit/next.js/tree/canary/examples/custom-server-express). The easiest way is to execute: 12 | 13 | $ npx create-next-app your-app-name 14 | # or 15 | $ yarn create next-app your-app-name 16 | 17 | ### Installing the Generator Dependencies 18 | 19 | Enable TypeScript in your next project 20 | 21 | $ yarn add --dev typescript @types/react @types/node 22 | 23 | Install required dependencies: 24 | 25 | $ yarn add lodash.get lodash.has @types/lodash isomorphic-unfetch 26 | 27 | ## Generating Routes 28 | 29 | $ npx @api-platform/client-generator https://demo.api-platform.com . --generator next --resource book 30 | # Replace the URL by the entrypoint of your Hydra-enabled API 31 | 32 | > Note: Omit the resource flag to generate files for all resource types exposed by the API. 33 | 34 | ## Starting the Project 35 | 36 | You can launch the server with 37 | 38 | $ yarn dev 39 | 40 | Go to `https://localhost:3000/books/` to start using your app. 41 | 42 | ## Screenshots 43 | 44 | ![List](images/nextjs/client-generator-nextjs-list.png) 45 | ![Show](images/nextjs/client-generator-nextjs-show.png) 46 | -------------------------------------------------------------------------------- /client-generator/index.md: -------------------------------------------------------------------------------- 1 | # The API Platform Client Generator 2 | 3 | Client Generator is the fastest way to scaffold fully featured webapps and native mobile apps from APIs supporting the [Hydra](http://www.hydra-cg.com/) format. 4 | 5 | ![Screencast](images/client-generator-demo.gif) 6 | 7 | *Generated React and React Native apps, updated in real time* 8 | 9 | It is able to generate apps using the following frontend stacks: 10 | 11 | * [React with Redux](react.md) 12 | * [React Native](react-native.md) 13 | * [Next.js](nextjs.md) 14 | * [Vue.js](vuejs.md) 15 | * [Quasar Framework](quasar.md) 16 | 17 | Client Generator works especially well with APIs built with the [API Platform](https://api-platform.com) framework. 18 | 19 | ## Features 20 | 21 | * Generates high-quality ES6: 22 | * list view (with pagination) 23 | * detail view 24 | * creation form 25 | * update form 26 | * delete button 27 | * Supports to-one and to-many relations 28 | * Uses the appropriate input type (`number`, `date`...) 29 | * Client-side validation 30 | * Subscribes to data updates pushed by servers supporting [the Mercure protocol](https://mercure.rocks) 31 | * Displays server-side validation errors under the related input (if using API Platform Core) 32 | * Integration with [Bootstrap](https://getbootstrap.com/) and [FontAwesome](https://fontawesome.com/) (Progressive Web Apps) 33 | * Integration with [React Native Elements](https://react-native-training.github.io/react-native-elements/) 34 | * Accessible to people with disabilities ([ARIA](https://www.w3.org/WAI/intro/aria) support in webapps) 35 | -------------------------------------------------------------------------------- /deployment/index.md: -------------------------------------------------------------------------------- 1 | # Deploying API Platform Applications 2 | 3 | API Platform apps are super easy to deploy in production on most cloud providers thanks to the native integration with 4 | [Kubernetes](kubernetes.md). 5 | 6 | The server part of API Platform is basically a standard Symfony application, which you can also easily [deploy on your 7 | own servers](http://symfony.com/doc/current/deployment.html). Documentation for deploying on various container [orchestration](https://en.wikipedia.org/wiki/Orchestration_(computing)) 8 | tools and PaaS (Platform as a Service) are also available: 9 | 10 | * [Deploying to a Kubernetes Cluster](kubernetes.md) 11 | * [Deploying with Docker Compose](docker-compose.md) 12 | * [Deploying on Heroku](heroku.md) 13 | * [Deploying on Platform.sh](https://platform.sh/blog/deploy-api-platform-on-platformsh) 14 | 15 | The clients are [Create React App](https://github.com/facebook/create-react-app/) skeletons. You can deploy them in a wink 16 | on any static website hosting service (including [Netlify](https://www.netlify.com/), [Firebase Hosting](https://firebase.google.com/docs/hosting/), 17 | [GitHub Pages](https://pages.github.com/), or [Amazon S3](https://docs.aws.amazon.com/en_us/AmazonS3/latest/dev/WebsiteHosting.html) 18 | by following [the relevant documentation](https://facebook.github.io/create-react-app/docs/deployment). 19 | 20 |

JWT screencast
Watch the Animated Deployment with Ansistrano screencast

21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'lts/*' 4 | 5 | cache: 6 | yarn: true 7 | directories: 8 | - $HOME/.cache/pip 9 | 10 | env: 11 | global: 12 | secure: gjiWHIgNjLXuEFmLiepiOTHOuauHfeKHutrE0sBwFZUQzP9FoGTZzJub1z8/vm+hhygA+TZbB0iclygHyNrXCkZyNdnZXChXl4iPdYqY3OARPOAbQff16+/XSUDsZ/Ok1etdb3kKor1R4rBt/WywBvXmmdggumTA3yT5ExI+dyomdONo4yMUZ7g1la0ehMEzGqyVjt0nUW31PN3l6dI1qgigHCuotSrrpWP6fTXuUh5l6YA7KXb/V1hJxaGENLz1Cdfk0sF66e4KsV/DX6JZSqpvdqVB8OTPU+si511yJtGS8OeuLs4RqmXMLrY/ChCodlYnfCE+NleBFpUnCVqth/RDRh7LvplUJlpsXNcfyoA3mOFmZa0euOMCqJ3qwESz802Y9c5oN63hn2OUF/raFDc3SMMC86FFHxwyYjz5+yzXYupnFNj39NKdQ1v1KBbY8BD8UT8RU3mlu4/3LRz0tSamREHj3pGgBmvgUZfUE1dMngeaZjOmBaIZH8TnKQ75CvfnoT+LJnZo6g9g4uwz6jtaIziSMZ0OW/95nF8yx+xONbHEtt5ex5M09NOFsN2vB2bcUAgIjyGrmmNLQadwJyQv557IGPEE5CyGhJpXh9XZ+WMw2vO45MOw4sQPkARF6OJkMteqFc2NLXMOQJ07EScbgEKR/9VOcyxIk/7a7nc= 13 | 14 | install: 15 | - pip install --quiet --user proselint 16 | - cp .proselintrc ~ 17 | - cd .. 18 | - git clone --depth 1 https://github.com/api-platform/website.git 19 | - cd website 20 | - yarn install --silent 21 | - cd $TRAVIS_BUILD_DIR 22 | 23 | script: 24 | - find . -name '*.md' -exec proselint {} \; 25 | - cd ../website 26 | - bin/retrieve-documentation 27 | - GATSBY_BRANCH_NAME=$TRAVIS_BRANCH yarn gatsby build 28 | # Preserve artifacts 29 | - cd $TRAVIS_BUILD_DIR 30 | - mv ../website/public public 31 | 32 | deploy: 33 | provider: pages 34 | skip_cleanup: true 35 | github_token: $GITHUB_TOKEN 36 | local_dir: public 37 | fqdn: api-platform.com 38 | on: 39 | all_branches: true 40 | condition: $TRAVIS_BRANCH =~ ^(master|2.5|2.4|2.3|2.2|2.1)$ 41 | -------------------------------------------------------------------------------- /admin/schema.org.md: -------------------------------------------------------------------------------- 1 | # Using the Schema.org Vocabulary 2 | 3 | API Platform Admin has native support for the popular [Schema.org](https://schema.org) vocabulary. 4 | 5 | > Schema.org is a collaborative, community activity with a mission to create, maintain, and promote schemas for structured data on the Internet, on web pages, in email messages, and beyond. 6 | 7 | To leverage this capability, your API must use the JSON-LD format and the appropriate Schema.org types. 8 | The following examples will use [API Platform Core](../core/) to create such API, but keep in mind that this feature will work with any JSON-LD API using the Schema.org vocabulary, regardless of the used web framework or programming language. 9 | 10 | ## Displaying Related Resource's Name Instead of its IRI 11 | 12 | By default, IRIs of related objects are displayed in lists and forms. 13 | However, it is often more user-friendly to display a string representation of the resource (such as its name) instead of its ID. 14 | 15 | To configure which property should be shown to represent your entity, map the property containing the name of the object with the `http://schema.org/name` type: 16 | 17 | ```php 18 | // api/src/Entity/Person.php 19 | 20 | /** 21 | * @ApiProperty(iri="http://schema.org/name") 22 | */ 23 | private $name; 24 | ``` 25 | 26 | ## Emails, URLs and Identifiers 27 | 28 | Besides, it is also possible to use the documentation to customize some fields automatically while configuring the semantics of your data. 29 | 30 | The following Schema.org types are currently supported by API Platform Admin: 31 | 32 | * `http://schema.org/email`: the field will be rendered using the `` React Admin component 33 | * `http://schema.org/url`: the field will be rendered using the `` React Admin component 34 | * `http://schema.org/identifier`: the field will be formatted properly in inputs 35 | -------------------------------------------------------------------------------- /core/default-order.md: -------------------------------------------------------------------------------- 1 | # Overriding Default Order 2 | 3 | API Platform Core provides an easy way to override the default order of items in your collection. 4 | 5 | By default, items in the collection are ordered in ascending (ASC) order by their resource identifier(s). If you want to 6 | customize this order, you must add an `order` attribute on your ApiResource annotation: 7 | 8 | ```php 9 | HTTP/2 allows a server to pre-emptively send (or "push") responses (along with corresponding "promised" requests) to a client in association with a previous client-initiated request. This can be useful when the server knows the client will need to have those responses available in order to fully process the response to the original request. 4 | > 5 | > —[RFC 7540](https://tools.ietf.org/html/rfc7540#section-8.2) 6 | 7 | API Platform leverages this capability by pushing relations of a resource to clients. 8 | 9 | ```php 10 | import('layouts/MyLayout.vue'), 26 | children: [ 27 | ...fooRoutes 28 | ], 29 | 30 | ``` 31 | 32 | and store modules in the `src/store/index.js` file. 33 | 34 | ```javascript 35 | // Replace "foo" with the name of the resource type 36 | import foo from '../generated/store/modules/foo/'; 37 | 38 | export default function(/* { ssrContext } */) { 39 | const Store = new Vuex.Store({ 40 | modules: { 41 | foo, 42 | }, 43 | 44 | ``` 45 | 46 | Make sure to update the `quasar.conf.js` file with the following: 47 | 48 | ```javascript 49 | framework: { 50 | components: [ 51 | 'QTable', 52 | 'QTh', 53 | 'QTr', 54 | 'QTd', 55 | 'QAjaxBar', 56 | 'QBreadcrumbs', 57 | 'QBreadcrumbsEl', 58 | 'QSpace', 59 | 'QInput', 60 | 'QForm', 61 | 'QSelect', 62 | 'QMarkupTable', 63 | 'QDate', 64 | 'QTime', 65 | 'QCheckbox', 66 | 'QPopupProxy' 67 | 68 | ... 69 | ], 70 | directives: [..., 'ClosePopup'], 71 | plugins: ['Notify'], 72 | config: { 73 | ... 74 | notify: { 75 | position: 'top', 76 | multiLine: true, 77 | timeout: 0, 78 | }, 79 | }, 80 | ``` 81 | -------------------------------------------------------------------------------- /client-generator/vuejs.md: -------------------------------------------------------------------------------- 1 | # Vue.js Generator 2 | 3 | Bootstrap a Vue.js application using [vue-cli](https://github.com/vuejs/vue-cli): 4 | 5 | $ vue init webpack-simple my-app 6 | $ cd my-app 7 | 8 | Install the required dependencies: 9 | 10 | $ yarn add vue-router vuex vuex-map-fields babel-plugin-transform-builtin-extend babel-preset-es2015 babel-preset-stage-2 lodash 11 | 12 | Optionally, install Bootstrap and Font Awesome to get an app that looks good: 13 | 14 | $ yarn add bootstrap font-awesome 15 | 16 | To generate all the code you need for a given resource run the following command: 17 | 18 | $ npx @api-platform/client-generator https://demo.api-platform.com src/ --generator vue --resource book 19 | # Replace the URL with the entrypoint of your Hydra-enabled API 20 | # Omit the resource flag to generate files for all resource types exposed by the API 21 | 22 | The code is ready to be executed! Register the generated routes and store modules in the `main.js` file. Here is an example: 23 | 24 | ```javascript 25 | import Vue from 'vue' 26 | import Vuex from 'vuex'; 27 | import VueRouter from 'vue-router'; 28 | import App from './App.vue'; 29 | import 'bootstrap/dist/css/bootstrap.css'; 30 | import 'font-awesome/css/font-awesome.css'; 31 | import book from './store/modules/book/'; 32 | import bookRoutes from './router/book'; 33 | 34 | Vue.config.productionTip = false; 35 | 36 | Vue.use(Vuex); 37 | Vue.use(VueRouter); 38 | 39 | const store = new Vuex.Store({ 40 | modules: { 41 | book, 42 | } 43 | }); 44 | 45 | const router = new VueRouter({ 46 | mode: 'history', 47 | routes: [ 48 | ...bookRoutes, 49 | ], 50 | }); 51 | 52 | new Vue({ 53 | store, 54 | router, 55 | render: h => h(App), 56 | }).$mount('#app'); 57 | 58 | ``` 59 | 60 | Make sure to update the `.babelrc` file with the following: 61 | 62 | ```javascript 63 | { 64 | "presets": [ 65 | ["es2015", { "modules": false }], 66 | ["stage-2"] 67 | ], 68 | "plugins": [ 69 | ["babel-plugin-transform-builtin-extend", { 70 | globals: ["Error", "Array"] 71 | }] 72 | ] 73 | } 74 | ``` 75 | 76 | Go to `https://localhost/books/` to start using your app. 77 | That's all! 78 | -------------------------------------------------------------------------------- /admin/index.md: -------------------------------------------------------------------------------- 1 | # The API Platform Admin 2 | 3 | ![Screencast](images/admin-demo.gif) 4 | 5 | API Platform Admin is a tool to automatically create a beautiful and fully featured administration interface 6 | for any API supporting [the Hydra Core Vocabulary](http://www.hydra-cg.com/) or other API specification formats supported by [`@api-platform/api-doc-parser`](https://github.com/api-platform/api-doc-parser) (experimental support for [OpenAPI](https://www.openapis.org/) is also available). 7 | 8 | API Platform Admin is the perfect companion of APIs created 9 | using [the API Platform framework](https://api-platform.com), but also supports APIs written with any other programming language or framework as long as they expose a standard Hydra API documentation. 10 | 11 | API Platform Admin is a 100% standalone Single-Page-Application with no coupling to the server part, 12 | according to the API-first paradigm. 13 | 14 | API Platform Admin parses the API documentation then uses the awesome [React Admin](https://marmelab.com/react-admin/) 15 | library to expose a nice, responsive, management interface (Create-Retrieve-Update-Delete) for all documented resource types. 16 | 17 | You can **customize everything** by using provided React Admin and [Material UI](https://material-ui.com/) components, or by writing your custom [React](https://reactjs.org/) components. 18 | 19 | ## Features 20 | 21 | * Automatically generates an admin interface for all the resources of the API thanks to hypermedia features of Hydra 22 | * Generates 'list', 'create', 'show', and 'edit' screens, as well as a delete button 23 | * Generates suitable inputs and fields according to the API doc (e.g. number HTML input for numbers, checkbox for booleans, selectbox for relationships...) 24 | * Generates suitable inputs and fields according to Schema.org types if available (e.g. email field for http://schema.org/email) 25 | * Handles relationships 26 | * Supports pagination 27 | * Supports filters and ordering 28 | * Automatically validates whether a field is mandatory client-side according to the API description 29 | * Sends proper HTTP requests to the API and decodes them using Hydra and JSON-LD formats 30 | * Nicely displays server-side errors (e.g. advanced validation) 31 | * **100% customizable** 32 | -------------------------------------------------------------------------------- /admin/authentication-support.md: -------------------------------------------------------------------------------- 1 | # Authentication Support 2 | 3 | API Platform Admin delegates the authentication support to React Admin. 4 | Refer to [the chapter dedicated to authentication in the React Admin documentation](https://marmelab.com/react-admin/Authentication.html) 5 | for more information. 6 | 7 | In short, you have to tweak data provider and api document parser, like this: 8 | 9 | ```javascript 10 | // admin/src/App.js 11 | 12 | import React from 'react'; 13 | import { HydraAdmin } from '@api-platform/admin'; 14 | import parseHydraDocumentation from '@api-platform/api-doc-parser/lib/hydra/parseHydraDocumentation'; 15 | import { dataProvider as baseDataProvider, fetchHydra as baseFetchHydra } from '@api-platform/admin'; 16 | import authProvider from './authProvider'; 17 | import { Redirect } from 'react-router-dom'; 18 | 19 | const entrypoint = process.env.REACT_APP_API_ENTRYPOINT; 20 | const fetchHeaders = {'Authorization': `Bearer ${window.localStorage.getItem('token')}`}; 21 | const fetchHydra = (url, options = {}) => baseFetchHydra(url, { 22 | ...options, 23 | headers: new Headers(fetchHeaders), 24 | }); 25 | const apiDocumentationParser = entrypoint => parseHydraDocumentation(entrypoint, { headers: new Headers(fetchHeaders) }) 26 | .then( 27 | ({ api }) => ({api}), 28 | (result) => { 29 | switch (result.status) { 30 | case 401: 31 | return Promise.resolve({ 32 | api: result.api, 33 | customRoutes: [{ 34 | props: { 35 | path: '/', 36 | render: () => , 37 | }, 38 | }], 39 | }); 40 | 41 | default: 42 | return Promise.reject(result); 43 | } 44 | }, 45 | ); 46 | const dataProvider = baseDataProvider(entrypoint, fetchHydra, apiDocumentationParser); 47 | 48 | export default props => ( 49 | 55 | ); 56 | ``` 57 | -------------------------------------------------------------------------------- /outline.yaml: -------------------------------------------------------------------------------- 1 | chapters: 2 | - title: 'The Distribution: Create Powerful APIs with Ease' 3 | path: distribution 4 | items: 5 | - index 6 | - testing 7 | - debugging 8 | - title: The API Component 9 | path: core 10 | items: 11 | - index 12 | - getting-started 13 | - design 14 | - extending 15 | - testing 16 | - operations 17 | - graphql 18 | - data-providers 19 | - data-persisters 20 | - filters 21 | - subresources 22 | - serialization 23 | - validation 24 | - security 25 | - content-negotiation 26 | - pagination 27 | - deprecations 28 | - default-order 29 | - performance 30 | - extensions 31 | - messenger 32 | - dto 33 | - swagger 34 | - json-schema 35 | - mercure 36 | - push-relations 37 | - errors 38 | - external-vocabularies 39 | - operation-path-naming 40 | - extending-jsonld-context 41 | - identifiers 42 | - mongodb 43 | - elasticsearch 44 | - controllers 45 | - events 46 | - file-upload 47 | - jwt 48 | - form-data 49 | - angularjs-integration 50 | - fosuser-bundle 51 | - nelmio-api-doc 52 | - configuration 53 | - title: The Schema Generator Component 54 | path: schema-generator 55 | items: 56 | - index 57 | - getting-started 58 | - configuration 59 | - title: The Admin Component 60 | path: admin 61 | items: 62 | - index 63 | - getting-started 64 | - customizing 65 | - handling-relations-to-collections 66 | - schema.org 67 | - authentication-support 68 | - title: The Client Generator Component 69 | path: client-generator 70 | items: 71 | - index 72 | - react 73 | - nextjs 74 | - react-native 75 | - vuejs 76 | - vuetify 77 | - quasar 78 | - typescript 79 | - troubleshooting 80 | - title: Deployment 81 | path: deployment 82 | items: 83 | - index 84 | - kubernetes 85 | - docker-compose 86 | - heroku 87 | - traefik 88 | - title: Extra 89 | path: extra 90 | items: 91 | - releases 92 | - philosophy 93 | - troubleshooting 94 | - contribution-guides 95 | - conduct 96 | -------------------------------------------------------------------------------- /client-generator/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | * The generator does not perform any authentication, so you must ensure that all referenced Hydra paths for your API are 4 | accessible anonymously. If you are using API Platform this will at least include: 5 | 6 | ``` 7 | api_entrypoint ANY ANY ANY /{index}.{_format} 8 | api_doc ANY ANY ANY /docs.{_format} 9 | api_jsonld_context ANY ANY ANY /contexts/{shortName}.{_format} 10 | ``` 11 | 12 | * If you receive `Error: The class http://www.w3.org/ns/hydra/core#ApiDocumentation doesn't exist.` you may have 13 | specified the documentation URL instead of the entrypoint. For example if you are using API Platform and your 14 | documentation URL is at [https://demo.api-platform.com/docs](https://demo.api-platform.com/docs) the entry point is 15 | likely at [https://demo.api-platform.com](https://demo.api-platform.com). You can see an example of the expected 16 | response from an entrypoint in your browser by visiting 17 | [https://demo.api-platform.com/index.jsonld](https://demo.api-platform.com/index.jsonld). 18 | 19 | * If you receive `TypeError: Cannot read property '@type' of undefined` or `TypeError: Cannot read property '0' 20 | of undefined` check that the URL you specified is accessible and returns jsonld. You can check from the command line 21 | you are using by running something like `curl https://demo.api-platform.com/`. 22 | 23 | * If you receive a message like this: 24 | 25 | ``` 26 | { Error 27 | at done (/usr/local/share/.config/yarn/global/node_modules/jsonld/js/jsonld.js:6851:19) 28 | at 29 | at process._tickCallback (internal/process/next_tick.js:188:7) 30 | name: 'jsonld.InvalidUrl', 31 | message: 'Dereferencing a URL did not result in a JSON object. The response was valid JSON, but it was not a JSON object.', 32 | details: 33 | { code: 'invalid remote context', 34 | url: 'https://demo.api-platform.com/contexts/Entrypoint', 35 | cause: null } } 36 | ``` 37 | 38 | Check access to the specified URL, in this case `https://demo.api-platform.com/contexts/Entrypoint`, use curl to check 39 | access and the response `curl https://demo.api-platform.com/contexts/Entrypoint`. In the above case an "Access Denied" 40 | message in JSON format was being returned. 41 | -------------------------------------------------------------------------------- /distribution/debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 |

API Platform debugging screencast
Watch the Debugging API Platform screencast

4 | 5 | ## Xdebug 6 | 7 | The default Docker stack is shipped without a Xdebug stage. It's easy 8 | though to add [Xdebug](https://xdebug.org/) to your project, for development 9 | purposes such as debugging tests or remote API requests. 10 | 11 | ## Add a Development Stage to the Dockerfile 12 | 13 | To avoid deploying API Platform to production with an active Xdebug extension, 14 | it's recommended to add a custom stage to the end of the `api/Dockerfile`. 15 | 16 | ```Dockerfile 17 | # api/Dockerfile 18 | FROM api_platform_php as api_platform_php_dev 19 | 20 | ARG XDEBUG_VERSION=2.7.2 21 | RUN set -eux; \ 22 | apk add --no-cache --virtual .build-deps $PHPIZE_DEPS; \ 23 | pecl install xdebug-$XDEBUG_VERSION; \ 24 | docker-php-ext-enable xdebug; \ 25 | apk del .build-deps 26 | ``` 27 | 28 | ## Configure Xdebug with Docker Compose Override 29 | 30 | Using an [override](https://docs.docker.com/compose/reference/overview/#specifying-multiple-compose-files) file named 31 | `docker-compose.override.yml` ensures that the production configuration remains untouched. 32 | 33 | As an example, an override could look like this: 34 | 35 | ```yml 36 | version: "3.4" 37 | 38 | services: 39 | php: 40 | build: 41 | target: api_platform_php_dev 42 | environment: 43 | # See https://docs.docker.com/docker-for-mac/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host 44 | # See https://github.com/docker/for-linux/issues/264 45 | # The `remote_host` below may optionally be replaced with `remote_connect_back` 46 | XDEBUG_CONFIG: >- 47 | remote_enable=1 48 | remote_host=host.docker.internal 49 | remote_connect_back=1 50 | remote_port=9000 51 | idekey=PHPSTORM 52 | # This should correspond to the server declared in PHPStorm `Preferences | Languages & Frameworks | PHP | Servers` 53 | # Then PHPStorm will use the corresponding path mappings 54 | PHP_IDE_CONFIG: serverName=api-platform 55 | ``` 56 | 57 | ## Troubleshooting 58 | 59 | Inspect the installation with the following command. The requested Xdebug 60 | version should be displayed in the output. 61 | 62 | ```console 63 | $ docker-compose exec php php --version 64 | 65 | PHP … 66 | with Xdebug v2.7.2 … 67 | ``` 68 | -------------------------------------------------------------------------------- /extra/conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, ethnic group, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This Code of Conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at dunglas@gmail.com. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality regarding the reporter of an 42 | incident. 43 | 44 | 45 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 46 | version 1.3.0, available at 47 | [http://contributor-covenant.org/version/1/3/0/][version] 48 | 49 | [homepage]: http://contributor-covenant.org 50 | [version]: http://contributor-covenant.org/version/1/3/0/ 51 | -------------------------------------------------------------------------------- /admin/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Installation 4 | 5 | If you use the [API Platform Distribution](../distribution/), API Platform Admin is already installed, you can skip this installation guide. 6 | 7 | Otherwise, all you need to install API Platform Admin is a JavaScript package manager. We recommend [Yarn](https://yarnpkg.com/) ([npm](https://www.npmjs.com/) is also supported). 8 | 9 | If you don't have an existing React Application, create one using [Create React App](https://create-react-app.dev/): 10 | 11 | $ yarn create react-app my-admin 12 | 13 | Go to the directory of your project: 14 | 15 | $ cd my-admin 16 | 17 | Finally, install the `@api-platform/admin` library: 18 | 19 | $ yarn add @api-platform/admin 20 | 21 | ## Creating the Admin 22 | 23 | To initialize API Platform Admin, register it in your application. 24 | For instance, if you used Create React App, replace the content of `src/App.js` by: 25 | 26 | ```javascript 27 | import React from "react"; 28 | import { HydraAdmin } from "@api-platform/admin"; 29 | 30 | // Replace with your own API entrypoint 31 | // For instance if https://example.com/api/books is the path to the collection of book resources, then the entrypoint is https://example.com/api 32 | export default () => ( 33 | 34 | ); 35 | ``` 36 | 37 | Be sure to make your API send proper [CORS HTTP headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) to allow 38 | the admin's domain to access it. 39 | 40 | To do so, if you use the API Platform Distribution, update the value of the `CORS_ALLOW_ORIGIN` parameter in `api/.env` (it will be set to `^https?://localhost:?[0-9]*$` 41 | by default). 42 | 43 | If you use a custom installation of Symfony and [API Platform Core](../core/), you will need to adjust the [NelmioCorsBundle configuration](https://github.com/nelmio/NelmioCorsBundle#configuration) to expose the `Link` HTTP header and to send proper CORS headers on the route under which the API will be served (`/api` by default). 44 | Here is a sample configuration: 45 | 46 | ```yaml 47 | # config/packages/nelmio_cors.yaml 48 | 49 | nelmio_cors: 50 | paths: 51 | '^/api/': 52 | origin_regex: true 53 | allow_origin: ['^http://localhost:[0-9]+'] # You probably want to change this regex to match your real domain 54 | allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] 55 | allow_headers: ['Content-Type', 'Authorization'] 56 | expose_headers: ['Link'] 57 | max_age: 3600 58 | ``` 59 | 60 | Clear the cache to apply this change: 61 | 62 | $ docker-compose exec php bin/console cache:clear --env=prod 63 | 64 | Your new administration interface is ready! Type `yarn start` to try it! 65 | 66 | Note: if you don't want to hardcode the API URL, you can [use an environment variable](https://create-react-app.dev/docs/adding-custom-environment-variables). 67 | -------------------------------------------------------------------------------- /core/index.md: -------------------------------------------------------------------------------- 1 | # The API Platform Core Library 2 | 3 | API Platform Core is an easy-to-use and powerful library to create [hypermedia-driven REST APIs](https://en.wikipedia.org/wiki/HATEOAS). 4 | It is a component of the [API Platform framework](https://api-platform.com). It can be used as a standalone or with [the Symfony 5 | framework](https://symfony.com) (recommended). 6 | 7 | It embraces [JSON for Linked Data (JSON-LD)](http://json-ld.org) and [Hydra Core Vocabulary](http://www.hydra-cg.com) web 8 | standards but also supports [HAL](http://stateless.co/hal_specification.html), [Swagger/Open API](https://www.openapis.org/), XML, JSON, CSV and YAML. 9 | 10 | Build a working and fully featured CRUD API in minutes. Leverage the awesome features of the tool to develop complex and 11 | high-performance API-first projects. 12 | 13 | If you are starting a new project, the easiest way to get API Platform up is to install 14 | the [API Platform Distribution](../distribution/index.md). 15 | 16 | ![Screenshot](../distribution/images/swagger-ui-1.png) 17 | 18 | ## Features 19 | 20 | Here is the fully featured REST API you'll get in minutes: 21 | 22 | * [Automatic CRUD](operations.md) 23 | * Hypermedia (JSON-LD and HAL) 24 | * Machine-readable documentation of the API in the Hydra and [Swagger/Open API](swagger.md) formats, 25 | guessed from PHPDoc, Serializer, Validator and Doctrine ORM / MongoDB ODM metadata 26 | * Nice human-readable documentation built with Swagger UI (including a sandbox) and/or ReDoc 27 | * [Pagination](pagination.md) 28 | * A bunch of [filters](filters.md) 29 | * [Ordering](default-order.md) 30 | * [Validation](validation.md) using the Symfony Validator Component (with groups support) 31 | * Advanced [authentication and authorization](security.md) rules 32 | * Errors serialization (Hydra and the [RFC 7807](https://tools.ietf.org/html/rfc7807) are supported) 33 | * Advanced [serialization](serialization.md) thanks to the Symfony Serializer Component (groups support, relation embedding, max depth...) 34 | * Automatic routes registration 35 | * Automatic entrypoint generation giving access to all resources 36 | * [JWT](jwt.md) and [OAuth](https://oauth.net/) support 37 | * Files and `\DateTime` and serialization and deserialization 38 | 39 | Everything is fully customizable through a powerful [event system](events.md) and strong OOP. 40 | 41 | This bundle is extensively tested (unit and functional). The [`Fixtures/` directory](https://github.com/api-platform/core/tree/master/tests/Fixtures) contains a working app covering all features of the library. 42 | 43 | ## Screencasts 44 | 45 |

SymfonyCasts, API Platform screencasts

46 | 47 | The easiest and funniest way to learn how to use API Platform is to watch [the more than 60 screencasts available on SymfonyCasts](https://symfonycasts.com/tracks/rest?cid=apip#api-platform)! 48 | -------------------------------------------------------------------------------- /extra/philosophy.md: -------------------------------------------------------------------------------- 1 | # API Platform's Philosophy 2 | 3 | In 20 years of PHP, the web changed dramatically and is now evolving faster than ever: 4 | 5 | * Now that [they are indexed by Google](http://searchengineland.com/tested-googlebot-crawls-javascript-heres-learned-220157) 6 | and thanks to awesome frontend technologies such as [AngularJS](http://angularjs.org/) or [React](https://facebook.github.io/react/), 7 | [full-JavaScript](https://en.wikipedia.org/wiki/Single-page_application) **webapps are becoming the standard**. 8 | * [Since 2014 internet users spend more time on their mobile devices than on desktops](http://techcrunch.com/2014/08/21/majority-of-digital-media-consumption-now-takes-place-in-mobile-apps/): having a responsive website is mandatory and **native mobile apps are a must-have**. 9 | * [The semantic web](https://en.wikipedia.org/wiki/Semantic_Web) and **especially [Linked Data](https://en.wikipedia.org/wiki/Linked_data) 10 | is a reality**: with the [Schema.org](http://schema.org/) initiative and new open web standards such as [JSON-LD](http://json-ld.org/), 11 | search engines (among a bunch of other services and software) consume structured and machine-readable data at web scale. 12 | Not exposing such data decrease interoperability and search engine ranking/efficiency (think rich snippets). 13 | 14 | [PHP.net](https://www.php.net), [Symfony](https://symfony.com), [Facebook](http://hhvm.com/) and many others have worked hard 15 | to improve and professionalize the PHP ecosystem. The PHP world has closed the gap with most backend solutions and is often 16 | more innovative than them. 17 | 18 | However in critical areas I've described previously, many things can be improved. Almost all existing solutions are still [designed 19 | and documented](https://symfony.com/doc/current/book/page_creation.html) to create websites the old way: a server generates 20 | then sends plain-old HTML documents to browsers. 21 | 22 | [API Platform](https://api-platform.com) is a set of tools for building modern web projects. It is a framework 23 | for API-first projects built on top of Symfony. Like other modern frameworks such as Zend Framework and Symfony, it's both 24 | a full-stack all-in-one framework and a set of independent PHP components and bundles that can be used separately. 25 | 26 | Many of you will distrust the architecture promoted by the framework but read this tutorial until the end and you will 27 | see how API Platform makes modern development easy and fun again: 28 | 29 | * [Start by **creating a hypermedia REST API**](../distribution/index.md) exposing structured data that can 30 | be understood by any compliant client such as your apps but also search engines (JSON-LD with Schema.org vocabulary). 31 | This API is the central and unique entry point to access and modify data. It also encapsulates the whole business logic. 32 | * [Then **create as many clients as you want using frontend technologies you love**](../client-generator/index.md): an HTML5/JavaScript 33 | webapp querying the API in AJAX (of course) but also a native iOS or Android app, or even a desktop application. Clients 34 | only display data and forms. 35 | -------------------------------------------------------------------------------- /core/operation-path-naming.md: -------------------------------------------------------------------------------- 1 | # Operation Path Naming 2 | 3 | With API Platform Core, you can configure the default resolver used to generate operation paths. 4 | Pre-registered resolvers are available and can easily be overridden. 5 | 6 | ## Configuration 7 | 8 | There are two pre-registered operation path naming services: 9 | 10 | Service name | Entity name | Path result 11 | ------------------------------------------------------|--------------|---------------- 12 | `api_platform.path_segment_name_generator.underscore` | `MyResource` | `/my_resources` 13 | `api_platform.path_segment_name_generator.dash` | `MyResource` | `/my-resources` 14 | 15 | The default resolver is `api_platform.path_segment_name_generator.underscore`. 16 | To change it to the dash resolver, add the following lines to `api/config/packages/api_platform.yaml`: 17 | 18 | ```yaml 19 | # api/config/packages/api_platform.yaml 20 | api_platform: 21 | path_segment_name_generator: api_platform.path_segment_name_generator.dash 22 | ``` 23 | 24 | ## Create a Custom Operation Path Resolver 25 | 26 | Let's assume we need URLs without separators (e.g. `api.tld/myresources`) 27 | 28 | ### Defining the Operation Segment Name Generator 29 | 30 | Make sure the custom segment generator implements [`ApiPlatform\Core\Operation\PathSegmentNameGeneratorInterface`](https://github.com/api-platform/core/blob/master/src/Operation/PathSegmentNameGeneratorInterface.php): 31 | 32 | ```php 33 | dashize($name); 53 | 54 | return $name; 55 | } 56 | 57 | private function dashize(string $string): string 58 | { 59 | return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '-$1', $string)); 60 | } 61 | } 62 | ``` 63 | 64 | Note that `$name` contains a camel case string, by default the resource class name (e.g. `MyResource`). 65 | 66 | ### Registering the Service 67 | 68 | If you haven't disabled the autowiring option, the service will be registered automatically and you have nothing more to 69 | do. 70 | Otherwise, you must register this class as a service like in the following example: 71 | 72 | ```yaml 73 | # api/config/services.yaml 74 | services: 75 | # ... 76 | 'App\PathResolver\NoSeparatorsOperationPathResolver': ~ 77 | ``` 78 | 79 | ### Configuring It 80 | 81 | ```yaml 82 | # api/config/packages/api_platform.yaml 83 | api_platform: 84 | path_segment_name_generator: 'App\PathResolver\NoSeparatorsOperationPathResolver' 85 | ``` 86 | -------------------------------------------------------------------------------- /core/external-vocabularies.md: -------------------------------------------------------------------------------- 1 | # Using External Vocabularies 2 | 3 | JSON-LD allows to define classes and properties of your API with open vocabularies such as [Schema.org](https://schema.org) 4 | and [Good Relations](http://www.heppnetz.de/projects/goodrelations/). 5 | 6 | API Platform Core provides annotations usable on PHP classes and properties for specifying a related external [IRI](https://en.wikipedia.org/wiki/Internationalized_resource_identifier). 7 | 8 | ```php 9 | { 39 | return ( 40 | 41 | 42 | {BookRoutes} 43 | 44 | 45 | ); 46 | }; 47 | 48 | export default RouterComponent; 49 | ``` 50 | 51 | Here is an example of an `App.js` file: 52 | 53 | ```javascript 54 | import React, { Component } from 'react'; 55 | import { Provider } from 'react-redux'; 56 | import thunk from 'redux-thunk'; 57 | import { createStore, applyMiddleware, combineReducers } from 'redux'; 58 | import { View } from 'react-native'; 59 | import {reducer as form} from 'redux-form'; 60 | 61 | // see https://github.com/facebook/react-native/issues/14796 62 | import { Buffer } from 'buffer'; 63 | global.Buffer = Buffer; 64 | 65 | // see https://github.com/facebook/react-native/issues/16434 66 | import { URL, URLSearchParams } from 'whatwg-url'; 67 | global.URL = URL; 68 | global.URLSearchParams = URLSearchParams; 69 | 70 | // see https://github.com/facebook/react-native/issues/12890 71 | import RNEventSource from 'react-native-event-source'; 72 | global.EventSource = RNEventSource; 73 | 74 | // Replace "book" with the name of resource type 75 | import book from './reducers/book'; 76 | import Router from './Router'; 77 | 78 | export default class App extends Component { 79 | render() { 80 | const store = createStore(combineReducers({ 81 | book, 82 | form 83 | }), {}, applyMiddleware(thunk)); 84 | return ( 85 | 86 | 87 | 88 | 89 | 90 | ); 91 | } 92 | } 93 | ``` 94 | 95 | The code is ready to be executed! 96 | 97 | $ expo start 98 | 99 | ## Screenshots in iOS Simulator 100 | 101 | ![List](images/react-native/client-generator-react-native-list.png) ![Show](images/react-native/client-generator-react-native-show.png) 102 | ![Add](images/react-native/client-generator-react-native-add.png) ![Delete](images/react-native/client-generator-react-native-delete.png) 103 | -------------------------------------------------------------------------------- /core/design.md: -------------------------------------------------------------------------------- 1 | # General Design Considerations 2 | 3 | Since you only need to describe the structure of the data to expose, API Platform is both [a "design-first" and "code-first"](https://swagger.io/blog/api-design/design-first-or-code-first-api-development/) 4 | API framework. However, the "design-first" methodology is strongly recommended: first you design the **public shape** of 5 | API endpoints. 6 | 7 | To do so, you have to write a plain old PHP object representing the input and output of your endpoint. This is the class 8 | that is [marked with the `@ApiResource` annotation](../distribution/index.md). 9 | This class **doesn't have** to be mapped with Doctrine ORM, or any other persistence system. It must be simple (it's usually 10 | just a data structure with no or minimal behaviors) and will be automatically converted to [Hydra](extending-jsonld-context.md), 11 | [OpenAPI](swagger.md) and [GraphQL](graphql.md) documentations or schemas by API Platform (there is a 1-1 mapping 12 | between this class and those docs). 13 | 14 | Then, it's up to the developer to feed API Platform with an hydrated instance of this API resource object by implementing 15 | the [`DataProviderInterface`](data-providers.md). Basically, the data provider will query the persistence system (RDBMS, 16 | document or graph DB, external API...), and must hydrate and return the POPO that has been designed as mentioned above. 17 | 18 | When updating a state (`POST`, `PUT`, `PATCH`, `DELETE` HTTP methods), it's up to the developer to properly persist the 19 | data provided by API Platform's resource object [hydrated by the serializer](serialization.md). 20 | To do so, there is another interface to implement: [`DataPersisterInterface`](data-persisters.md). 21 | 22 | This class will read the API resource object (the one marked with `@ApiResource`) and: 23 | 24 | * persist it directly in the database; 25 | * or hydrate a DTO then trigger a command; 26 | * or populate an event store; 27 | * or persist the data in any other useful way. 28 | 29 | The logic of data persisters is the responsibility of application developers, and is **out of the API Platform's scope**. 30 | 31 | For [Rapid Application Development](https://en.wikipedia.org/wiki/Rapid_application_development), convenience and prototyping, 32 | **if and only if the class marked with `@ApiResource` is also a Doctrine entity**, the developer can use the Doctrine 33 | ORM's data provider and persister implementations shipped with API Platform. 34 | 35 | In this case, the public (`@ApiResource`) and internal (Doctrine entity) data models are shared. Then, API Platform will 36 | be able to query, filter, paginate and persist data automatically. 37 | This approach is super-convenient and efficient, but is probably **not a good idea** for non-[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) 38 | and/or large systems. 39 | Again, it's up to the developers to use, or to not use these built-in data providers/persisters depending on the business logic 40 | they are dealing with. 41 | API Platform makes it easy to create custom data providers and persisters. 42 | It also makes it easy to implement patterns such as [CQS](https://www.martinfowler.com/bliki/CommandQuerySeparation.html) 43 | or [CQRS](https://martinfowler.com/bliki/CQRS.html) thanks to [the Messenger Component integration](messenger.md) and the [DTO support](dto.md). 44 | 45 | Last but not least, to create [Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html)-based systems, a convenient 46 | approach is: 47 | 48 | * to persist data in an event store using a Messenger handler or a custom [data persister](data-persisters.md) 49 | * to create projections in standard RDBMS (PostgreSQL, MariaDB...) tables or views 50 | * to map those projections with read-only Doctrine entity classes **and** to mark those classes with `@ApiResource` 51 | 52 | You can then benefit from the built-in Doctrine filters, sorting, pagination, auto-joins and all of [the extension points](extending.md) provided by API Platform. 53 | -------------------------------------------------------------------------------- /core/extending-jsonld-context.md: -------------------------------------------------------------------------------- 1 | # Extending JSON-LD AND Hydra Contexts 2 | 3 | ## JSON-LD 4 | 5 |

JSON-LD screencast
Watch the JSON-LD screencast
6 | 7 | API Platform Core provides the possibility to extend the JSON-LD context of properties. This allows you to describe JSON-LD-typed 8 | values, inverse properties using the `@reverse` keyword and you can even overwrite the `@id` property this way. Everything you define 9 | within the following annotation will be passed to the context. This provides a generic way to extend the context. 10 | 11 | ```php 12 | Hydra screencast
Watch the Hydra screencast

75 | 76 | It's also possible to replace the Hydra context used documentation generator: 77 | 78 | ```php 79 | 110 | 111 | 112 | 116 | 117 | 118 | 119 | 120 | bar 121 | 122 | 123 | 124 | 125 | 126 | ``` 127 | -------------------------------------------------------------------------------- /admin/handling-relations-to-collections.md: -------------------------------------------------------------------------------- 1 | # Handling Relations to Collections 2 | 3 | API Platform Admin handles `to-one` and `to-many` relations automatically. 4 | Thanks to [the Schema.org support](schema.org.md), it's also easy to display the name of a related object instead of its IRI. 5 | 6 | Let's go one step further thanks to the [customization capabilities](customizing.md) of API Platform Admin by adding autocompletion support to form inputs. 7 | 8 | ## Using an Autocomplete Input for Relations 9 | 10 | Let's consider an API exposing `Person` and `Book` resources linked by a `many-to-many` 11 | relation (through the `authors` property). 12 | 13 | This API uses the following PHP code: 14 | 15 | ```php 16 | authors = new ArrayCollection(); 79 | } 80 | } 81 | ``` 82 | 83 | Notice the "partial search" [filter](../core/filters.md) on the `name` property of the `Book` resource class. 84 | 85 | Now, let's configure API Platform Admin to enable autocompletion for the relation selector: 86 | 87 | ```javascript 88 | import React from "react"; 89 | import { 90 | HydraAdmin, 91 | ResourceGuesser, 92 | CreateGuesser, 93 | EditGuesser, 94 | InputGuesser 95 | } from "@api-platform/admin"; 96 | import { ReferenceInput, AutocompleteInput } from "react-admin"; 97 | 98 | const ReviewsCreate = props => ( 99 | 100 | 101 | ({ title: searchText })} 106 | > 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | ); 115 | 116 | const ReviewsEdit = props => ( 117 | 118 | 119 | 120 | ({ title: searchText })} 125 | > 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | ); 134 | 135 | export default () => ( 136 | 137 | 142 | 143 | ); 144 | ``` 145 | 146 | The autocomplete field should now work properly! 147 | -------------------------------------------------------------------------------- /schema-generator/index.md: -------------------------------------------------------------------------------- 1 | # The Schema Generator 2 | 3 | `schema` is a command line tool part of [the API Platform framework](https://api-platform.com) that instantly generates 4 | a PHP data model from the [Schema.org](http://schema.org) vocabulary. 5 | Browse Schema.org, choose the types and properties you need, run our code generator and you're done! You get 6 | a fully featured PHP data model including: 7 | 8 | * A set of PHP entities with properties, constants (enum values), getters, setters, adders and removers. The class 9 | hierarchy provided by Schema.org will be translated to a PHP class hierarchy with parents as `abstract` classes. The generated 10 | code complies with [PSR](http://www.php-fig.org/) coding standards. 11 | * Full high-quality PHPDoc for classes, properties, constants and methods extracted from Schema.org. 12 | * Doctrine ORM annotation mapping including database columns with type guessing, relations with cardinality guessing, class 13 | inheritance (through the `@AbstractSuperclass` annotation). 14 | * Data validation through [Symfony Validator](http://symfony.com/doc/current/book/validation.html) annotations including 15 | data type validation, enum support (choices) and check for required properties. 16 | * Interfaces and [Doctrine `ResolveTargetEntityListener`](https://www.doctrine-project.org/projects/doctrine-orm/en/current/cookbook/resolve-target-entity-listener.html) 17 | support. 18 | * Custom PHP namespace support. 19 | * List of values provided by Schema.org with [PHP Enum](https://github.com/myclabs/php-enum) classes. 20 | 21 | Bonus: 22 | 23 | * The code generator is fully configurable and extendable: all features can be deactivated (e.g.: the Doctrine mapping generator) 24 | and a custom generator can be added (e.g.: a Doctrine ODM mapping generator). 25 | * The generated code can be used as is in a [Symfony](http://symfony.com) app (but it will work too in a raw PHP project 26 | or any other framework including [Laravel](http://laravel.com) and [Zend Framework](http://framework.zend.com/)). 27 | 28 | ## What Is Schema.org? 29 | 30 | Schema.org is a vocabulary representing common data structures and their relations. Schema.org can be exposed as [JSON-LD](https://en.wikipedia.org/wiki/JSON-LD), 31 | [microdata](https://en.wikipedia.org/wiki/Microdata_(HTML)) and [RDFa](https://en.wikipedia.org/wiki/RDFa). 32 | Extracting semantical data exposed in the Schema.org vocabulary is supported by a growing number of companies including 33 | Google (Search, Gmail), Yahoo!, Bing and Yandex. 34 | 35 | ## Why Use Schema.org Data to Generate a PHP Model? 36 | 37 | ### Don't Reinvent the Wheel 38 | 39 | Data models provided by Schema.org are popular and were proven efficient. They cover a broad spectrum of topics including 40 | creative works, e-commerce, events, medicine, social networking, people, postal addresses, organization data, places or reviews. 41 | Schema.org has its root in a ton of preexisting well designed vocabularies and is 42 | successfully used by more and more websites and applications. 43 | 44 | Pick schemas applicable to your application, generate your PHP model, then customize and specialize it to fit your needs. 45 | 46 | ### Improve SEO and User Experience 47 | 48 | Adding Schema.org markup to websites and apps increases their ranking in search engines results and enables awesome features 49 | such as [Google Rich Snippets](https://support.google.com/webmasters/answer/99170?hl=en) and [Gmail markup](https://developers.google.com/gmail/markup/overview). 50 | 51 | Mapping your app data model to Schema.org structures can be tedious. When using the generator, your data model will be 52 | derived from Schema.org. Adding microdata markup to your templates or serializing your data as JSON-LD will not require 53 | specific mapping nor adaptation. It's a matter of minutes. 54 | 55 | ### Be Ready for The Future 56 | 57 | Schema.org improves the interoperability of your applications. Used with hypermedia technologies such as [Hydra](http://www.hydra-cg.com/) 58 | it's a big step towards the semantic and machine readable web. 59 | It opens the way to generic web API clients able to extract and process data from any website or app using such technologies. 60 | 61 | ## Documentation 62 | 63 | * [Getting Started](getting-started.md) 64 | * [Configuration](configuration.md) 65 | -------------------------------------------------------------------------------- /core/form-data.md: -------------------------------------------------------------------------------- 1 | # Accept `application/x-www-form-urlencoded` Form Data 2 | 3 | API Platform only supports raw documents as request input (encoded in JSON, XML, YAML...). This has many advantages including support of types and the ability to send back to the API documents originally retrieved through a `GET` request. 4 | However, sometimes - for instance, to support legacy clients - it is necessary to accept inputs encoded in the traditional [`application/x-www-form-urlencoded`](https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1) format (HTML form content type). This can easily be done using [the powerful event system](events.md) of the framework. 5 | 6 | **⚠ Adding support for `application/x-www-form-urlencoded` makes your API vulnerable to [CSRF attacks](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)). Be sure to enable proper countermeasures [such as DunglasAngularCsrfBundle](https://github.com/dunglas/DunglasAngularCsrfBundle).** 7 | 8 | In this tutorial, we will decorate the default `DeserializeListener` class to handle form data if applicable, and delegate to the built-in listener for other cases. 9 | 10 | ## Create your `DeserializeListener` Decorator 11 | 12 | This decorator is able to denormalize posted form data to the target object. In case of other format, it fallbacks to the original [DeserializeListener](https://github.com/api-platform/core/blob/91dc2a4d6eeb79ea8dec26b41e800827336beb1a/src/Bridge/Symfony/Bundle/Resources/config/api.xml#L85-L91). 13 | 14 | ```php 15 | denormalizer = $denormalizer; 36 | $this->serializerContextBuilder = $serializerContextBuilder; 37 | $this->decorated = $decorated; 38 | } 39 | 40 | public function onKernelRequest(GetResponseEvent $event): void { 41 | $request = $event->getRequest(); 42 | if ($request->isMethodSafe(false) || $request->isMethod(Request::METHOD_DELETE)) { 43 | return; 44 | } 45 | 46 | if ('form' === $request->getContentType()) { 47 | $this->denormalizeFormRequest($request); 48 | } else { 49 | $this->decorated->onKernelRequest($event); 50 | } 51 | } 52 | 53 | private function denormalizeFormRequest(Request $request): void 54 | { 55 | if (!$attributes = RequestAttributesExtractor::extractAttributes($request)) { 56 | return; 57 | } 58 | 59 | $context = $this->serializerContextBuilder->createFromRequest($request, false, $attributes); 60 | $populated = $request->attributes->get('data'); 61 | if (null !== $populated) { 62 | $context['object_to_populate'] = $populated; 63 | } 64 | 65 | $data = $request->request->all(); 66 | $object = $this->denormalizer->denormalize($data, $attributes['resource_class'], null, $context); 67 | $request->attributes->set('data', $object); 68 | } 69 | } 70 | ``` 71 | 72 | ## Creating the Service Definition 73 | 74 | ```yaml 75 | # api/config/services.yaml 76 | services: 77 | # ... 78 | 'App\EventListener\DeserializeListener': 79 | tags: 80 | - { name: 'kernel.event_listener', event: 'kernel.request', method: 'onKernelRequest', priority: 2 } 81 | # Autoconfiguration must be disabled to set a custom priority 82 | autoconfigure: false 83 | decorates: 'api_platform.listener.request.deserialize' 84 | arguments: 85 | $decorated: '@App\EventListener\DeserializeListener.inner' 86 | ``` 87 | -------------------------------------------------------------------------------- /core/identifiers.md: -------------------------------------------------------------------------------- 1 | # Identifiers 2 | 3 | Every item operation has an identifier in its URL. Although this identifier is usually a number, it can also be an `UUID`, a date, or the type of your choice. 4 | To help with your development experience, we introduced an identifier normalization process. 5 | 6 | ## Custom Identifier Normalizer 7 | 8 | > In the following chapter, we're assuming that `App\Uuid` is a project-owned class that manages a time-based UUID. 9 | 10 | Let's say you have the following class, which is identified by a `UUID` type. In this example, `UUID` is not a simple string but an object with many attributes. 11 | 12 | ```php 13 | getTimestamp() 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 62 | { 63 | return $resourceClass === Person::class; 64 | } 65 | } 66 | ``` 67 | 68 | To cover this use case, we need to `denormalize` the identifier to an instance of our `App\Uuid` class. This case is covered by an identifier denormalizer: 69 | 70 | ```php 71 | getMessage()); 89 | } 90 | } 91 | 92 | /** 93 | * {@inheritdoc} 94 | */ 95 | public function supportsDenormalization($data, $type, $format = null) 96 | { 97 | return is_a($type, Uuid::class, true); 98 | } 99 | } 100 | ``` 101 | 102 | Tag this service as an `api_platform.identifier.denormalizer`: 103 | 104 | ```xml 105 | 106 | 107 | 108 | ``` 109 | 110 | ```yaml 111 | services: 112 | App\Identifier\UuidNormalizer: 113 | tags: 114 | - { name: api_platform.identifier.denormalizer } 115 | ``` 116 | 117 | Your `PersonDataProvider` will now work as expected! 118 | 119 | 120 | ## Supported Identifiers 121 | 122 | API Platform supports the following identifier types: 123 | 124 | - `scalar` (string, integer) 125 | - `\DateTime` (uses the symfony `DateTimeNormalizer` internally, see [DateTimeIdentifierNormalizer](https://github.com/api-platform/core/blob/master/src/Identifier/Normalizer/DateTimeIdentifierDenormalizer.php)) 126 | - `\Ramsey\Uuid\Uuid` (see [UuidNormalizer](https://github.com/api-platform/core/blob/master/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php)) 127 | -------------------------------------------------------------------------------- /core/errors.md: -------------------------------------------------------------------------------- 1 | # Errors Handling 2 | 3 | API Platform comes with a powerful error system. It handles expected (such as faulty JSON documents sent by the 4 | client or validation errors) as well as unexpected errors (PHP exceptions and errors). 5 | API Platform automatically sends the appropriate HTTP status code to the client: `400` for expected errors, `500` for 6 | unexpected ones. It also provides a description of the error in [the Hydra error format](https://www.hydra-cg.com/spec/latest/core/#description-of-http-status-codes-and-errors) 7 | or in the format described in the [RFC 7807](https://tools.ietf.org/html/rfc7807), depending of the format selected during the [content negotiation](content-negotiation.md). 8 | 9 | ## Converting PHP Exceptions to HTTP Errors 10 | 11 | The framework also allows you configure the HTTP status code sent to the clients when custom exceptions are thrown. 12 | 13 | In the following example, we throw a domain exception from the business layer of the application and 14 | configure API Platform to convert it to a `404 Not Found` error: 15 | 16 | ```php 17 | ['checkProductAvailability', EventPriorities::PRE_VALIDATE], 47 | ]; 48 | } 49 | 50 | public function checkProductAvailability(GetResponseForControllerResultEvent $event): void 51 | { 52 | $product = $event->getControllerResult(); 53 | if (!$product instanceof Product || !$event->getRequest()->isMethodSafe(false)) { 54 | return; 55 | } 56 | 57 | if (!$product->isPubliclyAvailable()) { 58 | // Using internal codes for a better understanding of what's going on 59 | throw new ProductNotFoundException(sprintf('The product "%s" does not exist.', $product->getId())); 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | If you use the standard distribution of API Platform, this event listener will be automatically registered. If you use a 66 | custom installation, [learn how to register listeners](events.md#custom-event-listeners). 67 | 68 | Then, configure the framework to catch `App\Exception\ProductNotFoundException` exceptions and convert them into `404` 69 | errors: 70 | 71 | ```yaml 72 | # config/packages/api_platform.yaml 73 | api_platform: 74 | # ... 75 | exception_to_status: 76 | # The 4 following handlers are registered by default, keep those lines to prevent unexpected side effects 77 | Symfony\Component\Serializer\Exception\ExceptionInterface: 400 # Use a raw status code (recommended) 78 | ApiPlatform\Core\Exception\InvalidArgumentException: !php/const Symfony\Component\HttpFoundation\Response::HTTP_BAD_REQUEST 79 | ApiPlatform\Core\Exception\FilterValidationException: 400 80 | Doctrine\ORM\OptimisticLockException: 409 81 | 82 | # Custom mapping 83 | App\Exception\ProductNotFoundException: 404 # Here is the handler for our custom exception 84 | ``` 85 | 86 | Any type of `Exception` can be thrown, API Platform will convert it to a Symfony's `HttpException`. The framework also takes 87 | care of serializing the error description according to the request format. For instance, if the API should respond in JSON-LD, 88 | the error will be returned in this format as well: 89 | 90 | `GET /products/1234` 91 | 92 | ```json 93 | { 94 | "@context": "/contexts/Error", 95 | "@type": "Error", 96 | "hydra:title": "An error occurred", 97 | "hydra:description": "The product \"1234\" does not exist." 98 | } 99 | ``` 100 | -------------------------------------------------------------------------------- /deployment/heroku.md: -------------------------------------------------------------------------------- 1 | # Deploying an API Platform App on Heroku 2 | 3 | [Heroku](https://www.heroku.com) is a popular, fast, scalable and reliable *Platform As A Service* (PaaS). As Heroku offers a 4 | free plan including database support through [Heroku Postgres](https://www.heroku.com/postgres), it's a convenient way 5 | to experiment with API Platform. 6 | 7 | The API Platform Heroku integration also supports MySQL databases provided by [the ClearDB add-on](https://addons.heroku.com/cleardb). 8 | 9 | Deploying API Platform applications on Heroku is straightforward and you will learn how to do it in this tutorial. 10 | 11 | *Note: this tutorial works perfectly well with API Platform but also with any Symfony application based on the Symfony Standard 12 | Edition.* 13 | 14 | If you don't already have one, [create an account on Heroku](https://signup.heroku.com/signup/dc). Then install [the Heroku 15 | toolbelt](https://devcenter.heroku.com/articles/getting-started-with-php#set-up). We're guessing you already 16 | have a working install of [Composer](http://getcomposer.org). Perfect, we will need it. 17 | 18 | Create a new [API Platform project](distribution/index.md) which will be used in the rest of this example. 19 | 20 | Heroku relies on [environment variables](https://devcenter.heroku.com/articles/config-vars) for its configuration. Regardless 21 | of what provider you choose for hosting your application, using environment variables to configure your production environment 22 | is a best practice promoted by API Platform. 23 | 24 | Create a Heroku `app.json` file at the root of the `api/` directory to configure the deployment: 25 | 26 | ```json 27 | { 28 | "success_url": "/", 29 | "env": { 30 | "APP_ENV": "prod", 31 | "APP_SECRET": {"generator": "secret"}, 32 | "CORS_ALLOW_ORIGIN": "https://your-client-url.com" 33 | }, 34 | "addons": [ 35 | "heroku-postgresql" 36 | ], 37 | "buildpacks": [ 38 | { 39 | "url": "https://github.com/heroku/heroku-buildpack-php" 40 | } 41 | ], 42 | "scripts": { 43 | "postdeploy": "php bin/console doctrine:schema:create" 44 | } 45 | } 46 | ``` 47 | 48 | The file also tells the Heroku deployment system to build a PHP container and to add the Postgres add-on. 49 | 50 | We are almost done, but API Platform (and Symfony) has a particular directory structure which requires further configuration. 51 | We must tell Heroku that the document root is `public/`, and that all other directories must be private. 52 | 53 | Create a new file named `Procfile` in the `api/` directory with the following content: 54 | 55 | ```yaml 56 | web: vendor/bin/heroku-php-apache2 public/ 57 | ``` 58 | 59 | Be sure to add the Apache Pack to your dependencies: 60 | 61 | composer require symfony/apache-pack 62 | 63 | As Heroku doesn't support Varnish out of the box, let's disable its integration: 64 | 65 | ```diff 66 | # api/config/packages/api_platform.yaml 67 | - http_cache: 68 | - invalidation: 69 | - enabled: true 70 | - varnish_urls: ['%env(VARNISH_URL)%'] 71 | - max_age: 0 72 | - shared_max_age: 3600 73 | - vary: ['Content-Type', 'Authorization', 'Origin'] 74 | - public: true 75 | ``` 76 | 77 | Heroku provides another free service, [Logplex](https://devcenter.heroku.com/articles/logplex), which allows us to centralize 78 | and persist application logs. Because API Platform writes logs on `STDERR`, it will work seamlessly. 79 | 80 | However, if you use Monolog instead of the default logger, you'll need to configure it to output to `STDERR` instead of 81 | in a file. 82 | 83 | Open `api/config/packages/prod/monolog.yaml` and apply the following patch: 84 | 85 | ```diff 86 | handlers: 87 | nested: 88 | type: stream 89 | - path: "%kernel.logs_dir%/%kernel.environment%.log" 90 | + path: php://stderr 91 | level: debug 92 | ``` 93 | 94 | We are now ready to deploy our app! 95 | 96 | Go to the `api/` directory, then 97 | 98 | 1. Initialize a git repository: 99 | 100 | git init 101 | 102 | 2. Add all existing files: 103 | 104 | git add --all 105 | 106 | 3. Commit: 107 | 108 | git commit -a -m "My first API Platform app running on Heroku!" 109 | 110 | 4. Create the Heroku application: 111 | 112 | heroku create 113 | 114 | 5. And deploy for the first time: 115 | 116 | git push heroku master 117 | 118 | **We're done.** You can play with the demo API provided with API Platform. It is ready for production and you 119 | can scale it in one click from the Heroku interface. 120 | 121 | To see your logs, run `heroku logs --tail`. 122 | -------------------------------------------------------------------------------- /core/fosuser-bundle.md: -------------------------------------------------------------------------------- 1 | # FOSUserBundle Integration 2 | 3 | API Platform Core is shipped with a bridge for [FOSUserBundle](https://github.com/FriendsOfSymfony/FOSUserBundle). 4 | If the FOSUser bundle is enabled, this bridge will use its `UserManager` to create, update and delete user resources. 5 | 6 | Note: FOSUserBundle is not well suited for APIs. We strongly encourage you to use the [Doctrine user provider](https://symfony.com/doc/current/security/user_provider.html#entity-user-provider) 7 | shipped with Symfony or to [create a custom user provider](https://symfony.com/doc/current/security/user_provider.html#creating-a-custom-user-provider) 8 | instead of using this bundle. 9 | 10 |

User Entity screencast
Watch the User Entity screencast

11 | 12 | ## Installing the Bundle 13 | 14 | The installation procedure of the FOSUserBundle is described [in the main Symfony docs](https://symfony.com/doc/master/bundles/FOSUserBundle/index.html) 15 | 16 | You can: 17 | 18 | * Skip [step 3 (Create your User class)](https://symfony.com/doc/master/bundles/FOSUserBundle/index.html#step-3-create-your-user-class) 19 | and use the class provided in the next paragraph to set up serialization groups the correct way 20 | * Skip [step 4 (Configure your application's security.yml)](https://symfony.com/doc/master/bundles/FOSUserBundle/index.html#step-4-configure-your-application-s-security-yml) 21 | if you are planning to [use a JWT-based authentication using `LexikJWTAuthenticationBundle`](jwt.md) 22 | 23 | If you are using the API Platform Standard Edition, you will need to enable the form services in the symfony framework 24 | configuration options: 25 | 26 | ```yaml 27 | # api/config/packages/framework.yaml 28 | framework: 29 | form: { enabled: true } 30 | ``` 31 | 32 | ## Enabling the Bridge 33 | 34 | To enable the provided bridge with FOSUserBundle, you need to add the following configuration to API Platform: 35 | ```yaml 36 | # api/config/packages/api_platform.yaml 37 | api_platform: 38 | enable_fos_user: true 39 | ``` 40 | 41 | ## Creating a `User` Entity with Serialization Groups 42 | 43 | Here's an example of declaration of a [Doctrine ORM User class](https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/index.rst#a-doctrine-orm-user-class). 44 | There's also an example for a [Doctrine MongoDB ODM](https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/index.rst#b-mongodb-user-class). 45 | You need to use serialization groups to hide some properties like `plainPassword` (only in read) and `password`. The properties 46 | shown are handled with [`normalization_context`](serialization.md#normalization), while the properties 47 | you can modify are handled with [`denormalization_context`](serialization.md#denormalization). 48 | 49 | Create your User entity with serialization groups: 50 | 51 | ```php 52 | fullname = $fullname; 104 | } 105 | 106 | public function getFullname(): ?string 107 | { 108 | return $this->fullname; 109 | } 110 | 111 | public function isUser(?UserInterface $user = null): bool 112 | { 113 | return $user instanceof self && $user->id === $this->id; 114 | } 115 | } 116 | ``` 117 | -------------------------------------------------------------------------------- /extra/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | This is a list of common pitfalls while using API Platform, and how to avoid them. 4 | 5 | ## Using Docker 6 | 7 | ### With Docker Toolbox on Windows 8 | 9 | Docker Toolbox is not supported anymore by API Platform. Please upgrade to [Docker for Windows](https://www.docker.com/docker-windows). 10 | 11 | ### Error starting userland proxy 12 | 13 | If the `app` container cannot start and display this `Error starting userland proxy: Bind for 0.0.0.0:80`, it means that port 80 is already in use. You can check to see which processes are currently listening on certain ports. 14 | 15 | # Find out if any service listens on port 80. 16 | # You can use this command on UNIX-based OSes like MacOS and Linux. 17 | $ sudo lsof -n -i :80 | grep LISTEN 18 | 19 | # For Windows, you can use netstat. 20 | # This will give you all TCP/IP network connections and not just processes listening to port 80. 21 | $ netstat -a -b 22 | 23 | You can change the port to be used in the `docker-compose.yml` file (default is port 80). 24 | 25 | ## Using API Platform and JMS Serializer in the same project 26 | 27 | For the latest versions of [JMSSerializerBundle](http://jmsyst.com/bundles/JMSSerializerBundle), there is no conflict so everything should work out of the box. 28 | 29 | If you are still using the old, unmaintained v1 of JMSSerializerBundle, the best way would be to [upgrade to v2](https://github.com/schmittjoh/JMSSerializerBundle/blob/2.4.2/UPGRADING.md#upgrading-from-1x-to-20) of JMSSerializerBundle. 30 | 31 | In v1 of JMSSerializerBundle, the `serializer` alias is registered for the JMS Serializer service by default. However, API Platform requires the Symfony Serializer (and not the JMS one) to work properly. If you cannot upgrade for some reason, this behavior can be deactivated using the following configuration: 32 | 33 | ```yaml 34 | # app/config/config.yml 35 | 36 | jms_serializer: 37 |    enable_short_alias: false 38 | ``` 39 | 40 | The JMS Serializer service is available as `jms_serializer`. 41 | 42 | ## "upstream sent too big header while reading response header from upstream" 502 Error 43 | 44 | Some of your API calls fail with a 502 error and the logs for the api container shows the following error message `upstream sent too big header while reading response header from upstream`. 45 | 46 | This can be due to the cache invalidation headers that are too big for NGINX. When you query the API, API Platform adds the ids of all returned entities and their dependencies in the headers like so : `Cache-Tags: /entity/1,/dependent_entity/1,/entity/2`. This can overflow the default header size (4k) when your API gets larger and more complex. 47 | 48 | You can modify the `api/docker/nginx/conf.d/default.conf` file and set values to `fastcgi_buffer_size` and `fastcgi_buffers` that suit your needs, like so: 49 | 50 | ``` 51 | server { 52 | root /srv/api/public; 53 | 54 | location / { 55 | # try to serve file directly, fallback to index.php 56 | try_files $uri /index.php$is_args$args; 57 | } 58 | 59 | location ~ ^/index\.php(/|$) { 60 | # Comment the next line and uncomment the next to enable dynamic resolution (incompatible with Kubernetes) 61 | fastcgi_pass php:9000; 62 | #resolver 127.0.0.11; 63 | #set $upstream_host php; 64 | #fastcgi_pass $upstream_host:9000; 65 | 66 | # Bigger buffer size to handle cache invalidation headers expansion 67 | fastcgi_buffer_size 32k; 68 | fastcgi_buffers 8 16k; 69 | 70 | fastcgi_split_path_info ^(.+\.php)(/.*)$; 71 | include fastcgi_params; 72 | # When you are using symlinks to link the document root to the 73 | # current version of your application, you should pass the real 74 | # application path instead of the path to the symlink to PHP 75 | # FPM. 76 | # Otherwise, PHP's OPcache may not properly detect changes to 77 | # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126 78 | # for more information). 79 | fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 80 | fastcgi_param DOCUMENT_ROOT $realpath_root; 81 | # Prevents URIs that include the front controller. This will 404: 82 | # http://domain.tld/index.php/some-path 83 | # Remove the internal directive to allow URIs like this 84 | internal; 85 | } 86 | 87 | # return 404 for all other php files not matching the front controller 88 | # this prevents access to other php files you don't want to be accessible. 89 | location ~ \.php$ { 90 | return 404; 91 | } 92 | } 93 | ``` 94 | 95 | You then need to rebuild your containers by running `docker-compose build`. 96 | -------------------------------------------------------------------------------- /client-generator/react.md: -------------------------------------------------------------------------------- 1 | # React Generator 2 | 3 | ![List screenshot](images/react/client-generator-react-list.png) 4 | 5 | The React Client Generator generates a Progressive Web App built with battle-tested libraries from the ecosystem: 6 | 7 | * [React](https://facebook.github.io/react/) 8 | * [Redux](http://redux.js.org) 9 | * [React Router](https://reacttraining.com/react-router/) 10 | * [Redux Form](http://redux-form.com/) 11 | 12 | It is designed to generate code that works seamlessly with [Facebook's Create React App](https://github.com/facebook/create-react-app). 13 | 14 | ## Install 15 | 16 | The easiest way to get started is to install [the API Platform distribution](../distribution/index.md). 17 | It contains the React Client Generator, all dependencies it needs, a Progressive Web App skeleton generated with Create React App, 18 | a development Docker container to serve the webapp, and all the API Platform components you may need, including an API server 19 | supporting Hydra. 20 | 21 | If you use API Platform, jump to the next section! 22 | Alternatively, you can generate a skeleton and install the generator using [npx](https://www.npmjs.com/package/npx). 23 | To use this generator you need [Node.js](https://nodejs.org/) and [Yarn](https://yarnpkg.com/) (or [npm](https://www.npmjs.com/)) installed. 24 | 25 | Bootstrap a React application: 26 | 27 | $ npx create-react-app client 28 | $ cd client 29 | 30 | Install the required dependencies: 31 | 32 | $ yarn add redux react-redux redux-thunk redux-form react-router-dom connected-react-router prop-types lodash 33 | 34 | Optionally, install Bootstrap and Font Awesome to get an app that looks good: 35 | 36 | $ yarn add bootstrap font-awesome 37 | 38 | Finally, start the integrated web server: 39 | 40 | $ yarn start 41 | 42 | ## Generating a Progressive Web App 43 | 44 | If you use the API Platform distribution, generating all the code you need for a given resource is as simple as running the following command: 45 | 46 | $ docker-compose exec client generate-api-platform-client --resource book 47 | 48 | Omit the resource flag to generate files for all resource types exposed by the API. 49 | 50 | If you don't use the standalone installation, run the following command instead: 51 | 52 | $ npx @api-platform/client-generator https://demo.api-platform.com src/ --resource book 53 | # Replace the URL with the entrypoint of your Hydra-enabled API 54 | 55 | The code has been generated, and is ready to be executed! 56 | Register the reducers and the routes in the `client/src/index.js` file: 57 | 58 | ```javascript 59 | import React from 'react'; 60 | import ReactDOM from 'react-dom'; 61 | import { createStore, combineReducers, applyMiddleware } from 'redux'; 62 | import { Provider } from 'react-redux'; 63 | import thunk from 'redux-thunk'; 64 | import { reducer as form } from 'redux-form'; 65 | import { Route, Switch } from 'react-router-dom'; 66 | import { createBrowserHistory } from "history"; 67 | import { 68 | ConnectedRouter, 69 | connectRouter, 70 | routerMiddleware 71 | } from 'connected-react-router'; 72 | import 'bootstrap/dist/css/bootstrap.css'; 73 | import 'font-awesome/css/font-awesome.css'; 74 | import * as serviceWorker from './serviceWorker'; 75 | // Replace "book" with the name of the resource type 76 | import book from './reducers/book/'; 77 | import bookRoutes from './routes/book'; 78 | 79 | const history = createBrowserHistory(); 80 | const store = createStore( 81 | combineReducers({ 82 | router: connectRouter(history), 83 | form, 84 | book 85 | /* Replace book with the name of the resource type */ 86 | }), 87 | applyMiddleware(routerMiddleware(history), thunk) 88 | ); 89 | 90 | ReactDOM.render( 91 | 92 | 93 | 94 | {bookRoutes} 95 | {/* Replace bookRoutes with the name of the resource type */} 96 |

Not Found

} /> 97 |
98 |
99 |
, 100 | document.getElementById('root') 101 | ); 102 | 103 | // If you want your app to work offline and load faster, you can change 104 | // unregister() to register() below. Note this comes with some pitfalls. 105 | // Learn more about service workers: http://bit.ly/CRA-PWA 106 | serviceWorker.unregister(); 107 | ``` 108 | 109 | Go to `https://localhost/books/` to start using your app. 110 | That's all! 111 | 112 | ## Screenshots 113 | 114 | ![List](images/react/client-generator-react-list.png) 115 | ![Pagination](images/react/client-generator-react-list-pagination.png) 116 | ![Show](images/react/client-generator-react-show.png) 117 | ![Edit](images/react/client-generator-react-edit.png) 118 | ![Delete](images/react/client-generator-react-delete.png) 119 | -------------------------------------------------------------------------------- /core/testing.md: -------------------------------------------------------------------------------- 1 | # Testing Utilities 2 | 3 | API Platform Core provides a set of useful utilities dedicated to API testing. 4 | For an overview of how to test an API Platform app, be sure to read [the testing cookbook first](../distribution/testing.md). 5 | 6 |

Test and Assertions screencast
Watch the API Tests & Assertions screencast

7 | 8 | ## The Test HttpClient 9 | 10 | API Platform provides its own implementation of the [Symfony HttpClient](https://symfony.com/doc/current/components/http_client.html)'s interfaces, tailored to be used directly in [PHPUnit](https://phpunit.de/) test classes. 11 | 12 | While all the convenient features of Symfony HttpClient are available and usable directly, under the hood the API Platform implementation manipulates [the Symfony HttpKernel](https://symfony.com/doc/current/components/http_kernel.html) directly to simulate HTTP requests and responses. 13 | This approach results in a huge performance boost compared to triggering real network requests. 14 | It also allows access to the [Symfony HttpKernel](https://symfony.com/doc/current/components/http_kernel.html and to all your services via the [Dependency Injection Container](https://symfony.com/doc/current/testing.html#accessing-the-container). 15 | Reuse them to run, for instance, SQL queries or requests to external APIs directly from your tests. 16 | 17 | Install the `symfony/http-client` and `symfony/browser-kit` packages to enabled the API Platform test client: 18 | 19 | $ docker-compose exec php composer require symfony/http-client symfony/browser-kit 20 | 21 | To use the testing client, your test class must extend the `ApiTestCase` class: 22 | 23 | ```php 24 | request('GET', '/books'); 36 | // your assertions here... 37 | } 38 | } 39 | ``` 40 | 41 | Refer to [the Symfony HttpClient documentation](https://symfony.com/doc/current/components/http_client.html) to discover all the features of the client (custom headers, JSON encoding and decoding, HTTP Basic and Bearer authentication and cookies support, among other things). 42 | 43 | 44 | ## API Test Assertions 45 | 46 | In addition to [the built-in ones](https://phpunit.readthedocs.io/en/latest/assertions.html), API Platform provides convenient PHPUnit assertions dedicated to API testing: 47 | 48 | ```php 49 | request(...); 61 | 62 | // Asserts that the returned JSON is equal to the passed one 63 | $this->assertJsonEquals(/* a JSON document as an array or as a string */); 64 | 65 | // Asserts that the returned JSON is a superset of the passed one 66 | $this->assertJsonContains(/* a JSON document as an array or as a string */); 67 | 68 | // justinrainbow/json-schema must be installed to use the following assertions 69 | 70 | // Asserts that the returned JSON matches the passed JSON Schema 71 | $this->assertMatchesJsonSchema(/* a JSON Schema as an array or as a string */); 72 | 73 | // Asserts that the returned JSON is validated by the JSON Schema generated for this resource by API Platform 74 | 75 | // For collections 76 | $this->assertMatchesResourceCollectionJsonSchema(YourApiResource::class); 77 | // And for items 78 | $this->assertMatchesResourceItemJsonSchema(YourApiResource::class); 79 | } 80 | } 81 | ``` 82 | 83 | ## HTTP Test Assertions 84 | 85 | All tests assertions provided by Symfony (assertions for status codes, headers, cookies, XML documents...) can be used out of the box with the API Platform test client: 86 | 87 | ```php 88 | request('GET', '/books'); 100 | 101 | $this->assertResponseIsSuccessful(); 102 | $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); 103 | } 104 | } 105 | ``` 106 | 107 | [Check out the dedicated Symfony documentation entry](https://symfony.com/doc/current/testing/functional_tests_assertions.html). 108 | -------------------------------------------------------------------------------- /deployment/kubernetes.md: -------------------------------------------------------------------------------- 1 | # Deploying to a Kubernetes Cluster 2 | 3 | [Kubernetes](https://kubernetes.io/) has become the most popular way to deploy, run and manage containers in production. 4 | Both [Google Cloud Platform](https://cloud.google.com/kubernetes-engine/), [Microsoft Azure](https://azure.microsoft.com/en-us/services/container-service/kubernetes/) 5 | and [Amazon Web Services](https://aws.amazon.com/eks/) provide managed Kubernetes environment. 6 | 7 | [The official API Platform distribution](../distribution/index.md) contains a built-in [Helm](https://helm.sh/) (the k8s 8 | package manager) chart to deploy in a wink on any of these platforms. 9 | 10 | ## Preparing Your Cluster and Your Local Machine 11 | 12 | 1. Create a Kubernetes cluster on your preferred Cloud provider or install Kubernetes locally on your servers 13 | 2. Install [Helm](https://helm.sh/) locally and on your cluster following their documentation 14 | 3. Be sure to be connected to the right Kubernetes container e.g. running: `gcloud config get-value core/project` 15 | 4. Update the Helm repo: `helm repo update` 16 | 17 | ## Creating and Publishing the Docker Images 18 | 19 | 1. Build the PHP and NGINX Docker images: 20 | 21 | docker build -t gcr.io/test-api-platform/php -t gcr.io/test-api-platform/php:latest api --target api_platform_php 22 | docker build -t gcr.io/test-api-platform/nginx -t gcr.io/test-api-platform/nginx:latest api --target api_platform_nginx 23 | docker build -t gcr.io/test-api-platform/varnish -t gcr.io/test-api-platform/varnish:latest api --target api_platform_varnish 24 | 25 | 2. Push your images to your Docker registry, example with [Google Container Registry](https://cloud.google.com/container-registry/): 26 | 27 | Docker client versions <= 18.03: 28 | 29 | gcloud docker -- push gcr.io/test-api-platform/php 30 | gcloud docker -- push gcr.io/test-api-platform/nginx 31 | gcloud docker -- push gcr.io/test-api-platform/varnish 32 | 33 | Docker client versions > 18.03: 34 | 35 | gcloud auth configure-docker 36 | docker push gcr.io/test-api-platform/php 37 | docker push gcr.io/test-api-platform/nginx 38 | docker push gcr.io/test-api-platform/varnish 39 | 40 | ## Deploying 41 | 42 | Firstly you need to update helm dependencies by running: 43 | 44 | helm dependency update ./api/helm/api 45 | 46 | You are now ready to deploy the API! 47 | 48 | Deploy your API to the container: 49 | 50 | helm install ./api/helm/api --namespace=baz --name baz \ 51 | --set php.repository=gcr.io/test-api-platform/php \ 52 | --set nginx.repository=gcr.io/test-api-platform/nginx \ 53 | --set secret=MyAppSecretKey \ 54 | --set postgresql.postgresPassword=MyPgPassword \ 55 | --set postgresql.persistence.enabled=true \ 56 | --set corsAllowOrigin='^https?://[a-z\]*\.mywebsite.com$' 57 | 58 | If you prefer to use a managed DBMS like [Heroku Postgres](https://www.heroku.com/postgres) or 59 | [Google Cloud SQL](https://cloud.google.com/sql/docs/postgres/) (recommended): 60 | 61 | helm install --name api ./api/helm/api \ 62 | # ... 63 | --set postgresql.enabled=false \ 64 | --set postgresql.url=pgsql://username:password@host/database?serverVersion=9.6 65 | 66 | If you want to use a managed Varnish such as [Fastly](https://www.fastly.com) for the invalidation cache mechanism 67 | provided by API Platform: 68 | 69 | helm install --name api ./api/helm/api \ 70 | # ... 71 | --set varnish.enabled=false \ 72 | --set varnish.url=https://myvarnish.com 73 | 74 | Finally, build the `client` and `admin` JavaScript apps and [deploy them on a static 75 | website hosting service](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#deployment). 76 | 77 | ## Initializing the Database 78 | 79 | Before running your application for the first time, be sure to create the database schema: 80 | 81 | PHP_POD=$(kubectl --namespace=bar get pods -l app=php -o jsonpath="{.items[0].metadata.name}") 82 | kubectl --namespace=bar exec -it $PHP_POD -- bin/console doctrine:schema:create 83 | 84 | ## Tiller RBAC Issue 85 | 86 | We noticed that some tiller RBAC trouble occurred. You can usually resolve it by running: 87 | 88 | kubectl create serviceaccount --namespace kube-system tiller 89 | serviceaccount "tiller" created 90 | 91 | kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller 92 | clusterrolebinding "tiller-cluster-rule" created 93 | 94 | kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}' 95 | deployment "tiller-deploy" patched 96 | 97 | Please, see the [related issue](https://github.com/kubernetes/helm/issues/3130) for further details / information. 98 | You can also take a look at the [related documentation](https://github.com/kubernetes/helm/blob/master/docs/rbac.md) 99 | -------------------------------------------------------------------------------- /core/mercure.md: -------------------------------------------------------------------------------- 1 | # Pushing Live Updates Using the Mercure Protocol 2 | 3 | API Platform can automatically send real time updates to the currently connected clients (webapps, mobile apps...) using [the Mercure protocol](https://mercure.rocks). 4 | 5 | > *Mercure* is a protocol allowing to push data updates to web browsers and other HTTP clients in a convenient, fast, reliable and battery-efficient way. It is especially useful to publish real-time updates of resources served through web APIs, to reactive web and mobile apps. 6 | > 7 | > —https://mercure.rocks 8 | 9 | API Platform detects changes made to your Doctrine entities, and sends the updated resources to the Mercure hub. 10 | Then, the Mercure hub dispatches the updates to all connected clients using [Server-sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). 11 | 12 | ![Mercure subscriptions](images/mercure-subscriptions.png) 13 | 14 | ## Installing Mercure Support 15 | 16 | Mercure support is already installed, configured and enabled in [the API Platform distribution](../distribution/index.md). 17 | If you use the distribution, you have nothing more to do, and you can skip to the next section. 18 | 19 | If you have installed API Platform using another method (such as `composer require api`), you need to install a Mercure hub, and the [Symfony MercureBundle](https://github.com/symfony/mercure-bundle): 20 | 21 | First, [download and run a Mercure hub](https://github.com/dunglas/mercure#hub-implementation). 22 | Then, install the Symfony bundle: 23 | 24 | $ composer require mercure 25 | 26 | Finally, 3 environment variables [must be set](https://symfony.com/doc/current/configuration/external_parameters.html): 27 | 28 | * `MERCURE_PUBLISH_URL`: the URL that must be used by API Platform to publish updates to your Mercure hub (can be an internal or a public URL) 29 | * `MERCURE_SUBSCRIBE_URL`: the **public** URL of the Mercure hub that clients will use to subscribe to updates 30 | * `MERCURE_JWT_SECRET`: a valid Mercure [JSON Web Token (JWT)](https://jwt.io/) allowing API Platform to publish updates to the hub 31 | 32 | The JWT **must** contain a `mercure.publish` property containing an array of targets. 33 | This array can be empty to allow publishing anonymous updates only. 34 | [Example publisher JWT](https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyJmb28iLCJiYXIiXSwicHVibGlzaCI6WyJmb28iXX19.LRLvirgONK13JgacQ_VbcjySbVhkSmHy3IznH3tA9PM) (demo key: `!UnsecureChangeMe!`). 35 | 36 | [Learn more about Mercure authorization.](https://github.com/dunglas/mercure/blob/master/spec/mercure.md#authorization) 37 | 38 | ## Pushing the API Updates 39 | 40 | Use the `mercure` attribute to hint API Platform that it must dispatch the updates regarding the given resources to the Mercure hub: 41 | 42 | ```php 43 | i18n.t(key, params) 90 | } 91 | }; 92 | 93 | export default new Vuetify(opts); 94 | 95 | The generator comes with a i18n feature to allow quick translations of some labels in the generated code, to make it 96 | work, you need to create the `src/i18n.js` file with the following: 97 | ``` 98 | import Vue from 'vue'; 99 | import VueI18n from 'vue-i18n'; 100 | import messages from './locales/en'; 101 | Vue.use(VueI18n); 102 | 103 | export default new VueI18n({ 104 | locale: process.env.VUE_APP_I18N_LOCALE || 'en', 105 | fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en', 106 | messages 107 | }); 108 | ``` 109 | 110 | Update your App.vue with following: 111 | ``` 112 | 161 | 162 | 177 | ``` 178 | 179 | To finish, update your `main.js` with the following : 180 | 181 | ```javascript 182 | import Vue from 'vue'; 183 | import Vuelidate from 'vuelidate'; 184 | import App from './App.vue'; 185 | import vuetify from './plugins/vuetify'; 186 | 187 | import store from './store'; 188 | import router from './router'; 189 | import i18n from './i18n'; 190 | 191 | Vue.config.productionTip = false; 192 | 193 | Vue.use(Vuelidate); 194 | 195 | new Vue({ 196 | i18n, 197 | router, 198 | store, 199 | vuetify, 200 | render: h => h(App) 201 | }).$mount('#app'); 202 | 203 | ``` 204 | 205 | Go to `https://localhost:8000/books/` to start using your app. 206 | That's all! 207 | -------------------------------------------------------------------------------- /core/extending.md: -------------------------------------------------------------------------------- 1 | # Extending API Platform 2 | 3 | Because it handles the complex, tedious and repetitive task of creating an API infrastructure for you, API Platform lets you focus on what matter the most for the end user: the business logic. 4 | To do so, API Platform provides a lot of extension points you can use to hook your own code. 5 | Those extensions points are taken into account both by the REST and [GraphQL](graphql.md) subsystems. 6 | 7 | The following tables summarizes which extension point to use depending on what you want to do: 8 | 9 | | Extension Point | Usage | 10 | |------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 11 | | [Data Providers](data-providers.md) | adapters for custom persistence layers, virtual fields, custom hydration | 12 | | [Denormalizers](serialization.md) | post-process objects created from the payload sent in the HTTP request body | 13 | | [Voters](security.md#hooking-custom-permission-checks-using-voters) | custom authorization logic | 14 | | [Validation constraints](validation.md) | custom validation logic | 15 | | [Data Persisters](data-persisters) | custom business logic and computations to trigger before, during or after persistence (ex: mail, call to an external API...) | 16 | | [Normalizers](serialization.md#decorating-a-serializer-and-adding-extra-data) | customize the resource sent to the client (add fields in JSON documents, encode codes, dates...) | 17 | | [Filters](filters.md) | create filters for collections and automatically document them (OpenAPI, GraphQL, Hydra) | 18 | | [Serializer Context Builders](serialization.md#changing-the-serialization-context-dynamically) | Changing the Serialization context (e.g. groups) dynamically | 19 | | [Messenger Handlers](messenger.md) | create 100% custom, RPC, async, service-oriented endpoints (should be used in place of custom controllers because the messenger integration is compatible with both REST and GraphQL, while custom controllers only work with REST) | 20 | | [DTOs and Data Transformers](dto.md) | use a specific class to represent the input or output data structure related to an operation | 21 | | [Kernel Events](events.md) | customize the HTTP request or response (REST only, other extension points must be preferred when possible) | 22 | 23 | ## Doctrine Specific Extension Points 24 | 25 | | Extension Point | Usage | 26 | |------------------------------------------------------------|----------------------------------------------------------------------------------------------------| 27 | | [Extensions](extensions.md) | Access to the query builder to change the DQL query | 28 | | [Filters](filters.md#doctrine-orm-and-mongodb-odm-filters) | Add filters documentations (OpenAPI, GraphQL, Hydra) and automatically apply them to the DQL query | 29 | 30 | ## Leveraging the Built-in Infrastructure Using Composition 31 | 32 | While most API Platform classes are marked as `final`, built-in services are straightforward to reuse and customize [using composition](https://en.wikipedia.org/wiki/Composition_over_inheritance). 33 | 34 | For instance, if you want to send a mail after a resource has been persisted, but still want to benefit from the native Doctrine ORM [data persister](data-persisters.md), use [the decorator design pattern](https://en.wikipedia.org/wiki/Decorator_pattern#PHP) to wrap the native data persister in your own class sending the mail. 35 | 36 | To replace existing API Platform services with your decorators, [check out how to decorate services](https://symfony.com/doc/current/service_container/service_decoration.html). 37 | 38 |

Service Decoration screencast
Watch the Service Decoration screencast

39 | -------------------------------------------------------------------------------- /core/mongodb.md: -------------------------------------------------------------------------------- 1 | # MongoDB Support 2 | 3 | ## Overview 4 | 5 | [MongoDB](https://www.mongodb.com/) is one of the most popular NoSQL document-oriented database, used for its high 6 | write load (useful for analytics or IoT) and high availability (easy to set replica sets with automatic failover). It 7 | can also shard the database easily for horizontal scalability and has a powerful query language for doing aggregation, 8 | text search or geospatial queries. 9 | 10 | API Platform uses [Doctrine MongoDB ODM 2](https://www.doctrine-project.org/projects/mongodb-odm.html) and in particular 11 | its [aggregation builder](https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/aggregation-builder.html) 12 | to leverage all the possibilities of the database. 13 | 14 | Doctrine MongoDB ODM 2 relies on the [mongodb](https://secure.php.net/manual/en/set.mongodb.php) PHP extension and not on 15 | the legacy [mongo](https://secure.php.net/manual/en/book.mongo.php) extension. 16 | 17 | ## Enabling MongoDB Support 18 | 19 | If the `mongodb` PHP extension is not installed yet, [install it beforehand](https://secure.php.net/manual/en/mongodb.installation.pecl.php). 20 | 21 | If you are using the [API Platform Distribution](../distribution/index.md), modify the `Dockerfile` to add the extension: 22 | 23 | ```diff 24 | # api/Dockerfile 25 | pecl install \ 26 | apcu-${APCU_VERSION} \ 27 | + mongodb \ 28 | ; \ 29 | pecl clear-cache; \ 30 | docker-php-ext-enable \ 31 | apcu \ 32 | + mongodb \ 33 | opcache \ 34 | ; \ 35 | ``` 36 | 37 | Then rebuild the `php` image: 38 | 39 | $ docker-compose build php 40 | 41 | Add a MongoDB image to the docker-compose file: 42 | 43 | ```yaml 44 | # docker-compose.yml 45 | # ... 46 | db-mongodb: 47 | # In production, you may want to use a managed database service 48 | image: mongo 49 | environment: 50 | - MONGO_INITDB_DATABASE=api 51 | - MONGO_INITDB_ROOT_USERNAME=api-platform 52 | # You should definitely change the password in production 53 | - MONGO_INITDB_ROOT_PASSWORD=!ChangeMe! 54 | volumes: 55 | - db-data:/var/lib/mongodb/data:rw 56 | # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! 57 | # - ./docker/db/data:/var/lib/mongodb/data:rw 58 | ports: 59 | - "27017:27017" 60 | # ... 61 | ``` 62 | 63 | Once the extension is installed, to enable the MongoDB support, require the [Doctrine MongoDB ODM bundle](https://github.com/doctrine/DoctrineMongoDBBundle) 64 | package using Composer: 65 | 66 | $ docker-compose exec php composer req doctrine/mongodb-odm-bundle 67 | 68 | Execute the contrib recipe to have it already configured. 69 | 70 | Change the MongoDB environment variables to match your Docker image: 71 | 72 | ``` 73 | # api/.env 74 | MONGODB_URL=mongodb://api-platform:!ChangeMe!@db-mongodb 75 | MONGODB_DB=api 76 | ``` 77 | 78 | Change the configuration of API Platform to add the right mapping path: 79 | 80 | ```yaml 81 | # api/config/packages/api_platform.yaml 82 | api_platform: 83 | # ... 84 | 85 | mapping: 86 | paths: ['%kernel.project_dir%/src/Entity', '%kernel.project_dir%/src/Document'] 87 | 88 | # ... 89 | ``` 90 | 91 | ## Creating Documents 92 | 93 | Creating resources mapped to MongoDB documents is as simple as creating entities: 94 | 95 | ```php 96 | offers = new ArrayCollection(); 132 | } 133 | 134 | public function getId(): ?int 135 | { 136 | return $this->id; 137 | } 138 | 139 | public function addOffer(Offer $offer): void 140 | { 141 | $offer->product = $this; 142 | $this->offers->add($offer); 143 | } 144 | 145 | public function removeOffer(Offer $offer): void 146 | { 147 | $offer->product = null; 148 | $this->offers->removeElement($offer); 149 | } 150 | 151 | // ... 152 | } 153 | ``` 154 | 155 | ```php 156 | id; 198 | } 199 | } 200 | ``` 201 | 202 | Some important information about the mapping: 203 | * Identifier fields always need to be integers with an increment strategy. API Platform does not support the native 204 | [ObjectId](https://docs.mongodb.com/manual/reference/bson-types/#objectid). 205 | * When defining references, always use the id for storing them instead of the native [DBRef](https://docs.mongodb.com/manual/reference/database-references/#dbrefs). 206 | It allows API Platform to manage [filtering on nested properties](filters.md#apifilter-annotation) by using [lookups](https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/). 207 | 208 | ## Filtering 209 | 210 | Doctrine MongoDB ODM filters are practically the same as Doctrine ORM filters. 211 | 212 | See how to use them and how to create custom ones in the [filters documentation](filters.md). 213 | 214 | ## Creating Custom Extensions 215 | 216 | See how to create Doctrine MongoDB ODM custom extensions in the [extensions documentation](extensions.md). 217 | -------------------------------------------------------------------------------- /core/messenger.md: -------------------------------------------------------------------------------- 1 | # Symfony Messenger Integration: CQRS and Async Message Processing 2 | 3 | API Platform provides an integration with the [Symfony Messenger Component](https://symfony.com/doc/current/messenger.html). 4 | 5 | This feature allows to implement the [Command Query Responsibility Segregation (CQRS)](https://martinfowler.com/bliki/CQRS.html) pattern in a convenient way. 6 | It also makes it easy to send messages through the web API that will be consumed asynchronously. 7 | 8 | Many transports are supported to dispatch messages to async consumers, including RabbitMQ, Apache Kafka, Amazon SQS and Google Pub/Sub. 9 | 10 | ## Installing Symfony Messenger 11 | 12 | To enable the support of Messenger, install the library: 13 | 14 | $ docker-compose exec php composer require messenger 15 | 16 | ## Dispatching a Resource through the Message Bus 17 | 18 | Set the `messenger` attribute to `true`, and API Platform will automatically dispatch the API Resource instance as a message using the message bus provided by the Messenger Component: 19 | 20 | ```php 21 | = 6.5.0. 12 | 13 | ## Enabling Reading Support 14 | 15 | To enable the reading support for Elasticsearch, simply require the Elasticsearch-PHP package using Composer: 16 | 17 | $ composer require elasticsearch/elasticsearch:^6.0 18 | 19 | Then, enable it inside the API Platform configuration: 20 | 21 | ```yaml 22 | # api/config/packages/api_platform.yaml 23 | parameters: 24 | # ... 25 | env(ELASTICSEARCH_HOST): 'http://localhost:9200' 26 | 27 | api_platform: 28 | # ... 29 | 30 | mapping: 31 | paths: ['%kernel.project_dir%/src/Model'] 32 | 33 | elasticsearch: 34 | hosts: ['%env(ELASTICSEARCH_HOST)%'] 35 | 36 | #... 37 | ``` 38 | 39 | ## Creating Models 40 | 41 | API Platform follows the best practices of Elasticsearch: 42 | * a single index per resource should be used because Elasticsearch is going to [drop support for index types and will allow only a single type per 43 | index](https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html); 44 | * index name should be the short resource name in lower case; 45 | * the default `_doc` type should be used; 46 | * all fields should be lower case and should use snake case for combining words. 47 | 48 | This involves having mappings and models which absolutely match each other. 49 | 50 | Here is an example of mappings for 2 resources, `User` and `Tweet`, and their models: 51 | 52 | ```json 53 | PUT user 54 | { 55 | "mappings": { 56 | "_doc": { 57 | "properties": { 58 | "id": { 59 | "type": "keyword" 60 | }, 61 | "gender": { 62 | "type": "keyword" 63 | }, 64 | "age": { 65 | "type": "integer" 66 | }, 67 | "first_name": { 68 | "type": "text" 69 | }, 70 | "last_name": { 71 | "type": "text" 72 | }, 73 | "tweets": { 74 | "type": "nested", 75 | "properties": { 76 | "id": { 77 | "type": "keyword" 78 | }, 79 | "date": { 80 | "type": "date", 81 | "format": "yyyy-MM-dd HH:mm:ss" 82 | }, 83 | "message": { 84 | "type": "text" 85 | } 86 | }, 87 | "dynamic": "strict" 88 | } 89 | }, 90 | "dynamic": "strict" 91 | } 92 | } 93 | } 94 | ``` 95 | 96 | ```json 97 | PUT tweet 98 | { 99 | "mappings": { 100 | "_doc": { 101 | "properties": { 102 | "id": { 103 | "type": "keyword" 104 | }, 105 | "author": { 106 | "properties": { 107 | "id": { 108 | "type": "keyword" 109 | }, 110 | "gender": { 111 | "type": "keyword" 112 | }, 113 | "age": { 114 | "type": "integer" 115 | }, 116 | "first_name": { 117 | "type": "text" 118 | }, 119 | "last_name": { 120 | "type": "text" 121 | } 122 | }, 123 | "dynamic": "strict" 124 | }, 125 | "date": { 126 | "type": "date", 127 | "format": "yyyy-MM-dd HH:mm:ss" 128 | }, 129 | "message": { 130 | "type": "text" 131 | } 132 | }, 133 | "dynamic": "strict" 134 | } 135 | } 136 | } 137 | ``` 138 | 139 | ```php 140 | security = $security; 81 | } 82 | 83 | public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) 84 | { 85 | $this->addWhere($queryBuilder, $resourceClass); 86 | } 87 | 88 | public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []) 89 | { 90 | $this->addWhere($queryBuilder, $resourceClass); 91 | } 92 | 93 | private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void 94 | { 95 | if (Offer::class !== $resourceClass || $this->security->isGranted('ROLE_ADMIN') || null === $user = $this->security->getUser()) { 96 | return; 97 | } 98 | 99 | $rootAlias = $queryBuilder->getRootAliases()[0]; 100 | $queryBuilder->andWhere(sprintf('%s.user = :current_user', $rootAlias)); 101 | $queryBuilder->setParameter('current_user', $user); 102 | } 103 | } 104 | 105 | ``` 106 | 107 | Finally, if you're not using the autoconfiguration, you have to register the custom extension with either of those tags: 108 | 109 | ```yaml 110 | # api/config/services.yaml 111 | services: 112 | 113 | # ... 114 | 115 | 'App\Doctrine\CurrentUserExtension': 116 | tags: 117 | - { name: api_platform.doctrine.orm.query_extension.collection } 118 | - { name: api_platform.doctrine.orm.query_extension.item } 119 | ``` 120 | 121 | The `api_platform.doctrine.orm.query_extension.collection` tag will register this service as a collection extension. 122 | The `api_platform.doctrine.orm.query_extension.item` does the same thing for items. 123 | 124 | Note that your extensions should have a positive priority if defined. Internal extensions have negative priorities, for reference: 125 | 126 | | Service name | Priority | Class | 127 | |------------------------------------------------------------|------|---------------------------------------------------------| 128 | | api_platform.doctrine.orm.query_extension.eager_loading (collection) | -8 | ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\EagerLoadingExtension | 129 | | api_platform.doctrine.orm.query_extension.eager_loading (item) | -8 | ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\EagerLoadingExtension | 130 | | api_platform.doctrine.orm.query_extension.filter | -16 | ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\FilterExtension | 131 | | api_platform.doctrine.orm.query_extension.filter_eager_loading | -17 | ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\FilterEagerLoadingExtension | 132 | | api_platform.doctrine.orm.query_extension.order | -32 | ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\OrderExtension | 133 | | api_platform.doctrine.orm.query_extension.pagination | -64 | ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\PaginationExtension | 134 | 135 | #### Blocking Anonymous Users 136 | 137 | This example adds a `WHERE` clause condition only when a fully authenticated user without `ROLE_ADMIN` tries to access a resource. It means that anonymous users will be able to access all data. To prevent this potential security issue, the API must ensure that the current user is authenticated. 138 | 139 | To secure the access to endpoints, use the following access control rule: 140 | 141 | ```yaml 142 | # app/config/package/security.yaml 143 | security: 144 | # ... 145 | access_control: 146 | # ... 147 | - { path: ^/offers, roles: IS_AUTHENTICATED_FULLY } 148 | - { path: ^/users, roles: IS_AUTHENTICATED_FULLY } 149 | ``` 150 | 151 | ## Custom Doctrine MongoDB ODM Extension 152 | 153 | Creating custom extensions is the same as with Doctrine ORM. 154 | 155 | The interfaces are: 156 | * `ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationItemExtensionInterface` and `ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationCollectionExtensionInterface` to add stages to the [aggregation builder](https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/aggregation-builder.html). 157 | * `ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultItemExtensionInterface` and `ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultCollectionExtensionInterface` to return a result. 158 | 159 | The tags are `api_platform.doctrine_mongodb.odm.aggregation_extension.item` and `api_platform.doctrine_mongodb.odm.aggregation_extension.collection`. 160 | 161 | The custom extensions receive the [aggregation builder](https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/aggregation-builder.html), 162 | used to execute [complex operations on data](https://docs.mongodb.com/manual/aggregation/). 163 | 164 | ## Custom Elasticsearch Extension 165 | 166 | Currently only extensions querying for a collection of items through a [search request](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html) 167 | are supported. So your custom extensions must implement the `RequestBodySearchCollectionExtensionInterface`. Register your 168 | custom extensions as services and tag them with the `api_platform.elasticsearch.request_body_search_extension.collection` tag. 169 | -------------------------------------------------------------------------------- /core/subresources.md: -------------------------------------------------------------------------------- 1 | # Subresources 2 | 3 | A subresource is a collection or an item that belongs to another resource. 4 | API Platform makes it easy to create such operations. 5 | 6 |

Subresources screencast
Watch the Subresources screencast

7 | 8 | The starting point of a subresource must be a relation on an existing resource. 9 | For example, let's create two entities (Question, Answer) and set up a subresource so that `/question/42/answer` gives us 10 | the answer to the question 42: 11 | 12 | ```php 13 | id; 47 | } 48 | 49 | // ... 50 | } 51 | ``` 52 | 53 | ```php 54 | id; 91 | } 92 | 93 | // ... 94 | } 95 | ``` 96 | 97 | Alternatively, you can use the YAML configuration format: 98 | 99 | ```yaml 100 | # api/config/api_platform/resources.yaml 101 | App\Entity\Answer: ~ 102 | App\Entity\Question: 103 | properties: 104 | answer: 105 | subresource: 106 | resourceClass: 'App\Entity\Answer' 107 | collection: false 108 | ``` 109 | 110 | Note that all we had to do is to set up `@ApiSubresource` on the `Question::answer` relation. Because the `answer` is a to-one relation, we know that this subresource is an item. Therefore the response will look like this: 111 | 112 | ```json 113 | { 114 | "@context": "/contexts/Answer", 115 | "@id": "/answers/42", 116 | "@type": "Answer", 117 | "id": 42, 118 | "content": "Life, the Universe, and Everything", 119 | "question": "/questions/42" 120 | } 121 | ``` 122 | 123 | If you put the subresource on a relation that is to-many, you will retrieve a collection. 124 | 125 | Last but not least, subresources can be nested, such that `/questions/42/answer/comments` will get the collection of comments for the answer to question 42. 126 | 127 | Note: only for `GET` operations are supported at the moment 128 | 129 | ## Using Serialization Groups 130 | 131 | You may want custom groups on subresources, you can set `normalization_context` or `denormalization_context` on that operation. To do so, add a `subresourceOperations` node. For example: 132 | 133 | ```php 134 | 169 | 170 | 171 | 175 | 176 | 177 | 178 | GET 179 | 180 | 181 | foobar 182 | 183 | 184 | 185 | 186 | 187 | 188 | ``` 189 | 190 | In the previous examples, the `method` attribute is mandatory, because the operation name doesn't match a supported HTTP 191 | method. 192 | 193 | Note that the operation name, here `api_questions_answer_get_subresource`, is the important keyword. 194 | It'll be automatically set to `$resources_$subresource(s)_get_subresource`. To find the correct operation name you 195 | may use `bin/console debug:router`. 196 | 197 | ## Using Custom Paths 198 | 199 | You can control the path of subresources with the `path` option of the `subresourceOperations` parameter: 200 | 201 | ```php 202 | ` component](https://marmelab.com/react-admin/Resource.html) (and all its appropriate children) for each resource type exposed by a web API. 10 | Under the hood it uses the `@api-platform/api-doc-parser` library to parse the API documentation. The API documentation can use Hydra, OpenAPI and any other format supported by the library. 11 | Resources are listed in the order they appear in the machine-readable documentation. 12 | 13 | However, it's also possible to display only specific resources, and to order them, while still benefiting from all discovery features provided by API Platform Admin. 14 | To cherry-pick the resources to make available through the admin, pass a list of `` components as children of the root component: 15 | 16 | ```javascript 17 | import React from "react"; 18 | import { HydraAdmin, ResourceGuesser } from "@api-platform/admin"; 19 | 20 | export default () => ( 21 | 22 | 23 | 24 | 25 | {/* While deprecated resources are hidden by default, using an explicit ResourceGuesser component allows to add them back. */} 26 | 27 | 28 | ); 29 | ``` 30 | 31 | Instead of using the `` component provided by API Platform Admin, you can also pass custom React Admin's [`` components](https://marmelab.com/react-admin/Resource.html), or any other React components that are supported by React Admin's [``](https://marmelab.com/react-admin/Admin.html). 32 | 33 | ## Customizing the List View 34 | 35 | The list view can be customized following the same pattern: 36 | 37 | ```javascript 38 | import React from "react"; 39 | import { 40 | HydraAdmin, 41 | ResourceGuesser, 42 | ListGuesser, 43 | FieldGuesser 44 | } from "@api-platform/admin"; 45 | 46 | const ReviewsList = props => ( 47 | 48 | 49 | 50 | 51 | {/* While deprecated fields are hidden by default, using an explicit FieldGuesser component allows to add them back. */} 52 | 53 | 54 | ); 55 | 56 | export default () => ( 57 | 58 | 59 | {/* ... */} 60 | 61 | ); 62 | ``` 63 | 64 | In this example, only the fields `author`, `book` and `letter` (that is hidden by default because it is deprecated) will be displayed. The defined order will be respected. 65 | 66 | In addition to the `` component, [all React Admin Fields components](https://marmelab.com/react-admin/Fields.html) can be passed as children of ``. 67 | 68 | ## Customizing the Show View 69 | 70 | For the show view: 71 | 72 | ```javascript 73 | import React from "react"; 74 | import { 75 | HydraAdmin, 76 | ResourceGuesser, 77 | ShowGuesser, 78 | FieldGuesser 79 | } from "@api-platform/admin"; 80 | 81 | const ReviewsShow = props => ( 82 | 83 | 84 | 85 | 86 | 87 | {/* While deprecated fields are hidden by default, using an explicit FieldGuesser component allows to add them back. */} 88 | 89 | 90 | 91 | 92 | 93 | ); 94 | 95 | export default () => ( 96 | 97 | 98 | {/* ... */} 99 | 100 | ); 101 | ``` 102 | 103 | In addition to the `` component, [all React Admin Fields components](https://marmelab.com/react-admin/Fields.html) can be passed as children of ``. 104 | 105 | ## Customizing the Create Form 106 | 107 | Again, the same logic applies to forms. Here is how to customize the create form: 108 | 109 | ```javascript 110 | import React from "react"; 111 | import { 112 | HydraAdmin, 113 | ResourceGuesser, 114 | CreateGuesser, 115 | InputGuesser 116 | } from "@api-platform/admin"; 117 | 118 | const ReviewsCreate = props => ( 119 | 120 | 121 | 122 | 123 | 124 | {/* While deprecated fields are hidden by default, using an explicit InputGuesser component allows to add them back. */} 125 | 126 | 127 | 128 | 129 | 130 | ); 131 | 132 | export default () => ( 133 | 134 | 135 | {/* ... */} 136 | 137 | ); 138 | ``` 139 | 140 | In addition to the `` component, [all React Admin Input components](https://marmelab.com/react-admin/Inputs.html) can be passed as children of ``. 141 | 142 | For instance, using an autocomplete input is straightforward, [check out the dedicated documentation entry](handling-relations-to-collections.md#using-an-autocomplete-input)! 143 | 144 | ## Customizing the Edit Form 145 | 146 | Finally, you can customize the edit form the same way: 147 | 148 | ```javascript 149 | import React from "react"; 150 | import { 151 | HydraAdmin, 152 | ResourceGuesser, 153 | EditGuesser, 154 | InputGuesser 155 | } from "@api-platform/admin"; 156 | 157 | const ReviewsEdit = props => ( 158 | 159 | 160 | 161 | 162 | 163 | {/* While deprecated fields are hidden by default, using an explicit InputGuesser component allows to add them back. */} 164 | 165 | 166 | 167 | 168 | 169 | ); 170 | 171 | export default () => ( 172 | 173 | 174 | {/* ... */} 175 | 176 | ); 177 | ``` 178 | 179 | In addition to the `` component, [all React Admin Input components](https://marmelab.com/react-admin/Inputs.html) can be passed as children of ``. 180 | 181 | For instance, using an autocomplete input is straightforward, [checkout the dedicated documentation entry](handling-relations-to-collections.md#using-an-autocomplete-input)! 182 | 183 | ## Going Further 184 | 185 | API Platform is built on top of [React Admin](https://marmelab.com/react-admin/). 186 | You can use all the features provided by the underlying library with API Platform Admin, including support for [file upload](https://marmelab.com/react-admin/DataProviders.html#decorating-your-data-provider-example-of-file-upload), [authentication](https://marmelab.com/react-admin/Authentication.html), [authorization](https://marmelab.com/react-admin/Authorization.html) and deeper customization. 187 | 188 | To learn more about these capabilities, refer to [the React Admin documentation](https://marmelab.com/react-admin/). 189 | -------------------------------------------------------------------------------- /core/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | Here's the complete configuration of the Symfony bundle including default values: 4 | 5 | ```yaml 6 | # api/config/packages/api_platform.yaml 7 | api_platform: 8 | 9 | # The title of the API. 10 | title: 'API title' 11 | 12 | # The description of the API. 13 | description: 'API description' 14 | 15 | # The version of the API. 16 | version: '0.0.0' 17 | 18 | # Set this to false if you want Webby to disappear. 19 | show_webby: true 20 | 21 | # Specify a name converter to use. 22 | name_converter: ~ 23 | 24 | # Specify a path name generator to use. 25 | path_segment_name_generator: 'api_platform.path_segment_name_generator.underscore' 26 | 27 | # Allow using plain IDs for JSON format 28 | allow_plain_identifiers: false 29 | 30 | doctrine: 31 | # To enable or disable Doctrine ORM support. 32 | enabled: true 33 | 34 | doctrine_mongodb_odm: 35 | # To enable or disable Doctrine MongoDB ODM support. 36 | enabled: false 37 | 38 | eager_loading: 39 | # To enable or disable eager loading. 40 | enabled: true 41 | 42 | # Fetch only partial data according to serialization groups. 43 | # If enabled, Doctrine ORM entities will not work as expected if any of the other fields are used. 44 | fetch_partial: false 45 | 46 | # Max number of joined relations before EagerLoading throws a RuntimeException. 47 | max_joins: 30 48 | 49 | # Force join on every relation. 50 | # If disabled, it will only join relations having the EAGER fetch mode. 51 | force_eager: true 52 | 53 | # Enable the FOSUserBundle integration. 54 | enable_fos_user: false 55 | 56 | # Enable the Nelmio Api doc integration. 57 | enable_nelmio_api_doc: false 58 | 59 | # Enable the Swagger documentation and export. 60 | enable_swagger: true 61 | 62 | # Enable Swagger ui. 63 | enable_swagger_ui: true 64 | 65 | # Enable ReDoc. 66 | enable_re_doc: true 67 | 68 | # Enable the entrypoint. 69 | enable_entrypoint: true 70 | 71 | # Enable the docs. 72 | enable_docs: true 73 | 74 | # Enable the data collector and the WebProfilerBundle integration. 75 | enable_profiler: true 76 | 77 | oauth: 78 | # To enable or disable oauth. 79 | enabled: false 80 | 81 | # The oauth client id. 82 | clientId: '' 83 | 84 | # The oauth client secret. 85 | clientSecret: '' 86 | 87 | # The oauth type. 88 | type: 'oauth2' 89 | 90 | # The oauth flow grant type. 91 | flow: 'application' 92 | 93 | # The oauth token url. 94 | tokenUrl: '/oauth/v2/token' 95 | 96 | # The oauth authentication url. 97 | authorizationUrl: '/oauth/v2/auth' 98 | 99 | # The oauth scopes. 100 | scopes: [] 101 | 102 | graphql: 103 | enabled: false 104 | # The default IDE (graphiql or graphql-playground) used when going to the GraphQL endpoint. False to disable. 105 | default_ide: 'graphiql' 106 | graphiql: 107 | enabled: true 108 | graphql_playground: 109 | enabled: true 110 | collection: 111 | pagination: 112 | enabled: true 113 | # The nesting separator used in the filter names. 114 | nesting_separator: _ 115 | 116 | elasticsearch: 117 | # To enable or disable Elasticsearch support. 118 | enabled: false 119 | 120 | # The hosts to the Elasticsearch nodes. 121 | hosts: [] 122 | 123 | # The mapping between resource classes and indexes. 124 | mapping: [] 125 | 126 | swagger: 127 | # The active versions of OpenAPI to be exported or used in the swagger_ui. The first value is the default. 128 | versions: [2, 3] 129 | 130 | # The swagger api keys. 131 | api_keys: [] 132 | 133 | collection: 134 | # The name of the query parameter to filter nullable results (with the ExistsFilter). 135 | exists_parameter_name: 'exists' 136 | 137 | # The default order of results. 138 | order: 'ASC' 139 | 140 | # The name of the query parameter to order results (with the OrderFilter). 141 | order_parameter_name: 'order' 142 | 143 | pagination: 144 | # To enable or disable pagination for all resource collections by default. 145 | enabled: true 146 | 147 | # To allow the client to enable or disable the pagination. 148 | client_enabled: false 149 | 150 | # To allow the client to set the number of items per page. 151 | client_items_per_page: false 152 | 153 | # The default number of items per page. 154 | items_per_page: 30 155 | 156 | # The maximum number of items per page. 157 | maximum_items_per_page: 10 158 | 159 | # The default name of the parameter handling the page number. 160 | page_parameter_name: 'page' 161 | 162 | # The name of the query parameter to enable or disable pagination. 163 | enabled_parameter_name: 'pagination' 164 | 165 | # The name of the query parameter to set the number of items per page. 166 | items_per_page_parameter_name: 'itemsPerPage' 167 | 168 | # To allow partial pagination for all resource collections. 169 | # This improves performances by skipping the `COUNT` query. 170 | partial: false 171 | 172 | # To allow the client to enable or disable the partial pagination. 173 | client_partial: false 174 | 175 | # The name of the query parameter to enable or disable the partial pagination. 176 | partial_parameter_name: 'partial' # Default value 177 | 178 | mapping: 179 | # The list of paths with files or directories where the bundle will look for additional resource files. 180 | paths: [] 181 | 182 | # The list of your resources class directories. Defaults to the directories of the mapping paths but might differ. 183 | resource_class_directories: 184 | - '%kernel.project_dir%/src/Entity' 185 | 186 | http_cache: 187 | # Automatically generate etags for API responses. 188 | etag: true 189 | 190 | # Default value for the response max age. 191 | max_age: 3600 192 | 193 | # Default value for the response shared (proxy) max age. 194 | shared_max_age: 3600 195 | 196 | # Default values of the "Vary" HTTP header. 197 | vary: ['Accept'] 198 | 199 | # To make all responses public by default. 200 | public: ~ 201 | 202 | invalidation: 203 | # To enable the tags-based cache invalidation system. 204 | enabled: false 205 | 206 | # URLs of the Varnish servers to purge using cache tags when a resource is updated. 207 | varnish_urls: [] 208 | 209 | # To pass options to the client charged with the request. 210 | request_options: [] 211 | 212 | # The list of exceptions mapped to their HTTP status code. 213 | exception_to_status: 214 | # With a status code. 215 | Symfony\Component\Serializer\Exception\ExceptionInterface: 400 216 | 217 | # Or with a constant defined in the 'Symfony\Component\HttpFoundation\Response' class. 218 | ApiPlatform\Core\Exception\InvalidArgumentException: !php/const Symfony\Component\HttpFoundation\Response::HTTP_BAD_REQUEST 219 | 220 | # ... 221 | 222 | # The list of enabled formats. The first one will be the default. 223 | formats: 224 | jsonld: 225 | mime_types: ['application/ld+json'] 226 | 227 | json: 228 | mime_types: ['application/json'] 229 | 230 | html: 231 | mime_types: ['text/html'] 232 | 233 | # ... 234 | 235 | # The list of enabled error formats. The first one will be the default. 236 | error_formats: 237 | jsonproblem: 238 | mime_types: ['application/problem+json'] 239 | 240 | jsonld: 241 | mime_types: ['application/ld+json'] 242 | 243 | # ... 244 | ``` 245 | -------------------------------------------------------------------------------- /core/events.md: -------------------------------------------------------------------------------- 1 | # The Event System 2 | 3 | Note: using Kernel event with API Platform should be mostly limited to tweaking the generated HTTP response. Also, GraphQL is **not supported**. 4 | [For most use cases, better extension points, working both with REST and GraphQL, are available](extending.md). 5 | 6 | API Platform Core implements the [Action-Domain-Responder](https://github.com/pmjones/adr) pattern. This implementation 7 | is covered in depth in the [Creating custom operations and controllers](operations.md#creating-custom-operations-and-controllers) 8 | chapter. 9 | 10 | Basically, API Platform Core executes an action class that will return an entity or a collection of entities. Then a series 11 | of event listeners are executed which validate the data, persist it in database, serialize it (typically in a JSON-LD document) 12 | and create an HTTP response that will be sent to the client. 13 | 14 | To do so, API Platform Core leverages [events triggered by the Symfony HTTP Kernel](https://symfony.com/doc/current/reference/events.html#kernel-events). 15 | You can also hook your own code to those events. There are handy and powerful extension points available at all points 16 | of the request lifecycle. 17 | 18 | If you are using Doctrine, lifecycle events ([ORM](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/events.html#lifecycle-events), [MongoDB ODM](https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/events.html#lifecycle-events)) 19 | are also available if you want to hook into the persistence layer's object lifecycle. 20 | 21 | ## Built-in Event Listeners 22 | 23 | These built-in event listeners are registered for routes managed by API Platform: 24 | 25 | Name | Event | [Pre & Post hooks](#custom-event-listeners) | Priority | Description 26 | ------------------------------|--------------------|---------------------------------------------|----------|------------- 27 | `AddFormatListener` | `kernel.request` | None | 7 | Guesses the best response format ([content negotiation](content-negotiation.md)) 28 | `ReadListener` | `kernel.request` | `PRE_READ`, `POST_READ` | 4 | Retrieves data from the persistence system using the [data providers](data-providers.md) (`GET`, `PUT`, `DELETE`) 29 | `DeserializeListener` | `kernel.request` | `PRE_DESERIALIZE`, `POST_DESERIALIZE` | 2 | Deserializes data into a PHP entity (`GET`, `POST`, `DELETE`); updates the entity retrieved using the data provider (`PUT`) 30 | `DenyAccessListener` | `kernel.request` | None | 1 | Enforces [access control](security.md) using Security expressions 31 | `ValidateListener` | `kernel.view` | `PRE_VALIDATE`, `POST_VALIDATE` | 64 | [Validates data](validation.md) (`POST`, `PUT`) 32 | `WriteListener` | `kernel.view` | `PRE_WRITE`, `POST_WRITE` | 32 | Persists changes in the persistence system using the [data persisters](data-persisters.md) (`POST`, `PUT`, `DELETE`) 33 | `SerializeListener` | `kernel.view` | `PRE_SERIALIZE`, `POST_SERIALIZE` | 16 | Serializes the PHP entity in string [according to the request format](content-negotiation.md) 34 | `RespondListener` | `kernel.view` | `PRE_RESPOND`, `POST_RESPOND` | 8 | Transforms serialized to a `Symfony\Component\HttpFoundation\Response` instance 35 | `AddLinkHeaderListener` | `kernel.response` | None | 0 | Adds a `Link` HTTP header pointing to the Hydra documentation 36 | `ValidationExceptionListener` | `kernel.exception` | None | 0 | Serializes validation exceptions in the Hydra format 37 | `ExceptionListener` | `kernel.exception` | None | -96 | Serializes PHP exceptions in the Hydra format (including the stack trace in debug mode) 38 | 39 | Some of these built-in listeners can be enabled/disabled by setting operation attributes: 40 | 41 | Attribute | Type | Default | Description 42 | --------------|--------|---------|------------- 43 | `read` | `bool` | `true` | Enables or disables `ReadListener` 44 | `deserialize` | `bool` | `true` | Enables or disables `DeserializeListener` 45 | `validate` | `bool` | `true` | Enables or disables `ValidateListener` 46 | `write` | `bool` | `true` | Enables or disables `WriteListener` 47 | `serialize` | `bool` | `true` | Enables or disables `SerializeListener` 48 | 49 | Some of these built-in listeners can be enabled/disabled by setting request attributes (for instance in the [`defaults` 50 | attribute of an operation](operations.md#recommended-method)): 51 | 52 | Attribute | Type | Default | Description 53 | ---------------|--------|---------|------------- 54 | `_api_receive` | `bool` | `true` | Enables or disables `ReadListener`, `DeserializeListener`, `ValidateListener` 55 | `_api_respond` | `bool` | `true` | Enables or disables `SerializeListener`, `RespondListener` 56 | `_api_persist` | `bool` | `true` | Enables or disables `WriteListener` 57 | 58 | ## Custom Event Listeners 59 | 60 | Registering your own event listeners to add extra logic is convenient. 61 | 62 | The [`ApiPlatform\Core\EventListener\EventPriorities`](https://github.com/api-platform/core/blob/master/src/EventListener/EventPriorities.php) class comes with a convenient set of class constants corresponding to commonly used priorities: 63 | 64 | Constant | Event | Priority | 65 | -------------------|-------------------|----------| 66 | `PRE_READ` | `kernel.request` | 5 | 67 | `POST_READ` | `kernel.request` | 3 | 68 | `PRE_DESERIALIZE` | `kernel.request` | 3 | 69 | `POST_DESERIALIZE` | `kernel.request` | 1 | 70 | `PRE_VALIDATE` | `kernel.view` | 65 | 71 | `POST_VALIDATE` | `kernel.view` | 63 | 72 | `PRE_WRITE` | `kernel.view` | 33 | 73 | `POST_WRITE` | `kernel.view` | 31 | 74 | `PRE_SERIALIZE` | `kernel.view` | 17 | 75 | `POST_SERIALIZE` | `kernel.view` | 15 | 76 | `PRE_RESPOND` | `kernel.view` | 9 | 77 | `POST_RESPOND` | `kernel.response` | 0 | 78 | 79 | In the following example, we will send a mail each time a new book is created using the API: 80 | 81 | ```php 82 | mailer = $mailer; 101 | } 102 | 103 | public static function getSubscribedEvents() 104 | { 105 | return [ 106 | KernelEvents::VIEW => ['sendMail', EventPriorities::POST_WRITE], 107 | ]; 108 | } 109 | 110 | public function sendMail(ViewEvent $event) 111 | { 112 | $book = $event->getControllerResult(); 113 | $method = $event->getRequest()->getMethod(); 114 | 115 | if (!$book instanceof Book || Request::METHOD_POST !== $method) { 116 | return; 117 | } 118 | 119 | $message = (new \Swift_Message('A new book has been added')) 120 | ->setFrom('system@example.com') 121 | ->setTo('contact@les-tilleuls.coop') 122 | ->setBody(sprintf('The book #%d has been added.', $book->getId())); 123 | 124 | $this->mailer->send($message); 125 | } 126 | } 127 | ``` 128 | 129 | If you use the official API Platform distribution, creating the previous class is enough. The Symfony DependencyInjection 130 | component will automatically register this subscriber as a service and will inject its dependencies thanks to the [autowiring feature](https://symfony.com/doc/current/service_container/autowiring.html). 131 | 132 | Alternatively, [the subscriber must be registered manually](https://symfony.com/doc/current/components/event_dispatcher.html#connecting-listeners). 133 | -------------------------------------------------------------------------------- /deployment/docker-compose.md: -------------------------------------------------------------------------------- 1 | # Deploying with Docker Compose 2 | 3 | While [Docker Compose](https://docs.docker.com/compose/) is mainly known and used in a development environment, it [can 4 | actually be used in production too](https://docs.docker.com/compose/production/). This is especially suitable for prototyping 5 | or small-scale deployments, where the robustness (and the associated complexity) of [Kubernetes](kubernetes.md) is not 6 | required. 7 | 8 | It is recommended that you build the Docker images in a [CI (continuous integration)](https://en.wikipedia.org/wiki/Continuous_integration) 9 | job, or failing which, on your development machine. The built images should then be pushed to a [container registry](https://docs.docker.com/registry/introduction/), 10 | e.g. [Docker Hub](https://hub.docker.com/), [Google Container Registry](https://cloud.google.com/container-registry/), 11 | [GitLab Container Registry](https://docs.gitlab.com/ee/user/packages/container_registry/). On the production server, you 12 | would pull the pre-built images from the container registry. This maintains a separation of concerns between the build 13 | environment and the production environment. 14 | 15 | ## Installing the Docker Compose Setup for Production 16 | 17 | If you are using the [API Platform Distribution](../distribution/index.md), we provide a [ready-to-deploy Docker Compose 18 | setup for production](https://github.com/api-platform/docker-compose-prod), with (optional) [Let's Encrypt](https://letsencrypt.org/) 19 | integration. 20 | 21 | It is designed to be used as a companion to the distribution, and as such it needs to be placed inside a subdirectory at 22 | the top level of the distribution project: 23 | 24 | $ wget -O - https://github.com/api-platform/docker-compose-prod/archive/master.tar.gz | tar -xzf - && mv docker-compose-prod-master docker-compose-prod 25 | $ git add docker-compose-prod 26 | 27 | Then commit the changes to your git repository. The `docker-compose-prod` directory should be checked in. 28 | 29 | ## Building and Pushing the Docker Images 30 | 31 | These steps should be performed in a CI job (recommended) or on your development machine. 32 | 33 | 1. Make sure the environment variables required for the build are set. 34 | 35 | If you are building the images in a CI job, these environment variables should be set as part of your CI job's environment. 36 | 37 | If you are building on your development machine, you could set the environment variables in the `.env` file at the 38 | top level of the distribution project (not to be confused with `api/.env` which is used by the Symfony application). 39 | For example: 40 | 41 | ``` 42 | ADMIN_IMAGE=registry.example.com/api-platform/admin 43 | CLIENT_IMAGE=registry.example.com/api-platform/client 44 | NGINX_IMAGE=registry.example.com/api-platform/nginx 45 | PHP_IMAGE=registry.example.com/api-platform/php 46 | REACT_APP_API_ENTRYPOINT=https://api.example.com 47 | VARNISH_IMAGE=registry.example.com/api-platform/varnish 48 | ``` 49 | 50 | **Note**: `REACT_APP_API_ENTRYPOINT` must be an exact match of the target domain name where your API will be accessed 51 | from, since its value is [embedded during build time](https://create-react-app.dev/docs/adding-custom-environment-variables). 52 | See [this discussion for possible workarounds](https://github.com/facebook/create-react-app/issues/2353) if this limitation 53 | is unacceptable for your project. 54 | 55 | 2. Build the Docker images: 56 | 57 | $ docker-compose -f docker-compose-prod/docker-compose.build.yml pull --ignore-pull-failures 58 | $ docker-compose -f docker-compose-prod/docker-compose.build.yml build --pull 59 | 60 | 3. Push the built images to the container registry: 61 | 62 | $ docker-compose -f docker-compose-prod/docker-compose.build.yml push 63 | 64 | ## Pulling the Docker Images and Running the Services 65 | 66 | These steps should be performed on the production server. 67 | 68 | 1. Make sure the environment variables required are set. 69 | 70 | You could set the environment variables in the `.env` file at the top level of the distribution project (not to be 71 | confused with `api/.env` which is used by the Symfony application). For example: 72 | 73 | ``` 74 | ADMIN_HOST=admin.example.com 75 | ADMIN_IMAGE=registry.example.com/api-platform/admin 76 | API_HOST=api.example.com 77 | APP_SECRET=3c857494cfcc42c700dfb7a6 78 | CLIENT_HOST=example.com,www.example.com 79 | CLIENT_IMAGE=registry.example.com/api-platform/client 80 | CORS_ALLOW_ORIGIN=^https://(?:\w+\.)?example\.com$ 81 | DATABASE_URL=postgres://api-platform:4e3bc2766fe81df300d56481@db/api 82 | MERCURE_ALLOW_ANONYMOUS=0 83 | MERCURE_CORS_ALLOWED_ORIGINS=https://example.com,https://admin.example.com 84 | MERCURE_HOST=mercure.example.com 85 | MERCURE_JWT_KEY=4121344212538417de3e2118 86 | MERCURE_JWT_SECRET=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyJmb28iLCJiYXIiXSwicHVibGlzaCI6WyJmb28iXX19.B0MuTRMPLrut4Nt3wxVvLtfWB_y189VEpWMlSmIQABQ 87 | MERCURE_SUBSCRIBE_URL=https://mercure.example.com/hub 88 | NGINX_IMAGE=registry.example.com/api-platform/nginx 89 | PHP_IMAGE=registry.example.com/api-platform/php 90 | POSTGRES_PASSWORD=4e3bc2766fe81df300d56481 91 | REACT_APP_API_ENTRYPOINT=https://api.example.com 92 | TRUSTED_HOSTS=^(?:localhost|api|api\.example\.com)$ 93 | VARNISH_IMAGE=registry.example.com/api-platform/varnish 94 | ``` 95 | 96 | **Important**: Please make sure to change all the passwords, keys and secret values to your own. 97 | 98 | 2. Set up a redirect from e.g. `www.example.com` to `example.com`: 99 | 100 | $ mkdir -p docker-compose-prod/docker/nginx-proxy/vhost.d 101 | $ echo 'return 301 https://example.com$request_uri;' > docker-compose-prod/docker/nginx-proxy/vhost.d/www.example.com 102 | 103 | **Note**: If you do not want such a redirect, or you want it to be the other way round, please adapt to suit your needs. 104 | 105 | 3. *(optional)* Set up the Let's Encrypt integration. 106 | 107 | **Note**: If you are using Cloudflare, you might consider using their [free SSL/TLS encryption](https://www.cloudflare.com/ssl/) 108 | setup as a simpler alternative. But if you would prefer to have full control, read on. 109 | 110 | Make sure the environment variables required for the Let's Encrypt integration are set. 111 | 112 | You could set the environment variables in the `.env` file at the top level of the distribution project (not to be 113 | confused with `api/.env` which is used by the Symfony application). For example: 114 | 115 | ``` 116 | LETSENCRYPT_USER_MAIL=user@example.com 117 | LEXICON_CLOUDFLARE_AUTH_TOKEN=9e06358f74cbce70602c22fc3279f0aee3077 118 | LEXICON_CLOUDFLARE_AUTH_USERNAME=user@example.com 119 | ``` 120 | 121 | **Note**: If you are not using [Cloudflare DNS](https://www.cloudflare.com/dns/), please see [the documentation on 122 | how to pass the correct environment variables to Lexicon](https://github.com/adferrand/docker-letsencrypt-dns#configuring-dns-provider-and-authentication-to-dns-api). 123 | 124 | Configure the (sub)domains for which you want certificate(s) to be issued for in `docker-compose-prod/docker/letsencrypt/domains.conf`. 125 | For example, to request a wildcard certificate for `*.example.com` and `example.com`: 126 | 127 | ``` 128 | *.example.com example.com autorestart-containers=api-platform_nginx-proxy_1 129 | ``` 130 | 131 | **Note**: Replace the `api-platform` prefix in `api-platform_nginx-proxy_1` with your [Docker Compose project name 132 | (it defaults to the project directory name)](https://docs.docker.com/compose/reference/envvars/#compose_project_name). 133 | 134 | 4. Pull the Docker images. 135 | 136 | If you are **not** using the (optional) Let's Encrypt integration: 137 | 138 | $ docker-compose -f docker-compose-prod/docker-compose.yml pull 139 | 140 | If you are using the (optional) Let's Encrypt integration: 141 | 142 | $ docker-compose -f docker-compose-prod/docker-compose.yml -f docker-compose-prod/docker-compose.letsencrypt.yml pull 143 | 144 | 5. Bring up the services. 145 | 146 | If you are **not** using the (optional) Let's Encrypt integration: 147 | 148 | $ docker-compose -f docker-compose-prod/docker-compose.yml up -d 149 | 150 | If you are using the (optional) Let's Encrypt integration: 151 | 152 | $ docker-compose -f docker-compose-prod/docker-compose.yml -f docker-compose-prod/docker-compose.letsencrypt.yml up -d 153 | --------------------------------------------------------------------------------