Posts taggés Php

PHP 7 – Nouveautés de la nouvelle version de PHP

1

La date est fixée, dans moins de 3 semaines, le 12 Novembre prochain la première version stable de PHP 7 sera publiée. L’occasion de faire le point sur les nouveautés de ce langage utilisé par plus de 80% des sites et applications web selon les dernières estimations.

Un petit retour en arrière pour commencer

Avant de se pencher sur cette nouvelle version, il peut être intéressant de voir comment ce langage a évolué depuis sa création en 1994 par Rasmus Lerdorf.

A cette époque, Rasmus maintient son site personnel et cherche à garder une trace des consultations sur son CV. Il développe ainsi une bibliothèque en C qu’il enrichit en fonctionnalités au fur et à mesure jusqu’à décider de la publier en 1995 sous le nom de PHP/FI (Personal Home Page Forms Interpreter) puis PHP Tools.  Alors qu’il n’a jamais souhaité en faire un langage de programmation en tant que tel, l’engouement nait autour du projet et une équipe de développement se forme faisant évoluer la bibliothèque en un langage jusqu’à la publication d’une version 2 en 1997. A cette époque, le nouveau langage essuie de nombreuses critiques car il manque cruellement de consistance tant au niveau du nommage de ses fonctions que sur l’ordonnancement de leurs paramètres.

C’est à ce moment que deux développeurs (Zeev Suraski et Andi Gutmans) se lancent dans le projet en commençant par réécrire le parseur en collaboration avec Rasmus. PHP 3 qui devient un acronyme « PHP Hypertext Preprocessor », commence à ressembler à notre PHP moderne avec un début de support de la programmation orientée objet, un support de nombreuses bases de données mais également des possibilités d’extension qui continuent de rassembler une communauté autour du projet.  PHP 3 est installé sur plus de 10% des serveurs web à l’époque.

Fort de ce succès, Zeev et Andi se concentrent sur le cœur de PHP qui deviendra le Zend Engine (formé de leur 2 prénoms ZEev et aNDi) et donnera naissance à la société du même nom. Le Zend Engine est intégré à la nouvelle version de PHP 4 publiée en 2000 tout en conservant une bonne compatibilité avec la version antérieure. Les grandes nouveautés de PHP 4 sont l’output buffering, l’intégration de l’opérateur « === », l’intégration des composants COM Windows et un changement de licence (PHP Licence très proche de la licence BSD).

Il faudra attendre 4 années supplémentaires pour voir arriver la version moderne de PHP que nous connaissons, la version 5, qui apporte son lot de nouveautés. Le modèle objet est ainsi complètement remanié avec, entre autres, l’apparition des constructeurs/destructeurs modernes, la portée des variables jusque lors uniquement publique, les interfaces et classes abstraites, la gestion des exceptions, etc. C’est également l’arrivée de PDO, SQLite ou encore SimpleXML.

PHP se professionnalise donc et l’engouement autour de lui ne se dément pas. Au contraire, il continue à croître, encouragé par son adoption par les grands noms du web comme Yahoo ou encore Facebook.

En 2005, Andrei Zmievski entreprend d’offrir à PHP un support natif d’Unicode en embarquant la librairie ICU (International Components for Unicode) afin de répondre aux critiques de certains développeurs sur l’absence de celui-ci.  L’implémentation choisie requiert une représentation interne des chaînes de caractères en UTF-16 mais l’équipe se heurte à des problèmes de performances et manque d’experts en Unicode. Après avoir été maintes fois reporté, le projet est finalement abandonné. Toutes les évolutions du langage prévues autres que l’Unicode, sont reportées sur les versions 5.3 (namespaces, fonctions anonymes) et 5.4 (traits) respectivement en 2009 puis 2012.

En parallèle, un cycle programmé de publication des nouvelles versions est voté et adopté en 2010.
Celui-ci prévoit une nouvelle version du langage tous les ans ainsi qu’une durée de support de 3 ans pour chacune d’elles (2 ans de correctifs puis 1 an de patchs de sécurité uniquement) et une rétrocompatibilité entre toutes les versions mineures (5.x par exemple).

Voté ?

Oui, les plus attentifs auront remarqué que cette décision a été votée.
En effet, depuis 2011, chaque évolution du langage fait l’objet d’une proposition de RFC disponible sur le wiki de PHP (https://wiki.php.net/rfc) qui est votée à l’issue d’une période de discussion.
L’ensemble des contributeurs de PHP (c’est-à-dire les détenteurs d’un compte SVN) ainsi que des personnes représentatives de la communauté PHP (Lead developers de frameworks, CMS ou autres composants répandus) peuvent prendre part au vote. Chaque proposition doit recueillir au moins la majorité absolue pour être acceptée, voire 2/3 des votes pour les propositions ayant un impact fort sur le langage (nouvelle syntaxe par exemple).

C’est ainsi que l’ensemble des nouveautés du langage dans sa version 7 a été soumis au vote jusqu’à sa dénomination en PHP 7.

PHP 6 vs PHP 7

La version actuelle de PHP étant la 5.6, il aurait été assez logique que la nouvelle version majeure de PHP prenne le numéro 6.

Cette question a été au centre du débat il y a un an, amené par la RFC « Name of Next Release of PHP » en Juillet 2014 de 2 contributeurs (dont Zeev Suraski, un des auteurs du Zend Engine pour ceux qui suivent ;-)).

Les arguments majeurs en faveur de PHP 7 furent :

  • PHP 6 est le nom d’un projet qui a déjà existé, qui est documenté (il existe de nombreux articles sur PHP 6) et qui est connu de la plupart des membres de la communauté PHP. Cela pourrait être donc source de confusion.
  • De nombreux projets ont déjà sauté des pas dans la numérotation des versions de leurs projets (MariaDB par exemple est passé de la version 5.5 à la version 10.0). Il y a donc des précédents.
  • La version 6 est généralement associée à l’échec dans le monde des langages (Le précédent projet PHP 6 ne fait pas exception mais on peut également citer Perl 6 ou encore MySQL 6 qui n’a jamais été publié).
  • Le chiffre 7 est un porte bonheur dans de nombreuses cultures (non celle-ci n’était pas sérieuse ;-))

Ces arguments l’emportent lors du vote à 58 voix contre 24, la nouvelle version majeure de PHP sera donc la 7.

Il est temps désormais de rentrer dans le vif du sujet et d’aborder les principales nouveautés du langage.

HHVM vs PHPNG : PHP Next Generation

Vous avez certainement déjà entendu parler de HHVM ou HipHop for PHP.
HipHop est un compilateur créé par Facebook en 2008 qui permet de transformer puis compiler du code PHP 5.2 en C++ afin d’améliorer les performances à l’exécution et de gagner en ressources serveur. Ce produit a évolué ensuite en HHVM (pour HipHop Virtual Machine) à partir de 2010 reposant cette fois-ci sur une compilation Just In Time (ou à la volée). Cette technologie a eu le vent en poupe ces dernières années permettant d’atteindre des performances plus que doublées dans certains usages par rapport à un couple Apache/PHP 5.x ou Nginx/PHP 5.x-FPM par exemple.

PHPNG peut être vu comme la riposte de Zend à HHVM.
En Mai 2014, une branche fait son apparition sur le SVN de PHP afin d’étudier l’implémentation native de la compilation JIT à PHP.  Finalement, le projet tourne au nettoyage de printemps, les APIs core sont épurées et les structures internes de données sont modifiées permettant une amélioration conséquente des performances et de l’utilisation mémoire. Une RFC propose son intégration à la version 7 de PHP et est adoptée à la quasi-unanimité.

 

FigA

 

Beaucoup de benchmarks sont sortis ces derniers temps tendant à démontrer le gain en performance de PHPNG (et donc PHP 7) par rapport à PHP 5.6 et HHVM.  Si on prend l’un de ceux réalisés par Zend sur des frameworks répandus (Fig. A), on remarque que l’on approche d’un gain de 100% par rapport à PHP 5.6 et que PHPNG surpasse HHVM. Il faut cependant prendre ces chiffres avec prudence. Les benchmarks sont souvent réalisés sur des calculs ou des portions de code qui ne reflètent en rien une application typique et un cas concret. De plus, et c’est le cas de Zend, l’auteur du benchmark est souvent parti pris dans la comparaison et on peut gager qu’il met principalement en avant les cas qui sont à son avantage.

Si on regarde l’ensemble des benchmarks réalisés par différentes personnes ou organismes, PHPNG et HHVM sont au coude à coude en moyenne. Pour ce qui nous intéresse le plus, le gain de performance est évident entre PHP 5.6 et PHP 7 mais dans une proportion plus raisonnable. N’espérez donc pas diviser par deux le temps de génération des pages de votre application même si le gain se révélera très certainement appréciable.

Un typage plus évolué

C’est certainement l’une des évolutions proposées qui aura le plus déchaîné les passions lors de la phase de discussion de la RFC liée : Le typage scalaire, le typage des valeurs de retour d’une méthode et le typage strict.

Jusque lors, il était possible de typer les paramètres d’entrée d’une fonction mais uniquement pour les objets et tableaux et non pour les types dit « scalaires » (c’est-à-dire int, float, string et bool).
C’est désormais possible mais cela va encore plus loin car il est désormais possible de spécifier également le type de retour d’une fonction. Tout cela n’est bien entendu pas obligatoire mais cela vous permettra d’écrire un code plus propre et vous évitera certainement bien des bugs via la détection d’appels incorrects qu’offrent déjà aujourd’hui de nombreux IDE.

function isOdd(int $i) : bool {
    return $i % 2 === 1;
}

function add(float $a, float $b) : float {
    return $a + $b;
}
Exemple de la nouvelle syntaxe

L’autre nouveauté est la possibilité de configurer, à un niveau très strict, le contrôle du type des arguments passés à une fonction. Ainsi, si on active ce comportement en ajoutant la ligne

declare(strict_types=1);

en première ligne de notre fichier PHP, le code suivant devient incorrect et générera une erreur fatale :

isOdd('12');
isOdd(1.5);

La chaine de caractères ’12’ qui est habituellement castée en un (int) 12 et le (float) 1.5 en un (int) 1 ne seront plus acceptés dans ce cas.

Bien entendu ce comportement est, encore une fois, optionnel et il appartient au développeur de l’activer s’il le souhaite.

De nouveaux opérateurs

La version 7.0 est également l’occasion de voir 2 nouveaux opérateurs faire leur entrée.
Le premier est un opérateur de comparaison combiné (<=>) appelé également « Spaceship operator » en raison de sa forme de vaisseau spacial. Il vient rejoindre la liste des opérateurs de comparaison que nous connaissons bien (==, <=, <, >, >=) mais contrairement à eux il ne renvoie pas une valeur booléenne mais  3 valeurs distinctes.

Si Droite < Gauche 5 <=> 6 -1
Si Droite = Gauche 6 <=> 6 0
Si Droite > Gauche 6 <=> 5 1

Cela fonctionne à la fois sur des valeurs numériques (int et float) mais également sur des chaines de caractères, des tableaux et même des objets !

Cela a surtout de l’intérêt dans les algorithmes de tri en réduisant considérablement le code écrit :

$tab = array(1,4,3,2,2);

// PHP 5.6
usort($tab, function($a, $b) {
    return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);
});

// PHP 7
usort($tab, function($a, $b) {
    return $a <=> $b;
});
Exemple de la nouvelle syntaxe

Le second opérateur vient compléter avantageusement l’opérateur ternaire existant.
Il se nomme « Null Coalesce Operator » et prend la forme de deux points d’interrogation (??).
Il permet de s’assurer en une seule opération que la variable existe et qu’elle n’est pas nulle.

// Code PHP 5.x
$val = isset($tab['some_key']) && !empty($tab['some_key']) ? $tab['some_key'] : 'default_value';

// Equivalent PHP 7
$val = $tab['some_key'] ?? 'default_value';

// Enchainement possible
$val = $tab['some_key'] ?? $tab['other_key'] ?? 'default_value';
Exemple de la nouvelle syntaxe

Si la valeur gauche est définie et n’est pas nulle, elle est retournée. Dans le cas contraire c’est la valeur droite qui est retournée.

Imbrication des « use »

Il est désormais possible de regrouper les déclarations uses en groupe afin de gagner en lisibilité et d’éviter de répéter le même chemin n fois comme ceci :


use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion as Choice;
use Symfony\Component\Console\Question\ConfirmationQuestion;
Ancienne syntaxe

 


use Symfony\Component\Console\{  
    Output\OutputInterface,
    Question\Question,
    Question\ChoiceQuestion as Choice,
    Question\ConfirmationQuestion
};
Nouvelle syntaxe

Interception des erreurs fatales

Si, jusque lors, intercepter une erreur fatale était chose très difficile et même déconseillée, cela est désormais possible en PHP 7.
En effet, une erreur fatale va pouvoir être catchée tout comme une exception l’est aujourd’hui.
Pour cela une nouvelle organisation de l’arborescence a été mise en place :

FigB

 

 

Ainsi, la classe de base des exceptions « Exception » que l’on connaît aujourd’hui implémente désormais une interface « Throwable ». Cette interface regroupe l’ensemble des méthodes actuelles d’une Exception (getMessage(), getTrace(), etc.)

Une nouvelle classe « Error » apparaît qui implémente également cette interface et  représente la base d’une erreur remontée par PHP lors de l’exécution.  Il est possible ensuite de faire la distinction entre 2 types d’erreurs :

  • ParseError : Erreur du parseur PHP lorsque du code est évalué via eval()
  • TypeError : Ensemble des autres erreurs rencontrées à l’exécution

Cette nouvelle implémentation a l’avantage d’être compatible avec le code existant aujourd’hui.
En effet, les « catch » de votre code ne risqueront pas d’attraper une erreur fatale et, sans modifications de votre part, les erreurs fatales se comporteront toujours comme elles le font aujourd’hui, c’est-à-dire par une belle page blanche avec quelques lignes noires.

En revanche, il va vous être possible de traiter des erreurs comme ceci par la suite :


try {
    $obj->methodeInexistante();
} catch (\Exception $e) {
    // Dans ce cas précis, on ne passera jamais ici
} catch (\Error $e) {
    $logger->error($e->getMessage());
    // Renvoyer l’utilisateur vers une page d’erreur
}
Exemple de la nouvelle syntaxe

Mots réservés et scopes

Suite au typage des valeurs scalaires, les termes « int », « float », « string », « bool », « null », « true », « false », « resource », « objec t », « mixed » et « numeric » deviennent protégés et ne peuvent plus être utilisés en tant que nom de classe, interface ou trait.

En revanche, la liste des mots réservés ayant tendance à s’allonger (plus d’une soixantaine aujourd’hui), il sera désormais possible d’utiliser ces mots pour nommer des membres d’une classe, d’une interface ou d’un trait.


class Collection extends \ArrayAccess, \Countable, \IteratorAggregate {

    public function forEach(callable $callback) {
        //…
    }

    public function list() {
        //…
    }

    public static function new(array $items) {
        return new self($items);
    }
}

Collection::new([‘foo’, ‘bar’])->forEach(function($index, $item) {
    //…
})->list();
Exemple de la nouvelle syntaxe

Si, à l’heure actuelle, ce code ne fonctionne pas car les mots « foreach », « list » et « new » sont protégés, dès PHP 7.0, ce code devient valide, PHP prenant en compte le contexte. La seule exception est le mot « class » qui reste interdit dans l’ensemble des contextes.

Abstract Syntax Tree / Uniform Variable Syntax

Jusqu’à la version 5, l’opcode est généré en une passe unique : le parser analyse le code et génère l’opcode qui en résulte. Au-delà du fait que cette approche est peu standard, ce processus en une phase rend difficile ou impossible l’implémentation de certaines syntaxes et nuit à la maintenance et l’évolutivité de l’ensemble.

Une RFC votée à l’unanimité change la donne en implémentant un processus en 3 phases :

  • Génération d’un flux de tokens
  • Génération d’un AST (Abstract Syntax Tree) à partir de ce flux de tokens
  • Génération de l’opcode à partir de la lecture de l’AST

L’AST ou arbre syntaxique abstrait est un arbre dont les nœuds internes représentent les opérateurs et dont les feuilles représentent les variables manipulées. Il permet ainsi de découpler le travail d’analyse du code et de compilation.

Il a également été décidé que l’arbre produit serait exposé via une extension et deviendrait ainsi « hookable ». Cela pourra permettre par exemple de voir apparaître des extensions proposant de nouvelles syntaxes.

Bien que complètement transparente pour le développeur final, cette évolution pourrait avoir des impacts très positifs sur l’évolution du langage, tant en terme de nouvelles fonctionnalités qu’en terme de performances.

La deuxième évolution interne est la remise à plat de la manière dont les expressions sont évaluées via l’implémentation d’une « Uniform Variable Syntax » (syntaxe de variable uniforme). Cette nouveauté cherche à résoudre certaines incohérences dans l’interprétation actuelle des expressions par le moteur PHP.

Si cette évolution a toutes les chances d’être transparente pour vous, elle peut toutefois vous réserver quelques surprises si vous utilisez ce type de syntaxe :

$foo->$bar['baz']()

qui signifiait :

$foo->{$bar['baz']}()

et qui signifiera désormais :

{$foo->$bar}['baz']()

Sauf indication contraire, la lecture se fait désormais toujours de gauche à droite.
Il reste toutefois très facile de contourner le problème en ajoutant des accolades sur la partie devant être interprétée prioritairement.

Et après ?

Voici donc les principales nouveautés que la sortie de PHP 7 à la rentrée prochaine nous réserve.
Cette nouvelle version montre que le langage continue de s’améliorer et de se réinventer en permanence tant en performance qu’en structure tout en continuant à répondre aux besoins des développeurs et en n’abandonnant pas ce qui fait de lui un des langages les plus utilisés : sa simplicité.

La migration des applications PHP5 vers PHP7 ne devrait pas poser beaucoup de problèmes, une attention toute particulière ayant été apportée à la rétrocompatibilité.
Rasmus Lerdorf a résumé le problème de la migration comme ceci : « Si votre code a plus de 12 ans il plantera avec PHP 7. S’il fonctionne sous PHP 5, cela devrait bien se passer »

Il est d’ailleurs déjà possible de tester cette version pour les plus pressés d’entre vous.
A l’heure de la publication de cet article, la RC5 a été publiée et une nouvelle RC est prévue tous les 15 jours jusqu’au 14 Novembre. N’hésitez pas à remonter les éventuels bugs rencontrés sur vos projets suite à la migration, cela garantira une version 7.0 des plus stables !

Ezpublish script en ligne de commande

0

On ne le soupçonne pas forcément, mais eZpublish dispose d’outils forts utiles pour développer des scripts PHP en ligne de commande. Ces outils permettent de « charger un environnement », de passer  facilement des paramètres au code et de communiquer avec la console de ligne de commande.

Ces outils sont principalement eZScript et eZCLI.  eZScript s’occupera principalement du contexte des scripts et eZCLI s’occupera de l’affichage des informations vers la console. Cependant si le script n’est lancé que via l’utilisation d’un cron, il faudra privilégier eZLOG qui sera plus approprié qu’ eZCLI.

 

Pourquoi eZ Publish en ligne de Commande ?

L’intérêt d’utiliser eZ Publish via une ligne de commande réside principalement dans « le travail de masse »,  comme par exemple l’import et l’export de données, l’envoi massif de mail, une mise à jour de données ou tout autre travail nécessitant un temps d’exécution assez long. Dans une configuration basique lorsque PHP est lancé en « CLI »(via une ligne de commande) il s’exécute jusqu’à la fin du script sans contrainte de temps, ce qui n’est pas le cas lorsqu’il s’exécute en HTTP (via un navigateur web).  L’autre avantage est qu’en mode « CLI » on ne générera pas de HTML, donc toutes les « ressources » servent principalement au traitement du script,  permettant ainsi un gain en terme de performance.

 

Notre exercice :

Pour mettre en pratique nous allons créer un script pour régénérer en masse les mots de passe des utilisateurs du site. Le script devra prendre en compte plusieurs paramètres :

  • Le type de contenu de l’utilisateur
  • Le mode de régénération du mot de passe (Le même pour tout le monde ou différent pour tous)
  • Le script récapitulera les paramètres et affichera le nombre d’utilisateurs qui vont être modifiés
  • Le script attendra la validation de l’utilisateur (entrée clavier) pour lancer la procédure.
  • Le script enverra un mail à l’utilisateur pour lui donner le nouveau mot de passe.

Ce script peut s’avérer utile si une faille de sécurité est survenue et que par mesure de précaution on désire réinitialiser les mots de passes des utilisateurs. Il peut aussi s’avérer utile lorsque l’on récupère les données d’une base pour la connecter à son environnement de développement et disposer de compte utilisateur dont on connaît les mots de passe.

 

Conventions :

Par convention on place ces scripts dans le répertoire d’une de ses extensions eZ Publish( /bin/php/ étant préconisé). Pour l’exercice nous appellerons le script resetpassword.php.

 

Création du script :

En premier lieu on charge l’autoload ezpublish, cela nous permettra de disposer de toutes les classes déclarées dans ezpublish.

require 'autoload.php';

 

On initialise par la suite notre objet eZCLI, sans oublier de préciser que l’on utilisera les styles de couleur.

//Instanciate the cli object
$cli = eZCLI::instance();
//To have a colorized output
$cli->setUseStyles(true);

 

Maintenant que nous pouvons afficher efficacement des informations, nous devons instancier l’environnement de script pour bénéficier correctement des fonctionnalités d’eZ Publish et disposer  d’un outil de script pratique. Dans les fonctions intéressantes il y a l’arrêt propre du script et la possibilité de passer facilement des options.

$script = eZScript::instance( array( 'description' => ( "WEBNET reset password script\n"),
                                     'use-session' => false,
                                     'use-modules' => true,
                                     'use-extensions' => true ) );

 

Comme on peut le voir c’est le tableau d’arguments qui permet de spécifier son environnement, ici on utilise les modules et les extensions mais pas les sessions. On pourrait aussi spécifier un siteacces ou bien même un user.

Pour notre script nous aurons besoin de passer des paramètres (mot de passe générique, classe du user etc.), rien de plus simple, il suffit de les déclarer et même petit bonus, on peut ajouter un texte d’aide.

// Options processing
$options = $script->getOptions(
    '[password:][classes_identifier:][same_password:]',
    '',
    array(
        'password'                      => 'New password to set to user, default is webnet',
        'classes_identifier'            => 'User classes to reset, default is licencie',
        'same_password'                 => 'To create a new password for account(value != 1) or to give the same password for all account(value = 1). By default value is 1',
    )
);

 

Dans notre cas nous déclarons dans les arguments de la méthode :

  • Premier argument : la liste des options (password, class_identifier, same_password)
  • Deuxième argument : Inutile dans notre cas
  • Troisième argument : La liste des textes pour l’aide (lorsque l’on lance le script avec l’option -h).

Une fois cette partie réalisée il faut initialiser le script puis écrire notre code (pour notre part nous préconisons l’utilisation d’un  try catch). L’exercice étant axé sur l’utilisation de l’objet eZCLI et de l’objet  eZscript nous ne détaillerons que le code qui leur est « liés ».

Par sécurité nous allons demander à l’utilisateur de vérifier ses options et de confirmer le lancement du code. C’est là qu’intervient l’objet « CLI ». Il va nous permettre d’afficher efficacement et en couleur des informations à l’écran.

L’objet « CLI » dispose de quatre méthodes d’affichage d’informations, trois sont des affichages préformatés (notice, warning, error), le quatrième (output), quant à lui, permet l’affichage personnalisé.

  • Notice affiche le texte en blanc
  • Warning affiche le texte en jaune
  • Error affiche le texte en rouge
  • Output vous permet de mettre votre propre formatage.

Comme tout le monde ne connaît pas de tête le formatage « ligne de commande », on peut utiliser  les 51 styles préconçus de l’objet eZCLI grâce à la méthode stylize (à laquelle on passe le texte et le style désiré). On peut ainsi afficher facilement des textes en rouge, vert, bleu, jaune.
Il faut aussi savoir que les méthodes notice, warning, error, et output « effectue » par défaut un retour à la ligne  (mais ce comportement peut être modifié).

    //Mon texte avec retour à la ligne
    $cli->output($cli->stylize('green', 'mon texte avec retour à la ligne' ));
    //Mon texte sans retour à la ligne (false en deuxième argument)
    $cli->output($cli->stylize('green', 'Mon texte sans retour à la ligne' ), false);

 

On peut dans tous les cas utiliser des tabulations(\t) et ajouter des retours à la lignes (ex :\r\n mais il faire attention au système d’exploitation).

Dans notre exemple nous utiliserons un texte blanc sur fond vert, un texte vert et un texte bleu

    $cli->output($cli->stylize('green-bg', '=================================' ));
    $cli->output($cli->stylize('green-bg', '   Parameters and configuration   ' ));
    $cli->output($cli->stylize('green-bg', '=================================' ));
    $cli->output($cli->stylize('green',"New password\t : " . $password));
    $cli->output($cli->stylize('green',"Same password\t : " . $same_password));
    $cli->output($cli->stylize('green',"User classes\t : " . $identifier));
    $cli->output($cli->stylize('green',"user count\t : " . $count));
    $cli->output($cli->stylize('green-bg', '================================='));
    $cli->output($cli->stylize('cyan', "\n" . 'Check parameters before launch the script.' ));
    $cli->output($cli->stylize('green', "\n" . 'Continue [Y/n] ?'));
    $choix = trim(fgets(STDIN));
    if($choix != "Y") {
        $cli->error('Script abort.');
    }

ce qui donnera :

Ezpublish Capture 1

 

Pour la confirmation nous utiliserons la fonction fgets qui « attendra/écoutera » la saisie de l’utilisateur. Ici seule la saisie de Y majuscule permet le lancement du script. Le reste du code est du PHP des plus simples.

Pour rendre le script et son affichage plus vivant, nous afficherons son évolution en précisant quel est l’utilisateur en cours de modification ainsi que le nombre d’utilisateurs déjà modifiés.

Sans instruction spécifique, les informations s’afficheront les unes derrières les autres, faisant ainsi défiler l’écran de la console et rendant la lecture difficile. Pour pallier à cela il nous faut utiliser les fonctions storePosition et restorePosition de l’objet eZCLI qui vont nous permettre d’afficher les informations sur une même ligne.

$pos_store =  $cli->storePosition();
$pos_restore =  $cli->restorePosition();
…
...
$cli->output( $pos_store . $cli->stylize('green', 'New password for ' . $user->Login . " \t" . $i . "/" . $count) . $pos_restore, false);

La dernière touche reste l’arrêt du script lorsque le traitement est fini.

$script->shutdown();

 

Voilà vous avez désormais les bases pour utiliser les objets eZCLI et eZScript et lancer des scripts en ligne de commande dans eZpublish. A savoir qu’il existe aussi un objet  ezcConsoleProgressbar qui permet l’affichage comme son nom l’indique d’une barre de progression dans la console de commande.

 

Lancer le script :

Maintenant que nous avons le code voyons comment l’exécuter. Il suffit tout simplement de se mettre à la racine du site et de lancer le script : php extension/[mon_module]/bin/php/monscript.php

 

Pour notre exercice

Même mot de passe :

php extension/[mon_module]/bin/php/resetpassword.php --password=nouveaumotdepass --classes_identifier=my_user_class

 

Mot de passe différent :

php extension/[mon_module]/bin/php/resetpassword.php --same_password=0 --classes_identifier=my_user_class

 

Le script ayant des valeurs par défaut les options ne sont pas obligatoires. On peut imaginer aisément une variante du script utilisant un fichier de configuration pour renseigner les « options ».

Si on désire l’aide :

php extension/mon_module/bin/php/monscript.php -help

 

Ezpublish - ecran 2

 

Attention :  si vous lancez le script en étant root il faut ajouter –allow-root-user

Si vous avez lancé l’aide, vous verrez que l’on peut passer diverses options, notamment le siteacces qui permet de ne charger que la configuration d’un site.  Les autres options restant très contextuelles vous n’aurez à les utiliser qu’en de rares occasions, méfiez-vous de l’option –quiet qui bloque les affichages de eZCLI et –no-colors peut être pratique dans un environnement Windows qui n’interprète pas les couleurs dans la console de ligne de commande.

Il ne vous reste plus qu’à faire votre propre expérience.

Sécurité :

Pour éviter tout lancement intempestif de ce script, faites en sorte qu’il ne puisse être lancé que par un utilisateur « spécifique » ou rajouter la saisie obligatoire du login et mot de passe d’un utilisateur ezpublish pour lancer le script.

Exemple :

$cli->notice('Saisir l\'identifiant et le mot de passe d\'un utilisateur ');
$cli->notice('login');
$login = trim(fgets(STDIN));
$cli->notice('password');
$password = trim(fgets(STDIN));

$script = eZScript::instance( array( 'description' => ( "WEBNET reset password script\n"),
                                     'use-session' => false,
                                     'use-modules' => true,
                                     'user' => array('login'=> $login, 'password'=> $password),
                                     'use-extensions' => true ) );

.. Puis vérifier que le log est ok

Si vous avez beaucoup d’utilisateurs, faites attention à ne pas saturer votre serveur mail.

 

Script :

<?php
/**
 * @file
 * @author EB webnet
 * script to reset password of users
 */

require 'autoload.php';
//Instanciate the cli object
$cli = eZCLI::instance();
//To have a colorized output
$cli->setUseStyles(true);
//Instanciate the script objet with define environnement
$script = eZScript::instance(array('description' => ( "WEBNET reset password script\n"),
            'use-session' => false,
            'use-modules' => true,
            'use-extensions' => true));
//Start the script
$script->startup();

// Options processing
$options = $script->getOptions(
        '[password:][classes_identifier:][same_password:]', '', array(
    'password' => 'New password to set to user, default is webnet',
    'classes_identifier' => 'User classes to reset, default is licencie',
    'same_password' => 'To create a new password for account(value != 1) or to give the same password for all account(value = 1). By default value is 1',
        )
);
$script->initialize();
$script->setUseDebugAccumulators(true);
try {
    $password = 'UnM0tD1ff1c1le';
    $identifier = 'user';
    $same_password = true;
    if (isset($options['same_password']) && $options['same_password'] != 1) {
        $same_password = false;
    }
    if (!empty($options['password'])) {
        $password = $options['password'];
    }
    if (!empty($options['classes_identifier'])) {
        $identifier = $options['classes_identifier'];
    }
    $db = eZDB::instance();
    $value = $db->escapeString($identifier);
    /**
     * request to retrieve id.
     * Note we exclude anymous
     */
    $sql = "SELECT  DISTINCT  ezo.id
    FROM ezcontentobject AS ezo
    INNER JOIN ezcontentclass AS ezc ON ezc.id = ezo.contentclass_id
    INNER JOIN ezcontentobject_tree AS ezt ON ezt.contentobject_id = ezo.id AND ezt.contentobject_version = ezo.current_version
    WHERE ezc.identifier = '$value' AND ezo.id != 10;";
    $rows = $db->arrayQuery($sql);
    $count = count($rows);

    if($same_password){
        $same_password_text ='Yes';
        $password_add ='';
    }else{
        $same_password_text = 'No';
        $password_add =' (unused for this instance)';
    }
    /**
     * Display parameters
     */
    $cli->output($cli->stylize('green-bg', '================================='));
    $cli->output($cli->stylize('green-bg', '   Parameters and configuration   '));
    $cli->output($cli->stylize('green-bg', '================================='));
    $cli->output($cli->stylize('green', "New password\t : " . $password . $password_add));
    $cli->output($cli->stylize('green', "Same password\t : " . $same_password_text));
    $cli->output($cli->stylize('green', "User classes\t : " . $identifier));
    $cli->output($cli->stylize('green', "user count\t : " . $count));
    $cli->output($cli->stylize('green-bg', '================================='));
    $cli->output($cli->stylize('cyan', "\n" . 'Check parameters before launch the script.'));
    $cli->output($cli->stylize('green', "\n" . 'Continue [Y/n] ?'));
    /**
     * Wait for confirmation
     */
    $choix = trim(fgets(STDIN));
    /**
     * Only Y could launch script
     */
    if ($choix != "Y") {
        $cli->error('Script abort.');
    } else {
        if ($count > 0) {
            /**
             * To display information in the same line
             */
            $pos_store = $cli->storePosition();
            $pos_restore = $cli->restorePosition();
            for ($i = 0; $i < $count; $i++) {
                /**
                 * Load User
                 */
                $user = eZUser::fetch($rows[$i]['id']);
                if (is_object($user)) {
                    /**
                     * Check if we need to generate a password
                     */
                    if (!$same_password) {
                        $password = eZUser::createPassword(6);
                    }
                    /**
                     * update user
                     */
                    $user->setAttribute( "password_hash", eZUser::createHash( $user->Login, $password, eZUser::site(),
                                                                          eZUser::hashType() ) );
                    $user->setAttribute( "password_hash_type", eZUser::hashType() );
                    /**
                     * save user
                     */
                    $user->store();
                    /**
                     * Send a mail to user
                     * it's just a example, make your own mail
                     */
                    $mail = new eZMail();
                    $mail->setSender( 'no-reply@mondomaine.com' );
                    //$mail->setReceiver( $user->Email );
                    $mail->setReceiver( 'ebrowet@webnet.fr' );
                    $mail->setSubject( 'Your password have been regenerated' );
                    $body = "Hello,\n\n";
                    $body .= "Your password have been regenerated.\n\n";
                    $body .= "login : " . $user->Login . "\n";
                    $body .= "password : " . $password . "\n\n";
                    $body .= 'www.mysite.com';
                    $mail->setBody($body);
                    eZMailTransport::send( $mail );
                    /**
                     * Display informations
                     */
                    $cli->output($pos_store . $cli->stylize('green', 'New password for ' . $user->Login . " \t" . $i . "/" . $count) . $pos_restore, false);
                }
            }
        } else {
            /**
             * this exeption launch the shutdown of the script
             */
            throw new Exception('No user found...');
        }
        $cli->notice("\n" . 'Reset password is finish.');
    }
    /**
     * A clean script stop
     */
    $script->shutdown();
} catch (Exception $e) {
    $errCode = $e->getCode();
    $errCode = $errCode != 0 ? $errCode : 1; // If an error has occured, script must terminate with a status other than 0
    $script->shutdown($errCode, $e->getMessage() . "\n" . 'Shutdown sequence initiated..');
}

 

Références :

http://pubsvn.ez.no/doxygen/4.7/html/classeZCLI.html
http://pubsvn.ez.no/doxygen/4.7/html/classeZScript.html

http://www.ezpedia.org/ez/command_line_scripts