{"payload":{"allShortcutsEnabled":false,"fileTree":{"Resources/doc":{"items":[{"name":"examples","path":"Resources/doc/examples","contentType":"directory"},{"name":"1-setting_up_the_bundle.rst","path":"Resources/doc/1-setting_up_the_bundle.rst","contentType":"file"},{"name":"2-the-view-layer.rst","path":"Resources/doc/2-the-view-layer.rst","contentType":"file"},{"name":"3-listener-support.rst","path":"Resources/doc/3-listener-support.rst","contentType":"file"},{"name":"4-exception-controller-support.rst","path":"Resources/doc/4-exception-controller-support.rst","contentType":"file"},{"name":"annotations-reference.rst","path":"Resources/doc/annotations-reference.rst","contentType":"file"},{"name":"body_listener.rst","path":"Resources/doc/body_listener.rst","contentType":"file"},{"name":"conf.py","path":"Resources/doc/conf.py","contentType":"file"},{"name":"empty-content-status-code.rst","path":"Resources/doc/empty-content-status-code.rst","contentType":"file"},{"name":"format_listener.rst","path":"Resources/doc/format_listener.rst","contentType":"file"},{"name":"index.rst","path":"Resources/doc/index.rst","contentType":"file"},{"name":"param_fetcher_listener.rst","path":"Resources/doc/param_fetcher_listener.rst","contentType":"file"},{"name":"request_body_converter_listener.rst","path":"Resources/doc/request_body_converter_listener.rst","contentType":"file"},{"name":"requirements.txt","path":"Resources/doc/requirements.txt","contentType":"file"},{"name":"versioning.rst","path":"Resources/doc/versioning.rst","contentType":"file"},{"name":"view_response_listener.rst","path":"Resources/doc/view_response_listener.rst","contentType":"file"}],"totalCount":16},"Resources":{"items":[{"name":"config","path":"Resources/config","contentType":"directory"},{"name":"doc","path":"Resources/doc","contentType":"directory"}],"totalCount":2},"":{"items":[{"name":".github","path":".github","contentType":"directory"},{"name":"Context","path":"Context","contentType":"directory"},{"name":"Controller","path":"Controller","contentType":"directory"},{"name":"Decoder","path":"Decoder","contentType":"directory"},{"name":"DependencyInjection","path":"DependencyInjection","contentType":"directory"},{"name":"ErrorRenderer","path":"ErrorRenderer","contentType":"directory"},{"name":"EventListener","path":"EventListener","contentType":"directory"},{"name":"Exception","path":"Exception","contentType":"directory"},{"name":"Form","path":"Form","contentType":"directory"},{"name":"Negotiation","path":"Negotiation","contentType":"directory"},{"name":"Normalizer","path":"Normalizer","contentType":"directory"},{"name":"Request","path":"Request","contentType":"directory"},{"name":"Resources","path":"Resources","contentType":"directory"},{"name":"Response","path":"Response","contentType":"directory"},{"name":"Serializer","path":"Serializer","contentType":"directory"},{"name":"Tests","path":"Tests","contentType":"directory"},{"name":"Util","path":"Util","contentType":"directory"},{"name":"Validator","path":"Validator","contentType":"directory"},{"name":"Version","path":"Version","contentType":"directory"},{"name":"View","path":"View","contentType":"directory"},{"name":".gitattributes","path":".gitattributes","contentType":"file"},{"name":".gitignore","path":".gitignore","contentType":"file"},{"name":".php-cs-fixer.php","path":".php-cs-fixer.php","contentType":"file"},{"name":".readthedocs.yaml","path":".readthedocs.yaml","contentType":"file"},{"name":".styleci.yml","path":".styleci.yml","contentType":"file"},{"name":"CHANGELOG.md","path":"CHANGELOG.md","contentType":"file"},{"name":"FOSRestBundle.php","path":"FOSRestBundle.php","contentType":"file"},{"name":"LICENSE","path":"LICENSE","contentType":"file"},{"name":"README.md","path":"README.md","contentType":"file"},{"name":"UPGRADING-3.0.md","path":"UPGRADING-3.0.md","contentType":"file"},{"name":"composer.json","path":"composer.json","contentType":"file"},{"name":"phpunit","path":"phpunit","contentType":"file"},{"name":"phpunit.xml.dist","path":"phpunit.xml.dist","contentType":"file"}],"totalCount":33}},"fileTreeProcessingTime":11.087389,"foldersToFetch":[],"repo":{"id":1509011,"defaultBranch":"3.x","name":"FOSRestBundle","ownerLogin":"FriendsOfSymfony","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2011-03-21T23:14:13.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/529709?v=4","public":true,"private":false,"isOrgOwned":true},"symbolsExpanded":false,"treeExpanded":true,"refInfo":{"name":"3.x","listCacheKey":"v0:1695814887.0","canEdit":false,"refType":"branch","currentOid":"e01be8113d4451adb3cbb29d7d2cc96bbc698179"},"path":"Resources/doc/2-the-view-layer.rst","currentUser":null,"blob":{"rawLines":null,"stylingDirectives":null,"colorizedLines":null,"csv":null,"csvError":null,"dependabotInfo":{"showConfigurationBanner":false,"configFilePath":null,"networkDependabotPath":"/FriendsOfSymfony/FOSRestBundle/network/updates","dismissConfigurationNoticePath":"/settings/dismiss-notice/dependabot_configuration_notice","configurationNoticeDismissed":null},"displayName":"2-the-view-layer.rst","displayUrl":"https://github.com/FriendsOfSymfony/FOSRestBundle/blob/3.x/Resources/doc/2-the-view-layer.rst?raw=true","headerInfo":{"blobSize":"10.7 KB","deleteTooltip":"You must be signed in to make or propose changes","editTooltip":"You must be signed in to make or propose changes","ghDesktopPath":"https://desktop.github.com","isGitLfs":false,"onBranch":true,"shortPath":"5d403fe","siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2FFriendsOfSymfony%2FFOSRestBundle%2Fblob%2F3.x%2FResources%2Fdoc%2F2-the-view-layer.rst","isCSV":false,"isRichtext":true,"toc":[{"level":1,"text":"Step 2: The view layer","anchor":"step-2-the-view-layer","htmlText":"Step 2: The view layer"},{"level":2,"text":"Introduction","anchor":"introduction","htmlText":"Introduction"},{"level":2,"text":"Forms and Views","anchor":"forms-and-views","htmlText":"Forms and Views"},{"level":2,"text":"Data Transformation","anchor":"data-transformation","htmlText":"Data Transformation"},{"level":2,"text":"Configuration","anchor":"configuration","htmlText":"Configuration"},{"level":3,"text":"JSONP custom handler","anchor":"jsonp-custom-handler","htmlText":"JSONP custom handler"},{"level":3,"text":"CSRF validation","anchor":"csrf-validation","htmlText":"CSRF validation"}],"lineInfo":{"truncatedLoc":"282","truncatedSloc":"212"},"mode":"file"},"image":false,"isCodeownersFile":null,"isPlain":false,"isValidLegacyIssueTemplate":false,"issueTemplate":null,"discussionTemplate":null,"language":"reStructuredText","languageID":419,"large":false,"planSupportInfo":{"repoIsFork":null,"repoOwnedByCurrentUser":null,"requestFullPath":"/FriendsOfSymfony/FOSRestBundle/blob/3.x/Resources/doc/2-the-view-layer.rst","showFreeOrgGatedFeatureMessage":null,"showPlanSupportBanner":null,"upgradeDataAttributes":null,"upgradePath":null},"publishBannersInfo":{"dismissActionNoticePath":"/settings/dismiss-notice/publish_action_from_dockerfile","releasePath":"/FriendsOfSymfony/FOSRestBundle/releases/new?marketplace=true","showPublishActionBanner":false},"rawBlobUrl":"https://github.com/FriendsOfSymfony/FOSRestBundle/raw/3.x/Resources/doc/2-the-view-layer.rst","renderImageOrRaw":false,"richText":"

Step 2: The view layer

\n\n

Introduction

\n

The view layer makes it possible to write format (html, json, xml, etc)\nagnostic controllers, by placing a layer between the Controller and the\ngeneration of the final output via a serializer.

\n

The bundle works both with the Symfony Serializer Component and the more\nsophisticated serializer created by Johannes Schmitt and integrated via the\nJMSSerializerBundle.

\n

In your controller action you will then need to create a View instance that\nis then passed to the fos_rest.view_handler service for processing. The\nView is somewhat modeled after the Response class, but as just stated\nit simply works as a container for all the data/configuration for the\nViewHandler class for this particular action. So the View instance\nmust always be processed by a ViewHandler (see the below section on the\n\"view response listener\" for how to get this processing applied automatically).

\n

FOSRestBundle ships with a controller extending the default Symfony controller,\nwhich adds several convenience methods:

\n
<?php\n\nnamespace AppBundle\\Controller;\n\nuse FOS\\RestBundle\\Controller\\AbstractFOSRestController;\n\nclass UsersController extends AbstractFOSRestController\n{\n    public function getUsersAction()\n    {\n        $data = ...; // get data, in this case list of users.\n        $view = $this->view($data, 200);\n\n        return $this->handleView($view);\n    }\n\n    public function redirectAction()\n    {\n        $view = $this->redirectView($this->generateUrl('some_route'), 301);\n        // or\n        $view = $this->routeRedirectView('some_route', array(), 301);\n\n        return $this->handleView($view);\n    }\n}
\n
.. versionadded:: 2.0\n    The ``ControllerTrait`` trait was added in 2.0.\n\n
\n

There is also a trait called ControllerTrait for anyone that prefers to not\ninject the container into their controller. This requires using setter injection\nto set a ViewHandlerInterface instance via the setViewHandler method.

\n

To simplify this even more: If you rely on the ViewResponseListener in\ncombination with SensioFrameworkExtraBundle you can even omit the calls to\n$this->handleView($view) and directly return the view objects. See chapter\n3 on listeners for more details on the View Response Listener.

\n

As the purpose is to create a format-agnostic controller, data assigned to the\nView instance should ideally be an object graph, though any data type is\nacceptable.

\n

There are also two specialized methods for redirect in the View classes.\nView::createRedirect redirects to an URL called RedirectView and\nView::createRouteRedirect redirects to a route.

\n

There are several more methods on the View class, here is a list of all\nthe important ones for configuring the view:

\n\n\n

Forms and Views

\n

Symfony Forms have special handling inside the view layer. Whenever you:

\n\n

Then:

\n\n

A response example of an invalid form:

\n
{\n  \"code\": 400,\n  \"message\": \"Validation Failed\";\n  \"errors\": {\n    \"children\": {\n      \"username\": {\n        \"errors\": [\n          \"This value should not be blank.\"\n        ]\n      }\n    }\n  }\n}
\n

If you don't like the default exception structure, you can provide your own\nnormalizers.

\n

You can look at FOSRestBundle normalizers for examples.

\n\n

Data Transformation

\n

As we have seen in the section before, the FOSRestBundle relies on the\nSymfony form component to handle submission of view data. In fact, the\nSymfony form builder basically defines the structure of the expected view\ndata which shall be used for further processing - which most of the time\nrelates to a PUT or POST request. This brings a lot of flexibility and allows\nto exactly define the structure of data to be received by the API.

\n

Most of the time the requirements regarding a PUT/POST request are, in\nterms of data structure, fairly simple. The payload within a PUT or POST request\noftentimes will have the exact same structure as received by a previous GET\nrequest, but only with modified value fields. Thus, the fields to be defined\nwithin the form builder process will be the same as the fields marked to be\nserialized within an entity.

\n

However, there is a common use case where straightforward updating of data,\nreceived by a serialized object (GET request), will not work out of the box using\nthe given implementation of the form component: Simple assignment of a reference\nusing an object.

\n

Let's take an entity Task that holds a reference to a Person as\nan example. The serialized Task object will looks as follows:

\n
{\"task_form\":{\"name\":\"Task1\", \"person\":{\"id\":1, \"name\":\"Fabien\"}}}
\n

In a traditional Symfony application we simply define the property of the\nrelated class and it would perfectly assign the person to our task - in this\ncase based on the id:

\n
$builder\n    ->add('name', 'text')\n    ...\n    ->add('person', 'entity', array(\n        'class' => 'Acme\\DemoBundle\\Entity\\Person',\n        'property' => 'id'\n    ))
\n

Unfortunately, this form builder does not accept our serialized object as it is\n- even though it contains the necessary id. In fact, the object would have to\ncontain the id directly assigned to the person field to be accepted by the\nform validation process:

\n
{\"task_form\":{\"name\":\"Task1\", \"person\":1}}
\n

This is somewhat useless since we not only want to display the name of the\nperson, but also do not want to do some client side trick to extract the id\nbefore updating the data. Instead, we rather update the data the same way\nas we received it in our GET request and thus, extend the form builder with a\ndata transformer. Fortunately, the FOSRestBundle comes with an\nEntityToIdObjectTransformer, which can be applied to any form builder:

\n
$personTransformer = new EntityToIdObjectTransformer($this->om, \"AcmeDemoBundle:Person\");\n$builder\n    ->add('name', 'text')\n    ...\n    ->add($builder->create('person', 'text')->addModelTransformer($personTransformer))
\n

This way, the data structure remains untouched and the person can be assigned to\nthe task without any client modifications.

\n\n

Configuration

\n

The formats setting determines which formats are supported by the serializer.\nAny format listed in formats will use the serializer for rendering. A value\nof false means that the given format is disabled.

\n

When using RouteRedirectView::create() the default behavior of forcing a\nredirect to the route when HTML is enabled, but this needs to be enabled for other\nformats as needed.

\n

Finally the HTTP response status code for failed validation defaults to\n400. Note when changing the default you can use name constants of\nSymfony\\Component\\HttpFoundation\\Response class or an integer status code.

\n\n

JSONP custom handler

\n

To enable the common use case of creating JSONP responses, this Bundle provides an\neasy solution to handle a custom handler for this use case. Enabling this setting\nalso automatically uses the mime type listener (see the next chapter) to register\na mime type for JSONP.

\n

Simply add the following to your configuration

\n
fos_rest:\n    view:\n        jsonp_handler: ~
\n

It is also possible to customize both the name of the GET parameter with the\ncallback, as well as the filter pattern that validates if the provided callback\nis valid or not.

\n
fos_rest:\n    view:\n        jsonp_handler:\n           callback_param: mycallback
\n

Finally the filter can also be disabled by setting it to false.

\n
fos_rest:\n    view:\n        jsonp_handler:\n            callback_param: false
\n

When working with JSONP, be aware of CVE-2014-4671 (full explanation can be\nfound here: Abusing JSONP with Rosetta Flash). You SHOULD use NelmioSecurityBundle\nand disable the content type sniffing for script resources.

\n\n

CSRF validation

\n

When building a single application that should handle forms both via HTML forms\nas well as via a REST API, one runs into a problem with CSRF token validation.\nIn most cases, it is necessary to enable them for HTML forms, but it makes no\nsense to use them for a REST API. For this reason there is a form extension to\ndisable CSRF validation for users with a specific role. This of course requires\nthat REST API users authenticate themselves and get a special role assigned.

\n
fos_rest:\n    disable_csrf_role: ROLE_API
\n

That was it!

\n\n
","renderedFileInfo":null,"shortPath":null,"symbolsEnabled":true,"tabSize":8,"topBannersInfo":{"overridingGlobalFundingFile":false,"globalPreferredFundingPath":null,"showInvalidCitationWarning":false,"citationHelpUrl":"https://docs.github.com/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-citation-files","actionsOnboardingTip":null},"truncated":false,"viewable":true,"workflowRedirectUrl":null,"symbols":{"timed_out":false,"not_analyzed":true,"symbols":[]}},"copilotInfo":null,"copilotAccessAllowed":false,"csrf_tokens":{"/FriendsOfSymfony/FOSRestBundle/branches":{"post":"7h4B3flp5rskWkNsvP96eJZr6VJvl9PNAlpWWoOeR4ub78jHibCaZNvRG10MZ9KoipLPob52_Lh7bY-tY_Kj6A"},"/repos/preferences":{"post":"SPxXSmkeEVkq5vN6diWI-UNUFJ7kmE19_YiwzUuNHsIjHY2tWrZcAaQhMpB5U4CRyKP5zsNVXYsdRODr2iewRA"}}},"title":"FOSRestBundle/Resources/doc/2-the-view-layer.rst at 3.x ยท FriendsOfSymfony/FOSRestBundle"}