43 votes

Pourquoi les attributs PHP n'autorisent-ils pas les fonctions?

Je suis assez nouveau à PHP, mais j'ai été la programmation en langues similaires depuis des années. J'étais sidéré par ce qui suit:

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

Il produit une erreur de syntaxe: Parse error: syntax error, unexpected '(', expecting ')' in test.php on line 5 qui est l' realpath appel.

Mais cela fonctionne très bien:

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

Après cogner ma tête contre ça pendant un moment, m'a dit que vous ne pouvez pas appeler des fonctions dans un attribut par défaut; vous devez le faire dans __construct. Ma question est: pourquoi?! Est-ce une "fonction" ou bâclée mise en œuvre? Quelle est la justification?

54voto

Tim Stone Points 13966

Le compilateur de code suggère que c'est par la conception, mais je ne sais pas ce que le fonctionnaire raisonnement derrière cela est. Je suis également pas sûr de combien d'efforts il faudrait fiable pour mettre en œuvre cette fonctionnalité, mais il y a certainement des limites dans la façon dont les choses sont actuellement fait.

Bien que mes connaissances en PHP compilateur n'est pas vaste, je vais essayer d'illustrer ce que je crois va de sorte que vous pouvez voir où il y a un problème. Votre exemple de code qui fait un bon candidat pour ce processus, de sorte que nous allons utiliser que:

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

Comme vous le savez, cela provoque une erreur de syntaxe. C'est un résultat de l' PHP grammaire, ce qui rend la suite de définition pertinente:

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

Ainsi, lors de la définition des valeurs de variables telles que l' $path, la valeur doit correspondre à la définition d'une statique scalaire. Sans surprise, c'est un peu un abus de langage donné que la définition de la statique scalaire comprend également les types de tableau dont les valeurs sont aussi statique scalaires:

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

Imaginons une seconde que la grammaire est différente, et la ligne de la variable de classe delcaration de la règle cherché quelque chose de plus, comme les suivants qui correspondent à vos exemple de code (en dépit de la rupture valide affectations):

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

Après recompilation de PHP, le script d'exemple, ne serait plus échouer avec une erreur de syntaxe. Au lieu de cela, il échouera avec l'erreur de compilation "Invalid type de liaison". Depuis que le code est maintenant valide en fonction de la grammaire, cela indique qu'il y a réellement quelque chose de spécifique dans la conception du compilateur qui est la cause du problème. Pour comprendre ce que c'est, nous allons revenir à l'origine de la grammaire pour un instant et imaginez que le code de l'échantillon avaient un motif valable de cession de l' $path = array( 2 );.

En utilisant la grammaire comme un guide, il est possible de marcher à travers les actions invoquée dans le compilateur de code lors de l'analyse de cet exemple de code. J'ai laissé des moins importantes parties, mais le processus ressemble à quelque chose comme ceci:

// ...
// 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();

Alors que le compilateur ne beaucoup de ces différentes méthodes, il est important de noter quelques petites choses.

  1. Un appel à l' zend_do_begin_class_declaration() résultats dans un appel à l' get_next_op(). Cela signifie qu'il ajoute une nouvelle opcode pour le courant de l'opcode tableau.
  2. array_init() et zend_do_add_static_array_element() ne génèrent pas de nouveaux opérateurs. Au lieu de cela, le tableau est immédiatement créé et ajouté à la catégorie " tableau de propriétés. Des déclarations de méthode de travail de la même façon, par l'intermédiaire d'un cas spécial en zend_do_begin_function_declaration().
  3. zend_do_early_binding() consomme de la dernière opcode sur le courant de l'opcode tableau, la vérification de l'un des types suivants avant de le mettre à un NOP:
    • ZEND_DECLARE_FUNCTION
    • ZEND_DECLARE_CLASS
    • ZEND_DECLARE_INHERITED_CLASS
    • ZEND_VERIFY_ABSTRACT_CLASS
    • ZEND_ADD_INTERFACE

Notez que dans ce dernier cas, si l'opcode type n'est pas un de la des types, une erreur est levée – Le "Invalid type de liaison" erreur. À partir de cela, nous pouvons dire que le fait de permettre à des non-valeurs statiques pour être affecté en quelque sorte causes de la dernière opcode pour être autre chose que prévu. Donc, ce qui se passe lorsque nous utilisons un non-statique tableau avec la modification de la grammaire?

Au lieu d'appeler array_init(), le compilateur prépare les arguments et les appels zend_do_init_array(). Cela suppose get_next_op() et ajoute une nouvelle INIT_ARRAY opcode, la production de quelque chose comme ce qui suit:

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

C'est ici que réside la racine du problème. En ajoutant ces opcodes, zend_do_early_binding() obtient un inattendu d'entrée et lève une exception. Comme le processus du début de la liaison de la classe et des définitions de fonction semble assez partie intégrante de la compilation de PHP processus, il ne peut pas être ignoré (bien que le DECLARE_CLASS de production/consommation est une sorte de brouillon). De même, il n'est pas pratique de les essayer et d'évaluer ces opcodes en ligne (vous ne pouvez pas être sûr que la fonction ou de la classe a été résolu), donc il n'y a aucun moyen d'éviter de générer de la opcodes.

Une solution possible serait de construire un nouveau opcode tableau qui a été étendue à la classe la déclaration de la variable, semblable à la façon dont les définitions de méthode sont traitées. Le problème avec le faire est de décider d'évaluer une telle exécuter une fois la séquence. Serait-il faire lorsque le fichier contenant la classe est chargée, lorsque la propriété est d'abord accessible, ou quand un objet de ce type est-elle construite?

Comme vous l'avez souligné, d'autres langages dynamiques ont trouvé un moyen de gérer ce scénario, il n'est donc pas impossible de prendre cette décision et de le faire fonctionner. À partir de ce que je peux dire si, dans le cas de PHP ne serait pas une ligne fixe, et de la langue les concepteurs semblent avoir décidé que ce n'était pas quelque chose d'intéressant, y compris sur ce point.

23voto

Pekka 웃 Points 249607

Ma question est: pourquoi?! Est-ce une "fonctionnalité" ou une implémentation négligée?

Je dirais que c'est définitivement une fonctionnalité. Une définition de classe est un plan de code et n'est pas censée exécuter du code au moment de sa définition. Cela briserait l'abstraction et l'encapsulation de l'objet.

Cependant, ce n'est que mon avis. Je ne peux pas dire avec certitude quelle idée les développeurs avaient en définissant cela.

7voto

Robin Points 2615

Vous pouvez probablement obtenir quelque chose de similaire comme ceci:

 class Foo
{
    public $path = __DIR__;
}
 

IIRC __DIR__ besoin de PHP 5.3+, __FILE__ Cela fait plus longtemps

6voto

C'est une implémentation d'analyseur bâclée. Je n'ai pas la terminologie correcte pour le décrire (je pense que le terme "réduction bêta" trouve tout son sens ...), mais l'analyseur de langage PHP est plus complexe et plus compliqué qu'il ne devrait l'être, et toutes sortes de une casse spéciale est requise pour différentes constructions de langage.

2voto

Yanick Rochon Points 18537

J'imagine que vous ne pourrez pas obtenir une trace de pile correcte si l'erreur ne se produit pas sur une ligne exécutable ... Puisqu'il ne peut y avoir d'erreur d'initialisation des valeurs avec des constantes, cela ne pose aucun problème, mais la fonction peut générer des exceptions / erreurs et doit être appelée dans une ligne exécutable, et non pas déclarative.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X