Tag Archives: oop

PHP: Autres fonctions méconnues de la programmation objet

Ceci est la seconde partie de PHP: Quelques fonctions méconnues de la programmation objet.

__get, __set, __isset, __unset

PHP permet d’affecter à une instance d’une classe n’importe quelle valeur (presque comme à un tableau de hashage) pour autant que ça ne soit pas une variable privée:

<?php

class Objet {};
$obj = new Objet;
$obj->exemple = 1;
echo $obj->exemple . "\n"; // affiche '1'

Lors de l’affectation d’une variable de telle façon, PHP appelle en fait les fonctions __set et __get pour l’affectation et la lecture de la valeur. Il est évidement bien entendu possible de les réécrire afin de définir un nouveau comportement:

<?php // Objet.class.php

class Objet
{
    private $data;
    public function __construct()
    {
        $data = array();
    }

    public function __set($name, $value)
    {
        echo "Setting $name to $value\n";
        $this->data[ $name ] = $value;
    }

    public function __get($name)
    {
        echo "returns $name\n";
        return $this->data[ $name ];
    }
};
<?php // fichier test.php

require_once("Objet.class.php");
$obj = new Objet;
$obj->priv = 1;
echo $obj->priv;

Cet exemple retournera:

$ php test.php
Setting priv to 1
return priv
1

Cela permet de dévier les affectations des variables dans les instances de classe, mais aussi de « protéger » le contenu des classes. Il faut également noter que si l’appel s’effectue pour une variable déclarée privée dans la classe, on sera également redirigé par __get et __set et l’on n’aura plus l’erreur comme quoi on fait appel à une variable privée.

De plus, de la même façon que pour __get et __set, on aura également __isset et __unset qui serviront à redéfinir le comportement lors d’un appel à isset ou à unset.

__call et __callStatic

Comme pour les variables, il est possible d’attraper les appels des fonctions non définies et privées dans les classes, statiques ou non en utilisant __call et __callStatic.

<?php // Objet.class.php
class Objet
{
    public function __call($func, $args)
    {
        echo "Calling $func\n";
    }

    public static function __callStatic($func, $args)
    {
        echo "Calling static $func\n";
    }
};
<?php // test.php

class Objet {};
$obj = new Objet;
$obj->exemple = 1; echo $obj->exemple . "\n";

Et à l’exécution:

$ php test.php
Calling hello
Calling static hello

Ces deux fonctions, ajoutées aux setters/getters du dessus, permettent de « wrapper » entièrement des instances de classes permettant des utilisations bien plus intéressantes qu’un simple héritage.

__clone

__clone est appelé lorsque l’on crée une copie d’une instance d’objet en appelant clone. Cela résulte à deux instances de la classe totalement indépendante avec le même contenu. Bien que je ne sois pas sûr que clone soit utilisé tous les jours, __clone peut éventuellement être utilisé afin de modifier l’un des deux objets avant ou après le clonage, ou encore de ne pas effectuer ce clonnage afin d’assurer d’avoir un singleton.

__sleep, __wakeup

Finalement, __sleep et __wakeup sont utilisées lors de la sérialisation/désérialisation d’un objet. Elles seront appelées respectivement à l’utilisation de serialize et unserialize. A noter que __sleep doit retourner un array avec la liste des champs à sérialiser.

Comme les courts exemples sont mieux que les longs discours, voici une classe ouvrant un fichier à sa création, le refermant à sa sérialisation, pour le réouvrir à la désérialisation:

<?php // Objet.class.php

class Objet
{
    private $file_name = NULL;
    private $file_hd = NULL;

    public function __construct($file_name)
    {
        $this->file_name = $file_name;
        $this->file_open();
    }

    public function __destruct()
    {
        if($this->file_hd !== NULL)
            $this->file_close();
    }

    private function file_open()
    {
        echo "Opening file\n";
        $this->file_hd = fopen($this->file_name, "a+");
    }

    private function file_close()
    {
        if(NULL === $this->file_hd)
            return false;

        echo "Closing file\n";
        fclose($this->file_hd);
        $this->file_hd = NULL;
    }

    public function __sleep()
    {
        echo "* Serialization.\n";
        $this->file_close();

        return(array('file_name'));
    }

    public function __wakeup()
    {
        echo "* Unserialization.\n";
        $this->file_open();
    }
};

Et testons tout ça:

<?php // fichier test.php

require_once("Objet.class.php");

$obj = new Objet('helloworld.txt');

$serializedObj = serialize($obj);
unset($obj);
var_dump($serializedObj);

$newObj = unserialize($serializedObj);

echo "Fin du script\n";

On peut vérifier le comportement:

$ php test.php
Opening file
* Serialization.
Closing file
string(62) "O:5:"Objet":1:{s:16:"Objetfile_name";s:14:"helloworld.txt";}"
* Unserialization.
Opening file
Fin du script
Closing file

J’espère que quelqu’un aura découvert quelque chose ! :)

Pour ces deux articles, je me suis un peu inspiré d’un article analogue m’ayant poussé à lire un peu plus la doc de PHP: 9 magic methods for php (thinkvitamin.com).

PHP: Quelques fonctions méconnues de la programmation objet

En parcourant la doc de PHP, et spécialement le chapitre réservé aux objets (http://www.php.net/manual/en/language.oop5.php), j’ai découvert pas mal de fonctions que je ne connaissais pas malgré que je repratique de façon courante depuis les deux dernières années.

Même si PHP5 a été introduit en 2004, ces méthodes relatives aux objets sont toujours en constantes évolutions.

__autoload

Si l’on fait appel à une classe (ou une interface) encore non déclarée, PHP effectuera un appel à la fonction __autoload si celle ci a été déclarée. Cela a pour but de décharger les longues listes d’appels à require_once/include visant à inclure les différents fichiers sources définissant ces classes.

Exemple simple:

<?php // fichier Objet.class.php
class Objet
{
};
<?php // fichier test.php
function __autoload($class_name)
{
    echo "Autoloading class $class_name\n";
    $class_file = $class_name . '.class.php';
    if(FALSE === file_exists($class_file))
    {
        throw new Exception('Class "' . $class_name . '" could not be autoloaded (file missing)');
    }
    require_once $class_name . '.class.php';
    if(FALSE === class_exists($class_name))
    {
        throw new Exception('Class "' . $class_name . '" could not be autoloaded (not in file)');
    }
    echo "Autoloading done!";
    return true;
}

// Utilisation de la classe "Objet" définie dans Objet.class.php
$obj = new Objet;

// Tentative d'utilisation de la classe "ObjetNonDefini" qui provoquera une erreur:
$obj2 = new ObjetNonDefini;
$ php test.php
Autoloading class Objet
Autoloading done!
Autoloading class ObjetNonDefini
PHP Fatal error:  Uncaught exception 'Exception' with message 'Class "ObjetNonDefini" could not be autoloaded (file missing)' in /home/mycroft/tmp/php/test.php:9
Stack trace:
#0 /home/mycroft/tmp/php/test.php(26): __autoload('ObjetNonDefini')
#1 {main}
  thrown in /home/mycroft/tmp/php/test.php on line 9

__construct et __destruct

Comme les autres langages programmation objet, PHP propose de définir des constructeurs/destructeurs génériques qui seront appelés lors de la construction (et de la destruction) des dits objets.

A noter que le destructeur sera appelé même si l’objet n’est pas explicitement détruit (à la fin d’un script par exemple).

<?php // Objet.class.php
class Objet
{
    public function __construct()
    {
        echo "Constructor\n";
    }

    public function __destruct()
    {
        echo "Destructor\n";
    }
};
<?php // test.php
require_once("Objet.class.php");
$obj = new Objet;
echo "Fin du script.\n";
$ php test.php
Constructor
Fin du script.
Destructor

Les objets et la visibilité des méthodes privées

PHP gère aussi les variables et méthodes privées, accessible que dans la classe où celles ci existent. Cependant, surement du fait à comment sont gérés les objets dans PHP, il est possible d’appeler une méthode privée d’une instance d’un objet hors de cette instance, à la seule condition que l’on appelle dans un autre objet du même type. Ceci est également expliqué dans le chapitre « Visibility from other objects » de la documentation.

Exemple:

<?php // Objet.class.php
class Objet
{
    private $obj_name = NULL;

    public function __construct($name = NULL)
    {
        $this->obj_name = $name;
    }

    // La méthode privée en question
    private function privateMethod()
    {
        echo "Methode privée dans " . $this->obj_name . "\n";
    }

    // Une méthode publique qui va servir de "tremplin" vers la fonction privée d'une instance tierce
    public function test(Objet $t)
    {
        $t->privateMethod();
    }
};
<?php // fichier test.php
require_once("Objet.class.php");
$obj = new Objet('objet n°1');
$obj2 = new Objet('objet n°2');
// C'est l'objet "obj" qui va appeler une méthode privée appartenant à obj2:
$obj->test($obj2);

Et cela marche:

$ php test.php
Methode privée dans objet n°2

Cependant, cela est à mon avis tout simplement une abération du langage qui ne devrait être utilisée.

__toString

La fonction __toString est une fonction toute simple permettant de définir la façon dont la classe va réagir quand on demandera à la convertir en String (en faisant un appel à « echo », par exemple).

// En reprenant l'Objet ci dessus en rajoutant la méthode:
    public function __toString()
    {
        return "class " . $this->obj_name;
    }
// Et dans test.php
echo $obj;
// Retournera 'class objet n°1'

__invoke

En restant sur la même page de documentation, une autre fonction existe pour définir comment agira la classe si on l’appelle comme étant une fonction. Il s’agit de la fonction __invoke.

// En reprenant Objet.class.php, en y ajoutant:
    public function __invoke()
    {
        echo "Appel de echo sur " . $this . "\n";
    }
<?php // fichier test.php
require_once("Objet.class.php");
$obj = new Objet('objet n°1');
$obj();

Et cela donnera:

$ php test.php
Appel de echo sur class objet n°1

Il reste encore beaucoup de fonctions à découvrir (setters et getters/callers génériques…), et celles ci feront l’objet d’une suite à cet article.