-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[FrameworkBundle] Add new TemplateResponse class #21765
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
2b945a7
to
ec36c71
Compare
Just something else I had in mind when it comes to this, more of a builder pattern: <?php
interface ResponseBuilderInterface
{
public function build(EngineInterface $engine);
}
class TemplatedResponseBuilder implements ResponseBuilderInterface
{
private $template;
private $parameters;
private $statusCode;
private $headers;
public function __construct($template, array $parameters = array(), $statusCode = Response::HTTP_OK, array $headers = array())
{
$this->template = $template;
$this->parameters = $parameters;
$this->statusCode = $statusCode;
$this->headers = $headers;
}
public function build(EngineInterface $engine)
{
// or use PSR if you like
return new Response($engine->render($this->template), ...);
}
} Advantage with this setup is that you have 100% control over the view and you can create nice response builders: class InvoiceResponseBuilder implements ResponseBuilderInterface
{
private $invoices;
public function __construct(array $invoices)
{
$this->invoices= $invoices;
}
public function build(EngineInterface $engine)
{
$total = count($this->invoices);
if (0 === $total) {
return new Response($engine->render('/invoice_404.html.twig'));
}
if (5 > $total) {
return new Response($engine->render('/invoice_few.html.twig'));
}
return new Response($engine->render('/invoice_many.html.twig'));
}
} names to be discussed |
I think it should be a It can follow the same concept as you mentioned, but instead of a builder, just a |
315b627
to
10421f5
Compare
private $template; | ||
private $parameters; | ||
private $statusCode; | ||
private $headers; |
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'm not sure if this properties needs getters and/or setters?
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 don't think they do, they are not part of the public API
37bb6fb
to
3b788f2
Compare
/** | ||
* @author Pierre du Plessis <pdples@gmail.com> | ||
*/ | ||
class TemplateResponse implements TemplateResponseInterface |
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.
What about a
TemplatedResponse
instead of TemplateResponse
TemplatedResponseInterface
instead of TemplateResponseInterface
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.
Thinking about this again, I don't think the interface should be related to a template response at all. The interface just defines a way to return a Response. The Template(d)Response
class is just an implementation rendering a template, but you can just as well return a JsonResponse
, which means it's no longer rendering a template. So the name should probably be something more generic, although I'm not sure if just ResponseInterface
will do?
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 could simply return a new JsonResponse in that case like it's done now. It doesn't really make sense to me to first create an object to tell a listener to then create the Response for something you could've done in your controller already, this would trigger the view listeners which is just overhead.
private $template; | ||
private $parameters; | ||
private $statusCode; | ||
private $headers; |
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 don't think they do, they are not part of the public API
private $statusCode; | ||
private $headers; | ||
|
||
public function __construct($template, array $parameters = array(), $statusCode = Response::HTTP_OK, array $headers = array()) |
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.
What about cookies and cache information for example?
http://symfony.com/doc/current/components/http_foundation.html#setting-cookies
Internally cookies are added to the ResponseHeaderBag
:
public function setCookie(Cookie $cookie)
{
$this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
}
I think for this feature to be really viable, it should be possible to have caching and cookie control as well (hence I was aiming for a builder)
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.
It might make the parameter list too long. What about dropping the $statusCode
and $headers
parameters, and include the option to pass a Response object?
public function __construct($template, array $parameters = array(), Response $response = null)
That way we can keep it simple to only pass a template and parameters, but if you want more flexibility like setting cookies then you just create your own Response
I like this idea. However, I think this might be two steps to far for the moment. The
The advantage of this is that we can now define the return value of each action to always be Don't get me wrong though, I like the idea of your PR. This opens the door for other things like PDF responses which set the correct headers or switching templates based on the view data. I just think there needs to be done some prep-work before introducing this to the core. |
I don't think this needs to be removed. The annotation is still a useful feature for anyone that prefers it, but not everyone likes using annotations.
That did cross my mind as well, but I wasn't sure how the implementation would look like for it to make sense as a Response class, as I don't think the templating should be tied to the http-kernel or http-foundation components, hence I added it in the framework bundle which ties those components together. For me the |
@pierredup I've considered if extending the Response would be a good idea, but this would prevent the view event from being fired: // call controller
$response = call_user_func_array($controller, $arguments);
// view
if (!$response instanceof Response) {
$event = new GetResponseForControllerResultEvent($this, $request, $type, $response);
$this->dispatcher->dispatch(KernelEvents::VIEW, $event);
// ...
} |
If extending the Response, the event can then be tied into to |
3b788f2
to
268bd76
Compare
268bd76
to
f9e2e6e
Compare
Suggested changes made, ready for review |
I'm not sure about using |
{ | ||
private $templating; | ||
|
||
public function __construct(EngineInterface $templating) |
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.
having a hard dependency to the templating component is not fine IMO, as we worked very hard to make it possible to use Twig natively without wrapping it in the Templating component.
I'm not sure this should be in the core itself, especially given that it is strongly tied to the Templating component, that we are slowly deprecating. |
@stof perhaps this is something useful for the twig bundle instead? |
We are indeed moving away from the Templating component, so adding a feature requiring is a no-go. FrameworkExtraBundle will require Twig in its next major version, and |
I have one more edge case that I possibly would like to see supported; Redirects. class InvoiceResponse implements TemplateResponseInterface
{
// ..
public function build(EngineInterface $engine)
{
$total = count($this->invoices);
if (0 === $total) {
// ..
}
if (1 === $total) {
// how do I get my route here?
return ... new RedirectResponse?('app.invoice.one', [$this->invoices[0]]);
}
if (5 > $total) {
// ..
}
// ..
}
} My personal goal is to have no dependency on the |
…ly when twig is available
I'm not sure if the route and redirect should be solved in this PR, since the goal here is only to create an easy way to render templates without injecting twig into the controller. But for that issue, what about something like a custom |
@pierredup a lazy redirect, where you can just fill in the route + parameters is something I've wanted for quite some time, but was rejected in the past. Could be something explicit for this responder though |
What about trying to implement a broader feature that makes views a first-class citizen? We've had the view event since the beginning, but we never really provided anything to make it useful out of the box. |
What about the responder pattern? I'm not too familiar/experienced with it myself but it seems to solve certain challenges between the controller and the view. @fabpot, do you have something in mind or any ideas about the larger scope? |
@@ -181,6 +181,10 @@ public function load(array $configs, ContainerBuilder $container) | ||
$this->registerTemplatingConfiguration($config['templating'], $container, $loader); | ||
} | ||
|
||
if ($container->hasDefinition('twig')) { |
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 container will never have a Twig service here, as extensions can only see services they define themselves.
/** | ||
* @author Pierre du Plessis <pdples@gmail.com> | ||
*/ | ||
class TwigTemplateListener implements EventSubscriberInterface |
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.
A Twig listener should be in TwigBundle or in the Twig bridge (if it depends on FramworkBundle, it should be in TwigBundle).
FrameworkBundle is not allowed to depend on Twig.
* | ||
* @return Response | ||
*/ | ||
public function getResponse(\Twig_Environment $twig); |
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.
this interface depends on Twig, so it cannot be in FrameworkBundle
@fabpot I'd like to finish this PR, do you perhaps have any ideas about what a view as a first-class citizen would look like? |
Let's kick this to 4.1 and see if we can get something really nice and thoughtful done (since we're so close to feature freeze now, I think it would be rushed). |
Actually, I'm going to close this one. The proposed feature is not generic enough and should probably be experimented in a bundle first, before being integrated in code. |
@iltar ViewBundle. No tests for now. |
Add a new template reference class to resolve templates to a response (based on a comment from @iltar at symfony/symfony#21732).
This is an alternative approach to the
@Template
annotation, if you don't want to use annotations and you don't want to inject the templating service into your ControllerExample:
The
TemplateResponse
class implements theTemplateResponseInterface
interface, which allows you to customise the response.