2010-10-18 13 views
37

Soy bastante nuevo en PHP, pero he estado programando en idiomas similares durante años. Estaba desconcertado por el siguiente:¿Por qué los atributos de PHP no permiten funciones?

class Foo { 
    public $path = array(
     realpath(".") 
    ); 
} 

Se produjo un error de sintaxis: Parse error: syntax error, unexpected '(', expecting ')' in test.php on line 5 que es la llamada realpath.

Pero esto funciona bien:

$path = array(
    realpath(".") 
); 

Después de golpearse la cabeza contra esto durante un tiempo, me dijeron que no se puede llamar a funciones en un atributo por defecto; tienes que hacerlo en __construct. Mi pregunta es: ¿por qué? ¿Es esto una "característica" o implementación descuidada? ¿Cuál es el razonamiento?

+30

@Gordon Por favor, mejore las respuestas. – Schwern

+5

@Schwern Por favor, mejore las preguntas. – Gordon

+0

Para ser claro, acepto cuando la pregunta es definitivamente respondida. Hasta ahora, todas las respuestas son especulaciones. Útil, pero aún especulación. – Schwern

Respuesta

48

El compilador de código sugiere que esto es por diseño, aunque yo no' Sé cuál es el razonamiento oficial detrás de eso. Tampoco estoy seguro de cuánto esfuerzo se necesitaría para implementar de manera confiable esta funcionalidad, pero definitivamente existen algunas limitaciones en la forma en que se hacen las cosas actualmente.

Aunque mi conocimiento del compilador de PHP no es extenso, intentaré e ilustraré lo que creo que ocurre para que pueda ver dónde hay un problema. Su ejemplo de código hace un buen candidato para este proceso, por lo que vamos a utilizar lo siguiente:

class Foo { 
    public $path = array(
     realpath(".") 
    ); 
} 

Como bien sabe, esto provoca un error de sintaxis. Este es el resultado de la PHP grammar, lo que hace la siguiente definición pertinente:

class_variable_declaration: 
     //... 
     | T_VARIABLE '=' static_scalar //... 
; 

Así, al definir los valores de variables tales como $path, el valor esperado debe coincidir con la definición de un escalar estática.Como era de esperar, esto es algo de un nombre inapropiado ya que la definición de un escalar estática también incluye tipos de matriz cuyos valores son también escalares estáticas:

static_scalar: /* compile-time evaluated scalars */ 
     //... 
     | T_ARRAY '(' static_array_pair_list ')' // ... 
     //... 
; 

Supongamos por un segundo que la gramática era diferente, y la línea se indica en la regla delcaration variable de clase parecía algo más parecido a lo siguiente, que se correspondería con el ejemplo de código (a pesar de dividir las tareas de otro modo válidos):

class_variable_declaration: 
     //... 
     | T_VARIABLE '=' T_ARRAY '(' array_pair_list ')' // ... 
; 

Después de recompilar PHP, el script de ejemplo ya no fallar con el error de sintaxis. En cambio, fallaría con el error de tiempo de compilación "Tipo de enlace no válido". Como el código ahora es válido en función de la gramática, esto indica que realmente hay algo específico en el diseño del compilador que está causando problemas. Para descubrir qué es eso, volvamos a la gramática original por un momento e imaginemos que el ejemplo de código tenía una asignación válida de $path = array(2);.

Usando la gramática como guía, es posible recorrer las acciones invocadas en el compiler code al analizar este ejemplo de código. He dejado algunas partes menos importantes, pero el proceso es como la siguiente:

// ... 
// Begins the class declaration 
zend_do_begin_class_declaration(znode, "Foo", znode); 
    // Set some modifiers on the current znode... 
    // ... 
    // Create the array 
    array_init(znode); 
    // Add the value we specified 
    zend_do_add_static_array_element(znode, NULL, 2); 
    // Declare the property as a member of the class 
    zend_do_declare_property('$path', znode); 
// End the class declaration 
zend_do_end_class_declaration(znode, "Foo"); 
// ... 
zend_do_early_binding(); 
// ... 
zend_do_end_compilation(); 

Mientras que el compilador hace mucho en estos diversos métodos, es importante tener en cuenta algunas cosas.

  1. Una llamada al zend_do_begin_class_declaration() da como resultado una llamada al get_next_op(). Esto significa que agrega un nuevo código de operación al conjunto de códigos de operación actual.
  2. array_init() y zend_do_add_static_array_element() no genera nuevos códigos de operación. En cambio, la matriz se crea inmediatamente y se agrega a la tabla de propiedades de la clase actual. Las declaraciones de métodos funcionan de manera similar, a través de un caso especial en zend_do_begin_function_declaration().
  3. zend_do_early_binding()consume el último código de operación de la matriz de código de operación actual, la comprobación de uno de los siguientes tipos antes de dejarla a un NOP:
    • ZEND_DECLARE_FUNCTION
    • ZEND_DECLARE_CLASS
    • ZEND_DECLARE_INHERITED_CLASS
    • ZEND_VERIFY_ABSTRACT_CLASS
    • ZEND_ADD_INTERFACE

Tenga en cuenta que en el último caso, si el tipo de código de operación no es uno de los tipos esperados, se emite un error – El "tipo de enlace no válido" error. A partir de esto, podemos decir que permitir que los valores no estáticos se asignen de alguna manera hace que el último código de operación sea diferente de lo esperado. Entonces, ¿qué sucede cuando utilizamos una matriz no estática con la gramática modificada?

En lugar de llamar al array_init(), el compilador prepara los argumentos y llama al zend_do_init_array(). Esto a su vez llama get_next_op() y añade una nueva INIT_ARRAY opcode, produciendo algo como lo siguiente:

DECLARE_CLASS 'Foo' 
SEND_VAL  '.' 
DO_FCALL  'realpath' 
INIT_ARRAY 

Aquí reside la raíz del problema.Al agregar estos códigos de operación, zend_do_early_binding() recibe una entrada inesperada y arroja una excepción. Como el proceso de las definiciones de clases y funciones vinculantes tempranas parece bastante integral para el proceso de compilación de PHP, no puede simplemente ignorarse (aunque la producción/consumo de DECLARE_CLASS es un tanto desordenada). Del mismo modo, no es práctico intentar evaluar estos códigos de operación adicionales en línea (no se puede estar seguro de que una determinada función o clase se haya resuelto todavía), por lo que no hay forma de evitar generar los códigos de operación.

Una posible solución sería construir una nueva matriz de código de operación que tuviera como ámbito la declaración de la variable de clase, similar a cómo se manejan las definiciones de los métodos. El problema al hacerlo es decidir cuándo evaluar una secuencia de ejecución única. ¿Se realizará cuando se cargue el archivo que contiene la clase, cuando se acceda a la propiedad por primera vez o cuando se construya un objeto de ese tipo?

Como ha señalado, otros lenguajes dinámicos han encontrado una manera de manejar este escenario, por lo que no es imposible tomar esa decisión y ponerla en práctica. Sin embargo, por lo que puedo decir, hacerlo en el caso de PHP no sería una corrección de una línea, y los diseñadores del lenguaje parecen haber decidido que no era algo valioso incluir en este punto.

+0

¡Gracias! La respuesta a cuándo evaluar señala la falla obvia en la sintaxis por defecto del atributo de PHP: no debería poder asignarle nada, debe establecerse en el constructor del objeto. Ambigüedad resuelta. (¿Los objetos intentan compartir esa constante?) En cuanto a los atributos estáticos, no hay ambigüedad y se les podría permitir cualquier expresión. Así es como Ruby lo hace. Sospecho que no eliminaron los valores predeterminados attrib de objeto porque, al carecer de un constructor de clase, no hay una buena manera de establecer una clase attrib. Y no querían tener asignaciones separadas para los valores predeterminados attrib del objeto frente a la clase. – Schwern

+0

@Schwern: ¡Feliz de ayudar! Esto es algo de lo que tenía curiosidad en el pasado, pero nunca pensé en revisarlo en detalle, así que esta era una buena oportunidad para descubrir qué estaba pasando exactamente. Con respecto a la tarea, permitir este tipo de tarea evita forzarte a crear un constructor si no "necesitas" ... lo que creo que sería una justificación terrible, aunque en el caso de PHP, no es una sorpresa. . Creo que cada instancia replicará los valores de propiedad predeterminados en la creación, pero podría estar equivocado, por lo que es posible que intenten compartir. –

+0

En cualquier caso, los ahorros obtenidos al hacerlo (dados los datos limitados que puede asignar en primer lugar) serían mínimos, así que no estoy seguro de que valdría la pena tener esta configuración. En cuanto a tus comentarios sobre resolver la ambigüedad, me inclino a estar de acuerdo. –

19

My question is: why?! Is this a "feature" or sloppy implementation?

Yo diría que es definitivamente una característica. Una definición de clase es un modelo de código, y no se supone que ejecute código en el momento de su definición. Rompería la abstracción y la encapsulación del objeto.

Sin embargo, esta es solo mi opinión. No puedo decir con certeza qué idea tenían los desarrolladores al definir esto.

+5

+1 Acepto, por ejemplo, si digo: 'public $ foo = mktime()' ¿ahorrará el tiempo desde que la clase está analizado, construido o cuando se intentó acceder a estática. – Hannes

+1

Como se mencionó, no se define cuándo se evaluará la expresión. Sin embargo, debería poder asignar un cierre a un atributo, lo que podría devolver el tiempo sin ambigüedad, pero también produce un error de sintaxis. – erisco

+5

¿Es un poco de diseño de lenguaje BDSM en un lenguaje por lo demás muy permisivo e implementado como un error de sintaxis? – Schwern

4

Es una implementación de analizador descuidado. No tengo la terminología correcta para describirlo (creo que el término "reducción beta" se ajusta de alguna manera ...), pero el analizador de lenguaje PHP es más complejo y más complicado de lo que necesita ser, y así todo tipo de Se requiere una carcasa especial para diferentes construcciones de lenguaje.

+0

¿Hay otros idiomas que lo permitan? Tengo curiosidad porque honestamente no lo sé. Si mal no recuerdo, Pascal/Delphi no. –

+2

@Pekka: Los lenguajes estáticos generalmente no, ya que una clase en ellos casi siempre es solo una construcción de compilador. Pero con los lenguajes dinámicos, la clase se crea cuando se ejecuta la definición, por lo que no hay ninguna razón por la que no puedan usar el valor de retorno de la función en ese momento como el valor para el atributo. –

+0

@Ignacio aplausos. Bien, eso es verdad Todavía creo que es algo bueno en general, porque impone buenos principios de POO. –

2

Supongo que no podrá tener un seguimiento de pila correcto si el error no ocurre en una línea ejecutable ... Dado que no puede haber ningún error al inicializar valores con constantes, no hay problema con eso, pero la función puede arrojar excepciones/errores y debe llamarse dentro de una línea ejecutable, y no declarativa.

6

Probablemente puede lograr algo similar como esto:

class Foo 
{ 
    public $path = __DIR__; 
} 

IIRC __DIR__ necesidades php 5.3+, __FILE__ ha sido por más tiempo

+0

Buen punto. Esto funciona porque es una constante mágica y se reemplazará en el momento del análisis –

+3

Gracias, pero el ejemplo fue solo para ilustración. – Schwern

Cuestiones relacionadas