¿Por qué PHP 5.2+ no permite los métodos abstractos de clase estática?


121

Después de habilitar advertencias estrictas en PHP 5.2, vi una carga de advertencias de estándares estrictos de un proyecto que se escribió originalmente sin advertencias estrictas:

Estándares estrictos : Función estática Program :: getSelectSQL () no debe ser abstracto en Program.class.inc

La función en cuestión pertenece a un programa de clase primaria abstracta y se declara estática abstracta porque debe implementarse en sus clases secundarias, como TVProgram.

Encontré referencias a este cambio aquí :

Funciones de clase estáticas abstractas descartadas. Debido a un descuido, PHP 5.0.xy 5.1.x permitieron funciones estáticas abstractas en las clases. A partir de PHP 5.2.x, solo las interfaces pueden tenerlos.

Mi pregunta es: ¿alguien puede explicar de manera clara por qué no debería haber una función estática abstracta en PHP?


12
Los nuevos lectores deben tener en cuenta que esta restricción irracional se ha eliminado en PHP 7.
Mark Amery

Respuestas:


76

Los métodos estáticos pertenecen a la clase que los declaró. Al extender la clase, puede crear un método estático con el mismo nombre, pero de hecho no está implementando un método abstracto estático.

Lo mismo aplica para extender cualquier clase con métodos estáticos. Si extiende esa clase y crea un método estático de la misma firma, en realidad no está anulando el método estático de la superclase

EDITAR (16 de septiembre de 2009)
Actualización sobre esto. Al ejecutar PHP 5.3, veo que la estática abstracta está de vuelta, para bien o para mal. (ver http://php.net/lsb para más información)

CORRECCIÓN (por philfreo)
abstract statictodavía no está permitido en PHP 5.3, LSB está relacionado pero es diferente.


3
Bien, ¿y si quisiera imponer la necesidad de la función getSelectSQL () en todos los niños que extienden mi clase abstracta? getSelectSQL () en la clase padre no tiene una razón válida para existir. ¿Cuál es el mejor plan de acción? La razón por la que elegí la estática abstracta es que el código no se compilaría hasta que haya implementado getSelectSQL () en todos los niños.
Artem Russakovskii

1
Lo más probable es que deba rediseñar las cosas para que getSelectSQL () sea un resumen / instancia / método. De esa manera, las instancias / de cada niño tendrán dicho método.
Matthew Flaschen

77
La estática abstracta aún no está permitida en PHP 5.3. Los enlaces estáticos tardíos no tienen nada que ver con eso. Ver también stackoverflow.com/questions/2859633
Artefacto

40
En mi opinión, esta advertencia estricta es simplemente estúpida ya que PHP tiene un "enlace estático tardío", que naturalmente da la idea de usar métodos estáticos como si la clase misma hubiera sido un objeto (como, por ejemplo, en ruby). Lo que conduce a la sobrecarga del método estático y abstract staticpuede ser útil en este caso.
dmitry

44
Esta respuesta es vagamente incorrecta. "todavía no está permitido" solo significa que recibirá una advertencia de nivel E_STRICT, al menos en 5.3+ es perfectamente bienvenido a crear funciones estáticas abstractas, implementarlas en clases extendidas y luego consultarlas a través de la palabra clave static ::. Obviamente, la versión estática de la clase padre todavía está allí y no se puede llamar directamente (a través de self :: o static :: dentro de esa clase) ya que es abstracta y producirá un error fatal como si llamaras a una función abstracta no estática regular. Funcionalmente, esto es útil, estoy de acuerdo con los sentimientos de @dmitry en ese sentido.
ahoffner

79

Es una historia larga y triste.

Cuando PHP 5.2 introdujo esta advertencia por primera vez, los enlaces estáticos tardíos aún no estaban en el lenguaje. En caso de que no esté familiarizado con los enlaces estáticos tardíos, tenga en cuenta que un código como este no funciona de la manera esperada:

<?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();

Dejando de lado la advertencia de modo estricto, el código anterior no funciona. La self::bar()llamada foo()se refiere explícitamente al bar()método de ParentClass, incluso cuando foo()se llama como método de ChildClass. Si intenta ejecutar este código con el modo estricto desactivado, verá " Error fatal de PHP: No se puede llamar al método abstracto ParentClass :: bar () ".

Dado esto, los métodos estáticos abstractos en PHP 5.2 eran inútiles. El punto principal de usar un método abstracto es que puede escribir código que llame al método sin saber a qué implementación va a llamar, y luego proporcionar diferentes implementaciones en diferentes clases secundarias. Pero dado que PHP 5.2 no ofrece una forma limpia de escribir un método de una clase primaria que llame a un método estático de la clase secundaria en la que se llama, este uso de métodos estáticos abstractos no es posible. Por lo tanto, cualquier uso de abstract staticPHP 5.2 es un código incorrecto, probablemente inspirado por un malentendido sobre cómo funciona la selfpalabra clave. Era completamente razonable lanzar una advertencia sobre esto.

Pero luego llegó PHP 5.3 agregado en la capacidad de referirse a la clase en la que se llamó a un método a través de la staticpalabra clave (a diferencia de la selfpalabra clave, que siempre se refiere a la clase en la que se definió el método ). Si cambia self::bar()a static::bar()mi ejemplo anterior, funciona bien en PHP 5.3 y superior. Puede leer más sobre selfvs staticen New self vs. new static .

Con la palabra clave estática agregada, el argumento claro para haber abstract staticlanzado una advertencia había desaparecido. El objetivo principal de los enlaces estáticos tardíos era permitir que los métodos definidos en una clase primaria llamaran a métodos estáticos que se definirían en clases secundarias; permitir métodos estáticos abstractos parece razonable y consistente dada la existencia de enlaces estáticos tardíos.

Todavía podría, supongo, hacer un caso para mantener la advertencia. Por ejemplo, podría argumentar que dado que PHP le permite llamar a métodos estáticos de clases abstractas, en mi ejemplo anterior (incluso después de arreglarlo reemplazando selfcon static) está exponiendo un método público ParentClass::foo()que está roto y que realmente no desea exponer. El uso de una clase no estática, es decir, convertir todos los métodos en métodos de instancia y hacer que los hijos de ParentClasstodos sean solteros o algo así, resolvería este problema, ya que ParentClass, al ser abstracto, no se puede instanciar y, por lo tanto, sus métodos de instancia no pueden ser llamado. Creo que este argumento es débil (porque creo que exponerParentClass::foo() no es un gran problema y el uso de singletons en lugar de clases estáticas a menudo es innecesariamente detallado y feo), pero es posible que esté razonablemente en desacuerdo: es una llamada algo subjetiva.

Entonces, según este argumento, los desarrolladores de PHP mantuvieron la advertencia en el lenguaje, ¿verdad?

Uh, no exactamente .

El informe de error de PHP 53081, vinculado anteriormente, solicitó que se eliminara la advertencia ya que la adición de la static::foo()construcción había hecho que los métodos estáticos abstractos fueran razonables y útiles. Rasmus Lerdorf (creador de PHP) comienza etiquetando la solicitud como falsa y pasa por una larga cadena de mal razonamiento para tratar de justificar la advertencia. Entonces, finalmente, este intercambio tiene lugar:

Giorgio

lo sé pero:

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

Correcto, así es exactamente como debería funcionar.

Giorgio

pero no está permitido :(

Rasmus

¿Qué no está permitido?

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

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

cB::A();

Esto funciona bien Obviamente no puedes llamar self :: B (), pero static :: B () está bien.

La afirmación de Rasmus de que el código en su ejemplo "funciona bien" es falsa; como saben, arroja una advertencia de modo estricto. Supongo que estaba probando sin el modo estricto activado. En cualquier caso, un confundido Rasmus dejó la solicitud cerrada erróneamente como "falsa".

Y es por eso que la advertencia todavía está en el idioma. Puede que esta no sea una explicación completamente satisfactoria: probablemente haya venido aquí esperando que haya una justificación racional de la advertencia. Desafortunadamente, en el mundo real, a veces las elecciones nacen de errores mundanos y malos razonamientos, más que de la toma racional de decisiones. Este es simplemente uno de esos momentos.

Afortunadamente, la estimable Nikita Popov ha eliminado la advertencia del lenguaje en PHP 7 como parte de PHP RFC: Reclasifique los avisos E_STRICT . En última instancia, la cordura ha prevalecido, y una vez que se lanza PHP 7, todos podemos usarlo felizmente abstract staticsin recibir esta advertencia tonta.


70

Hay una solución muy simple para este problema, que en realidad tiene sentido desde el punto de vista del diseño. Como Jonathan escribió:

Lo mismo aplica para extender cualquier clase con métodos estáticos. Si extiende esa clase y crea un método estático de la misma firma, en realidad no está anulando el método estático de la superclase

Entonces, como una solución alternativa, podría hacer esto:

<?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();
}
?>

Y ahora hace cumplir que cualquier clase que subclasifique MyFoo implemente un método estático getInstance y un método getSomeData público. Y si no subclasifica MyFoo, aún puede implementar iMyFoo para crear una clase con una funcionalidad similar.


2
¿Es posible con este patrón proteger la función ? Cuando lo hago, significa que las clases que extienden MyFoo lanzan advertencias que getInstance deben ser públicas. Y no se puede poner protegido en una definición de interfaz.
artfulrobot

3
Algunas veces static::pueden ser útiles.
visto el

3
No funciona con rasgos. Si solo los Rasgos pudieran tener abstract staticmétodos, sin PHP bitching ...
Rudie

1
El uso de una interfaz es probablemente la mejor solución aquí. +1.
Juan Carlos Coto

Esto es realmente muy elegante debido a su simple simplicidad. +1
G. Stewart

12

Sé que esto es viejo pero ...

¿Por qué no simplemente lanzar una excepción al método estático de esa clase padre, de esa manera, si no lo anula, se produce la excepción?


1
Eso no ayuda, la excepción sucedería al llamar al método estático; al mismo tiempo, aparecerá un error de 'método no existe' si no lo anula.
BT

3
@BT quise decir, no declare el método abstracto, impleméntelo, solo arroje una excepción cuando se llame, lo que significa que no se lanzará si se ha anulado.
Petah

Esta parece ser la solución más elegante.
Alex S

Es mejor ver algo como esto en tiempo de compilación, que en tiempo de ejecución. Es más fácil encontrar el problema antes de que estén en producción porque solo tiene que cargar el archivo, no ejecutar el código para determinar si es incorrecto o no conforme
Rahly

4

Yo diría que una clase / interfaz abstracta podría verse como un contrato entre programadores. Trata más sobre cómo deberían verse / comportarse las cosas y no implementar la funcionalidad real. Como se ve en php5.0 y 5.1.x, no es una ley natural que impida que los desarrolladores de php lo hagan, sino la necesidad de seguir otros patrones de diseño OO en otros idiomas. Básicamente, estas ideas intentan evitar comportamientos inesperados, si uno ya está familiarizado con otros idiomas.


Aunque no está relacionado con PHP aquí hay otra buena explicación: stackoverflow.com/questions/3284/…
merkuro

3
¡Dios mío! ¡Las dos personas que te rechazaron están totalmente locas! Esta es la respuesta más perspicaz en este hilo.
Theodore R. Smith

55
@ TheodoreR.Smith perspicaz? Contiene errores y es apenas coherente. La afirmación de que las clases abstractas "no implementan la funcionalidad real" no es necesariamente cierta, y ese es el punto de que existan además de las interfaces. En la afirmación de que "no es una ley natural que impide que los desarrolladores de php lo hagan" , no tengo idea de qué es "eso" . Y en la afirmación de que "Básicamente estas ideas intentan evitar un comportamiento inesperado" , no tengo idea de cuáles son "estas ideas" o el potencial "comportamiento inesperado". Cualquier idea que hayas extraído de esto, se me ha perdido.
Mark Amery

2

No veo ninguna razón para prohibir las funciones abstractas estáticas. El mejor argumento de que no hay razón para prohibirlos es que están permitidos en Java. Las preguntas son: - ¿Son técnicamente factibles? - Sí, ya que existía en PHP 5.2 y existen en Java. Entonces, ¿cuándo PUEDE hacerlo? ¿DEBEMOS hacerlo? - ¿Tienen sentido? Si. Tiene sentido implementar una parte de una clase y dejar otra parte de una clase al usuario. Tiene sentido en funciones no estáticas, ¿por qué no debería tener sentido para las funciones estáticas? Un uso de las funciones estáticas son las clases donde no debe haber más de una instancia (singletons). Por ejemplo, un motor de cifrado. No es necesario que exista en varios casos y existen razones para evitarlo; por ejemplo, debe proteger solo una parte de la memoria contra intrusos. Por lo tanto, tiene mucho sentido implementar una parte del motor y dejar el algoritmo de cifrado al usuario. Este es sólo un ejemplo. Si está acostumbrado a usar funciones estáticas, encontrará muchas más.


3
Su punto sobre los métodos estáticos abstractos existentes en 5.2 es muy engañoso. En primer lugar, la advertencia de modo estricto que los prohibía se introdujo en 5.2; lo haces sonar como en 5.2 se les permitió. En segundo lugar, en 5.2 no podían usarse fácilmente "para implementar una parte de una clase y dejar otra parte de una clase para el usuario" porque aún no existían enlaces estáticos tardíos.
Mark Amery

0

En php 5.4+ use rasgo:

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

y en tu clase pon al principio:

use StaticExample;

1
¡Pude poner abstract public static function get_table_name();un rasgo y usar ese rasgo dentro de mi clase abstracta sin más advertencias E_STRICT! Esto todavía impuso la definición del método estático en los niños como esperaba. ¡Fantástico!
Programador

-1

Examine los problemas de 'Enlace estático tardío' de PHP. Si está poniendo métodos estáticos en clases abstractas, probablemente lo encontrará más pronto que tarde. Tiene sentido que las advertencias estrictas le indiquen que evite usar funciones de lenguaje roto.


44
Creo que se refiere a las advertencias de "Normas estrictas".
Jacob Hume

2
¿Qué "problemas" afirma que tienen los enlaces estáticos tardíos? Usted afirma que están "rotos", lo cual es un reclamo audaz, hecho aquí sin evidencia o explicación. La función siempre ha funcionado bien para mí, y creo que esta publicación no tiene sentido.
Mark Amery
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.