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

Reply via email to