Frequently Asked Questions (FAQ) - Agavi PHP Framework

I uploaded this document to github. You can find it under http://github.com/graste/Agavi-FAQ.

This unofficial FAQ is a work in progress. All information is provided as is without any guarantees. Use it at your own risk. Oh, and beware of that yellow fade animated gif in the background. Instant eye cancer. ;-)

On this page a few common questions are answered. Examples are given where appropriate. If you have questions that are not answered here, visit the user mailing list, join the IRC channel or search through mailing list archive or IRC channel logs as a start. The Agavi source code is well documented and the sample application should give you some good practices to start with. In case you want a question added here, ask via mail (agavi-faq@mivesto.de) or visit the IRC channel (ask graste).

The examples in this FAQ are skeletons most of the time. It is recommended to be strict when checking user input such as header variables, cookies, files or request parameters. The validator examples are just there to get you started with some features. The usage of fragment caching may lead to side effects if you define caches only for production environment and then begin to wonder why some code or slot or whatever doesn't get executed as it always was before. Please think twice before copying and pasting example code (which is not production ready on itself most of the time).

Most good practices or patterns are probably to be found in the sample application or the official Agavi quick start tutorial.

With Agavi you can have different contexts (think 'web', 'console' or 'soap') and multiple environments (like 'development', 'staging', 'production' etc.). The <ae:configuration> sections in the config files are sorted first (vanilla, environment-only, context-only, environment+context) and then merged together. Knowing this, you can just define default settings and then overwrite them in context- or environment-specific sections. Some examples below make use of this. You may use regular expressions like this in the attribute: environment="development.*". This would allow for common settings for all environments that start with development (so John and Chuck from your dev-team get those settings in their private development-john-rambo or development-chuck-norris configuration sections).

General topics

  1. Is there a way to change the action method mappings of HTTP verbs (e.g. swap PUT/POST for RESTful applications)?
  2. Is there any way to set a template file extension per output type?
  3. What's that isSimple() method all about (in an action)?
  4. XInclude? RelaxNG? Schema? Namespaces? How can I make use of that?
  5. How do I return XML to the client (like in an Atom/RSS newsfeed)?
  6. How do I implement AJAX- or JSON-related solutions using Agavi?
  7. How to set different exception templates (e.g. for AJAX requests)?
  8. Where to store application settings and how to access them?
  9. How to set custom database connection properties (e.g. for Propel or Doctrine)?
  10. How to get HTTP_* variables?
  11. What about cookies and how can I get/set/remove them?
  12. How to do forwards and redirects?
  13. How to return HTTP status codes?
  14. Where can I change the session settings?
  15. Fast! I need an example of slots and how to use them!
  16. How to load different layouts?
  17. How to re-use input templates to display errors?
  18. How to load another master template?
  19. What is the Agavi startup sequence?
  20. What do I need to know about Agavi models and how to use them?
  21. How to use custom configuration files?
  22. How to use Propel ORM models with Agavi?
  23. How to change the template lookup patterns Agavi uses?
  24. How to use different renderers for different layers of output types?
  25. Is it possible to change the directory structure of a module?

Routing related topics

  1. What do I have to know about Agavi's routing system?
  2. How to define default values for routing parameters?
  3. How to use those curly braces for min/max quantifiers?
  4. How to make route pattern matching case-insensitive?
  5. What additional options are there to be specified when generating routes?
  6. How to determine the current matched routes?
  7. What are routing callbacks and how can I use them?
  8. How do I generate routes with different output types?

Validation related topics

  1. What are the validation modes and what do I need to know about them?
  2. Is it possible to define nested validators?
  3. What is the name of a validator for?
  4. How to prevent performing other validations on a field in case one validator fails?
  5. Can validators depend on other validators?
  6. Can the "depends" attribute receive more than one dependency?
  7. Is there a possibility to register validators manually in an action?
  8. Is there any example of custom file validations or the AgaviImageFileValidator?
  9. How can I repopulate readonly/disabled input fields?
  10. How to manually output error messages in a view?
  11. Is there a better way than using isSet or isNotEmpty to validate route parameters?
  12. How do I use the inarray validator?
  13. How do I validate array input?
  14. Is there an example for the AgaviDateTimeValidator?
  15. How do I check validation results and retrieve the incidents of a failed validator?
  16. How to handle checkbox input fields?
  17. I'm writing the same validation code over and over again, which doesn't feel very DRY. How to solve this?
  18. How to write a custom validator?
  19. How to register shortnames for validators and why should I use them?

Caching related topics

  1. Is it possible to (globally) enable or disable fragment caching?
  2. What are caching groups and how are they used for fragment caching?
  3. Which source attribute values are available and what do they do?
  4. Any tips before starting to use fragment caching intensively?

Translation related topics

  1. Quickstart
  2. How do I use and customize a currency format?

Filter related topics

  1. What kind of filters are available by default and how to configure them?
  2. tbd (FormPopulationFilter tipps)
  3. What's the execution order of filters?

Testing related topics

  1. How do I start?

Misc topics

  1. How do I get improved code completion in Netbeans or Eclipse or $favoriteIDE?
  2. How do I set an exit code in console context or shell applications?

General topics

Is there a way to change the action method mappings of HTTP verbs (e.g. swap PUT/POST for RESTful applications)?
By default Agavi maps executeCreate() with HTTP PUT, executeWrite() with HTTP POST, executeRead() with HTTP GET and executeRemove() with HTTP DELETE. If you want to change these mappings modify the app/config/factories.xml file and add the following:
<request class="AgaviWebRequest">
    <ae:parameter name="method_names">
        <ae:parameter name="POST">create<ae:parameter>
        <ae:parameter name="GET">read<ae:parameter>
        <ae:parameter name="PUT">write<ae:parameter>
        <ae:parameter name="DELETE">remove<ae:parameter>
    <ae:parameter>
<request>
The above mappings are the default ones defined by Agavi. Please note, that you may use any arbitrary request method you want to support.

To support uncommon or custom request methods use a specific execute[Requestmethod]() in your actions to react to different specific request methods OR use the execute() method for any request method. Let's say you want your application to react to OPTIONS or HEAD requests: Just create executeOptions() and executeHead() in your action and be done. Remember, that you can only get validated parameters, headers, files and cookies. To test your executeHead()/executeOptions() you may use something like curl -I http://route/to/action or curl -X OPTIONS http://route/to/action.

Is there any way to set a template file extension per output type?
There are multiple possibilities. You can set an extension per individual layer using $this->getLayer('content')->setExtension($whatever); or do this in the config or set the default_extension parameter of the renderer. You could set a <parameter name="extension">.whatever</parameter> to the relevant layer as setExtension() is just a __call() overload for setParameter('.extension').
What's that isSimple() method all about (in an action)?
The isSimple() method determines whether the actions' validate* and execute* methods are called. If isSimple() returns true, action validation and execution are bypassed and the default view (see getDefaultView()) is executed. Note also, that no filters are run (not even the security filter) and no request data is available except for arguments explicitly passed to the container.

If you want to use an action both normal and as a simple slot you can try this:
public function isSimple()
{
    return (bool)$this->getContainer()->getParameter('is_slot'); // only simple when run as slot
}
Remember, that you have to validate parameters given to an action, if that action's not simple (and you only get parameters if there's at least an execute*() method).
XInclude? RelaxNG? Schema? Namespaces? How can I make use of that?
TBD - for some examples see the sample application's output_types.xml file:
<!-- include common layer definitions from the sandbox element -->
<xi:include xpointer="xmlns(ae=http://agavi.org/agavi/config/global/envelope/1.0)
                         xmlns(ot=http://agavi.org/agavi/config/parts/output_types/1.0)
                         xpointer(/ae:configurations/ae:sandbox/ot:layers/*)" />
How do I return XML to the client (like in an Atom/RSS newsfeed)?
".xml" route and xml output type with executeXml() method to return xml document instead of template use something like this as example...
How do I implement AJAX- or JSON-related solutions using Agavi?
similar to the answer above - define route and json output type and then executeJson() method returns json content
How to set different exception templates (e.g. for AJAX requests)?
You can set a different template in your output type definition like this:
<output_type exception_template="...">
Default exception templates and custom ones per context may be set via settings.xml file. Customizations according to the needs of your application are possible per environment, context and output type:
<!-- in settings.xml -->
<exception_templates>
    <!--
        this is the exception template that's used by default
        unless one for a specific context has been defined, or
        for the current output type
    -->
    <!--
        note that exceptions that occur before a context is even
        fully initialized, or an exception that happens before
        the output types are loaded and determined, will use a
        template defined here
    -->
    <exception_template>%core.agavi_dir%/exception/templates/shiny.php</exception_template>
    <!-- an example for per-context exception templates -->
    <!-- per-output-type templates can be set in output_types.xml -->
    <exception_template context="console">%core.agavi_dir%/exception/templates/plaintext.php</exception_template>
</exception_templates>


<!-- in output_types.xml -->
<ae:configuration environment="production.*">
    <output_types default="html">
        <!-- use a different exception template in production environments for HTML -->
        <output_type name="html" exception_template="%core.template_dir%/exceptions/web-html.php" />
    </output_types>
</ae:configuration>
This makes it possibles to have the verbose shiny exception template in development while using simple HTTP 500 status error pages in production.
Where to store application settings and how to access them?
Application wide settings can easily be stored in app/config/settings.xml. You can define different settings and values for all environments and contexts you use.
<ae:configuration> 
    <settings prefix="Blog."> 
        <setting name="enabled">true</setting> 
        <setting name="EntriesPerPage">25</setting> 
        <setting name="Theme">green</setting>
        <setting name="More">
            <ae:parameters>
                <ae:parameter name="foo">bar</ae:parameter>
                <ae:parameter name="blah">blub</ae:parameter>
            </ae:parameters>
        </setting>
    </settings> 
</ae:configuration> 

<ae:configuration environment="production"> 
    <settings prefix="Blog."> 
        <setting name="EntriesPerPage">10</setting> 
    </settings> 
</ae:configuration> 
You then get values like this:
$entries_per_page = AgaviConfig::get('Blog.EntriesPerPage', $default_value_if_not_defined);
Getting the Blog.More settings entry will result in an indexed array you can use.

If you have settings that are specific to various modules you can use each module's module.xml files to store values. You access these settings like this:
AgaviConfig::get('modules.lowercase_module_name.SomeSetting');
Remember, that you can always set values programatically using:
AgaviConfig::set('some.setting', 'some_value'); 
as you can see in you application's index.php or config.php files. Another thing to know about the different settings in different contexts and environments is the following: <ae:configuration> sections are sorted first (vanilla, environment-only, context-only, environment+context) and then merged together. Knowing this you can just define default settings and then overwrite them in context- or environment-specific sections like in the example above.
How to set custom database connection properties (e.g. for Propel or Doctrine)?
You can set additional options to your database connections in your app/config/database.xml file (see that settings parameter):
<ae:configurations xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0"
                      xmlns="http://agavi.org/agavi/config/parts/databases/1.0">
    <ae:configuration environment="development">
        <databases default="db">
            <database name="propel" class="AgaviPropelDatabase">
                <ae:parameter name="config">%core.app_dir%/config/propelproject-conf.php</ae:parameter>
                <ae:parameter name="overrides">
                    <ae:parameter name="connection">
                        <ae:parameter name="dsn">mysql:dbname=icanhazdb;host=127.0.0.1</ae:parameter>
                        <ae:parameter name="user">w00t</ae:parameter>
                        <ae:parameter name="password">not_default_pass</ae:parameter>
                        <ae:parameter name="settings">
                            <ae:parameter name="charset">
                                <ae:parameter name="value">utf8</ae:parameter>
                            </ae:parameter>
                        </ae:parameter>
                    </ae:parameter>
                </ae:parameter>      
            </database>
            <database name="db" class="AgaviDoctrineDatabase">
                <ae:parameters>
                    <ae:parameter name="attributes">
                        <ae:parameters>
                            <ae:parameter name="Doctrine_Core::ATTR_AUTOLOAD_TABLE_CLASSES">true</ae:parameter>                                       
                            <ae:parameter name="Doctrine::ATTR_USE_NATIVE_ENUM">true</ae:parameter>
                            <ae:parameter name="Doctrine_Core::ATTR_VALIDATE">LENGTHS</ae:parameter>
                            <ae:parameter name="Doctrine_Core::ATTR_AUTO_ACCESSOR_OVERRIDE">true</ae:parameter>
                            <ae:parameter name="Doctrine_Core::ATTR_QUOTE_IDENTIFIER">true</ae:parameter>
                        </ae:parameters>
                    </ae:parameter>
                    <ae:parameter name="manager_attributes">
                        <ae:parameters>
                            <ae:parameter name="Doctrine_Core::ATTR_MODEL_LOADING">2</ae:parameter>
                        </ae:parameters>
                    </ae:parameter>
                    <ae:parameter name="load_models">%core.lib_dir%/orm</ae:parameter>
                    <ae:parameter name="charset">UTF8</ae:parameter>
                </ae:parameters>
            </database>
        </databases>
    </ae:configuration>
    <!--
        Simple example on how to include e.g. more sensitive database settings from an external file that may have been
        generated on deployment or similar and contains only things like hostname, db name, port, username and password.
    -->
    <xi:include href="%core.app_dir%/../etc/local.databases.xml" xpointer="xmlns(ae=http://agavi.org/agavi/config/global/envelope/1.0) xpointer(/ae:configurations/*)">
    </xi:include>
</ae:configurations>
This example's taken from DracoBlue's blog entry and extended by a bit of XInclude magic and Doctrine settings.
How to get HTTP_* variables?
The AgaviWebRequestDataHolder defines headers, files and cookies as sources (apart from GET/POST parameters, which you simply access by validating them and then using $rd->getParameter('name', $default)).
The headers source contains all HTTP_* variables you might want to use. The only thing between you and your values is strict validation, as these settings are user provided request data and therefore unvalidated input. Here is an example how to access header variables:
// note the stripped HTTP_ prefix and the UPPERCASE characters:
$rd->get('headers','USER_AGENT'); // AgaviRequestDataHolder class
$rd->getHeader('User-Agent'); // AgaviWebRequestDataHolder class
You validate them by using the source parameter in your validator specification:
<validator ... source="headers">
    <argument>USER_AGENT</argument>
</validator>
<-- or perhaps for referer checking: -->
<validator ... source="headers">
    <-- you could e.g. check for a valid (and/or allowed) URL, the non-existance
        of Javascript/HTML/SQL/XPath/whatever code snippets etc. -->
    <argument>REFERER</argument>
</validator>
We urge you to be _really_ strict when checking user input such as header variables, cookies, files or parameters. It is _not_ proficient to just use a simple string validator as people could send you Javascript, SQL or whatever malicious input data not only using parameters, but instead trying (sometimes delayed) attacks using cookies and headers. The next time you use plain SQL and someone surfs on your page using a referer set to ' OR 1=1 -- "|# you will definitively be thankful for strict validation and _good_ validation rules in your validators.

Please note, that you can access $_SERVER (like $_SERVER['SERVER_NAME']) and $_ENV directly, as it is mostly not user provided data (and not really standardized as well).

To give you a very simple example of a custom validator, that exports an IS_AJAX header parameter to your action if the current request was issued via Javascript's XmlHttpRequest method, see the following snippets:
<!-- validate.xml -->
<validator class="SearchHttpXRequestedWithValidator" name="no_ajax_submit" required="false" provides="search[submit_search]" source="headers">
    <arguments>
        <argument>X_REQUESTED_WITH</argument>
    </arguments>
    <ae:parameters>
        <ae:parameter name="export">IS_AJAX</ae:parameter>
    </ae:parameters>
</validator>
and
/* SearchHttpXRequestedWithValidator.class.php */
class SearchHttpXRequestedWithValidator extends AgaviStringValidator 
{
    protected function validate()
    {
        $argument = $this->getArgument();
        $value = $this->getData($argument);
        $this->export(false !== stristr($value, 'XMLHttpRequest'));
        return true;
    }
    
    protected function checkAllArgumentsSet($throwError = true)
    {
        return true; // see AgaviValidator::checkAllArgumentsSet(); used to export default value in case of missing parameter
    }
}
Just add the custom SearchHttpXRequestedWithValidator to an autoload.xml, use it in a validation XML file and ask for AJAX requests in your action's execute*() method (or wherever) like this:
$this->is_ajax_request = $rd->getHeader('IS_AJAX', false);
What about cookies and how can I get/set/remove them?
Example how to set a cookie in a view:
$this->getResponse()->setCookie('cookieName', $value);
$this->getResponse()->setCookie('cookieName', $value, '+14 days'); // notice the hot strtotime syntax
If you want to read cookies, you have to validate them as they are user provided input data. Other cookie related methods can be found in AgaviWebResponse. To remove a cookie just call unsetCookie($name, $path, $domain, $secure, $httponly) with the same parameters you used for setting the cookie. To get a cookie value simply call getCookie($name).
How to do forwards and redirects?
You can either forward internally to another action or do a full redirect to another absolute URL (e.g. redirect-after-post pattern):
public function executeHtml(AgaviRequestDataHolder $rd)
{
    // ... some code ...

    if (some_constraint)
    {    
        // redirect to a specific URL:
        $this->getContainer()->getResponse()->setRedirect($absolute_target_url);
    
        // or redirect like this ('relative'=>false may be omitted as Agavi sets it automatically for you)
        $this->getResponse()->setRedirect($this->getContext()->getRouting()->gen('route.to.somewhere', $params_array, array('relative' => false)));
        return; // no false or AgaviView::NONE here
    }
    
    // or forward to another action (the default 404 page in this case):
    return $this->createForwardContainer(AgaviConfig::get('actions.error_404_module'), AgaviConfig::get('actions.error_404_action'));
}
When forwarding, the output type et cetera should be respected. If you want to set these things explicitly, use the three optional parameters $arguments, $outputType and $requestMethod.
Forwarding and redirecting only works inside views in Agavi. This is due to the fact, that redirecting and forwarding works differently for each output type. E.g. HTTP redirects in HTML based (web-) applications aren't available in SOAP or XML-RPC contexts. As your actions are not supposed to know, which output type they support (now or in the future), you do all your redirects and forwards in views.
How to return HTTP status codes?
Just do something like this in your view:
$this->getResponse()->setHttpStatusCode('301'); // 301 moved permanently
$this->getResponse()->setHttpStatusCode('304'); // 304 not modified
$this->getResponse()->setHttpStatusCode('402'); // 402 payment required ;-)
$this->getResponse()->setHttpStatusCode('404'); // 404 not found
$this->getResponse()->setHttpStatusCode('405'); // 405 method not allowed
$this->getResponse()->setHttpStatusCode('500'); // 500 internal server error
// etc. pp., you probably know all those HTTP codes...
Where can I change the session settings?
By default Agavi uses the session settings of your PHP installation. The default name of the Agavi session cookie is Agavi. You can override the PHP defaults by adding parameters to the <storage> element in app/config/factories.xml.
<storage class="AgaviSessionStorage">
    <ae:parameter name="session_save_path">some path</ae:parameter> <!-- current directory used for session data storage -->
    <ae:parameter name="session_cookie_lifetime">10800</ae:parameter> <!-- lifetime of the session cookie in seconds -->
    <ae:parameter name="session_cookie_path">/</ae:parameter> <!-- path on the domain (single slash for all paths) -->
    <ae:parameter name="session_cookie_domain">www.example.com</ae:parameter> <!-- cookie domain (be careful with leading '.' as cookies
                                                                                               are then visible to all subdomains) -->
    <ae:parameter name="session_cookie_secure">true</ae:parameter> <!-- true for secure connection cookie only -->
    <ae:parameter name="session_cookie_httponly">true</ae:parameter> <!-- set httponly flag (beware of XMLHttpRequest issues
                     - otherwise it's not a bad measure (even though it doesn't protect you e. g. from HTTP TRACE attacks or the like) -->

    <!-- for other parameters see AgaviSessionStorage file -->
</storage>
Fast! I need an example of slots and how to use them!
Slots are a decent solution to reuse existing functionality. You may either define slots in app/config/output_types.xml (to include them in the master template) or create/delete slot containers dynamically "on the fly" in your views. Defining slots inside <layer> definitions in output_types.xml could look like this:
<slots>
    <slot name="header" module="Default" action="Header" />
    <slot name="navigation" module="Default" action="Navigation.Main" />
        <ae:parameter name="page">%core.template_dir%/static/page.html</ae:parameter>
    </slot>
    <slot name="footer" module="Default" action="Footer"/>
</slots>
To be able to use the given parameters in a slot you have to validate them. If you want to create slot containers in a view, try it like this:
// in view:
$this->getLayer('content')->setSlot('slotName', $this->createSlotContainer(
            'ModuleName',               // name of module to use
            'Some.AwesomeActionName',   // name of action to execute
            array('param' => 'value'),  // parameters to pass to the slot
            'html',                     // output type to use
            'write'                     // request method to use
        ));

// in template:
if (!empty($slots['slotName'])
{
    echo $slots['slotName'];
}
You don't have to pass parameters explicitly to the slot, as it will have a copy of all the current request parameters. The output type and request method parameters are optional, too. To give you an impression on how flexible Agavi's output type, layer and slot handling is, see this:
// to change the action of a specific slot (you can set the module name as well)
$this->getLayer('content')->getSlot('slotName')->setActionName('SomeNewActionName');

// create and append layer when you need one in a view
$this->appendLayer($this->createLayer('AgaviFileTemplateLayer', 'content'));

Slots are rendered after the main action/view has run, but before the main template is rendered. Decorators are rendered after that. This makes it possible to change the layout to be used in the main view and add and remove slots dynamically as you like. Additionally you can set request attributes for later use in the decorator slots.
How to load different layouts?
You may define different layouts in your app/config/output_types.xml and then choose which layout to load in your views like this:
$this->setupHtml($rd, 'layoutName');
Imagine you have some modules and each module has multiple (nested) actions. You then can easily override setupHtml in each module's BaseView class and set a default layout name for that module (so different sections of your homepage could be organized into modules with different layouts/designs all over).
<!-- app/config/output_types.xml example part: -->
<layout name="default">
    <layers>
        <layer name="content"/>
        <layer name="decorator">
            <slots>
                <slot name="header" module="Default" action="Header" />
                <slot name="navigation" module="Default" action="Navigation" />
                <slot name="footer" module="Default" action="Footer"/>
            </slots>
            <ae:parameters>
                <parameter name="template">%core.template_dir%/decorators/DefaultMaster</ae:parameter>
            </ae:parameters>
        </layer>
    </layers>
</layout>

<layout name="another">
    <layers>
        <!-- xinclude layer definitions from default layout (should use better xpath expression and 1.0 style namespaces...) -->
        <xi:include xpointer="xmlns(agavi=http://agavi.org/agavi/1.0/config)
                    xpointer(//agavi:output_type[@name='html']/agavi:layouts/agavi:layout[@name='default']/agavi:layers/*)" />
    </layers>
</layout>
How to re-use input templates to display errors?
Without knowing the specific requirements of your application it is usually a good practice to have a setup of three views for your input form handling action: SuccessView, ErrorView and InputView. You would appoint each of those views depending on the state or the validation results. "Input" is used to display the empty form, "Error" is used in case any validation or other errors occur and "Success" will be appointed after handling data successfully. You may use the Agavi action wizard to create the necessary files for you:
$ dev/bin/agavi action-wizard
Module name: Posts
Action name: Add
Space-separated list of views to create for Add [Success]: Input Success Error
Your code (pseudo-style) could then look something like this:
// in the input handling action (e.g. Default_AddFoobarAction):
    public function executeWrite(AgaviRequestDataHolder $rd)
    {
        // ... some code ... update via models etc.
        if ($errors_while_trying_to_do_something_useful)
        {
            return 'Error';
        }
        return 'Success';
    }
    
    public function handleError() // could be handleWriteError or similar
    {
        // error handling like (p)re-populating the form
        return 'Error';
    }
    
    public function getDefaultViewName()
    {
        // by default show the input template
        return 'Input';
    }

// in the input and success views:
    public function executeHtml(AgaviRequestDataHolder $rd)
    {
        // set some attributes etc. for your template
    }
    
// in the error view
    public function executeHtml(AgaviRequestDataHolder $rd)
    {
        // re-use the input template for presenting the errors in the form  
        $this->getLayer('content')->setTemplate('Input');
    }
By re-using the input template in your error view you can avoid duplicating code without sacrificing later maintainability. You shouldn't go for having less and less files in these cases, as you'll notice later on, that you need to add views and templates when you add other output types. Without proper structuring you would need to write a lot of unmaintainable spaghetti code.
A common code smell when using Agavi would be too large classes/methods for your actions and views. It's pretty much time for refactoring when your files grow up to hundreds of lines. Try to make better use of the built-in validators or write custom validators to save on unnecessary validation logic in your actions. Use the FormPopulationFilter to pre- and re-populate your forms. Outsource code to different AgaviModel classes. Strive to have lean actions/views/templates by hiding business logic in those (re-usable!) model classes. Prevent duplicated code in templates and views by creating simple 'wizard like' actions to display given data. See the FAQ entries about slots and simple actions for further ideas.
How to load another master template?
Setting another master template is the same as setting a normal template to use. Just use the apropriate layer (decorator in this case) in the view:
$this->getContext()->getLayer('decorator')->setTemplate(AgaviConfig::get('core.templates').'/some_other_master');
How to change layouts using output_types.xml is described under How to load different layouts?.
What is the Agavi startup sequence?
Agavi starts something like that:
  1. TranslationManager
  2. User
  3. Request (init)
  4. Routing (init)
  5. Request (startup)
  6. Routing (startup)
  7. Controller
For used classes and startup sequence e. g. see the AgaviFactoryConfigHandler source file. The shutdown sequence is in reverse. So controller is shut down first, then request, routing, user, translation manager and storage.
The startup sequence is important, as this means, that you are able to e.g. access the user in routing and validation if you need it. Usually $_GET etc. are unset in AgaviRequest::startup() and/or AgaviWebRequest::startup(), but with the above startup sequence you are able to register $_GET etc. as a routing source in init. To disable certain functionalities of Agavi just look at settings.xml:
<setting name="use_database">true</setting>
<setting name="use_logging">true</setting>
<setting name="use_security">false</setting>
<setting name="use_translation">true</setting>
To change the implementing classes have a look at the factories.xml file. Please be careful, when you enable or disable features in settings, as you won't be able to access certain features. For example the AgaviDateTimeValidator needs the translation manager to work correctly.
In case you want to disable the logging manager you should better get used to write your logging code like this:
if (AgaviConfig::get('core.use_logging', false))
{
    // do your logging...
}
What do I need to know about Agavi models and how to use them?
In patterns such as MVC, data models are widely used to deal with the "business logic" of an application. The way you do this, or even, what patterns to integrate is completely up to you. Nonetheless, Agavi models are extremely useful for a good number of reasons, so you'll probably want to use/extend them with your classes. The AgaviModel class creates a bridge between the business logic, and the rest of the application logic, by simply using Agavi $context internally.
public function initialize(AgaviContext $context, array $parameters = array())
{
	$this->context = $context;
}

Every time you initialize a model a $context parameter is passed, along with an array of custom params. This is the way to do it:

$this->getContext()->getModel('Model_Name', 'Module_Name', array('foo','bar'));

Models are loaded in the way shown above, and additionally, you can skip the second parameter ONLY if the model is not inside any module, but located at the main application's models/ folder (at the lowest level). Bare in mind not to use the word 'Model' when passing the first parameter. It's 'FooBar', not 'FooBarModel'.

Here's a few extra things you need to know about Agavi models:

  • Models are only loaded once.
  • Models do not need to be included in autoload.xml, as long as they are under the models folder ( app/models/ ).
  • Models implement __sleep and __wakeup methods, therefore they are easily transform into serialized objects for caching and other common uses.
  • When using getModel(), a model's constructor is called as well (if the model has one) which might lead into some confusion. So, if needed you may write both, a constructor and a initialize method. Only the later will have access to the context though.
How to use custom configuration files?
There may always be situations where you need complex configuration settings for certain taks, that you don't want to add to the default settings.xml file or include via PHP, YAML, INI files or whatever other formats you like. Agavi gives you a very convenient way to use custom configs and have them validated and cached for further requests like normal Agavi config files. Just start by defining a new config handler in your app's config_handlers.xml file:
<handler pattern="%core.config_dir%/project/foo/*.xml" class="AgaviReturnArrayConfigHandler" />
This line tells Agavi to use the built-in (yet deprecated) AgaviReturnArrayConfigHandler class for all XML files to be found in the app/config/project/foo/ directory.
To use the cached config file in your actions, views, models or other classes do something like:
$config = include AgaviConfigCache::checkConfig(AgaviConfig::get('path_to_one_of_the_xml_configfiles'));
The above defined handler parses the XML file and returns an associated array of all defined tags and values for easy access. You may use the config like this:
if (!isset($config['some']))
{
    throw new LogicException('element "some" missing!');
}

foreach ($config['some']['bar'] as $foo => $data)
{
    $this->doSomethingWithEach($foo, $data);
}
The XML file for the above pseudo code could be like that:
<?xml version="1.0" encoding="UTF-8"?>
<some>
    <baz>asdf</baz>
    <foo>-1</foo>
    <bar>
        <some>...more data and xml...</some>
        <more>...more data and xml...</more>
        <deep>...more data and xml...</deep>
        <structs>...more data and xml...</structs>
    </bar>
</some>
To be able to write code without all the validational isset() etc. statements you may validate your XML structure against a well defined XML schema definition (XSD) and use your own custom config handlers. Create a schema file, define and create your config handler class and add it to autoload.xml:
class FooDefinitionConfigHandler extends AgaviXmlConfigHandler
{
    // have a look at AgaviXmlConfigHandler and AgaviIXmlConfigHandler
    // and then
    // implement necessary methods and validate your XML structure...
}
<handler pattern="%core.config_dir%/project/foo/*.foo.xml" class="FooDefinitionConfigHandler">
    <validation>%core.config_dir%/xsd/foo_definition.xsd</validation/>
</handler>
For examples of XSDs and custom config handler classes just have a look at the Agavi source code. In case you don't want to use Agavi's XML parser, you'll need to resolve parents, filter content and order <configuration> blocks by contexts and environments all by yourself (see AgaviXmlConfigParser). For further information and if you wonder what's possible, please head to the API docs or ask the devs. To quote the configuration section of the Agavi 1.0 RELEASE_NOTES file extensively:

Agavi now supports any number of XML Schema (including XML Schema Instance declarations), RELAX NG or Schematron files to be validated against config files or merged result documents in several different stages, can apply XSL transformations (including those declared in PIs) and has extended namespaces support that allow backwards-compatibility to old config files in future versions. New-style configuration handlers extending AgaviXmlConfigHandler are now given a single AgaviXmlConfigDomDocument (extends DOMDocument) instance to work on, which already holds only the relevant <configuration> blocks in the correct order. Together with other subclasses of ext/dom classes, it provides several convenience methods to ease the processing of configuration files, but of course, all capabilities of DOM, including XPath, are usable inside configuration handlers.

A more in-depth article making extensive use of the Agavi custom configuration handlers was written by Luis Merino. See Delivering static files: Helping your app scale using custom configs in Agavi Framework for an introduction about his concept of declaratively specifying which static files (e.g. javascript files or stylesheets) to include for matched routes via a StaticFilesConfigHandler.
How to use Propel models with Agavi?
For a simple explanation on how to include Propel in your own projects see this Blogpost. All you need is Phing which you probably already have thanks to Agavi.
In short: Just get Propel from GitHub as Tarball or via clone and put the generator files into your dev/libs/propel folder while the runtime files may be put into your project's app/lib/vendor/propel directory. Use this distinction when you don't need the generator files in production environments.
After getting Propel run it in your project's directory via dev/libs/propel/bin/propel-gen dev/db. Generate your runtime config files into the dev/db directory via convert-conf command. Your classes will probably be in your dev/db/build directory. The important files in your dev/db folder are schema.xml, runtime-conf.xml and build.properties. Configure your directories similar to this:
#relative to propel-gen script
propel.output.dir = ../../..
propel.php.dir = ${propel.output.dir}/app/lib/vendor/propel
propel.phpconf.dir = ${propel.output.dir}/dev/db/config
propel.sql.dir = ${propel.output.dir}/dev/db/sql
Configure your include path to include the Propel runtime and OM classes.
  $path = '/your/project/libs' . PATH_SEPARATOR . '/your/project/app/lib/vendor/propel';
  set_include_path(get_include_path() . PATH_SEPARATOR . $path);
You may setup your Propel connection in your app/lib/config/database.xml file similar to this:
<database name="propelom" class="AgaviPropelDatabase">
    <ae:parameter name="config">%core.app_dir%/config/your-project-conf.php</ae:parameter>
</database>
You may generate the classmap and conf files to the wanted directory or just copy classmap-your-project-conf.php and your-project-conf.php (from Propel's dev/db/build/conf) to Agavi's app/config directory. You can of course define custom database connection overrides when needed.
Remember to configure your DB connections for all environments and that you have set use_database to true in your app/config/settings.xml.
How to change the template lookup patterns Agavi uses?
You can customize the default pattern Agavi uses to find templates in the filesystem by overriding the targets parameter on a layer in output_types.xml. This enables your application to have different templates and let Agavi choose the first one that matches the given targets. The following snippet gives an example on how one could implement mobile templates that reuse existing actions and views by letting Agavi first look for Success.mobile.php before trying to use Success.php. That way you can have a mobile version of your website where it is not strictly necessary to duplicate all templates for the mobile version as you can reuse the original templates. In case you want to adjust a template for mobile devices you can just put a *.mobile.php file next to the existing normal one. The {extension} placeholder is usually set via the default_extension parameter of the renderer used for the layer.
<layer name="content">
    <parameters>
        <parameter name="targets">                                                                                       
            <parameter>${directory}/${template}.mobile${extension}</parameter>
            <parameter>${directory}/${template}${extension}</parameter>
        </parameter>
    </parameters>
</layer>
How to use different renderers for different layers of output types?
You can set a different renderer for each of the layers you define in output_types.xml or set dynamically within views:
<layer name="user_generated_content" renderer="twig" />
The above feature enables you to define specific layouts with different layers where e.g. all layers are rendered via a AgaviPhpRenderer while the layer that displays sensitive user provided content is rendered using a template engine renderer of your choice that has features such as sandboxing or limited sets of capabilities. The given example uses a renderer named twig (see AgaviTwigRenderer and the Twig homepage for more information) that is usually defined for specific output types. With this knowledge you could do crazy things like XML or Javascript islands within HTML all by defining according layers with appropriate custom renderers that handle escaping or whatever.
The following example defines three renderers. Two of them are provided by Agavi itself while the third one is a custom renderer that DracoBlue and me wrote as a small test to make transitioning an existing Agavi application from using PHP as its templating engine to allowing the use of Twig templates where needed. For refactoring reasons this enables you to start using Twig in templates one template at a time while the fallback will always be the PHP template variant.
<output_type name="html">
    <renderers default="proxy">                                                                                                          
        
        <renderer name="proxy" class="AgaviProxyRenderer">
            <ae:parameter name="renderers">
                <ae:parameter>twig</ae:parameter>
                <ae:parameter>pphp</ae:parameter>
            </ae:parameter>
        </renderer>
            
        <renderer name="pphp" class="ProjectPhpRenderer">
            <ae:parameter name="assigns">
                <!-- default assigns of Agavi omitted -->
            </ae:parameter>
            <ae:parameter name="default_extension">.php</ae:parameter>
            <ae:parameter name="var_name">t</ae:parameter>
        </renderer>
            
        
        <renderer name="twig" class="AgaviTwigRenderer">
            <ae:parameter name="assigns">
                <-- default assigns of Agavi omitted -->
            </ae:parameter>
            <ae:parameter name="default_extension">.twig</ae:parameter>
            <ae:parameter name="extract_vars">true</ae:parameter>
            <ae:parameter name="options">
                <ae:parameter name="auto_escape">true</ae:parameter>
                <ae:parameter name="auto_reload">true</ae:parameter>
            </ae:parameter>
        </renderer>
    </renderers>

    <layouts default="standard">
        <!-- standard layout with a content and a decorator layer -->
        <layout name="standard">
            <layer name="content" />
            <layer name="decorator">
                <ae:parameter name="directory">%core.template_dir%/Html</ae:parameter>
                <ae:parameter name="template">HtmlMaster</ae:parameter>
            </layer>
        </layout>
    </layouts>
</output_type>
You can find the AgaviProxyRenderer source code on Github. It was written and tested with Agavi v1.0.7. If you use the given example the normal Agavi behaviour on template lookup patterns is still working. That way, if you use the targets parameter for layers given in a previous answer, Agavi would try to lookup templates in this order:
  • *.mobile.twig
  • *.twig
  • *.mobile.php
  • *.php
This lookup behaviour does of course also work for the HtmlMaster template specified as a decorator. Please notice the options given to the AgaviTwigRenderer. It sets automatic escaping of all extracted variables (set via setAttribute() in views and enables automatic reloading of modified templates even when the core.debug setting of Agavi is set to false. By default Agavi uses the Twig template caching mechanism when not run in debug mode. For details see the renderer and the Twig docs.
The normal Agavi lookup patterns for locale specific templates like *.de_DE.php or en/*.php were omitted for brevity reasons in the above order listing.
Is it possible to change the directory structure of a module?
Short answer: Yes.
Long answer: You can change the way actions, views, templates and related validation and cache files are layed out per module in each module's module.xml file. Try these settings as an example (taken from Agavi ticket #668):
<setting name="agavi.action.path">%core.module_dir%/${moduleName}/impl/${actionName}/Action.class.php</setting>
<setting name="agavi.cache.path">%core.module_dir%/${moduleName}/impl/${actionName}/cache.xml</setting>
<setting name="agavi.template.directory">%core.module_dir%/${module}/impl/</setting>
<setting name="agavi.validate.path">%core.module_dir%/${moduleName}/impl/${actionName}/validate.xml</setting>
<setting name="agavi.view.path">%core.module_dir%/${moduleName}/impl/${viewName}View.class.php</setting>
<setting name="agavi.view.name">${actionName}/${viewName}</setting>
You can also put this into its own <settings> block and xi:include from elsewhere. Using the above example settings you get the following directory structure:
app                     // application directory
  modules               // contains all modules
    Default             // module name
      config            // contains module specific config files (e.g. module.xml, autoload.xml, config_handlers.xml, validators.xml)
      impl              // implementation directory with all actions (and their related files)
      lib               // should contain module specific base classes for views, actions, models or custom validators etc.
      models            // module specific model classes
All actions, views, templates and cache/validation configs are inside the impl directory in the folder next to each action. You can see fast, if a validation or caching file exists for that action and what views and templates are available for it.
Note that the example (has to) customize the view name, so you now have a slash (sub directory) in the view filename (and an underscore in the class name) between action name and view shortname ('Success', 'Input' etc.)... Example: Default_Sub_Marine_SuccessView instead of Default_Sub_MarineSuccessView (in directory Default/Sub are files like MarineAction.class.php and MarineSuccessView.class.php).
Also note, that using the template lookup pattern where there is a directory per locale does not make much sense with this layout. Keep in mind, that you need to set up the config handlers in your application's or module's config_handlers.xml:
<handlers>
    <handler pattern="%core.module_dir%/*/impl/*/validate.xml" class="AgaviValidatorConfigHandler">
        <validation>%core.agavi_dir%/config/xsd/validators.xsd</validation>
    </handler>
    <handler pattern="%core.module_dir%/*/impl/*/cache.xml" class="AgaviCachingConfigHandler">
        <validation>%core.agavi_dir%/config/xsd/caching.xsd</validation>
    </handler>
<handlers>

See http://trac.agavi.org/ticket/668 or the sample application for examples of the files.
The given new layout is nice to have all files related to an action in one directory, but leads to all the same names all over the place. So having multiple files open in your favorite IDE (Netbeans, anyone?) makes it harder to distinguish files and their according action. As always everything depends on your preferences and requirements. :-)
The Agavi build system up to 1.0 doesn't support these custom filesystem layouts. Versions later than Agavi 1.0.1 support the mentioned layout, but default to using the old layout. When running the action wizard from command line you'll have the choice for each module to use the new format.

Back to the top of this page


Routing related topics

What do I have to know about Agavi's routing system?
For an instant start just have a look at the sample application's app/config/routing.xml file. There are multiple nice things to learn about how to define and nest routes and what additional parameters might prove to be helpful.
The most important thing to remember is, that you have to validate all parameters (including those from routes) to be able to use them in your actions and views (at least in the default strict validation mode).
<!-- matches "/de" or "/en" at the beginning of the URL and uses either value to set the locale,
     with British Pounds Sterling as the currency. Execution will not stop if this route matches.
     If it matches, the matched fragment will be stripped from the input URL for the following routes.
     This route has the "imply" flag, which means that it will be included in generated routes even
     if the gen() call does not explicitly mention it. The matched "language" parameter will not be
     available as a request parameter in the code, because it's "ignore"d -->
<route pattern="^/({locale:[a-z]{2}(_[A-Z]{2})?})" stop="false" imply="true" cut="true" locale="${locale}@currency=GBP">
    <callbacks>
        <callback class="AgaviSampleAppLanguageRoutingCallback" />
    </callbacks>
    <ignores>
        <ignore>locale</ignore>
    </ignores>
</route>
        
<!-- If the HTTP Accept: header contains "application/json" (i.e. if you do an XMLHTTPRequest with one of the usual JS frameworks),
     set the output type to "json". Look at output_types.xml for the declaration of that output type. Execution will not stop if
     this route matches. This is nice for making XMLHTTPRequest calls. -->
<route pattern="application/json" source="_SERVER[HTTP_ACCEPT]" output_type="json" stop="false" />

<route pattern="^/auth" module="%actions.login_module%">
    <routes>
        <route name="login" pattern="^/login$" action="%actions.login_action%" />
        <!-- A nested route. This will match the URL "/login/logout" - matched fragments from
                the parent route are stripped from the URL string for child routs. -->
        <route name="logout" pattern="^/logout$" action="Logout" />
    </routes>
</route>
This example is taken from the sample application's routing.xml. The pattern parameters of the login and logout routes have a trailing $. Without them URLs like /auth/login/foo/bar could match later on, which is most probably not your desired behaviour.
The above example code tells you multiple things about Agavi's routing system. You define routes via patterns of regular expressions that are matched against the current request (or command line in console context or given parameters in xml-rpc context etc.). You should name routes, to be able to generate them in templates or views with $url = $ro->gen('route.name', $params, $options);. In case a nested route's name starts with a dot (".name") you have to specify route.name for route generation. If your nested route is just a normal name you can use it for gen() calls directly.
Another feature is setting the output type to use depending on some pattern matching (with using not only the current URL as source, but _SERVER values etc. as well). In the above example URLs that are requested via XHR (using one of the common Javascript libraries out there, that set the correct accept headers for such calls) would use the executeJson() method in the matched action's view (instead of the probably default executeHtml() you normally use in your web application).
How to define default values for routing parameters?
Required page parameter with default value start:
<route name="page.products" pattern="^/(page:[A-Za-z0-9_\-]+)/products" module="Products">
    <defaults>
        <default for="page">start</default>
    </defaults>
    <route name=".detail" pattern="^/({id:\d+})$" action="Products" />
</route>
Optional page parameter with default value start:
<route name="page.products" pattern="^(/{page:[[:alnum:]]+})?/products" module="Products">
    <defaults>
        <default for="page">/{start}</default>
    </defaults>
    <route name=".detail" pattern="^/({id:\d+})$" action="Products" />
</route>
How to use those curly braces for min/max quantifiers?
You will do fine by just escaping them like this:
<route name="hash" pattern="/(hash:[a-fA-F\d]\{32\})$">
<!-- or try it like this: -->
<route name="hash" pattern="/({hash:[a-fA-F\d]{32}})$">
How to make route pattern matching case-insensitive?
When matching URL patterns within your route's pattern attribute you can use the known regular expression pattern modifiers. As Agavi depends on PCRE you can just use the /i modifier to make matching case insensitive (or any other modifier to try other things):
<route name="asdf_and_foo_case_insensitive" pattern="... ((?i)asdf|foo) ...">
<!-- to only make a part of the pattern case insensitive just use braces: -->
<route name="asdf_case_insensitive" pattern="... (((?i)asdf)|foo) ...">
If you want the complete matching to be case insensitive, use (?i) at the beginning of the pattern. Please be aware of duplicate content issues when using case-insensitive URLs or optional trailing slashes etc. Read your favorite SEO-related articles/blogs to get some more information about this.
What additional options are there to be specified when generating routes?
There are some options available, that make your life easier, when generating routes via $ro->gen(). See the following examples:
// just as a reminder, a complete URL may consist of these parts:
// scheme://[authority[:password]@](host_name|\[host_address\])[:port][/hierarchical/path/to/resource[?search_string][#fragment_id]]

// default call for route generation:
$ro->gen('some.sub.route', $routeParams, $optionsArray);

// default routing generation options from AgaviWebRouting:
$this->defaultGenOptions = array_merge($this->defaultGenOptions, array(
    // separator, typically &amp; for HTML, & otherwise
    'separator' => '&amp;',
    // whether or not to append the SID if necessary
    'use_trans_sid' => false,
    // scheme, or true to include, or false to block
    'scheme' => null,
    // authority, or true to include, or false to block
    'authority' => null,
    // host, or true to include, or false to block
    'host' => null,
    // port, or true to include, or false to block
    'port' => null,
    // fragment identifier (#foo)
    'fragment' => null,
));

// there are other options like:

// tell Agavi to generate absolute (or relative) URLs:
$ro->gen('some.sub.route', $routeParams, array('relative' => false));

// tell Agavi to use the current URL with parameters (e.g. re-submit a form to the same URL):
$ro->gen(null);
You may combine the available options as you like.
How to determine the current matched routes?
As the executed main action/view may create/modify/remove slots dynamically you can find the matched routes in the request (not in the execution container):
// get array with all matched routes of the current request
$route_names_array = $this->getContext()->getRequest()->getAttribute('matched_routes', 'org.agavi.routing');

// you'll get an array that looks something like this with as many routes as matched:
array('product.edit' ...);

// which you can use to get an information array from it:
$route = $this->getContext()->getRouting()->getRoute($route_name_from_array);

// and determine the executed module and action from that:
$module = $route['opt']['module'];
$action = $route['opt']['action'];
The above example with some further explanations can be found in DracoBlue's blog entry about matched routes. In case you wonder, why multiple routes may have matched, think about changing the output type using a route, cutting locale parameters from routes or setting up things via routing callbacks (see the first routing related FAQ entry for examples).
What are routing callbacks and how can I use them?
Routing callbacks are something really useful. They are defined inside routes themselves, and by applying them you can perform things like extract, modify or write new parameters for the routes, redirections, and host of other things on execution time, all by just making use of its runtime events.
<route pattern="^/({locale:[a-z]{2}})" stop="false" imply="true" cut="true">
	<callbacks>
		<callback class="LanguageRoutingCallback" />
	</callbacks>
	<ignores>
		<ignore>locale</ignore>
	</ignores>
</route>

There are three events you could use for all purposes: onMatched, onNotMatched, onGenerate. To create a new routing callback extend the class AgaviRoutingCallback and make use of those. The most exceptional thing to realize about them is that parameters, options, and user parameters are passed by reference, so any update in them will affect the route execution or generation. This is perfect since you could use it to your advantage.

How do I generate routes with different output types?
Just add the route name of the output type changing route to your normal route generation call like this:
// e.g. in view/template when linking to resources
$ro->gen('music.genre+rss', ...); // link to rss feed of genres
$ro->gen('music.genre', ...);     // link to HTML listing of genres
The first route generates a link to your normale URL with the added extension ".rss" as you specified in your routing.xml file. You may add various other output types to your setup and will be able to support multiple types of data depending on the called route. To use this, follow the next steps.

Let's say, you defined an output type for RSS or Atom feeds in your output_types.xml file:
<output_type name="rss">
    <renderers default="php">
        <renderer name="php" class="AgaviPhpRenderer">
        </renderer>
    </renderers>
    <parameters>
        <parameter name="http_headers">
            <parameter name="Content-Type">application/rss+xml; charset=UTF-8</parameter>
        </parameter>
    </parameters>
</output_type>
You then define the routes you need to use and generate feeds in routing.xml:
<route name="rss" pattern=".rss$" stop="false" cut="true" output_type="rss" />
...
<route name="music" pattern="^/music">
  <route name=".genre" pattern="^/genre/(name:[\S\s]+)$">
  </route>
</route>
The cut="true" attribute tells Agavi to cut ".rss" from the route and continue with matching the routes as stop="false".
Then in your views you have the different execute*() methods for each output type to generate your music genre listing (in this example):
public function executeRss(AgaviRequestDataHolder $rd)
{
    // generate your feed using whatever you like (SimpleXML, XMLWriter, some external library ...)
    return $xml_output_for_feed;
}
You may add the appropriate execute methods as fallback to your base views to prevent Agavi exceptions when somebody calls an URL with ".rss" that has no specified executeRss() method (and forward/redirect to a 404 page or the HTML output or whatever fits your needs).

As a practice try adding a CSV, XML or JSON routes and return your data comma separated, as XML or JSON.
<route name="output_json" pattern="JSON" source="_SERVER[HTTP_X_REQUEST]" output_type="json" stop="false" />
<!-- or -->
<route name="output_ajax" pattern="XMLHttpRequest" source="_SERVER[HTTP_X_REQUESTED_WITH]" output_type="ajax" stop="false" />
Use whatever you think is appropriate - the different Javascript libraries or frameworks usually set the correct request headers for you to use in pattern matching for your routes. So using this scheme a single URL may return HTML or JSON depending on the request headers sent. Awesome! :-)

Back to the top of this page


Validation related topics

Just a reminder to validate ALL user inputs...

What are the validation modes and what do I need to know about them?
There are three modes, which you can set in factories.xml (if you want to relax things a bit, which is not advised):
<validation_manager class="AgaviValidationManager">
    <ae:parameter name="mode">strict|conditional|relaxed</ae:parameter>
</validation_manager>
The "strict" mode is the default and recommended mode as it is the most secure one. It requires you to validate ALL inputs. This includes not only form inputs, but also route parameters, headers, files and cookies.
  • strict => only validated input (get, post, cookies, headers, files etc.) is passed to the action/view
  • conditional => if there is a validation XML for the action "strict" rules, otherwise all data is passed
  • relaxed => all data is passed (except if explicit validation failed)
Is it possible to define nested validators?
Of course, have you ever used the AgaviAndoperatorValidator or AgaviXoroperatorValidator in a nested szenario? ;) For a simple start try some validator chaining like this:
<validators method="write">
    <validator class="and" name="email_rules">
        <validator class="string" name="email_too_short" trim="true"> ... </validator>
        <validator class="email" name="email_valid"> ... </validator>
        ...some more if you like...
    </validator>
</validators>
With the AgaviAndoperatorValidator you can bundle multiple validators (and hence their errors) into one. If the first validator fails, the execution stops there (= 1 error message) but all validators are still required to pass validation. Veikko provides another example, that is probably helpful in certain cases:
<validator class="or">
    <validators>

        <validator class="email" required="false" >
            <arguments>
                <argument>email</argument>
            </arguments>
        </validator>

        <validator class="MyPhoneNumberValidator" required="false" >
            <arguments>
                <argument>phone</argument>
            </arguments>
        </validator>

    </validators>

    <errors>
        <error>Please provide email or phone</error>
    </errors>
</validator>
The MyPhoneNumberValidator is a custom validator. With Agavi you can write your own custom validators to perform certain tasks on your input data before action execution. See How to create a custom Validator? to learn, how easy it is.
What is the name of a validator for?
Names of validators are optional and mainly for grabbing results more easily. See "How do I check validation results..." for an example.
How to prevent performing other validations on a field in case one validator fails?
Use the "severity" attribute like this:
<validator ... severity="critical">
The base class AgaviValidator defines multiple error severity constants in case of failure. They are SUCCESS, INFO, SILENT, NOTICE, ERROR and CRITICAL. For further details just have a look at the AgaviValidator source code. The severity levels work like this:
  • CRITICAL aborts validation instantly and other validators will not be executed.
  • ERROR is the default that marks the argument as not valid. The argument will be in the validation report and thus may be populated via the FormPopulationFilter (FPF). The value will of course not be available for your actions etc.
  • SUCCESS is used when the validator succeeded.
  • INFO means that the failing of the validator does not have an impact on the result of the whole validation process. The 'failed' value will still be available for actions.
  • SILENT means, that there will be no error in the validation report (and thus no automatic repopulation in html forms via the FormPopulationFilter can happen).
  • NOTICE is similar to SILENT, but will generate an error for the validation report. The validation process in general does not fail, but the value will not be available.
Can validators depend on other validators?
Yes, you can make a validator run only if an other validator succeeds. Example:
<validator ... provides="password_valid">
<validator ... depends="password_valid">
Veikko provides another nice example for this:
<validator ... required="false" provides="street_set">
    <arguments base="contact">
        <argument>Street</argument>
    </arguments>
</validator>

<validator ... required="true" depends="contact[street_set]">
    <arguments base="contact">
        <argument>Zip</argument>
    </arguments>
</validator>

Note: argument’s base attribute affects also provides/depends attributes (see example above)!

Can the "depends" attribute receive more than one dependency?
Yes, just use a space separated list. Every dependency must be met to make the validator run. Example:
<validator class="string" provides="password_required">
    <argument>password</argument>
    <error>Password is required</error>
</validator>

<validator class="string" provides="confirm_required">
    <argument>password_confirmation</argument>
    <error>Password confirmation is required</error>
</validator>

<validator class="equals" depends="password_required confirm_required">
    <argument>password_confirmation</argument>
    <ae:parameter name="value">password</ae:parameter>
    <ae:parameter name="asparam">true</ae:parameter>
    <error>Password confirmation doesn't match Password</error>
</validator>
In this simple case the password match validator is only run if both previous string validators succeed.
Is there a possibility to register validators manually in an action?
You can register validators manually in the action's register[Write|Read|etc]Validators() methods:
$validators = array();

$arguments = array('date');
$errors = array('' => 'something went wrong');
$parameters = array('export' => 'somedate', 'required' => true);
$validator = new SomeSpecialDateValidator();
$validator->initialize($this->getContext(), $parameters, $arguments, $errors);
$validators[] = $validator;

$this->getContainer()->getValidationManager()->registerValidators($validators);
For convenience you may use the createValidator() method to create validators like this (pseudocode; not tested):
$validationManager->createValidator('Some_CaptchaValidator', 
                        array(0  => 'captcha_comment'),
                        array('' => 'You entered a wrong validation code.'), 
                        array('type'                  => 'integer',
                              'severity'              => 'error',
                              'method'                => null,
                              'required'              => true,
                              'translation_domain'    => 'default.captcha', 
                              'name'                  => 'invalid_captcha',
                              'domain'                => $captcha_domain
                        )
                    );
Is there any example of custom file validations or the AgaviImageFileValidator?
Try something like this:
<validator class="AgaviImageFileValidator" severity="error" required="false">
    <arguments>
        <argument>image</argument>
    </arguments>
    <errors>
        <error>Provided image invalid.</error>
        <error for="max_width">Please only use images with a maximum width of 500px.</error>
        <error for="min_width">Your provided image should have a minimum width of at least 10px.</error>
        <error for="max_size">Maximum allowed filesize is 50kb.</error>
        <error for="format">Only JPEG, GIF and PNG images will be accepted.</error>
    </errors>
    <ae:parameters>
        <ae:parameter name="max_width">500</ae:parameter>
        <ae:parameter name="min_width">10</ae:parameter>
        <ae:parameter name="max_size">51200</ae:parameter>
        <ae:parameter name="format">
            <ae:parameter>jpeg</ae:parameter>
            <ae:parameter>gif</ae:parameter>
            <ae:parameter>png</ae:parameter>
        </ae:parameter>
    </ae:parameters>
</validator>

<validator class="AgaviFileValidator" severity="error" required="false">
    <arguments>
        <argument>css</argument>
    </arguments>
    <errors>
        <error>Invalid file. Use non-empty files with a '.css' extension.</error>
    </errors>
    <ae:parameters>
        <ae:parameter name="min_size">0</ae:parameter>
        <ae:parameter name="extension">css</ae:parameter>
    </ae:parameters>
</validator>
Please make sure, that you have proper security constraints when working with user provided content/files. There are many possible attack vectors and you should NEVER trust any uploaded content or your determined MIME types or extensions. Guessing the file types is not sufficient on its own as there could be other content hidden inside. Just google GIFARs (e.g. see Billy (BK) Rios' blog), JPGARs or other MIME type and file upload security related topics for a start. For enhanced security you could perhaps deliver uploaded content using a second web server instance, which does only have read only access for very specific directories (where you move uploaded files to) and will not execute scripts.
How can I repopulate read-only/disabled input fields?
Since disabled input fields will not be submitted (read-only inputs will be submitted - see HTML4.01 forms section) by the user agent, you could use the action's handleError() method to provide a value if the parameter is not present in the request.
Example:
public function handleError(AgaviRequestDataHolder $rd) {
    if (!$rd->hasParameter('readonly_or_disabled_field')) {
        $this->context->getRequest()->setAttribute('populate',
            new AgaviParameterHolder($rd->getParameters() + array('readonly_or_disabled_field' => 'default value')),
            'org.agavi.filter.FormPopulationFilter');
    }
    return 'Input';
}
This will work most of the time, but you will notice missing values in some cases as $rd does not contain all global parameters to repopulate. If you notice this try to repopulate everything using true and then set your value manually.

Here are some more examples:
// repopulate your form in the ErrorView (with the already posted data):
$this->getContext()->getRequest()->setAttribute('populate', true, 'org.agavi.filter.FormPopulationFilter');

// pre-fill form with some values:
$data = array("name_of_input_field" => "some_value"); 
$this->getContext()->getRequest()->setAttribute('populate', $data, 'org.agavi.filter.FormPopulationFilter'); 

// or pre-fill it like this:
$data = new AgaviRequestDataHolder(); 
$data->setParameter('name_of_input_field', 'some_value'); 
$this->getContext()->getRequest()->setAttribute('populate', $data, 'org.agavi.filter.FormPopulationFilter');
How to manually output error messages in a view?
Just use the validation manager and iterate over errors. For a small example of iterating over error messages see the validation section of the tutorial. Remember, that you can probably use the FormPopulationFilter to repopulate form fields and set necessary CSS error classes etc.
Is there a better way than using isSet or isNotEmpty to validate route parameters?
Unfortunately there is no better way than using AgaviIssetValidator and AgaviIsNotEmptyValidator, as you have to validate all parameters in strict mode. The difference of "set" vs. "empty" is as follows:
Anything that has a value (even null) is considered "set". By default "empty" is the same as "set", but for AgaviWebRequestDataHolder parameters are considered empty even when they're an empty string, because browsers submit an empty string for empty form fields. (Otherwise required="false" on validators for optional fields would require a somewhat cumbersome implementation)
How do I use the inArray validator?
Try something like this:
<validator class="inarray" name="invalid_day" required="false">
    <arguments>
        <argument>day</argument>
    </arguments>
    <errors>
        <error for="type">Only some values are allowed as a day.</error>
    </errors>
    <ae:parameters>
        <ae:parameter name="type">string</ae:parameter>
        <ae:parameter name="case">true</ae:parameter>
        <ae:parameter name="values">
            <ae:parameter>monday</ae:parameter>
            <ae:parameter>tuesday</ae:parameter>
            <ae:parameter>wednesday</ae:parameter>
            <ae:parameter>thursday</ae:parameter>
            <ae:parameter>friday</ae:parameter>
            <ae:parameter>saturday</ae:parameter>
            <ae:parameter>sunday</ae:parameter>
        </ae:parameter>
    </ae:parameters>
</validator>
If you don't like the values parameter array specification, you may use a custom separator and put all values in one line:
<validator class="inarray" name="invalid_day" required="false">
    <arguments>
        <argument>day</argument>
    </arguments>
    <errors>
        <error for="type">Only some values are allowed as a day.</error>
    </errors>
    <ae:parameters>
        <ae:parameter name="type">string</ae:parameter>
        <ae:parameter name="case">true</ae:parameter>
        <ae:parameter name="values">monday,tuesday,wednesday,thursday,friday,saturday,sunday</ae:parameter>
        <ae:parameter name="sep">,</ae:parameter>
    </ae:parameters>
</validator>
The 'case' parameter is false by default and specifies if the comparison should be case-sensitive or not (true = case-sensitive comparison).
How do I validate array input?
Imagine you are in a situation where you tried the AgaviInarrayValidator from the answer above to validate a form with multiple submit buttons. Each button element has the same name attribute day and a value attribute set to monday, tuesday etc. Everything works as expected, your daynames are matching a route with a pattern like ^/(day:(monday|tuesday|etc...))$ and your validator just works. At least until you check your multiple submit button scenario in Internet Exploder. The IE displays your nicely CSS formatted buttons (like <button type="submit" name="day" value="sunday">some HTML</button>), but does submit the inner content instead of the value attribute values (as any other browser out there sticking to standards does). So what now? Javascript not allowed, one form per button not possible, other alternatives fading fast...Easy. Breathe, just change your validators a bit to validate array input and use the names of the submit buttons to get your information to the server:
<button type="submit" name="day[sunday]" value="whatever">some HTML</button>

<validator class="string" name="invalid_day" required="false">
    <arguments base="day[]">
        <argument></argument>
    </arguments>
</validator>
Other examples for the validation of arrays are like this (thanks to Veikko once again :P):
<input name="rows[id]" ...>

<arguments base="rows">
    <argument>id</argument>
</arguments>

<!-- or -->

<arguments>
    <argument>rows[id]</argument>
</arguments>
or:
<input name="rows[1][id]" ...>

<arguments base="rows[]">
    <argument>id</argument>
</arguments>
or:
<input name="rows[]" ...>

<arguments base="rows[]">
    <argument/>
</arguments>
Note the usage of the base attribute in certain places. In addition to this there's a new AgaviArraylengthValidator since Agavi v1.0, which lets you specify min and max parameters to validate the number of elements in an array. Furthermore, if you would like to overwrite an input array with results from a validator, you can do so by specifying an export parameter as follows:
<arguments base="rows[]">
    <argument/>
</arguments>
<ae:parameters>
    <ae:parameter name="export">rows[%2$s]</ae:parameter>
</ae:parameters>
Is there an example for the AgaviDateTimeValidator?
The AgaviDateTimeValidator is a versatile tool. It can not only validate several date and time formats, but may also convert those values to a format that suits you in your actions/views (like unix timestamp, a formatted date etc.). The following is a small example, on how to cast your input date into another format:
<validator method="write" class="datetime" required="true" name="invalid_birthday" translation_domain="registration.errors">
    <arguments>
        <argument>birthday</argument>
    </arguments>
    <ae:parameters>
        <ae:parameter name="formats">dd.MM.yyyy</ae:parameter>
        <ae:parameter name="export">casted_birthday</ae:parameter>
        <ae:parameter name="cast_to">
            <ae:parameters>
                <ae:parameter name="type">date</ae:parameter>
                <ae:parameter name="format">yyyy-MM-dd</ae:parameter>
            </ae:parameters>
        </ae:parameter>
    </ae:parameters>
    <errors>
        <error>Invalid date format.</error>
    </errors>
</validator>
Notice the cast_to parameter and the parameters given to it. You can find further explanations and other possible parameters in the AgaviDateTimeValidator class. Another thing to notice is the translation_domain attribute, which allows for easy translation of validation error messages.

Veikko provides another example on how to use the validator to create a timestamp as a compound of three input arguments:
<validator class="AgaviDateTimeValidator">
    <arguments>
        <argument name="AgaviDateDefinitions::DATE">day</argument>
        <argument name="AgaviDateDefinitions::MONTH">month</argument>
        <argument name="AgaviDateDefinitions::YEAR">year</argument>
    </arguments>
    <errors>
        <error>Validation error</error>
    </errors>
    <ae:parameters>
        <ae:parameter name="export">MyDatestamp</ae:parameter>
    </ae:parameters>
</validator>
The AgaviDateTimeValidator supports the strtotime syntax (see ticket #1018). Therefore it's possible to do something like this:
<!-- beware of +1 month as imho it's always 30 days... -->
<ae:parameter name="min">+1 week</ae:parameter>
<ae:parameter name="max">-18 years</ae:parameter>

<!-- according to the ticket you can even use timezones: -->
<ae:parameter name="min">last monday this month 08:00UTC</ae:parameter>
See the custom AgeValidator of DracoBlue which could have used the default date validator (but chose not to because of error messages).
Please note, that you need to have use_translation set to true in settings.xml for this validator and its translations to work.
How do I check validation results and retrieve the incidents of a failed validator?
There's a brand new validation report query API you should check out. By chaining some by() method calls you can get information about errors and incidents of your validated arguments. Ticket #1022 contains examples:
$report = $this->getContainer()->getValidationManager()->getReport();
// or: $report = $this->getContainer()->getValidationManager()->getReport()->createQuery();

// check for a valid name
$nameValid = !$report->byArgument('name')->has();

// every by() method returns a clone of the query object, so you don't have to create new instances
$nameMissing = $report->byArgument('name')->byErrorName('required')->has();

// check for optional image file
$myoptionalfileIsImage = $report->byArgument(new AgaviValidationArgument('myoptionalfile', AgaviWebRequestDataHolder::SOURCE_FILES))
                                ->byValidator('is_image')
                                ->getResult() === AgaviValidator::SUCCESS;
The "old" way (but still functioning, I'd guess) would be to use named validators, checking the incidents of the validators in question and then dealing with them accordingly:
public function handleError(AgaviRequestDataHolder $rd)
{
  $vm = $this->getContainer()->getValidationManager();
  if (count($vm->getReport()->getValidatorResult('my_special_validator')->getIncidents())) {
    return 'CriticalError';
  }
  else {
    return 'Error';
  }
}
Above example taken from http://blog.veikko.fi/.
How to handle checkbox input fields?
As checkboxes are not submitted when not checked you can either use seemingly hackish approaches like Ruby on Rails does (adding a hidden text input field element with the same name and value "0" for each checkbox) or try to make some use of Agavi's built-in validators. The first and easiest way would be the usage of the AgaviEqualsValidator with the default value parameter of the Agavi(*)Request::getParameter() method:
<validator class="equals" required="false">
    <argument>user_disabled</argument>
    <ae:parameter name="value">1</ae:parameter>
</validator>
and then in action/view:
$rd->getParameter('user_disabled', 0); // default value = 0 when checkbox is not submitted
Presuming you don't want to rely on the default behaviour of checkbox input fields, there is a slightly different solution that makes use of a combination of validators to always have a checkbox value after validation (by setting the missing value to the default):
<validator class="or" required="true">
 
    <validator class="number" required="false">
        <argument>user_disabled</argument>
    </validator>

    <validator class="set" required="false">
        <ae:parameter name="export">user_disabled</ae:parameter>
        <ae:parameter name="value">0</ae:parameter>
    </validator>
 
</validator>
With this validator your checkbox field will always be defined and transmitted and all internal methods will work as expected on 'normal' input fields. Which solution you choose depends once again on your preferences and if it is useful to not have an unchecked checkbox input field submitted (so $rq->hasParameter() method behaves differently).
I'm writing the same configuration code over and over again, which doesn't feel very DRY. How to solve this?
If you don't always want to repeat yourself in various configuration files all over the place, try making some use of the niceties XML offers you. In validation files you should really make use of the parent="..." attribute. The value allows you to specify a parent validation file to include. Agavi needs this to let you use those shortnames for built-in validators instead of fully qualified class names.
<!-- in specific action's validator reference the module's validator -->
parent="%core.module_dir%/Programs/config/validators.xml"

<!-- in the module's validator file reference the application specific validator file -->
parent="%core.config_dir%/validators.xml"
The application's validator file then references Agavi's default validation file as parent. Proceeding from this it's probably clear, that you can define validators in those various places to prevent repeating yourself.
Another way to save some keystrokes and store some information in central places is to use XIncludes. You could easily put some complex code snippet in its own XML file and then include this snippet everywhere you need it. An example could be a complex application's caching definition:
<?xml version="1.0" encoding="UTF-8" ?>
<ae:configurations xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0"
                      xmlns="http://agavi.org/agavi/config/parts/caching/1.0"
                   xmlns:xi="http://www.w3.org/2001/XInclude">
    <ae:configuration>
        <cachings>
            <caching lifetime="30 seconds" enabled="true">
                <groups>
                    <group>some_group_name_to_use_for_this_action</group>
                    <group source="request_data">some_parameter</group>
                    <group source="request_attribute">or_attribute</group>
                </groups>
                <views>
                    <view>Success</view>
                </views>
                <!-- include default output type 'html' caching (template variables and request attributes
                     necessary to cache javascript and styles correctly and show a complete page etc. -->
                <xi:include href="%core.config_dir%/cache_default.xml"
                            xpointer="xmlns(ae=http://agavi.org/agavi/config/global/envelope/1.0)
                                      xmlns(ac=http://agavi.org/agavi/config/parts/caching/1.0)
                                      xpointer(/ae:configurations/ae:configuration/ac:cachings/ac:caching/*)"
                />
            </caching>
        </cachings>
    </ae:configuration>
</ae:configurations> 
The included snippet resides in app/config/cache_default.xml (or whatever fits your requirements) and could look like this (depends on your application, your used slots, global request attributes they need to function when being fragment cached etc.):
<?xml version="1.0" encoding="UTF-8" ?>
<ae:configurations xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0"
                      xmlns="http://agavi.org/agavi/config/parts/caching/1.0">
    <ae:configuration>
        <!-- this file contains default settings to be used via xincludes in
             caching xml files throughout the application - be careful with
             changes to this file as multiple cache.xml files are affected -->
        <cachings>
            <caching>
                <output_types>
                    <output_type name="html">
                        <layer name="content" />
                        <template_variable>_content_type</template_variable>
                        <template_variable>_robots</template_variable>
                        <template_variable>_meta_tags</template_variable>
                        <template_variable>_title</template_variable>
                        <template_variable>theme</template_variable>
                        <request_attributes>
                            <request_attribute namespace="app.layout">javascript_files</request_attribute>
                            <request_attribute namespace="app.layout">stylesheets</request_attribute>
                            <request_attribute namespace="www.example.com">some_pageid</request_attribute>
                        </request_attributes>
                    </output_type>
                </output_types>
            </caching>
        </cachings>
    </ae:configuration>
</ae:configurations>
Request attributes are set in your cached views and defined here explicitly to have them available, when fragment cached versions of your actions/views are displayed. Without this, the attributes wouldn't be available. Praise the XInclude for centralized storage, management, retrieval and reuse of snippets.
How to write a custom validator?
Writing a custom validator in Agavi is rather easy. Just extend AgaviValidator or one of its siblings and add a validate() method to it. Using parameters you can customize the behaviour of your validator. In case validation fails you simple throw errors and these error names may then be used for (translated) error messages in the validation XML file.
class SampleCustomValidator extends AgaviValidator
{
    protected function validate()
    {
        // if(!parent::validate()) // in case you extend another validator
        // {
        //     return false;
        // }

        // get input data (e.g. from input field)
        $inputdata = $this->getData($this->getArgument());
        
        // when required="false" is set on the validator
        // we usually can't set a default value for the following
        // actions - thus we override checkAllArgumentsSet() to return
        // true and return our default value in case of no input data
        if (empty($inputdata))
        {
            $this->export(array());
        }

        if (!$this->hasParameter('something'))
        {
            $this->throwError();
            return false;
        }
        
        if ($some_constraint !== $this->getParameter('some_param', $default_value))
        {
            $this->throwError('one_problem');
            if ($even_more_problems_arise)
            {
                $this->throwError('two_problems');
            }
            return false;
        }
        
        // check for consistency, ask your Agavi models for help, do whatever
        // you like to validate your input data - e.g. like this:
        $finder = $this->getContext()->getModel('BlogPostFinder');
        $post = $finder->findById($inputdata);
        if (null === $post)
        {
            $this->throwError('not_found');
            return false;
        }
        
        // export your validated data (e.g. the $post object so your actions don't need to retrieve it again)
        // you may optionally specify a name here as 2nd parameter (or set one using the ae:parameter 'export')
        $this->export($post);
        return true;
    }

    // you only need to override this method, when you want to export default values
    // to your actions in case no input was given (that is, when 'required' is set to 'false')
    protected function checkAllArgumentsSet($throwError = true)
    {
        return true; // look at AgaviValidator::checkAllArgumentsSet()
    }
}
After creating your own custom validator you have to add the class to autoload.xml and may start using it in your validation definitions like this:
<validator class="SampleCustomValidator" name="valid_post" required="false">
    <arguments>
        <argument>foo</argument>
    </arguments>
    <errors>
        <error>Strange things happened.</error>
        <error for="not_found">Blog post not found. Specify valid ID.</error>
        <error for="one_problem">"Some people, when confronted with a problem, think >I know, I'll use regular expressions.<.</error>
        <error for="two_problems">Now they have two problems" -- Jamie Zawinski.</error>
    </errors>
    <ae:parameters>
        <ae:parameter name="something">true</ae:parameter>
        <ae:parameter name="some_param">value</ae:parameter>
        <ae:parameter name="export">bar</ae:parameter>
    </ae:parameters>
</validator>
Everything should be rather obvious when reading this code. The export parameter is optional and specifies the name under which to export your data to the request (so foo will be available to your actions as bar).
Please note, that you only need to overwrite AgaviValidator::checkAllArgumentsSet() when you want to export default values to your actions when required="false". Keep in mind, that required="false" just prevents the validator from running when the argument the validator checks is not available.
How to register shortnames for validators and why should I use them?
Just follow the chain of parent validators until you find agavi/src/config/defaults/validators.xml. Within the chain you may always define short names everywhere. Examples are validator definitions specific to a module or sets that are used project wide. You may create validator definitions and set sensible default parameters for your own custom validators. Just add the following in a [validators].xml file:
<validator_definition name="sample" class="SampleCustomValidator">
    <ae:parameter name="something">true</ae:parameter>
</validator_definition>
The best about this mechanism isn't that you save on typing some classnames in your validator definitions. The convenient thing is, that you may not only reuse validators (built-in or custom), but also reuse existing validators with different sets of pre-defined parameters. Another advantage is, that you may easily change your validator implementation later on without changing the lot of action validators you may have already in use in your project. Just a cheap example to give you an example:

Let's assume you start your project with a simple validator for min-length checks on given usernames:
<validator_definition name="valid_username" class="AgaviStringValidator">
    <!-- ... etc. ... -->
    <ae:parameter name="min">5</ae:parameter>
</validator_definition>
Later on you extend that example by using the RegExp validator to check for valid characters and patterns:
<validator_definition name="valid_username" class="AgaviRegexValidator">
    <!-- ... etc. ... -->
    <ae:parameter name="pattern">[a-zA-Z_\-]{5,25}</ae:parameter>
</validator_definition>
As your project grows you now want to check for the availability of usernames upon registration and export valid User objects instead of just strings (unique names) to your actions. You create your own custom validator for this, do all your checks on the given arguments and export the User object to the request:
<validator_definition name="valid_username" class="CustomUsernameValidator">
    <!-- ... etc. ... -->
    <ae:parameter name="check_availability">false</ae:parameter>
</validator_definition>

<validator_definition name="valid_available_username" class="CustomUsernameValidator">
    <ae:parameter name="check_availability">true</ae:parameter>
</validator_definition>
The above example reuses the CustomUsernameValidator with different parameter settings. To use your defined validators simply write validators for your actions and use the specified shortnames as class name like this:
<validator class="valid_available_username" name="some_valid_and_available_username" required="false">
    <arguments>
        <argument>username</argument>
    </arguments>
    <errors>
        <error>Username neither valid nor available.</error>
    </errors>
</validator>
As you can see, the system is easily extendable and you're able to easily change certain validations in one place instead of various files in all your different actions or modules. The maintainance is eased and error prone changes of parameters everywhere in your project are no longer one of your concerns as you may simply add another shortname or change settings for the existant ones.

Back to the top of this page


Caching related topics

Is it possible to (globally) enable or disable fragment caching?
You can enable or disable the caching of fragments (using the app/[ModuleName]/cache/[ActionName].xml files) individually per environment. Just edit the factories.xml file and set the enable_caching parameter on the AgaviExecutionFilter accordingly:
<ae:configuration context="web" environment="development.*">
    <execution_filter class="AgaviExecutionFilter">
        <ae:parameters>
            <ae:parameter name="enable_caching">false</ae:parameter>
        </ae:parameters>
    </execution_filter>
</ae:configuration>
What are caching groups and how are they used for fragment caching?
You simply create a caching XML file with the actions' name in the cache directory (or one of its subdirectories in case you have a deeper folder structure for actions/slots) of your module and set some groups as keys to differentiate. Keys can be taken from different sources (request parameters, current locale, etc.). The groups are used to build a directory structure (folder names are base64 encoded) used for caching (see app/cache/content/ directory) fragments of your output.
The following is a bit bigger, to give you an impression of the possibilities of Agavi's fragment caching:
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0"
                      xmlns="http://agavi.org/agavi/config/parts/caching/1.0">
    <ae:configuration>
        <cachings>
            <caching lifetime="30 seconds" enabled="true">
                <groups>
                    <group>show_article_detail</group>
                    <group source="user_attribute" namespace="tld.example.sub">userId</group>
                    <group source="request_data">item[id]</group>
                    <group source="global_request_data">article_id</group>
                    <group source="global_request_data" namespace="cookies">someLocaleCookie</group>
                    <group source="callback">getCustomCachingKeyFromActionMethod</group>
                    <group source="locale" />
                </groups>
                <views>
                    <view>Success</view>
                </views>
                <output_types>
                    <output_type name="html">
                        <layer name="content" />
                        <template_variable>_content_type</template_variable>
                        <template_variable>_robots</template_variable>
                        <template_variable>_meta_tags</template_variable>
                        <template_variable>_title</template_variable>
                        <request_attributes>
                            <request_attribute namespace="app.layout">javascript_files</request_attribute>
                            <request_attribute namespace="app.layout">stylesheets</request_attribute>
                        </request_attributes>
                    </output_type>
                </output_types>
            </caching>
        </cachings>
    </ae:configuration>
    <ae:configuration environment="production">
        <cachings>
            <caching method="read" lifetime="24 hours" enabled="true">
                    <group source="callback">tryAnotherCustomCachingKeyFromActionMethod</group>
            </caching>
        </cachings>
    </ae:configuration>
    <ae:configuration environment="staging">
        <cachings>
            <caching method="read write" lifetime="30 seconds" enabled="false">
                <groups>
                    <group>show_article_detail</group>
                    <group source="user_attribute" namespace="tld.example.sub">userId</group>
                    <group source="request_data">someObjectInRequest</group>
                </groups>
            </caching>
        </cachings>
    </ae:configuration>
</ae:configurations>
As you see, you can use different sources for your caching keys (groups) and define different settings for different environments. It is also possible to create custom (more complex) group keys by using a caching callback method of your action, which returns the caching key to use. In case the action method throws an AgaviUncacheableException nothing is cached.
Which source attribute values are available and what do they do?
You can find the acceptable values for the source attribute in the AgaviExecutionFilter.
string
The default source used. Takes just the given value as a group. Uses '0' if string value is null, false or empty.
callback
Name of callback method to call on current action. Return value of the method will be used as caching key. If the method throws an AgaviUncacheableException nothing will be cached.
configuration_directive (Agavi 1.0.1+)
Uses AgaviConfig::get() to let you use configuration settings. Use something like core.debug or modules.modulname.Some as element content.
constant
Creates a constant of the given value and uses that as a caching group.
container_parameter
Retrieves a parameter from the current AgaviExecutionContainer (not its request!) to be used as a group.
global_request_data
The element's value should be a name of a parameter from the global request. If no namespace attribute is specified, the default is AgaviRequestDataHolder::SOURCE_PARAMETERS, which maps to normal request parameters. You can specify headers, files and cookies as namespace attribute values to use other sources as group names.
locale
Retrieves the current locale identifier from the AgaviTranslationManager and uses it as a group name.
request_attribute
Uses the current context's request attribute as a group name. You may specify the namespace attribute to get attributes from the request, that are stored using a namespace.
request_parameter
Legacy value. You can use it, but it's the same as source="global_request_data" without a namespace attribute.
request_data
The same as global_request_data, but gets its parameters from the current execution container's request instead of the global request. A namespace attribute may be specified as well.
user_attribute
Gets the specified attribute's value from the current context's user instance. You may specify a namespace attribute to use for the attribute value lookup.
user_authenticated
Uses isAuthenticated() method's return value if the current context's user is an instance of AgaviISecurityUser.
user_credential
Uses hasCredentials() method's return value if the current context's user is an instance of AgaviISecurityUser.
user_parameter
Gets the specified parameter's value from the current context's user instance.
Remember, that if the returned value from the request etc. is an object Agavi first tries to use the object's __toString() method or secondly looks, if a spl_object_hash method exists to get a nice key for the group name.
Any tips before starting to use fragment caching intensively?
Starting to use fragment caching on a larger scale in production environments/applications may result in some minor or not easily reproducible problems. The following few hints are there for you to consider, when some strange bugs are reported to you or if you just want to prevent some gotchas right from the beginning. Hopefully they spare you some minutes of testing when somewhat weird errors occur after you've enabled fragment caching:
  • The values of your cache groups are base64 encoded and are used as file/folder names. Therefore they may easily exceed the maximum filename/folder/path length constraints of your server's filesystem. See Ticket #1140 and the linked ticket #854 for a solution on how to mitigate these problems. The recommended solution is to use caching callbacks to bundle multiple of your caching groups together or if that seems overkill just remember to use not too many caching groups and not too long caching group values (try to hash them, if you don't need to reuse/revert folder names).
  • Use the current environment's name as the first caching group to prevent problems with other environments that may be run in parallel (e.g. an environment production-desk that the editorial team of a website uses whileas normal visitors use production) (Note: This is just an example where the environment used by the editors could show inplace editing buttons and stuff to make it easier for them to change certain aspects of the pages of a website in a WYSIWYG style instead of using a backend CMS or similar).
    As fragment caching is environment agnostic by default, these two environments would share the fragment cache (usually in app/cache/content) and may overwrite parts of each other which may result in wrongly generated routes/texts etc. (first user hitting a page in one environment would prime the cache and the user of the other environment would see this cached file instead of his environment's version). To prevent those problems you could use the environment's name from Agavi's settings as a caching group in your caching definition files:
    <group source="configuration_directive">core.environment</group>
                
    In case you have many default groups you could define a default cache group file or sandbox and XInclude everything you need. See the validation question about DRY for some example that enables you to quickstart.
  • There may be problems with your fragment cache, when you forget to take the is_slot parameter into account and use the same action as a slot and a full page. As a workaround use is_slot somewhere in your caching groups or callbacks.
  • Another problem with fragment caching related to slots is output type dependent. It may happen, that you cache a slot for an AJAX/JSON output type with the same caching groups as the main action/view/template. As this is definitly not what you want (return HTML to JSON or PDF to HTML etc. pp.), you should take the output type into account when you use fragment caching of actions/slots with multiple output types.
  • Don't forget to cache (global) request attributes, as these attribute's values would otherwise be missing in the cached version of pages. This may happen to you, when you set attributes to the global request in your (main) actions, views or generated slots to use these values later on in some decorator slots (think of tracking values in attributes that are used to create tracking URLs in a decorator/layout slot (like footer slot or master template)).
  • Remember to use Agavi's methods to clear entire caching groups. As Agavi doesn't clean up the content cache by itself (which is good for never changing content) you may consider to setup some maintainance routines (e.g. cronjobs), that clear the Agavi content cache for you depending on your app's requirements (e.g. delete all the files and directories older than 30 days). A simple cronjob approach could look like this (just ask your next server admin):
    17 03 * * *     find /srv/www/dir/of/your/app/cache/content -xdev -type f -mmin +1800 -print0 | xargs -r0 rm -v > /dev/null
                
A good help is to start using caching groups that have a combination of the name of the current module, action, view, is_slot parameter and output type. This should prevent you from most of the hidden aspects/gotchas of fragment caching that may hit you hard in production.
question
some nice description and example...
question
some nice description and example...

Back to the top of this page


Translation related topics

How do I start?
For a quickstart just see the sample application. The following example is basically taken from it. You start with defining translations by editing app/config/translation.xml and using the AgaviTranslationManager in your code:
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0"
                      xmlns="http://agavi.org/agavi/config/parts/translation/1.0">
    <ae:configuration>
        
        <available_locales default_locale="en_US@currency=GBP">
            <!-- German, Germany -->
            <available_locale identifier="de_DE">
                <ae:parameter name="description">Deutsch</ae:parameter>
            </available_locale>
            <!-- English, United States -->
            <available_locale identifier="en_US">
                <ae:parameter name="description">English</ae:parameter>
            </available_locale>
            <!-- Chinese Simplified, China (alias for zh_Hans_CN, Simplified Han) -->
            <available_locale identifier="zh_CN">
                <ae:parameter name="description">简体中文</ae:parameter>
            </available_locale>
            <!-- Chinese Traditional, Taiwan (alias for zh_Hant_TW, Traditional Han) -->
            <available_locale identifier="zh_TW">
                <ae:parameter name="description">繁體中文</ae:parameter>
            </available_locale>
        </available_locales>
        
        <translators default_domain="default">
            <translator domain="default">
                
                <translator domain="errors">
                    <message_translator class="AgaviSimpleTranslator">
                        <ae:parameter name="Login">
                            <ae:parameter name="de">
                                <ae:parameter name="Wrong Password">Falsches Passwort</ae:parameter>
                                <ae:parameter name="Wrong Username">Ungültiger Benutzername</ae:parameter>
                                <ae:parameter name="Please supply a password.">Bitte geben Sie ein Kennwort ein.</ae:parameter>
                                <ae:parameter name="Username's too short (5 chars min).">Benutzername zu kurz (fünf Zeichen Minimum).</ae:parameter>
                            </ae:parameter>
                        </ae:parameter>
                    </message_translator>
                </translator>
                
                <message_translator class="AgaviGettextTranslator">
                    <ae:parameter name="text_domains">
                        <ae:parameter name="menu">%core.app_dir%/data/i18n</ae:parameter>
                        <ae:parameter name="layout">%core.app_dir%/data/i18n</ae:parameter>
                        <ae:parameter name="Login">%core.app_dir%/data/i18n</ae:parameter>
                        <ae:parameter name="SearchEngineSpam">%core.app_dir%/data/i18n</ae:parameter>
                        <ae:parameter name="ErrorActions">%core.app_dir%/data/i18n</ae:parameter>
                    </ae:parameter>
                </message_translator>

                <date_formatter>
                    <ae:parameter name="type">date</ae:parameter>
                    <ae:parameter name="format">full</ae:parameter>
                </date_formatter>
                
            </translator>

            <translator domain="long_date">
              <date_formatter>
                <parameters>
                  <ae:parameter name="type">datetime</ae:parameter>
                  <ae:parameter name="format">EEEE, dd.MM.yyyy - HH:mm</ae:parameter>
                </ae:parameters>
              </date_formatter>
            </translator>
            
            <translator domain="medium">
              <date_formatter>
                <ae:parameters>
                  <ae:parameter name="type">datetime</ae:parameter>
                  <ae:parameter name="format">dd.MM.yyyy</ae:parameter>
                </ae:parameters>
              </date_formatter>
            </translator>
        </translators>
        
    </ae:configuration>
    
    <ae:configuration environment="development.*">
        <translators default_domain="default">
            <translator domain="default">
                <message_translator class="AgaviGettextTranslator">
                    <ae:parameter name="store_calls">%core.cache_dir%/data</ae:parameter>
                    <!-- other params are inherited from generic config -->
                </message_translator>
            </translator>
        </translators>
    </ae:configuration>
    
</ae:configurations>

As you can see, you can use the AgaviSimpleTranslator or the AgaviGettextTranslator (or roll out your own database-backed translator). For simple applications gettext or the simple mode will probably suffice long enough. If you want to translate validation error messages you have to specify the translation_domain parameter like in the datetime validator example.

Use the translation manager and the defined translations in your code like this:
// display date value in dd.MM.yyyy format (see translators defined above)
echo $tm->_d($field['value'], 'medium');

// display value of ...parameter name="Wrong Password"... element according to current locale in specified domain
echo $tm->_('Wrong Password', 'default.errors.Login');

// display value in a different currency (using british pounds for this call instead of default for current locale)
echo $tm->_c(1200, null, '@currency=GBP');

// display time based on specific timezone instead of current locale
echo $tm->_d(time(), null, '@timezone=Europe/London');
How do I use and customize a currency format?
It's a difference if you want to change the currency symbol in use or the number format pattern used. Just for the record: Agavi uses the CLDR of the Unicode Consortium. See the CLDR website for more information. If you want Agavi to not use the predefined CLDR formatting patterns, you can use your own formats by modifying the translations.xml file:
<currency_formatter>
    <ae:parameter name="format">
        <ae:parameter name="en">¤#,##0.00</ae:parameter>
        <ae:parameter name="de">#.##0,00 ¤</ae:parameter>
    </ae:parameter>
</currency_formatter>
The currency formatting follows the same rules as the number formatting. The only exception is the special symbol "¤". This symbol is the Unicode Character 'CURRENCY SIGN' (U+00A4) and is used as a placeholder for the currency symbol. To use a different currency than the one currently set on the active locale use:
$tm->_c(123.45, 'some.domain', '@currency=GBP');
You can globally change the default currency for a locale in your translations.xml.
question
some nice description and example...

Back to the top of this page


Filter related topics

What kind of filters are available by default and how to configure them?
Agavi ships with action and global filters. Filters are run in a chain. The sequences are determinated in app/config/action_filters.xml and app/config/global_filters.xml respectively. The wrapping of filters allows you, to let each filter alter the request and response. The AgaviExecutionTimeFilter can be used as followed in action_filters.xml:
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations xmlns="http://agavi.org/agavi/config/parts/filters/1.0"
                   xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0">

    <!-- only for web contexts in development envs -->
    <ae:configuration environment="development.*" context="web">
        <filters>

            <filter name="ExecutionTimeFilter" class="AgaviExecutionTimeFilter">
                <!--
                     insert a comment with the total runtime of that container in the response's
                     source code (adds the total page runtime and one comment for each slot)
                -->
                <ae:parameter name="comment">true</ae:parameter>
                
                <!-- only run for HTML to let JSON etc. unaltered -->
                <ae:parameter name="output_types">
                    <ae:parameter>html</ae:parameter>
                </ae:parameter>
            </filter>

        </filters>
    </ae:configuration>

</ae:configurations>
Agavi uses the filter mechanism internally e.g. for action execution (see AgaviExecutionFilter) or forcing credential constraints for secure actions (see AgaviSecurityFilter). If action filters are not enough for you, you may define filters, that are run globally. A global_filters.xml example file could therefore look like this:
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations xmlns="http://agavi.org/agavi/config/parts/filters/1.0"
                   xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0">

    <!-- this section is only for web contexts -->
    <ae:configuration context="web">
        <filters>

            <!-- TidyFilter tries to clean up the HTML code for you -->
            <filter name="TidyFilter" class="AgaviTidyFilter">

                <!-- only run on HTML instead of JSON and other output types you may have defined -->
                <ae:parameter name="output_types">
                    <ae:parameter>html</ae:parameter>
                </ae:parameter>

                <!-- don't let errors stop it -->
                <ae:parameter name="ignore_errors">true</ae:parameter>

                <!-- set some options to the filter -->
                <ae:parameter name="tidy_options">
                    <ae:parameter name="output-xhtml">true</ae:parameter>
                    <ae:parameter name="numeric-entities">true</ae:parameter>
                    <ae:parameter name="clean">true</ae:parameter>
                    <ae:parameter name="indent">true</ae:parameter>
                </ae:parameter>
                
                <!-- default encoding to use on any web apps is utf-8 of course -->
                <ae:parameter name="tidy_encoding">utf8</ae:parameter>
            </filter>

            <!--
                 FormPopulationFilter tries to automagically pre/repopulate form fields and
                 insert specific and general error messages from validators etc. in your page
                 at positions (and with markup) you find suitable for your application  
            -->
            <filter name="FormPopulationFilter" class="AgaviFormPopulationFilter">

                <ae:parameters>

                    <!-- only run for request method "write" (=POST on web) by default (can be changed at runtime, of course) -->
                    <!-- if you omit this, it will never run -->
                    <ae:parameter name="methods">
                        <ae:parameters>
                            <ae:parameter>write</ae:parameter>
                        </ae:parameters>
                    </ae:parameter>

                    <!-- only run for output type "html" (so it doesn't break on, say, JSON data) -->
                    <!-- if you omit this, it will run for all output types -->
                    <ae:parameter name="output_types">
                        <ae:parameters>
                            <ae:parameter>html</ae:parameter>
                        </ae:parameters>
                    </ae:parameter>

                    <!-- error message insertion rules -->
                    <!-- they are run in sequence; once the first one matched, execution stops -->
                    <!--
                        errors that belong to more than one field (e.g. date validator) can be handled using "multi_field_error_messages"
                        "normal" errors are handled through "field_error_messages"
                        errors that yield no match and those that have no corresponding field are inserted using rules defined in "error_messages".
                    -->

                    <!--
                        for all field error messages.
                    -->
                    <ae:parameter name="field_error_messages">
                        <!--
                            ${htmlnsPrefix} is either empty (for HTML) or something like "html:" for XHTML documents with xmlns="..." notation.
                            Always use this, makes your code more bullet proof. XPath needs the namespaces when the document is namespaced
                        -->

                        <!-- all input fields that are not checkboxes or radios, and all textareas -->
                        <ae:parameter name="self::${htmlnsPrefix}input[not(@type='checkbox' or @type='radio')] | self::${htmlnsPrefix}textarea">
                            <!-- if this rule matched, then the node found by the rule is our starting point for inserting the error message(s). -->

                            <!-- can be any of "before", "after" or "child" (to insert as prev, next sibling or last child) -->
                            <ae:parameter name="location">after</ae:parameter>
                            <!-- a container groups all errors for one element. ${errorMessages} is a string containing all errors (see below) -->
                            <ae:parameter name="container"><![CDATA[<div class="errors">${errorMessages}</div>]]></ae:parameter>
                            <!--
                                this defines the HTML for each individual error message; those are then put into the container.
                                ${errorMessage} is the error message string
                            -->
                            <ae:parameter name="markup"><![CDATA[<p class="error">${errorMessage}</p>]]></ae:parameter>
                        </ae:parameter>

                        <!-- all other inputs - note how we select the parent element and insert ourselves as last child of it -->
                        <ae:parameter name="parent::*">
                            <ae:parameter name="location">child</ae:parameter>
                            <ae:parameter name="container"><![CDATA[<div class="errors">${errorMessages}</div>]]></ae:parameter>
                            <ae:parameter name="markup"><![CDATA[<p class="error">${errorMessage}</p>]]></ae:parameter>
                        </ae:parameter>
                    </ae:parameter>

                    <!--
                    <parameter name="multi_field_error_messages">
                    </parameter>
                    -->

                    <!-- everything that did not match any of the rules above, or errors that do not belong to a field -->
                    <ae:parameter name="error_messages">
                        <!-- insert before the element -->
                        <!-- that can be an input, or a form, if the error does not belong to a field or didn't match anywhere else -->
                        <ae:parameter name="self::*">
                            <ae:parameter name="location">before</ae:parameter>
                            <!-- no container here! we just insert paragraph elements -->
                            <ae:parameter name="markup"><![CDATA[<p class="error">${errorMessage}</p>]]></ae:parameter>
                        </ae:parameter>
                    </ae:parameter>

                </ae:parameters>
            </filter>

        </filters>
    </ae:configuration>

    <-- disable the tidy filter for all development* environments to force devs to think about valid code ;) -->
    <ae:configuration environment="development.*">
        <filters>
            <filter name="TidyFilter" enabled="false" />
        </filters>
    </ae:configuration>

    <-- try to relax things a bit for all production related environments -->
    <ae:configuration environment="production.*">
        <filters>
            <filter name="FormPopulationFilter">
                <ae:parameter name="ignore_parse_errors">true</ae:parameter>
            </filter>
        </filters>
    </ae:configuration>

</ae:configurations>
It first uses the AgaviTidyFilter to cleanup code that may contain some markup glitches (think CMS or user-generated content). After that the AgaviFormPopulationFilter runs, repopulates values to your various form fields (inputs, textareas and the like) and inserts several types of error messages into your document. You may use XPath expressions as you like to insert input field specific or common messages with whatever container markup you prefer. For development environments the tidy filter is disabled and for production environments the form population filter is set to ignore parse errors.
What's the execution order of filters?
When you define multiple filters in global_filters.xml or action_filters.xml files, the markup order is essential in certain situations. The execution order of filters should be:
TOP  -->  BOTTOM  -->  DISPATCHFILTER  -->  BOTTOM  -->  TOP
This means, that dependant on the things your filters are doing in the chain BEFORE or AFTER the request dispatching you may have to move them up or down in the XML file to get your ideal execution order.

Back to the top of this page


Testing related topics

How do I start?
Try using the sample application's sample/test directory as a start and after copying stuff to your own project try editing /test/config/suites.xml to have your own test suites and tests. Modify run-tests.php and add some required files to the autoloader for libraries you need.
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0"
                      xmlns="http://agavi.org/agavi/config/parts/testing/suites/1.0">
  <ae:configuration>
    <suites>
      <suite name="Default">
        <testfiles>
          <testfile>OneNiceUnitTest.php</testfile>
          <testfile>AndSomeNiceFlowTest.php</testfile>
        </testfiles>
      </suite>
      <suite name="Login">
        <testfiles>
          <testfile>AnotherNiceUnitTest.php</testfile>
          <testfile>AndAnotherNiceFlowTest.php</testfile>
        </testfiles>
      </suite>
    </suites>
  </ae:configuration>
</ae:configurations>
After creating test cases and suites you may run them like this (or whatever, just have a look at the available commandline parameters):
php -c php.ini run-tests.php "TextUI/Command.php" --verbose
question
some nice description and example...
question
some nice description and example...
question
some nice description and example...
question
some nice description and example...
question
some nice description and example...

Back to the top of this page


Misc topics

How do I improve code completion in Netbeans or Eclipse or $favoriteIDE?
Netbeans (6.5+) or Eclipse (PDT) offer a good code completion by default already. To further improve things and save on some keystrokes you may use type hinting or an autocomplete file to help those IDEs.
// save the following as autocomplete.php somewhere in your project structure (or use it via project includes),
// it should give you code completion in Agavi templates for assigns (defined in app/config/output_types.xml):
<?php
exit();
$slots = array();
$template = array(); // agavi 0.11 default
$t = array(); // agavi 1.0 default
$tm = new AgaviTranslationManager();
$ro = new AgaviWebRouting();
$rq = new AgaviWebRequest();
$ct = new AgaviController();
$us = new AgaviSecurityUser();
$rd = new AgaviWebRequestDataHolder();
?>
In models, views and actions you better use some nice PHP documentation to give your IDE as much information about parameters and return values as you can. Further on you may use type hinting like this:
/* @var $author AuthorModel */
$author = $this->getContext()->getModel('Author');
If that's still not good enough for you, you can try to further enhance your project's base view like this:
class ProjectBaseView extends AgaviView
{
    /**
     * @var AgaviWebRequest
     */
    protected $rq = null;

    /**
     * @var AgaviWebRouting
     */
    protected $ro = null;

    /**
     * @var AgaviController
     */
    protected $ct = null;

    /**
     * @var AgaviWebResponse
     */
    protected $rs = null;

    /**
     * @var AgaviSecurityUser
     */
    protected $us = null;

    /**
     * @var AgaviTranslationManager
     */
    protected $tm = null;
    
    /**
     * @var AgaviValidationManager
     */
    protected $vm = null;
    
    /**
     * @var AgaviDatabaseManager
     */
    protected $dbm = null;
    
    /**
     * @var AgaviContext
     */
    protected $ctx = null;
    
    /**
     * @var bool
     */
    protected $isSlot = false;

    /**
     * @see AgaviView::initialize()
     */
    public function initialize(AgaviExecutionContainer $container)
    {
        parent::initialize($container);

        $this->ctx = $this->getContext(); 
        $this->dbm = $this->ctx->getDatabaseManager();
        $this->rq  = $this->ctx->getRequest();
        $this->ro  = $this->ctx->getRouting();
        $this->ct  = $this->ctx->getController();
        $this->us  = $this->ctx->getUser();
        $this->tm  = $this->ctx->getTranslationManager();
        $this->vm  = $this->ctx->getValidationManager();
        $this->rs  = $this->getResponse();

        $this->isSlot = $container->hasParameter('is_slot');

        return true;
    }
}
Not that you NEED this (or should use it), but some people may like these shortcuts. Have fun. :)
How do I set an exit code in console context or shell applications?
Just like response attributes and headers you are able to set the exit code in your views:
$this->getResponse()->setExitCode(1);
With this your Agavi PHP shell/console applications can now return correct status codes to your shell depending on success/error. :-)

Back to the top of this page