Hello David (and others),

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.

Keep up the good work.

Greetings,
Koen

> -----Oorspronkelijk bericht-----
> Van: [EMAIL PROTECTED] 
> [mailto:[EMAIL PROTECTED] Namens David Zülke
> Verzonden: donderdag 25 januari 2007 13:29
> Aan: [email protected]
> 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 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:
> 
>      $l1 = $this->createLayer('AgaviFileTemplateLayer', 'content');
>      $l1->setTemplate('IndexSuccess');
>      $this->appendLayer($l1);
>      $l2 = $this->createLayer('AgaviFileTemplateLayer', 'decorator');
>      $l2->setTemplate('Master');
>      $this->appendLayer($l2);
> 
> 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.
> 
> But first, let's look at slots and renderers.
> 
> First, slots. You can now set slots on _any_ of these layers:
>      $l2->setSlot('latestproductbox', $this->container-  
> >createExecutionContainer('ZeModule', 
> 'LatestProductWidget')); will set the slot on the decorator 
> layer, and is then available there via 
> $slots['latestproductbox'] (yes, $slots, not $template, more 
> on that later).
> 
> Second, renderers. Let's say you wanted to use Smarty instead 
> of the default renderer for the decorator layer:
>      $l2 = $this->createLayer('AgaviFileTemplateLayer', 
> 'decorator', 'nameofthesmartyrendererasconfiguredinoutput_types.xml');
> 
> Of course, you can also pass in a renderer instance:
>      $r = new AgaviSmartyRenderer();
>      $r->initialize($this->context);
>      $l2 = $this->createLayer('AgaviFileTemplateLayer', 
> 'decorator', $r);
> 
> !!! This means you can easily use renderers everywhere now, 
> let's say in a model to render an email. What's more, you can 
> even use layers, slots etc, by just creating them and 
> rendering each of them. This is a bit of an advanced task, 
> and there might be a helper to assist you with that, but even 
> without it, it's only a couple of lines of code.  
> Let me know if you need any assistance.
> 
> You probably noticed AgaviFileTemplateLayer. That's the name 
> of the layer implementation we want to use. 
> AgaviFileTemplateLayer is a special implementation of 
> AgaviStreamTemplateLayer that allows the use of a directory 
> etc, and is designed for the file system.  
> AgaviStreamTemplateLayer is a generic implementation for PHP 
> streams that allows you to load a template via HTTP, for 
> example, or any other (built-in or userspace) stream wrapper 
> registered with PHP:
>      $l = $this->createLayer('AgaviStreamTemplateLayer', 'asdf');
>      $l->setTemplate('www.myhost.com/thetemplate.php');
>      $l->setScheme('http');
> to load http://www.myhost.com/thetemplate.php, or, even 
> cooler, using an RFC 2397 data stream 
> (http://www.faqs.org/rfcs/rfc2397, http://
> de.php.net/manual/en/wrappers.data.php):
>      $l = $this->createLayer('AgaviStreamTemplateLayer', 'blah');
>      $l->setTemplate('text/plain;base64,SGVsbG8gVGVzdA==');
>      $l->setScheme('data');
> That will result in "Hello Test".
> 
> Note: you can also use setParameter('name', 'value') or setParameters
> (array(...)) instead of setWhatever('value').
> 
> Back to our original example:
>      $l1 = $this->createLayer('AgaviFileTemplateLayer', 'content');
>      $l1->setTemplate('IndexSuccess');
>      $this->appendLayer($l1);
> 
> setTemplate() isn't necessary here, because createLayer() 
> sets the following parameters on a layer by default:
> - "template" => the name of the current view
> - "module" => the name of the current view's module
> - "output_type" => the name of the output type
> - "name" => the name of the layer ("content", "decorator" and so on).
> 
> Now it's getting interesting: there's a parameter called 
> "targets", which holds a list of formatting patterns used for 
> looking up the template resource name. For 
> AgaviFileTemplateLayer, the default target pattern is:
>      ${directory}/${template}${extension}
> Where ${extension} is the extension you have set, or, if none 
> was set, the default extension of the renderer (e.g. ".php").
> ${directory} is a pattern itself:
>      %core.module_dir%/${module}/templates
> It's a bit complicated, but I hope you get the idea - the 
> effect is that the default directory is 
> app/modues/${module}/templates, and $ {module} is resolved to 
> the current module, because that is set by createLayer(). Now 
> you can easily set a different directory for the template of a layer:
>      $l->setDirectory('/the/template/dir'); (and yes, you 
> guessed it, you could also do $l->setParameter('directory', 
> '/the/template/dir');)
> 
> Of course, you can again use variables that will be expanded. 
> This variable can be _any_ parameter set on the layer:
>      $l->setDirectory(AgaviConfig::get('core.template_dir') . 
> '/$ {module}/${output_type}); would look for the template in 
> app/templates/Spam/html provided that the current module is 
> "Spam" and the output type is "html".
> 
> IMPORTANT: Variables are expanded just in time, not as you 
> set them, which means you can set a string containing a 
> variable, and change that variable after that. Also, as an 
> alternative to ${foo} you can also use {$foo} and $foo, the 
> syntax works just like with PHP vars.
> 
> NOTE: if i18n is enabled, there are two more default targets for
> FileTemplateLayer:
>      ${directory}/${locale}/${template}${extension}
>      ${directory}/${template}.${locale}${extension}
> 
> You can now easily implement your own template layer class 
> that loads the template from a database, and then caches it 
> into a file and returns that file as the resource name. Or, 
> alternatively, you can of course write a PHP stream wrapper 
> that interfaces with your database and simply use the 
> StreamTemplateLayer. Or you read from memcached.  
> The possibilities are endless.
> 
> But we already established that doing all this in code is 
> rather annoying. That's why you can define layers into 
> layouts, and then load a layout in your view. This is done in 
> output_types.xml, per
> <output_type>:
> 
>      <layouts default="standard">
>        <layout name="standard">
>          <layers>
>            <layer name="content" class="AgaviFileTemplateLayer" />
>            <layer name="decorator" class="AgaviFileTemplateLayer">
>              <parameters>
>                <parameter name="template">Master</parameter>
>              </parameters>
>            </layer>
>          </layers>
>        </layout>
>      </layouts>
> 
> $this->loadLayout() in a view will then load the default 
> layout "standard" unless you give it the name of a layout to 
> load as the first argument. The method returns an array of 
> parameters set for the layout, that might come in handy if 
> you need to carry any further information from the definition 
> into the view.
> 
> Each <layer> accepts the following attributes:
> - "name": name of the layer
> - "class": the name of the AgaviTemplateLayer implementation to use
> - "renderer": the optional name of a renderer to use if you 
> don't want to use the default renderer
> 
> IMPORTANT: you can now define multiple renderers per output type.  
> Each renderer must now have a name, and the <renderers> 
> element must define a default renderer.
> 
> Of course, you can do $this->getLayer('zename'); to grab a 
> layer after calling loadLayout()!
> 
> Also, you can configure slots for a layer by putting them 
> into a <slots> element which (obviously) goes into the 
> <layer> element:
> 
>      <slot name="loginbox" module="Default" action="LoginBox" />
> 
> Each <slot> accepts the following attributes:
> - "name": the name of the slot
> - "module": the module of the action
> - "action": the name of the action
> - "output_type": an optional output type to use for the slot, 
> else the current container's output type will be used.
> 
> You can also place <parameters> into a <slot> to pass these 
> parameters to the action as additional request information.
> 
> To manually set a slot in the code, use $layer->setSlot(). Of 
> course, again, you can modify slots, for instance:
> 
>      $this->loadLayout();
>      $this->getLayer('decorator')->getSlot('loginbox')->getRequestData
> ()->setParameter('foo', 'bar');
> 
> and so on.
> 
> 
> A general word on views, base views and base actions
> 
> It is strongly recommended that you _always_ use a 
> CustomBaseAction that all your actions extend, even if it is 
> empty. Same goes for the view, use a CustomBaseView. You can 
> have these autoloaded per-module using a module autoload.xml, 
> that keeps things tidy. The sample app does this, look at it 
> for an example.
> 
> The main reason why you should do this is because you can 
> easily inject code into all actions and all views at a later 
> time. Do it.  
> Even if the classes are empty. Do it. Always. The manual has 
> instructions on how to use custom build templates so "agavi action"  
> always generates actions and views that extend from your base classes.
> 
> Also, you should NEVER use execute() in views! Always, 
> always, always use executeHtml(), executeRss() and so on. 
> Then, in your base view, implement a "public final function 
> execute()" that forwards to the
> 404 action or so. There is a very simple reason for this: if 
> the output type that is set isn't handled by your view, 
> nothing breaks!  
> For instance, you might have this route at the top of your 
> routing.xml:
> 
> <route pattern="text/javascript" source="_SERVER[HTTP_ACCEPT]"  
> output_type="json" />
> that would set the output type to "json" when an ajax request 
> comes in (prototype sends this Accept: header, for example), 
> or <route name="rss" pattern="/rss$" cut="true" stop="false"  
> output_type="rss" />
> to set the output type to "rss" when a URL ends on /rss.
> 
> But imagine what happens if the view doesn't implement the "rss"  
> output type! Things break horribly. Therefor, always use a 
> specific execute method, and in your execute(), handle the 
> situation. Instead of forwarding to 404, you could also check 
> the output type and set a response accordingly, like the HTTP 
> 407 Not Implemented code. Or throw an exception and handle it 
> in the exception template (remember, these can be per output 
> type, too). But do it right!
> 
> 
> And finally: request data holders.
> 
> You already know the parameter holder passed to action and view
> execute() methods. This is now an AgaviRequestDataHolder (typically
> AgaviWebRequestDataHolder) object instead which also holds 
> information other than parameters, like cookies, files and headers.  
> they all pass validation too inthe exactly same manner as parameters:
> 
> $rd->getHeader('User-Agent'); // or 'USER_AGENT' or 
> 'user-agent' etc, doesn't matter
> 
> $rd->getCookie('cookiename');
> 
> Also, there's no getFileName, getFileError etc anymore. Instead,
> getFile() returns an AgaviUploadedFile object, which holds all this
> information:
> 
> $file = $rd->getFile('foo[bar]');
> $file->getSize();
> $file->hasError();
> $file->move('/path/to/destination');
> 
> The global request data holder is copied into each execution 
> container when that container is executed. You can access it 
> via $context->getRequest->getRequestData(). As before, this 
> cannot be done while inside an action's or view's execute() 
> method to encourage people to use validated request data only.
> 
> 
> Hope that helps. Let me know if there are any questions. 
> Comments and feedback welcome :)
> 
> Yours,
> 
> David
> 
> _______________________________________________
> users mailing list
> [email protected]
> http://lists.agavi.org/mailman/listinfo/users
> 

_______________________________________________
users mailing list
[email protected]
http://lists.agavi.org/mailman/listinfo/users

Reply via email to