feat: spec-compliant PUT method#4996
Conversation
| // This custom logic is needed because EntityManager::merge() has been deprecated and UPSERT isn't supported: | ||
| // https://github.com/doctrine/orm/issues/8461#issuecomment-1250233555 | ||
| if (isset($context['read_data'], $context['resource_class']) && $operation instanceof HttpOperation && 'PATCH' !== $operation->getMethod()) { | ||
| $propertyNames = iterator_to_array($this->propertyNameCollectionFactory->create($context['resource_class'])->getIterator()); |
There was a problem hiding this comment.
we can avoid all this, you can retrieve identifiers by looping over foreach $operation->getUriVariables() as $uriVariable then foreach $uriVariable->getIdentifiers() as $property this way we don't use metadata factories here.
There was a problem hiding this comment.
My refactoring doesn't handle the case where URI variables don't match the Doctrine properties, but I think that it's good enough for now, mainly because it's in sync with what is currently done here: https://github.com/api-platform/core/blob/main/src/Doctrine/Orm/State/ItemProvider.php#L51
At some point, we'll probably want to introduce a utility to map URI variables with internal Doctrine identifiers.
de18ece to
ce98746
Compare
|
|
||
| public function __construct(private readonly ProviderInterface $provider, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, private readonly ?SerializerContextBuilderInterface $serializerContextBuilder = null, UriVariablesConverterInterface $uriVariablesConverter = null) | ||
| { | ||
| public function __construct( |
There was a problem hiding this comment.
I thought we didn't want to have multiple lines in the constructor?
There was a problem hiding this comment.
Now that we have constructor promotion and the ability to add a comma after the last parameter, I think we should use multiple lines for clarity and to reduce merge conflicts.
There was a problem hiding this comment.
Good to know, it would be great to add it in the CS fixer.
| if ( | ||
| null === $data && | ||
| ( | ||
| HttpOperation::METHOD_PUT !== $operation->getMethod() || |
There was a problem hiding this comment.
Why using a constant here and not elsewhere?
| null !== $data && | ||
| ( | ||
| 'PATCH' === $method || | ||
| ('PUT' === $method && !($operation->getExtraProperties()['standard_put'] ?? false)) |
There was a problem hiding this comment.
use HttpOperation constants for methods
| * @author Kévin Dunglas <[email protected]> | ||
| */ | ||
| #[ApiResource(extraProperties: [ | ||
| 'standard_put' => true, |
| // PUT: reset the existing object managed by Doctrine and merge data sent by the user in it | ||
| // This custom logic is needed because EntityManager::merge() has been deprecated and UPSERT isn't supported: | ||
| // https://github.com/doctrine/orm/issues/8461#issuecomment-1250233555 | ||
| if ($operation instanceof HttpOperation && 'PUT' === $operation->getMethod()) { |
There was a problem hiding this comment.
| if ($operation instanceof HttpOperation && 'PUT' === $operation->getMethod()) { | |
| if ($operation instanceof HttpOperation && HttpOperation::METHOD_PUT === $operation->getMethod()) { |
6705312 to
83b200b
Compare
| $processor = null, | ||
| array $extraProperties = [] | ||
| array $extraProperties = [], | ||
| private ?bool $allowCreate = null, |
There was a problem hiding this comment.
IMHO the order of these parameters shouldn't be part of the BC promise and users must have to use named properties.
There was a problem hiding this comment.
note that this allows to use ...func_get_args() to call parent::__construct without having to be concerned about allowCreate.
d26e501 to
2256c61
Compare
Our current implementation of the
PUTHTTP method is not standard-compliant: according to the spec, aPUTmust create or replace the resource at the requested URL.However, currently, we don't allow the creation of new resources using
PUT, and we update the existing resources instead of replacing them: untouched properties are kept instead of being reset to their default values. Basically, ourPUTbehaves asPATCH.The main reason behind that non-conform behavior is a Doctrine ORM limitation.
This patch adds support for spec-compliant
PUTand provides a workaround for Doctrine ORM.Making
PUTspec-compliant is opt-in to preserve backward compatibility, but using the non-conform version will be deprecated (usePATCHinstead), and removed in API Platform 4.Allowing resource creation is not enabled by default because it can cause security issues.
TODO:
extra_propertiesstandard_puttotruePUTby default (usingPATCHis probably a better solution for most common use cases, so we should makePUTopt-in)