diff --git a/.travis.yml b/.travis.yml
index c5f39853b85..c8d484d9734 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,28 +4,26 @@ node_js:
cache:
yarn: true
+ directories:
+ - $HOME/.cache/pip
env:
global:
secure: gjiWHIgNjLXuEFmLiepiOTHOuauHfeKHutrE0sBwFZUQzP9FoGTZzJub1z8/vm+hhygA+TZbB0iclygHyNrXCkZyNdnZXChXl4iPdYqY3OARPOAbQff16+/XSUDsZ/Ok1etdb3kKor1R4rBt/WywBvXmmdggumTA3yT5ExI+dyomdONo4yMUZ7g1la0ehMEzGqyVjt0nUW31PN3l6dI1qgigHCuotSrrpWP6fTXuUh5l6YA7KXb/V1hJxaGENLz1Cdfk0sF66e4KsV/DX6JZSqpvdqVB8OTPU+si511yJtGS8OeuLs4RqmXMLrY/ChCodlYnfCE+NleBFpUnCVqth/RDRh7LvplUJlpsXNcfyoA3mOFmZa0euOMCqJ3qwESz802Y9c5oN63hn2OUF/raFDc3SMMC86FFHxwyYjz5+yzXYupnFNj39NKdQ1v1KBbY8BD8UT8RU3mlu4/3LRz0tSamREHj3pGgBmvgUZfUE1dMngeaZjOmBaIZH8TnKQ75CvfnoT+LJnZo6g9g4uwz6jtaIziSMZ0OW/95nF8yx+xONbHEtt5ex5M09NOFsN2vB2bcUAgIjyGrmmNLQadwJyQv557IGPEE5CyGhJpXh9XZ+WMw2vO45MOw4sQPkARF6OJkMteqFc2NLXMOQJ07EScbgEKR/9VOcyxIk/7a7nc=
-before_install:
- - curl -o- -L https://yarnpkg.com/install.sh | bash
- - export PATH="$HOME/.yarn/bin:$PATH"
-
install:
- - pip install --user proselint
+ - pip install --quiet --user proselint
- cp .proselintrc ~
- cd ..
- - git clone https://github.com/api-platform/website.git
+ - git clone --depth 1 https://github.com/api-platform/website.git
- cd website
- - yarn install
+ - yarn install --silent
- ln -s $TRAVIS_BUILD_DIR src/pages/docs
- cd $TRAVIS_BUILD_DIR
script:
- - find . -name '*.md' ! -name 'conduct.md' ! -name 'heroku.md' -exec proselint {} \;
- - cd $TRAVIS_BUILD_DIR/../website
+ - find . -name '*.md' -exec proselint {} \;
+ - cd ../website
- bin/generate-nav
- GATSBY_BRANCH_NAME=$TRAVIS_BRANCH yarn gatsby build
# Preserve artifacts
diff --git a/admin/getting-started.md b/admin/getting-started.md
index 706dbaa4bb9..e565c83f2cf 100644
--- a/admin/getting-started.md
+++ b/admin/getting-started.md
@@ -5,7 +5,7 @@
Install the skeleton and the library:
Start by installing [the Yarn package manager](https://yarnpkg.com/) ([NPM](https://www.npmjs.com/) is also supported) and
-the [Create React App](https://github.com/facebookincubator/create-react-app) tool.
+the [Create React App](https://facebook.github.io/create-react-app/) tool.
Then, create a new React application for your admin:
@@ -27,7 +27,9 @@ Edit the `src/App.js` file like the following:
import React from 'react';
import { HydraAdmin } from '@api-platform/admin';
-export default () => ; // Replace with your own API entrypoint
+// Replace with your own API entrypoint: if https://example.com/api/books is the path
+// to the collection of book resources, then the entrypoint is https://example.com/api
+export default () => ;
```
Be sure to make your API send proper [CORS HTTP headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) to allow
@@ -58,7 +60,7 @@ Clear the cache to apply this change:
Your new administration interface is ready! Type `yarn start` to try it!
-Note: if you don't want to hardcode the API URL, you can [use an environment variable](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-custom-environment-variables).
+Note: if you don't want to hardcode the API URL, you can [use an environment variable](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables).
## Customizing the Admin
@@ -83,6 +85,10 @@ const myApiDocumentationParser = entrypoint => parseHydraDocumentation(entrypoin
const books = api.resources.find(({ name }) => 'books' === name);
const description = books.fields.find(f => 'description' === f.name);
+ description.field = props => (
+
+ );
+
description.input = props => (
);
@@ -145,7 +151,7 @@ const myApiDocumentationParser = entrypoint => parseHydraDocumentation(entrypoin
src: value
});
- field.fieldComponent = (
+ field.field = (
parseHydraDocumentation(entrypoin
/>
);
- field.inputComponent = (
+ field.input = (
@@ -228,3 +234,180 @@ export default class extends Component {
}
}
```
+
+### Using the Hydra Data Provider Directly with react-admin
+
+By default, the `HydraAdmin` component shipped with API Platform Admin will generate a convenient admin interface for every resources and every properties exposed by the API. But sometimes, you may prefer having full control over the generated admin.
+
+To do so, an alternative approach is [to configure every react-admin components manually](https://marmelab.com/react-admin/Tutorial.html) instead of letting the library generating it, but to still leverage the built-in Hydra [data provider](https://marmelab.com/react-admin/DataProviders.html):
+
+```javascript
+// admin/src/App.js
+
+import React, { Component } from 'react';
+import { Admin, Resource } from 'react-admin';
+import parseHydraDocumentation from '@api-platform/api-doc-parser/lib/hydra/parseHydraDocumentation';
+import { hydraClient, fetchHydra as baseFetchHydra } from '@api-platform/admin';
+import authProvider from './authProvider';
+import { Redirect } from 'react-router-dom';
+import { createMuiTheme } from '@material-ui/core/styles';
+import Layout from './Component/Layout';
+import { UserShow } from './Components/User/Show';
+import { UserEdit } from './Components/User/Edit';
+import { UserCreate } from './Components/User/Create';
+import { UserList } from './Components/User/List';
+
+const theme = createMuiTheme({
+ palette: {
+ type: 'light'
+ },
+});
+
+const entrypoint = process.env.REACT_APP_API_ENTRYPOINT;
+const fetchHeaders = {'Authorization': `Bearer ${window.localStorage.getItem('token')}`};
+const fetchHydra = (url, options = {}) => baseFetchHydra(url, {
+ ...options,
+ headers: new Headers(fetchHeaders),
+});
+const dataProvider = api => hydraClient(api, fetchHydra);
+const apiDocumentationParser = entrypoint => parseHydraDocumentation(entrypoint, { headers: new Headers(fetchHeaders) })
+ .then(
+ ({ api }) => ({api}),
+ (result) => {
+ switch (result.status) {
+ case 401:
+ return Promise.resolve({
+ api: result.api,
+ customRoutes: [{
+ props: {
+ path: '/',
+ render: () => ,
+ },
+ }],
+ });
+
+ default:
+ return Promise.reject(result);
+ }
+ },
+ );
+
+export default class extends Component {
+ state = { api: null };
+
+ componentDidMount() {
+ apiDocumentationParser(entrypoint).then(({ api }) => {
+ this.setState({ api });
+ }).catch((e) => {
+ console.log(e);
+ });
+ }
+
+ render() {
+ if (null === this.state.api) return
Loading...
;
+ return (
+
+
+
+ )
+ }
+}
+```
+
+And accordingly create files `Show.js`, `Create.js`, `List.js`, `Edit.js`
+in the `admin/src/Component/User` directory:
+
+```javascript
+// admin/src/Component/User/Create.js
+
+import React from 'react';
+import { Create, SimpleForm, TextInput, email, required, ArrayInput, SimpleFormIterator } from 'react-admin';
+
+export const UserCreate = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+```
+
+```javascript
+// admin/src/Component/User/Edit.js
+
+import React from 'react';
+import { Edit, SimpleForm, DisabledInput, TextInput, email, ArrayInput, SimpleFormIterator, DateInput } from 'react-admin';
+
+export const UserEdit = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+```
+
+```javascript
+// admin/src/Component/User/List.js
+
+import React from 'react';
+import { List, Datagrid, TextField, EmailField, DateField, ShowButton, EditButton } from 'react-admin';
+import { CustomPagination } from '../Pagination/CustomPagination';
+
+export const UserList = (props) => (
+ } perPage={ 30 }>
+
+
+
+
+
+
+
+
+
+
+);
+```
+
+```javascript
+// admin/src/Component/User/Show.js
+import React from 'react';
+import { Show, SimpleShowLayout, TextField, DateField, EmailField, EditButton } from 'react-admin';
+
+export const UserShow = (props) => (
+
+
+
+
+
+
+
+
+
+
+);
+```
diff --git a/admin/handling-relations-to-collections.md b/admin/handling-relations-to-collections.md
index 86560d12cbf..57aa7819db1 100644
--- a/admin/handling-relations-to-collections.md
+++ b/admin/handling-relations-to-collections.md
@@ -71,8 +71,6 @@ class Book
The admin handles this `to-many` relation automatically!
-But we can go further:
-
## Customizing a Property
Let's customize the components used for the `authors` property, to display them by their 'name' instead 'id' (the default behavior).
@@ -122,6 +120,50 @@ export default class extends Component {
}
```
+
+## Customizing an Icon
+
+Now that our `authors` property is displaying the name instead of an 'id', let's change the icon shown in the list menu.
+
+Just add an import statement from `@material-ui` for adding the icon, in this case, a user icon:
+
+`import UserIcon from '@material-ui/icons/People';`
+
+and add it to the `authors.icon` property
+
+The code for just customizing the icon will be:
+
+```javascript
+import React, { Component } from 'react';
+import { AdminBuilder, hydraClient } from '@api-platform/admin';
+import parseHydraDocumentation from '@api-platform/api-doc-parser/lib/hydra/parseHydraDocumentation';
+import UserIcon from '@material-ui/icons/People';
+
+const entrypoint = 'https://demo.api-platform.com';
+
+export default class extends Component {
+ state = { api: null }
+
+ componentDidMount() {
+ parseHydraDocumentation(entrypoint).then(({api}) => {
+ const authors = books.fields.find(({ name }) => 'authors' === name)
+
+ // Set the icon
+ authors.icon = UserIcon
+
+ this.setState({ api });
+ }
+ )
+ }
+
+ render() {
+ if (null === this.state.api) return
Loading...
;
+
+ return
+ }
+}
+```
+
## Using an Autocomplete Input for Relations
We'll make one last improvement to our admin: transforming the relation selector we just created to use autocompletion.
diff --git a/admin/images/admin-demo.gif b/admin/images/admin-demo.gif
new file mode 100644
index 00000000000..464f3937a2a
Binary files /dev/null and b/admin/images/admin-demo.gif differ
diff --git a/admin/index.md b/admin/index.md
index 67beb6b55ed..b1a7ddaba73 100644
--- a/admin/index.md
+++ b/admin/index.md
@@ -1,5 +1,7 @@
# The API Platform Admin
+
+
API Platform Admin is a tool to automatically create a fancy (Material Design) and fully featured administration interface
for any API supporting [the Hydra Core Vocabulary](http://www.hydra-cg.com/), including but not limited to all APIs created
using [the API Platform framework](https://api-platform.com).
diff --git a/client-generator/images/client-generator-demo.gif b/client-generator/images/client-generator-demo.gif
new file mode 100644
index 00000000000..b31e9047373
Binary files /dev/null and b/client-generator/images/client-generator-demo.gif differ
diff --git a/client-generator/images/adnew.png b/client-generator/images/react-native/client-generator-react-native-add.png
similarity index 100%
rename from client-generator/images/adnew.png
rename to client-generator/images/react-native/client-generator-react-native-add.png
diff --git a/client-generator/images/del.png b/client-generator/images/react-native/client-generator-react-native-delete.png
similarity index 100%
rename from client-generator/images/del.png
rename to client-generator/images/react-native/client-generator-react-native-delete.png
diff --git a/client-generator/images/list.png b/client-generator/images/react-native/client-generator-react-native-list.png
similarity index 100%
rename from client-generator/images/list.png
rename to client-generator/images/react-native/client-generator-react-native-list.png
diff --git a/client-generator/images/item.png b/client-generator/images/react-native/client-generator-react-native-show.png
similarity index 100%
rename from client-generator/images/item.png
rename to client-generator/images/react-native/client-generator-react-native-show.png
diff --git a/client-generator/images/react/client-generator-react-delete.png b/client-generator/images/react/client-generator-react-delete.png
new file mode 100644
index 00000000000..ec64e603964
Binary files /dev/null and b/client-generator/images/react/client-generator-react-delete.png differ
diff --git a/client-generator/images/react/client-generator-react-edit.png b/client-generator/images/react/client-generator-react-edit.png
new file mode 100644
index 00000000000..39de92417ed
Binary files /dev/null and b/client-generator/images/react/client-generator-react-edit.png differ
diff --git a/client-generator/images/react/client-generator-react-list-pagination.png b/client-generator/images/react/client-generator-react-list-pagination.png
new file mode 100644
index 00000000000..eb529463d72
Binary files /dev/null and b/client-generator/images/react/client-generator-react-list-pagination.png differ
diff --git a/client-generator/images/react/client-generator-react-list.png b/client-generator/images/react/client-generator-react-list.png
new file mode 100644
index 00000000000..22888d8cc6f
Binary files /dev/null and b/client-generator/images/react/client-generator-react-list.png differ
diff --git a/client-generator/images/react/client-generator-react-show.png b/client-generator/images/react/client-generator-react-show.png
new file mode 100644
index 00000000000..3bdaeb34314
Binary files /dev/null and b/client-generator/images/react/client-generator-react-show.png differ
diff --git a/client-generator/index.md b/client-generator/index.md
index 478e56a6d71..71440833790 100644
--- a/client-generator/index.md
+++ b/client-generator/index.md
@@ -1,24 +1,32 @@
# The API Platform Client Generator
-API Platform Client Generator is a generator for scaffolding apps with Create-Retrieve-Update-Delete features for any API exposing a Hydra documentation. Currently the following targets are available:
+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.
- * React/Redux
- * React Native
- * Vue.js
+
-The generator works especially well with APIs built with the [API Platform](https://api-platform.com) framework.
+*Generated React and React Native apps, updated in real time*
+
+It is able to generate apps using the following frontend stacks:
+
+* [React with Redux](react.md)
+* [React Native](react-native.md)
+* [Vue.js](vuejs.md)
+
+Client Generator works especially well with APIs built with the [API Platform](https://api-platform.com) framework.
## Features
-* Generate high-quality ES6 components and files built with [React](https://facebook.github.io/react/), [Redux](http://redux.js.org), [React Router](https://reacttraining.com/react-router/) and [Redux Form](http://redux-form.com/) including:
- * A list view
- * A creation form
- * An editing form
- * A delete button
-* Use the Hydra API documentation to generate the code
-* Generate the suitable HTML5 input type (`number`, `date`...) according to the type of the API property
-* Display of the server-side validation errors under the related input (if using API Platform Core)
-* Client-side validation (`required` attributes)
-* The generated HTML is compatible with [Bootstrap](https://getbootstrap.com/) and includes mandatory classes
-* The generated HTML code is accessible to people with disabilities ([ARIA](https://www.w3.org/WAI/intro/aria) support)
-* The Redux and the React Router configuration is also generated
+* Generate high-quality ES6:
+ * list view (with pagination)
+ * detail view
+ * creation form
+ * edition form
+ * delete button
+* Supports to-one and to-many relations
+* Uses the appropriate input type (`number`, `date`...)
+* Client-side validation
+* Subscribe to data updates pushed by servers supporting [the Mercure protocol](https://mercure.rocks)
+* Display server-side validation errors under the related input (if using API Platform Core)
+* Integration with [Bootstrap](https://getbootstrap.com/) and [FontAwesome](https://fontawesome.com/) (Progressive Web Apps)
+* Integration with [React Native Elements](https://react-native-training.github.io/react-native-elements/)
+* Accessible to people with disabilities ([ARIA](https://www.w3.org/WAI/intro/aria) support in webapps)
diff --git a/client-generator/react-native.md b/client-generator/react-native.md
index 4c11f5e188f..bb4a3d4e6aa 100644
--- a/client-generator/react-native.md
+++ b/client-generator/react-native.md
@@ -1,40 +1,44 @@
-React Native generator
-======================
+# React Native generator
-Create a React Native application using [React Community's Create React Native App](https://github.com/react-community/create-react-native-app)
+
+
+## Install
+
+To use this generator you need [Node.js](https://nodejs.org/) and [Yarn](https://yarnpkg.com/) (or [NPM](https://www.npmjs.com/)) installed.
+To run the command line tool, we also recommend using [npx](https://www.npmjs.com/package/npx).
+
+Create a React Native application using [Expo CLI](https://docs.expo.io/versions/latest/workflow/expo-cli).
```bash
-$ yarn add -g expo-cli
+$ yarn global add expo-cli
$ expo init my-app
+# When asked, choose to use the blank template
$ cd my-app
```
-Install: React Native Elements, React Native Router Flux, React Native Vector Icons, Redux, React Redux, Redux Thunk, Redux Form, Prop Types
+Install the required dependencies:
```bash
-$ yarn add redux react-redux redux-thunk redux-form react-native-elements react-native-router-flux
-react-native-vector-icons prop-types
+$ yarn add redux react-redux redux-thunk redux-form react-native-elements react-native-router-flux react-native-vector-icons prop-types whatwg-url buffer react-native-event-source
```
-Install the generator globally:
+## Generating a Native App
+
+In the app directory, generate the files for the resource you want:
```bash
-$ yarn global add @api-platform/client-generator
+$ npx @api-platform/client-generator https://demo.api-platform.com . -g react-native --resource book
```
-In the app directory, generate the files for the resource you want:
+Replace the URL with the entrypoint of your Hydra-enabled API.
+Omit the resource flag to generate files for all resource types exposed by the API.
-```
- $ generate-api-platform-client https://demo.api-platform.com -g react-native --resource foo
- # Replace the URL by the entrypoint of your Hydra-enabled API
- # Omit the resource flag to generate files for all resource types exposed by the API
-```
-Create **Router.js** file to import all routes
+Create a `Router.js` file to import all routes:
```javascript
import React from 'react';
import { Router, Stack } from 'react-native-router-flux';
-//replace "book" with the name of resource type
+// Replace "book" with the name of the resource type
import BookRoutes from './routes/book';
const RouterComponent = () => {
@@ -49,16 +53,31 @@ const RouterComponent = () => {
export default RouterComponent;
```
-Here is an example of **App.js**
+
+Here is an example of an `App.js` file:
```javascript
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
-import { createStore, applyMiddleware, combineReducers} from 'redux';
+import { createStore, applyMiddleware, combineReducers } from 'redux';
import { View } from 'react-native';
import {reducer as form} from 'redux-form';
-//replace "book" with the name of resource type
+
+// see https://github.com/facebook/react-native/issues/14796
+import { Buffer } from 'buffer';
+global.Buffer = Buffer;
+
+// see https://github.com/facebook/react-native/issues/16434
+import { URL, URLSearchParams } from 'whatwg-url';
+global.URL = URL;
+global.URLSearchParams = URLSearchParams;
+
+// see https://github.com/facebook/react-native/issues/12890
+import RNEventSource from 'react-native-event-source';
+global.EventSource = RNEventSource;
+
+// Replace "book" with the name of resource type
import book from './reducers/book';
import Router from './Router';
@@ -80,11 +99,12 @@ export default class App extends Component {
```
The code is ready to be executed!
+
```bash
$ expo start
```
-#### Example of running application on IOS simulator
+## Screenshots in iOS Simulator
- 
-  
+ 
+ 
diff --git a/client-generator/react.md b/client-generator/react.md
index 43d2d5373ab..5fb2b3b7413 100644
--- a/client-generator/react.md
+++ b/client-generator/react.md
@@ -1,83 +1,113 @@
-# React generator
+# React Generator
-Create a React application using [Facebook's Create React App](https://github.com/facebookincubator/create-react-app):
+
- $ create-react-app my-app
- $ cd my-app
+The React Client Generator generates a Progressive Web App built with battle-tested libraries from the ecosystem:
-Install React Router, Redux, React Redux, React Router Redux, Redux Form and Redux Thunk (to handle AJAX requests):
+* [React](https://facebook.github.io/react/)
+* [Redux](http://redux.js.org)
+* [React Router](https://reacttraining.com/react-router/)
+* [Redux Form](http://redux-form.com/)
- $ yarn add redux react-redux redux-thunk redux-form react-router-dom react-router-redux prop-types
+It is designed to generate code that works seamlessly with [Facebook's Create React App](https://github.com/facebook/create-react-app).
-Install the generator globally:
+## Install
- $ yarn global add @api-platform/client-generator
+The easiest way to get started is to install [the API Platform distribution](../distribution/index.md).
+It contains the React Client Generator, all dependencies it needs, a Progressive Web App skeleton generated with Create React App,
+a development Docker container to serve the webapp, and all the API Platform components you may need, including an API server
+supporting Hydra.
-Reference the Bootstrap CSS stylesheet in `public/index.html` (optional):
+If you use the API Platform, jump to the next section!
+Alternatively, you can generate a skeleton and install the generator using [npx](https://www.npmjs.com/package/npx).
+To use this generator you need [Node.js](https://nodejs.org/) and [Yarn](https://yarnpkg.com/) (or [NPM](https://www.npmjs.com/)) installed.
-Bootstrap 3 - last release 0.1.15
-```html
-
- React App
+Bootstrap a React application:
-
-
-
+```bash
+$ npx create-react-app client
+$ cd client
+```
+
+Install the required dependencies:
+
+```bash
+$ yarn add redux react-redux redux-thunk redux-form react-router-dom connected-react-router prop-types lodash
+```
+
+Optionally, install Bootstrap and Font Awesome to get an app that looks good:
+
+```bash
+$ yarn add redux bootstrap font-awesome
+```
+
+Finally, start the integrated web server:
+
+```bash
+$ yarn start
```
-Bootstrap 4 - from release 0.1.16
-```html
-
- React App
+## Generating a Progressive Web App
+
+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:
-
-
-
-
+```bash
+$ docker-compose exec client generate-api-platform-client --resource book
```
-In the app directory, generate the files for the resource you want:
+Omit the resource flag to generate files for all resource types exposed by the API.
+
+If you don't use the standalone installation, run the following command instead:
```bash
-$ generate-api-platform-client https://demo.api-platform.com src/ --resource book
+$ npx @api-platform/client-generator https://demo.api-platform.com src/ --resource book
# Replace the URL by the entrypoint of your Hydra-enabled API
-# Omit the resource flag to generate files for all resource types exposed by the API
```
-The code is ready to be executed! Register the generated reducers and components in the `index.js` file, here is an example:
+The code has been generated, and is ready to be executed!
+Register the reducers and the routes in the `client/src/index.js` file:
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
-import * as serviceWorker from './serviceWorker';
-
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { reducer as form } from 'redux-form';
-import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
+import { Route, Switch } from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory';
-import { syncHistoryWithStore, routerReducer as routing } from 'react-router-redux'
-
+import {
+ ConnectedRouter,
+ connectRouter,
+ routerMiddleware
+} from 'connected-react-router';
+import 'bootstrap/dist/css/bootstrap.css';
+import 'font-awesome/css/font-awesome.css';
+import * as serviceWorker from './serviceWorker';
// Replace "book" with the name of the resource type
import book from './reducers/book/';
import bookRoutes from './routes/book';
+const history = createBrowserHistory();
const store = createStore(
- combineReducers({routing, form, book}), // Don't forget to register the reducers here
- applyMiddleware(thunk),
+ combineReducers({
+ router: connectRouter(history),
+ form,
+ book
+ /* Replace book with the name of the resource type */
+ }),
+ applyMiddleware(routerMiddleware(history), thunk)
);
-const history = syncHistoryWithStore(createBrowserHistory(), store);
-
ReactDOM.render(
-
+
{bookRoutes}
-
Not Found
}/>
+ {/* Replace bookRooutes with the name of the resource type */}
+
Not Found
} />
-
+
,
document.getElementById('root')
);
@@ -87,3 +117,14 @@ ReactDOM.render(
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
```
+
+Go to `https://localhost/books/` to start using your app.
+That's all!
+
+## Screenshots
+
+
+
+
+
+
diff --git a/client-generator/vuejs.md b/client-generator/vuejs.md
index 3676b7078ea..d8b077dd84e 100644
--- a/client-generator/vuejs.md
+++ b/client-generator/vuejs.md
@@ -1,4 +1,4 @@
-# Vue.js generator
+# Vue.js Generator
Create a Vue.js application using [vue-cli](https://github.com/vuejs/vue-cli):
@@ -41,7 +41,7 @@ import App from './App.vue';
// Replace "foo" with the name of the resource type
import foo from './store/modules/foo/';
-import fooRoutes from './routes/foo';
+import fooRoutes from './router/foo';
Vue.use(Vuex);
Vue.use(VueRouter);
diff --git a/core/configuration.md b/core/configuration.md
index 4efe43a45df..98893ca857f 100644
--- a/core/configuration.md
+++ b/core/configuration.md
@@ -1,6 +1,6 @@
# Configuration
-Here's the complete configuration of the Symfony bundle with including default values:
+Here's the complete configuration of the Symfony bundle including default values:
```yaml
# api/config/packages/api_platform.yaml
@@ -170,7 +170,7 @@ api_platform:
Symfony\Component\Serializer\Exception\ExceptionInterface: 400
# Or with a constant defined in the 'Symfony\Component\HttpFoundation\Response' class.
- ApiPlatform\Core\Exception\InvalidArgumentException: !php/const:Symfony\Component\HttpFoundation\Response::HTTP_BAD_REQUEST
+ ApiPlatform\Core\Exception\InvalidArgumentException: !php/const Symfony\Component\HttpFoundation\Response::HTTP_BAD_REQUEST
# ...
diff --git a/core/content-negotiation.md b/core/content-negotiation.md
index 27ed9cd17b5..73e6b749e2e 100644
--- a/core/content-negotiation.md
+++ b/core/content-negotiation.md
@@ -87,7 +87,67 @@ In the example above, `xml` or `jsonld` will be allowed and there is no need to
Additionally the `csv` format is added with the mime type `text/csv`.
It is also important to notice that the usage of this attribute will override the formats defined in the configuration, therefore
-this configuration might disable the `json` or the `htlm` on this resource for example.
+this configuration might disable the `json` or the `html` on this resource for example.
+
+You can specify different accepted formats at operation level too:
+```php
+
+
+
+
+
+
+ text/xml
+
+
+
+
+
+
+
+ jsonld
+ text/csv
+
+
+
+```
+YAML:
+```yaml
+resources:
+ App\Entity\Book:
+ collectionOperations:
+ get:
+ formats:
+ xml: ['text/xml'] # works also with "text/html"
+ attributes:
+ formats:
+ 0: 'jsonld' # format already defined in the config
+ csv: 'text/csv'
+```
## Registering a Custom Serializer
diff --git a/core/data-providers.md b/core/data-providers.md
index 5904563b0ab..2fbcc12705c 100644
--- a/core/data-providers.md
+++ b/core/data-providers.md
@@ -101,10 +101,10 @@ final class BlogPostItemDataProvider implements ItemDataProviderInterface, Restr
return BlogPost::class === $resourceClass;
}
- public function getItem(string $resourceClass, $identifiers, string $operationName = null, array $context = []): ?BlogPost
+ public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?BlogPost
{
// Retrieve the blog post item from somewhere then return it or null if not found
- return new BlogPost($identifiers['id']);
+ return new BlogPost($id);
}
}
```
diff --git a/core/deprecations.md b/core/deprecations.md
new file mode 100644
index 00000000000..5907ded3211
--- /dev/null
+++ b/core/deprecations.md
@@ -0,0 +1,97 @@
+# Deprecating Resources and Properties (Alternative to Versioning)
+
+A best practice regarding web APIs development is to apply [the evolution strategy](https://philsturgeon.uk/api/2018/05/02/api-evolution-for-rest-http-apis/)
+to indicate to client applications which resource types, operations and fields are deprecated and shouldn't be used anymore.
+
+While versioning an API requires modifying all clients to upgrade, even the ones not impacted by the changes.
+It's a tedious task that should be avoided as much as possible.
+
+On the other hand, the evolution strategy (also known as versionless APIs) consists of deprecating the fields, resources
+types or operations that will be removed at some point.
+
+Most modern API formats including [JSON-LD / Hydra](content-negotiation.md), [GraphQL](graphql.md) and [OpenAPI](swagger.md)
+allow to mark resources types, operations or fields as deprecated.
+
+## Deprecating Resource Classes, Operations and Properties
+
+When using API Platform, it's easy to mark a whole resource, a specific operation or a specific property as deprecated.
+All documentation formats mentioned in the introduction will then automatically take the deprecation into account.
+
+To deprecate a resource class, use the `deprecationReason` attribute:
+
+```php
+query->get('filter[order]', []);
- }
-}
-```
-
-Finally, register the custom filter:
-
-```yaml
-# api/config/services.yaml
-services:
- # ...
- 'App\Filter\CustomOrderFilter': ~
- # Uncomment only if autoconfiguration isn't enabled
- #tags: [ 'api_platform.filter' ]
-```
-
## ApiFilter Annotation
The annotation can be used on a `property` or on a `class`.
diff --git a/core/form-data.md b/core/form-data.md
index 93c87d21fb2..4f8725c741c 100644
--- a/core/form-data.md
+++ b/core/form-data.md
@@ -17,7 +17,6 @@ This decorator is able to denormalize posted form data to the target object. In
namespace App\EventListener;
-use ApiPlatform\Core\Exception\RuntimeException;
use ApiPlatform\Core\Util\RequestAttributesExtractor;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
@@ -40,7 +39,7 @@ final class DeserializeListener
public function onKernelRequest(GetResponseEvent $event): void {
$request = $event->getRequest();
- if ($request->isMethodSafe() || $request->isMethod(Request::METHOD_DELETE)) {
+ if ($request->isMethodSafe(false) || $request->isMethod(Request::METHOD_DELETE)) {
return;
}
@@ -84,4 +83,4 @@ services:
decorates: 'api_platform.listener.request.deserialize'
arguments:
$decorated: '@App\EventListener\DeserializeListener.inner'
-```
\ No newline at end of file
+```
diff --git a/core/fosuser-bundle.md b/core/fosuser-bundle.md
index 9ff7790b3e5..1b1442e090c 100644
--- a/core/fosuser-bundle.md
+++ b/core/fosuser-bundle.md
@@ -22,7 +22,7 @@ If you are using the API Platform Standard Edition, you will need to enable the
configuration options:
```yaml
-# api/config/packages/api_platform.yaml
+# api/config/packages/framework.yaml
framework:
form: { enabled: true }
```
diff --git a/core/graphql.md b/core/graphql.md
index 157d49ec822..7fe6b4b5948 100644
--- a/core/graphql.md
+++ b/core/graphql.md
@@ -12,11 +12,13 @@ Once enabled, you have nothing to do: your schema describing your API is automat
## Enabling GraphQL
-To enable GraphQL and GraphiQL interface in your API, simply require the graphql-php package using Composer:
+To enable GraphQL and GraphiQL interface in your API, simply require the [graphql-php](https://webonyx.github.io/graphql-php/) package using Composer and clear the cache one more time:
- $ composer require webonyx/graphql-php
+```bash
+docker-compose exec php composer req webonyx/graphql-php && bin/console cache:clear
+```
-You can now use GraphQL at the endpoint: `http://localhost/graphql`.
+You can now use GraphQL at the endpoint: `https://localhost:8443/graphql`.
## GraphiQL
diff --git a/core/identifiers.md b/core/identifiers.md
index 5391eedee9c..bb0b00b341d 100644
--- a/core/identifiers.md
+++ b/core/identifiers.md
@@ -20,7 +20,7 @@ use App\Uuid;
*/
final class Person {
/**
- * @type Uuid
+ * @var Uuid
* @ApiProperty(identifier=true)
*/
public $code;
diff --git a/core/images/deprecated-graphiql.png b/core/images/deprecated-graphiql.png
new file mode 100644
index 00000000000..ae7c421759a
Binary files /dev/null and b/core/images/deprecated-graphiql.png differ
diff --git a/core/images/deprecated-swagger-ui.png b/core/images/deprecated-swagger-ui.png
new file mode 100644
index 00000000000..725a27d92b3
Binary files /dev/null and b/core/images/deprecated-swagger-ui.png differ
diff --git a/core/operations.md b/core/operations.md
index d9b4ec2d474..61d035c3a61 100644
--- a/core/operations.md
+++ b/core/operations.md
@@ -147,7 +147,7 @@ use ApiPlatform\Core\Annotation\ApiResource;
/**
* ...
* @ApiResource(itemOperations={
- * "get"={"method"="GET", "path"="/grimoire/{id}", "requirements"={"id"="\d+"}, "defaults"={"color"="brown"}, "options"={"my_option"="my_option_value", "schemes"={"https"}, "host"="{subdomain}.api-platform.com"}},
+ * "get"={"method"="GET", "path"="/grimoire/{id}", "requirements"={"id"="\d+"}, "defaults"={"color"="brown"}, "options"={"my_option"="my_option_value"}, "schemes"={"https"}, "host"="{subdomain}.api-platform.com"},
* "put"={"method"="PUT", "path"="/grimoire/{id}/update", "hydra_context"={"foo"="bar"}},
* })
*/
@@ -304,7 +304,6 @@ class Answer
namespace App\Entity;
-use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Doctrine\ORM\Mapping as ORM;
@@ -422,7 +421,7 @@ Or in XML:
GET
- foobar
+ foobar
@@ -480,6 +479,7 @@ The `subresourceOperations` attribute also allows you to add an access control o
* }
* }
* )
+ */
class Question
{
}
@@ -518,7 +518,7 @@ class Question
API Platform can leverage the Symfony routing system to register custom operations related to custom controllers. Such custom
controllers can be any valid [Symfony controller](http://symfony.com/doc/current/book/controller.html), including standard
-Symfony controllers extending the [`Symfony\Bundle\FrameworkBundle\Controller\Controller`](http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html)
+Symfony controllers extending the [`Symfony\Bundle\FrameworkBundle\Controller\AbstractController`](http://api.symfony.com/4.1/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.html)
helper class.
However, API Platform recommends to use **action classes** instead of typical Symfony controllers. Internally, API Platform
@@ -534,11 +534,7 @@ Thanks to the [autowiring](http://symfony.com/doc/current/components/dependency_
Symfony Dependency Injection container, services required by an action can be type-hinted in its constructor, it will be
automatically instantiated and injected, without having to declare it explicitly.
-In the following examples, the built-in `GET` operation is registered as well as a custom operation called `special`.
-The `special` operation reference the Symfony route named `book_special`.
-
-Since version 2.3, you can also use the route name as operation name by convention as shown in the next example
-for `book_custom` if no `method` nor `route_name` attributes are specified.
+In the following examples, the built-in `GET` operation is registered as well as a custom operation called `post_publication`.
By default, API Platform uses the first `GET` operation defined in `itemOperations` to generate the IRI of an item and the first `GET` operation defined in `collectionOperations` to generate the IRI of a collection.
@@ -550,24 +546,24 @@ First, let's create your custom operation:
```php
myService = $myService;
+ $this->bookPublishingHandler = $bookPublishingHandler;
}
public function __invoke(Book $data): Book
{
- $this->myService->doSomething($data);
+ $this->bookPublishingHandler->handle($data);
return $data;
}
@@ -580,7 +576,7 @@ passed in the URL.
Here we consider that [autowiring](https://symfony.com/doc/current/service_container/autowiring.html) is enabled for
controller classes (the default when using the API Platform distribution).
This action will be automatically registered as a service (the service name is the same as the class name:
-`App\Controller\BookSpecial`).
+`App\Controller\CreateBookPublication`).
API Platform automatically retrieves the appropriate PHP entity using the data provider then deserializes user data in it,
and for `POST` and `PUT` requests updates the entity with data provided by the user.
@@ -604,15 +600,15 @@ The routing has not been configured yet because we will add it at the resource c
// src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
-use App\Controller\BookSpecial;
+use App\Controller\CreateBookPublication;
/**
* @ApiResource(itemOperations={
* "get",
- * "special"={
- * "method"="GET",
- * "path"="/books/{id}/special",
- * "controller"=BookSpecial::class
+ * "post_publication"={
+ * "method"="POST",
+ * "path"="/books/{id}/publication",
+ * "controller"=CreateBookPublication::class,
* }
* })
*/
@@ -629,10 +625,10 @@ Or in YAML:
App\Entity\Book:
itemOperations:
get: ~
- special:
- method: 'GET'
- path: '/books/{id}/special'
- controller: 'App\Controller\BookSpecial'
+ post_publication:
+ method: POST
+ path: /books/{id}/publication
+ controller: App\Controller\CreateBookPublication
```
Or in XML:
@@ -648,10 +644,10 @@ Or in XML:
-
- GET
- /books/{id}/special
- App\Controller\BookSpecial
+
+ POST
+ /books/{id}/publication
+ App\Controller\CreateBookPublication
@@ -670,16 +666,17 @@ You may want different serialization groups for your custom operations. Just con
// src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
-use App\Controller\BookSpecial;
+use App\Controller\CreateBookPublication;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* @ApiResource(itemOperations={
* "get",
- * "special"={
- * "path"="/books/{id}/special",
- * "controller"=BookSpecial::class,
- * "normalization_context"={"groups"={"special"}}
+ * "post_publication"={
+ * "method"="POST",
+ * "path"="/books/{id}/publication",
+ * "controller"=CreateBookPublication::class,
+ * "normalization_context"={"groups"={"publication"}},
* }
* })
*/
@@ -688,7 +685,7 @@ class Book
//...
/**
- * @Groups("special")
+ * @Groups("publication")
*/
private $isbn;
}
@@ -701,12 +698,12 @@ Or in YAML:
App\Entity\Book:
itemOperations:
get: ~
- special:
- method: 'GET'
- path: '/books/{id}/special'
- controller: 'App\Controller\BookSpecial'
+ post_publication:
+ method: POST
+ path: /books/{id}/publication
+ controller: App\Controller\CreateBookPublication
normalization_context:
- groups: ['special']
+ groups: ['publication']
```
Or in XML:
@@ -722,13 +719,13 @@ Or in XML:
-
- GET
- /books/{id}/special
- App\Controller\BookSpecial
+
+ POST
+ /books/{id}/publication
+ App\Controller\CreateBookPublication
- special
+ publication
@@ -740,22 +737,23 @@ Or in XML:
#### Entity Retrieval
If you want to bypass the automatic retrieval of the entity in your custom operation, you can set the parameter
-`_api_receive` to `false` in the `default` attribute:
+`_api_receive` to `false` in the `defaults` attribute:
```php
-
- /books/{id}/special
- App\Controller\BookSpecial
+
+ POST
+ /books/{id}/publication
+ App\Controller\CreateBookPublicationfalse
@@ -812,6 +812,11 @@ information).
There is another way to create a custom operation. However, we do not encourage its use. Indeed, this one disperses
the configuration at the same time in the routing and the resource configuration.
+The `post_publication` operation references the Symfony route named `book_post_publication`.
+
+Since version 2.3, you can also use the route name as operation name by convention, as shown in the following example
+for `book_post_discontinuation` when neither `method` nor `route_name` attributes are specified.
+
First, let's create your resource configuration:
```php
@@ -823,8 +828,8 @@ use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(itemOperations={
* "get",
- * "special"={"route_name"="book_special"},
-* "book_custom",
+ * "post_publication"={"route_name"="book_post_publication"},
+* "book_post_discontinuation",
* })
*/
class Book
@@ -840,9 +845,9 @@ Or in YAML:
App\Entity\Book:
itemOperations:
get: ~
- special:
- route_name: 'book_special'
- book_custom: ~
+ post_publication:
+ route_name: book_post_publication
+ book_post_discontinuation: ~
```
Or in XML:
@@ -858,50 +863,50 @@ Or in XML:
-
- book_special
+
+ book_post_publication
-
+
```
-API Platform will automatically map this `special` operation with the route `book_special`. Let's create a custom action
+API Platform will automatically map this `post_publication` operation to the route `book_post_publication`. Let's create a custom action
and its related route using annotations:
```php
myService = $myService;
+ $this->bookPublishingHandler = $bookPublishingHandler;
}
/**
* @Route(
- * name="book_special",
- * path="/books/{id}/special",
- * methods={"PUT"},
+ * name="book_post_publication",
+ * path="/books/{id}/publication",
+ * methods={"POST"},
* defaults={
* "_api_resource_class"=Book::class,
- * "_api_item_operation_name"="special"
+ * "_api_item_operation_name"="post_publication"
* }
* )
*/
public function __invoke(Book $data): Book
{
- $this->myService->doSomething($data);
+ $this->bookPublishingHandler->handle($data);
return $data;
}
@@ -909,11 +914,10 @@ class BookSpecial
```
It is mandatory to set `_api_resource_class` and `_api_item_operation_name` (or `_api_collection_operation_name` for a collection
-operation) in the parameters of the route (`defaults` key). It allows API Platform and the Symfony routing system to hook
-together.
+operation) in the parameters of the route (`defaults` key). It allows API Platform to work with the Symfony routing system.
-Alternatively, you can also use standard Symfony controller and YAML or XML route declarations. The following example does
-exactly the same thing than the previous example in a more Symfony-like fashion:
+Alternatively, you can also use a traditional Symfony controller and YAML or XML route declarations. The following example does
+the exact same thing as the previous example:
```php
doSomething($data);
+ return $bookPublishingHandler->handle($data);
}
}
```
```yaml
# api/config/routes.yaml
-book_special:
- path: '/books/{id}/special'
- methods: ['PUT']
+book_post_publication:
+ path: /books/{id}/publication
+ methods: ['POST']
defaults:
- _controller: '\App\Controller\Book::special'
- _api_resource_class: 'App\Entity\Book'
- _api_item_operation_name: 'special'
+ _controller: App\Controller\BookController::createPublication
+ _api_resource_class: App\Entity\Book
+ _api_item_operation_name: post_publication
```
diff --git a/core/pagination.md b/core/pagination.md
index f1f9ef48574..534173893e6 100644
--- a/core/pagination.md
+++ b/core/pagination.md
@@ -345,3 +345,128 @@ class Book
```
Please note that this parameter will always be forced to false when the resource have composite keys due to a [bug in doctrine](https://github.com/doctrine/doctrine2/issues/2910)
+
+## Custom Controller Action
+
+In case you're using a custom controller action make sure you return the `Paginator` object to get the full hydra response with `hydra:view` (which contains information about first, last, next and previous page). The following examples show how to handle it within a repository method. The controller needs to pass through the page number. You will need to use the Doctrine Paginator and pass it to the API Platform Paginator.
+
+First example:
+
+```php
+tokenStorage = $tokenStorage;
+ parent::__construct($registry, Book::class);
+ }
+
+ public function getBooksByFavoriteAuthor(int $page = 1): Paginator
+ {
+ $firstResult = ($page -1) * self::ITEMS_PER_PAGE;
+
+ $user = $this->tokenStorage->getToken()->getUser();
+ $queryBuilder = $this->createQueryBuilder();
+ $queryBuilder->select('b')
+ ->from(Book::class, 'b')
+ ->where('b.author = :author')
+ ->setParameter('author', $user->getFavoriteAuthor()->getId())
+ ->andWhere('b.publicatedOn IS NOT NULL');
+
+ $criteria = Criteria::create()
+ ->setFirstResult($firstResult)
+ ->setMaxResults(self::ITEMS_PER_PAGE);
+ $queryBuilder->addCriteria($criteria);
+
+ $doctrinePaginator = new DoctrinePaginator($queryBuilder);
+ $paginator = new Paginator($doctrinePaginator);
+
+ return $paginator;
+ }
+}
+```
+The Controller would look like this:
+
+```php
+query->get('page', 1);
+
+ return $bookRepository->getBooksByFavoriteAuthor($page);
+ }
+}
+```
+
+The service need to use the proper repository method.
+You can also use the Query object inside the repository method and pass it to the Paginator instead of passing the QueryBuilder and using Criteria. Second Example:
+
+```php
+tokenStorage->getToken()->getUser();
+ $queryBuilder = $this->createQueryBuilder();
+ $queryBuilder->select('b')
+ ->from(Book::class, 'b')
+ ->where('b.author = :author')
+ ->setParameter('author', $user->getFavoriteAuthor()->getId())
+ ->andWhere('b.publicatedOn IS NOT NULL');
+
+ $query = $queryBuilder->getQuery()
+ ->setFirstResult($firstResult)
+ ->setMaxResults(self::ITEMS_PER_PAGE);
+
+ $doctrinePaginator = new DoctrinePaginator($query);
+ $paginator = new Paginator($doctrinePaginator);
+
+ return $paginator;
+ }
+}
+```
diff --git a/core/performance.md b/core/performance.md
index 3d86bb4ecce..6a9853a0589 100644
--- a/core/performance.md
+++ b/core/performance.md
@@ -264,3 +264,47 @@ api_platform:
```
More details are available on the [pagination documentation](pagination.md#partial-pagination).
+
+## Profiling with Blackfire.io
+
+Blackfire.io allows you to monitor the performance of your applications. For more information, visit the [Blackfire.io website](https://blackfire.io/).
+
+To configure Blackfire.io follow these simple steps:
+
+1. Add the following to your `docker-compose.yml` file (or an [override file](https://docs.docker.com/compose/reference/overview/#specifying-multiple-compose-files), if only to be used in development)
+
+```yaml
+ blackfire:
+ image: blackfire/blackfire
+ environment:
+ # Exposes the host BLACKFIRE_SERVER_ID and TOKEN environment variables.
+ - BLACKFIRE_SERVER_ID
+ - BLACKFIRE_SERVER_TOKEN
+```
+
+2. Add your Blackfire.io id and server token to your `.env` file at the root of your project (be sure not to commit this to a public repository)
+
+ BLACKFIRE_SERVER_ID=xxxxxxxxxx
+ BLACKFIRE_SERVER_TOKEN=xxxxxxxxxx
+
+ or set it in the console before running Docker commands
+
+ $ export BLACKFIRE_SERVER_ID=xxxxxxxxxx
+ $ export BLACKFIRE_SERVER_TOKEN=xxxxxxxxxx
+
+3. Install and configure the Blackfire probe in the app container, by adding the following to your `./Dockerfile`
+
+```dockerfile
+ RUN version=$(php -r "echo PHP_MAJOR_VERSION.PHP_MINOR_VERSION;") \
+ && curl -A "Docker" -o /tmp/blackfire-probe.tar.gz -D - -L -s https://blackfire.io/api/v1/releases/probe/php/alpine/amd64/$version \
+ && tar zxpf /tmp/blackfire-probe.tar.gz -C /tmp \
+ && mv /tmp/blackfire-*.so $(php -r "echo ini_get('extension_dir');")/blackfire.so \
+ && printf "extension=blackfire.so\nblackfire.agent_socket=tcp://blackfire:8707\n" > $PHP_INI_DIR/conf.d/blackfire.ini
+```
+
+4. Rebuild and restart all your containers
+
+ $ docker-compose build
+ $ docker-compose up -d
+
+For details on how to perform profiling, see [the Blackfire.io documentation](https://blackfire.io/docs/integrations/docker#using-the-client-for-http-profiling).
diff --git a/core/security.md b/core/security.md
index 49c90ca9f18..ab5e763f61b 100644
--- a/core/security.md
+++ b/core/security.md
@@ -120,3 +120,8 @@ App\Entity\Book:
access_control_message: 'Sorry, but you are not the book owner.'
# ...
```
+
+In access control expressions for collection, the `object` variable contains the list of resources that will be serialized.
+To remove entries from a collection, you should implement [a Doctrine extension](extensions.md) to customize the generated DQL query (e.g. add `WHERE` clauses depending of the currently connected user) instead of using access control expressions.
+
+If you use [custom data providers](data-providers.md), you'll have to implement the filtering logic accordingly to the persistence layer you rely on.
diff --git a/core/serialization.md b/core/serialization.md
index df0b363e551..a9bb935709e 100644
--- a/core/serialization.md
+++ b/core/serialization.md
@@ -54,11 +54,21 @@ framework:
If you use [Symfony Flex](https://github.com/symfony/flex), just execute `composer req doctrine/annotations` and you are
all set!
+If you want to use YAML or XML, please add the mapping path in the serializer configuration:
+
+```yaml
+# api/config/packages/api_platform.yaml
+framework:
+ serializer:
+ mapping:
+ paths: ['%kernel.project_dir%/config/serialization']
+```
+
## Using Serialization Groups
It is simple to specify what groups to use in the API system:
-1. Add the `normalizationContext` and `denormalizationContext` annotation properties to the `@ApiResource` annotation, and specify which groups to use. Here you see that we add `read` and `write`, respectively. You can use any group names you wish.
+1. Add the `normalizationContext` and `denormalizationContext` annotation properties to the `@ApiResource` annotation, and specify which groups to use. Here you see that we add `read` and `write`, respectively. You can use any group names you wish.
2. Apply the `@Groups` annotation to properties in the object.
```php
@@ -129,7 +139,7 @@ App\Entity\Book:
```
In the previous example, the `name` property will be visible when reading (`GET`) the object, and it will also be available
-to write (`PUT/POST`). The `author` property will be write-only; it will not be visible when serialized responses are
+to write (`PUT/POST`). The `author` property will be write-only; it will not be visible when serialized responses are
returned by the API.
Internally, API Platform passes the value of the `normalization_context` to the Symfony Serializer during the normalization
@@ -415,7 +425,7 @@ final class BookContextBuilder implements SerializerContextBuilderInterface
```
If the user has the `ROLE_ADMIN` permission and the subject is an instance of Book, `admin_input` group will be dynamically added to the
-denormalization context. The `$normalization` variable lets you check whether the context is for normalization (if `TRUE`) or denormalization
+denormalization context. The `$normalization` variable lets you check whether the context is for normalization (if `TRUE`) or denormalization
(`FALSE`).
## Changing the Serialization Context on a Per-item Basis
@@ -438,7 +448,7 @@ services:
```
The Normalizer class is a bit harder to understand, because it must ensure that it is only called once and that there is no recursion.
-To accomplish this, it needs to be aware of the Serializer instance itself.
+To accomplish this, it needs to be aware of the parent Normalizer instance itself.
Here is an example:
@@ -450,15 +460,15 @@ namespace App\Serializer;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
+use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
+use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
-use Symfony\Component\Serializer\SerializerAwareInterface;
-use Symfony\Component\Serializer\SerializerAwareTrait;
-class BookAttributeNormalizer implements ContextAwareNormalizerInterface, SerializerAwareInterface
+class BookAttributeNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface
{
- use SerializerAwareTrait;
+ use NormalizerAwareTrait;
- private const BOOK_ATTRIBUTE_NORMALIZER_ALREADY_CALLED = 'BOOK_ATTRIBUTE_NORMALIZER_ALREADY_CALLED';
+ private const ALREADY_CALLED = 'BOOK_ATTRIBUTE_NORMALIZER_ALREADY_CALLED';
private $tokenStorage;
@@ -473,13 +483,15 @@ class BookAttributeNormalizer implements ContextAwareNormalizerInterface, Serial
$context['groups'][] = 'can_retrieve_book';
}
- return $this->passOn($object, $format, $context);
+ $context[self::ALREADY_CALLED] = true;
+
+ return $this->normalizer->normalize($object, $format, $context);
}
public function supportsNormalization($data, $format = null, array $context = [])
{
// Make sure we're not called twice
- if (isset($context[self::BOOK_ATTRIBUTE_NORMALIZER_ALREADY_CALLED])) {
+ if (isset($context[self::ALREADY_CALLED])) {
return false;
}
@@ -492,25 +504,14 @@ class BookAttributeNormalizer implements ContextAwareNormalizerInterface, Serial
// for the current $object (book) and
// return true or false
}
-
- private function passOn($object, $format = null, array $context = [])
- {
- if (!$this->serializer instanceof NormalizerInterface) {
- throw new \LogicException(sprintf('Cannot normalize object "%s" because the injected serializer is not a normalizer', $object));
- }
-
- $context[self::BOOK_ATTRIBUTE_NORMALIZER_ALREADY_CALLED] = true;
-
- return $this->serializer->normalize($object, $format, $context);
- }
}
```
This will add the serialization group `can_retrieve_book` only if the currently logged-in user has access to the given book
instance.
-Note: In this example, we use the `TokenStorageInterface` to verify access to the book instance. However, Symfony
-provides many useful other services that might be better suited to your use case. For example, the [`AuthorizationChecker`](https://symfony.com/doc/current/components/security/authorization.html#authorization-checker).
+Note: In this example, we use the `TokenStorageInterface` to verify access to the book instance. However, Symfony
+provides many useful other services that might be better suited to your use case. For example, the [`AuthorizationChecker`](https://symfony.com/doc/current/components/security/authorization.html#authorization-checker).
## Name Conversion
@@ -533,7 +534,7 @@ api_platform:
## Decorating a Serializer and Adding Extra Data
-In the following example, we will see how we add extra informations to the serialized output. Here is how we add the
+In the following example, we will see how we add extra informations to the serialized output. Here is how we add the
date on each request in `GET`:
```yaml
@@ -608,7 +609,7 @@ API Platform is able to guess the entity identifier using [Doctrine metadata](ht
It also supports composite identifiers.
If you are not using the Doctrine ORM Provider, you must explicitly mark the identifier using the `identifier` attribute of
-the `ApiPlatform\Core\Annotation\ApiProperty` annotation. For example:
+the `ApiPlatform\Core\Annotation\ApiProperty` annotation. For example:
```php
@@ -642,8 +643,18 @@ class Book
}
```
+You can also use the YAML configuration format:
+
+```yaml
+# api/config/api_platform/resources.yaml
+App\Entity\Book:
+ properties:
+ id:
+ identifier: true
+```
+
In some cases, you will want to set the identifier of a resource from the client (e.g. a client-side generated UUID, or a slug).
-In such cases, you must make the identifier property a writable class property. Specifically, to use client-generated IDs, you
+In such cases, you must make the identifier property a writable class property. Specifically, to use client-generated IDs, you
must do the following:
1. create a setter for the identifier of the entity (e.g. `public function setId(string $id)`) or make it a `public` property ,
@@ -666,7 +677,7 @@ an IRI. A client that uses JSON-LD must send a second HTTP request to retrieve i
}
```
-You can configure API Platform to embed the JSON-LD context in the root document like the by adding the `jsonld_embed_context`
+You can configure API Platform to embed the JSON-LD context in the root document by adding the `jsonld_embed_context`
attribute to the `@ApiResource` annotation:
```php
diff --git a/core/swagger.md b/core/swagger.md
index 172f8861f04..b5d41426ac9 100644
--- a/core/swagger.md
+++ b/core/swagger.md
@@ -1,6 +1,6 @@
# Swagger / Open API Support
-API Platform natively support the [Open API](https://www.openapis.org/) (formerly Swagger) API documentation format.
+API Platform natively supports the [Open API](https://www.openapis.org/) (formerly Swagger) API specification format.
It also integrates a customized version of [Swagger UI](https://swagger.io/swagger-ui/), a nice tool to display the
API documentation in a user friendly way.
@@ -18,7 +18,7 @@ the `GET` operation of `/foos` path
# api/config/services.yaml
services:
'App\Swagger\SwaggerDecorator':
- decorates: 'api_platform.swagger.normalizer.documentation'
+ decorates: 'api_platform.swagger.normalizer.api_gateway'
arguments: [ '@App\Swagger\SwaggerDecorator.inner' ]
autoconfigure: false
```
@@ -46,7 +46,7 @@ final class SwaggerDecorator implements NormalizerInterface
$customDefinition = [
'name' => 'fields',
- 'definition' => 'Fields to remove of the output',
+ 'description' => 'Fields to remove of the output',
'default' => 'id',
'in' => 'query',
];
@@ -234,6 +234,8 @@ class User
You also have full control over both built-in and custom operations documentation:
+In Yaml:
+
```yaml
resources:
App\Entity\Rabbit:
@@ -262,6 +264,54 @@ resources:
description: Pink rabbit
```
+or with XML:
+
+```xml
+
+
+
+
+
+ get
+ /rabbit/rand
+ App\Controller\RandomRabbit
+
+ Random rabbit picture
+
+ # Pop a great rabbit picture by color!
+
+ 
+
+
+
+ body
+
+ object
+
+
+ string
+
+
+ string
+
+
+
+
+ Rabbit
+ Pink rabbit
+
+
+
+
+
+
+
+
+```
+

## Changing the Swagger UI Location
@@ -320,11 +370,9 @@ As described [in the Symfony documentation](https://symfony.com/doc/current/temp
You may want to copy the [one shipped with API Platform](https://github.com/api-platform/core/blob/master/src/Bridge/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig) and customize it.
-### Enable Swagger doc for API Gateway
-
-[AWS API Gateway](https://aws.amazon.com/api-gateway/) supports Swagger 2.0 partially, but it [requires some changes](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-known-issues.html).
-Fortunately, API Platform provides a way to be compatible with both Swagger 2.0 & API Gateway.
+## Compatibilily Layer with Amazon API Gateway
-To enable API Gateway compatibility on your Swagger doc, add `api_gateway=true` query parameter:
+[AWS API Gateway](https://aws.amazon.com/api-gateway/) supports OpenAPI partially, but it [requires some changes](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-known-issues.html).
+Fortunately, API Platform provides a way to be compatible with Amazon API Gateway.
-`http://www.example.com/docs.json?api_gateway=true`
+To enable API Gateway compatibility on your OpenAPI docs, add `api_gateway=true` as query parameter: `http://www.example.com/docs.json?api_gateway=true`
diff --git a/deployment/heroku.md b/deployment/heroku.md
index 893925dea34..17587278215 100644
--- a/deployment/heroku.md
+++ b/deployment/heroku.md
@@ -58,7 +58,11 @@ Create a new file named `Procfile` in the `api/` directory with the following co
web: vendor/bin/heroku-php-apache2 public/
```
-As Heroku doesn't support Varnish out of the box, let's remove it's integration:
+Be sure to add the Apache Pack in your dependencies:
+
+ composer require symfony/apache-pack
+
+As Heroku doesn't support Varnish out of the box, let's disable its integration:
```patch
# api/config/packages/api_platform.yaml
diff --git a/deployment/kubernetes.md b/deployment/kubernetes.md
index 1a8872ec040..e005ac15564 100644
--- a/deployment/kubernetes.md
+++ b/deployment/kubernetes.md
@@ -18,58 +18,72 @@ package manager) chart to deploy in a wink on any of these platforms.
1. Build the PHP and Nginx Docker images:
- docker build -t gcr.io/test-api-platform/php -t gcr.io/test-api-platform/php:latest api --target api_platform_php
- docker build -t gcr.io/test-api-platform/nginx -t gcr.io/test-api-platform/nginx:latest api --target api_platform_nginx
- docker build -t gcr.io/test-api-platform/varnish -t gcr.io/test-api-platform/varnish:latest api --target api_platform_varnish
+```console
+docker build -t gcr.io/test-api-platform/php -t gcr.io/test-api-platform/php:latest api --target api_platform_php
+docker build -t gcr.io/test-api-platform/nginx -t gcr.io/test-api-platform/nginx:latest api --target api_platform_nginx
+docker build -t gcr.io/test-api-platform/varnish -t gcr.io/test-api-platform/varnish:latest api --target api_platform_varnish
+```
2. Push your images to your Docker registry, example with [Google Container Registry](https://cloud.google.com/container-registry/):
- Docker client versions <= 18.03:
-
- gcloud docker -- push gcr.io/test-api-platform/php
- gcloud docker -- push gcr.io/test-api-platform/nginx
- gcloud docker -- push gcr.io/test-api-platform/varnish
-
- Docker client versions > 18.03:
-
- gcloud auth configure-docker
- docker push gcr.io/test-api-platform/php
- docker push gcr.io/test-api-platform/nginx
- docker push gcr.io/test-api-platform/varnish
+Docker client versions `<= 18.03`:
+
+```console
+gcloud docker -- push gcr.io/test-api-platform/php
+gcloud docker -- push gcr.io/test-api-platform/nginx
+gcloud docker -- push gcr.io/test-api-platform/varnish
+```
+
+Docker client versions `> 18.03`:
+
+```console
+gcloud auth configure-docker
+docker push gcr.io/test-api-platform/php
+docker push gcr.io/test-api-platform/nginx
+docker push gcr.io/test-api-platform/varnish
+```
## Deploying
Firstly you need to update helm dependencies by running:
- helm dependency update ./api/helm/api
+```console
+helm dependency update ./api/helm/api
+```
You are now ready to deploy the API!
Deploy your API to the container:
- helm install ./api/helm/api --namespace=baz --name baz \
- --set php.repository=gcr.io/test-api-platform/php \
- --set nginx.repository=gcr.io/test-api-platform/nginx \
- --set secret=MyAppSecretKey \
- --set postgresql.postgresPassword=MyPgPassword \
- --set postgresql.persistence.enabled=true \
- --set corsAllowOrigin='^https?://[a-z\]*\.mywebsite.com$'
+```console
+helm install ./api/helm/api --namespace=baz --name baz \
+ --set php.repository=gcr.io/test-api-platform/php \
+ --set nginx.repository=gcr.io/test-api-platform/nginx \
+ --set secret=MyAppSecretKey \
+ --set postgresql.postgresPassword=MyPgPassword \
+ --set postgresql.persistence.enabled=true \
+ --set corsAllowOrigin='^https?://[a-z\]*\.mywebsite.com$'
+```
If you prefer to use a managed DBMS like [Heroku Postgres](https://www.heroku.com/postgres) or
[Google Cloud SQL](https://cloud.google.com/sql/docs/postgres/) (recommended):
- helm install --name api ./api/helm/api \
- # ...
- --set postgresql.enabled=false \
- --set postgresql.url=pgsql://username:password@host/database?serverVersion=9.6
+```console
+helm install --name api ./api/helm/api \
+ # ...
+ --set postgresql.enabled=false \
+ --set postgresql.url=pgsql://username:password@host/database?serverVersion=9.6
+```
If you want to use a managed Varnish such as [Fastly](https://www.fastly.com) for the invalidation cache mechanism
provided by API Platform:
- helm install --name api ./api/helm/api \
- # ...
- --set varnish.enabled=false \
- --set varnish.url=https://myvarnish.com
+```console
+helm install --name api ./api/helm/api \
+ # ...
+ --set varnish.enabled=false \
+ --set varnish.url=https://myvarnish.com
+```
Finally, build the `client` and `admin` JavaScript apps and [deploy them on a static
website hosting service](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#deployment).
@@ -78,21 +92,25 @@ website hosting service](https://github.com/facebookincubator/create-react-app/b
Before running your application for the first time, be sure to create the database schema:
- PHP_POD=$(kubectl --namespace=bar get pods -l app=php -o jsonpath="{.items[0].metadata.name}")
- kubectl --namespace=bar exec -it $PHP_POD -- bin/console doctrine:schema:create
+```console
+PHP_POD=$(kubectl --namespace=bar get pods -l app=php -o jsonpath="{.items[0].metadata.name}")
+kubectl --namespace=bar exec -it $PHP_POD -- bin/console doctrine:schema:create
+```
## Tiller RBAC Issue
-We noticed that some tiller RBAC trouble occured, you generally can resolve it running:
+We noticed that some tiller RBAC trouble occurred, you generally can resolve it running:
- kubectl create serviceaccount --namespace kube-system tiller
- serviceaccount "tiller" created
+```console
+kubectl create serviceaccount --namespace kube-system tiller
+ serviceaccount "tiller" created
- kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
- clusterrolebinding "tiller-cluster-rule" created
+kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
+ clusterrolebinding "tiller-cluster-rule" created
- kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'
- deployment "tiller-deploy" patched
+kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'
+ deployment "tiller-deploy" patched
+```
Please, see the [related issue](https://github.com/kubernetes/helm/issues/3130) for further details / informations
You can also take a look to the [related documentation](https://github.com/kubernetes/helm/blob/master/docs/rbac.md)
diff --git a/distribution/debugging.md b/distribution/debugging.md
index 64b8479f39f..170b547b17c 100644
--- a/distribution/debugging.md
+++ b/distribution/debugging.md
@@ -43,6 +43,7 @@ services:
XDEBUG_CONFIG: >-
remote_enable=1
remote_host=host.docker.internal
+ remote_connect_back=1
remote_port=9000
idekey=PHPSTORM
# This should correspond to the server declared in PHPStorm `Preferences | Languages & Frameworks | PHP | Servers`
diff --git a/distribution/index.md b/distribution/index.md
index d06268d52d1..19654f49068 100644
--- a/distribution/index.md
+++ b/distribution/index.md
@@ -6,7 +6,7 @@
> - Fabien Potencier (creator of Symfony), SymfonyCon 2017
[API Platform](https://api-platform.com) is a powerful but easy to use **full stack** framework dedicated to API-driven projects.
-It contains a **PHP** library to create fully-featured APIs supporting industry-leading standards (JSON-LD, GraphQL, OpenAPI...),
+It contains a **PHP** library to create fully featured APIs supporting industry-leading standards (JSON-LD, GraphQL, OpenAPI...),
provides ambitious **JavaScript** tooling to consume those APIs in a snap (admin, PWA and mobile apps generators, hypermedia
client...) and is shipped with a nice **Docker** and **Kubernetes** integration to develop and deploy instantly in the cloud.
@@ -66,9 +66,13 @@ it.
API Platform is shipped with a [Docker](https://docker.com) setup that makes it easy to get a containerized development
environment up and running. If you do not already have Docker on your computer, [it's the right time to install it](https://docs.docker.com/install/).
+On Mac, only [Docker for Mac](https://docs.docker.com/docker-for-mac/) is supported.
+Similarly, on Windows, only [Docker for Windows](https://docs.docker.com/docker-for-windows/) is supported. Docker Machine **is not** supported out of the box.
+
Open a terminal, and navigate to the directory containing your project skeleton. Run the following command to start all
services using [Docker Compose](https://docs.docker.com/compose/):
+ $ docker-compose pull # Download the latest versions of the pre-built images
$ docker-compose up -d # Running in detached mode
This starts the following services:
@@ -83,11 +87,6 @@ This starts the following services:
| cache-proxy | A HTTP cache proxy for the API provided by Varnish | 8081 | all (prefer using a managed service in prod)
| h2-proxy | A HTTP/2 and HTTPS development proxy for all apps | 443 (client) 444 (admin) 8443 (api) 8444 (cache-proxy) | dev (configure properly your web server in prod)
-If you encounter problems running Docker on Windows (especially with Docker Toolbox), see [our Troubleshooting guide](../extra/troubleshooting.md#using-docker).
-
-The first time you start the containers, Docker downloads and builds images for you. It will take some time, but don't worry,
-this is done only once. Starting servers will then be lightning fast.
-
To see the container's logs, run:
$ docker-compose logs -f # follow the logs
@@ -151,7 +150,7 @@ And start the built-in PHP server or the Symfony WebServerBundle:
All JavaScript components are also [available as standalone libraries](https://github.com/api-platform?language=javascript)
installable with NPM or Yarn.
-**Note:** when installing API Platform this way, the API will be exposed as the `/api/` path. You need to open `http://localhost:8000/api/` to see the API documentation.
+**Note:** when installing API Platform this way, the API will be exposed as the `/api/` path. You need to open `http://localhost:8000/api/` to see the API documentation. If you are deploying API Platform directly on an Apache or Nginx webserver and getting a 404 error on opening this link, you will need to enable the [rewriting rules](https://symfony.com/doc/current/setup/web_server_configuration.html) for your specific webserver software.
## It's Ready!
@@ -510,7 +509,7 @@ Now try to add another book by issuing a `POST` request to `/books` with the fol
}
```
-Oops, we missed to add the title. But submit the request anyway. You should get a 500 error with the following message:
+Oops, we missed to add the title. Submit the request anyway, you should get a 500 error with the following message:
An exception occurred while executing 'INSERT INTO book [...] VALUES [...]' with params [...]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'title' cannot be null
@@ -646,7 +645,9 @@ ISBN isn't valid...
Isn't API Platform a REST **and** GraphQL framework? That's true! GraphQL support isn't enabled by default, to add it we
need to install the [graphql-php](https://webonyx.github.io/graphql-php/) library. Run the following command (the cache needs to be cleared twice):
- $ docker-compose exec php composer req webonyx/graphql-php && bin/console cache:clear
+```bash
+docker-compose exec php composer req webonyx/graphql-php && docker-compose exec php bin/console cache:clear
+```
You now have a GraphQL API! Open `https://localhost:8443/graphql` to play with it using the nice [GraphiQL](https://github.com/graphql/graphiql)
UI that is shipped with API Platform:
diff --git a/distribution/testing.md b/distribution/testing.md
index 1b4e3fe23e0..30d0b8f3ba6 100644
--- a/distribution/testing.md
+++ b/distribution/testing.md
@@ -185,6 +185,10 @@ You may also be interested in these alternative testing tools (not included in t
## Running Unit Tests with PHPUnit
+To install [PHPUnit](https://phpunit.de/) test suite, execute the following command:
+
+ $ docker-compose exec php composer require --dev symfony/phpunit-bridge
+
To run your [PHPUnit](https://phpunit.de/) test suite, execute the following command:
- $ docker-compose exec php vendor/bin/phpunit
+ $ docker-compose exec php bin/phpunit
diff --git a/extra/releases.md b/extra/releases.md
index 8bc88532bae..5ac75a393fe 100644
--- a/extra/releases.md
+++ b/extra/releases.md
@@ -4,8 +4,8 @@ API Platform follows the [Semantic Versioning](https://semver.org) strategy.
Only 3 versions are maintained at the same time:
-* **stable** (currently the **2.2** branch): regular bug fixes are integrated in this version
-* **old-stable** (currently **2.1** branch): security fixes are integrated in this version, regular bug fixes are **not** backported in it
+* **stable** (currently the **2.3** branch): regular bug fixes are integrated in this version
+* **old-stable** (currently **2.2** branch): security fixes are integrated in this version, regular bug fixes are **not** backported in it
* **development** (**master** branch): new features target this branch
Older versions (1.x, 2.0...) **are not maintained**. If you still use them, you must upgrade as soon as possible.
diff --git a/extra/troubleshooting.md b/extra/troubleshooting.md
index ebb759bc33c..60d9e9ae7d0 100644
--- a/extra/troubleshooting.md
+++ b/extra/troubleshooting.md
@@ -26,12 +26,72 @@ You can change the port to be used in the `docker-compose.yml` file (default is
## Using API Platform and JMS Serializer in the same project
-By default, [JMS Serializer Bundle](http://jmsyst.com/bundles/JMSSerializerBundle) replaces the `serializer` service by its own. However, API Platform requires the Symfony serializer (and not the JMS one) to work properly.
-Fortunately, this behavior can be deactivated using the following configuration:
+For the latest versions of [JMSSerializerBundle](http://jmsyst.com/bundles/JMSSerializerBundle), there is no conflict so everything should work out of the box.
+
+If you are still using the old, unmaintained v1 of JMSSerializerBundle, the best way should be to [upgrade to v2](https://github.com/schmittjoh/JMSSerializerBundle/blob/2.4.2/UPGRADING.md#upgrading-from-1x-to-20) of JMSSerializerBundle.
+
+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:
```yaml
-# api/config/packages/api_platform.yaml
+# app/config/config.yml
-jms_serializer:
+jms_serializer:
enable_short_alias: false
```
+
+The JMS Serializer service is available as `jms_serializer`.
+
+## "upstream sent too big header while reading response header from upstream" 502 Error
+
+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`.
+
+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.
+
+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:
+
+```
+server {
+ root /srv/api/public;
+
+ location / {
+ # try to serve file directly, fallback to index.php
+ try_files $uri /index.php$is_args$args;
+ }
+
+ location ~ ^/index\.php(/|$) {
+ # Comment the next line and uncomment the next to enable dynamic resolution (incompatible with Kubernetes)
+ fastcgi_pass php:9000;
+ #resolver 127.0.0.11;
+ #set $upstream_host php;
+ #fastcgi_pass $upstream_host:9000;
+
+ # Bigger buffer size to handle cache invalidation headers expansion
+ fastcgi_buffer_size 32k;
+ fastcgi_buffers 8 16k;
+
+ fastcgi_split_path_info ^(.+\.php)(/.*)$;
+ include fastcgi_params;
+ # When you are using symlinks to link the document root to the
+ # current version of your application, you should pass the real
+ # application path instead of the path to the symlink to PHP
+ # FPM.
+ # Otherwise, PHP's OPcache may not properly detect changes to
+ # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126
+ # for more information).
+ fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
+ fastcgi_param DOCUMENT_ROOT $realpath_root;
+ # Prevents URIs that include the front controller. This will 404:
+ # http://domain.tld/index.php/some-path
+ # Remove the internal directive to allow URIs like this
+ internal;
+ }
+
+ # return 404 for all other php files not matching the front controller
+ # this prevents access to other php files you don't want to be accessible.
+ location ~ \.php$ {
+ return 404;
+ }
+}
+```
+
+You then need to rebuild your containers by running `docker-compose build`.
diff --git a/outline.yaml b/outline.yaml
new file mode 100644
index 00000000000..56fe1937fe8
--- /dev/null
+++ b/outline.yaml
@@ -0,0 +1,78 @@
+chapters:
+ - title: 'The Distribution: Create Powerful APIs with Ease'
+ path: distribution
+ items:
+ - index
+ - testing
+ - debugging
+ - title: The API Component
+ path: core
+ items:
+ - index
+ - getting-started
+ - design
+ - operations
+ - graphql
+ - filters
+ - serialization
+ - validation
+ - security
+ - data-providers
+ - data-persisters
+ - pagination
+ - events
+ - content-negotiation
+ - deprecations
+ - performance
+ - extensions
+ - swagger
+ - dto
+ - file-upload
+ - default-order
+ - errors
+ - external-vocabularies
+ - operation-path-naming
+ - extending-jsonld-context
+ - form-data
+ - identifiers
+ - jwt
+ - angularjs-integration
+ - fosuser-bundle
+ - nelmio-api-doc
+ - configuration
+ - title: The Schema Generator Component
+ path: schema-generator
+ items:
+ - index
+ - getting-started
+ - configuration
+ - title: The Admin Component
+ path: admin
+ items:
+ - index
+ - getting-started
+ - authentication-support
+ - handling-relations-to-collections
+ - title: The Client Generator Component
+ path: client-generator
+ items:
+ - index
+ - react
+ - react-native
+ - vuejs
+ - troubleshooting
+ - title: Deployment
+ path: deployment
+ items:
+ - index
+ - kubernetes
+ - heroku
+ - traefik
+ - title: Extra
+ path: extra
+ items:
+ - releases
+ - philosophy
+ - troubleshooting
+ - contribution-guides
+ - conduct
diff --git a/schema-generator/configuration.md b/schema-generator/configuration.md
index 16fc0947bb3..09949f63194 100644
--- a/schema-generator/configuration.md
+++ b/schema-generator/configuration.md
@@ -431,7 +431,7 @@ Example:
```yaml
rdfa:
- - https://raw.githubusercontent.com/rvguha/schemaorg/master/data/schema.rdfa # Experimental version of Schema.org
+ - https://raw.githubusercontent.com/schemaorg/schemaorg/master/data/schema.rdfa # Experimental version of Schema.org
- http://example.com/data/myschema.rfa # Additional types
```