-
-
Notifications
You must be signed in to change notification settings - Fork 922
Fix allow creating resources with custom identifers #2022
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
throw new InvalidArgumentException('Update is not allowed for this operation.'); | ||
} | ||
|
||
// TODO: use $this->iriConverter->getIriFromPlainIdentifier() once https://github.com/api-platform/core/pull/1837 is merged. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The #1837 PR is based on master.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, but the todo is still valid, it can be updated in master.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was a note for myself :D
), | ||
$context + ['fetch_data' => true] | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we're using twice the same logic here, can't we avoid to repeat these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i noticed yes... it would be nice,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@soyuka if you have some ideas, please say. I can't see a very nice way atm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you shed some light on why this is needed? Looks like a fallback algorithm that should go into the iriconverter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a comment a few lines up. it is a fix for #857
Not sure I agree with the fix, but I have kept it as per the original as a behat feature tests it.
Nice one! Want me to close the other PR then, right? |
@Toflar I'm happy to close this and you can cherry pick into the original PR if you like. this PR is rebased on 2.2 btw. |
Nah, I'm perfectly fine with you taking over! Thanks for the work! |
@dunglas @teohhanhui @meyerbaptiste can i ask for a review please? |
features/main/custom_normalized.feature:52 failed though |
Hmm. I'll check it out.
…On Sun, 17 Jun 2018, 12:11 Antoine Bluchet, ***@***.***> wrote:
features/main/custom_normalized.feature:52 failed though
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#2022 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAmK8JwfRY6olgiGteZCIwTXX4BzmX1Aks5t9jlJgaJpZM4UnCoq>
.
|
think I've fixed this by making the feature correct, to correctly address #857. |
if (isset($context['api_allow_update']) && true !== $context['api_allow_update']) { | ||
throw new InvalidArgumentException('Update is not allowed for this operation.'); | ||
try { | ||
parent::setObjectToPopulate($data, $class, $context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a return;
here and move the code in the catch
part outside?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mean an empty catch right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
// https://github.com/api-platform/core/issues/857 | ||
$identifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($class); | ||
$identifiersData = \array_intersect_key($data, array_flip($identifiers)); | ||
if (0 === \count($identifiersData)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!$identifiersData) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this a style adopted by API-P? i prefer to be explicit but happy to align with project style.
$identifiers = $this->getIdentifiersForDenormalization($class); | ||
$identifiersData = \array_intersect_key($data, array_flip($identifiers)); | ||
|
||
if (0 === \count($identifiersData)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!$identifiersData)) {
protected function getIdentifiersForDenormalization(string $class): array | ||
{ | ||
// Backwards compatibility | ||
if (null === $this->identifiersExtractor) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!$this->identifiersExtractor) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again, happy to follow the style of the project, but if (null === $variable)
is done quite a lot in this project, so i'm not sure there's any point unless consistency is introduced.
} | ||
|
||
if (!$updateAllowed) { | ||
throw new InvalidArgumentException('Update is not allowed for this operation.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not this exception be thrown before the previous condition (the return;
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't think so, we only throw if identifiers are provided.
When I add "Accept" header equal to "application/json" | ||
And I send a "PUT" request to "/related_normalized_dummies/1" with body: | ||
""" | ||
{ | ||
"name": "My Dummy", | ||
"customNormalizedDummy":[{ | ||
"@context": "/contexts/CustomNormalizedDummy", | ||
"@id": "/custom_normalized_dummies/1", | ||
"@id": "app_dev.php/custom_normalized_dummies/1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why app_dev.php
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For completeness, as I said to @dunglas privately, I won't have a chance to work on this proposal for quite a while. So if anyone else has some time, please feel free :) |
I'm -1 for using |
Anyway we need a special key for the IRI. We may also use the GraphQL convention (
So to me |
My stand is consistent with what I've said about the Problem RFC. We don't get any benefit by mimicking a standard. It only leads to confusion as to whether we're actually following the standard or not (we aren't, so it shouldn't look like we are). |
Hello, what is the status of this particular PR and the issue itself? From time to time people keep creating bugs in different repositories about this problem, and the freshest PR is this one which is also not active anymore.
Questions:
This bug is quite old and very annoying, so I would like to point your attention to it and try to move it forward. Thank you. |
Well, a couple of days with debugger and it works now.
What we did is just replaced POST /resources
{
"id": 1234545,
"anyOther": "fields"
} Custom ItemNormalizer (click to show)declare(strict_types=1);
namespace App\Serializer\Normalizer;
use ApiPlatform\Core\Exception\InvalidArgumentException;
use ApiPlatform\Core\Serializer\AbstractItemNormalizer;
class ItemNormalizer extends AbstractItemNormalizer
{
private const IDENTIFIER = 'id';
public function denormalize($data, $class, $format = null, array $context = [])
{
$context['api_denormalize'] = true;
if (!isset($context['resource_class'])) {
$context['resource_class'] = $class;
}
$this->setObjectToPopulate($data, $context);
return parent::denormalize($data, $class, $format, $context);
}
protected function setObjectToPopulate($data, array &$context): void
{
// in PUT request OBJECT_TO_POPULATE is already set by this moment
if (!\is_array($data) || isset($context[self::OBJECT_TO_POPULATE])) {
return;
}
[$identifierName, $identifierMetadata] = $this->getResourceIdentifierData($context);
$isUpdateAllowed = $context['api_allow_update'] ?? false;
$hasIdentifierInRequest = array_key_exists(self::IDENTIFIER, $data);
$hasWritableIdentifierInRequest = $hasIdentifierInRequest && $identifierMetadata->isWritable();
// when it is POST, update is not allowed for top level resource, but is allowed for nested resources
$isTopLevelResourceInPostRequest = !$isUpdateAllowed
&& $context['operation_type'] === 'collection'
&& $context['collection_operation_name'] === 'post';
// if Resource does not have an ID OR if it is writable custom id - we should not populate Entity from DB
if (!$hasIdentifierInRequest || ($hasWritableIdentifierInRequest && $isTopLevelResourceInPostRequest)) {
return;
}
if (!$isUpdateAllowed) {
throw new InvalidArgumentException('Update is not allowed for this operation.');
}
try {
$context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getItemFromIri(
(string) $data[self::IDENTIFIER],
$context + ['fetch_data' => true]
);
} catch (InvalidArgumentException $e) {
$context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getItemFromIri(
sprintf(
'%s/%s',
$this->iriConverter->getIriFromResourceClass($context['resource_class']),
$data[$identifierName]
),
$context + ['fetch_data' => true]
);
}
}
private function getResourceIdentifierData(array $context): array
{
$identifierPropertyName = null;
$identifierPropertyMetadata = null;
$className = $context['resource_class'];
$properties = $this->propertyNameCollectionFactory->create($className, $context);
foreach ($properties as $propertyName) {
$property = $this->propertyMetadataFactory->create($className, $propertyName);
if ($property->isIdentifier()) {
$identifierPropertyName = $propertyName;
$identifierPropertyMetadata = $property;
break;
}
}
if ($identifierPropertyMetadata === null) {
throw new \LogicException(
sprintf(
'Resource "%s" must have an identifier. Properties: %s.',
$className,
implode(',', iterator_to_array($properties->getIterator()))
)
);
}
return [$identifierPropertyName, $identifierPropertyMetadata];
}
} To replace api-platform's analyzer with this custom one - just override
+ use App\Serializer\Normalizer\ItemNormalizer;
- class Kernel extends BaseKernel
+ class Kernel extends BaseKernel implements CompilerPassInterface
+ public function process(ContainerBuilder $container)
+ {
+ $container->getDefinition('api_platform.serializer.normalizer.item')->setClass(ItemNormalizer::class);
+ }
} Still, I want to move forward this discussion. This is one of the feature that I expect to work out of the box from platform named And by the way, provided patch in this PR does not work, I literally copied code to UPD: the full code for api-platform 2.4.3+ is here api-platform/api-platform#343 (comment) |
Can you rebase against 2.3 please? |
@dunglas why? You have proposed a better solution above. This pr should not be merged, I thought we decided. |
My bad, I read this PR too fast. |
Let's close this then. |
let's try not forget about your proposal @dunglas :) |
I suggest opening another issue with that proposal, because it will be forgotten 100% ;) |
lol was doing too many things at once and forgot to submit the issue, done :) |
Hello everyone, My use case is that I have an application, consuming my API, that needs to work offline, and still create new resources instances locally, while offline. A good solution would be to have my resources uses UUID as identifiers, and allow to specify the identifiers in As this issues is closed, I'll post it on ##2201 too, because I'm not sure somebody is reading here :) |
See api-platform/api-platform#343 (comment) if you want make it work |
Hello everyone, Here is an example I made for ApiPlatform >= 3.0 that converts plain ids sent for relation field (either for item or collection relation) to IRI's before denormalization.
Which is registered such as follow in services.yaml :
|
This is a re-submission of #1939 with fixes and passing tests. @Toflar's commits are retained.