8768 sujets

Développement web côté serveur, CMS

Pages :
Salut à tous,

Je vous propose un sujet pour échanger des trucs, astuces et bonnes pratiques en PHP.

Si on peut juste éviter les trucs genre micro-optimisation (echo plutôt que print, simple quote plutôt que double quote) qui ne servent à rien à part perdre du temps ça serait cool. Smiley smile

Je commence :

Yoda condition

C'est une convention d'écriture qui veut qu'on place toujours les constantes à gauche et les variables à droite dans une condition.


if (false == $foo && 1 == $bar) {
  // fait un truc
}


Ça peut paraitre étrange mais en fait c'est très utile pour éviter des bugs très difficile à tracker :


if ($foo = false) {
  // fait un truc
}


J'ai oublié un signe égale dans ma condition. Dans ce cas je n'aurais pas d'erreur mais ma condition ne fonctionnera pas et la variable $foo se verra toujours assigner la valeur false. Comme je l'ai dit c'est un bug difficile à traquer car il ne cause pas d'erreur visible. En revanche si j'écris :


if (5 = $foo) {
  // fait un truc
}


Là j'aurais bien une erreur fatale car j'éssai d'assigner une valeur ($foo) à une constante (5).
Bon puisque y'a pas foule, je continue. Smiley smile

Pour vérifier si une variable existe et n'est pas nulle on voit parfois des conditions qui utilisent à la fois isset() et empty() :


if (isset($foo) && !empty($foo))


En fait en interne, ces 2 fonctions sont identiques (la fonction qui traduit ça pour le zend engine s'appelle zend_do_isset_or_isempty()) à la différence que empty() en plus de tester si la variable existe, teste aussi si elle est non nulle. Sont considérés comme nulles les valeurs : null, false, 0, une chaine vide ou ne contenant que des 0.

Donc quand on utilise empty() pour tester une variable il n'y a aucune raison d'utiliser isset() en même temps, il suffit de faire :


if (!empty($foo))

Modifié par jb_gfx (13 Aug 2012 - 14:37)
Ah voilà un topic qu'il va être bien !

Le Yoda condition est vraiment bien et je ne connaissais pas, par contre je crois que je vais avoir du mal à me l'intégrer, l'impression de bosser à l'envers...
Sympa comme topic !

J'en profite pour vous poser une question :

Comment faites-vous pour conserver une indentation de code HTML correcte ? Dès que j'insère du code HTML via du PHP, je perds évidemment tous mes retours à la ligne et tabulations. Insérer ces caractères avec "\n" et "\t" me paraît trop tiré par les cheveux. Est-ce la seule solution?
Anymah a écrit :

Comment faites-vous pour conserver une indentation de code HTML correcte ? Dès que j'insère du code HTML via du PHP, je perds évidemment tous mes retours à la ligne et tabulations. Insérer ces caractères avec "\n" et "\t" me paraît trop tiré par les cheveux. Est-ce la seule solution?


Pour moi c'est simple : je ne met jamais de HTML dans du PHP. Tout comme je ne met jamais de CSS dans mon HTML. Je garde toutes les couches bien séparées à tous les niveaux.
jb_gfx a écrit :


Pour moi c'est simple : je ne met jamais de HTML dans du PHP. Tout comme je ne met jamais de CSS dans mon HTML. Je garde toutes les couches bien séparées à tous les niveaux.

Euh, ça t'est jamais arrivé de devoir générer un tableau de données issus d'une table en php ? Smiley eek ou alors tu fermes et ouvres les balises php entre deux balises ? (je sais que ça se fait, je l'utilise moi-même parfois)



En petite astuce, c'est un simple truc idiot, mais sur un serveur un peu limite en capacité, ça peut changer pas mal : pensez à unset() sur les tableaux de ressources issues du mysql quand vous en avez plus besoin, elles prennent souvent pas mal de places pour pas grand chose.
Lothindil a écrit :

Euh, ça t'est jamais arrivé de devoir générer un tableau de données issus d'une table en php ? Smiley eek ou alors tu fermes et ouvres les balises php entre deux balises ? (je sais que ça se fait, je l'utilise moi-même parfois)


Oui je fais comme ça. Regarde tous les frameworks modernes ils fonctionnent tous sur ce mode.


<table class="user">
  <thead>
    <th>ID</th>  
    <th>Prénom</th>
    <th>Nom</th>
    <th>Email</th>
    <th>Date inscription</th>
    <th>Dernière connexion</th>
  </thead>
  <tbody>
  <?php foreach ($users as $user): ?>
    <tr>
      <td><?=$user->user_id ?></td>  
      <td><?=$user->first_name ?></td>
      <td><?=$user->last_name ?></td>
      <td><?=$user->email ?></td>
      <td><?=$user->created_at ?></td>
      <td><?=$user->updated_at ?></td>
    </tr>
  <?php endforeach; ?>
  </tbody>
</table>

Modifié par jb_gfx (13 Aug 2012 - 17:36)
Allez une bonne pratique simple mais super importante : l'extension mysql c'est fini.

Cette extension n'est plus développé depuis 2004 et elle n'est pas capable de tirer partie de fonctionnalités de MySQL ajoutées depuis cette date. Elle ne reste dans PHP que pour assurer la comptabilité avec les vieux projets.

Elle est officiellement marquée comme obsolète depuis 2010 :

http://www.php.net/manual/fr/intro.mysql.php

On devra donc choisir en mysqli ou PDO. PDO est plus simple à utiliser et plus complète donc le choix est vite vu.
jb_gfx a écrit :


Pour moi c'est simple : je ne met jamais de HTML dans du PHP.

Salut.
Super topic btw merci de partager.

Mais kezako pas HTML dans PHP ? en quoi c bonne pratique ? Pour la maintenance j'imagine?

Sinon perso je préfère include a require. Même perf alors qu'une bouze dans require te fout en l'air le script entier
jmlapam a écrit :

Salut.
Super topic btw merci de partager.

Mais kezako pas HTML dans PHP ? en quoi c bonne pratique ? Pour la maintenance j'imagine?

Sinon perso je préfère include a require. Même perf alors qu'une bouze dans require te fout en l'air le script entier


Je pense que c'est justement le but de require de remonter une erreur fatale et d'arrêter l'éxecution du script. Ça évite d'avoir un code qui s'exécute avec des données obsolètes. Smiley biggrin

Concernant l'absence de HTML dans le PHP, c'est pour séparer les couches (comme le disait jb_gfx plus haut). Par exemple, garder tout le code HTML pour les vues, dans le cas d'un projet MVC, et n'utiliser le PHP que pour afficher le contenu des variables.

Une autre bonne pratique en vigueur, c'est de ne pas fermer les balises <?php à la fin des fichiers PHP (surtout ceux qui sont justement inclus), ceci afin d'éviter la présence d'une ligne vide ou d'un espace malencontreux qui empêcherait l'envoi d'headers plus tard dans le script. Smiley langue
Modifié par Modnar (14 Aug 2012 - 09:57)
jb_gfx a écrit :


Oui je fais comme ça. Regarde tous les frameworks modernes ils fonctionnent tous sur ce mode.


&lt;table class=&quot;user&quot;&gt;
  &lt;thead&gt;
    &lt;th&gt;ID&lt;/th&gt;  
    &lt;th&gt;Prénom&lt;/th&gt;
    &lt;th&gt;Nom&lt;/th&gt;
    &lt;th&gt;Email&lt;/th&gt;
    &lt;th&gt;Date inscription&lt;/th&gt;
    &lt;th&gt;Dernière connexion&lt;/th&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
  &lt;?php foreach ($users as $user): ?&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;?=$user-&gt;user_id ?&gt;&lt;/td&gt;  
      &lt;td&gt;&lt;?=$user-&gt;first_name ?&gt;&lt;/td&gt;
      &lt;td&gt;&lt;?=$user-&gt;last_name ?&gt;&lt;/td&gt;
      &lt;td&gt;&lt;?=$user-&gt;email ?&gt;&lt;/td&gt;
      &lt;td&gt;&lt;?=$user-&gt;created_at ?&gt;&lt;/td&gt;
      &lt;td&gt;&lt;?=$user-&gt;updated_at ?&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;?php endforeach; ?&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

*se rassure, pour une fois, je fonctionne presque comme ça aussi*

Au passage, c'est quoi ton :
<?=
Il signifie quoi exactement ? Parce que je l'ai jamais vu avant ^^


Au passage, petit truc : utiliser <?php au lieu de <? . Il m'est déjà arrivé plus d'une fois de tomber sur des serveurs mutualisés qui n'apprécient vraiment pas l'ouverture raccourcie.
Modifié par Lothindil (14 Aug 2012 - 10:25)
Modnar a écrit :


Je pense que c'est justement le but de require de remonter une erreur fatale et d'arrêter l'éxecution du script. Ça évite d'avoir un code qui s'exécute avec des données obsolètes. Smiley biggrin

Smiley lol
Je me doute bien que l'on a pas inventé require pour rien mais on le retrouve pourtant dans BCP de codes alors qu'un include suffirait amplement. Utiliser a bon escient les expressions fait partie de la bonne pratique. Smiley langue
Lothindil a écrit :

*se rassure, pour une fois, je fonctionne presque comme ça aussi*

Au passage, c'est quoi ton :
<?=
Il signifie quoi exactement ? Parce que je l'ai jamais vu avant ^^


Au passage, petit truc : utiliser <?php au lieu de <? . Il m'est déjà arrivé plus d'une fois de tomber sur des serveurs mutualisés qui n'apprécient vraiment pas l'ouverture raccourcie.


le <?= équivaut à <?php echo. C'est une sorte de raccourci assez pratique. Par contre, il faudrait vérifier la compatibilité selon la version PHP, avec l'activation ou non des shorts open tags. Dans les dernières versions, il me semble que les shorts open tags sont désactivés par défaut, mais qu'on peut tout de même utiliser <?= sans soucis... à confirmer. Smiley biggrin

jmlapam a écrit :

Smiley lol
Je me doute bien que l'on a pas inventé require pour rien mais on le retrouve pourtant dans BCP de codes alors qu'un include suffirait amplement. Utiliser a bon escient les expressions fait partie de la bonne pratique. Smiley langue

On est bien daccord. Smiley lol
Bonjour,
Je vous présente : L'autoload en PHP c'est sympa mais le problème c'est que c'est gourmand ....
Solution : un autoload avec un cache !!!!!
source 'DirectoriesAutoloader.php':

class ExtensionFilterIteratorDecorator extends FilterIterator {
   private $_ext;
   public function accept (){
      if (substr ($this->current (), -1 * strlen ($this->_ext)) === $this->_ext){
         return is_readable ($this->current ());
      }
      return false;
   }
   public function setExtension ($pExt){
      $this->_ext = $pExt;
   }
}
 
class DirectoriesAutoloaderException extends Exception {}
class DirectoriesAutoloader {
   //--- Singleton
   private function __construct (){}
   private static $_instance = false;
   public static function instance ($pTmpPath){
      if(self::$_instance === false){
         self::$_instance = new DirectoriesAutoloader();
         self::$_instance->setCachePath ($pTmpPath);
      }
      return self::$_instance;
   }
   //--- /Singleton
 
   //--- Cache
   private $_cachePath;
   public function setCachePath ($pTmp){
      if (!is_writable ($pTmp)){
         throw new DirectoriesAutoloaderException('Cannot write in given CachePath ['.$pTmp.']');
      }
      $this->_cachePath = $pTmp;
   }
   //--- /Cache
 
   //--- Autoload
   public function autoload ($pClassName){
      //On regarde si on connais la classe
      if ($this->_loadClass ($pClassName)){
         return true;
      }
 
      //Si on a le droit de tenter la regénération du fichier d'autoload, on retente l'histoire
      if ($this->_canRegenerate){
         $this->_canRegenerate = false;//pour éviter que l'on
         $this->_includesAll ();
         $this->_saveInCache ();
         return $this->autoload ($pClassName);
      }
      //on a vraiment rien trouvé.
      return false;
   }
   private $_canRegenerate = true;
   //--- /Autoload
 
   /**
    * Recherche de toutes les classes dans les répertoires donnés
    */
   private function _includesAll (){
      //Inclusion de toute les classes connues
      foreach ($this->_directories as $directory=>$recursive){
         $directories = new AppendIterator ();
 
         //On ajoute tous les chemins à parcourir
         if ($recursive){
            $directories->append (new RecursiveIteratorIterator (new RecursiveDirectoryIterator ($directory)));
         }else{
            $directories->append (new DirectoryIterator ($directory));
         }
 
         //On va filtrer les fichiers php depuis les répertoires trouvés.
         $files = new ExtensionFilterIteratorDecorator ($directories);
         $files->setExtension ('.php');
 
         foreach ($files as $fileName){
            $classes = $this->_extractClasses ((string) $fileName);
            foreach ($classes as $className=>$fileName){
               $this->_classes[strtolower ($className)] = $fileName;
            }
         }
      }
   }
 
   /**
    * Extraction des classes & interfaces d'un fichier
    */
   private function _extractClasses ($pFileName){
      $toReturn = array ();
      $tokens = token_get_all (file_get_contents ($pFileName, false));
      $tokens = array_filter ($tokens, 'is_array');
 
      $classHunt = false;
      foreach ($tokens as $token){
         if ($token[0] === T_INTERFACE || $token[0] === T_CLASS){
            $classHunt = true;
            continue;
         }
 
         if ($classHunt && $token[0] === T_STRING){
            $toReturn[$token[1]] = $pFileName;
            $classHunt = false;
         }
      }
 
      return $toReturn;
   }
   private $_classes = array ();
   private function _saveIncache (){
      $toSave = '<?php $classes = '.var_export ($this->_classes, true).'; ?>';
      file_put_contents ($this->_cachePath.'directoriesautoloader.cache.php', $toSave);
   }
 
   /**
    * Tente de charger une classe
    */
   private function _loadClass ($pClassName){
      $className = strtolower ($pClassName);
      if (count ($this->_classes) === 0){
         if (is_readable ($this->_cachePath.'directoriesautoloader.cache.php')){
            require ($this->_cachePath.'directoriesautoloader.cache.php');
                                $this->_classes = $classes;
         }
      }
      if (isset ($this->_classes[$className])){
         require_once ($this->_classes[$className]);
         return true;
      }
      return false;
   }
 
   /**
    * Ajoute un répertoire a la liste de ceux à autoloader
    */
   public function addDirectory ($pDirectory, $pRecursive = true){
      if (! is_readable ($pDirectory)){
         throw new DirectoriesAutoloaderException('Cannot read from ['.$pDirectory.']');
      }
      $this->_directories[$pDirectory] = $pRecursive ? true : false;
      return $this;
   }
   private $_directories = array ();
}

S'utilise de la manière suivante :

require_once 'DirectoriesAutoloader.php';
//car oui c'est un élément requis donc require, si il n'y est pas mon application doit planter immédiatement.
$autoloader = DirectoriesAutoloader::instance ('cache/')->addDirectory ('load');

Et après comme dans un environnement compilé c'est Byzance plus besoin de faire d'include de classe.
En réalité lorsque vous instanciez un objet avec le mot clé "new" si le fichier qui contient la classe n'est pas référencé dans le fichier de cache. L'autoload va remplir son rôle et chercher dans les dossiers données (dans mon cas "load") si la classe existe auquel cas il l'ajoute au fichier de cache.
Ainsi la recherche n'est effectué qu'une seule fois par classe et uniquement en phase de développement ce qui vous permet de vous passer d'écrire à la main tous vos petits include.
Modifié par Su4p (14 Aug 2012 - 14:16)
Une autre pratique que je trouve plutôt bonne Smiley cligne mais qui peut être discutable serait :

Avoir un et un seul point d'entré ! qui redirige vers les différents contrôleurs ou vers les différentes actions et vues à réaliser.

cela évite la redondance du code (include, session_start(), header()) et améliore sa maintenabilité.
jb_gfx a écrit :


Oui je fais comme ça. Regarde tous les frameworks modernes ils fonctionnent tous sur ce mode.


<table class="user">
  <thead>
    <th>ID</th>  
    <th>Prénom</th>
    <th>Nom</th>
    <th>Email</th>
    <th>Date inscription</th>
    <th>Dernière connexion</th>
  </thead>
  <tbody>
  <?php foreach ($users as $user): ?>
    <tr>
      <td><?=$user->user_id ?></td>  
      <td><?=$user->first_name ?></td>
      <td><?=$user->last_name ?></td>
      <td><?=$user->email ?></td>
      <td><?=$user->created_at ?></td>
      <td><?=$user->updated_at ?></td>
    </tr>
  <?php endforeach; ?>
  </tbody>
</table>


Peux aussi s'écrire (sans langage template) :

<table class="user">
  <thead>
    <th>ID</th>  
    <th>Prénom</th>
    <th>Nom</th>
    <th>Email</th>
    <th>Date inscription</th>
    <th>Dernière connexion</th>
  </thead>
  <tbody>
  <?php foreach ($users as $user){ ?>
    <tr>
      <td><?=$user->user_id ?></td>  
      <td><?=$user->first_name ?></td>
      <td><?=$user->last_name ?></td>
      <td><?=$user->email ?></td>
      <td><?=$user->created_at ?></td>
      <td><?=$user->updated_at ?></td>
    </tr>
  <?php } ?>
  </tbody>
</table>

Oui le HTML dans du PHP c'est le mal !!! Pourquoi pas taper du js et du css dans du PHP tant qu'on y est !
Modifié par Su4p (14 Aug 2012 - 14:15)
Su4p a écrit :

Peux aussi s'écrire (sans langage template) :


Je n'ai pas utilisé de langage de template dans mon exemple, c'est du pur PHP. Smiley cligne
Su4p a écrit :
Une autre pratique que je trouve plutôt bonne Smiley cligne mais qui peut être discutable serait :

Avoir un et un seul point d'entré ! qui redirige vers les différents contrôleurs ou vers les différentes actions et vues à réaliser.

cela évite la redondance du code (include, session_start(), header()) et améliore sa maintenabilité.

Ce que j'ai fait perso pour éviter la redondance (après, je sais pas ce que ça vaut), c'était un fichier inc.debut.php que j'inclus au départ de tous mes fichiers php et qui comprends :

- le session_start()
- l'include du fichier commun à tous mes fichiers pour les fonctions (fct.commun.php)
- la gestion des erreurs php (ce qui me permet de le différencier entre le test et la prod)
- le header de non-connexion
- le fichier de configuration des connexions mysql
- ainsi que le lancement d'une fonction d'activité (qui indique dans ma table que le joueur est actif sur le site)
jmlapam a écrit :

Mais kezako pas HTML dans PHP ? en quoi c bonne pratique ? Pour la maintenance j'imagine?


C'est un des résultat de l'application du principe "Separation of concerns" (je ne connais pas la traduction). On peut aller plus loin en disant : pas de PHP dans les vues.

Là tu as un excellent article qui explique le "Separation of concerns" : http://aspiringcraftsman.com/2008/01/03/art-of-separation-of-concerns/

jmlapam a écrit :

Sinon perso je préfère include a require. Même perf alors qu'une bouze dans require te fout en l'air le script entier



Include ne présente aucun intéret. Personnellement j'utilise uniquement require (quand un fichier manque tu VEUX que ça provoque une erreur).

Modnar a écrit :

Je pense que c'est justement le but de require de remonter une erreur fatale et d'arrêter l'éxecution du script. Ça évite d'avoir un code qui s'exécute avec des données obsolètes. Smiley biggrin


Absolument.

Modnar a écrit :

Concernant l'absence de HTML dans le PHP, c'est pour séparer les couches (comme le disait jb_gfx plus haut). Par exemple, garder tout le code HTML pour les vues, dans le cas d'un projet MVC, et n'utiliser le PHP que pour afficher le contenu des variables.


Oui, le template (la vue) devrait ne contenir aucune logique et simplement servir à un bête affichage des données.

Modnar a écrit :

le <?= C'est une sorte de raccourci assez pratique. Par contre, il faudrait vérifier la compatibilité selon la version PHP, avec l'activation ou non des shorts open tags. Dans les dernières versions, il me semble que les shorts open tags sont désactivés par défaut, mais qu'on peut tout de même utiliser &lt;?= sans soucis... à confirmer. Smiley biggrin


En PHP 5.3 et moins <?= nécessite que short open tags soit activé, en PHP 5.4 ce n'est plus le cas (d'ailleurs les short open tags n'existent plus).

Su4p a écrit :

Autant pour moi :
http://php.net/manual/fr/control-structures.alternative-syntax.php

J'étais persuader que cette syntaxe était propre à un langage de template.


Oui c'est une syntaxe très pratique pour les templates car elle améliore largement leur lisibilité.

Su4p a écrit :

Avoir un et un seul point d'entré ! qui redirige vers les différents contrôleurs ou vers les différentes actions et vues à réaliser.


+1
Modifié par jb_gfx (14 Aug 2012 - 16:17)
Pages :