Zend Framework 1.8.x – documentation CHM & PDF

Updated on 2009-05-17 for 1.8.1 release.Attention for those which use Vista (thanks to Julien for the link).

If you are using Microsoft Internet Explorer under Windows XP SP2 or later and you are going to download in CHM format, you should « unblock » the file after downloading it, by right-clicking on it and selecting the properties menu item. Then click on the ’Unblock’ button. Failing to do this may lead to errors in the visualization of the file, due to a Microsoft bug.

| | PDF | CHM |
|English|||
| ^|||
|Deutsch|||
| ^|||
|Japanese|||
| ^|||
|French|||
| ^|||

Adding extensions to Zend Server

For my translator tool for Zend Framework, I needed the pspell extension to add a spell checker to the tool and I also wanted to test the PHP SVN extension. Unfortunately, pspell and svn are not included by default in Zend Server. There are 2 ways to add it to Zend Server: using Zend Server sources or using PECL. I followed the below instructions to install them on my Ubuntu 8.10.

Installing PSPELL via Zend Server sources

First of all, as suggested by Jess comment (see below), you have to check the availability of differents packages to compile a PHP extension:

autoconf -V | grep '^autoconf'
#autoconf (GNU Autoconf) 2.61
automake --version | grep '^automake'
#automake (GNU automake) 1.10.1
gcc --version | grep '^gcc'
#gcc (Ubuntu 4.3.2-1ubuntu12) 4.3.2

Second: Aspell and languages that you want to check:

apt-get install aspell libaspell-dev aspell-en aspell-fr ...

Third: pspell library:

apt-get install libpspell-dev

Fourth: retrieve Zend Server sources (in my case PE Edition but you can do the same with CE Edition by replacing ‘pe’ with ‘ce’):

apt-get install php-source-zend-pe

Fifth: configure and compile extension (I use here the default installation directory ‘/usr/local/zend’, exchange the occurrences with your installation dir)]:

cd /usr/local/zend/share/php-source/php-5.2.9/ext/pspell
/usr/local/zend/bin/phpize
./configure --with-pspell --with-php-config=/usr/local/zend/bin/php-config
make
make install

Last: enable it in Zend Server configuration:

and check your phpinfo:

Installing SVN via PECL

Of course, you also need autoconf, automake and gcc like described above.

First: you need subversion libraries:

apt-get install subversion libsvn-dev

Second: you use PECL to install the extension, SVN is a beta extension for the moment, so you need to define explicitly the version you want. Or you can change PECL configuration:

/usr/local/zend/bin/pecl install svn-0.5.0
# or
/usr/local/zend/bin/pecl config-set preferred_state beta
/usr/local/zend/bin/pecl install svn
/usr/local/zend/bin/pecl config-set preferred_state stable

You will have 2 questions: prefix of Subversion installation and prefix of the APR installation used with Subversion. If you didn’t change them during their installation, you just have to keep autodetect.

Last: enable it in Zend Server configuration:

and check your phpinfo:

Special thanks to Remi Woler for the help and Jess Portnoy for the suggestions.

Zend Framework CHM Compilation

Preparation

Get last version of Zend Framework
Follow installation’s intructions of this page
– I take example of english documentation, so all my link will refer to the english, you have to adapt for your preferred language.

Docbook to HTML

I make some modifications in the configuration file (‘manual/en/html.xsl.in’):

    <!-- Adding these lines-->
    <xsl:param name="suppress.navigation" select="0"/>
    <xsl:param name="chapter.autolabel" select="0" />
    <xsl:param name="navig.graphics" select="1" />
    <xsl:param name="navig.graphics.extension" select="'.png'" />
    <xsl:param name="navig.graphics.path">images/

The goal is to activate navigation with images at the top and the bottom of each page.
I also delete the prepend term ‘Chapter’. You need to put in ‘manual/en/html/images/’ this 9 images:

  • home: (http://www.mikaelkael.fr/dwld/home.png)
  • up: (http://www.mikaelkael.fr/dwld/up.png)
  • prev: (http://www.mikaelkael.fr/dwld/prev.png)
  • next: (http://www.mikaelkael.fr/dwld/next.png)
  • note: (http://www.mikaelkael.fr/dwld/note.png)
  • warning: (http://www.mikaelkael.fr/dwld/warning.png)
  • important: (http://www.mikaelkael.fr/dwld/important.png)
  • caution: (http://www.mikaelkael.fr/dwld/caution.png)
  • tip: (http://www.mikaelkael.fr/dwld/tip.png)

Furthermore, the created HTML pages link images in the directory ‘manual/en/html/modules_specs/figures/*’ instead of ‘manual/en/html/figures/*’. So you need to create the directory and copy the images into.

On the same point, under Windows, at the end of the HTML generation there is a copy of the directory ‘manual/en/modules_specs/figures/*’ to ‘manual/en/html/figures/*’. This create an unreachable directory ‘figures’. So I modify ‘manual/en/Makefile.in’:

# In html/index.html:
	@echo "Copying manual figures (recursively)..."
	-[ -d figures ] && cp figures/*.* html/module_specs/figures
    # instead of
	@echo "Copying manual figures (recursively)..."
	-[ -d figures ] && cp -r figures html/figures
# In clean:
	-rm -f html/module_specs/figures/*
    # instead of
	-rm -Rf html/figures

CSS enhancements

The purpose is to resemble the official site:
colour of link (#009EEB)

These are the modifications made in ‘manual/en/html/dbstyle.css’:

body {
	background: url("images/bkg_top.gif") repeat-x;
	margin: 6px 15px;
}
a {
	color:#009EEB;
}
h1 {
	margin: 0;
}
div.note, div.warning, div.tip {
	/*border: solid 1px green;*/
	background-color: #EEEEEE;
}
div.note img, div.warning img, div.tip img {
	/*border: solid 1px green;*/
}
pre.programlisting {
	border: 1px solid #D3E0EB;
	background-color: #EDF7FF;
}
div.navheader {
	/*border-bottom: solid 1px black;
	border-color: #aaa;*/
}
div.navfooter td {
	font-size: 0.80em;
}
a img {
	border: 0px;
}
div.navheader table {
	background: 50px 12px url("./images/logo_small.gif") no-repeat;
	page-break-before: always;
}
div.navheader img {
    position: relative;
	top: -14px;
}
div.table {
	text-align: center;
}
table {
	border: 0;
	border-collapse: collapse;
	margin: auto;
}
th {
	border-collapse: collapse;
	color: #444444;
}
td {
	border-collapse: collapse;
	color: #444444;
}
div.table th, div.table td {
	border: 1px solid #444444;
	font-size: 0.85em;
}
div.table th {
	background: #DDDDDD;
}
div.table p.title {
	font-size: 0.75em;
	margin-bottom: 0px
}

Tip: CSS in CHM

Problem with background image, they must be in html content with ‘<img>’ tag to be compile in CHM. See below.
no padding

Syntax highlighting PHP code

This is a PHP script which highlight the code after rendering HTML (« make » or « make -e ») and before CHM compilation. I create a dir ‘tools’ in my language dir (‘manual/en/tools’) and I put this script inside:

/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Documentation
 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id$
 */

/**
 * Run this code:
 *  - after HTML compilation ("make" or "make -e")
 *  - before CHM compilation ("C:/path/to/workshop/hhc htmlhelp.hhp")
 */

function highlight ($text)
{
    $code = $text{(2)};
    // Replace element create by html compilation
    $code = str_replace(array('&gt;' , '&lt;'), array('>' , ' all \n will create a new line, but we already have some
    $code = str_replace("\n", '', $code);
    // In English manual, there is space between })> and , we remode it
    $code = preg_replace('/(<br \/>)(\&nbsp\;)*(<\/span><\/code>)/', '$3', $code);
    $code = preg_replace('/(<br \/>)(\&nbsp\;)*(<\/span><\/code>)/', '$3', $code);
    return $text{(1)} . $code . $text{(3)};
}

class HTML_Filter extends FilterIterator
{

    public function accept ()
    {
        return (substr($this->current(), - 5) == '.html');
    }
}

$dir = new DirectoryIterator(dirname(__FILE__) . '/../html');
$filter = new HTML_Filter($dir);
foreach ($filter as $file) {
    $text = file_get_contents($file->getPathName());
    $text = preg_replace_callback('/(<pre class="programlisting">)(.*?)(<\/pre>)/s', 'highlight', $text);
    file_put_contents($file->getPathName(), $text);
}

CHM Compilation

You need to install HTML Help Workshop. You just call ‘C:/path/to/workshop/hhc’ on ‘manual/en/html/htmlhelp.hhp’.

Since background images aren’t import in CHM, we need to declare them like real images in HTML stream. There is 2 possibilities:
* the bad one, insert images in xml document like this:

<!-- for example in 'manual/en/refs/installation.xml' -->
<para>
<inlinegraphic align="center" fileref="../images/bkg_top.gif" format="PNG" scale="1" width="1" />
<inlinegraphic align="center" fileref="../images/logo_small.gif" format="PNG" scale="1" width="1" />
</para>

This result of an image of 1×1. I think it’s bad solution because the actual problem is HTML Help Workshop which omit to insert images only declared in css. (!!! Please note that I haven’t really test this modification !!!).

the other one, a modification of the highlight script to insert this line in the rendered ‘index.html’:

<!-- Before the ending tag body -->
<img src="images/bkg_top.gif" height="1" />
<img src="images/logo_small.gif" height="1" width="1" />

Other

Insert the version of ZF documentation (1.6.x) and why not SVN revision in ‘manual/en/manual.xml.in’:

/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Documentation
 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id$
 */

/**
 * Run this code:
 *  - after HTML compilation ("make" or "make -e")
 *  - before CHM compilation ("C:/path/to/workshop/hhc htmlhelp.hhp")
 */

define('WITH_ZF_VERSION', true);
define('WITH_SVN_VERSION', true);
define('UPDATE_LINK',
       'http://mikaelkael.fr/index.php?post/2008/08/21/Zend-Framework-CHM-Francais');

/**
 * Highlight a string
 */
function highlight ($text)
{
    $code = $text{(2)};
    // Replace element create by html compilation
    $code = str_replace(array('&gt;' , '&lt;'), array('>' , ' all \n will create a new line, but we already have some
    $code = str_replace("\n", '', $code);
    // In English manual, there is spaces between })> and, we remode it
    $code = preg_replace('/(<br \/>)(\&nbsp\;)*(<\/span><\/code>)/', '$3', $code);
    $code = preg_replace('/(<br \/>)(\&nbsp\;)*(<\/span><\/span><\/code>)/', '$3', $code);
    return $text{(1)} . $code . $text{(3)};
}

class HTML_Filter extends FilterIterator
{

    public function accept ()
    {
        return (substr($this->current(), - 5) == '.html');
    }
}

/**
 * Highlight all HTML files
 */
$dir = new DirectoryIterator(dirname(__FILE__) . '/../html');
$filter = new HTML_Filter($dir);
foreach ($filter as $file) {
    $text = file_get_contents($file->getPathName());
    $text = preg_replace_callback('/(<pre class="programlisting">)(.*?)(<\/pre>)/s',
                                  'highlight',
                                  $text);
    file_put_contents($file->getPathName(), $text);
}

/**
 * Retrieve ZF version
 * (assume that all trunk tree is available)
 */
$page_title = 'Zend Framework';
if (WITH_ZF_VERSION == true) {
    require_once dirname(__FILE__) . '/../../../../library/Zend/Version.php';
    $version = implode('.', array_slice(explode('.', Zend_Version::VERSION), 0, 2)) . '.x';
    $page_title .=  " $version";
}
/**
 * Retrieve SVN revision
 */
if (WITH_SVN_VERSION == true) {
    putenv('LANG=en_US.UTF-8');
    exec("svn info " . realpath(dirname(__FILE__) . '/..'), $svn_info);
    foreach ($svn_info as $info) {
        if (substr($info, 0, 9) == 'Revision:') {
            $svn = (int) substr($info, 10);
        }
    }
    $page_title .=  " (SVN $svn)";
}
/**
 * Modify index.html
 */
$index = file_get_contents(dirname(__FILE__) . '/../html/index.html');
$index = str_replace('<h2>Zend Framework</h2>',
                     "<h2 class=\"subtitle\">$page_title</h2>",
                     $index);
$index = preg_replace('#(<p class\="pubdate"\>)(.*?)(<\/p>)#',
        '$1Compilé le $2 (<a href="' . UPDATE_LINK . '">mise à jour</a>)$3',
        $index);
$index = str_replace('</body>',
                     '<img src="images/bkg_top.gif" height="1" />'
                   . PHP_EOL
                   . '<img src="images/logo_small.gif" height="1" width="1" />'
                   . PHP_EOL
                   . '</body>',
                     $index);
file_put_contents(dirname(__FILE__) . '/../html/index.html', $index);
/**
 * Make CHM
 */
exec('hhc ' . dirname(__FILE__) . '/../html/htmlhelp.hhp');

Result

See result!

Bugs

  • If you are using Microsoft Internet Explorer under Windows XP SP2 or later and you are going to download the documentation in CHM format, you should « unblock » the file after downloading it, by right-clicking on it and selecting the properties menu item. Then click on the ‘Unblock’ button. Failing to do this may lead to errors in the visualization of the file, due to a Microsoft bug.
  • If you are using Microsoft Internet Explorer under Windows Vista: see this.

Zend Framework 1.7.x – documentation CHM & PDF

Updated on 2008-12-24 for 1.7.2 release.
No update for 1.7.3 release.Attention for those which use Vista (thanks to Julien for the link).

If you are using Microsoft Internet Explorer under Windows XP SP2 or later and you are going to download in CHM format, you should « unblock » the file after downloading it, by right-clicking on it and selecting the properties menu item. Then click on the ’Unblock’ button. Failing to do this may lead to errors in the visualization of the file, due to a Microsoft bug.

| | PDF | CHM |
|Français|||
| ^|||
|English|||
| ^|||
|Deutsch|||
| ^|||
|日本語|||
| ^|||

Centraliser la gestion de Zend_Session et des Zend_Session_Namespace

La classe suivante me permet de centraliser la gestion des sessions grâce à Zend_Session.

class Mp_Session
{

    /**
     * Name of the actual application
     *
     * @var string
     */
    protected static $_application = null;

    /**
     * Default namespace
     *
     * @var string
     */
    protected static $_default_namespace = 'default';

    /**
     * @var array
     */
    protected static $_session_app = array();

    /**
     * Retourne le namespace de session de l'appli courante
     *
     * @param  string $namespace
     * @return Zend_Session_Namespace
     */
    private static function _getSessionApp ($namespace = null)
    {
        if ($namespace === null) {
            $namespace = self::$_default_namespace;
        }
        if (! isset(self::$_session_app{($namespace)}) || null === self::$_session_app{($namespace)}) {
            self::$_session_app{($namespace)} = new Zend_Session_Namespace(
                    self::getApplication() . '_' . ucfirst(
                            strtolower(
                                    $namespace)));
        }
        return self::$_session_app{($namespace)};
    }

    /**
     * Lit une variable de session
     * (de l'application en cours par défaut)
     *
     * @param string $cle
     * @param mixed $alternatif valeur par défaut
     * @return mixed
     */
    public static function get ($cle, $alternatif = null, $namespace = null)
    {
        if ($cle == '') {
            return null;
        }
        $session_app = self::_getSessionApp($namespace);
        if (isset($session_app->{$cle})) {
            return $session_app->{$cle};
        } else {
            return $alternatif;
        }
    }

    /**
     * Détruit une (ou plusieurs) variable de session
     *
     */
    public static function del ($namespace)
    {
        $session_app = self::_getSessionApp($namespace);
        $cles = func_get_args();
        $cles = array_shift($cles); // namespace
        foreach ($cles as $cle) {
            unset($session_app->{$cle});
        }
    }

    /**
     * Ecrit une variable de session
     *
     * @param string $cle
     * @param mixed $valeur
     * @return mixed
     */
    public static function set ($cle, $valeur, $namespace = null)
    {
        if ($cle == '') {
            return null;
        }
        $session_app = self::_getSessionApp($namespace);
        $session_app->{$cle} = $valeur;
        return $valeur;
    }

    /**
     * Ecrit une(des) variable(s) de session
     *
     * @param mixed $valeur
     * @return mixed
     */
    public static function setMulti ($valeurs, $namespace = null)
    {
        $session_app = self::_getSessionApp($namespace);
        $valeurs = (array) $valeurs;
        while (list ($cle, $valeur) = each($valeurs)) {
            $session_app->{$cle} = $valeur;
        }
        reset($valeurs);
        return $valeurs;
    }

    /**
     * @param  string $name
     * @return void
     */
    public static function setApplication ($name)
    {
        self::$_application = ucfirst(strtolower($name));
    }

    /**
     * @return string
     */
    public static function getApplication ()
    {
        return self::$_application;
    }

    /**
     * @param  string $name
     * @return void
     */
    public static function setDefaultNamespace ($name)
    {
        self::$_default_namespace = ucfirst(strtolower($name));
    }

    /**
     * @return string
     */
    public static function getDefaultNamespace ()
    {
        return self::$_default_namespace;
    }
}

Voyons maintenant son utilisation :

  • dans le bootstrap, j’initialise l’application :
Mp_Session::setApplication('MonAppli');
  • dans un plugin en preDispatch j’initialise le namespace, dans mon cas il s’agit du module de l’application, s’il n’est pas initialisé sa valeur est « default » ce qui correspond à un namespace global :
Mp_Session::setNamespace($this->getRequest()
                              ->getModuleName());
  • dans l’application, je peux écrire des données :
 // dans le namespace en cours
Mp_Session::set('maVar', 'toto');
// dans le namespace du module Tata
Mp_Session::set('maVar', 'toto', 'Tata');
// avec plusieurs variables
Mp_Session::setMulti(array('maVar1' => 'toto',
                           'maVar2' => 'titi'));
// ou
Mp_Session::setMulti(array('maVar1' => 'toto',
                           'maVar2' => 'titi')
                     'Tata');
  • ou les lire :
$var = Mp_Session::get('toto');
// ou
$var = Mp_Session::get('toto', 'Tata');

Piloter le chargement d’une application avec Zend_Test

Contexte

On m’a demandé de convertir une application existante écrite en C avec une BDD Access en une application PHP+Oracle. La structure de la base de données a été entièrement modifié pour accepter de nouvelles évolutions. Il y avait donc une possibilité toute simple d’injecter les anciennes données issues d’Access dans la nouvelle base Oracle par l’intermédiaire d’un script SQL. J’ai cependant opté pour un pilotage de la nouvelle application avec Zend_Test chaque test correspondant à une donnée issue d’Access à entrer dans Oracle.

Note

Ce qui suit ne correspond pas aux tests de mon application (qui sont d’ailleurs eux aussi réalisés avec Zend_Test).

Initialisation

L’application est une supervision de production d’éléments. Chaque élément suit une gamme de fabrication. Les étapes de fabrication sont sauvegardées (qui à fait quoi et quand). Cette application est de type CRUD. Elle est composée de formulaires construits avec Zend_Form. Le script va donc consisté en l’appel des pages d’ajout (ou plus rarement de modification) avec les bons paramètres. Chaque ajout ou modification est assigné à un utilisateur. L’authentification est donc nécessaire pour chaque action puisque l’utilisateur peut changer à chaque étape.

La structure est similaire à la structure préconisée sur le site du Zend Framework.

Le bootstrap de l’application

Il doit permettre l’initialisation de l’application indépendamment de la distribution de la requête. 2 fichiers sont dans mon cas utilisés :

// index.php dans /appli/html (seul dossier accessible par Apache)
require_once ('../library/Mp/Site.php');
echo Mp_Site::run('MONAPPLI', 'PROD');
// Site.php dans /appli/library/Mp
class Mp_Site
{
    ... // mes variables (toutes statiques)

    public static function run ($app_name, $mode = 'PROD')
    {
        self::prepare($app_name, $mode);
        self::execute();
    }

    public static function prepare ($app_name, $mode = 'TEST')
    {
        self::setupEnvironment($mode);
        self::setupRegistry();
        self::setupLogger();
        self::setupConfiguration();
        self::setupDatabase();
        self::setupSession();
        self::setupFrontController();
        self::setupLayout();
        self::setupView();
        self::setupCache();
        self::setupApplication();
        self::$_already_loaded = true;
    }

    public static function execute ()
    {
        $request = new Zend_Controller_Request_Http();
        $response = new Zend_Controller_Response_Http();
        $response->append('body', '');
        try {
            self::$front->dispatch($request, $response);
        } catch (Exception $e) {
            exit($e->getMessage());
        }
    }

    ... // autres méthodes (elles aussi statiques)
}

Dans le cas de l’appel de l’application en mode Web, on passe par index.php qui prépare l’appli et l’exécute. Dans notre cas, nous allons seulement faire appel à prepare().

Script global (à l’image d’une suite PHPUnit)

Chaque table à remplir est dans un fichier séparé.

if (! defined('PHPUnit_MAIN_METHOD')) {
    define('PHPUnit_MAIN_METHOD', 'AllChargementMonappli::main');
}
class AllChargementMonappli
{

    public static function main ()
    {
        PHPUnit_TextUI_TestRunner::run(self::suite());
    }

    public static function suite ()
    {
        $suite = new PHPUnit_Framework_TestSuite('ChargementMonappli');
        $suite->addTestSuite('ChargementMonappli_TypeFournisMatprem');
        $suite->addTestSuite('ChargementMonappli_Table2');
        $suite->addTestSuite('ChargementMonappli_Table3');
        // ...
        $suite->addTestSuite('ChargementMonappli_TableN');
        return $suite;
    }
}
if (PHPUnit_MAIN_METHOD == 'AllChargementMonappli::main') {
    AllChargementMonappli::main();
}

Classe commune pour toutes les tables

  • La fonction setUp() est appelée à chaque démarrage de test. Elle permet de réinitialiser l’environnement ZF (requête, réponse, dispatcheur…). Il faut lui fournir les infos permettant l’amorçage de votre application (dans mon cas il faut définir $this->bootstrap comme un callback valide).
  • La fonction appBootstrap() est nécessaire car mon bootstrap est une fonction statique.
  • La fonction connect() permet de connecter un utilisateur en mode ‘TEST’ le password n’est pas vérifié.
  • La fonction _loadPage() charge une page suivant les paramètres fournis, comme dans notre cas il s’agit de pilotages de formulaires (ajout ou modif) : la méthode est toujours de type POST.
  • La fonction _verifError() vérifie les erreurs retournées par Zend_Form (normalement aucune !)
  • Les fonctions _searchAjax() et _readAjax() permettent d’aller lire une information au format json (je n’utilise pas dans ce cas le ContextSwitch, mon application ne fonctionne pas sans Javascript car il s’agit d’un Intranet donc je connais l’ensemble du parc). Toutes les requêtes pures Ajax sont donc tout simplement stockées dans un contrôleur AjaxController.
class ChargementMonappli_Commun
      extends Zend_Test_PHPUnit_ControllerTestCase
{

    public function setUp ()
    {
        $this->bootstrap = array($this , 'appBootstrap');
        parent::setUp();
    }

    public function appBootstrap ()
    {
        Mp_Site::prepare('MONAPPLI', 'TEST');
    }

    public function connect ($user, $password = null)
    {
        if ($password == null) {
            $password = $user;
        }
        $request = $this->getRequest();
        $request->setMethod('POST')->setPost(array('user' => $user,
                                                   'password' => $password,
                                                   'page_demandee' => '/toto'));
        $this->dispatch('/default/login/authentifie');
        $this->assertRedirectTo('/toto');
        $this->resetResponse();
        $request->setMethod('GET')->clearPost();
    }

    protected function _loadPage ($page, $param, $ajax = false)
    {
        if ($ajax) {
            $param = $this->_searchAjax($param);
        }
        $this->getRequest()->setMethod('POST');
        $this->getRequest()->setPost($param);
        $this->dispatch($page);
        $this->_verifError();
        $this->resetResponse();
        $this->getRequest()->clearPost();
    }

    protected function _verifError ()
    {
        $retour = null;
        $text = $this->getResponse()->getBody();
        if (preg_match("`<ul class=\"errors\">(.*)</ul>`", $text, $retour)) {
            $this->assertTrue(0, $retour[1]);
        }
        $retour = null;
        if (preg_match("`<div id=\"texte\">L'élément n'a pas été ajouté(.*)<br/>`", $text, $retour)) {
            $this->assertTrue(0, $retour[1]);
        }
    }
    protected function _searchAjax ($table)
    {
        foreach ($table as &$v) {
            if (is_string($v) && substr($v, 0, 26) == '/paimbinfo/outillage/ajax/') {
                $v = $this->_readAjax($v);
            }
        }
        return $table;
    }
    protected function _readAjax ($address)
    {
        $this->dispatch($address);
        $response = Zend_Json::decode($this->getResponse()->getBody());
        $this->resetResponse();
        $this->getRequest()->setMethod('GET')->clearPost();
        return $response[1];
    }
}

 Chargement de la table 1

Il s’agit de la table des fournisseurs de matière première. Chaque testTypeFournisMatpremX() correspond à une entrée dans la base et renverra un ‘.’, un ‘F’ ou un ‘E’ comme les tests classiques de PHUnit m’avertissant ainsi des problèmes rencontrés à l’injection des données. Le fichier ci-dessous est entièrement créé via un script VBA.

class ChargementMonappli_TypeFournisMatprem extends ChargementMonappli_Commun
{
    public function testTypeFournisMatprem1 ()
    {
        $this->connect('toto', 'password');
        $this->_loadPage('/monappli/base/ajout/table/type_fournis_matprem',
                         array('s_type_fournis_matprem' => 'FOURNISSEUR 1', 'n_type_outillage' => 0, 's_adresse_fm' => 'USA'));
    }
    public function testTypeFournisMatprem2 ()
    {
        $this->connect('toto', 'password');
        $this->_loadPage('/monappli/base/ajout/table/type_fournis_matprem',
                         array('s_type_fournis_matprem' => 'FOURNISSEUR 2', 'n_type_outillage' => 0, 'n_telephone_fm' => 33240000000, 'n_fax_fm' => 33240000001, 's_adresse_fm' => '44300 NANTES'));
    }
    public function testTypeFournisMatprem3 ()
    {
        $this->connect('toto', 'password');
        $this->_loadPage('/monappli/base/ajout/table/type_fournis_matprem',
                         array('s_type_fournis_matprem' => 'FOURNISSEUR 3', 'n_type_outillage' => 0, 'n_telephone_fm' => 33240000002, 'n_fax_fm' => 33240000003, 's_adresse_fm' => '44600 SAINT-NAZAIRE'));
    }
    public function testTypeFournisMatprem4 ()
    {
        $this->connect('toto', 'password');
        $this->_loadPage('/monappli/base/ajout/table/type_fournis_matprem',
                         array('s_type_fournis_matprem' => 'FOURNISSEUR 4', 'n_type_outillage' => 0));
    }
    public function testTypeFournisMatprem5 ()
    {
        $this->connect('toto', 'password');
        $this->_loadPage('/monappli/base/ajout/table/type_fournis_matprem',
                         array('s_type_fournis_matprem' => 'FOURNISSEUR 5', 'n_type_outillage' => 0));
    }
}

Pour terminer

Il suffit de lancer en ligne de commande : phpunit --verbose AllChargementMonappli > export.txt pour récupérer l’ensemble du résultat dans un fichier ‘export.txt’. Zend_Test permet donc bien plus que le simple test unitaire.

Zend_View_Helper_Abstract, enfin !

Avec la version 1.6 du Zend Framework apparaît une classe qui manquait singulièrement.

Elle ne fait certes pas grand chose mais c’est tellement utile plutôt que de le rappeler à chaque création d’aides de vue :

abstract class Zend_View_Helper_Abstract
         implements Zend_View_Helper_Interface
{
    /**
     * View object
     *
     * @var Zend_View_Interface
     */
    public $view = null;

    /**
     * Set the View object
     *
     * @param  Zend_View_Interface $view
     * @return Zend_View_Helper_Abstract
     */
    public function setView(Zend_View_Interface $view)
    {
        $this->view = $view;
        return $this;
    }

    /**
     * Strategy pattern: currently unutilized
     *
     * @return void
     */
    public function direct()
    {}
}

Vos aides de vue peuvent maitenant étendre Zend_View_Helper_Abstract (mais ce n’est pas obligatoire du moment qu’elles implémentent Zend_View_Helper_Interface). C’est cependant fortement recommandé.

Documentation du Zend Framework

Etendre Zend_Auth

Pourquoi vouloir étendre Zend_Auth ?

Dans mon cas, il s’agissait initialement de plusieurs applications situées sur le même serveur auxquelles on pouvait se connecter au cours de la même session avec des logins différents. Ces applications partageaient les mêmes librairies, le même bootstrap.

Et ensuite d’avoir un accès simple aux données d’authentification.

Vous allez vous dire pourquoi étendre Zend_Auth pour si peu ou encore pourquoi écrire un article sur tout ceci. Principalement parce que Zend_Auth est un singleton et qu’étendre un singleton nécessite de bien comprendre son fonctionnement.

Étendre un singleton

Extrait de Wikipédia :

Le singleton est un patron de conception (design pattern) dont l’objet est de restreindre l’instanciation d’une classe à un seul objet

Quand on veut étendre un singleton en PHP : il faut donc surtout penser à surcharger la méthode qui permet de récupérer l’instance (souvent getInstance()). Sinon vous récupèrerez une instance de la classe parente.

Code de l’extension

require_once 'Zend/Auth.php';

class Extension_Auth extends Zend_Auth
{

    /**
     * Name of the actual application
     *
     * @var string
     */
    protected static $_application = null;

    protected static $_infos = null;

    public static function getInstance()
    {
        if (null === self::$_instance) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }

    public static function readInfo($info)
    {
        if (null === $info) {
            return null;
        }
        if (null === self ::$_infos) {
            self ::$_infos = self::getInstance()
                                 ->getStorage()
                                 ->read() ;
        }
        if (isset(self::$_infos->$info)) {
            return self::$_infos->$info;
        } else {
            return null;
        }
    }

    public function getStorage()
    {
        if (null === $this->_storage) {
            require_once 'Zend/Auth/Storage/Session.php';
            $this->setStorage(new Zend_Auth_Storage_Session(
                    'Auth_' . $this->getApplication()
                ));
        }
        return $this->_storage;
    }

    public static function setApplication($name)
    {
        self::$_application = $name;
    }

    public static function getApplication()
    {
        return self::$_application;
    }

    public function clearIdentity()
    {
        parent::clearIdentity();
        self::$_infos = null;
    }
}

Utilisation

Dans mon bootstrap, je paramètre le nom de l’application :

Extension_Auth::setApplication('toto');

Dans mes scripts de vues, je peux faire :

Actuellement connecté :  
<?=Extension_Auth::readInfo('s_first_name')?>  
<?=Extension_Auth::readInfo('s_last_name')?>