Pourquoi PHP 5.2+ interdit-il les méthodes de classe statiques abstraites?

121

Après avoir activé les avertissements stricts dans PHP 5.2, j'ai vu une foule d'avertissements de normes strictes d'un projet qui a été initialement écrit sans avertissements stricts:

Standards stricts : la fonction statique Program :: getSelectSQL () ne doit pas être abstraite dans Program.class.inc

La fonction en question appartient à une classe parent abstraite Program et est déclarée statique abstraite car elle doit être implémentée dans ses classes enfants, telles que TVProgram.

J'ai trouvé des références à ce changement ici :

Suppression des fonctions de classe statique abstraite. En raison d'un oubli, PHP 5.0.x et 5.1.x ont autorisé les fonctions statiques abstraites dans les classes. Depuis PHP 5.2.x, seules les interfaces peuvent les avoir.

Ma question est la suivante: quelqu'un peut-il expliquer clairement pourquoi il ne devrait pas y avoir de fonction statique abstraite en PHP?

Artem Russakovskii
la source
12
Les nouveaux lecteurs devraient noter que cette restriction irrationnelle a été supprimée en PHP 7.
Mark Amery

Réponses:

76

les méthodes statiques appartiennent à la classe qui les a déclarées. Lors de l'extension de la classe, vous pouvez créer une méthode statique du même nom, mais vous n'implémentez pas en fait une méthode abstraite statique.

Il en va de même pour l'extension de toute classe avec des méthodes statiques. Si vous étendez cette classe et créez une méthode statique de la même signature, vous ne remplacez pas réellement la méthode statique de la superclasse

EDIT (16 septembre 2009)
Mise à jour à ce sujet. En exécutant PHP 5.3, je vois que la statique abstraite est de retour, pour le meilleur ou pour le pire. (voir http://php.net/lsb pour plus d'informations)

La CORRECTION (par philfreo)
abstract staticn'est toujours pas autorisée en PHP 5.3, le LSB est lié mais différent.

Jonathan Fingland
la source
3
OK, alors que faire si je voulais imposer la nécessité de la fonction getSelectSQL () dans tous les enfants qui étendent ma classe abstraite? getSelectSQL () dans la classe parente n'a aucune raison valable d'exister. Quel est le meilleur plan d'action? La raison pour laquelle j'ai choisi la statique abstraite est que le code ne se compilerait pas tant que j'aurais implémenté getSelectSQL () dans tous les enfants.
Artem Russakovskii
1
Très probablement, vous devriez repenser les choses afin que getSelectSQL () soit un abstract / instance / method. De cette façon, / instances / de chaque enfant aura une telle méthode.
Matthew Flaschen
7
La statique abstraite est toujours interdite dans PHP 5.3. Les liaisons statiques tardives n'ont rien à voir avec cela. Voir aussi stackoverflow.com/questions/2859633
Artefacto
40
À mon avis, cet avertissement strict est tout simplement stupide car PHP a une "liaison statique tardive", ce qui donne naturellement l'idée d'utiliser des méthodes statiques comme si la classe elle-même avait été un objet (comme, par exemple, dans ruby). Ce qui conduit à une surcharge de méthode statique et abstract staticpeut être utile dans ce cas.
dmitry
4
Cette réponse est vaguement fausse. "toujours non autorisé" signifie simplement que vous obtiendrez un avertissement de niveau E_STRICT, au moins dans la version 5.3+, vous êtes parfaitement bienvenu pour créer des fonctions statiques abstraites, les implémenter dans des classes étendues, puis y faire référence via le mot-clé static ::. De toute évidence, la version statique de la classe parent est toujours là et ne peut pas être appelée directement (via self :: ou static :: à l'intérieur de cette classe) car elle est abstraite et provoquera une erreur fatale comme si vous appeliez une fonction abstraite non statique régulière. Fonctionnellement, c'est utile, je suis d'accord avec les sentiments de @dmitry à cet effet.
ahoffner
79

C'est une histoire longue et triste.

Lorsque PHP 5.2 a introduit cet avertissement pour la première fois, les liaisons statiques tardives n'étaient pas encore dans le langage. Si vous n'êtes pas familier avec les liaisons statiques tardives, notez qu'un code comme celui-ci ne fonctionne pas comme prévu:

<?php

abstract class ParentClass {
    static function foo() {
        echo "I'm gonna do bar()";
        self::bar();
    }

    abstract static function bar();
}

class ChildClass extends ParentClass {
    static function bar() {
        echo "Hello, World!";
    }
}

ChildClass::foo();

Laissant de côté l'avertissement de mode strict, le code ci-dessus ne fonctionne pas. L' self::bar()appel en foo()fait explicitement référence à la bar()méthode de ParentClass, même lorsqu'elle foo()est appelée en tant que méthode de ChildClass. Si vous essayez d'exécuter ce code avec le mode strict désactivé, vous verrez " Erreur fatale PHP: impossible d'appeler la méthode abstraite ParentClass :: bar () ".

Compte tenu de cela, les méthodes statiques abstraites de PHP 5.2 étaient inutiles. L' intérêt d'utiliser une méthode abstraite est que vous pouvez écrire du code qui appelle la méthode sans savoir quelle implémentation elle va appeler - puis fournir différentes implémentations sur différentes classes enfants. Mais puisque PHP 5.2 n'offre aucun moyen propre d'écrire une méthode d'une classe parente qui appelle une méthode statique de la classe enfant sur laquelle elle est appelée, cette utilisation de méthodes statiques abstraites n'est pas possible. Par conséquent, toute utilisation de abstract staticPHP 5.2 est un mauvais code, probablement inspiré par une mauvaise compréhension du fonctionnement du selfmot clé. Il était tout à fait raisonnable de jeter un avertissement à ce sujet.

Mais ensuite PHP 5.3 est venu ajouté dans la possibilité de faire référence à la classe sur laquelle une méthode a été appelée via le staticmot - clé (contrairement au selfmot - clé, qui fait toujours référence à la classe dans laquelle la méthode a été définie ). Si vous passez self::bar()à static::bar()dans mon exemple ci-dessus, cela fonctionne bien dans PHP 5.3 et supérieur. Vous pouvez en savoir plus sur selfvs staticà New self vs new static .

Avec le mot clé static ajouté, l'argument clair pour avoir abstract staticlancé un avertissement avait disparu. Le but principal des liaisons statiques tardives était de permettre aux méthodes définies dans une classe parente d'appeler des méthodes statiques qui seraient définies dans des classes enfants; autoriser des méthodes statiques abstraites semble raisonnable et cohérente étant donné l'existence de liaisons statiques tardives.

Vous pourriez encore, je suppose, plaider pour le maintien de l'avertissement. Par exemple, vous pourriez soutenir que puisque PHP vous permet d'appeler des méthodes statiques de classes abstraites, dans mon exemple ci-dessus (même après l'avoir corrigé en le remplaçant selfpar static) vous exposez une méthode publique ParentClass::foo()qui est cassée et que vous ne voulez pas vraiment exposer. Utiliser une classe non statique - c'est-à-dire faire de toutes les méthodes des méthodes d'instance et faire des enfants de ParentClasstous des singletons ou quelque chose - résoudrait ce problème, car ParentClassétant abstrait, ne peut pas être instancié et donc ses méthodes d'instance ne peuvent pas être appelé. Je pense que cet argument est faible (car je pense qu'exposerParentClass::foo() n'est pas un gros problème et utiliser des singletons au lieu de classes statiques est souvent inutilement verbeux et laid), mais vous pourriez raisonnablement être en désaccord - c'est un appel quelque peu subjectif.

Donc, sur la base de cet argument, les développeurs PHP ont gardé l'avertissement dans le langage, non?

Euh, pas exactement .

Le rapport de bogue PHP 53081, lié ci-dessus, demandait que l'avertissement soit supprimé car l'ajout de la static::foo()construction avait rendu les méthodes statiques abstraites raisonnables et utiles. Rasmus Lerdorf (créateur de PHP) commence par qualifier la demande de fausse et passe par une longue chaîne de mauvais raisonnement pour essayer de justifier l'avertissement. Puis, enfin, cet échange a lieu:

Giorgio

je sais, mais:

abstract class cA
{
      //static function A(){self::B();} error, undefined method
      static function A(){static::B();} // good
      abstract static function B();
}

class cB extends cA
{
    static function B(){echo "ok";}
}

cB::A();

Rasmus

Bon, c'est exactement comme ça que ça devrait fonctionner.

Giorgio

mais ce n'est pas autorisé :(

Rasmus

Qu'est-ce qui n'est pas autorisé?

abstract class cA {
      static function A(){static::B();}
      abstract static function B();
}

class cB extends cA {
    static function B(){echo "ok";}
}

cB::A();

Cela fonctionne très bien. Vous ne pouvez évidemment pas appeler self :: B (), mais static :: B () est bien.

L'affirmation de Rasmus selon laquelle le code de son exemple "fonctionne bien" est fausse; comme vous le savez, il lance un avertissement de mode strict. Je suppose qu'il testait sans le mode strict activé. Quoi qu'il en soit, un Rasmus confus a laissé la demande fermée à tort comme "bidon".

Et c'est pourquoi l'avertissement est toujours dans la langue. Ce n'est peut-être pas une explication entièrement satisfaisante - vous êtes probablement venu ici en espérant qu'il y avait une justification rationnelle de l'avertissement. Malheureusement, dans le monde réel, les choix naissent parfois d'erreurs banales et de mauvais raisonnements plutôt que d'une prise de décision rationnelle. C'est simplement l'un de ces moments.

Heureusement, l'estimable Nikita Popov a supprimé l'avertissement du langage en PHP 7 dans le cadre des notifications PHP RFC: Reclassify E_STRICT . En fin de compte, la raison a prévalu, et une fois que PHP 7 est sorti, nous pouvons tous l'utiliser avec plaisir abstract staticsans recevoir cet avertissement stupide.

Mark Amery
la source
70

Il existe une solution très simple pour résoudre ce problème, ce qui est en fait logique du point de vue de la conception. Comme l'a écrit Jonathan:

Il en va de même pour l'extension de toute classe avec des méthodes statiques. Si vous étendez cette classe et créez une méthode statique de la même signature, vous ne remplacez pas réellement la méthode statique de la superclasse

Donc, pour contourner le problème, vous pouvez le faire:

<?php
abstract class MyFoo implements iMyFoo {

    public static final function factory($type, $someData) {
        // don't forget checking and do whatever else you would
        // like to do inside a factory method
        $class = get_called_class()."_".$type;
        $inst = $class::getInstance($someData);
        return $inst;
    }
}


interface iMyFoo {
    static function factory($type, $someData);
    static function getInstance();
    function getSomeData();
}
?>

Et maintenant vous faites en sorte que toute classe sous-classant MyFoo implémente une méthode statique getInstance et une méthode publique getSomeData. Et si vous ne sous-classez pas MyFoo, vous pouvez toujours implémenter iMyFoo pour créer une classe avec des fonctionnalités similaires.

Wouter Van Vliet
la source
2
Est-il possible avec ce modèle de rendre la fonction protégée . Quand je le fais, cela signifie que les classes qui étendent MyFoo lancent des avertissements que getInstance doit être public. Et vous ne pouvez pas mettre protégé dans une définition d'interface.
artfulrobot
3
certains moments static::peuvent être utiles.
seyed
3
Ne fonctionne pas avec les traits. Si seulement Traits pouvait avoir des abstract staticméthodes, sans râler PHP ...
Rudie
1
L'utilisation d'une interface est probablement la meilleure solution ici. +1.
Juan Carlos Coto
C'est en fait très élégant en raison de sa simplicité. +1
G. Stewart
12

Je sais que c'est vieux mais ...

Pourquoi ne pas simplement lancer une exception à la méthode statique de cette classe parente, de cette façon si vous ne la remplacez pas, l'exception est provoquée.

Petah
la source
1
Cela n'aide pas, l'exception se produirait à l'appel de la méthode statique - en même temps qu'une erreur «méthode n'existe pas» se produirait si vous ne la remplacez pas.
BT
3
@BT Je voulais dire, ne déclarez pas la méthode abstraite, implémentez-la, mais lancez simplement une exception lorsqu'elle est appelée, ce qui signifie qu'elle ne la lancera pas si elle a été remplacée.
Petah
Cela semble être la solution la plus élégante.
Alex S
Mieux vaut voir quelque chose comme ça au moment de la compilation que lors de l'exécution. Il est plus facile de trouver le problème avant qu'il ne soit en production car il vous suffit de charger le fichier, pas d'exécuter du code pour déterminer s'il est mauvais ou non conforme
Rahly
4

Je dirais qu'une classe / interface abstraite pourrait être vue comme un contrat entre programmeurs. Il traite davantage de l'apparence / du comportement des choses et non de la mise en œuvre des fonctionnalités réelles. Comme on le voit dans php5.0 et 5.1.x, ce n'est pas une loi naturelle qui empêche les développeurs de php de le faire, mais l'envie d'aller avec d'autres modèles de conception OO dans d'autres langues. Fondamentalement, ces idées essaient d'éviter un comportement inattendu, si l'on est déjà familier avec d'autres langues.

merkuro
la source
Bien que cela ne soit pas lié à php, voici une autre bonne explication: stackoverflow.com/questions/3284/…
merkuro
3
Mon Dieu! Les deux personnes qui vous ont critiqué sont complètement fous! C'est la réponse la plus perspicace sur ce fil.
Theodore R. Smith
5
@ TheodoreR.Smith Insightful? Il contient des erreurs et est à peine cohérent. L'affirmation selon laquelle les classes abstraites "n'implémentent pas la fonctionnalité réelle" n'est pas nécessairement vraie, et c'est là tout l'intérêt qu'elles existent en plus des interfaces. Dans l'affirmation que "ce n'est pas une loi naturelle qui empêche les développeurs PHP de le faire" , je n'ai aucune idée de ce que "c'est" . Et dans l'affirmation selon laquelle «ces idées essaient essentiellement d'éviter un comportement inattendu» , je n'ai aucune idée de ce que sont «ces idées» ou le «comportement inattendu» potentiel. Quelle que soit la perspicacité que vous en avez tirée, je l'ai perdue.
Mark Amery
2

Je ne vois aucune raison d'interdire les fonctions abstraites statiques. Le meilleur argument selon lequel il n'y a aucune raison de les interdire est qu'ils sont autorisés en Java. Les questions sont les suivantes: - Est-ce techniquement faisable? - Oui, car ils existaient en PHP 5.2 et ils existent en Java. Alors, où PEUT le faire. Devrions-nous le faire? - Ont-ils un sens? Oui. Il est logique d'implémenter une partie d'une classe et de laisser une autre partie d'une classe à l'utilisateur. Cela a du sens dans les fonctions non statiques, pourquoi cela n'aurait-il pas de sens pour les fonctions statiques? Une utilisation des fonctions statiques sont les classes où il ne doit pas y avoir plus d'une instance (singletons). Par exemple, un moteur de chiffrement. Il n'a pas besoin d'exister dans plusieurs instances et il y a des raisons de l'empêcher - par exemple, vous ne devez protéger qu'une partie de la mémoire contre les intrus. Il est donc parfaitement logique d'implémenter une partie du moteur et de laisser l'algorithme de cryptage à l'utilisateur. Ceci n'est qu'un exemple. Si vous êtes habitué à utiliser des fonctions statiques, vous en trouverez beaucoup plus.

user2964021
la source
3
Votre point sur les méthodes statiques abstraites existant dans la version 5.2 est très trompeur. Premièrement, l'avertissement de mode strict les interdisant a été introduit dans la version 5.2; vous donnez l'impression qu'en 5.2, ils étaient autorisés. Deuxièmement, dans la version 5.2, ils ne pouvaient pas être facilement utilisés "pour implémenter une partie d'une classe et laisser une autre partie d'une classe à l'utilisateur" car les liaisons statiques tardives n'existaient pas encore.
Mark Amery
0

En php 5.4+, utilisez le trait:

trait StaticExample {
    public static function instance () {
    return new self;
    }
}

et dans votre classe, mettez au début:

use StaticExample;
kamil
la source
1
J'ai pu insérer abstract public static function get_table_name();un trait et l'utiliser dans ma classe abstraite sans plus d'avertissements E_STRICT! Cela imposait toujours la définition de la méthode statique chez les enfants comme je l'avais espéré. Fantastique!
Programster
-1

Examinez les problèmes de «liaison statique tardive» de PHP. Si vous mettez des méthodes statiques sur des classes abstraites, vous allez probablement y tomber plus tôt que tard. Il est logique que les avertissements stricts vous indiquent d'éviter d'utiliser des fonctionnalités de langage brisées.

Sean McSomething
la source
4
Je pense qu'il veut dire les avertissements «Strict Standards».
Jacob Hume
2
Quels "problèmes" prétendez-vous que les liaisons statiques tardives ont? Vous affirmez qu'ils sont "cassés", ce qui est une affirmation audacieuse, faite ici sans preuve ni explication. La fonctionnalité a toujours bien fonctionné pour moi, et je pense que cet article est absurde.
Mark Amery