Propriétés et méthodes statiques en PHP. Pourquoi certains développeurs PHP préfèrent-ils les API statiques ? Méthodes statiques PHP

Late Static Binding (LSB) est un sujet de discussion brûlant depuis trois ans dans les cercles de développement PHP (et nous l'avons finalement compris dans PHP 5.3). Mais pourquoi est-ce nécessaire ? Dans cet article, nous examinerons exactement comment liaison statique tardive peut grandement simplifier votre code.

Lors d'une réunion de développeurs PHP tenue à Paris en novembre 2005, le sujet des liaisons statiques tardives a été formellement discuté par l'équipe de développement principale. Ils ont convenu de le mettre en œuvre, ainsi que de nombreux autres sujets à l'ordre du jour. Les détails devaient être convenus dans le cadre de discussions ouvertes.

Depuis liaison statique tardive a été annoncé comme un long métrage à venir, deux ans se sont écoulés. Et finalement, LSB est devenu disponible pour une utilisation dans PHP 5.3. Mais cet événement est passé inaperçu des développeurs utilisant PHP, d'après les notes d'une seule page du manuel.

En bref, la nouvelle fonctionnalité de liaison statique tardive permet aux objets d'hériter toujours des méthodes des classes parents, mais permet en outre aux méthodes héritées d'avoir accès aux constantes statiques, aux méthodes et aux propriétés de la classe enfant, et pas seulement à la classe parent. Regardons un exemple :

Classe Beer ( const NAME = "Beer!"; public function getName() ( return self::NAME; ) ) class Ale extends Beer ( const NAME = "Ale!"; ) $beerDrink = new Beer; $aleDrink = nouvelle bière ; echo "La bière c'est : " . $beerDrink->getName() ."\n"; echo "La bière est : " . $aleDrink->getName() ."\n";

Ce code produira le résultat suivant :

La bière, c'est : la bière ! La bière, c'est : la bière !

Classe Ale méthode héritée getName(), mais en même temps soi pointe toujours vers la classe dans laquelle il est utilisé (dans ce cas, la classe Bière). Ceci est resté dans PHP 5.3, mais le mot a été ajouté statique. Regardons à nouveau l'exemple :

Classe Beer ( const NAME = "Beer!"; fonction publique getName() ( return self::NAME; ) fonction publique getStaticName() ( return static::NAME; ) ) classe Ale étend Beer ( const NAME = "Ale!" ; ) $beerDrink = nouvelle bière ; $aleDrink = nouvelle bière ; echo "La bière c'est : " . $beerDrink->getName() ."\n"; echo "La bière est : " . $aleDrink->getName() ."\n"; echo "La bière est en fait : " . $beerDrink->getStaticName() ."\n"; echo "La bière est en fait : " . $aleDrink->getStaticName() ."\n";

Nouveau mot-clé statique indique qu'il est nécessaire d'utiliser une constante d'une classe héritée, au lieu d'une constante qui a été définie dans la classe où la méthode est déclarée getStaticName(). Mot statique a été ajouté pour implémenter de nouvelles fonctionnalités et pour une compatibilité ascendante soi fonctionne de la même manière que dans les versions précédentes de PHP.

En interne, la principale différence (et, en fait, la raison pour laquelle la liaison est appelée tardivement) entre ces deux méthodes d'accès est que PHP définira la valeur de soi::NOM lors de la "compilation" (lorsque les caractères PHP sont convertis en code machine qui sera traité par le moteur Zend), et pour statique ::NOM la valeur sera déterminée au démarrage (au moment où le code machine est exécuté dans le moteur Zend).

Ceci est un autre outil pour les développeurs PHP. Dans la deuxième partie, nous verrons comment il peut être utilisé à bon escient.

À partir de PHP 5.3.0, il existait une fonctionnalité appelée liaison statique tardive qui pouvait être utilisée pour obtenir une référence à une classe appelable dans le contexte de l'héritage statique.

Plus précisément, la liaison statique tardive préserve le nom de la classe spécifiée dans le dernier « appel non transféré ». Dans le cas d'appels statiques, il s'agit d'une classe explicitement spécifiée (généralement à gauche de l'opérateur :: ); dans le cas d'appels non statiques, il s'agit de la classe de l'objet. Un « appel redirigé » est un appel statique commençant par soi::, parent::, statique::, ou, si nous montons dans la hiérarchie des classes, forward_static_call(). Fonction get_call_class() peut être utilisé pour obtenir une chaîne avec le nom de la classe appelée, et statique:: représente sa portée.

Le nom « liaison statique tardive » lui-même reflète l’implémentation interne de cette fonctionnalité. La « reliure tardive » reflète le fait que statique:: ne sera pas calculé par rapport à la classe dans laquelle la méthode appelée est définie, mais sera calculé en fonction des informations au moment de l'exécution. Cette fonctionnalité a également été appelée « liaison statique » car elle peut être utilisée (mais ce n’est pas obligatoire) dans les méthodes statiques.

Restrictions soi::

Exemple n°1 d'utilisation soi::

Classe A(
écho __CLASS__ ;
}
fonction statique publique
test()(
soi :: qui ();
}
}

la classe B étend A (
fonction statique publique who() (
écho __CLASS__ ;
}
}

B::test();
?>

Utilisation de la liaison statique tardive

Les liaisons statiques ultérieures tentent de surmonter cette limitation en fournissant un mot-clé faisant référence à une classe appelée directement au moment de l'exécution. En termes simples, un mot-clé qui vous permettra de créer un lien vers B depuis test() dans l'exemple précédent. Il a été décidé de ne pas introduire de nouveau mot-clé, mais d'utiliser statique, qui est déjà réservé.

Exemple n°2 Facile à utiliser statique::

Classe A(
fonction statique publique who() (
écho __CLASS__ ;
}
fonction statique publique
test()(
statique :: qui (); // La liaison statique tardive s'applique ici
}
}

la classe B étend A (
fonction statique publique who() (
écho __CLASS__ ;
}
}

B::test();
?>

Résultat de l'exécution cet exemple:

Commentaire:

Dans un contexte non statique, la classe appelée sera celle à laquelle appartient l'instance d'objet. Parce que le $ce-> va essayer d'appeler des méthodes privées du même portée, utilisation statique:: peut donner des résultats différents. Une autre différence est que statique:: ne peut faire référence qu'aux champs statiques d'une classe.

Exemple n°3 d'utilisation statique:: dans un contexte non statique

Classe A(
fonction privée foo() (
echo "succès !\n" ;
}
test de fonction publique() (
$this -> foo();
statique :: foo ();
}
}

la classe B étend A (
/* foo() sera copié dans B, donc sa portée est toujours A,
et l'appel réussira*/
}

la classe C étend A (
fonction privée foo() (
/* méthode originale remplacée ; portée de la nouvelle méthode C */
}
}

$b = nouveau B();
$b -> test();
$c = nouveau C();
$c -> test(); //pas vrai
?>

Le résultat de l'exécution de cet exemple :

succès! succès! succès! Erreur fatale : Appel à la méthode privée C::foo() depuis le contexte "A" dans /tmp/test.php à la ligne 9

Commentaire:

La région de résolution de la liaison statique tardive sera corrigée par l’appel statique qui la calcule. D'un autre côté, les appels statiques utilisant des directives telles que parent:: ou soi:: rediriger les informations d’appel.

Exemple #4 Appels redirigés et non redirigés

Ce n’est un secret pour personne : les gens aiment poser des questions délicates lors des entretiens. Pas toujours adéquat, pas toujours lié à la réalité, mais le fait reste un fait - demandent-ils. Bien sûr, la question est différente, et parfois une question qui vous semble stupide à première vue vise en réalité à tester votre connaissance de la langue dans laquelle vous écrivez.

Essayons de démonter une de ces questions « pièce par pièce » - Que signifie le mot « statique » en PHP et pourquoi est-il utilisé ?

Le mot clé static a trois significations différentes en PHP. Regardons-les par ordre chronologique, tels qu'ils sont apparus dans la langue.

La première valeur est une variable locale statique

fonction foo() ( $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 foo(); // 0 foo(); // 0

En PHP, les variables sont locales. Cela signifie qu'une variable définie et dotée d'une valeur dans une fonction (méthode) n'existe que pendant l'exécution de cette fonction (méthode). Lorsque la méthode se termine, la variable locale est détruite et lorsqu'elle rentre, elle est recréée. Dans le code ci-dessus, une telle variable locale est la variable $a - elle n'existe qu'à l'intérieur de la fonction foo() et est créée à nouveau à chaque fois que cette fonction est appelée. Incrémenter une variable dans ce code n'a aucun sens, car dès la ligne de code suivante, la fonction terminera son travail et la valeur de la variable sera perdue. Peu importe le nombre de fois que nous appelons la fonction foo(), elle affichera toujours 0...

Cependant, tout change si l'on met le mot-clé static avant l'affectation :

Fonction foo() ( static $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 foo(); // 1 foo(); // 2

Le mot clé static, écrit avant d'attribuer une valeur à une variable locale, a les effets suivants :

  1. L'affectation n'est effectuée qu'une seule fois, au premier appel à la fonction
  2. La valeur d'une variable ainsi marquée est enregistrée une fois la fonction terminée.
  3. Lors des appels ultérieurs à la fonction, au lieu d'une affectation, la variable reçoit la valeur précédemment stockée
Cette utilisation du mot statique est appelée variable locale statique.
Pièges des variables statiques
Bien sûr, comme toujours en PHP, il existe quelques pièges.

La première pierre est que seules des constantes ou des expressions constantes peuvent être affectées à une variable statique. Voici le code :
statique $a = bar();
conduira inévitablement à une erreur d'analyseur. Heureusement, depuis la version 5.6, il est devenu possible d'attribuer non seulement des constantes, mais aussi des expressions constantes (par exemple, « 1+2 » ou « »), c'est-à-dire des expressions qui ne dépendent pas d'un autre code et peuvent être calculées. au stade de la compilation

La deuxième pierre est que les méthodes existent en un seul exemplaire.
Ici, tout est un peu plus compliqué. Pour comprendre l'essentiel, voici le code :
classe A ( fonction publique foo() ( static $x = 0; echo ++$x; ) ) $a1 = new A; $a2 = nouveau A ; $a1->foo(); // 1 $a2->foo(); // 2 $a1->foo(); // 3 $a2->foo(); // 4
Contrairement à l’attente intuitive « différents objets – différentes méthodes », nous voyons clairement dans cet exemple que les méthodes dynamiques en PHP « ne se multiplient pas ». Même si nous avons une centaine d’objets de cette classe, la méthode n’existera que dans une seule instance ; c’est juste qu’un $this différent y sera ajouté à chaque appel.

Ce comportement peut être inattendu pour un développeur qui n’y est pas préparé et peut être source d’erreurs. Il est à noter que l'héritage de classe (et de méthode) conduit à la création d'une nouvelle méthode :

Classe A ( public function foo() ( static $x = 0; echo ++$x; ) ) classe B étend A ( ) $a1 = new A; $b1 = nouveau B ; $a1->foo(); // 1 $b1->foo(); // 1 $a1->foo(); // 2 $b1->foo(); // 2

Conclusion : les méthodes dynamiques en PHP existent dans le contexte de classes et non d'objets. Et ce n'est qu'au moment de l'exécution que la substitution « $this = current_object » se produit

La deuxième signification concerne les propriétés statiques et les méthodes des classes

Dans le modèle objet PHP, il est possible de définir des propriétés et des méthodes non seulement pour les objets - instances d'une classe, mais également pour la classe dans son ensemble. Le mot-clé static est également utilisé pour cela :

Classe A ( public static $x = "foo"; public static function test() ( return 42; ) ) echo A::$x; // "foo" echo A::test(); // 42
Pour accéder à ces propriétés et méthodes, des constructions à double deux-points (« Paamayim Nekudotayim ») sont utilisées, telles que CLASS_NAME::$Variablename et CLASS_NAME::Methodname().

Il va sans dire que les propriétés statiques et les méthodes statiques ont leurs propres caractéristiques et pièges que vous devez connaître.

La première fonctionnalité est banale - il n'y a pas de $this. En fait, cela vient de la définition même d'une méthode statique : puisqu'elle est associée à une classe et non à un objet, la pseudo-variable $this, qui pointe vers l'objet courant dans les méthodes dynamiques, n'est pas disponible. Ce qui est tout à fait logique.

Cependant, il faut savoir que contrairement à d'autres langages, PHP ne détecte pas la situation « $this est écrit dans une méthode statique » au stade de l'analyse ou de la compilation. Une erreur comme celle-ci ne peut se produire qu'au moment de l'exécution si vous essayez d'exécuter du code avec $this dans une méthode statique.

Codez comme ceci :
classe A ( public $id = 42; fonction publique statique foo() ( echo $this->id; ) )
ne provoquera aucune erreur, tant que vous n'essayez pas d'utiliser la méthode foo() de manière inappropriée :
$a = nouveau A ; $a->foo(); (et obtenez immédiatement « Erreur fatale : utilisation de $this lorsqu'il n'est pas dans le contexte de l'objet »)

La deuxième caractéristique est que la statique n’est pas un axiome !
classe A ( fonction publique statique foo() ( echo 42; ) ) $a = new A; $a->foo();
C'est ça, oui. Une méthode statique, si elle ne contient pas $this dans le code, peut être appelée dans un contexte dynamique, comme une méthode objet. Ce n'est pas un bug de PHP.

L’inverse n’est pas entièrement vrai :
classe A ( public function foo() ( echo 42; ) ) A::foo();
Une méthode dynamique qui n'utilise pas $this peut être exécutée dans un contexte statique. Cependant, vous recevrez un avertissement « La méthode non statique A::foo() ne doit pas être appelée de manière statique » au niveau E_STRICT. C'est à vous de décider si vous souhaitez suivre strictement les normes du code ou supprimer les avertissements. La première est évidemment préférable.

Et d'ailleurs, tout ce qui est écrit ci-dessus s'applique uniquement aux méthodes. Utiliser une propriété statique via "->" est impossible et conduit à une erreur fatale.

Le troisième sens, qui semble être le plus difficile - la liaison statique tardive

Les développeurs du langage PHP ne se sont pas arrêtés à deux valeurs mot-clé« statique » et dans la version 5.3, ils ont ajouté une autre « fonctionnalité » du langage, qui est implémentée avec le même mot ! C'est ce qu'on appelle la « liaison statique tardive » ou LSB (Late Static Binding).

Le moyen le plus simple de comprendre l’essence du LSB consiste à utiliser des exemples simples :

Modèle de classe ( public static $table = "table"; public static function getTable() ( return self::$table; ) ) echo Model::getTable(); // "tableau"
Le mot-clé self en PHP signifie toujours « le nom de la classe où ce mot est écrit ». Dans ce cas, self est remplacé par la classe Model et self::$table par Model::$table.
Cette fonctionnalité du langage est appelée « liaison statique précoce ». Pourquoi tôt ? Parce que la liaison de soi et d'un nom de classe spécifique ne se produit pas au moment de l'exécution, mais à des étapes antérieures - lors de l'analyse et de la compilation du code. Eh bien, "statique" - parce que nous parlons de sur les propriétés et méthodes statiques.

Modifions un peu notre code :

Modèle de classe ( public static $table = "table"; public static function getTable() ( return self::$table; ) ) class User extends Model ( public static $table = "users"; ) echo User::getTable() ; // "tableau"

Vous comprenez maintenant pourquoi PHP se comporte de manière non intuitive dans cette situation. self était associé à la classe Model alors que l'on ne savait rien de la classe User et pointe donc vers Model.

Que dois-je faire?

Pour résoudre ce dilemme, un mécanisme de liaison « tardive » a été inventé au stade de l'exécution. Cela fonctionne très simplement - il suffit d'écrire « static » au lieu du mot « self » et la connexion sera établie avec la classe qui appelle ce code, et non avec celle où il est écrit :
modèle de classe ( public static $table = "table"; public static function getTable() ( return static::$table; ) ) class User extends Model ( public static $table = "users"; ) echo User::getTable() ; // "utilisateurs"

Il s’agit de la mystérieuse « liaison statique tardive ».

A noter que pour plus de commodité en PHP, en plus du mot « statique », il y a aussi fonction spéciale get_call_class(), qui vous dira quelle classe est en contexte ce moment votre code fonctionne.

Bonnes entrevues !

J'ai longtemps voulu écrire sur ce sujet. Le premier élan a été l'article de Miško Hevery "Les méthodes statiques sont la mort de la testabilité". J'ai écrit un article de réponse, mais je ne l'ai jamais publié. Mais récemment, j’ai vu quelque chose que l’on peut appeler « programmation orientée classe ». Cela a renouvelé mon intérêt pour le sujet et voici le résultat.

La « programmation orientée classe » consiste à utiliser des classes composées uniquement de méthodes et de propriétés statiques, et une instance de la classe n'est jamais créée. Dans cet article, je parlerai de :

  • il n'offre aucun avantage par rapport à la programmation procédurale
  • n'abandonnez pas les objets
  • présence de membres de classe statiques = mort aux tests !
Bien que cet article concerne PHP, les concepts s'appliquent également à d'autres langages.

Dépendances

En règle générale, le code dépend d'un autre code. Par exemple:

$foo = substr($bar, 42);
Ce code dépend de la variable $bar et de la fonction substr. $bar est juste une variable locale définie un peu plus haut dans le même fichier et dans la même portée. substr est une fonction principale de PHP. Tout est simple ici.

Maintenant, cet exemple :

Classe BloomFilter ( ... fonction publique __construct($m, $k) ( ... ) fonction statique publique getK($m, $n) ( return ceil(($m / $n) * log(2)); ) ... )
Cette petite fonction d'assistance fournit simplement un wrapper pour un algorithme spécifique qui permet de calculer un bon nombre pour l'argument $k utilisé dans le constructeur. Parce que elle doit être appelée avant la création de l'instance de classe, elle doit être statique. Cet algorithme n'a aucune dépendance externe et il est peu probable qu'il soit remplacé. On l'utilise ainsi :

m$ = 10 000 ; $n = 2 000 ; $b = nouveau BloomFilter($m, BloomFilter::getK($m, $n));
Cela ne crée aucune dépendance supplémentaire. La classe dépend d'elle-même.

  • Constructeur alternatif. Un bon exemple est la classe DateTime intégrée à PHP. Son instance peut être créée par deux différentes façons:

    $date = nouveau DateHeure("04/11/2012"); $date = DateTime::createFromFormat("d-m-Y", "04-11-2012");
    Dans les deux cas, le résultat sera une instance DateTime, et dans les deux cas, le code est lié à la classe DateTime d'une manière ou d'une autre. La méthode statique DateTime::createFromFormat est un constructeur d'objet alternatif qui renvoie la même chose que new DateTime , mais avec des fonctionnalités supplémentaires. Là où vous pouvez écrire un nouveau Class , vous pouvez également écrire Class::method() . Cela ne crée aucune nouvelle dépendance.

  • D'autres utilisations de méthodes statiques affectent la liaison et peuvent créer des dépendances implicites.

    Un mot sur l'abstraction

    Pourquoi toute cette agitation autour des addictions ? La capacité d'abstraction ! À mesure que votre produit grandit, sa complexité augmente. Et l’abstraction est la clé pour gérer la complexité.

    Par exemple, vous disposez d’une classe Application qui représente votre application. Il communique avec la classe User, qui est une représentation de l'utilisateur. Qui reçoit les données de la base de données. La classe Database a besoin d’un DatabaseDriver. DatabaseDriver a besoin de paramètres de connexion. Et ainsi de suite. Si vous appelez simplement Application::start() de manière statique, qui appellera User::getData() de manière statique, qui appellera la base de données de manière statique, et ainsi de suite, et espérez que chaque couche triera ses dépendances, vous pouvez vous retrouver avec un terrible gâchis si quelque chose ne va pas de cette façon. Il est impossible de deviner si l'appel de Application::start() fonctionnera car le comportement des dépendances internes n'est pas du tout évident. Pire encore, la seule façon d'influencer le comportement de Application::start() est de changer source cette classe et le code des classes qu'elle appelle et le code des classes qui appellent ces classes... dans la maison que Jack a construite.

    L'approche la plus efficace lors de la création d'applications complexes consiste à créer des parties distinctes sur lesquelles vous pourrez vous appuyer ultérieurement. Des pièces auxquelles vous pouvez arrêter de penser, dans lesquelles vous pouvez avoir confiance. Par exemple, lors de l'appel d'un Database::fetchAll(...) statique, il n'y a aucune garantie qu'une connexion à la base de données a déjà été établie ou sera établie.

    Fonction(Base de données $base de données) ( ... )
    Si le code à l'intérieur de cette fonction est exécuté, cela signifie que l'instance de base de données a été transmise avec succès, ce qui signifie que l'instance d'objet de base de données a été créée avec succès. Si la classe Database est conçue correctement, vous pouvez être sûr que la présence d'une instance de cette classe signifie la possibilité d'effectuer des requêtes de base de données. S'il n'y a pas d'instance de la classe, le corps de la fonction ne sera pas exécuté. Cela signifie que la fonction ne doit pas se soucier de l’état de la base de données ; la classe Database le fera elle-même. Cette approche vous permet d'oublier les dépendances et de vous concentrer sur la résolution des problèmes.

    Sans la capacité de ne pas penser aux dépendances et aux dépendances de ces dépendances, il est presque impossible d’écrire une application complexe. La base de données peut être une petite classe wrapper ou un monstre géant multicouche avec un tas de dépendances, elle peut commencer comme un petit wrapper et se transformer en un monstre géant au fil du temps, vous pouvez hériter de la classe Database et la transmettre à une fonction descendante. , rien de tout cela n'a d'importance pour votre fonction (Database $ database), tant que l'interface publique de la Database ne change pas. Si vos classes sont correctement séparées du reste de l'application à l'aide de l'injection de dépendances, vous pouvez tester chacune d'elles à l'aide de stubs au lieu de leurs dépendances. Une fois que vous avez suffisamment testé la classe pour vous assurer qu'elle fonctionne comme prévu, vous pouvez éliminer les incertitudes simplement en sachant que vous devez utiliser une instance de base de données pour travailler avec la base de données.

    La programmation orientée classe est stupide. Apprenez à utiliser la POO.

    Partager