8000 [HttpKernel] Extending the Request class · Issue #29059 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content
8000

[HttpKernel] Extending the Request class #29059

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

Closed
andreaslarssen opened this issue Nov 2, 2018 · 11 comments
Closed

[HttpKernel] Extending the Request class #29059

andreaslarssen opened this issue Nov 2, 2018 · 11 comments

Comments

@andreaslarssen
Copy link
andreaslarssen commented Nov 2, 2018

I'm on symfony/framework-bundle 4.1.6 which requires symfony/http-kernel ^4.1 (and symfony/http-foundation ^4.1).

I wanted to extend the Symfony\Component\HttpFoundation\Request class, and started googling it, and found very little on the subject. I started reading the docs, and on the http-kernel page, there's a gray info box on getting the controller arguments (...in the symfony framework). The second bullet in the box reads:

"2. If the argument in the controller is type-hinted with Symfony's Request object, the Request is passed in as the value. If you have a custom Request class, it will be injected as long as you extend the Symfony Request."

So I created a custom Request class, extending the HttpFoundation Request class:

namespace App\Requests;

use Symfony\Component\HttpFoundation\Request;

class MyCustomRequest extends Request {}

I injected the new custom Request into my controller:

namespace App\Controller;

use App\Requests\MyCustomRequest;

public function postSomething(MyCustomRequest $request) {

}

Then my app crashed, saying that:

Argument 1 passed to App\Controller\MyController::postSomething() must be an instance of App\Requests\MyCustomRequest, instance of Symfony\Component\HttpFoundation\Request given, called in /var/www/symfony/vendor/symfony/http-kernel/HttpKernel.php on line 149.

I debugged my way to the RequestValueResolver class:

namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver;

final class RequestValueResolver implements ArgumentValueResolverInterface
{
    /**
     * {@inheritdoc}
     */
    public function supports(Request $request, ArgumentMetadata $argument)
    {
        return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class);
    }

    /**
     * {@inheritdoc}
     */
    public function resolve(Request $request, ArgumentMetadata $argument)
    {
        yield $request;
    }
}

...and when the request hits the supports method, the argument type is MyCustomRequest class, the $request is the HttpFoundation\Request, and the is_subclass_of test returns true.

When the request hits the resolve method, the argument type is still MyCustomRequest, the $request is still the HttpFoundation\Request, but the method returns (yields) the HttpFoundation\Request, which ends up injecting the HttpFoundation\Request class, and not MyCustomRequest, into my controller.

For now, I've added a custom RequestValueResolver, and changed it to this:

class RequestValueResolver implements ArgumentValueResolverInterface {
  /**
   * {@inheritdoc}
   */
  public function supports(Request $request, ArgumentMetadata $argument) {
      return is_subclass_of($argument->getType(), Request::class);
   }

  /**
   * {@inheritdoc}
   */
  public function resolve(Request $request, ArgumentMetadata $argument) {
    $requestClass = $argument->getType();

    yield forward_static_call([$requestClass, 'createFromGlobals']);
  }
}

...which injects the correct Request class. Is it the right thing to do? No idea.

If I'm reading the docs or the code wrong, or if there's another way to do this, let me know. If not, this just might be a surprising bug as noone have reported it yet.

@ro0NL
Copy link
Contributor
ro0NL commented Nov 2, 2018

As you've discovered, the request is already determined at this point. I wouldnt change the request type here / ignore the current instance.. only to end up with 2 type of request instances.

Shouldnt you init the custom request type at the root of bootstrapping, thus in the front controller: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/4.2/public/index.php#L36

Then everything is OK no?

@andreaslarssen
Copy link
Author

I might have misunderstood this completely, and confused the request value resolver with the actual request on the request stack.

I think I'll use the paramconverter and turn the request content into a entity instead.

But just out of curiosity; If one would like to dynamically override the Request class, how would one do that? Use the event cycle to swap the request? Or should you just never override it?

Editing index.php (a framework specific file) don't seem like a good idea

@nicolas-grekas
Copy link
Member

Editing index.php (a framework specific file) don't seem like a good idea

Yes it is! This file is yours, it doesn't belong to the framework!

@andreaslarssen
Copy link
Author

@nicolas-grekas: Thinking of it, true.

So that's the idea then. You can override the Request globally and then make use of it in index.php, but there's no way of dynamically resolving and swapping a custom request class on the stack, cause that's a bad idea (?). And to validate a request (that's what I'm trying to do), use paramconverter to convert the request content to an entity?

@fabpot
Copy link
Member
fabpot commented Nov 2, 2018

Be careful that overriding Request is not something we want to support. If you want to do it, don't forget to call Request::setFactory as Symfony might create Request instances for some features.

@andreaslarssen
Copy link
Author

Thanks for the clarification.
I now use paramconverter to achieve what I wanted.

But if you're not supposed to extend the Request class, can I ask why the docs are saying:

"2. If the argument in the controller is type-hinted with Symfony's Request object, the Request is passed in as the value. If you have a custom Request class, it will be injected as long as you extend the Symfony Request."

And also why the RequestValueResolver checks for subclass of the Request?

@fabpot
Copy link
Member
fabpot commented Nov 2, 2018

The docs should be updated; "If you have a custom Request class, it will be injected as long as you extend the Symfony Request." should be removed. @andreaslarssen Can you submit a PR on symfony/symfony-docs?

RequestValueResolver checks for a subclass to keep BC.

But in any case, even if we don't recommend extending the Request, we don't want to make it impossible.

@andreaslarssen
Copy link
Author

Makes sense. I'll submit a PR. Thanks.

javiereguiluz pushed a commit to symfony/symfony-docs that referenced this issue Nov 5, 2018
Extending the Request class is not recommended:

symfony/symfony#29059
javiereguiluz added a commit to symfony/symfony-docs that referenced this issue Nov 5, 2018
…arssen)

This PR was submitted for the 4.1 branch but it was merged into the 3.4 branch instead (closes #10628).

Discussion
----------

RM: Sentence about extending the Request class

Extending the Request class is not recommended:

symfony/symfony#29059

Commits
-------

a23feff RM: Sentence about extending the Request class
@HongKilDong
Copy link

But in any case, even if we don't recommend extending the Request, we don't want to make it impossible.

Nevertheless it was done in Symfony 5.4, where \Symfony\Component\HttpFoundation\Request is hardcoded https://github.com/symfony/runtime/blob/f7a8403ae0e6847e56881c3c106e4ea2ec4ef8c9/SymfonyRuntime.php#L123

@nicolas-grekas
Copy link
Member

Symfony runtime is designed for extensibility. It is possible to override this logic.

@HongKilDong
Copy link

Symfony runtime is designed for extensibility. It is possible to override this logic.

Ok, I got it, I just need to create own Runtime which overrides SymfonyRuntime or GenericRuntime

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

No branches or pull requests

5 participants
0