Zend Framework CheckList

Introduction

This document is a simple collection of notes about the Zend Framework.

I often ask myself: What should I do in order to use this feature?

I have already used this feature, but I can't exactly remember how… Something is missing. I spent time reading old codes again and again.

This document is a kind of check list. It shows the big steps.

Author: Denis BEURIVE

The INI file

Array

resources.myResourceName.myResourceValue.0 = ...
resources.myResourceName.myResourceValue.1 = ...
resources.myResourceName.myResourceValue.2 = ...
...

Hash

resources.myResourceName.myResourceValue.key1 = value1
resources.myResourceName.myResourceValue.key2 = value2
resources.myResourceName.myResourceValue.key3 = value3
...

Warning

A resource is initialized only if it appears in the INI file! If you are using the "view" resource, you must, at least, put in your INI file :

resources.view =

Warning

Use this: APPLICATION_PATH "/../logs/application.log"

Do NOT use this (_NEVER_): APPLICATION_PATH '/../logs/application.log'

The second form will add a space (' ') in the middle of the concatenation!!!!!

The .htaccess

# Ligne ajoutée 
SetEnv APPLICATION_ENV development

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
# RewriteRule !\.(js|ico|gif|jpg|css|htm|jpeg|jgz|swf)$ index.php

RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]

Class Auto Loader

In the INI file

autoloaderNamespaces.0 = "My"
autoloaderNamespaces.1 = "MyOther"
...

Or, using PHP code

// Déclaration d'un espace de nom pour les classes.
Zend_Loader_Autoloader::getInstance()->registerNamespace('My_');
Zend_Loader_Autoloader::getInstance()->registerNamespace('Libs_');

Resource Auto Loader

// Déclaration de l'espace de nom **des ressources** applicatives.
// On déclare le chemin vers une ressource, désignée par son espace de nom.
// En l'occurrence : La ressource, désignée par l'espace de nom "Application", est stockée dans le répertoire "APPLICATION_PATH".

// NOTE: Les autoloaders de ressources s'enregistrent dans l'autoloader à leur instanciation.
//       http://framework.zend.com/manual/fr/zend.loader.autoloader-resource.html
// NOTE: Une classe qui commence par "Application_" sera considérée comme une "ressource applicative".

$resourceLoader = new Zend_Loader_Autoloader_Resource(  array( 'namespace' => 'Application', 'basePath' => APPLICATION_PATH));

// On déclare un "type" de "ressource applicative" (le type "model", en l'occurence).
// En l'occurrence, les ressources de type "model", sont stockées dans le sous-répertoire "model/" du répertoire des "ressources applicatives", soit:
//   APPLICATION_PATH . DIRECTORY_SEPARATOR . 'models'.
// Et les noms des classes qui implémentent ce type de "ressource applicative" commencent par : "Application_Model"

// First arg: An arbitrary name, that identifies the type of resource.
// Second arg : The subdirectory, within the resource directory, where to store all classes.
// Third arg : The classes' prefix.

$resourceLoader->addResourceType('model', 'models/', 'Model');

Action Helpers

The action's helper (File: My/ActionsHelpers/MyHelper.php)

class My_ActionsHelpers_MyHelper extends Zend_Controller_Action_Helper_Abstract
{
    public function direct(...) { }

    private function __getBootstrap() { return $this->getFrontController()->getParam('bootstrap'); }

    private function __getResource($inName) { return $this->__getBootstrap()->getResource($inName); }
}

Before the bootstrap (File: public/index.php)

// Juste avant la ligne "$application->bootstrap()->run();"!!!!
Zend_Controller_Action_HelperBroker::addPrefix('My_ActionsHelpers_');
$application->bootstrap()->run();

In the controller's action

class IndexController extends Zend_Controller_Action
{
  public function indexAction()
  {
    $this->_helper->MyHelper(...);
  }
  ...
}

Initializing resources from Bootstrap

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initSession()
    {
        Zend_Session::start();
        $session = new Zend_Session_Namespace('user', true);
        return $session;

        // http://framework.zend.com/manual/fr/zend.application.theory-of-operation.html
        // Pour initialiser la ressource depuis une autre méthode du fichier "Bootstrap.php".
        // $this->bootstrap('session');
        // Pour accéder à la session depuis une autre méthode du fichier "Bootstrap.php":
        // $bootstrap->getResource('session');
    }

    protected function _initRessource()
    {
        $this->bootstrap('session');
        $session = $bootstrap->getResource('session');
    }
}

Resource plugins

The resource's plugin (File: My/Resources/Googleajaxsearchapikey.php)

ATTENTION !!! Le nom du fichier PHP doit se composer uniquement de caractères minuscules, sauf le premier caractère!

class My_Resources_Googleajaxsearchapikey extends Zend_Application_Resource_ResourceAbstract
{
    const DEFAULT_REGISTRY_KEY = 'googleAjaxSearchApiKey';

    protected $_key = null;

    /** Cette propriété définit, explicitement, le nom de la ressource ($bootstrap->bootstrap(<nom>); $rcs = $bootstrap->getResource(<nom>);).
     */
    public $_explicitType = 'googleAjaxSearchApiKey';

    /** Initialise la ressource.
     *  @return La méthode retourne la "valeur" de la ressource.
     */
    public function init()
    {
        // La valeur de la ressource sera automatiquement stockée dans le registre, associée à la clé "$this->_explicitType".
        return $this->__getGoogleAjaxSearchApiKey();
    }

    private function __getGoogleAjaxSearchApiKey()
    {
        if (null === $this->_key)
        {
            $options  = $this->getOptions();
            $bootStart = $this->getBootstrap();

            // On s'assure que les ressources utilisées ont été initialisées au préalable.
            $bootStart->bootstrap('view');
            $view = $bootStart->getResource('view');

            ...            

            $key =    (isset($options['registry_key']) && !is_numeric($options['registry_key']))
                    ? $options['registry_key']
                    : self::DEFAULT_REGISTRY_KEY;

            Zend_Registry::set($key, $this->_key);
        }

        return $this->_key;
    }
}

In the INI file

pluginPaths.My_Resources   = APPLICATION_PATH "/../library/My/Resources"

resources.googleAjaxSearchApiKey.value = "ABQIA..."

Warning

A resource is initialized only if it appears in the INI file! If you are using the "view" resource, you must, at least, put in your INI file :

resources.view =

Get resource from controller

public function loginAction()
    {
        // L'utilisateur est-il déjà identifié?
        $session = $this->getFrontController()->getParam('bootstrap')->getResource('session');
        ...
    }

Get resource from view

$front = Zend_Controller_Front::getInstance();
$session = $front->getParam('bootstrap')->getResource('session');

Form validators

The validator (File: My/Validators/Login.php)

class My_Validators_Login extends Zend_Validate_Abstract
{

    const NOT_MATCH = 'notMatch';
    const MIN       = 'notMin';
    const MAX       = 'notMax';
    const FIRST     = 'notFirst';

    public $minimum = 6;
    public $maximum = 12;

    protected $_messageVariables = array
    (
        'min' => 'minimum',
        'max' => 'maximum'
    );

    protected $_messageTemplates = array
    (
        self::NOT_MATCH => "Le login '%value%' que vous avez entré n'est pas valide. Seuls les caractères suivants sont autorisés : Les lettres non accentuées, les chiffres (de 0 à 9), le point (.), le souligné (_) et le tiret (-).",
        self::MIN       => "Le login '%value%' que vous avez entré n'est pas valide. La taille minimum autorisée est de '%min%' caractères.",
        self::MAX       => "Le login '%value%' que vous avez entré n'est pas valide. La taille maximale autorisée est de '%max%' caractères.",
        self::FIRST     => "Le login '%value%' que vous avez entré n'est pas valide. Un login doit commencer par une lettre non accentuée."
    );

    public function isValid($inValue)
    {
        $isValid = true;
        $this->_setValue($inValue);

        if (strlen($inValue) < $this->minimum)
        {
            $this->_error(self::MIN);
            $isValid = false;
        }

        if (strlen($inValue) > $this->maximum)
        {
            $this->_error(self::MAX);
            $isValid = false;
        }

        if (! preg_match('/^[a-z]/i', $inValue))
        {
            $this->_error(self::FIRST);
            $isValid = false;
        }

        if (! preg_match('/^[a-z0-9_\.\-]+$/i', $inValue))
        {
            $this->_error(self::NOT_MATCH);
            $isValid = false;
        }

        return $isValid;
    }
}

In the formular's class

$login = new Zend_Form_Element_Text('login');
$login->setRequired(true)->addValidator('login');

In the INI file

autoloaderNamespaces.0 = "My"

How to set a custom message to validators?

$valLoginNotEmpty = new Zend_Validate_NotEmpty();
$valLoginNotEmpty->setMessage("Vous n'avez pas spécifié de login.");

$numberRegExp = new Zend_Validate_Regex('/^0?\d{9}$/');
$numberRegExp->setMessage("Le numéro de téléphone que vous avez saisi n'est pas valide. Un numéro de téléphone se compose de 9 ou 10 chiffres (le zéro, en tête de numéro est facultatif).");

// Warning! Setting a translation may interfer with the validator' settings.        

$login->setRequired(true)->addValidator($valLoginNotEmpty, true);
$numero->addValidator($numberRegExp, true);

How to write a validator that has access to all the formular's fields?

/**
 * Ce validateur vérifie qu'au moins un champ du formulaire est renseigné, sur une liste de champs.
 * Doc: http://framework.zend.com/manual/en/zend.form.elements.html#zend.form.elements.validators
 * @note CF notes sur: Validation Context
 *                     setAllowEmpty(false)
 *                     setRequired(true)
 *                     
 */

class My_Form_Validator_OneAmongSeveral extends Zend_Validate_Abstract
{
    const          MISSING = 'oneAmongSeveralMissing';
    protected      $_messageTemplates = array();
    private        $__entries = null;
    private static $__ok = false;

    /**
     * @param $inEntries Ce tableau contient la liste des noms des champs.
     * @param $inMessage Message à renvoyer en cas d'erreur.
     */

    public function __construct($inEntries, $inMessage)
    {
        $this->__entries = $inEntries;
        $this->_messageTemplates[self::MISSING] = $inMessage;
    }

    /**
     * The array "$context" contains all the formulars' fields. 
     */

    public function isValid($value, $context = null)
    {
        if (self::$__ok) { return true; }
        $value = (string) $value;
        $this->_setValue($value);

        if (is_array($context))
        {
            foreach ($this->__entries as $entry)
            {
                if (isset($context[$entry]))
                if (! empty($context[$entry]))
                { self::$__ok = true; return true; }
            }
        }

        $this->_error(self::MISSING);
        return false;
    }
}

Utilisation

        // Validateurs pour les numéros de téléphone.
        $numberRegExp = new Zend_Validate_Regex('/^(0?\d{9})?$/');
        $numberRegExp->setMessage("Le numéro de téléphone que vous avez saisi n'est pas valide. Un numéro de téléphone se compose de 9 ou 10 chiffres (le zéro, en tête de numéro est facultatif).");
        $oneAmongSeveral = new My_Form_Validator_OneAmongSeveral(array('callingNumber', 'calledNumber'), "Vous devez, au minimum, spécifier un numéro de téléphone : Le numéro de téléphone de l'appelant, ou celui de l'appelé.");

        $callingNumber->setLabel('Numéro appellant')
                      ->setDescription("Veuillez saisir le numéro de téléphone de l'appelant.")
                      ->setAllowEmpty(false)   // <= TRES IMPORTANT
                      ->addValidator($oneAmongSeveral, true)
                      ->addValidator($numberRegExp);
        $calledNumber->setLabel('Numéro appelé')
                     ->setDescription("Veuillez saisir le numéro de téléphone de l'appelé.")
                     ->setAllowEmpty(false)   // <= TRES IMPORTANT
                     ->addValidator($oneAmongSeveral, true)
                     ->addValidator($numberRegExp);

In the view…

<?php $front = Zend_Controller_Front::getInstance(); ?>
<?php echo $front->getRequest()->getModuleName(); ?>
<?php echo $front->getRequest()->getActionName(); ?>
<?php echo $front->getRequest()->getControllerKey(); ?>
<?php echo $front->getRequest()->getControllerName(); ?>
<?php $params = $front->getRequest()->getParams(); ?>
<?php echo $this->escape(...); ?>
<?php echo $this->htmlList(...); ?>

<?php
    $front = Zend_Controller_Front::getInstance();
    $params = $front->getRequest()->getParams();
    echo $this->action($action, $controller, null, $params);

    $session = $front->getParam('bootstrap')->getResource('session');
?>

<?php echo $this->baseUrl('css/base.css'); ?>
<?php $this->headMeta()->...; ?>
<?php $this->headScript()->...; ?>
<?php $this->headStyle()->...; ?>
<?php $this->headTitle(...); ?>

In the action

// Clear all HTTP headers.
$this->getResponse()->clearAllHeaders(); // Optionnel.

// Disable the layout.
$this->_helper->layout->disableLayout();

// Disable the rendering of the current view.
$this->_helper->viewRenderer->setNoRender();

// Return JSON data.
$this->_helper->json($result);

// Rendering another view that the action's view.
$this->_helper->viewRenderer('the-new-view');

// Redirect with parameters
$redirector = $this->_helper->getHelper('Redirector');
$redirector->setGotoSimple($action, $controller, null, $inParams);
$redirector->redirectAndExit();

View helpers

The view helper (File: application/views/standard/helpers/Tagger.php)

class Application_Standard_View_Helper_Tagger
{
    // Warning: The name of the function is "tagger"... like the name of the PHP file.
    public function tagger() { return 'This is the "Application" view helper for the "Standard" terminal!'; }
}

In the bootstrap code (plugin or Bootstrap.php)

$bootStart = $this->getBootstrap();
// Make sure that the "view" resource is bootstraped.
$bootStart->bootstrap('view');
// Get the "view" resource.
$view = $bootStart->getResource('view');
$view->addHelperPath(APPLICATION_PATH . "/views/standard/helpers", "Application_Standard_View_Helper");

_OR_ in the INI file

resources.view.encoding = "UTF-8"
resources.view.helperPath.Application_Standard_View_Helper = APPLICATION_PATH . "/views/standard/helpers"

In the view

<?php echo $this->tagger(); >

In a decorator

$formDecorator    = new Zend_Form_Decorator_Form();
$formDecorator->setHelper('tagger');

In the layout…

The layout is a view, like other views. All you can do in a view, you can do in the layout.

<head>
    ...
    <?php echo $this->headLink(); ?>
    <?php echo $this->headScript(); ?>
    <?php echo $this->headTitle(); ?>
    ...
</head>
<body>
    ...
    <?php echo $this->layout()->content; ?><p/>   <!-- Or whatever configured key ...  -->
    ...
</body>

The "place holder"

In the bootstrap file (File: Bootstrap.php)

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initInlineJs()
    {
        $this->bootstrap('View');
        $view = $this->getResource('View');

        $view->placeholder('inlineJs')
             // "prefix" -> markup to emit once before all items in collection
             ->setPrefix("\n<script  language=\"javascript\">\n")
             // "separator" -> markup to emit between items in a collection
             ->setSeparator("\n")
             // "postfix" -> markup to emit once after all items in a collection
             ->setPostfix("\n</script>\n");
    }
}

Anywhere in the code (Ex: in a form decorator)

class My_Forms_Decorators_SubmitLink extends Zend_Form_Decorator_Abstract
{
    public function render($content)
    {
        ...
        $view = $element->getView();
        $markup = sprintf($this->_format, $id, $label);
        $js = "...";

        // Le "placeholder" doit être initialisé dans le fichier "Bootstrap.php"!!!
        $view->placeholder('inlineJs')->append($js);

        return $markup;
    }
}

In the layout

<?php echo $this->placeholder('inlineJs'); ?>

Form decorator

A decorator can be used to create custom form's element.

The decorator (File: My\Forms\Decorators\SubmitLink.php)

class My_Forms_Decorators_SubmitLink extends Zend_Form_Decorator_Abstract
{
    public function render($content)
    {
        $element = $this->getElement();

        // Liste de toutes les méthodes disponibles:
        // http://framework.zend.com/apidoc/core/Zend_Form/Element/Zend_Form_Element.html#getAttrib

        $label   = htmlentities($element->getLabel());          // Le texte du lien.
        $id      = htmlentities($element->getId());             // l'ID du compasant HTML.
        $class   = htmlentities($element->getAttrib('class'));  // Le classe CSS.
        $formId  = htmlentities($element->getAttrib('formId')); // l'ID du formulaire associé.
        $view    = $element->getView();

        // You create the HTML for this element.
        $markup  = ...

        return $markup;
    }
}

Apply the decorator to an form element

$decorator = new My_Forms_Decorators_SubmitLink();
$submit = new Zend_Form_Element($submitId);
$submit->setLabel('Editer le template')
            ->setAttrib('class', 'myClass')
            ->setAttrib('formId', $formularId)
            ->setDecorators(array($decorator));

In the INI file

autoloaderNamespaces.0 = "My"

Data models

The data mapper (File: application/models/UsersMapper.php)

class Application_Model_UsersMapper
{
    private $__dbTable = null;

    public function __construct()
    {
        $this->__dbTable = new Application_Model_DbTable_Users();
        if (! $this->__dbTable instanceof Zend_Db_Table_Abstract) { throw new Exception('Invalid table data gateway provided'); }
    }

    public function getDbTable() { return $this->__dbTable; }

}

The table file (File: application/models/DbTable/Users.php)

class Application_Model_DbTable_Users extends Zend_Db_Table_Abstract
{
    /** Nom de la table. */
    protected $_name = 'users';

    /** Nom du champ qui représente la clé primaire de la table. */
    protected $_primary = 'idusers';

    /**
     * Cette variable contient la liste des tables qui "dépendent" de la présente table (de référence).
     * Une table "dépendante" présente une clé étrangère vers une table de "référence".
     */
    protected $_dependentTables = array ('profiles', 'participants', 'messages');
}

In the application's code (ex: in a crontroller)

class UserController extends Zend_Controller_Action
{
    ...

    private function __getUserMapper()
    {
        $db = new Application_Model_UsersMapper();
        $this->__userMapper = $db;
        return $this->__userMapper;
    }
    ...
}

Note

The model mechanism relies on the autoloader. We could place mappers and tables in other directories, as long as the autoloader is correctly configured.

Several databases used by one application.

In the file application/config/application.ini

app.db.base1.adapter               = "pdo_mysql"
app.db.base1.params.host           = "hostname1"
app.db.base1.params.username       = "user1"
app.db.base1.params.password       = "pass1"
app.db.base1.params.dbname         = "dbname1"
app.db.base1.isDefaultTableAdapter = false

app.db.base2.adapter               = "pdo_mysql"
app.db.base2.params.host           = "hostname2"
app.db.base2.params.username       = "user2"
app.db.base2.params.password       = "pass1"
app.db.base2.params.dbname         = "dbname2"
app.db.base2.isDefaultTableAdapter = false

In the file application Bootstrap.php

Databases' adapters are stored in the registry.

protected function _initApp()
{
    // On stocke la configuration globale.
    $config = $this->getOption('app');
    Zend_Registry::set('app', $config);

    // On crée les adaptateurs vers les bases de données.
    $databases = array();
    foreach ($config['db'] as $dbName => $dbConfig)
    {
        $databases[$dbName] = Zend_Db::factory($dbConfig['adapter'], $dbConfig['params']);
        if ($dbConfig['isDefaultTableAdapter']) { Zend_Db_Table_Abstract::setDefaultAdapter($databases[$dbName]); }
    }
    Zend_Registry::set('databases', $databases);

    return $config;
}

Creating the tables

    self::$_table11 = new Application_Model_Base1_DbTable_Table1(array('db' => self::_getAdapter('base1')));
    self::$_table21 = new Application_Model_Base2_DbTable_Table1(array('db' => self::_getAdapter('base2')));

    ...

    protected static function _getAdapter($inDbName)
    {
        $adapters = Zend_Registry::get('databases');
        return $adapters[$inDbName];
    }

Where to place models's definitions?

Assuming that you use the default application's resources' configuration :

// --- Base 1 ---

// class Application_Model_Base1_MyMapper
application/models/Base1/MyMapper.php

// class Application_Model_Base1_DbTable_Table1
application/models/Base1/DbTable/Table1.php

// --- Base 2 ---

// class Application_Model_Base2_MyMapper
application/models/Base2/MyMapper.php

// class Application_Model_Base2_DbTable_Table1
application/models/Base2/DbTable/Table1.php

You can change these settings.

// See section about the resource auto loader.

$resourceLoader = new Zend_Loader_Autoloader_Resource(...);
$resourceLoader->addResourceType(..., ..., ...);

Getting the MySql error code

public function addUser($inRecord, &$outError)
{
    try
    {
        $this->__userTable->insert($inRecord);
    }
    catch (Exception $e)
    {
        if (is_a($e->getPrevious(), 'PDOException'))
        { 
            // WARNING !!!
            // The values below depend on the adapter. The following code assumes that we are using MySql.
            // The values below may change depending on the version of MySql.
            $mySqlErrorCode    = $e->getPrevious()->errorInfo[1];
            $mySqlErrorMessage = $e->getPrevious()->errorInfo[2];

            if ($mySqlErrorCode == 1062)
            if (preg_match('/for\s+key\s+\'pseudoidx\'/', $mySqlErrorMessage))
            { $outError = 'pseudo'; return false; }
        }
        throw (new Exception("Une erreur interne est survenue. L'administrateur du site a été averti."));
    }
    return true;
}

Important points :

if (is_a($e->getPrevious(), 'PDOException'))
...
$mySqlErrorCode    = $e->getPrevious()->errorInfo[1]; // The numerical code
$mySqlErrorMessage = $e->getPrevious()->errorInfo[2]; // The textual error message

Typical form management

The action is used "directly".

    public function registerAction()
    {
        // We get the formular (Zend_Form).
        $this->__getRegisterForm();

        // We check to know if it is the first time that the form is called.
        if (! $this->getRequest()->isPost())
        {
            // The form is called for the first time.
            $this->view->registerForm = $this->__registerForm;

        }
        elseif (! $this->__registerForm->isValid($_POST))
        {
            // Some fields of the form are not valid!
            $this->view->registerForm = $this->__registerForm;
        }
        else
        {
            // All fields are individually valid.
            // Let's check the coherence of the overall.
            $code = null;
            $this->__getUserMapper();
            $this->__users->addUser(array(    'pseudo'          => $this->__registerForm->getValue('login'),
                                            'email'           => $this->__registerForm->getValue('email1'),
                                            'password'        => $this->__registerForm->getValue('password1'),
                                            'secret'          => uniqid(),
                                            'inscriptionDate' => new Zend_Db_Expr("NOW()")
                                            ), $code);

            if ($code === 'pseudo')
            {
                // All fields are individually valid. But there is a problem with the backend.
                $this->__registerForm->getElement('login')->setErrors(array("Le nom d'utilisateur que vous avez choisi est déjà utilisé."));
                $this->view->registerForm = $this->__registerForm;
            }
            else
            {
                // The form is OK.
                $this->view->registerForm = null;
                require_once '/Libs/Js/JQuery.php';
                JQery_Hide('messageBoard');
            }
        }

        // ...
    }

The action is used through the "action" view helper.

    public function loginAction()
    {
        // This is the important line:
        $params = $this->getRequest()->getParams();

        $this->__getLoginForm();

        if ((! isset($params['loginLogin'])) && (! isset($params['loginPassword'])))
        {
            $this->view->loginForm = $this->__loginForm;
            echo "FIRST TIME<br>";
        }
        elseif (! $this->__loginForm->isValid($params))
        {
            $this->view->loginForm = $this->__loginForm;
        }
        else
        {
            $this->__getUserMapper();

            $code     = null;
            $login    = $this->getRequest()->getParam('loginLogin');
            $password = $this->getRequest()->getParam('loginPassword');
            $user     = $this->__users->login($login, $password, $code);

            if (false === $user)
            {
                $this->__loginForm->setFinalErrorMessage("Les paramètres d'identification que vous avez entrés ne sont pas valides. Veuillez vérifier que vous avez rentré le bon nom et le bon mot de passe.");
                $this->view->loginForm = $this->__loginForm;
            }
            else
            {
                $this->view->loginForm = null;
            }
        }

        // ...
    }

And, in the view :

<?php
    $front = Zend_Controller_Front::getInstance(); 
    $params = $front->getRequest()->getParams();
    $loginFormular = $this->action('login', 'user', null, $params);
?>

Note about formulars

For Zend_Form_Element_Submit

  • The element's ID is the text used to create the element. Ex: $submit = new Zend_Form_Element_Submit('submit');
  • The text that appears on the buton is the element's label. $submit->setLabel('Envoyer');

Captchas

File: My\Captchas\Image.php

require_once 'Zend/Captcha/Image.php';

// See: http://zendframework.com/issues/browse/ZF-9557

class My_Captchas_Image extends Zend_Captcha_Image
{
    // public function __construct() { parent::__construct(); }

    public function getUseNumbers()
    {
        return $this->_useNumbers;
    }

    public function setUseNumbers($useNumbers)
    {
        $this->_useNumbers = $useNumbers;
        return $this;
    }

    protected function _generateWord()
    {
        // WARNING: Do **NOT** use uppercase letters !!!!
        $word = parent::_generateWord();
        $word = preg_replace(array('/i/', '/j/', '/x/', '/q/', '/r/'), array('l', 'l', 'h', 'p', 'o'), $word);
        $this->_setWord($word);
        return $word;
    }
}

Creating the captche

        const CAPTCHA_NAME = 'captchaimg';

        ...

        // Specific sesson is very important!
        $captchaSession = new Zend_Session_Namespace('My_Captcha_'. self::CAPTCHA_NAME);

        $captchaOpt = Zend_Registry::get('captcha');

        $captchaimg = New My_Captchas_Image(self::CAPTCHA_NAME);
        $captchaimg->setFont(WinCompat_Path($captchaOpt['fontPath']));
        $captchaimg->setImgDir(WinCompat_Path($captchaOpt['imageDir']));
        $captchaimg->setImgUrl($captchaOpt['imageUrl']);
        $captchaimg->setTimeout($captchaOpt['timeout']);
        $captchaimg->setHeight($captchaOpt['height']);
        $captchaimg->setWidth($captchaOpt['width']);
        $captchaimg->setWordLen($captchaOpt['wordLen']);
        $captchaimg->setDotNoiseLevel($captchaOpt['dotNoiseLevel']);
        $captchaimg->setLineNoiseLevel($captchaOpt['lineNoiseLevel']);
        $captchaimg->setUseNumbers($captchaOpt['useNumbers']);
        $captchaimg->setSession($captchaSession);

        $captcha   = new Zend_Form_Element_Captcha(self::CAPTCHA_NAME, array('captcha' => $captchaimg));

HTTP requests

See for :

Zend_Controller_Request_Http: http://framework.zend.com/apidoc/core/Zend_Controller/Request/Zend_Controller_Request_Http.html

The LOGs

In the INI file

resources.log.stream.writerName = "Stream"
resources.log.stream.writerParams.stream = APPLICATION_PATH "/../logs/application.log"
resources.log.stream.writerParams.mode = "a"

Somewhere in the code

$logger = Zend_Controller_Front::getInstance()->getParam('bootstrap')->getResource('log');
$logger->log("Search sorties ($condition)", Zend_Log::INFO);

ou bien:

In the INI file

log.path = APPLICATION_PATH "/../logs/application.log"

In the file "Bootstrap.php"

    protected function _initLogger()
    {
        $id       = uniqid();
        $config   = $this->getOption('log');
        $format   = '%timestamp% %priorityName% (%priority%) [' . $id . ']: %message%' . PHP_EOL;
        $redacter = new Zend_Log_Writer_Stream($config['path']);
        $formater = new Zend_Log_Formatter_Simple($format);
        $redacter->setFormatter($formater);
        $logger   = new Zend_Log();
        $logger->addWriter($redacter);

        Zend_Registry::set('logger', $logger);
        $logger->log("Start session \"$id\".", Zend_Log::DEBUG);
        return $logger;
    }

Redirection

class IndexController extends Zend_Controller_Action
{
    // Référence: http://framework.zend.com/manual/fr/zend.controller.actionhelpers.html

    private $__params = null;

    public function init()
    {
        $this->__params = $this->getRequest()->getParams();
    }

    public function indexAction()
    {
        $this->view->params = $this->__params;
    }

    public function destinationAction()
    {
        $this->view->params = $this->__params;
    }

    public function redirectAction()
    {
        $test = 1;

        $redirector = $this->_helper->getHelper('Redirector');

        // Le code ci-dessous provoque une redirection vers l'action "destination".
        // Les paramètres sont transmis.
        if (0 === $test)
        {
            $redirector->setCode(303)
                       ->setGotoSimple('destination', 'index', null, $this->__params);
            $redirector->redirectAndExit();
        }

        // Le code ci-dessous provoque une redirection vers l'action "destination".
        // La méthode setGotoUrl() ne permet pas de spécifier des paramètres.
        // Des paramètres peuvent, éventuellement être concaténés à l'URL de destination (manuellement).
        if (1 === $test)
        {
            $redirector->setCode(303)
                       ->setGotoUrl('/index/destination');
            $redirector->redirectAndExit();
        }
    } 
}

Partial views

Create a PHTML file into the "views directory":

application/views/partials/MenuActions.phtml

OK

In the view (could be the layout)*

<?php echo $this->partial('/partials/MenuActions.phtml'); ?>

Note: You can pass parameters to the partial view: http://framework.zend.com/manual/fr/zend.view.helpers.html

WEB services

Returning JSON from the action

$this->_helper->json($result);

Returning custom text from the action

$this->getResponse()->clearAllHeaders(); // Optionnel.
$this->_helper->layout->disableLayout();
$this->_helper->viewRenderer->setNoRender();
echo "my text here";

Output management

See the list of available methods:

http://framework.zend.com/apidoc/core/Zend_Controller/Response/Zend_Controller_Response_Abstract.html

Using the application's resources from outside the WEB environment.

This is a skeleton for the startup script:

// On définit le répertoire d'installation de l'application.
define('APPLICATION_PATH',   realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'application'));

// On définit le chemin vers le fichier de configuration.
define('CONFIGURATION_PATH', APPLICATION_PATH . DIRECTORY_SEPARATOR . 'configs' . DIRECTORY_SEPARATOR . 'application.ini');

// On définit l'environnement de l'application.
define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'development'));

// On définit les chemins de chargement des librairies PHP.
set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'library'),
    get_include_path(),
)));

// On configure le système d'auto-chargement des classes.
require_once 'Zend' . DIRECTORY_SEPARATOR . 'Loader' . DIRECTORY_SEPARATOR . 'Autoloader.php';

// Déclaration de l'espace de nom **des classes** pour le framework ZEND.
Zend_Loader_Autoloader::getInstance()->registerNamespace('Zend_');

// Déclaration de l'espace de nom **des ressources** applicatives.
// On déclare le chemin vers une ressource, désignée par son espace de nom.
// En l'occurrence : La ressource, désignée par l'espace de nom "Application", est stockée dans le répertoire "APPLICATION_PATH".

// NOTE: Les autoloaders de ressources s'enregistrent dans l'autoloader à leur instanciation.
//       http://framework.zend.com/manual/fr/zend.loader.autoloader-resource.html
// NOTE: Une classe qui commence par "Application_" sera considérée comme une "ressource applicative".
$resourceLoader = new Zend_Loader_Autoloader_Resource(    array(    'namespace'    => 'Application',
                                                                'basePath'    => APPLICATION_PATH));

// On déclare un "type" de "ressource applicative" (le type "model", en l'occurence).
// En l'occurrence, les ressources de type "model", sont stockées dans le sous-répertoire "model/" du répertoire des "ressources applicatives", soit:
//   APPLICATION_PATH . DIRECTORY_SEPARATOR . 'models'.
// Et les noms des classes qui implémentent ce type de "ressource applicative" commencent par : "Application_Model"
$resourceLoader->addResourceType('model', 'models/', 'Model');

// On charge la configuration.
$config = new Zend_Config_Ini(CONFIGURATION_PATH, APPLICATION_ENV);
Zend_Registry::set('config', $config);

// ------------------------------------------------------------------------
// Procédure spécifique à l'application.
// ------------------------------------------------------------------------

// Déclaration d'un espace de nom pour les classes.
Zend_Loader_Autoloader::getInstance()->registerNamespace('My_');
Zend_Loader_Autoloader::getInstance()->registerNamespace('Libs_');

// Connection à la base de données.
$databaseConfig  = $config->app->db->dbcdr;
$databaseHandler = Zend_Db::factory($databaseConfig->adapter, $databaseConfig->params);
if ($databaseConfig->isDefaultTableAdapter) { Zend_Db_Table_Abstract::setDefaultAdapter($databaseHandler); }
Zend_Registry::set('database', $databaseHandler);

$povHd = new Application_Model_Base1_PovMapper();

...

Sending emails

<?php

   // Ce script illustre l'utilisation de la classe Zend_Mail.
   // Usage: php mail.php -s <mail server ID> -a <accout ID> -t <destination email>

   // Configuration des serveurs de mail.
   // Vous pouvez ajouter d'autres serveurs d'email (Yahoo,...).
   // Pour chaque serveur ajouté, vous pouvez ajouter des comptes.
   $smtpConfigs = array('gmail.com' => array('address'   => 'smtp.gmail.com',
                                             'auth'      => 'login',
                                             'ssl'       => 'tls',
                                             'port'      => 587,
                                             'myAccount' => array('username' => 'myAccount@gmail.com',
                                                                  'password' => 'MyPassword',
                                                                  'identity' => 'MyIdentity')
                                            )
                       );

   // Configuration de l'environnement PHP.
   ini_set('include_path', ini_get('include_path') . ':/path/to/the/Zend/library');

   // Initialisation du chargeur de classe.
   require_once 'Zend' . DIRECTORY_SEPARATOR . 'Loader' . DIRECTORY_SEPARATOR . 'Autoloader.php';
   Zend_Loader_Autoloader::getInstance();

   // Analyse de la ligne de commande.
   $options = getopt('s:a:t:');
   if (FALSE === $options) { echo "ERROR: Invalid options.\n"; exit(1); }
   if (! array_key_exists('s', $options)) { echo "ERROR: Mandatory option -s is missing!\n"; exit(1); }
   if (! array_key_exists('a', $options)) { echo "ERROR: Mandatory option -a is missing!\n"; exit(1); }
   if (! array_key_exists('t', $options)) { echo "ERROR: Mandatory option -t is missing!\n"; exit(1); }
   $server      = $options['s'];
   $account     = $options['a'];
   $destination = $options['t'];

   // Test de cohérence.
   if (! array_key_exists($server, $smtpConfigs)) { echo "ERROR: Server \"$server\" does not exist!\n"; exit(1); }
   if (! array_key_exists($account, $smtpConfigs[$server])) { echo "ERROR: Account \"$account\" does not exist, for server \"$server\"!\n"; exit(1); }

   // Création de l'adaptateur de transport.
   $transport = new Zend_Mail_Transport_Smtp($smtpConfigs[$server]['address'], array('auth'     => $smtpConfigs[$server]['auth'],
                                                                                     'ssl'      => $smtpConfigs[$server]['ssl'],
                                                                                     'port'     => $smtpConfigs[$server]['port'],
                                                                                     'username' => $smtpConfigs[$server][$account]['username'],
                                                                                     'password' => $smtpConfigs[$server][$account]['password']));
   if (is_null($transport)) { echo "ERROR: Could not create transport adapter!\n"; exit(1); }

   // Création su mail.
   $mail = new Zend_Mail();
   $mail->setBodyText("Ceci est le texte de l'email.");
   $mail->setFrom($smtpConfigs[$server][$account]['username'], $smtpConfigs[$server][$account]['identity']);
   $mail->addTo($destination);
   $mail->setSubject('Sujet de test');

   // Envoi du mail.
   try { $mail->send($transport); }
   catch (Exception $e) { echo "ERROR: " . urlencode($e->getMessage()) . "\n"; exit(1); }

?>

Converting files into UTF8 (with no BOM)

Sometimes, you have to make sure that the PHP files you are using are UTF8 encoded, with no BOM. The following Perl script can help.

use strict;
use Encode;
use Encode::Guess;
use Getopt::Long;
use Data::Uniqid qw(suniqid uniqid luniqid);
use File::Temp qw(tmpnam);
use File::Slurp;

my $optDirectory = undef;
my $help         = undef;
my @files        = ();
my @templates    = ();

# -------------------------------------------------------------------------------
# Parsing the command line.
# -------------------------------------------------------------------------------

unless (
         GetOptions (
                      'directory=s' => \$optDirectory,
                      'help'        => \$help,
                    )
       )
{
  print STDERR "ERROR: Invalid command line. Use option --help to get help.\n";
  exit 1;
}

if (defined($help)) { help(); exit 0; }
$optDirectory = defined($optDirectory) ? $optDirectory : '.';

# -------------------------------------------------------------------------------
# Get the list of all files in the directory.
# -------------------------------------------------------------------------------

unless (opendir (FDDIR, "$optDirectory")) { print STDERR "ERROR: Can not open directory <$optDirectory>: $!\n"; exit 1; }
@files = readdir(FDDIR);
foreach my $file (@files) { if ($file =~ m/^.+\.php$/i) { push (@templates, $file); } }
closedir (FDDIR);

# -------------------------------------------------------------------------------
# Convert all templates into UYF8, without BOM.
# -------------------------------------------------------------------------------

foreach my $file (@templates)
{
  my $input            = undef;  # Path to the iput file.
  my $fileContent      = undef;  # Content of the file to convert into UTF8, without BOM.
  my $decoder          = undef;  # Charset's decoder.
  my $data             = undef;  # This string represents the content of the file to convert into Perl's internal representation.
  my $convertedContent = undef;  # This string represents the content of the file, converted into UTF8, without BOM.
  my $start            = undef;  # This string represents the three first bytes of the UTF8 converted string (that is $convertedContent).
  my $toSave           = undef;  # This string represents the string that will be written into the target file.
  my $tmpFile          = undef;  # Path to a temporary file.
  my $rv               = undef;

  print STDOUT "Converting file $optDirectory/$file\n";

  $input       = $optDirectory . '/' . $file;
  $fileContent = read_file($input, binmode => ':raw');
  unless(defined($fileContent)) { print STDERR "ERROR: Could not read template file <$fileContent>!\n"; exit 1; }

  $decoder = guess_encoding($fileContent, 'latin1');
  unless (ref($decoder)) { $decoder = guess_encoding($fileContent); }
  unless (ref($decoder)) { print STDERR "ERROR: Could not guess file encoding!"; exit 1; }

  $data             = $decoder->decode($fileContent);
  $convertedContent = encode('utf-8', $data);
  $start            = length($convertedContent) >= 3 ? unpack('H6', substr($convertedContent, 0, 3)) : undef;
  $toSave           = $start eq 'efbbbf' ? substr($convertedContent, 3) : $convertedContent;

  $tmpFile = tmpnam();
  print STDOUT "\tTemporary file: $tmpFile\n";
  $rv = write_file($tmpFile, {binmode => ':raw'}, $toSave);
  unless(defined($rv)) { print STDERR "ERROR: Could not write data into file <$tmpFile>.\n"; exit 1; }

  if (1 != unlink($input)) { print STDERR "ERROR: Could not remove file <$input>: $!\n"; }
  unless (rename($tmpFile, $input)) { print STDERR "ERROR: Could not rename file <$tmpFile> into <$input>: $!\n"; exit 1; }
  unlink ($tmpFile);
  chmod (0666, $input);
}

print STDOUT "done\n";
exit 0;

sub help
{
  print STDOUT "Usage: perl utf8.pl [--directory=<path to the directory that contains the PHP files to convert>] [--help]\n";
}