• Billet écrit dans : Découverte PHP 02.12.2008 3 Réactions !!

    Dans un article précédent, je créais la procédure stocké « import », voilà comment je l’utilise dans Zend.

    Je n’invente rien, j’ai trouvé la manip sur http://www.ericmery.fr/blog/zend_db-sql-server-procedure-stockee.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    $registry = Zend_Registry::getInstance();
    $config = $registry->get("config");
     
    $db = Zend_Db::factory($config->db);
     
    $stmt = $db->prepare("call import(?,?,?);");
    $stmt->bindParam(1, $param1, PDO::PARAM_STR,1000);
    $stmt->;bindParam(2, $param2, PDO::PARAM_STR, 255);
    $stmt->bindParam(3, $param3, PDO::PARAM_STR, 255);
    $res = $stmt->execute();

    Magie, c’est la même chose. On remercie PDO et son abstracteur de base de donnée utilisé !

    Billet écrit dans : Découverte PHP 02.12.2008 3 Réactions !!
  • Billet écrit dans : SQL 02.12.2008 Quelqu'un a réagi !

    Pour un projet d’annuaire de Flux RSS, j’ai du faire en sorte que l’on puisse importer ses Flux dans l’annuaire via un fichier XML opml que tout bon agrégateur (google reader, netvibes) fournit. Donc admettons que l’on a 100 flux RSS à importer il faut pour chaque flux vérifier si le flux n’existe pas déjà sinon le créer en base. Donc il faut prévoir un maximum de 200 requêtes SQL.

    Seulement voilà, si on fait les instructions de test d’existence et d’insertion au niveau PHP, cela fait 200 aller retour entre PHP et la base Mysql. Hors ce qui plombe un script c’est souvent ces aller retour incessants .. Un moyen d’optimiser ce traitement a été de créer une procédure stocké qui prend en paramètre le fichier XML (où plutôt une représentation épuré plus strict), et de faire exécuter les 200 requêtes DANS Mysql. Chouette non ? Plus rapide, plus stable, plus optimisé .. que du bonheur.

    Procédure Stocké en Mysql

    Vous allez rire, mais d’après mes recherches googlidiennes très peu de personnes font des procédures en Mysql. Cela vient sans doute du fait qu’elles sont apparru avec mysql 5 et que c’est encore « neuf ».

    Tous les exemples que l’on trouve sur le sujet sont pour SQL serveur PRo, donc du transact-SQL. Et le souci c’est qu’il n’y pas forcement d’équivalence entre les fonctions transact-SQL et les fonctions mysql. La synthaxe n’est décidement pas la même.

    Outil utilisé

    Mysql query browser !

    L’exemple que je donne n’est valable qu’a travers sont interface .. zarb.. m’enfin c’est un très bon outil quand même, rien à voir avec un phpMyAdmin tout POURRI !

    Création de la requête

    Voilà la précédure allégé que j’ai crée (je fais d’autres test en version un peu plus lourde). Je vais essayer de la commenter au mieux.

    La procédure s’appelle import, elle est dans la base de donné nommé « Fluxe » et on l’appelle et l’utilise comme ceci

    1
    2
    3
    4
    
    call import(
    "premier flux|http://monsite|http://monsite/fluxrss|description;
    deuxieme flix|http://monsite2|http://www.google.fr/bidule.xml|autre description;
    troisieme|http://monsite3|http://monsite2/rss.xml|description;",3);

    Le premier paramètre est la chaine retravaillé coté PHP qui correspond à chaque ligne de l’XML, et le deuxième paramètre est le nombre de flux. Une entré se fini via un « ; » et chaque colonne est séparé via un  » | « . Bon je sais, mes délimiteurs ne sont pas assez complexe dans cet exemple et cela serait risque de bug si une description ou url comporterait des points virgules et des pipes…

    Voici la requête :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    
    DELIMITER $$  -- pas trop compris l'interet O_o
    DROP PROCEDURE IF EXISTS `fluxe`.`import` $$
    -- Supprime la procédure si elle existe (je suis fort en anglais hein ? ^^) 
     
    CREATE DEFINER=`root`@`localhost` PROCEDURE `import`(In myCsv Text, In nbElement Int)
    -- crée la procédure avec deux paramètres "IN" de type Text et Int. C'est à dire que c'est deux paramètres obligatoires pour exécuter la requête.  
     
    BEGIN -- Debut de la procédure
     
    -- Declare mes variables correspondant aux colonnes de ma table Flux
    DECLARE nom varchar(255);
    DECLARE urlSite varchar(255);
    DECLARE myurlFlux varchar(255);
    DECLARE description text;
    DECLARE  ligne_flux VARCHAR(1000); -- correspondra à une ligne de Flux
    DECLARE cpt int; -- un petit compteur pour faire une jolie boucle While
     
    set cpt = nbElement;  -- / !  Voilà comment on définit une variable SET var = valeur; 
     
    WHILE cpt <= nbElement and cpt > 0 do  -- j'aurais pu utiliser une boucle Loop aussi.
     
      select replace(substring(substring_index(myCsv, ';', cpt), length(substring_index(myCsv, ';', cpt - 1)) + 1), ';', '') as ligne into ligne_flux;
     
      -- Outch "KEKECER ?" vous devez vous pausez comme question :)
      -- c'est simple, je découpe le premier paramètre In pour en extraire la 1er ligne trouvé.
      -- Les fonctions appellés ne sont pas du php mais bien du SQL ! Puissant non ?
      -- l'idée de récupèrer la 1er ligne DANS une variable "ligne_flux", d'où le "select ... INTO variable" 
     
      -- Parsage de la chaine "ligne_flux" pour en extraire les colonnes, rebelote quoi ..
      select  replace(substring(substring_index(ligne_flux, '|', 1), length(substring_index(ligne_flux, '|', 1 - 1)) + 1), '|', '') into nom;
      select  replace(substring(substring_index(ligne_flux, '|', 2), length(substring_index(ligne_flux, '|', 2 - 1)) + 1), '|', '') into urlSite;
      select  replace(substring(substring_index(ligne_flux, '|', 3), length(substring_index(ligne_flux, '|', 3 - 1)) + 1), '|', '') into myurlFlux;
      select  replace(substring(substring_index(ligne_flux, '|', 4), length(substring_index(ligne_flux, '|', 4 - 1)) + 1), '|', '') into description;
     
      -- Insertion que si la ligne n'existe pas. Le critère est l'url du flux.
      if(NOT EXISTS (select * from flux where urlFlux = myurlFlux)) THEN
           insert into flux (nom,urlSite,urlFlux,description) values (nom,urlSite,myurlFlux,description);
       end if;
     
     -- on décrémente le compteur
     set cpt = cpt - 1;
    end while;
     
    END $$
    DELIMITER ;

    Et voilà c’est fini !

    Lien de référence

    La base :http://dev.mysql.com/doc/refman/5.0/fr/stored-procedure-syntax.html

    les variables : http://dev.mysql.com/doc/refman/5.0/fr/variables-in-stored-procedures.html
    If, while, case, et tous les contrôles : http://dev.mysql.com/doc/refman/5.0/fr/flow-control-constructs.html

    Mis à part la documentation officiel, j’ai rien trouvé d’autres à me mettre sous la dent ..

    Billet écrit dans : SQL 02.12.2008 Quelqu'un a réagi !
  • Billet écrit dans : Découverte PHP 06.09.2008 7 Réactions !!

    Dans ce billet nous allons voir comment utiliser Ajax pour valider champ par champ un formulaire constuit avec sfForm de Symfony 1.1.

    Je prend pour acquis que vous ayez lu / que vous connaissez la validatation de formulaire « classique » offert par sfForm. Tout est expliqué et surtout .

    En fait le souci avec les validateurs symfony 1.1 est qu’ils ne possèdent pas la méthode isValid() contraitement aux validateurs symfony 1.0. Du coup, quand on fait de l’ajax, nous sommes obligés de passer d’un type de validateur à un autre.

    Cette exemple illuste le fait avec le validateur sfValidatorEmail (sf 1.1) et sfEmailValidator (sf 1.0). L’idée est de définir les paramêtres du validateur (les messages d’erreurs dans notre cas) qu’à un seul endroid afin de respecter le concept Dry (Don’t Repeat Yourself).

    Voilà les étapes nécessaires pour mettre en place une validation de formulaire classique et « Ajaxé » :

    1. Vous créez vos widgets dans lib/form/MaClasseForm.class.php
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
      class MaClasseForm extends BaseMaClassForm
      {
      	public function configure()
      	{
      		$this->setWidgets(array(
      		'id'         => new sfWidgetFormInputHidden(),
      		'FirstName' => new sfWidgetFormInput(),
      		'LastName'  => new sfWidgetFormInput(),
      		'Email'      => new sfWidgetFormInput(),
      		'password_repeat'      => new sfWidgetFormInput(),
      		'password'      => new sfWidgetFormInput()
      		));
       
                      // On peut modifier les labels si besoin est ...
      		$this->widgetSchema->setLabels(array(
      		'FirstName'    => 'Prenom',
      		'Email'   => 'addresse Email ',
      		'LastName' => 'Nom',
      		));
              }
      }
    2. Vous définissez vos validateurs dans lib/form/MaClasseForm.class.php à la suite de la méthode configure()
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
       
       $this->setValidators(array(
      'id'         => new sfValidatorPropelChoice(array('model' => 'Utilisateurs', 'column' => 'id', 'required' => false)),
      'FirstName' => new sfValidatorString(array('max_length' => 20, 'required' => false)),
      'LastName'  => new sfValidatorString(array('max_length' => 20, 'required' => false)),
      'password'      => new sfValidatorString(array('max_length' => 40, 'required' => false)),
      'password_repeat' => new sfValidatorString(array('max_length' => 40, 'required' => false)),
      'Email'      => new sfValidatorEmail(array('required' => true)),
      'Birthday'   => new sfValidatorDate(array('required' => false)),
      ));
       
      // J'en profite pour definir un nom formater des champs pour avoir $_POST['utilisateurs['email']] etc
      $this->widgetSchema->setNameFormat('utilisateurs[%s]');
    3. Normallement à ce stade vous pouvez utiliser la classe de formulaire dans votre fichier apps/MonApp/MonModule/actions/actions.class.php
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
      public function executeMonAction() {
       
         $this->formInscription = new MaClasseForm();
         if ($this->request->isMethod('post'))
         {
            // récupère les champs du formulaire sous forme de tableau.
           $array_champ = $this->getRequest()->getParameter("utilisateurs");
           $this->formInscription->bind($array_champ);
           if ($this->formInscription->isValid())
      	{
             // Formulaire valide.
             }
      }
    4. Voilà la validation « classique » et faite. Et l’ajax dans tout ça ? On y vient ^^ Alors au lieu d’afficher le formulaire de manière classique (via le template adéquate apps/MonApp/MonModule/templates/MonActionSuccess.php)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      
      // Au lieu de
      echo $form;
       
      // On va rendre le template comme on le souhaite
      // Par souci de flemme je ne fait que le champ Email
      <table>
        <tr>
          <td><?php echo $form['Email ']->renderLabel() ?>:</td>
          <td><?php echo $form['Email '] ?></td>
         <td><span id="error_for_email ">
                      <?php if ($form['Email ']->hasError()): ?>
      		<ul class="error_list">
      		<?php foreach ($form['Email ']-<getError() as $error): ?>
      			<li class="error_for"><?php echo $error ?></li>
      			<?php endforeach; ?>
      		</ul>
      		<?php endif; ?> </span></td>
        </tr>
      </table>
    5. Et maintenant on va jouer avec l’ajax pour vérifier ce champ email lors de la saisie.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
      /************** Ajax Time :) *****************/
      // Dans le template on met :
      <?php
      // Crée l'observateur pour le champ utilisateurs_email du formulaire.
      echo observe_field("utilisateurs_email",array(
      'update' => "error_for_email",
      'url' => "MonModule/VerifierEmail",
      'with' => "'email='+ value",
      ));
      ?>
    6. On rajoute l’action VerifierEmail() dans le fichier d’actions.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      
       
      public function executeVerifierEmail()
      	{
      	 // Initialisation des variables internes
      	 $chaine_a_verifier = $this->getRequestParameter("email");
      	 $array_erreur = array();
       
      	 // Récupère le validateur d'email définit dans lib/form/MaClasseForm.class.php
      	 $form = new MaClasseForm();
      	 $validateur_form = $form->getValidatorSchema();
      	 $all_validateurs_field = $validateur_form->getFields();
      	 $email = $all_validateurs_field["email"];
       
      	 // Récupere le bon code d'erreur
      	 if($chaine_a_verifier == "")
      	 {
      	 	$array_erreur =	array('email_error' => $email->getMessage("required"));
      	 }
      	 else
      	 {
      	 	$array_erreur =	array('email_error' => $email->getMessage("invalid"));
      	 }
       
      	 // Crée un validateur d'email hérité de Symfony 1.0
      	 $validateur_email = new sfEmailValidator($this->getContext());
       
               // Un peu de Dry (Don't Repeat Yourself) afin d'éviter d'avoir des messages d'erreurs définit à deux endroits différents.
      	 $validateur_email->initialize($this->getContext(), $array_erreur  );
      	 // Dispatcheur vers le bon templates
      	 if($validateur_email->execute($chaine_a_verifier,$message_retour))
      	 {
      	 	$message_a_afficher = $this->getPartial("ajax_error",array("message"=>$message_retour,"reussi"=>true));
      	 }
      	 else
      	 {
      	 	$message_a_afficher = $this->getPartial("ajax_error",array("message"=>$message_retour,"reussi"=>false));
      	 }
       
      	 return $this->renderText($message_a_afficher);
      	}
    7. Donc en fait on met à jour le « span » error_for_email avec le résultat de l’appel de la méthode VerifierEmail(). J’ai utilisé un template partial car le but est de le réutiliser pour tous les appels en Ajax. Celui ci n’est pas bien compliqué
      1
      2
      3
      4
      
      <ul>
          <li class="<?php if($reussi) echo "accept_for"; else echo "error_for"; ?>">&amp;nbsp;<?php echo $message; ?>
          </li>
      </ul>
    8. Et voilà, le formulaire fonctionne classiquement et dispose de vérifications en Ajax aussi.

    Nous venons de voir le validateur d’Email, mais tous fonctionnent sur le même principe. Voici la méthode pour utiliser le validateur de Sting qui diffère un peu.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
       public function executeVerifierPseudo()
       {
         // Initialisation des variables internes
        $chaine_a_verifier = trim($this->getRequestParameter("pseudo"));
     
        // Récupère le validateur de pseudo
        $form = new MaClasseForm();
        $validateur_form = $form->getValidatorSchema();
        $all_validateurs_field = $validateur_form->getFields();
        $validateurs = $all_validateurs_field["FirstName"];
        $pseudo = $validateurs->getValidators();
        $pseudo = $pseudo[0];
     
        // Crée un validateur de String sf1.0
        $validateur_pseudo = new sfStringValidator($this->getContext());
        $validateur_pseudo->initialize($this->getContext(), array(
        'min' => $pseudo->getOption("min_length"),
        'min_error' => $pseudo->getMessage("min_length"),
        'max' =>$pseudo->getOption("max_length"),
        'max_error' => $pseudo->getMessage("max_length"),
        'required' => $pseudo->getMessage("required"),
        ));
        // Dispatcheur vers le bon templates
        if($validateur_pseudo->execute($chaine_a_verifier,$message_retour) &amp;&amp; $chaine_a_verifier != "")
        {
           $message_a_afficher = $this->getPartial("ajax_error",array("message"=>$message_retour,"reussi"=>$bool));
        }
        else
        {
         if($chaine_a_verifier == "") $test = $pseudo->getMessage("required");
     
         // Remplace les jokers des messages d'erreurs.
         $min = $pseudo->getOption("min_length");
         $max = $pseudo->getOption("max_length");
         $message_retour=    preg_replace("#%value%#",$chaine_a_verifier,$message_retour);
         $message_retour=    preg_replace("#%min_length%#",$min,$message_retour);
         $message_retour=    preg_replace("#%max_length%#",$max,$message_retour);
         $message_a_afficher = $this->getPartial("ajax_error",array("message"=>$message_retour,"reussi"=>false));
       }
     
       return $this->renderText($message_a_afficher);
    }

    Et voilà c’est fini :)

    Billet écrit dans : Découverte PHP 06.09.2008 7 Réactions !!
CV Guillaume chave