Skip to content

Instantly share code, notes, and snippets.

@stof
Created December 10, 2011 03:10
Show Gist options
  • Save stof/27bccb88a7cea8e53495 to your computer and use it in GitHub Desktop.
Save stof/27bccb88a7cea8e53495 to your computer and use it in GitHub Desktop.
"Drupal meets HttpKernel" discussion with @Crell... by night
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 [~couac@aln63-1-88-188-221-14.fbx.proxad.net] 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 [~damcgn@xdsl-87-79-214-162.netcologne.de] 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 [~drak@49.244.27.159] 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 [~drak@49.244.27.159] 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 [~krymen@ip-109196055007.syrion.pl] has joined #symfony-dev
01:50 -!- rooster [~russ@cm-84.215.90.57.getinternet.no] 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 [~danielcsg@pa-217-129-62-144.netvisao.pt] 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 [~jstout@wsip-98-175-247-26.sd.sd.cox.net] 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 [~notjosh@a212044.upc-a.chello.nl] has quit [Remote host closed the connection]
02:37 -!- krymen [~krymen@ip-109196055007.syrion.pl] 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 [~magnus@c-f6a172d5.026-17-73746f34.cust.bredbandsbolaget.se] 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 [~juokaz@188-223-12-184.zone14.bethere.co.uk] 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 [~guilherme@bas2-toronto09-2925239527.dsl.bell.ca] 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 [~tystr@wsip-98-175-247-26.sd.sd.cox.net] 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 [~sirprize@84-74-145-220.dclient.hispeed.ch] 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 [~drak@49.244.154.15] 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 [~Crell@209.41.114.202] has left #symfony-dev []
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment