Skip to content
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

fix(Symfony): canWrite guessing in WriteListener #6564

Open
wants to merge 2 commits into
base: 3.3
Choose a base branch
from

Conversation

Fabrn
Copy link

@Fabrn Fabrn commented Aug 30, 2024

Q A
Branch? 3.3
Tickets Closes #6501

As explained in the issue, in version 3.3 : a Get operation using a Controller is always calling the WriteProcessor after the Controller is called, which causes an "Invalid identifier or value configuration" if you are using another identifier.

Example :

new Get(
    output: SomethingOutput::class,
    provider: SomethingProvider::class,
)

A simple operation like this calls the WriteProcessor.

@Fabrn Fabrn changed the title feat(Symfony): canWrite guessing in WriteListener fix(Symfony): canWrite guessing in WriteListener Aug 30, 2024
@Fabrn Fabrn changed the base branch from main to 3.3 August 30, 2024 20:24
Comment on lines +93 to +102
$canWrite = $operation?->canWrite();

if (null === $canWrite) {
$canWrite = !$request->isMethodSafe();
}

if (!$canWrite) {
return;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$canWrite = $operation?->canWrite();
if (null === $canWrite) {
$canWrite = !$request->isMethodSafe();
}
if (!$canWrite) {
return;
}
if (false == ($operation?->canWrite() ?? !$request->isMethodSafe())) {
return;
}

But shouldn't you put this line 146?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit of code seems fine to me 🙂 You could even use === instead of == here isn't it ? Since the expression between parenthesis will always return a boolean here.

But shouldn't you put this line 146?

I don't think so, since the issue is that we call the WriteProcessor about line 117 like so :

$data = $this->processor->process($controllerResult, $operation, $uriVariables, [
    'request' => $request,
    'uri_variables' => $uriVariables,
    'resource_class' => $operation->getClass(),
    'previous_data' => false === $operation->canRead() ? null : $request->attributes->get('previous_data'),
]);

We must leave the WriteListener before this call

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already do this correctly line 104:

            if (null === $operation->canWrite()) {
                $operation = $operation->withWrite(!$request->isMethodSafe());
            }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Holidays and a month went by, I had to setup a little project to remember what was going wrong here 😄

Initially, I wrote this issue since I get an "Invalid identifier value or configuration." error while running the following operation :

new Get(
    uriTemplate: '/users/by_identifier/{identifier}',
    controller: GetUserByIdentifier::class,
    read: false,
),

I then made my Controller which resolves the User like so :

#[AsController]
class GetUserByIdentifier
{
    public function __construct(
        private readonly UserRepository $userRepository,
    ) {}

    public function __invoke(string $identifier): User
    {
        return $this->userRepository->findOneBy(['identifiant' => $identifier]);
    }
}

The point of using read: false here is to prevent resource reading since we aren't using the Doctrine identifier here, and let the Controller do the job. However, after line 104, you get the following :

$uriVariables = $request->attributes->get('_api_uri_variables') ?? [];

if (!$uriVariables && !$operation instanceof Error && $operation instanceof HttpOperation) {
    try {
        $uriVariables = $this->getOperationUriVariables($operation, $request->attributes->all(), $operation->getClass());
    } catch (InvalidIdentifierException|InvalidUriVariableException $e) {
        throw new NotFoundHttpException('Invalid identifier value or configuration.', $e);
    }
}

This bit of code results in the "Invalid identifier value or configuration." error. If I add the bit of code discussed here, I dodge the URI variables resolving, and the call of the write processor, which allows me to have this working.

The point here is to not call the ReadListener with read: false because we would have got the issue and it would have been expected. But calling the WriteListener is not expected here and should not prevent my read operation from working.

Also, I personally think that we better dodge any more logic if possible. Resolving URI variables and calling the Processor for an operation that should not be handled for this Listener is too much

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why you'd use controllers with API Platform ^^ use uriVariables: ['identifier']

Where is the getOperationUriVariables code you pasted here? you can also juste use write: false.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why you'd use controllers with API Platform ^^ use uriVariables: ['identifier']

I confirm that my simple use case works fine using uriVariables: ['identifier']. I had a lack of knowledge about this.

However, since Api Platform does not prevent you from using controllers, they are a part of the bundle and the bundle must adapt and work with it. Since it works fine on 3.2 and always did, I don't know why we should stop supporting it. People with more complex behaviors may rely on controllers to work for their use case.

Also, if you guys don't want us to use controllers to work anymore, I think it's better to do it on purpose instead of explicitly letting a bug exist and leading to incorrect and confusing behavior.

Where is the getOperationUriVariables code you pasted here?

Version 3.3, WriteListener :

image

you can also juste use write: false

I don't agree with the fact that I must use write: false in a read operation. It is absurd

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

WriteListener is called on Get operations
2 participants