web-dev-qa-db-fra.com

Getter et Setter?

Je ne suis pas un développeur PHP, je me demande donc si, dans PHP, il est plus courant d'utiliser des méthodes explicites de lecture/définition, dans un style pur OOP, avec des champs privés (comme je les aime):

class MyClass {
    private $firstField;
    private $secondField;

    public function getFirstField() {
        return $this->firstField;
    }
    public function setFirstField($x) {
        $this->firstField = $x;
    }
    public function getSecondField() {
        return $this->secondField;
    }
    public function setSecondField($x) {
        $this->secondField = $x;
    }
}

ou simplement des champs publics:

class MyClass {
    public $firstField;
    public $secondField;
}

Merci

194
Mark

Vous pouvez utiliser méthodes magiques php__get et __set.

<?php
class MyClass {
  private $firstField;
  private $secondField;

  public function __get($property) {
    if (property_exists($this, $property)) {
      return $this->$property;
    }
  }

  public function __set($property, $value) {
    if (property_exists($this, $property)) {
      $this->$property = $value;
    }

    return $this;
  }
}
?>
210
Dave

Pourquoi utiliser des getters et des setters?

  1. Évolutivité: Il est plus facile de refactoriser un getter que de rechercher toutes les affectations de variables dans un code de projet.
  2. Débogage: Vous pouvez définir des points d'arrêt sur les setters et les getters.
  3. Cleaner: Les fonctions magiques ne sont pas une bonne solution pour écrire moins, votre IDE ne suggérera pas le code. Mieux utiliser les modèles pour les getters à écriture rapide.

direct assignment and getters/setters

109
Wiliam

Google a déjà publié un guide sur l'optimisation de PHP et la conclusion est la suivante:

Aucun getter et setter Optimisation de PHP

Et non, vous devez ne pas utiliser de méthodes magiques . Pour PHP, les méthodes magiques sont diaboliques. Pourquoi?

  1. Ils sont difficiles à déboguer.
  2. Il y a un impact négatif sur les performances.
  3. Ils ont besoin d'écrire plus de code.

PHP n'est pas Java, C++ ou C #. PHP est différent et joue avec différents rôles.

37
magallanes

L'encapsulation est importante dans n'importe quel langage OO, la popularité n'y étant pour rien. Dans les langages à typage dynamique, tels que PHP, il est particulièrement utile car il existe peu de moyens de s’assurer qu’une propriété est d’un type spécifique sans utiliser de séparateur.

En PHP, cela fonctionne:

class Foo {
   public $bar; // should be an integer
}
$foo = new Foo;
$foo->bar = "string";

En Java, ce n'est pas le cas:

class Foo {
   public int bar;
}
Foo myFoo = new Foo();
myFoo.bar = "string"; // error

L'utilisation de méthodes magiques (__get et __set) fonctionne également, mais uniquement lorsque vous accédez à une propriété dont la visibilité est inférieure à celle de la portée actuelle. Cela peut facilement vous donner des maux de tête lorsque vous essayez de déboguer, s’il n’est pas utilisé correctement.

13
netcoder

En plus des réponses déjà importantes et respectées ici, je voudrais développer PHP ne pas avoir de setters/getters.

PHP n'a pas de syntaxe getter et setter . Il fournit des méthodes sous-classées ou magiques permettant le "raccordement" et le remplacement du processus de recherche de propriété, comme indiqué par Dave .

Magic permet programmeurs paresseux de faire plus avec moins de code à un moment où nous sommes activement engagés dans un projet et connaissons intimement, mais généralement au détriment de la lisibilité.

Performance Chaque fonction inutile, résultant du forçage d'une architecture de code de type getter/setter en PHP, implique son propre frame de pile de mémoire lors de son invocation et est gaspillage des cycles de processeur.

Lisibilité: La base de code engendre des lignes de code qui gonflent, ce qui a un impact sur la navigation dans le code car plus de LOC signifie plus de défilement.

Préférence: Personnellement, comme règle de base, je considère l'échec de l'analyse de code statique comme un signe pour éviter de suivre la voie magique aussi longtemps que cela est évident. les avantages à long terme m'échappent à cette époque.

Idées fausses:

Un argument commun est la lisibilité. Par exemple, $someobject->width est plus facile à lire que $someobject->width(). Cependant, contrairement à la circumference ou width d'une planète, que l'on peut supposer être static, l'instance d'un objet tel que $someobject, qui nécessite une fonction width, prend probablement une mesure de la largeur d'instance de l'objet.
Par conséquent, la lisibilité augmente principalement à cause de schémas de nommage assertifs et non en masquant la fonction qui génère une valeur de propriété donnée.

__ get/__set utilise:

  • pré-validation et pré-assainissement des valeurs des propriétés

  • des chaînes, par exemple.

    "
    some {mathsobj1->generatelatex} multi
    line text {mathsobj1->latexoutput}
    with lots of variables for {mathsobj1->generatelatex}
     some reason
    "
    

    Dans ce cas, generatelatex adhérerait à un schéma de nommage actionname + methodname

  • cas spéciaux et évidents

    $dnastringobj->homeobox($one_rememberable_parameter)->gattaca->findrelated()
    $dnastringobj->homeobox($one_rememberable_parameter)->gttccaatttga->findrelated()
    

Remarque: PHP a choisi de ne pas implémenter la syntaxe getter/setter. Je ne prétends pas que les Getters/Setter sont généralement mauvais.

7
Lorenz Lo Sauer

Si vous préférez utiliser la fonction __call, vous pouvez utiliser cette méthode. Ça marche avec

  • GET => $this->property()
  • SET => $this->property($value)
  • GET => $this->getProperty()
  • SET => $this->setProperty($value)

kalsdas

public function __call($name, $arguments) {

    //Getting and setting with $this->property($optional);

    if (property_exists(get_class($this), $name)) {


        //Always set the value if a parameter is passed
        if (count($arguments) == 1) {
            /* set */
            $this->$name = $arguments[0];
        } else if (count($arguments) > 1) {
            throw new \Exception("Setter for $name only accepts one parameter.");
        }

        //Always return the value (Even on the set)
        return $this->$name;
    }

    //If it doesn't chech if its a normal old type setter ot getter
    //Getting and setting with $this->getProperty($optional);
    //Getting and setting with $this->setProperty($optional);
    $prefix = substr($name, 0, 3);
    $property = strtolower($name[3]) . substr($name, 4);
    switch ($prefix) {
        case 'get':
            return $this->$property;
            break;
        case 'set':
            //Always set the value if a parameter is passed
            if (count($arguments) != 1) {
                throw new \Exception("Setter for $name requires exactly one parameter.");
            }
            $this->$property = $arguments[0];
            //Always return the value (Even on the set)
            return $this->$name;
        default:
            throw new \Exception("Property $name doesn't exist.");
            break;
    }
}
7
J-Rou
class MyClass {
    private $firstField;
    private $secondField;
    private $thirdField;

    public function __get( $name ) {
        if( method_exists( $this , $method = ( 'get' . ucfirst( $name  ) ) ) )
            return $this->$method();
        else
            throw new Exception( 'Can\'t get property ' . $name );
    }

    public function __set( $name , $value ) {
        if( method_exists( $this , $method = ( 'set' . ucfirst( $name  ) ) ) )
            return $this->$method( $value );
        else
            throw new Exception( 'Can\'t set property ' . $name );
    }

    public function __isset( $name )
    {
        return method_exists( $this , 'get' . ucfirst( $name  ) ) 
            || method_exists( $this , 'set' . ucfirst( $name  ) );
    }

    public function getFirstField() {
        return $this->firstField;
    }

    protected function setFirstField($x) {
        $this->firstField = $x;
    }

    private function getSecondField() {
        return $this->secondField;
    }
}

$obj = new MyClass();

echo $obj->firstField; // works
$obj->firstField = 'value'; // works

echo $obj->getFirstField(); // works
$obj->setFirstField( 'value' ); // not works, method is protected

echo $obj->secondField; // works
echo $obj->getSecondField(); // not works, method is private

$obj->secondField = 'value'; // not works, setter not exists

echo $obj->thirdField; // not works, property not exists

isset( $obj->firstField ); // returns true
isset( $obj->secondField ); // returns true
isset( $obj->thirdField ); // returns false

Prêt!

6
joas

Eh bien, PHP a des méthodes magiques __get, __set, __isset & __unset , ce qui est toujours un début. Hélas, les propriétés OO sont plus que des méthodes magiques. Le principal problème de l'implémentation de PHP est que les méthodes magiques sont appelées pour toutes les propriétés inaccessibles. Ce qui signifie que vous devez vous répéter (par exemple, en appelant property_exists ()) dans les méthodes magiques pour déterminer si name est réellement une propriété de votre objet. Et vous ne pouvez pas vraiment résoudre ce problème général avec une classe de base à moins que toutes vos classes héritent de ie. ClassWithProperties, puisque PHP ne possède pas d'héritage multiple.

En revanche, Python les nouvelles classes de style vous donnent property(), ce qui vous permet de définir explicitement toutes vos propriétés. C # a une syntaxe spéciale.

http://en.wikipedia.org/wiki/Property_ (programmation)

5
Emanuel Landeholm

J'ai fait une expérience en utilisant la méthode magique __call. Je ne sais pas si je devrais le poster (à cause de tous les avertissements "NE PAS UTILISER DE MÉTHODE MAGIQUE" dans les autres réponses et commentaires) mais je le laisserai ici .. juste au cas où quelqu'un le trouverait utile.


public function __call($_name, $_arguments){
    $action  = substr($_name, 0, 4);
    $varName = substr($_name, 4);

    if (isset($this->{$varName})){
        if ($action === "get_") return $this->{$varName};
        if ($action === "set_") $this->{$varName} = $_arguments[0];
    }
}

Ajoutez simplement cette méthode ci-dessus dans votre classe, vous pouvez maintenant taper:

class MyClass{
    private foo = "bar";
    private bom = "bim";
    // ...
    // public function __call(){ ... }
    // ...
}
$C = new MyClass();

// as getter
$C->get_foo(); // return "bar"
$C->get_bom(); // return "bim"

// as setter
$C->set_foo("abc"); // set "abc" as new value of foo
$C->set_bom("zam"); // set "zam" as new value of bom


De cette façon, vous pouvez obtenir/définir tout dans votre classe si elle existe. Ainsi, si vous en avez besoin pour seulement quelques éléments spécifiques, vous pouvez utiliser une "liste blanche" comme filtre.

Exemple:

private $callWhiteList = array(
    "foo" => "foo",
    "fee" => "fee",
    // ...
);

public function __call($_name, $_arguments){
    $action  = substr($_name, 0, 4);
    $varName = $this->callWhiteList[substr($_name, 4)];

    if (!is_null($varName) && isset($this->{$varName})){
        if ($action === "get_") return $this->{$varName};
        if ($action === "set_") $this->{$varName} = $_arguments[0];
    }
}

Maintenant, vous ne pouvez obtenir/définir que "foo" et "fee".
Vous pouvez également utiliser cette "liste blanche" pour attribuer des noms personnalisés pour accéder à vos vars.
Par exemple,

private $callWhiteList = array(
    "myfoo" => "foo",
    "zim" => "bom",
    // ...
);

Avec cette liste, vous pouvez maintenant taper:

class MyClass{
    private foo = "bar";
    private bom = "bim";
    // ...
    // private $callWhiteList = array( ... )
    // public function __call(){ ... }
    // ...
}
$C = new MyClass();

// as getter
$C->get_myfoo(); // return "bar"
$C->get_zim(); // return "bim"

// as setter
$C->set_myfoo("abc"); // set "abc" as new value of foo
$C->set_zim("zam"); // set "zam" as new value of bom

.
.
.
C'est tout.


Doc: __ call () est déclenché lors de l'appel de méthodes inaccessibles dans un contexte d'objet.

3
Sabaz

Après avoir lu les autres conseils, je suis enclin à dire que:

En règle GENERIC, vous ne définissez pas toujours les paramètres pour les propriétés ALL, en particulier "internes" (sémaphores, indicateurs internes ...). Les propriétés en lecture seule n'auront pas de séparateur, bien entendu, certaines propriétés n'auront donc que des getters; C'est là que __get () vient réduire le code:

  • définir un __get () (accesseurs globaux magiques) pour toutes les propriétés qui se ressemblent,
  • groupez-les dans des tableaux afin:
    • ils partagent des caractéristiques communes: les valeurs monétaires seront/peuvent être correctement formatées, les dates dans un format spécifique (ISO, US, International), etc.
    • le code lui-même peut vérifier que seules les propriétés existantes et autorisées sont lues à l'aide de cette méthode magique.
    • chaque fois que vous devez créer une nouvelle propriété similaire, déclarez-la et ajoutez son nom au tableau approprié, et c'est terminé. C'est ainsi PLUS RAPIDE que de définir un nouveau getter, peut-être avec quelques lignes de code répétées encore et encore dans le code de classe.

Oui! nous pourrions aussi écrire une méthode privée pour le faire, mais là encore, nous aurons BEAUCOUP de méthodes déclarées (++ memory) qui finiront par appeler une autre méthode, toujours la même. Pourquoi ne pas écrire une méthode UNIQUE pour les gouverner tous ...? [oui! jeu de mots absolument voulu! :)]

Magic setters peut également répondre UNIQUEMENT à des propriétés spécifiques. Ainsi, toutes les propriétés de type de date peuvent être filtrées en fonction de valeurs non valides dans une seule méthode. Si les propriétés du type de date étaient répertoriées dans un tableau, leurs paramètres peuvent être définis facilement. Juste un exemple, bien sûr. il y a beaucoup trop de situations.

À propos de lisibilité ... Bon ... C'est un autre débat: je n'aime pas être lié aux utilisations d'un IDE (en fait, je ne les utilise pas, ils ont tendance à me dire (et de me forcer moi)) comment écrire ... et j'ai mon aime à propos du codage "beauté"). J'ai tendance à être cohérent dans les noms, alors utiliser ctags et quelques autres aides me suffit ... Quoi qu'il en soit: une fois que tous ces paramètres magiques sont terminés, j'écris les autres paramètres trop spécifiques ou "spéciaux" pour être généralisé dans une méthode __set (). Et cela couvre tout ce dont j'ai besoin pour obtenir et définir des propriétés. Bien sûr: il n’ya pas toujours de terrain d’entente commun, ni de propriétés qui ne valent pas la peine de coder une méthode magique, et il reste encore l’ancienne bonne paire setter/getter traditionnelle.

Les langages de programmation ne sont que cela: des langages artificiels humains. Donc, chacun d’eux a sa propre intonation ou accent, syntaxe et saveur, je ne prétends donc pas écrire un code Ruby ou Python en utilisant le même "accent" que Java ou C #, je n'écrirais pas de code JavaScript ni PHP pour ressembler à Perl ou SQL ... Utilisez-les tels qu'ils sont censés être utilisés.

2
Manuel

De manière générale, le premier moyen est plus populaire dans l’ensemble, car ceux qui ont des connaissances en programmation peuvent facilement passer à PHP et effectuer leur travail de manière orientée objet. La première façon est plus universelle. Mon conseil serait de s'en tenir à ce qui est essayé et vrai dans de nombreuses langues. Ensuite, lorsque et si vous utilisez une autre langue, vous serez prêt à accomplir quelque chose (au lieu de passer du temps à réinventer la roue).

1
Anthony Rutledge

Validation + Mise en forme/Dérivation de valeurs

Les setters vous permettent de valider les données et les getters vous permettent de formater ou d’obtenir des données. Les objets vous permettent d'encapsuler les données et leur code de validation et de formatage dans un package ordonné qui encourage DRY.

Par exemple, considérons la classe simple suivante qui contient une date de naissance.

class BirthDate {

    private $birth_date;

    public function getBirthDate($format='Y-m-d') {
        //format $birth_date ...
        //$birth_date = ...
        return $birth_date;
    }

    public function setBirthDate($birth_date) {                   
        //if($birth_date is not valid) throw an exception ...          
        $this->birth_date = $birth_date;
    }

    public function getAge() {
        //calculate age ...
        return $age;
    }

    public function getDaysUntilBirthday() {
        //calculate days until birth days
        return $days;
    }
}

Vous voudrez valider que la valeur définie est

  • Une date valide
  • Pas dans le futur

Et vous ne voulez pas faire cette validation sur votre application (ou sur plusieurs applications d'ailleurs). Au lieu de cela, il est plus facile de rendre la variable membre protégée ou privée (afin de faire du poseur le seul point d'accès) et de la valider dans le poseur, car vous saurez ainsi que l'objet contient une date de naissance valide, quelle que soit la partie du application d'où provient l'objet et si vous souhaitez ajouter plus de validation, vous pouvez l'ajouter à un seul endroit.

Vous pouvez ajouter plusieurs formateurs qui fonctionnent sur la même variable de membre, à savoir getAge() et getDaysUntilBirthday(), et appliquer un format configurable dans getBirthDate(), en fonction des paramètres régionaux. Par conséquent, je préfère systématiquement accéder aux valeurs via des accesseurs plutôt que de mélanger $date->getAge() avec $date->birth_date.

les getters et les setters sont également utiles lorsque vous développez des objets. Par exemple, supposons que votre application doit permettre des dates de naissance de plus de 150 ans à certains endroits mais pas à d’autres. Une façon de résoudre le problème sans répéter le code serait d'étendre l'objet BirthDate et de placer la validation supplémentaire dans le programme de configuration.

class LivingBirthDate extends BirthDate {

    public function setBirthDate($birth_date) {
        //if $birth_date is greater than 150 years throw an exception
        //else pass to parent's setter
        return parent::setBirthDate($birth_date);
    }
}
0
FuzzyTree

Il existe de nombreuses façons de créer un code source dans une convention Netbeans. C'est sympa. Cela rend la pensée plus facile === FAUX. Utilisez simplement traditionel, en particulier si vous ne savez pas laquelle des propriétés doit être encapsulée ou non. Je sais, c’est un code boi .... pla ... mais pour les travaux de débogage et beaucoup d’autres pense que c’est le meilleur moyen, le plus clair. Ne passez pas trop de temps avec des milliers d’art à fabriquer des accesseurs simples et des setters. Vous ne pouvez pas non plus implémenter certains modèles, comme la règle de déméter, etc. si vous utilisez la magie. Dans certaines situations, vous pouvez utiliser magic_calls ou pour des solutions petites, rapides et claires. Bien sûr, vous pouvez aussi créer des solutions pour les modèles de conception, mais pourquoi vous rendre la vie plus difficile.

0
Dennis Komnenovic

Ce message ne concerne pas spécifiquement __get et __set, mais plutôt __call, qui est la même idée sauf pour l'appel de méthode. En règle générale, je m'éloigne de tout type de méthode magique permettant la surcharge pour les raisons décrites dans les commentaires et les messages CEPENDANT, j'ai récemment lancé dans une API tierce que j'utilise et qui utilise un service et un sous-service, par exemple:

http://3rdparty.api.com?service=APIService.doActionOne&apikey=12341234

La partie importante de ceci est que cette API a tout de même, sauf la sous-action, dans ce cas doActionOne. L'idée est que le développeur (moi-même et les autres utilisateurs de cette classe) puisse appeler le sous-service par son nom plutôt que:

$myClass->doAction(array('service'=>'doActionOne','args'=>$args));

Je pourrais faire la place:

 $myClass->doActionOne($args);

Pour coder en dur, ce serait juste beaucoup de duplication (cet exemple très ressemble vaguement au code):

public function doActionOne($array)
    {
        $this->args     =   $array;
        $name           =   __FUNCTION__;
        $this->response =   $this->executeCoreCall("APIService.{$name}");
    }

public function doActionTwo($array)
    {
        $this->args     =   $array;
        $name           =   __FUNCTION__;
        $this->response =   $this->executeCoreCall("APIService.{$name}");
    }

public function doActionThree($array)
    {
        $this->args     =   $array;
        $name           =   __FUNCTION__;
        $this->response =   $this->executeCoreCall("APIService.{$name}");
    }

protected function executeCoreCall($service)
    {
        $cURL = new \cURL();
        return $cURL->('http://3rdparty.api.com?service='.$service.'&apikey='.$this->api.'&'.http_build_query($this->args))
                    ->getResponse();
    }

Mais avec la méthode magique de __call(), je peux accéder à tous les services avec des méthodes dynamiques:

public function __call($name, $arguments)
    {
        $this->args     =   $arguments;
        $this->response =   $this->executeCoreCall("APIService.{$name}");   
        return $this;
    }

L'avantage de cet appel dynamique pour le retour des données est que si le fournisseur ajoute un autre sous-service, je n'ai pas besoin d'ajouter une autre méthode à la classe ni de créer une classe étendue, etc. Je ne suis pas sûr que cela soit utile. n'importe qui, mais j'ai pensé que je montrerais un exemple où __set, __get, __call, etc. peuvent être une option à prendre en compte puisque la fonction principale est le retour des données.


MODIFIER:

Par coïncidence, je l'ai vu quelques jours après la publication, décrivant exactement mon scénario. Ce n’est pas l’API à laquelle je faisais référence, mais l’application des méthodes est identique:

Est-ce que j'utilise correctement api?

0
Rasclatt