revision 1568 (http://trac.agavi.org/changeset/1568) saw the merge of the david-execution_flow branch to the 0.11 branch. This means some things will break, and it means many things have improved.
Before I begin, let me apologize for promising that there wouldn't be any more breaking changes after 0.11RC1. It was found out that the way things were, we couldn't implement caching (http://trac.agavi.org/ ticket/78, coming to SVN today!), so I figured I should just throw out rendering filters (http://trac.agavi.org/ticket/377) which were the primary reason caching wouldn't be possible, and while doing that, it became clear that I could remodel the execution flow entirely, with minimal to no BC breaks, away from the hacky Action Stack implementation to a system where we have a container that fully encapsulates the execution of an action, without any possibility of the "outer world" breaking things inside this container (http:// trac.agavi.org/ticket/373, and http://trac.agavi.org/ticket/290 which was originally scheduled for 2.0).
That all went very well, and it even became possible to use decorators within slots, and slots inside the content template, without decorators. We now had a very modern, very advanced, and were forward compatible execution flow model, but the templating system all of a sudden seemed horribly archaic in comparison. After some pondering, I finally had the idea to re-do the templating system so it works just like the decorators, but with an infinite number of layers, like an onion or a russian doll (http://trac.agavi.org/ticket/ 287, also originally scheduled for 2.0). As a result, existing templates would work without modification, only the API in the views would change.
We also discovered that while our validation system worked very well for protecting code from malicious or non-validating request parameters, files, cookies, http headers etc didn't get filtered that way. Thus, we introduced request data holders that are now passed to the actions and hold all request data: parameters, files, cookies, headers etc (http://trac.agavi.org/ticket/389). While doing that, it became clear how awfully crappy the current file handling was. getFile () will now return an object on which you can call getSize() to get the size, or move() to move the uploaded file (http://trac.agavi.org/ ticket/391). These two changes should result in only minimal breaking changes and can mostly be adapted to by some search and replace work.
Also, let me stress that that's it for now. No more planned features, everyone's happy. There are some issues and minor bugs to clean up, but besides that, I'm sure we won't see any more breakage from now on. Also because neither I nor anyone else wants that. Really. Again, my apologies.
Now let's move on to the actual changes.
First, the execution flow.
Until now, execution was done using Controller::forward(). A forward put the information about the action to run on the global action stack and then started running through the filters etc. Each time some code wanted to get, say, the action name, it had to pull the last item from the stack and read the information from that. As you can probably imagine, that was very prone to errors and totally messed things up when forwarding, for instance, since the information about what was going on (the action name, module name, action instance, request data etc) was on the stack, not encapsulated within the execution. This decoupling and indirect way of accessing the current state of execution was rather ugly.
Enter execution containers. Controller::forward() is gone now, and each container encapsulates all information necessary to run an action: module name, action name, request information, the output type (yes, the output type is _not_ global anymore!), the response, information about the view etc. When execute() is called, the entire execution happens fully isolated, like a black box. This also brings a slight performance improvement for the general execution process.
As a consequence, you obviously cannot use $actionStack->getLastEntry ()->getActionName() etc anymore. Instead, Views and Actions are given the container instance as the first argument to the initialize() method. It is then available via $this->getContainer(). Note that in Views, $this->getResponse() is a shortcut for $this->getContainer()- >getResponse(). So for example to grab the micro timestamp at which the current action started execution, do $this->getContainer()- >getMicrotime().
Also, filters have changed due to this. Filter execute() methods now get the filter chain as the first argument, and the container as the second. To proceed to the next filter in the chain, you must call $filterChain->execute($container), i.e. hand on the container you got as the second argument. Again, the response for the container is available from $container->getResponse().
Also, for forwarding and for setting slots, you use an execution container instance now, so to forward to EggAction in module Spam, you do return $this->container->createExecutionContainer('Spam', 'Egg'); Likewise, to set a slot, you do setSlot('name', $container); (I'll get to slots later) An important note here. As I mentioned, each execution container also has it's own output type. This is rather useful since you can run a slot with a different output type and have it produce, say, JSON data you use in a script tag in the page header. Unfortunately, the standard way to create an execution container is the createExecutionContainer method in the Controller, which will use the default output type. That's why each container has such a method too, which calls the controller's method, but with the same output type as that controller - basically, the container creates a "brother" of itself. createExecutionContainer() optionally accepts a RequestDataHolder object as the third argument to pass additional information such as parameters, files, cookies to the forwarded action or the slot, and the name of an output type as the fourth argument.
When you define a forward, this information is stored in the current execution container. Once that finished running, it will execute this next container, and return THAT CONTAINER's RESPONSE. That means if you set a cookie or a redirect in the container and then forward, that information gets lost. This is usually the desired behavior, but there might be situations where you don't want that to happen. Because of that, there's also a global response in $controller- >getGlobalResponse(). There are circumstances when you should use that one, like when setting a cookie from a routing callback or so.
Also, redirection is now done via the response by calling $response- >redirect('targeturl'); Since you don't want & there, remember to pass array('separator' => '&') as the third argument to Routing::gen(). WebResponse::redirect also accepts a second parameter for an HTTP status code other than the default 302.
Again, you have to make a wise decision between local container's response and the global response. Remember, only a local container's response will be included in caches. Use it whenever possible.
Other than that, this change shouldn't affect actions and/or views. Yes, rendering filters were removed, but I don't think anyone ever uesed these anyway. Likely since there was absolutely no use case for them, all they did was cause unnecessary overhead.
Next: the new template architecture.
This is a rather big change. Until now, we had a content template, and a decorator template. A decorator template could also have slots, but the content template couldn't, and a slot couldn't use a decorator template itself. Per- locale templates were only possible for the content template, not for the decorator. You could only have these two layers. Both content templates any layers had to use the same renderer with the same configuration. Templates couldn't come from a database. And so on. Clearly, that was poorly implemented.
Now, all of the above works. And it's not even poorly implemented ;)
First, the layering. Instead of setTemplate and setDecoratorTemplate, layers are now used. To mimic the old behavior, a view would have a layer called "content" with the content template (e.g. "IndexSuccess"), and another layer who's name doesn't really matter (let's call it "decorator") with the template "Master". Now what happens is that the first layer is rendered, and it's result is available to the next layer as a slot. The name of the slot is the name of that layer. Here, we used "content" because it doesn't only make sense, but actually produces results similar to the old system, where the content template was always available as "content" in the decorator. As you probably have guessed, layers are executed in the order they are defined. Of course, you can re-arrange them at runtime, remove them, add new ones at arbitrary locations and so on. So let's have a look at the code:
Yes, you are absolutely right. This code is HIDEOUS. You don't want to do that in every view, and even with a base view (more on that later), it's awfully ugly.
BUT!
You can configure these layouts very easily, and as a result, you don't have to write that code anymore, instead you just do
$this->loadLayout();
to load a layout (no name passed here, so it will use the default one). I'll explain this in more detail in a minute.
It is Response::setRedirect(), not redirect(), sorry.
Also, I forgot to mention that you cannot extract slots anymore, and that slots and template attributes cannot share a variable. That means slots won't be available in $template anymore, the default variable name is now $slots. All still configurable using "var_name", "slot_var_name" and "extract_vars" parameters of the renderer
> revision 1568 (http://trac.agavi.org/changeset/1568) saw the merge of > the david-execution_flow branch to the 0.11 branch. This means some > things will break, and it means many things have improved.
> Before I begin, let me apologize for promising that there wouldn't be > any more breaking changes after 0.11RC1. It was found out that the > way things were, we couldn't implement caching (http://trac.agavi.org/ > ticket/78, coming to SVN today!), so I figured I should just throw > out rendering filters (http://trac.agavi.org/ticket/377) which were > the primary reason caching wouldn't be possible, and while doing > that, it became clear that I could remodel the execution flow > entirely, with minimal to no BC breaks, away from the hacky Action > Stack implementation to a system where we have a container that fully > encapsulates the execution of an action, without any possibility of > the "outer world" breaking things inside this container (http:// > trac.agavi.org/ticket/373, and http://trac.agavi.org/ticket/290 which > was originally scheduled for 2.0).
> That all went very well, and it even became possible to use > decorators within slots, and slots inside the content template, > without decorators. We now had a very modern, very advanced, and were > forward compatible execution flow model, but the templating system > all of a sudden seemed horribly archaic in comparison. After some > pondering, I finally had the idea to re-do the templating system so > it works just like the decorators, but with an infinite number of > layers, like an onion or a russian doll (http://trac.agavi.org/ticket/ > 287, also originally scheduled for 2.0). As a result, existing > templates would work without modification, only the API in the views > would change.
> We also discovered that while our validation system worked very well > for protecting code from malicious or non-validating request > parameters, files, cookies, http headers etc didn't get filtered that > way. Thus, we introduced request data holders that are now passed to > the actions and hold all request data: parameters, files, cookies, > headers etc (http://trac.agavi.org/ticket/389). While doing that, it > became clear how awfully crappy the current file handling was. getFile > () will now return an object on which you can call getSize() to get > the size, or move() to move the uploaded file (http://trac.agavi.org/ > ticket/391). These two changes should result in only minimal breaking > changes and can mostly be adapted to by some search and replace work.
> Also, let me stress that that's it for now. No more planned features, > everyone's happy. There are some issues and minor bugs to clean up, > but besides that, I'm sure we won't see any more breakage from now > on. Also because neither I nor anyone else wants that. Really. Again, > my apologies.
> Now let's move on to the actual changes.
> First, the execution flow.
> Until now, execution was done using Controller::forward(). A forward > put the information about the action to run on the global action > stack and then started running through the filters etc. Each time > some code wanted to get, say, the action name, it had to pull the > last item from the stack and read the information from that. As you > can probably imagine, that was very prone to errors and totally > messed things up when forwarding, for instance, since the information > about what was going on (the action name, module name, action > instance, request data etc) was on the stack, not encapsulated within > the execution. This decoupling and indirect way of accessing the > current state of execution was rather ugly.
> Enter execution containers. > Controller::forward() is gone now, and each container encapsulates > all information necessary to run an action: module name, action name, > request information, the output type (yes, the output type is _not_ > global anymore!), the response, information about the view etc. When > execute() is called, the entire execution happens fully isolated, > like a black box. This also brings a slight performance improvement > for the general execution process.
> As a consequence, you obviously cannot use $actionStack->getLastEntry > ()->getActionName() etc anymore. Instead, Views and Actions are given > the container instance as the first argument to the initialize() > method. It is then available via $this->getContainer(). Note that in > Views, $this->getResponse() is a shortcut for $this->getContainer()- >> getResponse(). So for example to grab the micro timestamp at which > the current action started execution, do $this->getContainer()- >> getMicrotime().
> Also, filters have changed due to this. Filter execute() methods now > get the filter chain as the first argument, and the container as the > second. To proceed to the next filter in the chain, you must call > $filterChain->execute($container), i.e. hand on the container you got > as the second argument. Again, the response for the container is > available from $container->getResponse().
> Also, for forwarding and for setting slots, you use an execution > container instance now, so to forward to EggAction in module Spam, > you do return $this->container->createExecutionContainer('Spam', > 'Egg'); Likewise, to set a slot, you do setSlot('name', $container); > (I'll get to slots later) > An important note here. As I mentioned, each execution container also > has it's own output type. This is rather useful since you can run a > slot with a different output type and have it produce, say, JSON data > you use in a script tag in the page header. Unfortunately, the > standard way to create an execution container is the > createExecutionContainer method in the Controller, which will use the > default output type. That's why each container has such a method too, > which calls the controller's method, but with the same output type as > that controller - basically, the container creates a "brother" of > itself. > createExecutionContainer() optionally accepts a RequestDataHolder > object as the third argument to pass additional information such as > parameters, files, cookies to the forwarded action or the slot, and > the name of an output type as the fourth argument.
> When you define a forward, this information is stored in the current > execution container. Once that finished running, it will execute this > next container, and return THAT CONTAINER's RESPONSE. That means if > you set a cookie or a redirect in the container and then forward, > that information gets lost. This is usually the desired behavior, but > there might be situations where you don't want that to happen. > Because of that, there's also a global response in $controller- >> getGlobalResponse(). There are circumstances when you should use > that one, like when setting a cookie from a routing callback or so.
> Also, redirection is now done via the response by calling $response- >> redirect('targeturl'); Since you don't want & there, remember > to pass array('separator' => '&') as the third argument to > Routing::gen(). WebResponse::redirect also accepts a second parameter > for an HTTP status code other than the default 302.
> Again, you have to make a wise decision between local container's > response and the global response. Remember, only a local container's > response will be included in caches. Use it whenever possible.
> Other than that, this change shouldn't affect actions and/or views. > Yes, rendering filters were removed, but I don't think anyone ever > uesed these anyway. Likely since there was absolutely no use case for > them, all they did was cause unnecessary overhead.
> Next: the new template architecture.
> This is a rather big change. > Until now, we had a content template, and a decorator template. A > decorator template could also have slots, but the content template > couldn't, and a slot couldn't use a decorator template itself. Per- > locale templates were only possible for the content template, not for > the decorator. You could only have these two layers. Both content > templates any layers had to use the same renderer with the same > configuration. Templates couldn't come from a database. And so on. > Clearly, that was poorly implemented.
> Now, all of the above works. And it's not even poorly implemented ;)
> First, the layering. > Instead of setTemplate and setDecoratorTemplate, layers are now used. > To mimic the old behavior, a view would have a layer called "content" > with the content template (e.g. "IndexSuccess"), and another layer > who's name doesn't really matter (let's call it "decorator") with the > template "Master". > Now what happens is that the first layer is rendered, and it's result > is available to the next layer as a slot. The name of the slot is the > name of that layer. Here, we used "content" because it doesn't only > make sense, but actually produces results similar to the old system, > where the content template was always available as "content" in the > decorator. > As you probably have guessed, layers are executed in the order they > are defined. Of course, you can re-arrange them at runtime, remove > them, add new ones at arbitrary locations and so on. So let's have a > look at the code:
> It is Response::setRedirect(), not redirect(), sorry.
> Also, I forgot to mention that you cannot extract slots anymore, and > that slots and template attributes cannot share a variable. That > means slots won't be available in $template anymore, the default > variable name is now $slots. All still configurable using "var_name", > "slot_var_name" and "extract_vars" parameters of the renderer
> Am 25.01.2007 um 13:28 schrieb David Zülke:
>> Hi guys,
>> revision 1568 (http://trac.agavi.org/changeset/1568) saw the merge of >> the david-execution_flow branch to the 0.11 branch. This means some >> things will break, and it means many things have improved.
>> Before I begin, let me apologize for promising that there wouldn't be >> any more breaking changes after 0.11RC1. It was found out that the >> way things were, we couldn't implement caching (http:// >> trac.agavi.org/ >> ticket/78, coming to SVN today!), so I figured I should just throw >> out rendering filters (http://trac.agavi.org/ticket/377) which were >> the primary reason caching wouldn't be possible, and while doing >> that, it became clear that I could remodel the execution flow >> entirely, with minimal to no BC breaks, away from the hacky Action >> Stack implementation to a system where we have a container that fully >> encapsulates the execution of an action, without any possibility of >> the "outer world" breaking things inside this container (http:// >> trac.agavi.org/ticket/373, and http://trac.agavi.org/ticket/290 which >> was originally scheduled for 2.0).
>> That all went very well, and it even became possible to use >> decorators within slots, and slots inside the content template, >> without decorators. We now had a very modern, very advanced, and were >> forward compatible execution flow model, but the templating system >> all of a sudden seemed horribly archaic in comparison. After some >> pondering, I finally had the idea to re-do the templating system so >> it works just like the decorators, but with an infinite number of >> layers, like an onion or a russian doll (http://trac.agavi.org/ >> ticket/ >> 287, also originally scheduled for 2.0). As a result, existing >> templates would work without modification, only the API in the views >> would change.
>> We also discovered that while our validation system worked very well >> for protecting code from malicious or non-validating request >> parameters, files, cookies, http headers etc didn't get filtered that >> way. Thus, we introduced request data holders that are now passed to >> the actions and hold all request data: parameters, files, cookies, >> headers etc (http://trac.agavi.org/ticket/389). While doing that, it >> became clear how awfully crappy the current file handling was. >> getFile >> () will now return an object on which you can call getSize() to get >> the size, or move() to move the uploaded file (http://trac.agavi.org/ >> ticket/391). These two changes should result in only minimal breaking >> changes and can mostly be adapted to by some search and replace work.
>> Also, let me stress that that's it for now. No more planned features, >> everyone's happy. There are some issues and minor bugs to clean up, >> but besides that, I'm sure we won't see any more breakage from now >> on. Also because neither I nor anyone else wants that. Really. Again, >> my apologies.
>> Now let's move on to the actual changes.
>> First, the execution flow.
>> Until now, execution was done using Controller::forward(). A forward >> put the information about the action to run on the global action >> stack and then started running through the filters etc. Each time >> some code wanted to get, say, the action name, it had to pull the >> last item from the stack and read the information from that. As you >> can probably imagine, that was very prone to errors and totally >> messed things up when forwarding, for instance, since the information >> about what was going on (the action name, module name, action >> instance, request data etc) was on the stack, not encapsulated within >> the execution. This decoupling and indirect way of accessing the >> current state of execution was rather ugly.
>> Enter execution containers. >> Controller::forward() is gone now, and each container encapsulates >> all information necessary to run an action: module name, action name, >> request information, the output type (yes, the output type is _not_ >> global anymore!), the response, information about the view etc. When >> execute() is called, the entire execution happens fully isolated, >> like a black box. This also brings a slight performance improvement >> for the general execution process.
>> As a consequence, you obviously cannot use $actionStack->getLastEntry >> ()->getActionName() etc anymore. Instead, Views and Actions are given >> the container instance as the first argument to the initialize() >> method. It is then available via $this->getContainer(). Note that in >> Views, $this->getResponse() is a shortcut for $this->getContainer()- >>> getResponse(). So for example to grab the micro timestamp at which >> the current action started execution, do $this->getContainer()- >>> getMicrotime().
>> Also, filters have changed due to this. Filter execute() methods now >> get the filter chain as the first argument, and the container as the >> second. To proceed to the next filter in the chain, you must call >> $filterChain->execute($container), i.e. hand on the container you got >> as the second argument. Again, the response for the container is >> available from $container->getResponse().
>> Also, for forwarding and for setting slots, you use an execution >> container instance now, so to forward to EggAction in module Spam, >> you do return $this->container->createExecutionContainer('Spam', >> 'Egg'); Likewise, to set a slot, you do setSlot('name', $container); >> (I'll get to slots later) >> An important note here. As I mentioned, each execution container also >> has it's own output type. This is rather useful since you can run a >> slot with a different output type and have it produce, say, JSON data >> you use in a script tag in the page header. Unfortunately, the >> standard way to create an execution container is the >> createExecutionContainer method in the Controller, which will use the >> default output type. That's why each container has such a method too, >> which calls the controller's method, but with the same output type as >> that controller - basically, the container creates a "brother" of >> itself. >> createExecutionContainer() optionally accepts a RequestDataHolder >> object as the third argument to pass additional information such as >> parameters, files, cookies to the forwarded action or the slot, and >> the name of an output type as the fourth argument.
>> When you define a forward, this information is stored in the current >> execution container. Once that finished running, it will execute this >> next container, and return THAT CONTAINER's RESPONSE. That means if >> you set a cookie or a redirect in the container and then forward, >> that information gets lost. This is usually the desired behavior, but >> there might be situations where you don't want that to happen. >> Because of that, there's also a global response in $controller- >>> getGlobalResponse(). There are circumstances when you should use >> that one, like when setting a cookie from a routing callback or so.
>> Also, redirection is now done via the response by calling $response- >>> redirect('targeturl'); Since you don't want & there, remember >> to pass array('separator' => '&') as the third argument to >> Routing::gen(). WebResponse::redirect also accepts a second parameter >> for an HTTP status code other than the default 302.
>> Again, you have to make a wise decision between local container's >> response and the global response. Remember, only a local container's >> response will be included in caches. Use it whenever possible.
>> Other than that, this change shouldn't affect actions and/or views. >> Yes, rendering filters were removed, but I don't think anyone ever >> uesed these anyway. Likely since there was absolutely no use case for >> them, all they did was cause unnecessary overhead.
>> Next: the new template architecture.
>> This is a rather big change. >> Until now, we had a content template, and a decorator template. A >> decorator template could also have slots, but the content template >> couldn't, and a slot couldn't use a decorator template itself. Per- >> locale templates were only possible for the content template, not for >> the decorator. You could only have these two layers. Both content >> templates any layers had to use the same renderer with the same >> configuration. Templates couldn't come from a database. And so on. >> Clearly, that was poorly implemented.
>> Now, all of the above works. And it's not even poorly implemented ;)
>> First, the layering. >> Instead of setTemplate and setDecoratorTemplate, layers are now used. >> To mimic the old behavior, a view would have a layer called "content" >> with the content template (e.g. "IndexSuccess"), and another layer >> who's name doesn't really matter (let's call it "decorator") with the >> template "Master". >> Now what happens is that the first layer is rendered, and it's result >> is available to the next layer as a slot. The name of the slot is the >> name of that layer. Here, we used "content" because it doesn't only >> make sense, but actually produces results similar to the old system, >> where the content template was always available as "content" in the >> decorator. >> As you probably have guessed, layers are executed in the order they >> are defined. Of course, you can re-arrange them at runtime, remove >> them, add new ones at arbitrary locations and so on. So let's have a >> look at the
Thanks for all the hard work. Seems like there are some new cool things to play with. When I have a chance, I'll update to the latest revision and give it a try. Escecially the view/templating improvements seem to be just what I need.
> -----Oorspronkelijk bericht----- > Van: users-boun...@lists.agavi.org > [mailto:users-boun...@lists.agavi.org] Namens David Zülke > Verzonden: donderdag 25 januari 2007 13:29 > Aan: d...@lists.agavi.org > CC: Agavi Users Mailing List > Onderwerp: [Agavi-Users] IMPORTANT: Breaking changes in 0.11 > branch: tons ofnew features!
> Hi guys,
> revision 1568 (http://trac.agavi.org/changeset/1568) saw the > merge of the david-execution_flow branch to the 0.11 branch. > This means some things will break, and it means many things > have improved.
> Before I begin, let me apologize for promising that there > wouldn't be any more breaking changes after 0.11RC1. It was > found out that the way things were, we couldn't implement > caching (http://trac.agavi.org/ ticket/78, coming to SVN > today!), so I figured I should just throw out rendering > filters (http://trac.agavi.org/ticket/377) which were the > primary reason caching wouldn't be possible, and while doing > that, it became clear that I could remodel the execution flow > entirely, with minimal to no BC breaks, away from the hacky > Action Stack implementation to a system where we have a > container that fully encapsulates the execution of an action, > without any possibility of the "outer world" breaking things > inside this container (http:// trac.agavi.org/ticket/373, and > http://trac.agavi.org/ticket/290 which was originally > scheduled for 2.0).
> That all went very well, and it even became possible to use > decorators within slots, and slots inside the content > template, without decorators. We now had a very modern, very > advanced, and were forward compatible execution flow model, > but the templating system all of a sudden seemed horribly > archaic in comparison. After some pondering, I finally had > the idea to re-do the templating system so it works just like > the decorators, but with an infinite number of layers, like > an onion or a russian doll (http://trac.agavi.org/ticket/ > 287, also originally scheduled for 2.0). As a result, > existing templates would work without modification, only the > API in the views would change.
> We also discovered that while our validation system worked > very well for protecting code from malicious or > non-validating request parameters, files, cookies, http > headers etc didn't get filtered that way. Thus, we introduced > request data holders that are now passed to the actions and > hold all request data: parameters, files, cookies, headers > etc (http://trac.agavi.org/ticket/389). While doing that, it > became clear how awfully crappy the current file handling was. getFile > () will now return an object on which you can call getSize() > to get the size, or move() to move the uploaded file > (http://trac.agavi.org/ ticket/391). These two changes should > result in only minimal breaking changes and can mostly be > adapted to by some search and replace work.
> Also, let me stress that that's it for now. No more planned > features, everyone's happy. There are some issues and minor > bugs to clean up, but besides that, I'm sure we won't see any > more breakage from now on. Also because neither I nor anyone > else wants that. Really. Again, my apologies.
> Now let's move on to the actual changes.
> First, the execution flow.
> Until now, execution was done using Controller::forward(). A > forward put the information about the action to run on the > global action stack and then started running through the > filters etc. Each time some code wanted to get, say, the > action name, it had to pull the last item from the stack and > read the information from that. As you can probably imagine, > that was very prone to errors and totally messed things up > when forwarding, for instance, since the information about > what was going on (the action name, module name, action > instance, request data etc) was on the stack, not > encapsulated within the execution. This decoupling and > indirect way of accessing the current state of execution was > rather ugly.
> Enter execution containers. > Controller::forward() is gone now, and each container > encapsulates all information necessary to run an action: > module name, action name, request information, the output > type (yes, the output type is _not_ global anymore!), the > response, information about the view etc. When > execute() is called, the entire execution happens fully > isolated, like a black box. This also brings a slight > performance improvement for the general execution process.
> As a consequence, you obviously cannot use $actionStack->getLastEntry > ()->getActionName() etc anymore. Instead, Views and Actions > are given the container instance as the first argument to the > initialize() method. It is then available via > $this->getContainer(). Note that in Views, > $this->getResponse() is a shortcut for $this->getContainer()- > >getResponse(). So for example to grab the micro timestamp > at which the current action started execution, do > $this->getContainer()- >getMicrotime().
> Also, filters have changed due to this. Filter execute() > methods now get the filter chain as the first argument, and > the container as the second. To proceed to the next filter in > the chain, you must call $filterChain->execute($container), > i.e. hand on the container you got as the second argument. > Again, the response for the container is available from > $container->getResponse().
> Also, for forwarding and for setting slots, you use an > execution container instance now, so to forward to EggAction > in module Spam, you do return > $this->container->createExecutionContainer('Spam', > 'Egg'); Likewise, to set a slot, you do setSlot('name', > $container); (I'll get to slots later) An important note > here. As I mentioned, each execution container also has it's > own output type. This is rather useful since you can run a > slot with a different output type and have it produce, say, > JSON data you use in a script tag in the page header. > Unfortunately, the standard way to create an execution > container is the createExecutionContainer method in the > Controller, which will use the default output type. That's > why each container has such a method too, which calls the > controller's method, but with the same output type as that > controller - basically, the container creates a "brother" of itself. > createExecutionContainer() optionally accepts a > RequestDataHolder object as the third argument to pass > additional information such as parameters, files, cookies to > the forwarded action or the slot, and the name of an output > type as the fourth argument.
> When you define a forward, this information is stored in the > current execution container. Once that finished running, it > will execute this next container, and return THAT CONTAINER's > RESPONSE. That means if you set a cookie or a redirect in the > container and then forward, that information gets lost. This > is usually the desired behavior, but there might be > situations where you don't want that to happen. > Because of that, there's also a global response in > $controller- >getGlobalResponse(). There are circumstances > when you should use that one, like when setting a cookie from > a routing callback or so.
> Also, redirection is now done via the response by calling > $response- >redirect('targeturl'); Since you don't want > & there, remember to pass array('separator' => '&') as > the third argument to Routing::gen(). WebResponse::redirect > also accepts a second parameter for an HTTP status code other > than the default 302.
> Again, you have to make a wise decision between local > container's response and the global response. Remember, only > a local container's response will be included in caches. Use > it whenever possible.
> Other than that, this change shouldn't affect actions and/or views. > Yes, rendering filters were removed, but I don't think anyone > ever uesed these anyway. Likely since there was absolutely no > use case for them, all they did was cause unnecessary overhead.
> Next: the new template architecture.
> This is a rather big change. > Until now, we had a content template, and a decorator > template. A decorator template could also have slots, but the > content template couldn't, and a slot couldn't use a > decorator template itself. Per- locale templates were only > possible for the content template, not for the decorator. You > could only have these two layers. Both content templates any > layers had to use the same renderer with the same > configuration. Templates couldn't come from a database. And so on. > Clearly, that was poorly implemented.
> Now, all of the above works. And it's not even poorly implemented ;)
> First, the layering. > Instead of setTemplate and setDecoratorTemplate, layers are > now used. > To mimic the old behavior, a view would have a layer called > "content" > with the content template (e.g. "IndexSuccess"), and another > layer who's name doesn't really matter (let's call it > "decorator") with the template "Master". > Now what happens is that the first layer is rendered, and > it's result is available to the next layer as a slot. The > name of the slot is the name of that layer. Here, we used > "content" because it doesn't only make sense, but actually > produces results similar to the old system, where the content > template was always available as "content" in the decorator. > As you probably have guessed, layers are executed in the > order they are defined. Of
Discussion subject changed to "Advanced Layout/Layers example (was: Re: IMPORTANT: Breaking changes in 0.11 branch: tons of new features!)" by David Zülke
I thought I'd give you a little example of what the new template layer system can do.
We participated in the PHP Throwdown (http://www.phpthrowdown.com) and built an IRC bot using Agavi. It's called "Chuckwalla", contains a fully fledged IRC library that doesn't suck, has live logs, cool web interfaces etc and will be open sourced soon.
One feature is the live logs. You click a channel, and it uses Ajax to refresh the contents, so you can follow the discussion in a channel. So for the first load, we need a full document, and for subsequent XMLHttpRequest, we just want the new messages.
Here is the layout's configuration for output type "html": <layout name="standard"> <layers> <layer name="content" class="AgaviFileTemplateLayer" /> <layer name="decorator" class="AgaviFileTemplateLayer"> <parameter name="template">Master</parameter> </layer> </layers> </layout>
Let's assume this is our decorator template: <html> <head><title>logs!</title></head> <body> <?php echo $slots['content']; ?> </body> </html>
And here is the content template (LiveSuccess.php): <div id="roomTitle"><?php echo $t['topic']; ?></div> <table border="0" cellspacing="0" cellpadding="0"> <tbody> <?php foreach($t['messages'] as $message): ?> <tr> <td class="name"><?php echo $message->getNick()->getNick(); ?></td> <td class="message"><?php echo $message->getMessage(); ?></td> <td class="time"><?php echo $message->getMessageDate(); ?></td> </tr> <?php endforeach; ?> </tbody> </table>
Now we also have an output type for the AJAX calls, called "json". It's configured like this: <layout name="standard"> <layers> <layer name="content" class="AgaviFileTemplateLayer" /> </layers> </layout>
But now we have a problem: If an AJAX call is made, we get back the entire inner content template, complete with the <divs> and the <table>, but we only want the <tbody> to append to the current table (remember, tables can have multiple <tbody> elements).
One approach would be to simply have two templates. The other approach would be to split them up and include() the inner portion in the non-ajax version, and set the templates differently per output type.
However, there also is a third approach. Right now, we have these layers: +------------------------+ | <html> decorator | | +--------------------+ | | | <table> content | | | | with <tbody>, <tr> | | | +--------------------+ | | </html> | +------------------------+
What we can do now is split up the template like we would with the include() solution: +------------------------+ | <html> decorator | | +--------------------+ | | | <table> wrapper | | | | +----------------+ | | | | | <tbody> inner | | | | | | content | | | | | +----------------+ | | | | </table> | | | +--------------------+ | | </html> | +------------------------+
And the actual "inner" content template (LiveSuccess.php): <tbody> <?php foreach($t['messages'] as $message): ?> <tr> <td class="name"><?php echo $message->getNick()->getNick(); ?></td> <td class="message"><?php echo $message->getMessage(); ?></td> <td class="time"><?php echo $message->getMessageDate(); ?></td> </tr> <?php endforeach; ?> </tbody>
For Ajax, everything is fine now. We'll get the <tbody> content. But for output type "html", the result will be this: <html> <head><title>logs!</title></head> <body> <tbody> <tr> <td class="name">John McClane</td> <td class="message">Yippie-kay-yay motherfucker</td> <td class="time">03:27</td> </tr> </tbody> </body> </html>
What we need now is insert the wrapper IN BETWEEN "decorator" and "content" layer.
Remember that the decorator template expects to output $slots ['content']. Hence, we actually have to modify the "content" layer to display the wrapper template (LiveSuccess.wrapper.php), and then prepend another "inner" layer (LiveSuccess.php) to the layer list. We do it like this, in the view:
public function executeHtml(AgaviRequestDataHolder $rd) { // remember: first, you should have a base view and call a method on the parent to load the layout instead of doing it here. // second, loadLayout() would be enough since "standard" is the default layer $this->loadLayout('standard');
// we get the "content" layer and change the template to the wrapper $c = $this->getLayer('content'); $c->setTemplate('LiveSuccess.wrapper');
// and then prepend the actual "inner" content template to the list $i = $this->prependLayer($this->createLayer ('AgaviFileTemplateLayer', 'inner')); $i->setTemplate('LiveSuccess');
}
And that's it! Now we have the desired result: <html> <head><title>logs!</title></head> <body> <div id="roomTitle"><?php echo $t['topic']; ?></div> <table border="0" cellspacing="0" cellpadding="0"> <tbody> <tr> <td class="name">John McClane</td> <td class="message">Yippie-kay-yay motherfucker</td> <td class="time">03:27</td> </tr> </tbody> </table> </body> </html>
IMPORTANT: Some advice regarding base views. In the original email, I recommended that you always have a base view you extend from where execute() throws an exception. This view would also have base methods you can call that load layers, maybe set dynamic slots, or do stuff like: $this->setAttribute('_contentType', $this->container->getOutputType()- >getParameter('Content-Type'));
However, there is a problem here. The whole point of having execute() throw an exception is that if there is a request using, say, ajax, that sets the output type to, say, "json" because of a routing rule like this: <route pattern="^XMLHttpRequest$" source="_SERVER [HTTP_X_REQUESTED_WITH]" stop="false" output_type="json" />
And you then implement a base executeJson() in the base view... all of your views DO serve the json output type, and the normal execute() is never called, not even for actions/views that don't implement ajax features and should thus get an exception!
To solve that problem, name these base methods differently. I suggest you call them "setupHtml", "setupJson" and so on, and then call them using $this->setupHtml($rd); etc inside your concrete view's executeHtml() method.
As of http://trac.agavi.org/changeset/1609, the output of the inner layer is available via the variable $inner in templates. Also available are $container for the current execution container and $view for the template's view. Like with factories, the names of these assigns ("inner", "container" and "view") can be configured using the "assigns" parameter of a renderer.
Since the wrapping template now does not have to know the inner layer's name anymore, the example can be simplified quite a bit.
And the code that makes it happen: public function executeHtml(AgaviRequestDataHolder $rd) { // remember: first, you should have a base view and call a method on the parent to load the layout instead of doing it here. // second, loadLayout() would be enough since "standard" is the default layout $this->loadLayout('standard');
// now we insert the wrapper template between the decorator and the actual content layer $i = $this->appendLayer($this->createLayer ('AgaviFileTemplateLayer', 'wrapper'), $this->getLayer('content')); $i->setTemplate('LiveSuccess.wrapper');
> I thought I'd give you a little example of what the new template > layer system can do.
> We participated in the PHP Throwdown (http://www.phpthrowdown.com) > and built an IRC bot using Agavi. It's called "Chuckwalla", contains > a fully fledged IRC library that doesn't suck, has live logs, cool > web interfaces etc and will be open sourced soon.
> One feature is the live logs. You click a channel, and it uses Ajax > to refresh the contents, so you can follow the discussion in a > channel. So for the first load, we need a full document, and for > subsequent XMLHttpRequest, we just want the new messages.
> Here is the layout's configuration for output type "html": > <layout name="standard"> > <layers> > <layer name="content" class="AgaviFileTemplateLayer" /> > <layer name="decorator" class="AgaviFileTemplateLayer"> > <parameter name="template">Master</parameter> > </layer> > </layers> > </layout>
> Now we also have an output type for the AJAX calls, called "json". > It's configured like this: > <layout name="standard"> > <layers> > <layer name="content" class="AgaviFileTemplateLayer" /> > </layers> > </layout>
> But now we have a problem: If an AJAX call is made, we get back the > entire inner content template, complete with the <divs> and the > <table>, but we only want the <tbody> to append to the current table > (remember, tables can have multiple <tbody> elements).
> One approach would be to simply have two templates. The other > approach would be to split them up and include() the inner portion in > the non-ajax version, and set the templates differently per output > type.
> However, there also is a third approach. Right now, we have these > layers: > +------------------------+ > | <html> decorator | > | +--------------------+ | > | | <table> content | | > | | with <tbody>, <tr> | | > | +--------------------+ | > | </html> | > +------------------------+
> What we can do now is split up the template like we would with the > include() solution: > +------------------------+ > | <html> decorator | > | +--------------------+ | > | | <table> wrapper | | > | | +----------------+ | | > | | | <tbody> inner | | | > | | | content | | | > | | +----------------+ | | > | | </table> | | > | +--------------------+ | > | </html> | > +------------------------+
> For Ajax, everything is fine now. We'll get the <tbody> content. But > for output type "html", the result will be this: > <html> > <head><title>logs!</title></head> > <body> > <tbody> > <tr> > <td class="name">John McClane</td> > <td class="message">Yippie-kay-yay motherfucker</td> > <td class="time">03:27</td> > </tr> > </tbody> > </body> > </html>
> What we need now is insert the wrapper IN BETWEEN "decorator" and > "content" layer.
> Remember that the decorator template expects to output $slots > ['content']. Hence, we actually have to modify the "content" layer to > display the wrapper template (LiveSuccess.wrapper.php), and then > prepend another "inner" layer (LiveSuccess.php) to the layer list. We > do it like this, in the view:
> public function executeHtml(AgaviRequestDataHolder $rd) > { > // remember: first, you should have a base view and call a method on > the parent to load the layout instead of doing it here. > // second, loadLayout() would be enough since "standard" is the > default layer > $this->loadLayout('standard');
> // we get the "content" layer and change the template to the wrapper > $c = $this->getLayer('content'); > $c->setTemplate('LiveSuccess.wrapper');
> // and then prepend the actual "inner" content template to the list > $i = $this->prependLayer($this->createLayer > ('AgaviFileTemplateLayer', 'inner')); > $i->setTemplate('LiveSuccess'); > }
> IMPORTANT: Some advice regarding base views. In the original email, I > recommended that you always have a base view you extend from where > execute() throws an exception. This view would also have base methods > you can call that load layers, maybe set dynamic slots, or do stuff > like: > $this->setAttribute('_contentType', $this->container->getOutputType()- >> getParameter('Content-Type'));
> However, there is a problem here. The whole point of having execute() > throw an exception is that if there is a request using, say, ajax, > that sets the output type to, say, "json" because of a routing rule > like this: > <route pattern="^XMLHttpRequest$" source="_SERVER > [HTTP_X_REQUESTED_WITH]" stop="false" output_type="json" />
> And you then implement a base executeJson() in the base view... all > of your views DO serve the json output type, and the normal execute() > is never called, not even for actions/views that don't implement ajax > features and should thus get an exception!
> To solve that problem, name these base methods differently. I suggest > you call them "setupHtml", "setupJson" and so on, and then call them > using $this->setupHtml($rd); etc inside your concrete view's > executeHtml() method.
Discussion subject changed to "Advanced Layout/Layers example (was: Re:IMPORTANT: Breaking changes in 0.11 branch: tons of new features!)" by Van Daele, Koen
The new layout stuff works really well. I've just converted my app to using the layouts, took a bit of getting used to it, but it wasn't to difficult. And now I don't need to set up the decorator manually in the base view anymore. I've made the default view have a method like this:
public function executeHtml(AgaviRequestDataHolder $rd) { $this->setupHtml($rd);
}
For simple views that just serve up a template without doing anyting else this means I don't even need to override the executeHtml() method anymore.
I have set up the setupHtml method so that it takes as an optional parameter the name of a layout, that way I can do setupHtml($rd, 'notDecorated') to have a view that renders without decoration (e.g. a view that needs to be inserted in some other slot) while the setupHtml($rd) automatically decorates with the standard site template. And I have a third layout that just wraps some simple layout around a template for pages like Unavailable.php.
The setting of slots for an inner layer is also very helpful. Something I've been waiting for a long time.
I haven't encountered any problems so far. One thing I do keep on running into is agavi telling me that a config file is not valid, but only giving back a very general error-message (Element Content is invalid) that doesn't really help in debugging. Maybe this is only a problem now because the config-file format has changed a few times. Is it possible to have more specific errors? It might also be useful to add the different parameters to xsd for validation since right now you don't get a warning if you've used a wrong parameter.
> -----Oorspronkelijk bericht----- > Van: users-boun...@lists.agavi.org > [mailto:users-boun...@lists.agavi.org] Namens David Zülke > Verzonden: dinsdag 30 januari 2007 10:32 > Aan: Agavi Users Mailing List > CC: Agavi Dev Mailing List > Onderwerp: Re: [Agavi-Users] Advanced Layout/Layers example > (was: Re:IMPORTANT: Breaking changes in 0.11 branch: tons of > new features!)
> As of http://trac.agavi.org/changeset/1609, the output of the > inner layer is available via the variable $inner in > templates. Also available are $container for the current > execution container and $view for the template's view. Like > with factories, the names of these assigns ("inner", > "container" and "view") can be configured using the "assigns" > parameter of a renderer.
> Since the wrapping template now does not have to know the > inner layer's name anymore, the example can be simplified quite a bit.
> And the code that makes it happen: > public function executeHtml(AgaviRequestDataHolder $rd) { > // remember: first, you should have a base view and > call a method on the parent to load the layout instead of > doing it here. > // second, loadLayout() would be enough since > "standard" is the default layout > $this->loadLayout('standard');
> // now we insert the wrapper template between the > decorator and the actual content layer > $i = $this->appendLayer($this->createLayer > ('AgaviFileTemplateLayer', 'wrapper'), $this->getLayer('content')); > $i->setTemplate('LiveSuccess.wrapper'); > }
> Cheers,
> David
> Am 29.01.2007 um 14:02 schrieb David Zülke:
> > Hi guys,
> > I thought I'd give you a little example of what the new > template layer > > system can do.
> > We participated in the PHP Throwdown > (http://www.phpthrowdown.com) and > > built an IRC bot using Agavi. It's called "Chuckwalla", contains a > > fully fledged IRC library that doesn't suck, has live logs, > cool web > > interfaces etc and will be open sourced soon.
> > One feature is the live logs. You click a channel, and it > uses Ajax to > > refresh the contents, so you can follow the discussion in a > channel. > > So for the first load, we need a full document, and for subsequent > > XMLHttpRequest, we just want the new messages.
> > Here is the layout's configuration for output type "html": > > <layout name="standard"> > > <layers> > > <layer name="content" class="AgaviFileTemplateLayer" /> > > <layer name="decorator" class="AgaviFileTemplateLayer"> > > <parameter name="template">Master</parameter> > > </layer> > > </layers> > > </layout>
> > Now we also have an output type for the AJAX calls, called "json". > > It's configured like this: > > <layout name="standard"> > > <layers> > > <layer name="content" class="AgaviFileTemplateLayer" /> > > </layers> > > </layout>
> > But now we have a problem: If an AJAX call is made, we get back the > > entire inner content template, complete with the <divs> and the > > <table>, but we only want the <tbody> to append to the > current table > > (remember, tables can have multiple <tbody> elements).
> > One approach would be to simply have two templates. The > other approach > > would be to split them up and include() the inner portion in the > > non-ajax version, and set the templates differently per output type.
> > However, there also is a third approach. Right now, we have these > > layers: > > +------------------------+ > > | <html> decorator | > > | +--------------------+ | > > | | <table> content | | > > | | with <tbody>, <tr> | | > > | +--------------------+ | > > | </html> | > > +------------------------+
> > What we can do now is split up the template like we would with the > > include() solution: > > +------------------------+ > > | <html> decorator | > > | +--------------------+ | > > | | <table> wrapper | | > > | | +----------------+ | | > > | | | <tbody> inner | | | > > | | | content | | | > > | | +----------------+ | | > > | | </table> | | > > | +--------------------+ | > > | </html> | > > +------------------------+
> > For Ajax, everything is fine now. We'll get the <tbody> > content. But > > for output type "html", the result will be this: > > <html> > > <head><title>logs!</title></head> > > <body> > > <tbody> > > <tr> > > <td class="name">John McClane</td> > > <td class="message">Yippie-kay-yay motherfucker</td> > > <td class="time">03:27</td> > > </tr> > > </tbody> > > </body> > > </html>
> > What we need now is insert the wrapper IN BETWEEN "decorator" and > > "content" layer.
> > Remember that the decorator template expects to output $slots > > ['content']. Hence, we actually have to modify the > "content" layer to > > display the wrapper template (LiveSuccess.wrapper.php), and then > > prepend another "inner" layer (LiveSuccess.php) to the > layer list. We > > do it like this, in the view:
> > public function executeHtml(AgaviRequestDataHolder $rd) { > > // remember: first, you should have a base view and > call a method on > > the parent to load the layout instead of doing it here. > > // second, loadLayout() would be enough since "standard" is the > > default layer > > $this->loadLayout('standard');
> > // we get the "content" layer and change the template > to the wrapper > > $c = $this->getLayer('content'); > > $c->setTemplate('LiveSuccess.wrapper');
> > // and then prepend the actual "inner" content template > to the list > > $i = $this->prependLayer($this->createLayer > > ('AgaviFileTemplateLayer', 'inner')); > > $i->setTemplate('LiveSuccess'); > > }