I. Prérequis▲
La version du Zend Framework 2 utilisée pour cet article est la 2.0.0RC02. L'utilisation du Zend Framework 2 nécessite au minimum la version 5.3.3 de PHP.
Zend Framework 2.0 RequirementsZend Framework 2.0 Requirements
II. Composant et classes standards▲
Le composant Zend\Stdlib est une bibliothèque de classes permettant de résoudre les problèmes récurrents que l'on rencontre sur les projets web. Ce composant se veut être complémentaire à la SPL (bibliothèque standard de PHP) que l'on connaît. Les classes de ce composant s'appuient aussi souvent sur ce standard afin d'enrichir un concept ou objet existant. Le framework s'appuie beaucoup sur ce composant qui permet d'introduire une certaine homogénéisation dans le framework. Nous allons présenter dans cet article quelques classes que l'on retrouvera beaucoup au sein du framework et que vous pourrez utiliser au sein de vos projets.
III. La classe AbstractOptions▲
La classe abstraite AbstractOptions fournit des méthodes de base aux classes permettant de gérer la configuration d'un composant. Cette classe offre un mécanisme d'initialisation qui, à partir d'un tableau d'options, permet de distribuer aux méthodes d'altérations, les données qu'elles doivent recevoir. Afin de pouvoir automatiser cette tâche, la classe formate les noms de clés du tableau de paramètres en nom de méthode correspondant à cette clé en la préfixant du mot « set » :
public function __construct($options
=
null)
{
if (null !==
$options
) {
$this
->
setFromArray($options
);
}
}
public function setFromArray($options
)
{
if (!
is_array($options
) &&
!
$options
instanceof Traversable) {
[&
#8230;]
}
foreach ($options
as $key
=>
$value
) {
$this
->
__set($key
,
$value
);
}
}
protected function assembleSetterNameFromKey($key
)
{
$parts
=
explode('
_
'
,
$key
);
$parts
=
array_map('
ucfirst
'
,
$parts
);
$setter
=
'
set
'
.
implode(''
,
$parts
);
if (!
method_exists($this
,
$setter
)) {
[&
#8230;]
}
return $setter
;
}
Le constructeur fait appel à la méthode « setFromArray() » qui s'occupe d'injecter les options depuis un tableau afin que chaque valeur de la configuration soit traitée par la méthode magique « __set() » .Cette méthode en recherche une autre du nom de « setMaVariable() » où « ma_variable » est une clé du tableau de configuration. Par exemple, la clé « config_cache_key » est injectée depuis la méthode « setConfigCacheKey() » dont le nom est formaté en « CamelCase » tout en supprimant les underscores du nom de la variable. Nous comprenons donc que le fait de ne pas respecter cette règle, en nommant ou en insérant une clé qui ne correspond à aucune méthode interne, lèvera une exception :
protected function assembleSetterNameFromKey($key
)
{
[&
#8230;]
if (!
method_exists($this
,
$setter
)) {
throw new Exception\BadMethodCallException(
'
The option "
'
.
$key
.
'
" does not
'
.
'
have a matching
'
.
$getter
.
'
getter method
'
.
'
which must be defined
'
);
}
return $setter
;
}
Fatal error
:
Uncaught exception '
Zend\Stdlib\Exception\BadMethodCallException
'
with message '
The option "ma_cle_inconnu" does not have a matching setMaCleInconnu
Il devient alors très simple de composer une classe responsable de la gestion d'options d'un composant. Il suffit d'écrire les « setters » et « getters », méthodes de récupération et de modification, correspondant aux noms des attributs et la classe devient fonctionnelle. Cette classe est utilisée à de nombreuses reprises dans le framework : Zend\Cache, Zend\Mail, Zend\Serializer, etc.
IV. La classe ArrayUtils▲
La classe ArrayUtils offre de nombreuses fonctions afin de manipuler au mieux les tableaux. La classe offre des méthodes afin de connaître le type des clés d'un tableau, comme par exemple en testant s'il existe des clés du type de la chaîne de caractères :
public static function hasStringKeys($value
,
$allowEmpty
=
false)
{
if (!
is_array($value
)) {
return false;
}
if (!
$value
) {
return $allowEmpty
;
}
return count(array_filter(array_keys($value
),
'
is_string
'
)) >
0
;
}
La méthode « hasStringKeys() » applique un filtre sur la liste des clés du tableau afin de récupérer les clés de type chaîne de caractères. Deux autres méthodes similaires existent afin de tester s'il existe des clés de type entier ou numérique.
La méthode statique « iteratorToArray() » permet de compléter la fonction native de PHP « iterator_to_array() » en permettant de parcourir récursivement le tableau ou l'objet implémentant l'interface Traversable passé en paramètre. En effet, la méthode laisse la possibilité de passer un tableau en paramètre, auquel cas il retournera simplement le tableau si l'on n'a pas demandé de le parcourir récursivement :
public static function iteratorToArray($iterator
,
$recursive
=
true)
{
if (!
is_array($iterator
) &&
!
$iterator
instanceof Traversable) {
throw new Exception\InvalidArgumentException(__METHOD__ .
'
expects an array or Traversable object
'
);
}
if (!
$recursive
) {
if (is_array($iterator
)) {
return $iterator
;
}
return iterator_to_array($iterator
);
}
[&
#8230;]
}
La méthode vérifie ensuite si l'objet ne comporte pas une méthode « toArray() », auquel cas elle est utilisée avant de parcourir le tableau récursivement :
public static function iteratorToArray($iterator
,
$recursive
=
true)
{
[&
#8230;]
if (method_exists($iterator
,
'
toArray
'
)) {
return $iterator
->
toArray();
}
$array
=
array();
foreach ($iterator
as $key
=>
$value
) {
if (is_scalar($value
)) {
$array
[
$key
]
=
$value
;
continue;
}
if ($value
instanceof Traversable) {
$array
[
$key
]
=
static::
iteratorToArray($value
,
$recursive
);
continue;
}
if (is_array($value
)) {
$array
[
$key
]
=
static::
iteratorToArray($value
,
$recursive
) ;
continue;
}
$array
[
$key
]
=
$value
;
}
return $array
;
}
À chaque fois que la boucle rencontre un tableau ou un objet de type Traversable, la méthode est appelée récursivement. Nous comprenons l'intérêt de la souplesse de la méthode sur le type en premier paramètre. Cette méthode permet de gérer facilement les compositions de variables entre tableaux, objet de type Traversable et objet implémentant la méthode « toArray() ».
V. La classe CallbackHandler▲
La classe CallbackHandler permet de surcharger le comportement des fonctions natives de PHP « call_user_func() » ou encore « call_ user_ func_ array() » qui permettent d'appeler des fonctions de rappels. La classe offre la possibilité de prendre en paramètre tout ce qui peut être appelé en tant que fonction :
public function __construct($callback
,
array $metadata
=
array())
{
$this
->
metadata =
$metadata
;
$this
->
registerCallback($callback
);
}
protected function registerCallback($callback
)
{
if (!
is_callable($callback
)) {
throw new Exception\InvalidCallbackException('
Invalid callback provided; not callable
'
);
}
[&
#8230;]
}
Il est donc possible de passer de nombreux paramètres au constructeur de la classe : tableau, closure, chaîne de caractères, etc. Voici quelques exemples d'utilisation :
class Callback
{
public
function
doSomething()
{
echo "ok
\n
"
;
}
public
function
doSomethingWithArg($arg
)
{
echo $arg
.
"
\n
"
;
}
}
function
doSomething()
{
echo "ok
\n
"
;
}
$callbackObject
=
new
Callback();
$callback
=
new
Stdlib\CallbackHandler(array
('Callback'
,
'doSomething'
));
$callback
->
call();
$callback
=
new
Stdlib\CallbackHandler(array
($callbackObject
,
'doSomething'
));
$callback
->
call();
$callback
=
new
Stdlib\CallbackHandler(function
() {
echo "ok
\n
"
;
}
);
$callback
->
call();
$callback
=
new
Stdlib\CallbackHandler(array
($callbackObject
,
'doSomethingWithArg'
));
$callback
->
call(array
('ok'
));
$callback
=
new
Stdlib\CallbackHandler('doSomething'
);
$callback
->
call();
$callback
=
new
Stdlib\CallbackHandler('\Callback::doSomething'
);
$callback
->
call();
Comme on le voit, la méthode « call() » permet de fournir des arguments à la méthode de callback. Évidemment, cette méthode s'appuie sur les fonctions « call_user_func() » et « call_ user_ func_ array() » de PHP que l'on connaît :
public function call(array $args
=
array())
{
[&
#8230;]
$argCount
=
count($args
);
[&
#8230;]
switch ($argCount
) {
case 0
:
if (self::
$isPhp54
) {
return $callback
();
}
return call_user_func($callback
);
case 1
:
if (self::
$isPhp54
) {
return $callback
(array_shift($args
));
}
return call_user_func($callback
,
array_shift($args
)) ;
case 2
:
$arg1
=
array_shift($args
);
$arg2
=
array_shift($args
);
if (self::
$isPhp54
) {
return $callback
($arg1
,
$arg2
);
}
return call_user_func($callback
,
$arg1
,
$arg2
);
case 3
:
$arg1
=
array_shift($args
);
$arg2
=
array_shift($args
);
$arg3
=
array_shift($args
);
if (self::
$isPhp54
) {
return $callback
($arg1
,
$arg2
,
$arg3
);
}
return call_user_func($callback
,
$arg1
,
$arg2
,
$arg3
) ;
default:
return call_user_func_array($callback
,
$args
);
}
}
La classe offre aussi la possibilité d'utiliser l'instance de l'objet CallbackHandler directement comme une fonction :
public function __invoke()
{
return $this
->
call(func_get_args()) ;
}
Nous pouvons donc écrire ces instructions :
$callback
=
new Stdlib\CallbackHandler(array($callbackObject
,
'
doSomething
'
));
$callback
();
Ce composant peut s'avérer particulièrement utile pour vos gestions de fonctions de rappel afin de fournir un peu plus de flexibilité dans vos applications.
VI. La classe ErrorHandler▲
La classe ErrorHandler fournit des méthodes de gestion pour capturer les erreurs de l'application. Voici un exemple de capture de notice :
class ErrorHandlerTest
{
public
function
doSomething()
{
trigger_error(&
#8216;lancement de notice’, \E_USER_NOTICE);
}
}
Stdlib\ErrorHandler::
start(\E_ALL);
$errHandler
=
new
ErrorHandlerTest();
$errHandler
->
doSomething();
$err
=
Stdlib\ErrorHandler::
stop();
echo $err
->
getMessage();
La capture d'erreur est démarrée par la méthode « start() » qui fait appel à la méthode « set_error_handler() » de PHP afin de modifier la fonction qui gère les erreurs :
public static function start($errorLevel
=
\E_WARNING)
{
if (static::
started() ===
true) {
throw new Exception\LogicException('
ErrorHandler already started
'
) ;
}
static::
$started
=
true;
static::
$errorException
=
null ;
set_error_handler(array(get_called_class(),
'
addError
'
),
$errorLevel
);
}
La méthode prend le type d'erreur à gérer en paramètre et enregistre sa méthode « addError() » comme méthode de gestion d'erreurs. Cette méthode se contente d'enregistrer l'erreur dans l'attribut « $errorException » que l'on peut récupérer automatiquement sous forme d'exception depuis la méthode statique « stop() » :
public static function stop($throw
=
false)
{
if (static::
started() ===
false) {
throw new Exception\LogicException('
ErrorHandler not started
'
);
}
$errorException
=
static::
$errorException
;
static::
$started
=
false;
static::
$errorException
=
null;
restore_error_handler();
if ($errorException
&&
$throw
) {
throw $errorException
;
}
return $errorException
;
}
La classe ErrorHandler possède une API relativement facile et agréable à prendre en main qui permet de se détacher de la gestion des erreurs que l'on implémente dans chaque projet.
VII. La classe Glob▲
La classe Glob permet d'utiliser la fonction native « glob() » avec les systèmes qui ne gèrent pas la méthode « glob() » avec l'utilisation du flag « GLOB_BRACE ». Une fonction a été spécialement portée de la bibliothèque GLIBC afin de rendre l'utilisation de « GLOB_BRACE » possible :
public static function glob($pattern
,
$flags
,
$forceFallback
=
false)
{
if (!
defined('
GLOB_BRACE
'
) ||
$forceFallback
) {
return self::
fallbackGlob($pattern
,
$flags
);
}
else {
return self::
systemGlob($pattern
,
$flags
);
}
}
Nous remarquons que cette fonction « fallbackGlob() » est utilisée si le système ne définit pas la constante « GLOB_BRACE ». Cette classe permet donc d'utiliser cette fonctionnalité sans bloquer les plates-formes qui ne sont pas compatibles. Il est évidemment conseillé d'utiliser ce composant lorsque vous souhaitez utiliser la fonction « glob() » avec le flag « GLOB_BRACE ».
VIII. Les hydrateurs▲
Les hydrateurs permettent d'extraire les données et de peupler les attributs d'un objet suivant une stratégie définie. Les hydrateurs de la Stdlib se trouvent dans le namespace Zend\Stdlib\Hydrator et offrent plusieurs stratégies :
• extraction et modification depuis les méthodes « getXXX() » et « setXXX() » de l'objet ;
• extraction et modification depuis les propriétés publiques de l'objet ;
• extraction et modification depuis une analyse, à l'aide de la classe reflectionClass, de l'objet ;
• extraction et modification depuis les méthodes « getArrayCopy() » et « exchangeArray() » propres aux objets travaillant avec les tableaux.
Voici un exemple d'utilisation de l'hydrateur basé sur les « getters » et « setters » de l'objet :
class Exemple
{
protected
$ma_propriete
=
'ma valeur'
;
public
function
getMaPropriete()
{
return
$this
->
ma_propriete;
}
public
function
setMaPropriete($ma_propriete
) {
$this
->
ma_propriete =
$ma_propriete
;
return
$this
;
}
}
$exemple
=
new
Exemple();
$hydrateur
=
new
Stdlib\Hydrator\ClassMethods();
$datas
=
$hydrateur
->
extract($exemple
);
L'hydrateur implémente l'interface Zend\Stdlib\Hydrator\HydratorInterface qui définit le contrat de base que doit respecter les hydrateurs :
interface HydratorInterface
{
public
function
extract($object
);
public
function
hydrate(array
$data
,
$object
);
}
La méthode « extract() » permet d'extraire les données de l'objet suivant la stratégie de l'hydrateur et la méthode « hydrate() » permet de peupler l'objet depuis les données fournies en paramètre.
Dans notre exemple, nous utilisons l'hydrateur basé sur les méthodes de l'objet. Les méthodes « getMaPropriete() » et « setMaPropriete() » vont lui permettre d'extraire de l'objet, un tableau avec comme entrée une clé « ma_propriete » avec la valeur « ma valeur ».
Examinons le fonctionnement de l'hydrateur Zend\Stdlib\Hydrator\ClassMethods :
public function extract($object
)
{
if (!
is_object($object
)) {
throw new Exception\BadMethodCallException(sprintf(
'
%s expects the provided $object to be a PHP object)
'
,
__METHOD__ ));
}
$transform
=
function($letters
) {
$letter
=
array_shift($letters
);
return '
_
'
.
strtolower($letter
);
};
$attributes
=
array();
$methods
=
get_class_methods($object
);
foreach ($methods
as $method
) {
if (preg_match('
/^get[A-Z]\w*/
'
,
$method
)) {
$setter
=
preg_replace('
/^get/
'
,
'
set
'
,
$method
);
if (!
in_array($setter
,
$methods
)) {
continue;
}
$attribute
=
substr($method
,
3
);
$attribute
=
lcfirst($attribute
);
if ($this
->
underscoreSeparatedKeys) {
$attribute
=
preg_replace_callback('
/([A-Z])/
'
,
$transform
,
$attribute
);
}
$attributes
[
$attribute
]
=
$this
->
extractValue($attribute
,
$object
->
$method
());
}
}
return $attributes
;
}
L'hydrateur récupère tout d'abord la liste des méthodes de l'objet avant de les parcourir :
public function extract($object
)
{
[&
#8230;]
$methods
=
get_class_methods($object
);
foreach ($methods
as $method
) {
[&
#8230;]
}
}
Comme nous souhaitons extraire des données, l'hydrateur se base alors sur le « getter » de l'objet et donc ses méthodes commençant par « get », tout en vérifiant que le « setter » correspondant existe bel et bien :
public function extract($object
)
{
foreach ($methods
as $method
) {
if (preg_match('
/^get[A-Z]\w*/
'
,
$method
)) {
$setter
=
preg_replace('
/^get/
'
,
'
set
'
,
$method
);
if (!
in_array($setter
,
$methods
)) {
continue;
}
[&
#8230;]
}
}
}
Une fois les vérifications faites, le nom de l'attribut est formaté afin d'être enregistré dans le tableau de retour :
public function extract($object
)
{
[&
#8230;]
foreach ($methods
as $method
) {
[&
#8230;]
$attribute
=
substr($method
,
3
);
$attribute
=
lcfirst($attribute
);
if ($this
->
underscoreSeparatedKeys) {
$attribute
=
preg_replace_callback('
/([A-Z])/
'
,
$transform
,
$attribute
);
}
$attributes
[
$attribute
]
=
$this
->
extractValue($attribute
,
$object
->
$method
());
}
[&
#8230;]
}
Nous remarquons que nous avons la possibilité de transformer les noms de clés au format « CamelCase » ou un format avec underscore grâce à l'attribut « $underscoreSeparatedKeys » que nous pouvons définir depuis le constructeur :
public function __construct($underscoreSeparatedKeys
=
true)
{
parent::
__construct();
$this
->
underscoreSeparatedKeys =
$underscoreSeparatedKeys
;
}
Par défaut, la classe utilise les underscores. Nous aurons donc la clé « ma_propriete » pour la méthode « getMaPropriete() ». Si nous passons le paramètre à « false », nous obtiendrons la clé « maPropriete ».
Lors de l'hydratation, ou peuplement de l'objet, la méthode « extract() » utilisera les méthodes commençant par « set » de l'objet si celles-ci existent :
$hydrateur
=
new Stdlib\Hydrator\ClassMethods();
$datas
=
array('
ma_propriete
'
=>
'
nouvelle valeur
'
);
$hydrateur
->
hydrate($datas
,
$exemple
);
Revenons à l'extraction et remarquons que l'hydrateur utilise la méthode « extractValue() » pour enregistrer la valeur plutôt qu'une affectation classique. Ceci permet d'ajouter une surcouche lors de l'extraction ou de l'hydratation. Voici comment se comporte la méthode « extractValue() » :
public function extractValue($name
,
$value
)
{
if ($this
->
hasStrategy($name
)) {
$strategy
=
$this
->
getStrategy($name
);
$value
=
$strategy
->
extract($value
);
}
return $value
;
}
L'AbstractHydrator dont héritent les hydrateurs vérifie si une stratégie particulière existe sur la propriété à hydrater afin de l'utiliser. Par défaut, la valeur est simplement retournée. Voici un exemple de stratégie ajoutée sur l'hydrateur :
class FilterStrategy implements
StrategyInterface
{
public
function
extract($value
) {
return
strip_tags($value
);
}
public
function
hydrate($value
)
{
return
"<p>"
.
$value
.
"</p>"
;
}
}
$exemple
->
setMaPropriete('<p>ma valeur</p>'
);
$hydrateur
=
new
Stdlib\Hydrator\ClassMethods();
$hydrateur
->
addStrategy('ma_propriete'
,
new
FilterStrategy);
$datas
=
$hydrateur
->
extract($exemple
);
La stratégie, permet ici de supprimer les tags de la clé correspondant à « ma_propriete » lors de l'extraction et de les ajouter lors de l'hydratation.
Attention, nous utilisons les underscores pour les clés. Si nous passons au format CamelCase, cela ne fonctionnera plus, sauf si nous modifions la clé pour la stratégie :
$exemple
->
setMaPropriete('
<p>ma valeur</p>
'
);
$hydrateur
=
new Stdlib\Hydrator\ClassMethods(false);
$hydrateur
->
addStrategy('
maPropriete
'
,
new FilterStrategy;
$datas
=
$hydrateur
->
extract($exemple
);
Les hydrateurs sont très utilisés avec les formulaires et peuvent l'être dans de nombreuses applications. Ils sont très extensibles et très simples d'utilisation. Les autres hydrateurs fonctionnent de la même manière, ils ne diffèrent que dans la récupération des données de l'objet depuis ses méthodes de classes ou directement depuis ses propriétés.
IX. Conclusion et ressources▲
Complète et simple d'utilisation, la librairie standard du Zend Framework 2 vous permettra de réaliser les opérations usuelles tout en conservant souplesse et performance.
Cet article est tiré en partie du livre « Au cœur de Zend Framework 2 »livre "Au cœur de Zend Framework 2" disponible dans le début du quatrième trimestre de l'année 2012.