-
-
Notifications
You must be signed in to change notification settings - Fork 50
Added View layer #200
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
Added View layer #200
Conversation
First overview => nice! But can you make the DefaultView have an interface? |
This addresses the remaining issue from http://pooteeweet.org/blog/1850 |
@beberlei: Yeah, I think actually that many internals classes in Symfony2 should get interfaces (request, response, etc.) |
Of course I can do that (same as testing & docs, once we agree on the implementation). Also if you have ideas of some methods that should be renamed just say so. |
btw .. from the examples its not immediately obvious, but the redirect() is now split in two methods, one for internal and one for external redirects. furthermore there is handling for redirect*() for non HTML requests. |
I don't see how the split of the redirect() method is useful in this context? Am I missing something? If not, can we remove this change, so that the pull request is really just about the View? |
the separation is important since in order to cleanly handle redirects() in non HTML cases we implemented an automatic "forwarding" to the target route for internal routes. in the external case we default to just returning the url for now, though this is probably a broken use case. but due to the separation its easier to also add custom logic to handle the two cases in a sensible manner for the given use case of the developer. of course one could also use the route matching to try and find if there is an internal route for the given url (which i did in my MultiplexBundle), but that seems like a lot of overhead. another approach we discussed was using one method but supporting both 2 (url+code) and 3 (route+parameters+code) parameters in setRedirect(). we however decided that a clean separation was easier to work with. |
oh and btw .. redirect is totally a view "thing" .. IIRC even you agreed on that :) |
The splitting of the redirect both seems unnatural (i.e. why should my application care if I'm redirecting internally or externally?) and quite sensible (i.e. because the redirect is truly internal, and thus looks more like a forward). I'm barely negative on the idea of the internal vs external redirect, just because it seems like the type of thing that we've been keeping out of Symfony so far. Though not as performant, couldn't the full redirect url be passed back into Router::match() if you wanted to forward instead of redirect? Btw, I do like the idea of having the redirect in this new view in general - this seems like it'll be much more flexible. |
Like I said its possible without this separation: However this also means that with the current implemented default its no longer possible to prevent the magic follow we currently have, aka if you don't want the magic follow one would use setExternalRedirect(). Not sure how frequent that use case will be though, but I could see this be relevant for cases where you just want to fire the JSON request and you dont care about a "proper" reply ("proper" meaning something equivalent to the HTML case). If this is what is stopping things we can move it out. But honestly I don't see the harm at all and didn't expect this to be controversial. In 99% of the cases we redirect internally and from the API its immediately obvious that if you want to redirect else where that you will have to look for another method. So this API will will make the API easier to use, since you don't have to generateUrl(), which is particularly useful if you don't extend from the Controller base class. So to me this separation is full of win with zero draw backs. Lets admit it, this redirect() stuff in the HTML view is an ugly hack to a browser problem |
Basically, for redirects, the current implementation means faster JSON/XML responses (since we don't have to generate an url, then parse it back, then do the internal forward), and less code to write for HTML responses, since you just say which route you want to redirect to, no need to actually call the router->generate() method yourself. The only issue is if you want to redirect to an arbitrary URI, be it on the current site (typically if you use HTTP_REFERER) or just another site, then yes you have to use |
I'm still not sold on the redirect thing, but everything else looks like a good initial implementation. I think there is one missing thing though: the possibility to restrict the supported formats. The
I have changed the order of the first two parameters because the Last but not the least, I think we also need to optionally take into account the HTTP |
The proposed API changes seem sensible and at least the order of the two parameters we also discussed and just left as is to make "migration" easier (though that shouldnt yet really be a concern at this stage). With the format restriction we would then throw a 404 Exception if the format doesnt match? As for Accept header, I was expecting this to be handled in the layer that currently sets _format in the routing. Finally for the redirect method split, just to make it clear. You prefer having to write: Overwriting: Its obviously more compact for the internal redirect which is what is done much more often. The later means not having to inject a router dependencies into the controller (of course if you want to change the router you can do so with a custom view class). It also means less work to do in the non HTML case. It also semantically clearer as an internal redirect is actually a forward in all cases, except for in HTML, where you want it to be a real redirect because of how browsers work (its really a hack). The semantic differences is illustrated by the difference in how we handle the two cases in the non HTML case (aka forward vs. returning the target URL). |
@fabpot: I agree with the handle parameter swap, but I don't think you need to restrict types at this level. The route already has the option to do that with a
As for the redirect issue, I won't repeat what lukas said, but something that may solve the confusion is if we found another name for internal redirects. Forward is not really correct because it is not always an internal forward, redirect is not really right either because it is only a redirect for HTML (the hack of which lukas speaks btw is just the fact that we mostly redirect to avoid issues with page refreshes after form submits). If we had some other name for this thing, maybe it would help separate the concept between internal/optimized redirects/forwards and external ones that are really meant to redirect the user anywhere, which is quite a rare case I'd say. |
About the format restriction. The client can restrict the format it wants via the Accept header (several formats) or via the special _format route attribute (only one format). Each resource can then decide which resource to support via the _format requirement (only useful when you use _format in the route pattern), or via something else. And my proposal address this case. |
On a side note, the router is only responsible for the conversion of the path info into request attributes. The fact that we have a _route requirement is just a nice side effect. |
@lukas: my preference is for the first proposal: $this->view->setRedirect($this->router->generateUrl('doctrine_user_user_list')); It's up to you to construct the URL the way you want; you can use the Router or anything else. That should have no impact on the view class. |
ok, guess we will then have to remove it from this pull request, but to me its not even something that makes remotely sense to do. its simply an inferior approach anyway i try to look at it. i will surely bring it up later on in another pull request hoping that down the line it will become clear what a horrible mistake it would be to leave this separation out. :) |
so since without the differentiation of internal and external redirects the entire format handling falls apart, we will therefore drop setExternalRedirect(), but add logic to determine if its an internal or an external redirect in setRedirect() aka compare domain and then use the router match routine to find if there is a matching routine and if so assume its an internal redirect, which will lead to a forward in all but the HTML case. |
I'll submit a new version of this code tomorrow incorporating some of the changes discussed here, then we can maybe rediscuss. |
Since there is some discussion on IRC, here an overview of the behavior for the two redirect methods in the initial (aka current) proposal (again the method naming is debatable): Without this separation there would be no way to express what should happen in the format != 'html' case, furthermore if one wants to do a forward it would require comparing the domain in the url with the domain in the current request and asking the router to try and match the path to determine the route (and therefore controller) and parameters to forward to. I will write an RFC to the list that summarizes the discussion tomorrow to make this a topic at the IRC meeting. |
I was linked to this conversation via some lively #symfony-dev discussion. After looking over the code and comments above, I think there's a miscommunication over what "redirect" means. Fabien, if I understand correctly, you don't see any reason why one would Assuming we have a View class that is delegated redirect responsibility, an efficient solution to what Lukas/Seldaek are trying to do might be injecting their own View implementation via DIC with an overloaded the
This does add significant overhead, considering immediately before That would be the method for Lukas/Seldaek to overload and add their own logic. By default, Symfony2's standard View class would just proxy a call to The benefits of the extra method would be flexibility for Lukas/Seldaek's admittedly *special" use case, and convenience for common developers (esp. those coming from Symfony1). Downside is an extra method on the brand new View class and having to inject a Router into the View. |
btw, what is the fundamental (Browser) problem here that leads to this internal vs external redirect requirement? I see the whole discussion but nobody wrote about the technical requirement that forces us to do something about it. :-) |
I explained it shortly in #200 (comment) - Lukas went a bit overboard calling it a browser issue maybe, but the thing is, in most cases you do redirects after form submits to avoid that the user refreshes the page and re-submits the form. The other use cases like redirecting to the canonical for example are not browser-hacks but rather SEO hacks, but again only apply to HTML requests, you don't really want to do a real redirect in json, a forward is more than enough. |
This seems like a very philosophical issue, which usually (ideally) means that a group in some land (e.g. Java, Rails, etc) has beat it to death and come up with a best-practice. Does anything like that exist? Surely we can't be alone in struggling with this. I do think this view layer solves a very legitimate problem with respect to redirecting (though it's an issue we lived with in sf1) and formats, and I like it. In general, please just continue to be weary of the beginner and keep this layer present, but "out of their way". This pull request seems to address that nicely (redirect issue aside). Also, not to complicate things, but the internal redirect behavior is unique to HTML formatted non-XMLHttpRequest (an HTML formatted ajax request would also forward internally). |
Here is how I understand the current code: a nice abstraction for read-only web services; a way to automatically convert an entity/array/... to XML/JSON/HTML/... And I don't see the need for redirection for that. The next step will probably be the other side of web services: creation and update; you submit an XML/JSON/HTML form and serialize it to the native format of your application (probably a Doctrine entity). For this second step, I can see a need for redirection; but not in this first step. Am I missing something? Am I way off? |
We've beaten this to death on irc a few times I think. So I'll quickly refactor some things, update the pull request, then hopefully it's mergeable and we'll rediscuss the controversial stuff later on if it makes sense once we have some application code to show as an example, discussing this further on a theoretical level is just not possible. |
Updated the patch. If everyone is happy with the API and functionality and naming of it all now, I'll start writing tests and docs. Changes:
P.S.: Thanks for avalanche123 & jmikola & johanness & whoever else was around to help figuring out naming and reading the http specs on redirects etc etc. I hope we can all be happy now. |
+1 |
1 similar comment
+1 |
+1 |
+1, plus suggestion for CS fix in DefaultView.php: |
here is a little example using the latest code to inject both static as well as dynamic parameters into the templates when rendering html |
An article explaining that javascript cannot handle a 302 redirect and instead the browser will automatically redirect in case the redirect is to another domain: This highlights that for non HTML responses its probably not smart to return a 302 in many cases. |
Please don't start this again :) It was agreed (though, now that I think of it, you weren't there I think) that people redirecting XHRs outside of their domain sandbox were asking for it, and deserve the error. If the use case really mandates to do things that break the standard, you can still override the handleJson & handleXml calls to handle this in a custom way. |
i just put it here so it isnt forgotten. but its a real issue. for example facebook requires you redirect to their login page if the access token is out of date, which is of course a redirect to another domain, which means that the use case has nothing to do with being stupid. |
@jordi: just do we dont forget .. we should also add a supports($format) method |
great .. could you also make $request optional in the handle method? |
Since things have stalled with this pull, we created a Bundle: The idea is to make it easier for people to try out the view layer and to make it easier to provide feedback. We are still hoping that this view class will eventually make it into core. |
As discussed with Seldaek, the first step is to extract the serialization code to a new Serializer component. |
Just FYI: I have started a new branch of Liip\ViewBundle using the Serializer at https://github.com/liip/ViewBundle/tree/serializer |
Closing this PR as the new RestBundle replaces this PR. |
This is obviously up for discussion, once/if we reach an agreement I will document the methods & write tests.
Example usage is available in the FrameworkBundle\View\DefaultView class.
Lukas will send a mail to the ml soon to explain a bit what we did and have a discussion with everyone there.