2010-10-18 22 views
37

Sono abbastanza nuovo in PHP, ma ho programmato in lingue simili per anni. Ero sconcertato dal seguente:Perché gli attributi PHP non consentono le funzioni?

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

ha prodotto un errore di sintassi: Parse error: syntax error, unexpected '(', expecting ')' in test.php on line 5 che è la chiamata realpath.

Ma questo funziona bene:

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

Dopo sbattere la testa contro questo per un po ', mi è stato detto che non è possibile chiamare le funzioni in un predefinito per l'attributo; devi farlo in __construct. La mia domanda è: perché ?! Si tratta di un'implementazione "caratteristica" o sciatta? Qual è la logica?

+30

@Gordon Si prega di migliorare le risposte. – Schwern

+5

@Schwern Per favore, migliora le domande. – Gordon

+0

Per essere chiari, accetto quando la domanda ha una risposta definitiva. Finora tutte le risposte sono speculazioni. Utile, ma ancora congetture. – Schwern

risposta

48

Il codice compilatore suggerisce che questo è di progettazione, anche se I don' so qual è il ragionamento ufficiale che sta dietro. Inoltre, non sono sicuro di quanto impegno ci vorrebbe per implementare in modo affidabile questa funzionalità, ma ci sono sicuramente alcune limitazioni nel modo in cui le cose sono attualmente fatte.

Anche se la mia conoscenza del compilatore PHP non è ampia, cercherò di illustrare ciò che credo continui in modo da poter vedere dove c'è un problema. Il tuo esempio di codice rende un buon candidato per questo processo, in modo da useremo che:

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

Come siete ben consapevoli, questo causa un errore di sintassi. Questo è il risultato della PHP grammar, il che rende la seguente definizione rilevanti:

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

Così, quando si definiscono i valori delle variabili come $path, il valore atteso deve corrispondere alla definizione di uno scalare statica.Non sorprende, questo è un termine improprio dato che la definizione di uno scalare statica comprende anche tipi array i cui valori sono anche scalari statiche:

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

Supponiamo per un secondo che la grammatica era diverso, e la linea indicato in la regola delcaration variabile di classe sembrava qualcosa di più come la seguente che sarebbe abbinare il vostro codice di esempio (nonostante la rottura assegnazioni altrimenti validi):

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

Dopo ricompilare PHP, lo script di esempio non sarebbe più fallire con quella errore di sintassi. Invece, fallirebbe con l'errore di tempo di compilazione "Tipo di binding non valido". Poiché il codice è ora valido in base alla grammatica, ciò indica che c'è effettivamente qualcosa di specifico nella progettazione del compilatore che causa problemi. Per capire di cosa si tratta, torniamo alla grammatica originale per un momento e immaginiamo che l'esempio di codice abbia un compito valido di $path = array(2);.

Utilizzando la grammatica come guida, è possibile esaminare le azioni richiamate nello compiler code durante l'analisi di questo esempio di codice. Ho lasciato alcune parti meno importanti, ma il processo di simile a questa:

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

Mentre il compilatore fa un sacco in questi vari metodi, è importante notare alcune cose.

  1. Una chiamata a zend_do_begin_class_declaration() risultati in una chiamata a get_next_op(). Ciò significa che aggiunge un nuovo codice operativo all'array di codice operativo corrente.
  2. array_init() e zend_do_add_static_array_element() non generare nuovi codici. Invece, la matrice viene immediatamente creata e aggiunta alla tabella delle proprietà della classe corrente. Le dichiarazioni di metodo funzionano in modo simile, tramite un caso speciale in zend_do_begin_function_declaration().
  3. zend_do_early_binding()consuma l'ultimo codice operativo sull'array codice operativo corrente, controllo per uno dei seguenti tipi prima di impostarlo a un NOP:
    • ZEND_DECLARE_FUNCTION
    • ZEND_DECLARE_CLASS
    • ZEND_DECLARE_INHERITED_CLASS
    • ZEND_VERIFY_ABSTRACT_CLASS
    • ZEND_ADD_INTERFACE

Si noti che in quest'ultimo caso, se il tipo di codice operativo non è uno dei tipi previsti, viene generato un errore – Il "non valido tipo di rilegatura" errore . Da ciò, possiamo dire che consentire l'assegnazione dei valori non statici in qualche modo fa sì che l'ultimo codice operativo sia qualcosa di diverso dal previsto. Quindi, cosa succede quando usiamo un array non statico con la grammatica modificata?

Invece di chiamare array_init(), il compilatore prepara gli argomenti e chiama zend_do_init_array(). Questo a sua volta chiama get_next_op() e aggiunge un nuovo INIT_ARRAY opcode, producendo qualcosa di simile al seguente:

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

Qui sta la radice del problema.Aggiungendo questi codici opzionali, zend_do_early_binding() riceve un input imprevisto e genera un'eccezione. Poiché il processo di definizione delle classi e delle funzioni di associazione precoce sembra essere parte integrante del processo di compilazione di PHP, non può essere ignorato (sebbene la produzione/consumo di DECLARE_CLASS sia un po 'disordinato). Allo stesso modo, non è pratico provare e valutare questi opcode aggiuntivi in ​​linea (non si può essere sicuri che una data funzione o classe sia già stata risolta), quindi non c'è modo di evitare di generare gli opcode.

Una possibile soluzione sarebbe quella di creare un nuovo array di codice operativo che fosse stato applicato alla dichiarazione della variabile di classe, analogamente a come vengono gestite le definizioni del metodo. Il problema nel fare ciò è decidere quando valutare una tale sequenza run-once. Si farebbe quando il file contenente la classe viene caricato, quando si accede per la prima volta alla proprietà o quando viene costruito un oggetto di quel tipo?

Come hai sottolineato, altri linguaggi dinamici hanno trovato un modo per gestire questo scenario, quindi non è impossibile prendere quella decisione e farlo funzionare. Da quello che posso dire però, farlo nel caso di PHP non sarebbe una correzione su una sola riga, e i progettisti linguistici sembrano aver deciso che non era qualcosa che valesse la pena includere a questo punto.

+0

Grazie! La risposta a quando valutare evidenzia l'evidente difetto nella sintassi di default dell'attributo di PHP: non dovresti essere in grado di assegnarlo affatto, dovrebbe essere impostato nel costruttore di oggetti. Ambiguità risolta. (Gli oggetti cercano di condividere quella costante?) Per quanto riguarda gli attributi statici, non c'è ambiguità e potrebbe essere consentita qualsiasi espressione. È così che lo fa Ruby. Sospetto che non abbiano rimosso i valori di default di attributo dell'oggetto perché, mancando un costruttore di classi, non c'è un buon modo per impostare un attributo di classe. E non volevano avere quote separate per i valori di attributo oggetto vs classe. – Schwern

+0

@Schwern: felice di aiutare! Questo è qualcosa di cui ero curioso in passato, ma non ho mai pensato di controllare nei dettagli, quindi questa è stata una buona opportunità per capire cosa stava succedendo esattamente. Per quanto riguarda il compito, consentire questo tipo di incarichi evita di costringerti a creare un costruttore se non ne hai "bisogno" uno ... che ritengo sarebbe una giustificazione terribile, anche se nel caso di PHP, non uno scioccante . Penso che ogni istanza replicherà i valori di proprietà predefiniti durante la creazione, ma potrei sbagliarmi, quindi è possibile che provino a condividere. –

+0

In ogni caso, i risparmi ottenuti in tal modo (dati i dati limitati che è possibile assegnare in primo luogo) sarebbero minimi, quindi non sono sicuro che varrebbe la pena avere questa configurazione. Per quanto riguarda i tuoi commenti sulla risoluzione dell'ambiguità, sono propenso a concordare. –

19

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

Direi che è sicuramente una funzione. Una definizione di classe è un progetto di codice e non dovrebbe eseguire codice al momento della definizione. Romperebbe l'astrazione e l'incapsulamento dell'oggetto.

Tuttavia, questa è solo la mia opinione. Non posso dire con certezza quale idea avessero gli sviluppatori al momento di definire questo.

+5

+1 Sono d'accordo, per esempio se dico: 'pubblica $ foo = mktime()' farà si salva il tempo da quando la classe è analizzato, costruito, o quando è stato tentato l'accesso statico. – Hannes

+1

Come detto, non è definito quando l'espressione sarà valutata. Tuttavia, dovresti essere in grado di assegnare una chiusura a un attributo - che potrebbe restituire il tempo senza ambiguità - ma che produce anche un errore di sintassi. – erisco

+5

Quindi è un po 'di linguaggio BDSM in un linguaggio altrimenti molto permissivo e implementato come errore di sintassi? – Schwern

4

È un'implementazione di parser sciatta. Non ho la terminologia corretta per descriverlo (penso che il termine "riduzione beta" si adatti in qualche modo ...), ma il parser del linguaggio PHP è più complesso e complicato di quanto dovrebbe essere, e quindi tutti i tipi di è richiesto un involucro speciale per diversi costrutti linguistici.

+0

Altre lingue lo consentono? Sono curioso perché sinceramente non lo so. Se ricordo bene, Pascal/Delphi no. –

+2

@Pekka: i linguaggi statici di solito no, dal momento che una classe in essi è quasi sempre un costrutto compilatore. Ma con i linguaggi dinamici, la classe viene creata quando viene eseguita la definizione, quindi non c'è motivo per cui non possano utilizzare il valore di ritorno della funzione in quel momento come valore per l'attributo. –

+0

@Ignacio applausi. Ok, è vero. Continuo a pensare che sia una buona cosa nel complesso, perché impone buoni principi OOP. –

2

La mia ipotesi sarebbe che non sarà possibile avere una traccia di stack corretta se l'errore non si verifica su una linea eseguibile ... Poiché non ci può essere alcun errore con l'inizializzazione dei valori con costanti, non c'è problema con quello, ma la funzione può generare eccezioni/errori e deve essere chiamata all'interno di una riga eseguibile e non dichiarativa.

6

Probabilmente si può ottenere qualcosa di simile come questo:

class Foo 
{ 
    public $path = __DIR__; 
} 

IIRC __DIR__ esigenze php 5.3+, __FILE__ è stato più a lungo

+0

Buon punto. Funziona perché è una costante magica e verrà sostituita al momento dell'analisi. –

+3

Grazie, ma l'esempio era solo per illustrazione. – Schwern

Problemi correlati