HTTP PUT - Update object

(Thanks to Dustin Chabrowski for writing this FAQ entry!)

The problem

TL;DR

  • HTTP PUT request for updateAction
  • payload (formatted as JSON) gets ignored
  • the object is created by the identifier in the URI, not by http payload
  • updateAction() takes the existing object for upgrade, therefore no changes happen

In a RESTful service, an already existing object should be updated. While all other HTTP methods and
their respective actions work out of the box when the Flow RestController is used, the
updateAction() seems to only create the object from the given URI (e.g. http://example.com/authors/123-12132-12321), but it does not create the new object from the request payload data.

Example

Controller:


/**
 * @param \Acme\Project\Domain\Model\Author $author
 * @throws \TYPO3\Flow\Persistence\Exception\IllegalObjectTypeException
 */
public function updateAction(Author $author) {
//$author is the existing object with old properties
    $this->authorRepository->update($author);
    $this->response->setStatus(200);
}

Routes.yaml


- name: 'Authors'
  uriPattern: 'authors<AuthorSubroutes>(.{@format})'
  defaults:
    @package: 'Acme.Project'
    @controller: Author
  subRoutes:
    AuthorSubroutes:
      package: 'Acme.Project'
      suffix: Author
      

Routes.Author.yaml


- name: 'Author Update URI'
  uriPattern: '/{author}'
  defaults:
    '@action': 'update'
  httpMethods:
    - PUT

The solution

Replace the uriPattern in the subroute for PUT with /{author.__identity}.
By doing so, the object gets created with the data from the http request payload.

Routes.Author.yaml


- name: 'Author Update URI'
  uriPattern: '/{author.__identity}'
  defaults:
    '@action': 'update'
  httpMethods:
    - PUT

Thanks for sharing! I think there’s an issue with the suggested approach:
Using {author.__identity} is a bit hacky IMO and it has the drawback that you won’t be able to use this route for outgoing links.
I think using Object Route Parts is a slightly better solution:

Routes.yaml:

- name: 'Authors'
  uriPattern: 'authors<AuthorSubroutes>(.{@format})'
  defaults:
    @package: 'Acme.Project'
    @controller: Author
  routeParts:
    'author':
      objectType: 'Acme\Project\Domain\Model\Author'
  subRoutes:
    AuthorSubroutes:
      package: 'Acme.Project'
      suffix: Author

Routes.Author.yaml:

- name: 'Details'
  uriPattern: '/{author}'
  defaults:
    '@action': 'show'
  httpMethods: [GET]

- name: 'Update'
  uriPattern: '/{author}'
  defaults:
    '@action': 'update'
  httpMethods: [PUT]

The corresponding Fluid Template could look sth like:

<f:form action="update" arguments="{author: author}" objectName="author" object="{author}">
	<f:form.hidden name="__method" value="PUT" />
	<f:form.textfield property="name" />
	<f:form.submit value="Update" />
</f:form>

Note1: The “objectType” modifier configures the router to use the IdentityRoutePart which matches incoming UUIDs to ['__identity' => '<UUID>'] (and it won’t use a mapping table if no custom uriPattern is defined, see documentation)

Note2: Instead of creating sub routes for each resource type you could also create Routes.Rest.yaml and use placeholders.

2 Likes