Argh... "slots_var_name"


Am 25.01.2007 um 14:26 schrieb David Zülke:

> 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 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
>>
>> _______________________________________________
>> Agavi Dev Mailing List
>> [email protected]
>> http://lists.agavi.org/mailman/listinfo/dev
>>
>
>
> _______________________________________________
> Agavi Dev Mailing List
> [email protected]
> http://lists.agavi.org/mailman/listinfo/dev
>


_______________________________________________
Agavi Dev Mailing List
[email protected]
http://lists.agavi.org/mailman/listinfo/dev

Reply via email to