lundi 30 janvier 2012

Zend Framework : librairie de gestion de données, à partir d'un formulaire vers une base de données, avec traitement des fichiers

Voici un ensemble de classes permettant de gérer facilement l'insertion et la modification, en base de données et en répertoires, les valeurs d'un formulaire contenant n'importe quel type d'éléments, y compris des fichiers, et d'appliquer des traitements sur les fichiers uploadés.

Cette petite librairie permet de :
  • Insérer ou modifier des éléments divers (varchar, text...) en les filtrant éventuellement,
  • Insérer ou supprimer des fichiers dans des répertoires choisis,
  • Renommer éventuellement les fichiers uploadés, aléatoirement ou selon un nom défini,
  • Redimensionner les images en plusieurs tailles (par exemple vignettes, grandes, plein écran...),
  • Rogner éventuellement des images,
  • Changer éventuellement les clés des valeurs du formulaire, si elles ne correspondent pas aux noms de lignes de la table de db.
Nous utiliseront la librairie PHP Thumbnailer adaptée pour le Zend Framework, que vous pouvez trouver dans un petit tuto ici

Le code source de cette librairie est disponible sur Github.

Commençons par les éléments que nous utliserons :

Une petite classe permettant de générer un nom aléatoire, à placer dans library/Web82/Generate/Random.php :
class Web82_Generate_Random
{

        /**
         * 
         * @param integer $length
         */
        public function generate ( $length )
        {

                $random = '' ;
                $chaine = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789' ;

                srand ( ( double ) microtime () * 1000000 ) ;

                for ( $i = 0 ; $i < $length ; $i ++  ) {

                        $random .= $chaine [ rand () % strlen ( $chaine ) ] ;
                }
                return $random ;

        }

}
Une classe qui étend Zend_Form_Element_File en y ajoutant deux petites méthodes. Les éléments File du formulaire traité devront être des instances de cette classe. À placer dans library/Web82/Form/Element/File.php.
class Web82_Form_Element_File
    extends Zend_Form_Element_File
{

        /**
         * renomme le fichier
         * $name : le nom que l'on veut donner au fichier. S'il n'est pas spécifié, un nom aléatoire sera donné
         * $forceExtension : si l'on veut forcer l'extension du fichier (peu conseillé !)
         *
         * @param string|null $name
         * @param string|null $forceExtension
         * @return string
         */
        public function setNewName ( $name = NULL , $forceExtension = NULL )
        {
                /**
                 * on récupère l'extension du fichier, ou on lui assigne l'extension forcée
                 */
                if ( is_null ( $forceExtension ) ) {

                        $fileName = $this -> getFileName ( NULL , FALSE ) ;

                        $explodedName = explode ( '.' , $fileName ) ;

                        $extension = $explodedName [ count ( $explodedName ) - 1 ] ;
                } else {

                        $extension = $forceExtension ;
                }

                /**
                 * on génère le nom, ou on prend celui donné
                 */
                if ( is_null ( $name ) ) {

                        /**
                         * ici on utilise la classe précédente
                         */
                        $makeName = new Web82_Generate_Random ( ) ;

                        $newName = $makeName -> generate ( 10 ) . '.' . $extension ;
                } else {

                        $newName = $name . '.' . $extension ;
                }

                return $newName ;

        }

        /**
         *
         * @return string
         */
        public function getExtension ()
        {
                $fileName = $this -> getFileName ( NULL , FALSE ) ;

                $explodedName = explode ( '.' , $fileName ) ;

                $extension = $explodedName [ count ( $explodedName ) - 1 ] ;

                return $extension ;

        }

}
Une classe avec une méthode statique permettant de rogner une image suivant une proportion largeur/hauteur. Cela permet, par exemple, d'avoir des vignettes toutes de la même taille sur un catalogue, en supprimant des parties en haut et en bas, ou à gauche et à droite. Elle est placée dans library/Web/Image/Tools.php (Je posterai plus tard une librairie plus complète de traitements d'images).
class Web82_Image_Tools
{

        /**
         * $source : l'image source
         * $destination : la destination désirée
         * $proportionWidthOnHeight : la proportion largeur/hauteur souhaitée
         * $extension :  l'extension du fichier
         * 
         * @param resource $source
         * @param string $destination
         * @param integer $proportionWidthOnHeight
         * @param string $extension
         */
        static public function cropImage ( $source , $destination , $proportionWidthOnHeight , $extension )
        {
                /**
                 * on récupère la taille de l'image source
                 */
                $size = getimagesize ( $source ) ;

                $actualWidth  = $size[ 0 ] ;
                $actualHeight = $size[ 1 ] ;

                /**
                 * on détermine ensuite la taille de l'image de destination, 
                 */
                /**
                 * cas d'une image source en paysage
                 */
                if ( $actualWidth >= $actualHeight * $proportionWidthOnHeight ) {

                        $newWidth  = $actualHeight * $proportionWidthOnHeight ;
                        $newHeight = $actualHeight ;

                        $startX = ($actualWidth - $newWidth) / 2 ;
                        $startY = 0 ;
                }

                /**
                 * cas d'une image source en portrait
                 */ else {

                        $newWidth  = $actualWidth ;
                        $newHeight = $actualWidth / $proportionWidthOnHeight ;

                        $startX = 0 ;
                        $startY = ($actualHeight - $newHeight) / 2 ;
                }

                /**
                 * on crée l'image de destination
                 */
                $newImage = imagecreatetruecolor ( $newWidth , $newHeight ) ;

                /**
                 * puis la resource image à partir de la source
                 */
                switch ( $extension ) {
                        case 'jpg':
                        case 'jpeg':
                        case 'JPG':
                        case 'JPEG':
                                $sourceImage = imagecreatefromjpeg ( $source ) ;
                                break ;
                        case 'gif':
                        case 'GIF':
                                $sourceImage = imagecreatefromgif ( $source ) ;
                                break ;
                        case 'png':
                        case 'PNG':
                                $sourceImage = imagecreatefrompng ( $source ) ;
                                break ;
                }

                /**
                 * puis on copie la partie de l'image source dans celle de destination
                 */
                imagecopy ( $newImage , $sourceImage , 0 , 0 , $startX , $startY , $newWidth , $newHeight ) ;

                /**
                 * et on enregistre l'image
                 */
                switch ( $extension ) {
                        case 'jpg':
                        case 'jpeg':
                        case 'JPG':
                        case 'JPEG':
                                imagejpeg ( $newImage , $destination ) ;
                                break ;
                        case 'gif':
                        case 'GIF':
                                imagegif ( $newImage , $destination ) ;
                                break ;
                        case 'png':
                        case 'PNG':
                                imagepng ( $newImage , $destination ) ;
                                break ;
                }

                /**
                 * on détruit les deux resources
                 */
                imagedestroy ( $newImage ) ;
                imagedestroy ( $sourceImage ) ;

        }

}
Avant les classes de traitement proprement dites, voici le nerf de la guerre, le fichier que vous aurez à adapter suivant vos besoins, un fichier INI qui va gérer les traitements des fichiers de notre formulaire. Suivez bien les commentaires.
; fichier "files.ini" à placer dans application/configs

; supposons que le nom de l'élément image soit "image" (ce qui est bien parlant, ma foi...)

; ici le répertoire temporaire d'upload
file.image.tmpPath = APPLICATION_PATH "/../public/images/upload/tmp"

; veut-on renommer le fichier ? oui: 1, non: 0
file.image.rename.ok = 1
; si l'on veut donner un nom précis au fichier, le spécifier ici
; si l'on veut donner un nom aléatoire, ne pas mettre cette ligne
file.image.rename.name = "nomQueLonVeutDonner"
; veut-on en forcer l'extension (fort peu conseillé !) ? oui: 1, non: 0
file.image.rename.forceExtension = 0

; on a ici le choix de directement enregistrer le fichier, sans lui appliquer aucun traitement
; dans ce cas, décommenter cette ligne et donner le répertoire désiré :

; file.image.moveToPath = APPLICATION_PATH "/../public/repertoire/ou/on/place/le/fichier"

; veut-on redimensionner ? oui: 1, non: 0
file.image.resize = 1

; on peut opérer plusieurs redimensionnements (clé "version"), aboutissant à plusieurs images
; à placer dans des répertoires différents, car elles auront le même nom

; le répertoire de destination
file.image.versions.thumb.path = APPLICATION_PATH "/../public/images/upload/thumb"
; la largeur désirée
file.image.versions.thumb.size.width = 120
; la hauteur
file.image.versions.thumb.size.height = 60
; veut-on rogner l'image ? oui: 1, non, 0
file.image.versions.thumb.crop.ok = 1
; dans ce cas, on spécifie la proportion largeur/hauteur
file.image.versions.thumb.crop.proportionWidthOnHeight = 2

; un autre version, sans rognage...
file.image.versions.diapo.path = APPLICATION_PATH "/../public/images/upload/diapo"
file.image.versions.diapo.size.width = 1900
file.image.versions.diapo.size.height = 369
file.image.versions.diapo.crop.ok = 0

; et une dernière, pour laquelle on va récupérer les dimensions pour les ajouter en base de donnée
file.image.versions.fullScreen.path = APPLICATION_PATH "/../public/images/upload/fullScreen"
file.image.versions.fullScreen.size.width = 1900
file.image.versions.fullScreen.size.height = 1000
file.image.versions.fullScreen.crop.ok = 0
; 1 pour récupérer les dimensions
file.image.versions.fullScreen.getSize.ok = 1
; ici le nom du champ de la db pour la largeur...
file.image.versions.fullScreen.getSize.widthField = "width"
; et celui pour la hauteur
file.image.versions.fullScreen.getSize.heightField = "height"


; une vidéo uploadée, dont le champ s'appelle "vidéo" (original...)
file.video.tmpPath = APPLICATION_PATH "/../public/images/upload/tmp"

; on la renomme...
file.video.rename.ok = 1
file.video.rename.forceExtension = 0

; mais on ne la redimensionne pas, bien sûr ! Donc, directement  moveToPath
file.video.moveToPath = APPLICATION_PATH "/../public/images/upload/video"


; et hop, un PDF
file.pdf.tmpPath = APPLICATION_PATH "/../public/pdf/tmp"

file.pdf.rename.ok = 1
file.pdf.rename.name = "paf"
file.pdf.rename.forceExtension = 0

file.pdf.moveToPath = APPLICATION_PATH "/../public/pdf"

Voici enfin les deux classes qui gèrent tous les traitements : D'abord, la classe que l'on appellera dans le contrôleur : library/Web82/Content/Insert.php
class Web82_Content_Insert
{

        /**
         *
         * @var Zend_Form
         */
        public $form ;

        /**
         *
         * @var Zend_Db_Table_Abstract
         */
        public $table ;

        /**
         *
         * @var array
         */
        public $values ;

        /**
         *
         * @var Zend_Filter
         */
        public $filter ;

        /**
         *
         * @var Zend_Config
         */
        public $config ;

        /**
         * 
         * $form : le formulaire peuplé des valeurs
         * $table : la table de la base, instanciée de Zend_Db_Table_Abstract
         * $filtre : l'éventuel filtre
         * $config : la config
         * 
         * @param Zend_Form $form
         * @param Zend_Db_Table_Abstract $table
         * @param Zend_Filter|null $filter
         * @param Zend_Config|null $config // en cas de files
         */
        public function __construct ( Zend_Form $form , Zend_Db_Table_Abstract $table , $filter = NULL , Zend_Config $config = NULL )
        {
                /**
                 * voir plus bas tous les setters
                 */
                $this -> setForm ( $form ) ;
                $this -> setTable ( $table ) ;
                $this -> setValues () ;

                if ( ! is_null ( $filter ) ) {

                        $this -> setFilter ( $filter ) ;
                }

                if ( ! is_null ( $config ) ) {

                        $this -> setConfig ( $config ) ;
                }

        }

        /**
         * $action : l'action désirée, "insert" ou "update"
         * $id : le nom du champ clé primaire en db
         * $valueId : le nom du champ clé primaire dans le formulaire
         * $keyModifier : le tableau de changement des clés
         * 
         * @param string $action
         * @param string $id
         * @param string $valueId
         * @param array $keyModifier
         * @return boolean 
         */
        public function insert ( $action , $id = 'id' , $valueId = 'id' , $keyModifier = NULL )
        {
                /**
                 * le form n'est pas valide
                 */
                if ( ! $this -> form -> isValid ( $this -> values ) ) {

                        return FALSE ;
                }

                /**
                 * il l'est
                 */ else {

                        /**
                         * on vérifie s'il y a des files
                         */
                        $fileElements = array ( ) ;

                        foreach ( $this -> form -> getElements () as $formElement ) {

                                if ( $formElement instanceof Zend_Form_Element_File && ! is_null ( $formElement -> getValue () ) ) {

                                        $fileElements[ ] = $formElement ;
                                }
                        }

                        /**
                         * si le tableau est rempli, c'est qu'il y a des files
                         */
                        if ( count ( $fileElements ) > 0 ) {

                                /**
                                 * alors on appelle la classe Upload
                                 */
                                $upload = new Web82_Content_File_Upload ( $this -> form , $this -> table , $this -> filter , $this -> config ) ;
                                $this -> values = $upload -> upload () ;
                        }

                        /**
                         * on change évetuellement les clés des valeurs
                         * avec un tableau ayant pour clés les anciennes clés
                         * et pour valeurs les clés désirées
                         */
                        if ( ! is_null ( $keyModifier ) ) {

                                $valuesArray = $this -> values ;

                                $this -> values = array ( ) ;

                                foreach ( $keyModifier as $oldKey => $newKey ) {

                                        $this -> values[ $newKey ] = $valuesArray[ $oldKey ] ;
                                }
                        }

                        /**
                         * on filtre éventuellement les données
                         * avec un filtre implémentant Zend_Filter_Interface
                         */
                        if ( ! is_null ( $this -> filter ) ) {

                                $this -> values = $this -> filter -> filter ( $this -> values ) ;
                        }

                        /**
                         * on balance en db
                         */
                        if ( $action == 'insert' ) {

                                $rowId = $this -> table -> insert ( $this -> values ) ;

                                $this -> values[ $id ] = $rowId ;
                        } elseif ( $action == 'update' ) {

                                $this -> table -> update ( $this -> values , $this -> table -> getAdapter () -> quoteInto ( $id . '=?' , $this -> values[ $valueId ] ) ) ;
                        }

                        /**
                         * et on retourne les valeurs finales
                         */
                        return $this -> values ;
                }

        }

        /**
         *
         * @param Zend_Form $form 
         */
        public function setForm ( Zend_Form $form )
        {

                try {

                        if ( $form instanceof Zend_Form ) {

                                $this -> form = $form ;
                        }
                }
                catch ( Zend_Exception $exc ) {

                        $log = Zend_Registry::get ( 'logger' ) ;
                        $log -> err ( $exc -> getTraceAsString () ) ;
                }

        }

        /**
         *
         * @param Zend_Form $form 
         */
        public function setTable ( Zend_Db_Table_Abstract $table )
        {

                try {

                        if ( $table instanceof Zend_Db_Table_Abstract ) {

                                $this -> table = $table ;
                        }
                }
                catch ( Zend_Exception $exc ) {

                        $log = Zend_Registry::get ( 'logger' ) ;
                        $log -> err ( $exc -> getTraceAsString () ) ;
                }

        }

        /**
         *
         * @param array
         */
        public function setValues ()
        {

                $this -> values = $this -> form -> getValues () ;

        }

        /**
         *
         * @param Zend_Filter $form 
         */
        public function setFilter ( $filter )
        {

                $this -> filter = $filter ;

        }

        /**
         *
         * @param Zend_Config $config 
         */
        public function setConfig ( Zend_Config $config )
        {
                try {

                        if ( $config instanceof Zend_Config ) {

                                $this -> config = $config ;
                        }
                }
                catch ( Zend_Exception $exc ) {

                        $log = Zend_Registry::get ( 'logger' ) ;
                        $log -> err ( $exc -> getTraceAsString () ) ;
                }

        }

}
Puis la classe qui va traiter les fichiers : library/Web82/Content/Files/Upload.php :
class Web82_Content_File_Upload
    extends Web82_Content_Insert
{

        /**
         *
         * @return array
         */
        public function upload ()
        {
                $values = $this -> form -> getValues () ;

                /**
                 * on itère les files
                 */
                foreach ( $this -> config -> file as $element => $file ) {

                        /**
                         * le champ du formulaire n'a pas été rempli, on ne le traite pas
                         */
                        if ( ! $this -> form -> {$element} -> isUploaded () ) {

                                unset ( $values[ $element ] ) ;
                        }

                        /**
                         * il l'a été
                         */ else {

                                /**
                                 * le fichier n'a pas été correctement reçu
                                 */
                                if ( ! $this -> form -> {$element} -> receive () ) {

                                        throw new Zend_Exception ( 'Le fichier n\'a pas été reçu' ) ;
                                }

                                /**
                                 * il l'a été
                                 */ else {

                                        /**
                                         * on choppe le nom et l'extension de l'image
                                         */
                                        $tmpName   = $this -> form -> {$element} -> getFileName ( NULL , FALSE ) ;
                                        $extension = $this -> form -> {$element} -> getExtension () ;

                                        /**
                                         * on renomme ?
                                         */
                                        if ( $file -> rename -> ok == 1 ) {

                                                if ( ! is_string ( $file -> rename -> name ) ) {

                                                        $renameName = NULL ;
                                                } else {

                                                        $renameName = $file -> rename -> name ;
                                                }

                                                if ( $file -> rename -> forceExtension == 0 ) {

                                                        $renameForceExtension = NULL ;
                                                } else {

                                                        $renameForceExtension = $file -> rename -> forceExtension ;
                                                }

                                                $newName = $this -> form -> {$element} -> setNewName ( $renameName , $renameForceExtension ) ;
                                        } else {

                                                $newName = $tmpName ;
                                        }

                                        /**
                                         * le path tmp
                                         */
                                        $tmpPath = $file -> tmpPath ;

                                        /**
                                         * on redimensionne ?
                                         */
                                        if ( $file -> resize == 1 ) {

                                                /**
                                                 * on itère les versions
                                                 */
                                                foreach ( $file -> versions as $version ) {

                                                        /**
                                                         * on rogne ?
                                                         */
                                                        if ( $version -> crop -> ok == 1 ) {

                                                                $croppedName = 'crop_' . $tmpName ;

                                                                try {

                                                                        Web82_Image_Tools::cropImage (
                                                                            $tmpPath . '/' . $tmpName , $tmpPath . '/' . $croppedName , $version -> crop -> proportionWidthOnHeight , $extension ) ;
                                                                }
                                                                catch ( Zend_Exception $exc ) {

                                                                        /**
                                                                         * si on a défini un logger dans le Bootstrap, sinon, on fait ce qu'on veut...
                                                                         */
                                                                        $log = Zend_Registry::get ( 'logger' ) ;
                                                                        $log -> err ( $exc -> getTraceAsString () ) ;
                                                                }

                                                                $tmpNameToInsert = $croppedName ;
                                                        } else {

                                                                $tmpNameToInsert = $tmpName ;
                                                        }

                                                        /**
                                                         * on resize avec Thumbnailer, et on enregistre
                                                         */
                                                        try {

                                                                Thumbnailer_CreateThumb::createThumbnail (
                                                                    $tmpPath . '/' . $tmpNameToInsert , $version -> path . '/' . $newName , $version -> size -> width , $version -> size -> height ) ;
                                                        }
                                                        catch ( Zend_Exception $exc ) {

                                                                /**
                                                                 * si on a défini un logger dans le Bootstrap, sinon, on fait ce qu'on veut...
                                                                 */
                                                                $log = Zend_Registry::get ( 'logger' ) ;
                                                                $log -> err ( $exc -> getTraceAsString () ) ;
                                                        }

                                                        /**
                                                         * doit-on récupérer les dimensions pour les insérer en db ?
                                                         */
                                                        if ( $version -> getSize -> ok == 1 ) {

                                                                $dimensions = getimagesize ( $version -> path . '/' . $newName ) ;

                                                                $values[ $version -> getSize -> widthField ]  = $dimensions[ 0 ] ;
                                                                $values[ $version -> getSize -> heightField ] = $dimensions[ 1 ] ;
                                                        }
                                                }
                                        }

                                        /**
                                         * on ne redimensionne pas, on copie juste
                                         */ else {

                                                copy ( $tmpPath . '/' . $tmpName , $file -> moveToPath . '/' . $newName ) ;
                                        }

                                        /**
                                         * on supprime les images temporaires
                                         */
                                        if ( is_file ( $tmpPath . '/' . $tmpName ) ) {

                                                unlink ( $tmpPath . '/' . $tmpName ) ;
                                        }

                                        if ( is_file ( $tmpPath . '/' . $croppedName ) ) {

                                                unlink ( $tmpPath . '/' . $croppedName ) ;
                                        }

                                        /**
                                         * on assigne le nom de l'élément aux valeurs à insérer en db
                                         */
                                        $values[ $element ] = $newName ;
                                }
                        }
                }

                /**
                 * et on retourne les valeurs
                 */
                return $values ;

        }

}
Et voilà ! On va pouvoir appeler la classe Insert de notre contrôleur de cette manière :
/**
 * on instancie la config
 */
$config   = new Zend_Config_Ini ( APPLICATION_PATH . '/configs/files.ini' ) ;

/**
 * le formulaire
 */
$form     = new My_Form ( $_POST ) ;

/**
 * et le modèle de la table
 */
$myModel = new My_Model() ;

/**
 * on appelle la classe en lui passant les arguments (ici sans filtre)
 */
$insert = new Web82_Content_Insert ( $form , $myModel , NULL , $config ) ;

/**
 * et on traite les données (ici en insertion)
 */
$result = $insert -> insert ( 'insert' ) ;
Voilà, c'est terminé ! N'hésitez pas à me laisser vos commentaires, notamment si vous trouvez des bugs !

jeudi 29 septembre 2011

Zend Framework : validateur de mot de passe avec options

Un petit validateur de mot de passe, avec des options détaillées dans les commentaires, pour obliger l'utilisateur à entrer un minimum de caractères, des majuscules, des minuscules et/ou des chiffres.
class Web82_Validate_Password
 extends Zend_Validate_Abstract
{

    /**
     * constantes
     *
     */
    const NOUPPERCASE = 'noUppercase' ;
    const NOLOWERCASE = 'noLowercase' ;
    const NODIGIT = 'noDigit' ;
    const TOOSHORT = 'tooShort' ;

    /**
     * options
     *
     * @var array|Zend_Config|null
     */
    public $options ;
    /**
     * @var array
     */
    protected $_messageVariables = array (
 'min' => '_min' ,
     ) ;
    /**
     *
     * @var integer
     */
    protected $_min ;
    /**
     * variable des messages
     *
     * @var array
     */
    protected $_messageTemplates = array (
 self :: NOUPPERCASE => 'Votre mot de passe doit contenir au moins une majuscule' ,
 self :: NOLOWERCASE => 'Votre mot de passe doit contenir au moins une minuscule' ,
 self :: NODIGIT => 'Votre mot de passe doit contenir au moins un chiffre' ,
 self :: TOOSHORT => 'Votre mot de passe doit faire plus de %min% caractères'
     ) ;

    /**
     * constructeur
     *
     * clés possibles pour les options (activées avec la valeur 1) :
     *
     * 'uppercase' => majuscule obligatoire
     * 'lowercase' => minuscule obligatoire
     * 'digit' => chiffre obligatoire
     * 'min' => nombre de caractères minimum
     *
     * @param array|Zend_Config|null $options
     */
    public function __construct ( $options=NULL )
    {
 if ( $options instanceof Zend_Config ) {

     $options = $options -> toArray () ;
 }

 if ( is_array ( $options ) ) {

     $this -> setOptions ( $options ) ;
 }

    }

    /**
     * validation
     *
     * @param string $string
     * @return bool
     */
    public function isValid ( $string )
    {
 $return = array ( ) ;

 if ( isset ( $this -> options[ 'lowercase' ] ) && $this -> options[ 'lowercase' ] == 1 ) {

     if ( ! preg_match ( '`^.*[a-z]+.*$`' , $string ) ) {
  $this -> _error ( self :: NOLOWERCASE ) ;
  $return[ ] = FALSE ;
     }
 }

 if ( isset ( $this -> options[ 'uppercase' ] ) && $this -> options[ 'uppercase' ] == 1 ) {

     if ( ! preg_match ( '`^.*[A-Z]+.*$`' , $string ) ) {
  $this -> _error ( self :: NOUPPERCASE ) ;
  $return[ ] = FALSE ;
     }
 }

 if ( isset ( $this -> options[ 'digit' ] ) && $this -> options[ 'digit' ] == 1 ) {

     if ( ! preg_match ( '`^.*[0-9]+.*$`' , $string ) ) {
  $this -> _error ( self :: NODIGIT ) ;
  $return[ ] = FALSE ;
     }
 }

 if ( isset ( $this -> options[ 'min' ] ) && is_numeric ( $this -> options[ 'min' ] ) ) {

     $this -> _min = $this -> options[ 'min' ] ;

     $strlenValidator = new Zend_Validate_StringLength ( array ( 'min' => $this -> options[ 'min' ] ) ) ;

     if ( ! $strlenValidator -> isValid ( $string ) ) {
  $this -> _error ( self :: TOOSHORT , $this -> options[ 'min' ] ) ;
  $return[ ] = FALSE ;
     }
 }

 if ( in_array ( FALSE , $return ) ) {

     return FALSE ;
 }

 return TRUE ;

    }

    /**
     *
     * @param array $options
     */
    public function setOptions ( $options )
    {
 $this -> options = $options ;

    }

}
Dans un .ini, on l'utilise comme suit (toutes les options activées et un minimum de 8 caractères) :
...
elements.password.options.validators.password.validator = "Password"
elements.password.options.validators.password.options.lowercase = 1
elements.password.options.validators.password.options.uppercase = 1
elements.password.options.validators.password.options.digit = 1
elements.password.options.validators.password.options.min = 8
...

vendredi 26 août 2011

Zend Framework - Filtre de conversion de dates d'un format à un autre

Il est souvent nécessaire, au sein d'une application, de convertir des dates d'un format à un autre, notamment quand on récupère un timestamp ou un datetime d'une base de données et qu'on veut l'afficher d'une autre façon, ou l'inverse, pour envoyer une date d'un formulaire vers une base de données.

Voici donc un petit tutoriel pour créer et appeler un filtre qui implémente Zend_Filter_Interface, et peut donc être traité comme tout Zend_Filter.

On y utilise Zend_Date et les constantes de date.

On crée le filtre (attention aux commentaires) :

class Web82_Filter_Date
	implements Zend_Filter_Interface
{

    /**
     * format d'entrée
     *
     * @var string
     */
    public $fromFormat ;

    /**
     * format de sortie
     *
     * @var string
     */
    public $toFormat ;

    /**
     * contantes de formats de date 
     * 
     * Il est bien sûr possible d'en créer d'autre à loisir
     */
    const DATETIME = 'yyyy-MM-dd HH:mm:ss' ;
    const DATE = 'yyyy-MM-dd' ;
    const SIMPLE_DATE = 'dd/MM/yyyy' ;
    const FULL_DATE = 'dd/MM/yyyy HH:mm:ss' ;
    const FULL_DATE_NO_SEC = 'dd/MM/yyyy HH:mm' ;
    const TIME = 'HH:mm:ss' ;
    const HOUR = 'HH:mm' ;
    const YEAR = 'yyyy' ;

    /**
     * constructeur
     *
     * @param string|array $fromFormat
     * @param string $toFormat
     */
    public function __construct ( $fromFormat , $toFormat = NULL )
    {

	/**
	 * cas d'appel dans un fichier ini 
	 */
	if ( is_array ( $fromFormat ) ) {

	    $this -> fromFormat = $fromFormat[ 'from' ] ;
	    $this -> toFormat = $fromFormat[ 'to' ] ;
	}

	/**
	 * cas d'appel aux constantes 
	 */ else {

	    $this -> fromFormat = $fromFormat ;
	    $this -> toFormat = $toFormat ;
	}

    }

    /**
     * formate la date
     *
     * @param string|integer $date
     * @return string|integer
     */
    public function formatConverter ( $date )
    {
	/**
	 * instanciation d'un objet date 
	 */
	$dateObject = new Zend_Date() ;

	/**
	 * on lui passe éventuellement une locale du registre 
	 */
	if ( Zend_Registry::isRegistered ( 'Zend_Locale' ) ) {

	    $dateObject -> setLocale ( Zend_Registry::get ( 'Zend_Locale' ) ) ;
	}

	/**
	 * la date n'est pas valide, on retourne NULL
	 * (on pourrait lancer une exception à la place)
	 */
	if ( ! $dateObject -> isDate ( $date , $this -> fromFormat ) 
               && ! $dateObject -> isDate ( $date , $this -> toFormat ) ) {

	    return NULL ;
	}

	/**
	 * elle est valide 
	 */

	/**
	 * on assigne la date à l'objet Zend_Date 
	 */

	/**
	 * dans le cas où la date est déjà dans le format de sortie 
	 */
	if ( $dateObject -> isDate ( $date , $this -> toFormat ) ) {

	    $dateObject -> setDate ( $date , $this -> toFormat ) ;
	}

	/**
	 * sinon, nous avons déjà vérifié que la date était soit au format d'entrée, soit au format de sortie 
	 */ else {

	    $dateObject -> setDate ( $date , $this -> fromFormat ) ;
	}

	/**
	 * et on retourne la chaîne date formatée 
	 */
	return $dateObject -> toString ( $this -> toFormat ) ;

    }

    /**
     * filtre
     *
     * @param mixed $date
     * @throws Zend_Filter_Exception If filtering $value is impossible
     * @return mixed
     * @see Zend_Filter_Interface::filter()
     */
    public function filter ( $date )
    {

	return $this -> formatConverter ( $date ) ;

    }

}

On peut ensuite appeler ce filtre au sein de l'application, même dans la définition d'un formulaire. Par exemple pour convertir un DATETIME ou TIMESTAMP MySql en jj/mm/aaaa, on utilise le format d'entrée DATETIME et le format de sortie SIMPLE_DATE (voir les formats dans la classe du filtre) :

$dateFilter = new Web82_Filter_Date ( 
                           Web82_Filter_Date :: DATETIME , 
                           Web82_Filter_Date :: SIMPLE_DATE ) ;

/**
 * Si $dateFromDb est '2011-08-26 14:00:00'
 * alors $dateToDisplay sera '26/08/2011'
 */
$dateToDisplay = $dateFilter -> filter ( $dateFromDb ) ;

On peut aussi le définir directement avec les formats dans un .ini de formulaire :

...
myForm.elements.dateElement.options.filters.Date.filter = "Date"
myForm.elements.dateElement.options.filters.Date.options.from = "yyyy-MM-dd HH:mm:ss"
myForm.elements.dateElement.options.filters.Date.options.to = "dd/MM/yyyy"
...

Le premier argument du constructeur du filtre sera alors traité comme un tableau, et les clés "from" et "to" seront assignées respectivement au variables $fromFormat et $toFormat.

Voilà, c'est tout !

Pour créer d'autres constantes dans votre filtre, voir celles qui sont dans la documentation de ZF

Merci de laisser vos commentaires !

samedi 20 août 2011

Zend Framework - Éléments de formulaires HTML5

Considérant qu'il était temps désormais de développer mes sites en HTML5, J'ai remarqué que les nouveaux éléments de formulaires de ce drenier (comme "tel" ou "email") étaient absent de la librairie Zend Framework.

À mon avis, ces nouveaux éléments sont intéressants, car ils apportent un bon nombre de nouvelles fonctionnalités, comme par exemple une forme "d'auto-validation".

Je les ai donc implémenté, et j'ai trouve que ce serait une bonne idée de les partager.

Prenons l'exemple d'un élément de formulaire email. Étant donné qu'il est très similaire à un élément text, je l'étend simplement de Zend_Form_Element_Text, en changeant simplement la variable $helper :

class Web82_Form_Element_Email
	extends Zend_Form_Element_Text
{
    /**
     * Default form view helper to use for rendering
     * @var string
     */
    public $helper = 'formEmail';

}

je crée ensuite un view helper, simplement en copiant/collant Zend_View_Helper_FormText et en changeant le input type :

class Web82_View_Helper_FormEmail
	extends Zend_View_Helper_FormElement
{

    /**
     * Generates a 'email' element.
     *
     * @access public
     *
     * @param string|array $name If a string, the element name.  If an
     * array, all other parameters are ignored, and the array elements
     * are used in place of added parameters.
     *
     * @param mixed $value The element value.
     *
     * @param array $attribs Attributes for the element tag.
     *
     * @return string The element XHTML.
     */
    public function formEmail ( $name , $value = null , $attribs = null )
    {
	$info = $this -> _getInfo ( $name , $value , $attribs ) ;
	extract ( $info ) ; // name, value, attribs, options, listsep, disable
	// build the element
	$disabled = '' ;
	if ( $disable ) {
	    // disabled
	    $disabled = ' disabled="disabled"' ;
	}

	// XHTML or HTML end tag?
	$endTag = ' />' ;
	if ( ($this -> view instanceof Zend_View_Abstract) && ! $this -> view -> doctype () -> isXhtml () ) {
	    $endTag = '>' ;
	}

	$xhtml = '<input type="email"' // LE CHANGEMENT EST LÀ
		. ' name="' . $this -> view -> escape ( $name ) . '"'
		. ' id="' . $this -> view -> escape ( $id ) . '"'
		. ' value="' . $this -> view -> escape ( $value ) . '"'
		. $disabled
		. $this -> _htmlAttribs ( $attribs )
		. $endTag ;

	return $xhtml ;

    }

}

Je dois maintenant ajouter mon chemin de helper dans mon application.ini :

autoloadernamespaces[] = "Web82"
...
resources.view.helperPath.Web82_View_Helper = "Web82/View/Helper"
...

... Et initialiser ma ressource de view dans mon Boostrap.php :

protected function _initViews ()
    {
	$this -> bootstrap ( 'view' ) ;
    }

Je peux maintenant appeler Web82_Form_Element_Email comme je le ferais pour n'importe quel élément :


$myEmailElement = new Web82_Form_Element_Email ( 'email' ) ;

Bien sûr, je peux faire exactement la même chose pour un élément "tel".

Merci d'ajouter vos commentaires et à bientôt.

Franck.