Methode direct ou via getters/setters

Eléphant du PHP | 479 Messages

05 juin 2009, 11:37

Salut à tous,

Ce n'est pas réellement un problème de programmation qui m'amène ici, mais plutôt un problème de conceptualisation.
Pour faire très bref, le système du site que je développe est constitué de fiches qui appartiennent à des catégories, avec une gestion des utilisateurs (toussa, toussa, ...).

Les puristes de la POO disent que par défaut, toutes les propriétés devraient être privées, sauf si on a une bonne raison de les rendre publiques ou protégées. Pour y accéder au sein de mes méthodes, deux façons de faire :
1. La méthode un peu laxiste, mais très pratique : je passe mes données directement en paramêtre de mes méthodes, et je stocke les valeurs dans les propriétés adéquates
2. La méthode radicale : je crée un super objet (qui peut être abstrait) qui ne sert qu'à faire des Get() et des Set(), et tous mes objets en héritent.

Exemple 1 :
  public function Create($name, $parent_id = 0)
    {
      try
      {
        // Tester les variables en entrée
        if ( !is_numeric($parent_id) || !is_string($name) ) throw new Exception();
        $mysql = MySQL::getInstance();
        $id = $mysql->Query('INSERT INTO categories (cat_id, cat_parentid, cat_name) VALUES ("",'.$mysql->EscapeString($parent_id).',"'.$mysql->EscapeString($name).'")', TRUE); 
        $this->id = $id;
        $this->parent_id = $parent_id;
        $this->name = $name;
      }
      catch (Exception $e)
      {
        throw $e;
      }
    }
Les valeurs que prendront les propriétés de ma catégorie sont définies directement dans la classe. Les propriétés sont privées, donc ça ne pose pas de problème.

Exemple 2 :
  class Item
  {
    function Get($property_name)
    {
      if ( ($return_value = property_exists($this, $property_name)) === TRUE) $return_value = $this->{$property_name};
      
      return $return_value;
    }
    
    function Set($property_name, $value)
    {
      if ( ($return_value = property_exists($this, $property_name)) === TRUE) $this->{$property_name} = $value;
      
      return $return_value;
    }
En reprenant l'exemple 1 et celui-ci, je devrais appeler mes Set() en dehors de la classe et la méthode de l'exemple 1 ne contiendrait plus que ce qui a trait à la vérification des données et au traitement MySQL.

Selon vous, quelle serait la meilleure méthode à adopter au niveau de la conceptualisation ?

ViPHP
ViPHP | 4674 Messages

05 juin 2009, 11:46

Hey :-),

Tu soulèves un problème qui a déjà été posé maintes et maintes fois sur le forum. Cherche dans les postes récents de ce salon, tu trouveras des réponses (exemple récent).
Je pense à faire un article dans la FAQ pour expliquer les pour et les contre car cette question revient très régulièrement. On dit toujours que ça ajoute de l'abstraction sans vraiment comprendre pourquoi, j'ai plusieurs exemples sous le coude en ce moment. Si je trouve le temps, je fais ça.

Un indice par contre pour ton exemple 2, regarde du côté de __set() et __get() plutôt que ta technique :-).
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

Eléphant du PHP | 479 Messages

05 juin 2009, 11:59

Mea culpa, je n'ai pas fait de recherche avant.

Je crois comprendre que tu es plus partisan de la méthode getters/setters ! ;-)
Je pense que je vais aussi procéder ainsi, mais je me pose une p'tite question :
Tu dis que c'est mieux pour faire de la vérification de données, mais dans ce cas, est-ce qu'on ne perd pas l'intérêt d'une classe abstraire, vu qu'il faudra redéfinir le setter à chaque fois en fonction des méthodes de la classe.

Bien sûr, n'hésite pas à me reprendre si je fais fausse route.
Merci pour le tuyau __set() et __get() ! ;-)

ViPHP
ViPHP | 4674 Messages

05 juin 2009, 12:08

Oui je suis partisan mais j'ai des bonnes raisons. Je développe un framework (voir Hoa Framework) et combien de fois les getters et setters m'ont éviter des casse-têtes à n'en plus finir ! Il y a quelques jours encore …
Quand je parle d'une réelle abstraction et modularisation du code, c'est très pensé et abouti comme raisonnement.
Je l'expliquerais dans mon article de la FAQ si je le fais, mais crois-moi, le jour où tu veux étendre ton code, tu seras tellement content d'avoir fait des méthodes :-).

Sinon, je ne vois ce que tu veux dire avec une classe abstraite :-? ?
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

Eléphant du PHP | 479 Messages

05 juin 2009, 13:55

Pour ma classe abstraite, je parlais de ma classe Item de l'exemple 2 ... mais je fais peut-être fausse route.
En gros, si je remplace mes fonctions Get() et Set() par __get() et __set(), ça devrait fonctionner non ?

Toujours dans l'optique où j'utilise cette classe, en la rendant abstraite, est-ce que je ne perds pas l'intérêt de l'héritage ? Vu que je vais devoir redéfinir les setters() pour chaque objet hérité, car ils ont des propriétés différentes et que le contrôle des valeurs sera aussi différent, je pense que oui ...

Bon, je ne sais pas si je suis très clair. Je peux essayer de te montrer ça par des exemples.


Edit:
En fait, il y a une incompréhension de ma part au vu des exemples dans la doc PHP sur cette page :
http://fr.php.net/manual/fr/language.oo ... oading.php
Dans les exemples, les fonction __get() et __set() se servent d'un tableau qui est une propriété, mais ne teste que la présence de clés et de valeur qu'au sein de ce tableau. Personnellement, je voudrais essayer de faire ça, mais directement avec les propriétés de ma classe.

Je ne sais toujours pas si je suis clair ! :P

ViPHP
ViPHP | 4674 Messages

05 juin 2009, 15:14

__set et __get sont des méthodes magiques de PHP qui sont appelées lorsqu'on appelle un attribut public qui n'existe pas. Exemple :
$ php -a
Interactive mode enabled

<?php

class A {

    public    $public     = 'I am public!';
    protected $_protected = 'I am protected!';
    private   $_private   = 'I am private!';

    public function __set ( $name, $value ) {

        var_dump($name, $value);
    }

    public function __get ( $name ) {

        var_dump($name);
    }
}

$a = new A();
$a->dummy;
// string(5) "dummy"
// dummy n'est pas un attribut public existant, il est capturé par __get.
var_dump($a->public);
// string(12) "I am public!"
// public est un attribut public existant, il n'est pas capturé par __get.
$a->_protected;
// string(10) "_protected"
// _protected est un attribut existant mais pas public, il est capturé par __get.
$a->_private;
// string(8) "_private"
// Pareil pour _private.

$a->public = 'Toto';
var_dump($a->public);
// string(4) "Toto"
// public existe et il est public, donc il n'est pas capturé par __set.
$a->_protected = 'Hopla';
// string(10) "_protected"
// string(5) "Hopla"
// _protected n'existe pas et n'est pas public, donc il est capturé par __set.
Voilà à quoi sert __set et __get.

Une classe native de PHP le fait déjà : StdClass. Je te laisse t'amuser avec :
<?php

$sc = new StdClass();
$sc->amuse_toi = 'bien';
print_r($sc);
Si tu veux transformer un objet en tableau, alors regarde du côté de l'interface ArrayAccess (et Iterator du même coup, sinon on perd quelques avantages).

Si tu veux vraiment t'amuser et aller plus loin, je te conseille de lire les sources d'une classe très générique : Hoa_StdClass, avec sa documentation. C'est vraiment pour t'amuser avec PHP, ça n'a pas beaucoup d'intérêt ici ;-).

Maintenant, que penses-tu faire ?
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

Eléphant du PHP | 479 Messages

05 juin 2009, 15:49

Merci beaucoup pour les exemples, ça me parle déjà nettement mieux.
Ce que je comptais faire, c'est une classe abstraite avec getter/setter simple pour gérer mes différents objet.

Ma classe Item devient donc :
class Item
{
  function __get($name)
  {
    if ( ($return_value = property_exists($this, $name)) === TRUE) $return_value = $this->{$name};
    
    return $return_value;
  }
  
  function __set($name, $value)
  {
    if ( ($return_value = property_exists($this, $name)) === TRUE) $this->{$name} = $value;
    
    return $return_value;
  }
}
Ca ne marche que pour les attributs privés et protégés, mais c'est intéressant.

Si j'avais à utiliser ma classe Category, qui hérite de Item, j'aurais un truc du genre :
$cat = new Category();
$cat->name = 'blabla';
$cat->parent_id = 0;
$cat->Create();
Est-ce que, dans l'ensemble, ça te semble pertinent ?

ViPHP
ViPHP | 4674 Messages

05 juin 2009, 15:58

Tout à fait :-). Mais pourquoi ne pas utiliser la classe StdClass de PHP directement ? Si tu tiens à en être indépendant, alors fait tout simplement :
class Item extends StdClass { }
et utilise Item comme dans ton exemple. La classe StdClass est plus rapide que celle que tu as écrit car implémentée en C directement. Tu n'auras pas tes tests avec property_exists à faire (tests qui sont longs).
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

Eléphant du PHP | 479 Messages

05 juin 2009, 16:22

Je n'arrive pas à trouver de références claires à StdClass() dans la doc PHP. Sinon, je suis d'accord pour l'utiliser ! ;-)
J'aimerais bien voir ce qu'elle a dans le bide, cette fameuse classe.

ViPHP
ViPHP | 4674 Messages

05 juin 2009, 17:27

La doc' est très peu bavarde sur cette classe, même ~helly
En fait, je ne lis plus le manuel mais je lis les sources directement :-D.

Saches que tu peux ajouter des attributs à la volée, c'est suffisant ;-). Cette classe est très bien gérée par PHP, surtout niveau mémoire.
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

Eléphant du PHP | 479 Messages

08 juin 2009, 09:17

Effectivement, je ne trouve pas grand chose.
Je vais utiliser ta méthode (ci-dessous), en attendant de trouver quelque chose de mieux :
class Item extends StdClass { }
Edit:
Ouhlala, je m'y perds !
Si j'utilise Item comme ci-dessus (classe vide héritant de StdClass), j'obtiens des erreurs d'accès sur ma classe Advertisement (propriété privée, ne peut donc pas être lue depuis l'extérieur de la classe), qui hérite de Item.
Voici mon appel :
include('./class/item.class.php');
include('./class/advertisement.class.php');

$adv = new Advertisement();
echo var_dump($adv->id);
Le constructeur de Advertisement ne fait rien du tout, j'en suis juste à tester les getters/setters.
J'avoue que je suis un peu perdu là.

ViPHP
ViPHP | 4674 Messages

08 juin 2009, 13:36

Bon … reprenons depuis le début. Qu'est-ce que tu veux faire :-) ?
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

Eléphant du PHP | 217 Messages

08 juin 2009, 13:45

Pourquoi faire un extends de stdClass ?
Il me semblais que les objets php héritait de toute manière de cette classe native.
On m'aurait mentit à l'insu de mon plein gré? :)

ViPHP
ViPHP | 4674 Messages

08 juin 2009, 14:10

Non il n'hérite pas de cette classe. Il n'y a pas de super-classe en PHP (peut-être du côté C, mais pas du côté PHP). Tu dois confondre avec la super-classe Object de Java non ?

Ta problématique est la suivante : beaucoup de getters et de setters. Tu veux les éliminer ? Si oui, alors si on veut contrôler les données dynamiquement, on peut utiliser __set et __get sans déclarer les attributs, du coup on peut les ranger dans un tableau. Si tu veux déclarer tes attributs, tu n'as aucun contrôle, donc il te faut des getters et setters, et tu peux en faire des génériques : set($name, $value), à l'instar de __set et __get, sauf qu'on les appelle les manuellement.

Comme tu avais l'air de ne pas vouloir de méthode, il faut être capable d'ajouter des attributs dynamiquement. Toutes les classes supportent ce comportement, mais ce n'est pas bon de laisser PHP le faire tout seul. Donc on utilise la classe StdClass, wala.

Pour montrer que PHP n'a pas de super-classe :
$ php -a
Interactive mode enabled

<?php

class A { }
$a = new A();

var_dump($a instanceof StdClass);
bool(false)
var_dump(is_subclass_of($a, 'StdClass'));
bool(false)
var_dump(get_parent_class($a));
bool(false)
 Q.E.D. :-).
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

Eléphant du PHP | 479 Messages

08 juin 2009, 14:39

On reprend depuis le début : je souhaite me créer une classe générique (abstraite) Item, dont la plupart de mes classes vont hériter. Comme c'est une sorte de "super classe", je voulais y définir directement un getter et un setter génériques. Ensuite, suivant les fonctionnalités que je vais implémenter dans mes objets, je redéfinirais les getters/setters pour coller à des besoins spécifiques.

Voici ma classe Item (la "super classe") et son héritière :
abstract class Item
{
  function __get($name)
  {
    if ( ($return_value = property_exists($this, $name)) === TRUE) $return_value = $this->{$name};
    
    return $return_value;
  }
  
  function __set($name, $value)
  {
    if ( ($return_value = property_exists($this, $name)) === TRUE) $this->{$name} = $value;
    
    return $return_value;
  }
}

class Advertisement extends Item
{
  private $id;
  
  function __construct($id = 0)
  {
    if (is_numeric($id) && $id > 0)
    {
      // Bla bla bla, on récupère les informations relatives à l'objet et on stocke ça dans les propriétés de l'objet.
    }
    else $this->id = NULL;
  }
}
Et j'appelle ça comme ça :
$adv = new Advertisement();
echo var_dump($adv->id);
Rien que là, j'ai un problème car le getter est déclaré au niveau de Item, et l'héritage ne fonctionne pas. Le $this dans Item::__get() est vu comme un objet Item, et non comme l'objet hérité.

Sinon, ça ne me dérange pas de faire un Get() et un Set() manuels comme j'avais fait au départ.

Quelqu'un peut m'expliquer comme contourner ce problème lié à l'héritage ?

Merci !

Edit:
En passant Advertisement::id de "private" à "protected", Item est capable d'avoir accès à celui-ci ... sauf que j'ai peur d'avoir commis une erreur de compréhension. Est-ce là le fonctionnement normal d'un attribut protégé ? Je pensais qu'un attribut protégé de Item serait accessible dans Advertisement, et pas l'inverse.
Dans ce cas là, ça voudrait dire que si je veux utiliser Item::__get et Item::__set en l'état, tous mes attributs de Advertisement devraient être protégés.