{"action":"created","issue":{"url":"https://api.github.com/repos/zf-fr/zfr-rest/issues/183","labels_url":"https://api.github.com/repos/zf-fr/zfr-rest/issues/183/labels{/name}","comments_url":"https://api.github.com/repos/zf-fr/zfr-rest/issues/183/comments","events_url":"https://api.github.com/repos/zf-fr/zfr-rest/issues/183/events","html_url":"https://github.com/zf-fr/zfr-rest/issues/183","id":53536699,"number":183,"title":"[RFC] ZfrNext","user":{"login":"bakura10","id":1198915,"avatar_url":"https://avatars.githubusercontent.com/u/1198915?v=3","gravatar_id":"","url":"https://api.github.com/users/bakura10","html_url":"https://github.com/bakura10","followers_url":"https://api.github.com/users/bakura10/followers","following_url":"https://api.github.com/users/bakura10/following{/other_user}","gists_url":"https://api.github.com/users/bakura10/gists{/gist_id}","starred_url":"https://api.github.com/users/bakura10/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bakura10/subscriptions","organizations_url":"https://api.github.com/users/bakura10/orgs","repos_url":"https://api.github.com/users/bakura10/repos","events_url":"https://api.github.com/users/bakura10/events{/privacy}","received_events_url":"https://api.github.com/users/bakura10/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":5,"created_at":"2015-01-06T17:05:14Z","updated_at":"2015-01-19T18:06:07Z","closed_at":null,"body":"This post replaces the [previous one](https://github.com/zf-fr/zfr-rest/issues/182) and tries to formalise a bit more what I have in mind for next ZfrRest (that I will start developing once we have ZF3 components, especially refactored router). Those changes are based on my experience in using ZfrRest for a moderately complex API in production.\r\n\r\n## Error handling\r\n\r\nZfrRest uses its own set of exceptions based on HTTP error. A listener is used to automatically catch the error, and generate a coherent error message that includes status_code, error message and additional information (in case of validation).\r\n\r\nIt also has the ability to configure an exception_map that automatically maps a user-land exception to a ZfrRest exception.\r\n\r\nIn overall, I especially like this part of ZfrRest. It works as expected, and I have no intention to modify it.\r\n\r\n## Routing/controller\r\n\r\nCurrent ZfrRest provides an automatic routing through the `ResourceGraphRoute`. This route reads the Doctrine mapping and ZfrRest mapping, and uses the Criteria API to recursively filter the resources.\r\n\r\n### Advantages\r\n\r\n#### Simplicity\r\n\r\nFor simple use cases (few resources with very simple associations), this works well and allows to quickly build an API with a few ZfrRest annotations.\r\n\r\n#### RAD\r\n\r\nBecause resources are fetched transparently in the route, you receive a ready-to-consume entity in your controler. In case of POST or PUT, incoming data has already been validated, filtered, and hydrated. This means that your actions are very lightweight (basically, just a permission call and a service update).\r\n\r\n### Drawbacks\r\n\r\nIn real-life applications though, current ZfrRest architecture proved to be highly frustrating.\r\n\r\n#### Force to introduce bi-directional associations\r\n\r\nBecause accessing resources is made through associations traversal, ZfrRest forces you to introduce associations that may not make sense or may complicate your domain model. For instance, I had in my application a \"Project\" entity. Each projet could have multiple \"members\". However, I originally did not want to include the bi-directional associations (and instead keep it one-sided, with the \"Member\" entity having an association to a Project).\r\n\r\nHowever, for the \"/projects/:id/members\" to be accessible, I needed to introduce this non-necessary association.\r\n\r\n#### Unecessary database calls\r\n\r\nAssuming the following path \"/users/4/tweets\", the router would have first matched the users 4 (one database call), and then returned a PersistentCollection to tweets. The persistent collection does not generate API calls until it is traversed (allowing you to filter it without loading all the tweets). However, this still does 2 database calls, while it could have been done in 1.\r\n\r\nThe problem is more visible for URI that are even more nested: /users/4/tweets/5/retweets. While not necesarily the best practice, URIs could be nested like that, and this one would generate 3 database calls, while an optimized service could do that in 1.\r\n\r\nThe reason is that the route is a recursive process, and cannot know how to perform optimized DB calls as it uses the simple Criteria API.\r\n\r\n#### Can lead to highly inefficient code\r\n\r\nIn my application, I have a \"Project\" entity, and each team member has their own set of settings for the project. I want my API to return something like that:\r\n\r\n```json\r\n{\r\n   \"projects\": [\r\n     {\r\n       \"id\": 1,\r\n       \"settings\": {\r\n       \t \"id\": 4,\r\n       \t \"user\": 3,\r\n       \t \"language\": \"fr\"\r\n       }\r\n   \t },\r\n   \t {\r\n   \t   \"id\": 2,\r\n   \t   \"settings\": {\r\n   \t     \"id\": 5,\r\n   \t     \"user\": 3,\r\n   \t     \"language\": \"fr\"\r\n   \t   }\r\n   \t }\r\n   ]\r\n}\r\n```\r\n\r\nDepending on the user, the \"settings\" is filled with the specific data of the user. Indeed, it is not possible to introduce a \"settings\" association to the Project entity because it depends on the user.\r\n\r\nIn ZfrRest, this resource can be acceded through the \"/users/4/projects\" endpoint. The controller receives a Collection of projects, and you then need to iterate through them, and fetching the settings based on the logged user. If you have 10 projects, this would mean 10 DB calls, while this could be done in 1 JOIN.\r\n\r\nOnce again, the problem comes from the fact that ZfrRest automatically gives you resources, and prevents you to use an optimized service layer.\r\n\r\n### Proposal\r\n\r\nI suggest completely removing the routing layer of ZfrRest, and instead replace it by an explicit routing. This document uses a Dash-like syntax.\r\n\r\nI'd like if ZF3/next completely removes the AbstractRestfulController, and just comes with an AbstractActionController.\r\n\r\nIn overall, the ZfrRest controller/routing layer will be a better ZF2 AbstractRestfulController.\r\n\r\n#### Routing\r\n\r\n> Current Dash config assumes \"path\" / \"action\" / \"controller\", but I think this should be changed for \"path\" / \"controller\" / \"action\". This proposal assumes this order.\r\n\r\n##### Simple routes\r\n\r\nFor simple routes, it works like any action controller, except that the action is ommited:\r\n\r\n```php\r\nreturn [\r\n\t'routes' => [\r\n\t\t'users' => ['/users', UserListController::class]\t]\r\n];\r\n```\r\n\r\nThis route would be used to dispatch a collection URI (like POST /users, GET /users). The controller would automatically dispatch to the method based on the HTTP verb. If no matching method could be used, the controller would trigger a `MethodNotAllowedException` to return a 405 error.\r\n\r\nFor OPTIONS method, a built-in method will be present in the base controller.\r\n\r\n##### Details route\r\n\r\nNow, if we want to access a single user:\r\n\r\n```php\r\nreturn [\r\n\t'routes' => [\r\n\t\t'users' => ['/users', UserListController::class, 'children' => [\r\n\t\t\t'user' => ['/:user_id', UserController::class]\r\n\t\t]]\r\n\t]\r\n];\r\n```\r\n\r\nIf hitting the /users/3 URI, the request will be dispatched to the UserController instead, and would follow the same rules.\r\n\r\n##### Nested routes\r\n\r\nNested routes would work as you would expect them to work:\r\n\r\n```php\r\nreturn [\r\n\t'routes' => [\r\n\t\t'users' => ['/users', UserListController::class, 'children' => [\r\n\t\t\t'user' => ['/:user_id', UserController::class, 'children' => [\r\n\t\t\t\t'tweets' => ['/tweets', UserTweetListController::class, 'children' => [\r\n\t\t\t\t\t'tweet' => ['/:tweet_id', UserTweetController::class]\r\n\t\t\t\t]]\r\n\t\t\t]]\r\n\t\t]],\r\n\t\t\r\n\t\t'tweets' => ['/tweets', TweetListController::class]\r\n\t]\r\n];\r\n```\r\n\r\nContrary to ZF2 AbstractRestfulController, ZfrRest AbstractRestfulController will allows you to pass different named parameters (instead of forcing you to name all identifiers \"id\").\r\n\r\nDash compact syntax (assuming that some changes are made on the default order - ping @Dasprid) allows to make this very usable and explicit. Contrary to ZfrRest, you can very easily map a different controller depending on wether you access a resource directly, or through a nested URI (here, you can see that tweets is mapped either to UserTweetListController or TweetListController).\r\n\r\n#### Injection\r\n\r\nFor simplicity, ZfrRest will not try to automatically inject repository or service based on a resource name. As all controllers are fetched from service manager, you are responsible to inject the right service.\r\n\r\n#### Controller verbs\r\n\r\nIn ZfrRest, you used to have code like this:\r\n\r\n```php\r\npublic function get(User $user)\r\n{\r\n   // Do what you want\r\n}\r\n```\r\n\r\nIn ZfrRest NEXT, it will mostly behave like a normal action controller:\r\n\r\n```php\r\npublic function get()\r\n{\r\n\t$userId = $this->params()->fromRoute('user_id');\r\n\t\r\n\t// Fetch from your service\r\n}\r\n```\r\n\r\n#### RPC like action\r\n\r\nSometimes, it is actually useful to be able to define actions, even in a REST context, as not everything map to REST properly. For instance, a \"start import\" cannot be expressed as a HTTP verb that easily.\r\n\r\nIn ZfrRest this was frustrating, because you were forced to create a different controller, with a different factory. This leaded to a lot of duplication code.\r\n\r\nI suggest that ZfrRest controller actually supports both things by extending the default AbstractActionController. For instance:\r\n\r\n```php\r\nreturn [\r\n\t'routes' => [\r\n\t\t'projects' => ['/projects', ProjectListController::class, 'children' => [\r\n\t\t\t'project' => ['/:project_id', ProjectController::class, 'children' => [\r\n\t\t\t\t'start-import' => ['/start_import', null, 'startImport']\r\n\t\t\t]]\r\n\t\t]]\r\n\t]\r\n];\r\n```\r\n\r\n#### Data validation/filtering\r\n\r\nIn ZfrRest, the validation and filtering was done automagically:\r\n\r\n1. You would set an input filter and hydrator in the mapping.\r\n2. If POST/PUT, ZfrRest would automatically extract the data using the bound hydrator, validate it and hydrate it.\r\n3. Resulting entity would be pass to the controller.\r\n\r\nOnce again, this works quite well for simple cases, but falls up completely for more complexe use cases. I'd suggest that we'd go back to explicitness again, with some shortcuts for simplicity.\r\n\r\n##### Validation/filtering\r\n\r\nBefore refactoring ZfrRest, I'll probably refactor input filters. What I'd like to add is a better validation groups, and more importantly \"named\" validation group. Each named validation group will actually contain a list of fields to validate, plus additional context for dynamic based validation/filtering. ZfrRest controller would offer a shortcut to avoid manually fetching the input filter manager, creating it, setting it...\r\n\r\n```php\r\npublic function post()\r\n{\r\n    $values = $this->validateIncomingData(TweetInputFilter::class, 'create', ['user' => $this->identity()]);\r\n}\r\n```\r\n\r\nInternally, the `validateIncomingData` will be responsible to:\r\n\r\n1. Fetching the data from POST, and converting it into a PHP array (it will only support JSON for now but may support XML as well).\r\n2. Retrieving the input filter given from the input filter plugin manager.\r\n3. Set the named validation group (here, 'create').\r\n4. Validate the data using the given context (here, we pass the given user, that could be used to dynamically exclude/include some data based on the user's role, for instance).\r\n5. Throwing a UnprocessableEntity (422 error) if the input filter fails.\r\n6. Returning the filtered values if success.\r\n\r\n#### Data hydration\r\n\r\nOnce again, new ZfrRest will go explicit. Similarily, controllers would provide a shortcut for hydrating data:\r\n\r\n```php\r\npublic function post()\r\n{\r\n\t// ...\r\n\t\r\n\t$object = new MyObject();\r\n\t$object = $this->hydrateForClass(TweetHydrator::class, $object, $values, ['user' => $this->identity()]);\r\n}\r\n```\r\n\r\nOnce again, this is just a shortcut for:\r\n\r\n1. Getting the hydrator plugin manager.\r\n2. Fetching it.\r\n3. Calling the hydrate method, and handle any possible error that would occur.\r\n\r\nBoth `validateIncomingData` and `hydrateForClass` would no nothing magic, those are just shortcut to reduce boilerplate code.\r\n\r\n#### Typical GET request\r\n\r\n```php\r\npublic function get()\r\n{\r\n   $tweet = $this->tweetService->getById($this->params()->fromRoute('tweet_id'));\r\n   \r\n   // ... See section about view\r\n}\r\n```\r\n\r\n#### Typical DELETE request\r\n\r\n```php\r\npublic function delete()\r\n{\r\n   $tweet = $this->tweetService->getById($this->params()->fromRoute('tweet_id'));\r\n   $this->tweetService->delete($tweet);\r\n   \r\n   // ... See section about view\r\n}\r\n```\r\n\r\n#### Typical POST request\r\n\r\n```php\r\npublic function post()\r\n{\r\n   $values = $this->validateIncomingData(TweetInputFilter::class, $values /*, $context */);\r\n   \r\n   $tweet = new Tweet();\r\n   $tweet = $this->hydrateForClass(TweetHydrator::class, $tweet, $values /*, $context */);\r\n   \r\n   $tweet = $this->tweetService->create($tweet);\r\n   \r\n   // ... See section about view\r\n}\r\n```\r\n\r\n#### Typical PUT request\r\n\r\n```php\r\npublic function put()\r\n{\r\n   $values = $this->validateIncomingData(TweetInputFilter::class, $values /*, $context */);\r\n   \r\n   $tweet = $this->tweetService->getById($this->params()->fromRoute('tweet_id'));\r\n   $tweet = $this->hydrateForClass(TweetHydrator::class, $tweet, $values /*, $context */);\r\n   \r\n   $tweet = $this->tweetService->update($tweet);\r\n   \r\n   // ... See section about view\r\n}\r\n```\r\n\r\n\r\nAll of this is more verbose than current ZfrRest, but at least we keep control about what happen (I suspect that as we remove all the magic, performance will be drastically better too). This also solves cleanly different validation rules based on method.\r\n\r\n### Resource rendering\r\n\r\nIn current ZfrRest, the rendering is automatically done through the the bound hydrator, and a specific rendering strategy.\r\n\r\n#### Advantages\r\n\r\n* Once again, for simple use cases, it does everything automatically, and this works pretty well... but advantages barely stop here.\r\n\r\n#### Drawbacks\r\n\r\n* It is complex to provide custom rendering: because everything is automatic, you are force to create your own rendering strategy, and copy-pasting a large amount of code, just for customizing a simple thing.\r\n* Can lead to horrible performance if not taken care of: because the current renderer is automatic, what it does is basically iterating through all the fields, outputing them, then iterating through all the relations and outputing them. However, for large associations, this can perform SQL queries you may not expect, just because it wants to render all the fields. You are therefore force to go into the entity, add a specific mapping on the association just to say ZfrRest NOT to output this association. Annoying.\r\n* It may output unwanted fields: similarily, remember that ZfrRest forces you to introduce bi-directional associations if you want to take advantage of routing... but as a consequence, all those assocations introduced purely for routing, will also be outputed in JSON result.\r\n* Slow as hell: check the code of the renderer, you will understand. It's a complete mess of Doctrine's ClassMetadata calls.\r\n* Hard to implement versioning: because everything is handled automatically, versioning is nearly impossible.\r\n\r\n#### Proposal\r\n\r\nI actually suggest a new concept called a ResourceModel. This is actually a hybrid thing between JsonModel and ViewModel, and will look like a .phtml template, instead that it will output your model. At its simplest, you will return a ResourceModel like you would return a ViewModel:\r\n\r\n```php\r\npublic function get()\r\n{\r\n\t$tweet = $this->tweetService->getById($this->params()->fromQuery('tweet_id'));\r\n\t\r\n\treturn new ResourceModel(['tweet' => $tweet, 'context1' => 'value1', ...]);\r\n}\r\n```\r\n\r\nSimilarily as a view model, it will actually look for a template (I suppose in the \"views\" folder, but we may decide something else). Its name will be infered from the route hierarchy and method.\r\n\r\nAssuming the following route config:\r\n\r\n```php\r\nreturn [\r\n\t'routes' => [\r\n\t\t'tweets' => ['/tweets', TweetListController::class, 'children' => [\r\n\t\t\t'tweet' => ['/:tweet_id', TweetController::class]\r\n\t\t]]\r\n\t]\r\n];\r\n```\r\n\r\nIt will look for a template called \"get.php\":\r\n\r\n\t-- views\r\n\t\t-- tweets\r\n\t\t\t-- tweet\r\n\t\t\t\t-- get.php\r\n\t\t\t\t\r\nThis file will be very simple, and you will need to return a simple PHP array (yeah, 100% control!). Here is an example of such a file (assuming a \"tweet\" context and \"user\" that is the logged user):\r\n\r\n```php\r\n$data = [\r\n\t'id' => $tweet->getId(),\r\n\t'content' => $tweet->getContent(),\r\n\t'writer' => $tweet->getOwner()->getId()\r\n];\r\n\r\nforeach ($tweet->getLatestRetweets() as $retweet) {\r\n\t$data['retweets'][] = [\r\n\t\t'id' => $retweet->getId(),\r\n\t\t'retweeted_by' => $retweet->getUser()->getId()\r\n\t];\r\n}\r\n\r\nif ($user->isAdmin()) {\r\n\t$data['internal_id'] = $tweet->getInternalId();\r\n}\r\n\r\nreturn $data;\r\n```\r\n\r\nAs you can see, templates do not contain any complex logic. It's basically controlling how the data is rendered. Because of the context, it allows you to include or refused some fields depending on the context.\r\n\r\n#### Post-processors\r\n\r\nOften, responses include a lot of common patterns. For instance, some API always wrap the response under a \"data\" key. When paginating, you always want to include a \"total_count\", \"offset\" and \"limit\" properties... and so-on.\r\n\r\nObviously, manually writing those would be cumbersome. You can therefore register post-processors (and create your own), inside the ZfrRest config:\r\n\r\n```php\r\nreturn [\r\n\t'zfr_rest' => [\r\n\t\t'post_processors' => [\r\n\t\t\tResponseWrapperPostProcessor::class,\r\n\t\t\tPaginationPostProcessor::class,\r\n\t\t\tUnderscorePostProcessor::class\r\n\t\t]\r\n\t]\r\n];\r\n```\r\n\r\nBecause order is important, you will be able to set \"priority\". Internally, I suspect that those post_processors will be implemented as listeners inside the Rendering Strategy.\r\n\r\nAt its simplest, the PostProcessor interface looks like this:\r\n\r\n```php\r\ninterface PostProcessor\r\n{\r\n\tpublic function postProcess(array $data);\r\n\t\r\n\tpublic function isEnabled();\r\n}\r\n```\r\n\r\nEach post-processor can be enabled/disabled directly in the view, and configured.\r\n\r\nFor instance, if we want to always wrap data around a \"data\" key, we could write our post-processor:\r\n\r\n```php\r\nclass DataWrapperPostProcessor implements PostProcessor\r\n{\r\n\tpublic function postProcess(array $data)\r\n\t{\r\n\t\treturn ['data' => $data];\r\n\t}\r\n\t\r\n\tpublic function isEnabled()\r\n\t{\r\n\t\treturn true; // always enabled\r\n\t}\r\n}\r\n```\r\n\r\nFor instance, let's assume that we are using a \"GET\" and that we want to wrap the payload around a common \"data\" key:\r\n\r\n```php\r\n$data = [\r\n\t'id' => $tweet->getId()\r\n\t// ...\r\n];\r\n\r\nreturn $data;\r\n```\r\n\r\nBecause this is always registered, it will automatically wrap the result.\r\n\r\nLet's take another example with a Paginator post-processor, that we need to enable whenever we have a collection resource:\r\n\r\n```php\r\n// Prepare your data\r\n\r\n$this->enablePostProcessor('name')->setPaginator($paginator);\r\n\r\nreturn $data;\r\n```\r\n\r\nBoom, the post-processor will execute.\r\n\r\n#### Versioning\r\n\r\nZfrRest will be able to support simple versioning by dynamically changing the template. There will be two ways to configure it: either using a special parameter in the ResourceModel, or using headers. For instance, using headers, you will configure a specific Accept-Content to a version:\r\n\r\n```php\r\nreturn [\r\n\t'zfr_rest' => [\r\n\t\t'versions' => [\r\n\t\t\t'vnd2' => 'v2'\r\n\t\t]\r\n\t]\r\n];\r\n```\r\n\r\nNow, instead of fetching the default view, it will check according to a based folder called \"v2\" (or whatever you prefer). If I take my previous example, it will look for this file:\r\n\r\n\t-- views\r\n\t\t-- v2\r\n\t\t\t-- tweets\r\n\t\t\t\t-- tweet\r\n\t\t\t\t\t-- get.php\r\n\t\t-- tweets\r\n\t\t\t-- tweet\r\n\t\t\t\t-- get.php\r\n\t\t\t\t\r\nIf ZfrRest does not find the \"v2\", it will fallback to the default template. Alternatively, you can force a specific version in the controller by using a second parameter:\r\n\r\n```php\r\nreturn new ResourceModel($context, 'v2');\r\n```\r\n\r\nFor instance, let's take we have a \"cards\" resource that will output the credit card resource. The default template will be:\r\n\r\n```php\r\nreturn [\r\n\t'id' => $card->getId(),\r\n\t'last4' => $card->getLast4(),\r\n\t'country' => $card->getLast4(),\r\n\t'type' => $card->getType()\r\n];\r\n```\r\n\r\nNow, let's say that we release a v2, that output the \"type\" as \"brand\" instead. We therefore create our new template inside the v2 folder:\r\n\r\n```php\r\nreturn [\r\n\t'id' => $card->getId(),\r\n\t'last4' => $card->getLast4(),\r\n\t'country' => $card->getLast4(),\r\n\t'brand' => $card->getType()\r\n];\r\n```\r\n\r\nWhile this work, there is a lot of duplicate. Therefore, the renderer will support two helpers methods: `renderDefaultVersion' and `renderVersion`. Previous example could be re-written as:\r\n\r\n```php\r\n$data = $this->renderDefaultVersion();\r\n$data['brand'] = $data['type'];\r\nunset($data['type']);\r\n\r\nreturn $data;\r\n```\r\n\r\nIf we release a v3 that depends on v2:\r\n\r\n```php\r\n$data = $this->renderVersion('v2');\r\n// Do changes\r\n\r\nreturn $data;\r\n```\r\n\r\n#### Conversion to JSON\r\n\r\nAt the very end of the process (once all the post-processor have been run), ZfrRest will convert the output to JSON, and return the response to the client.\r\n\r\n### End rendering\r\n\r\nZfrRest takes care of properly setting the response status code for GET, POST, PUT and DELETE methods. It is currently doing it in the MethodHandler.\r\n\r\nHowever, I intend to do this in a listener, that will likely hook to the `finish` event. Based on the method, and the result, it will properly set the headers.\r\n\r\n## Thoughts?\r\n\r\nping @ocramius @danizord @mac_nibblet @thinkscape @blanchonvincent"},"comment":{"url":"https://api.github.com/repos/zf-fr/zfr-rest/issues/comments/70536127","html_url":"https://github.com/zf-fr/zfr-rest/issues/183#issuecomment-70536127","issue_url":"https://api.github.com/repos/zf-fr/zfr-rest/issues/183","id":70536127,"user":{"login":"bakura10","id":1198915,"avatar_url":"https://avatars.githubusercontent.com/u/1198915?v=3","gravatar_id":"","url":"https://api.github.com/users/bakura10","html_url":"https://github.com/bakura10","followers_url":"https://api.github.com/users/bakura10/followers","following_url":"https://api.github.com/users/bakura10/following{/other_user}","gists_url":"https://api.github.com/users/bakura10/gists{/gist_id}","starred_url":"https://api.github.com/users/bakura10/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bakura10/subscriptions","organizations_url":"https://api.github.com/users/bakura10/orgs","repos_url":"https://api.github.com/users/bakura10/repos","events_url":"https://api.github.com/users/bakura10/events{/privacy}","received_events_url":"https://api.github.com/users/bakura10/received_events","type":"User","site_admin":false},"created_at":"2015-01-19T18:06:07Z","updated_at":"2015-01-19T18:06:07Z","body":"Hi everyone,\r\n\r\nI've had a talk with @Orkin, and he found the view layer confusing. Instead, he suggests skipping the hydrator for rendering (which kinda makes sense as the view is already a \"rendering purpose\"). This means that in ZfrRest context, it makes \"extract\" method useless.\r\n\r\nHe therefore suggests something completely explicit. Here is an example of a view:\r\n\r\n```php\r\n$data  = [];\r\n\r\nforeach ($this->users as $user) {\r\n    $data[] = $this->renderTemplate('users.user', ['user' => $user]);\r\n}\r\n\r\nreturn $data;\r\n```\r\n\r\nOr a single user:\r\n\r\n```php\r\n$data = [\r\n   'first_name' => $this->user->getFirstName(),\r\n   'last_name' => $this->user->getLastName()\r\n];\r\n\r\n// A related resource\r\nif (isset($this->billing)) {\r\n   $data['billing'] = [\r\n       'card' => $this->billing->getCard()\r\n   ]\r\n}\r\n\r\n// Tweets of the user\r\n\r\nforeach ($this->user->getTweets() as $tweet) {\r\n    $data['tweets'][] = $this->renderTemplate('tweets.tweet', ['tweet' => $tweet]);\r\n}\r\n\r\nTo me it is essential to keep a \"renderTemplate\" that allows to render another named template, to avoid code duplication. What do you think?\r\n"}}