Created
December 10, 2011 03:10
-
-
Save stof/27bccb88a7cea8e53495 to your computer and use it in GitHub Desktop.
"Drupal meets HttpKernel" discussion with @Crell... by night
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
01:06 < Crell> I've an architectural question if anyone has a moment. | |
01:07 < Stof> Crell: shoot | |
01:07 < Stof> couac: merged | |
01:07 < couac> thanks! | |
01:08 < Crell> The stuff I've seen on Symfony makes a big deal about how it doesn't do application caching, and just "lets HTTP do it". | |
01:08 < couac> Stof: oh and I'm ok with your conclusion (about the end of the proxy) | |
01:08 < Crell> Doesn't that mean that you cannot control when newly posted content is available? You cannot say "this object has changed, so next time it's requested regenerate the page and recache"? | |
01:09 < Crell> Since you have to wait for HTTP to expire its cache for a message to even get to you to find out that it's been updated. | |
01:09 -!- couac [[email protected]] has quit [Remote host closed the connection] | |
01:10 < Stof> Crell: lsmith wants to build such a tool using advanced features provided by Varnish to invalidate the cache selectively | |
01:10 < pounard> If I remember well, Plone does it | |
01:11 < Crell> pounard: Ohhai. :-) | |
01:11 < Stof> Crell: http://pooteeweet.org/blog/1979 | |
01:11 -!- damcgn [[email protected]] has joined #symfony-dev | |
01:11 < DrRotmos> Crell: there are three basic approaches to caching: expiration, validation and invalidation, the HTTP protocol has direct support for 1 and 2, for number 3 you need to do more stuff :) | |
01:12 < Crell> Stof: So the answer is "yeah that's a problem, lsmith wants to solve it"? :-) | |
01:12 < Stof> yeah | |
01:12 < DrRotmos> to be honest, invalidation is a pain in the ass | |
01:12 * Crell is thinking of Drupal in particular, where we have a page cache built in. | |
01:12 < Crell> And ripping it out is going to get me lynched unless we have a very, VERY good alternative available. | |
01:12 < pounard> DrRotmos> yes it is | |
01:13 < Stof> Crell: if you are caching the output to disk, you have the same issue to invalidate all pages using a given content | |
01:14 < Stof> so invalidation is also the issue in the case of output caching | |
01:14 < DrRotmos> of course | |
01:14 < Crell> Yes. | |
01:14 < Crell> I'm thinking of the "primary" page primarily. | |
01:14 < Crell> (Since the Drupal answer to that problem right now is horrificially bad.) | |
01:14 < Crell> catch is working on token-based selective cache invalidation. | |
01:14 < Crell> Which... I hope works. :-) | |
01:15 < pounard> cache tags | |
01:15 < Stof> well, then it's not really an issue either: send a PURGE request to varnish for the primary url | |
01:15 < pounard> really efficient solution, imho, but not every backend supports it well | |
01:15 < Crell> That assumes you have varnish. | |
01:15 < Stof> well, other http cache probably support a way to invalidate them | |
01:15 < pounard> s/well// | |
01:15 < Crell> Many sites don't, and authenticated users generally don't (in Drupal's current implementation). | |
01:15 < Crell> I mean that assumes you have a cache proxy at all. :-) That's a high-end feature, not something a run of the mill site has. | |
01:16 < Stof> ah, if you mean the browser cache, you can indeed not invalidate it | |
01:16 < Crell> Er, no. | |
01:16 * Crell backs up. | |
01:16 < Stof> Crell: Symfony2 implements a proxy cache in PHP in the HttpKernel component :) | |
01:16 < pounard> you can send a javascript alert("please click ctrl + reload"); via some ajax polling system | |
01:17 < Crell> Right now, Drupal always sends a cache invalidation timestamp in the past. It then has its own appplication-level page cache (backed by the DB, memcache, or whatever you want). | |
01:17 < Stof> not as efficient as Varnish of course (as it is hitting apache and PHP first) but usable on shared hostings :) | |
01:17 < Crell> That ensures that it has full control over the page update time. | |
01:17 < DrRotmos> Stof: also, it allows for ESI | |
01:17 < Stof> yep, but Varnish too | |
01:17 < DrRotmos> sure | |
01:18 < Stof> so when using Varnish, no need for HttpCache | |
01:18 < Crell> If you have Varnish, you disable that and Varnish takes over, at the expense of not being able to force a cache clear (currently), so you just set the lifetime to a few minutes. | |
01:18 < DrRotmos> (although varnish doesn't support stale-while-revalidate, which is a shame, but that's another thing :) ) | |
01:18 < pounard> You can still force cache clear with PURGE I guess | |
01:19 < pounard> that's what Plone does | |
01:19 < Crell> I am trying to figure out a better way to deal with this, assuming we end up using more Symfony that we do now. (LIkely, but not guaranteed.) | |
01:19 < Stof> Crell: btw, I think it is possible to implement an output cache in Symfony2 if you really need it, by using a response listener to cache and a request listener to set the response early when available | |
01:19 < Stof> totally untested idea though | |
01:19 * Crell does not full follow that statement. | |
01:19 < DrRotmos> Stof: but that's just unnecessary? | |
01:20 < pounard> Stof> that's almost what Drupal does | |
01:20 < Stof> DrRotmos: I said "if you really need it" | |
01:20 < DrRotmos> if you need an output cache, you can just use the HttpCache | |
01:20 < DrRotmos> and not use ESI | |
01:20 < Stof> an http cache would be better indeed | |
01:20 < Crell> An HTTP Cache internal to the application layer? | |
01:21 < Stof> Crell: the HttpCache is not inside the application layer but around it | |
01:21 -!- drak [[email protected]] has joined #symfony-dev | |
01:21 < Crell> But in PHP, I mean. | |
01:21 < Stof> it is a special kernel wrapping the standard one | |
01:21 * Crell still hasn't spent the necessary 20 hours digging through Symfony core to grok it. | |
01:21 < Stof> so if it has the cached response, he sends it directly | |
01:21 < Stof> otherwise, it boots the inner kernel and delegates the work to the app | |
01:22 < Stof> Crell: looking at Silex is enough to see the kernel into action | |
01:22 < Crell> Also on my list. | |
01:23 < Stof> Symfony2 builds lots on top of that (and uses the extended Kernel, not only the simple HttpKernel) so it is more complex | |
01:23 < Crell> I know HttpKernel is designed to be infinitely stackable. How far down does that go, though? | |
01:24 < Stof> look at the HttpKernel section in this blog post: http://fabien.potencier.org/article/49/what-is-symfony2 | |
01:25 < Crell> I think I've seen that a few times. :-) | |
01:25 < Crell> The use of Request rather than a RequestInterface could be problematic for me. | |
01:25 < Stof> well, you already use the Request, no ? | |
01:26 < Crell> Well... we have it in the repo. The code to actually leverage it hasn't merged yet. :-) | |
01:26 < Stof> do you need a different implementation (assuming we have an interface with the current implementation) ? | |
01:27 < Crell> But, our plan was that the request would only be one part of an extended "context" object, which included the request as well as other application-level things that depend on it, like the active user object, the "current" | |
node (if any), etc. | |
01:27 < DrRotmos> that's… contrary to the purpose of the request object :) | |
01:27 < Stof> hmm, btw, simply adding an interface is not possible as the interface cannot enforce the public properties used currently... | |
01:27 < Crell> DrRotmos: Please explain. | |
01:28 < Stof> Crell: the current node could be an attribute of the request | |
01:28 < Stof> just like the route is in Symfony | |
01:28 < Crell> The current node is *usually* the result of a node_load() call on the second argument of the path, if the first argument is "node". | |
01:29 < DrRotmos> Crell: the Request class represents an HTTP request, since HTTP is a request/response protocol, there is no overlying context to it | |
01:29 < Crell> However, we also want to have individual chunks of the page, blocks, be able to accept a node as current from anywhere else without knowing. Basically the current implementation of the context object is rather | |
Pimple-like. | |
01:30 < Crell> What I meant before by "how far does it go" is that we want to make each block on the page asynchronous for ESI, or our own caching, or whatever else. | |
01:30 -!- drak [[email protected]] has quit [Ping timeout: 240 seconds] | |
01:30 < Stof> Crell: the kernel is meant to be the entry point of the app (called in the front controller). Determining the current node would be done by some logic triggered inside handle() IMO, not before calling it | |
01:31 < Crell> Hence my question, since that implies that we then cannot also use it for those individual blocks. | |
01:31 < Crell> Although that's also part of the issue we're facing. | |
01:31 < Stof> Crell: if you are using ESI, you cannot pass objects to your block. The cache will only be able to trigger an url, not to pass them | |
01:31 < Crell> The appropriate controller, or controller/configuration, for a given request may depend on the node. | |
01:31 < Crell> Eg, use a different one for nodes of different types. | |
01:31 < Stof> you can load the node before choosing the controller | |
01:31 < Crell> That's something we support now via add-on modules in a sucky way. | |
01:32 < DrRotmos> Crell: what you're saying is that the controller depends on the node, which in turn depends on the request, right? | |
01:32 < DrRotmos> i.e. Routing | |
01:32 < Crell> Right. | |
01:32 < Stof> in Symfony, the controller depends of the route, which is determined based on the request | |
01:32 < DrRotmos> take a look at the CMS routing bundle | |
01:32 < Crell> So perhaps I'm conflating the kernel and router. | |
01:32 < DrRotmos> sorry, CMF routing bundle | |
01:32 < Stof> DrRotmos: no need for it | |
01:33 < Stof> it is for PHPCR-based routing | |
01:33 < DrRotmos> Stof: if he wants to do routing to different controllers depending on some domain object, he should use it | |
01:33 < Crell> Which Drupal is not using at present. | |
01:33 < Stof> it's not the first thing to look at to understand routing | |
01:33 < DrRotmos> Stof: it's not just for PHPCR, I should know, I wrote the first iteration of the code :) | |
01:34 < Stof> ah indeed, it is a DoctrineRouter | |
01:35 < DrRotmos> Stof: the built in routing system is great if you can deduce which controller to use depending only on the request object, but if you depend on a domain object as well, you need to extend it somehow | |
01:35 < DrRotmos> and the CMF routing bundle simply allows you to chain multiple routers together | |
01:36 < Stof> Crell: HttpKernel has a ControllerResolver which chooses the controller based on the _controller attribute of the request (and different implementation could use additional stuff) | |
01:36 < DrRotmos> so and while it ships with a DoctrineRouter, you can write your own router and inject it in the chain | |
01:36 < Stof> but it does not bother about the way you fill the _controller attribute | |
01:36 < Stof> in Symfony, there is an event listener using the router to achieve it | |
01:36 < Crell> Right, that's one of the things that concerns me. | |
01:37 < Stof> but you can use a different way to fill it | |
01:37 < Stof> simply use a different listener to populate the attribute (one that you wrote) | |
01:37 < Crell> All of the examples I've seen of using the routing system are not going to scale to the number of routes that Drupal has to support. | |
01:37 < Crell> Unless that's just "demos are too simple to be useful". :-) | |
01:37 < Stof> HttpKernel dependency to Routing is only an optional one | |
01:38 < Stof> jwage: there ? | |
01:40 < pounard> Good night all | |
01:48 -!- krymen [[email protected]] has joined #symfony-dev | |
01:50 -!- rooster [[email protected]] has quit [Remote host closed the connection] | |
01:59 < Crell> Stof: So, let me ask this. | |
02:00 < Crell> If I want to break the page up into N components, and render each separately so that they're ESI-able and can be integrated into a single page render... is that a place that HttpKernel would make sense? | |
02:02 < Crell> That's the sort of problem we were trying to solve with the "context" system, which is basically a data DIC. | |
02:04 -!- danielcsgomes [[email protected]] has quit [Quit: danielcsgomes] | |
02:05 < Stof> yeah | |
02:06 < Crell> Please elaborate. :-) | |
02:06 < Stof> but keep in mind that rendering something by ESI means that the proxy cache will do another request for the ESI block. so the block needs to be represented by a single url | |
02:07 < Crell> Right. | |
02:07 < Stof> not by a complex PHP object | |
02:07 < Crell> So how would you go about injecting more detailed information than just the request object itself? | |
02:08 < Stof> injecting it for what and where ? | |
02:08 < Crell> A given block renders a node, in a given form. | |
02:09 < Crell> On the node/5 page, we can derive that the node is the one with ID 5. | |
02:09 < Crell> On esi-block/blockname-whatever, we cannot get that from the same source because the URL is different. | |
02:09 < Stof> btw, this can be a place for Routing, with a route /node/{node_id} :p | |
02:09 < Crell> Our thinking so far has been that the context object is serializable, so the ESI path that is generated includes the necessary information in GET parameters. | |
02:10 < Stof> Crell: the ESI url needs to include it | |
02:10 < Crell> I figured. :-) We have a routing system now. | |
02:10 < Crell> So you'd do what I just described, essentially. | |
02:10 < Crell> esi-block/blockname?nid=5 | |
02:10 < Stof> when doing ESI in Symfony2, the generated url includes a bunch of GET parameters | |
02:11 < Stof> Crell: or event /esi-block/5/blockname :p | |
02:11 < Crell> Whatever. :-) | |
02:11 < Stof> let me write some code in a gist about the way HttpKernel could be used | |
02:11 < Crell> OK, thanks. | |
02:12 * Crell is still trying to wrap his head around how close S2 is conceptually to what we want to do, in the abstract. | |
02:12 < Crell> Because that determines how hard I can push to use S2 bits to achieve that goal. :-) | |
02:12 < Stof> hmm, just to be sure. Do you need the node object to choose the controller or just its id ? | |
02:13 < Crell> Potentially we need the node object. | |
02:13 < Stof> I can provide 2 examples so I need to choose the good one :) | |
02:13 < Crell> Because two different node types could have vastly different layouts. | |
02:13 < Crell> Or they could be different based on language, or the access level of the user, or other semi-arbitrary "stuff". | |
02:13 -!- jstout [[email protected]] has quit [Quit: jstout] | |
02:13 < Crell> (See why I struggle with this problem space? <g>) | |
02:31 < Crell> Stof: Still demo coding? | |
02:32 < Stof> quite done | |
02:32 < Stof> cleaning some stuff I copy-pasted | |
02:32 < Crell> Cool. :-) | |
02:35 -!- notjosh [[email protected]] has quit [Remote host closed the connection] | |
02:37 -!- krymen [[email protected]] has quit [Ping timeout: 255 seconds] | |
02:38 < Stof> https://gist.github.com/1454212 | |
02:39 < Stof> Crell: ^ | |
02:39 < Crell> Looking now... | |
02:41 < Stof> note that my current example integrates the Symfony routing to match the elements of the url (the node id in my case) but it could be another system as long as the listener using it sets the param in the Request before | |
DrupalNodeListener | |
02:42 < Stof> simply, I know the Symfony routing (and the listener is already in HttpKernel) whereas I don't know the Drupal current solution | |
02:42 < Crell> The current Drupal solution is path matching with infix variables against a denormalized path SQL table. | |
02:43 < Crell> The resulting record that comes back includes instructions on how to translate node/$nid/view into a node object, the access callback, and various other such things. | |
02:43 < Stof> well, the matching to SQL is what is done in DrupalNodeListener | |
02:44 < Crell> So if we had node/nid, and user/uid, and /some/form/goes/here/damnit, those would all be different listeners? | |
02:44 < Crell> Or would we have a single listener that is internally basically our existing code? | |
02:44 < Stof> what the routing does in my case is extracting node_id from the url | |
02:44 * Crell sees the word RequestContext and cries at the noun collision... :-) | |
02:45 < Stof> Crell: no name collision because a class name includes the namespace | |
02:45 < Crell> No, I mean conceptually with what we've been calling "Context". | |
02:45 < Crell> Which already means about 4 different things in Drupal. :-) | |
02:45 < Stof> I simply omitted them there because of lazyness | |
02:46 < Stof> well, this RequestContext class will probably never be seen by a drupal user | |
02:46 < Stof> even a Symfony user does not see it | |
02:46 < Crell> What is that, exactly? | |
02:46 < Crell> (If there's some sort of deep dive documentation on this stuff already beyond fabpot's blog post I should be reading instead, please point me to it.) | |
02:46 < Stof> an object holding values representing the request for the router: host, base_path, path_info... | |
02:47 * Crell blinks. | |
02:47 < Stof> and some parameters that can be used when generating an url without having to pass it | |
02:47 < Crell> So it's an "enhanced request object"? | |
02:47 < Stof> no, a specialized one | |
02:47 < DrRotmos> more like a stripped down | |
02:47 < Stof> containing only stuff needed to match a route | |
02:48 < Stof> regarding the parameters I talked about, symfony registers one in it: the locale | |
02:48 < Crell> Which in Drupal's case could be a variety of things. | |
02:48 < Crell> So line 12 of index.php is the first Drupal-specific bit, right? | |
02:48 < Stof> Crell: not really. the RequestContext does not define the different routers | |
02:48 < Stof> routes* | |
02:49 < Stof> it contains data available in the incoming request | |
02:49 < Crell> That is more or less than what is in HttpFoundation\Request? | |
02:49 < Stof> a subset of it | |
02:49 < Crell> How is that subset selected? | |
02:50 < Stof> route matching is about the path info, so no need of GET, POST and FILES parameters for instance | |
02:50 < Stof> look at the RequestContext::fromRequest method | |
02:50 < Crell> What if your route depends on other headers, like accept types or language? | |
02:51 < Crell> Is that is HttpKernel, or in the Router component? | |
02:51 < Stof> RequestContext is the Routing component | |
02:51 < Stof> well, I will add the namespaces :) | |
02:51 < Crell> :-) | |
02:51 * Crell is trying to keep up, really! | |
02:52 < Stof> and I add a typo. using the request at the beginning and creating it at the end :) | |
02:52 < Crell> hehehe. | |
02:52 < Crell> OK, so if we wanted to expose more "stuff" to the router, we'd subclass RequestContext and add more stuff in fromRequest? | |
02:53 < Crell> I guess I'm not seeing what the value is of RequestContext if it's replicating Request almost exactly. | |
02:53 < Stof> probably or even use a totally different routing system than the sf2 one if you prefer | |
02:54 < Stof> RequestContext allows the Routing component to be used fully standalone | |
02:54 < Stof> even without HttpFoundation | |
02:54 < Crell> Hm. | |
02:54 < Crell> So in the default case it doesn't add much except another layer of loose coupling. | |
02:54 < Stof> Routing has no dependency (well, an optional one to the Config component to load routes from XML or YAML config files) | |
02:55 < Crell> Which we'll likely need, although probably not using the Symfony Config one. | |
02:55 < Crell> Although I've not looked at it in detail. | |
02:58 < Crell> I think I need to throw this into my debugger and step through it. | |
02:58 < Crell> There's a lot of indirection here that I can't grok immediately. | |
03:00 -!- DrRotmos [[email protected]] has quit [Quit: DrRotmos] | |
03:02 < Crell> So DrupalNodeListener is the controller that DrupalControllerResolver locates...? | |
03:03 < Stof> no | |
03:03 < Crell> Damn, missed it again. | |
03:03 < Stof> it is the listener responsible to load the node object before the controller is resolved | |
03:03 < Crell> Ahhh... | |
03:03 < Stof> the kernel.request event is dispatched before choosing the controller (the router also uses it to match the route) | |
03:04 < Crell> OK, so in Drupal right now, if you have a path of node/%node/view, then %node maps to node_load($argument_that_was_in_the_path), which returns a node object. | |
03:04 < Crell> That's what the listener does. | |
03:04 < Stof> yes | |
03:05 < Crell> And an arbitrary number of those can be registered, so one for nodes, one for user, one for taxonomy term, one for a "view object", etc. | |
03:05 < Stof> and ControllerResolver uses the logic you want to retrieve a PHP callable that will return a response | |
03:06 < Stof> this callable will then be called by the kernel (with another event before to add another hook) | |
03:06 < Crell> Will all resolvers fire, or just selected resolvers (somehow)? | |
03:06 -!- juokaz [[email protected]] has quit [Remote host closed the connection] | |
03:06 < Stof> there is only one ControllerResolver in the kernel | |
03:06 * Crell is using the wrong word here... | |
03:07 < Crell> So in index.php, lines 1-11, that's "do standard Symfony stuff". | |
03:07 < Stof> but you could implement a chain resolver containing several ones and calling them until one does the job :) | |
03:07 < Stof> which 1-11 ? before or after adding the namespaces ? | |
03:07 < Crell> lsmith has tried to talk my ear off about those but I didn't have the context (no pun intended) to figure out what he was talking about. | |
03:07 < Crell> Er, before. I don't see one with yet. | |
03:07 < Stof> refresh the page | |
03:07 < Crell> Oh, reloading... | |
03:08 < Crell> OK, so lines 15-26 are Symfony-generic. | |
03:08 < Crell> So technically we could start at 27 if we wanted JUST the Drupal logic only. | |
03:08 < Stof> yep | |
03:09 < Crell> And then DrupalControllerResolver wraps basically our current logic. | |
03:09 < Stof> well, to be efficient, line 17 to 33 should be wrapped in another class | |
03:09 < Crell> True. | |
03:09 < Stof> which also implements the HttpKernelInterface and creates all things only when calling handle() in it | |
03:10 < Stof> so that when using HttpCache, it is not created at all if the cache is used | |
03:10 < Stof> and called only when the cache is expired or missing | |
03:10 < Crell> So class Drupal implements HttpKernelInterface { handle() { That gist } } | |
03:10 < Stof> line 17 to 33 yes | |
03:10 * Crell is beginning to understand. | |
03:11 < Stof> and then, line 41 $kernel being this class | |
03:11 < Stof> oh, in my gist, I duplicated the creation of the request | |
03:11 < Crell> lol | |
03:11 * Crell can fix it later. | |
03:11 < Stof> well, copied it at the top and let it at the bottom | |
03:12 < Stof> anyway, putting it at the top was because of RequestContext using it | |
03:12 < Crell> OK, here's where I run into an issue, maybe. | |
03:12 < Crell> Right | |
03:12 < Crell> Which it sounds like we may not actually need if we're just using our own routing matcher. | |
03:12 < Stof> but if it is created inside Drupal::handle, the request is passed as parameter | |
03:12 < Stof> (and created like 40 ^^) | |
03:12 < Stof> you can use your own router | |
03:13 < Crell> We have a code-time-unknown number of possible listeners. | |
03:13 < Stof> simply use a different listener instead of RouterListener to update the attributes of the route with your own router implementation | |
03:14 < Crell> The normal Drupal answer to that is a hook, which if you're not familiar with them is a low-level event/pointcut system. Works great aside from the need to have all code loaded at all times so that we can do a | |
function_exists() on it. | |
03:14 < Crell> That is not cheap, especially this early. | |
03:14 < Crell> And especially if the objects all need to be created to be passed in rather than lazy-created via a closure. | |
03:14 < Stof> the standard EventDispatcher component requires to create all listeners | |
03:15 < Crell> Right. | |
03:15 < Stof> FrameworkBundle extends it be able to lazy-load them using the DI container | |
03:15 < Crell> The "we have an assload of them and we cannot edit code or do code generation problem" applies to a lot of problems in Drupal. :-) | |
03:15 < Stof> you can create your own extended dispatcher with your own way to lazy-load | |
03:15 < Crell> Ah, OK. | |
03:16 -!- noisebleed [~quassel@gentoo/contributor/noisebleed] has quit [Ping timeout: 252 seconds] | |
03:16 < Stof> basically, instead of giving the listener, FrameworkBundle adds another method allowing to give the id in the container and the event name | |
03:16 * Crell nods. | |
03:16 -!- guilhermeblanco [[email protected]] has joined #symfony-dev | |
03:16 < Stof> and when this event name is dispatched, it loads all listeners for this event | |
03:17 < Stof> but only for this event | |
03:17 < Crell> Container being...? | |
03:17 < Stof> clearly useful for events that are not always dispatched (kernel.exception for instance) | |
03:17 < Stof> the dependency injection container | |
03:17 < Crell> ah | |
03:18 < Crell> I am fairly certain we won't end up using the S2 DIC, although I am toying with the idea of using Pimple. | |
03:18 < Stof> an extended version could eventually have been written based on Pimple but Silex tried not to do it and to register the listeners directly | |
03:19 < Crell> Right, which wouldn't scale for us. | |
03:20 < Stof> Crell: copy https://github.com/symfony/FrameworkBundle/blob/master/ContainerAwareEventDispatcher.php using Pimple instead of the Sf2 container :p | |
03:21 < Stof> the only needed change is in lazyLoad (and of course the constructor) | |
03:21 < Stof> and getContainer obviously which could be either renamed to getPimple or dropped... | |
03:22 < Crell> So what we'd need to do is: 1) Write a lazy-loading EventDispatcher; 2) Somehow register an arbitrary number of listeners to add extra stuff to the request object (nodes, users, etc.); 3) Move our existing routing logic | |
into DrupalControllerResolver::getController(); 4) Always return a Response object from anything that getController() says to call; 5) Profit. | |
03:23 < Stof> btw, there is a way to return something else from the controller (as long as it is not null) | |
03:23 < Crell> ... And then for individual blocks, treat them as a BlockApp class rather than a DrupalApp class that does all the same stuff, but for a different path that we fake before calling that. | |
03:23 < Stof> when the return value is not a Response (and only in this case), the kernel dispatches the kernel.view event as a last chance | |
03:24 < Crell> Kernel depends on EventDispatcher then? | |
03:24 < Stof> yes | |
03:24 < Crell> OK. | |
03:24 < Crell> So... am I finally starting to understand what this is all about? :-) | |
03:24 < Stof> well, the EventDispatcher component is 2 classes and 2 interfaces :) | |
03:24 < Crell> Big whup. | |
03:25 < Stof> the event dispatcher provides hooks in the request handling: | |
03:25 < Stof> kernel.request first | |
03:25 < Stof> then the controllerResolver is used | |
03:25 < Stof> kernel.controller event | |
03:25 < Stof> the controller is now called | |
03:25 < Stof> kernel.view if it does not return a Response | |
03:26 < Stof> if an error occured previously: kernel.exception (to display an error page for instance) | |
03:26 < Stof> and finally kernel.response | |
03:26 < Stof> and then returning the response | |
03:27 < Stof> and the kernel.request event allows a listener to return a response early (skipping all other things until kernel.response) | |
03:27 < Crell> ... And if we want to explicitly set a given object, such as the node, we could set that on the request object before passing it to the sub-app, and write the listeners to ignore it if it's already there. | |
03:28 < Stof> yep, of course | |
03:29 < Stof> look at the RouterListener for instance: it first check if _controller is already set or not | |
03:30 < Crell> And... because each request object is potentially created new, we can guarantee that you cannot modify the request information (the "context" in Drupal speak) in one block/subapp and cause a change in another block/subapp. | |
03:31 < Stof> hmm, you can change the request object | |
03:31 < Stof> but there is a duplicate() method so you could pass a copy to the subapp | |
03:31 < Crell> OK, that's kinda what I'm after. | |
03:31 < Stof> so the subapp will do whatever it wants without impacting the main app | |
03:32 < Crell> So... I'm not sure if I should be happy or annoyed that we basically just described 75% of what I'm trying to do in Drupal 8 as "a couple of Symfony subclasses". | |
03:33 < Stof> and btw, you can force changing the properties of the Request (GET and so on) when duplicating if you add some parameters | |
03:33 < Crell> And the request object can hold arbitrary information. I did not realize that. | |
03:33 < Stof> that's what attributes are for | |
03:34 < Stof> others are about abstracting the superglobals | |
03:34 < Stof> Crell: and think about it: if you use HttpKernel, you get ESI for free (see the additional file in my gist enabling it ^^) | |
03:34 < Crell> Right. | |
03:34 < Crell> And if every block is a DrupalApp of its own, then you get ESI at every level. | |
03:35 < Stof> you could indeed wrap the subapps in a cache :p | |
03:36 < Stof> but are they really subapps or controllers in the main app ? | |
03:36 < Crell> Well, what I'm hoping we can do is allow individual blocks to be rendered asynchronously, either within a single request, managed by varnish, or managed by backbone.js in the client. | |
03:37 < Crell> Actually, I'm thinking we may need to pass an extended request object around as well to handle things like queuing up JS and CSS files. Hm. | |
03:38 < Crell> So that makes 3 objects to pass around: Request, Response, and Services. | |
03:38 < Stof> Response ? | |
03:39 < Stof> the response is the return value, not the argument | |
03:39 < Crell> Any block could add css files to the page. | |
03:39 < Crell> Right, I'm pondering making it an in/out parameter. | |
03:39 < Stof> ah, this is a bit tricky about the building of the content | |
03:39 < Crell> (Actually right now the page array could add css at virtually any point, which is godawful but there are reasons for it.) | |
03:40 < Stof> yeah, I see what can be the advantage for drupal :) | |
03:40 < Stof> you could create a special subclass of the Response class with getStylesheets and getJavascripts | |
03:40 < Crell> So you'd create a DrupalResponse object, pass it around, and let anyone call ->addCSS() and ->addJs(). | |
03:40 < Crell> Right. | |
03:41 < Crell> And then send() stitches that all together via our template system into a page, or into a partial page response, or whatever. | |
03:41 < Stof> if the subapp wants to add some of them, it creates a DrupalResponse | |
03:41 < Crell> And some other controllers could instead ignore that and return a JSON string. | |
03:41 < Stof> and the main app uses them | |
03:42 < Crell> I see I have a lot of work to do this weekend. | |
03:42 < Crell> And I'm probably going to get lynched. :-) | |
03:42 < Stof> Crell: yeah. my idea is similar except that the subapp creates the response itself | |
03:42 < Crell> But then how do you merge response objects? | |
03:42 < Stof> the main app | |
03:43 < Stof> which receives all subapps responses | |
03:43 < Crell> Does the response class have enough getters on it to extract the body and such? | |
03:43 < Crell> Well, I guess that's up to me... | |
03:43 < Crell> So a subapp would never send(), it would get returned, then dissected and merged by its parent app. | |
03:43 < Stof> look at my message above: a subclass with 2 additional getters | |
03:43 < Stof> yes, it returns | |
03:43 < Crell> Right. | |
03:44 < Stof> send() sets the headers and echoes the content | |
03:44 < Crell> Which could return as far up a stack of subapps as we want, and the top level one would ->send() instead of returning. | |
03:44 < Stof> the top level one also returns | |
03:44 < Stof> as it is needed by handle() | |
03:44 < Crell> Ah, OK. | |
03:44 < Stof> look at the gist: send is called by index.php | |
03:44 < Stof> outside the app itself | |
03:45 < Crell> Ahhh... | |
03:45 < Crell> Wait. | |
03:45 < Stof> so really at the whole end | |
03:45 < Crell> I thought DrupalApp wraps $kernel, and returns a response. | |
03:45 < Crell> Oh, right. | |
03:46 < Stof> here is how the index.php could look like: https://github.com/symfony/symfony-standard/blob/master/web/app.php | |
03:46 < Crell> Right. | |
03:46 < Stof> replace the Sf2 AppKernel by your DrupalKernel and you're done | |
03:46 * Crell nods. | |
03:47 < Crell> Now, how much performance overhead are we looking at for making each block on the page a mini-app? | |
03:47 < Stof> I will update the gist with a DrupalKernel containing the bootstrapping logic | |
03:47 < Crell> Or do we assume that if we wrap each one in a cache object that it will work out in the end? | |
03:47 -!- tystr [[email protected]] has quit [Quit: tystr] | |
03:47 < Crell> Does this channel have a karma tracking bot? :-) | |
03:47 < Stof> depends if each subapp uses the Sf2 httpKErnel dispatching events or if it simply implements the inetrface | |
03:48 < Stof> dunno | |
03:48 < Stof> I think #symfony has one | |
03:48 < Crell> Well, I definitely at least owe you dinner the next time we're at a conference together. :-) | |
03:48 < Stof> but you can go vote on http://awards.symfony.com/ if you want :) | |
03:48 < Crell> LOL. | |
03:49 < Crell> Fascinating! | |
03:49 < Crell> If each subapp has to also function on its own ESI request, would that not mandate that it have its own kernel inside it? | |
03:49 < Crell> Or rather that it extend the kernel... | |
03:49 < Stof> Crell: no, it mandates it is a kernel | |
03:49 -!- sirprize [[email protected]] has joined #symfony-dev | |
03:50 < Stof> not that it uses the HttpKernel implementation internally | |
03:50 < Crell> Right, so each block then is doing a full routing lookup. | |
03:50 < Stof> no, it can be a simpler kernel | |
03:50 < Stof> a kernel is simply something with a handle($request) method returning a response | |
03:50 < Stof> the subapp can do whatever it wants to implement it | |
03:50 < Crell> OK... | |
03:51 < Crell> So we special case the routing of sub-apps, essentially. | |
03:51 < Crell> Or rather, we special case the routing of the top-level Drupal app. | |
03:51 < Stof> even public function handle($request) { return new Response('useless subapp spotted);} | |
03:52 < Stof> this subapp does not use the Symfony2 HttpKernel. It simply implements the interface | |
03:52 < Stof> and it can be wrapped in HttpCache (which is useless here as the cache has more overhead than this subapp ^^) | |
03:52 < Crell> But then how does it support block/blockname?nid=5 | |
03:52 < Crell> Vis, being called from Varnish independently. | |
03:53 < Stof> Crell: the subapp handles it if it wants to (eventually using a DrupalKernel) | |
03:53 < Stof> but it does not *need* to | |
03:53 < Crell> I see. | |
03:53 < Stof> depending of what the subapp is meant to do | |
03:53 < Crell> So a block could be directly callable or not, depending on what class it inherits from. | |
03:53 < Stof> let's see someone does a subapp simply to add jQuery in all pages | |
03:53 < Crell> So a lightweight and heavyweight version. | |
03:54 < Stof> it does not need to match the node or to dispatch events | |
03:54 -!- drak [[email protected]] has joined #symfony-dev | |
03:56 < Crell> OK, I need to get to dinner. | |
03:57 < Crell> I am going to save this log and the gist and try to read it over again to fully grok it this weekend. | |
03:57 < drak> Hi Crell | |
03:57 < Crell> But... I think you just saved me about 6 months of work, and replaced it with 6 months of political maneuvering. | |
04:01 < Crell> OK, night folks. | |
04:01 < Crell> Stof: You officially rock. Remind me I owe you dinner whenever we eventually meet. :-) | |
04:01 -!- Crell [[email protected]] has left #symfony-dev [] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment