Come configurare smartphone e PC. Portale informativo

Creare un oggetto in php. Oggetti

Classi e oggetti in PHP

Classe è un concetto base della programmazione orientata agli oggetti (OOP). Le classi costituiscono la base sintattica dell'OOP. Possono essere pensati come una sorta di "contenitori" per dati e funzioni logicamente correlati (comunemente chiamati - vedi sotto). Per dirla semplicemente, una classe è una sorta di tipo di dati.

Un'istanza di una classe è un oggetto. Un oggetto è una raccolta di dati () e funzioni (metodi) per elaborarli. Vengono chiamate proprietà e metodi. In generale, un oggetto è tutto ciò che supporta l'incapsulamento.

Se una classe può essere considerata un tipo di dati, allora un oggetto può essere considerato una variabile (per analogia). Lo script può funzionare contemporaneamente con più oggetti della stessa classe, così come con più variabili.

All'interno di un oggetto, i dati e il codice (membri della classe) possono essere pubblici o meno. I dati pubblici e i membri della classe sono accessibili ad altre parti del programma che non fanno parte dell'oggetto. Ma i dati privati ​​e i membri della classe sono disponibili solo all'interno di questo oggetto.

Le descrizioni delle classi in PHP iniziano con una parola funzione classe:

classe NomeClasse (
// descrizione dei membri della classe: proprietà e metodi per elaborarli
}

Per dichiarare un oggetto è necessario utilizzare l'operatore nuovo:

Oggetto = nuovo NomeClasse;

I dati vengono descritti utilizzando una parola funzione var. Il metodo viene descritto allo stesso modo di una normale funzione definita dall'utente. Puoi anche passare parametri al metodo.

Riassumiamo: la dichiarazione della classe deve iniziare con la parola chiave classe(simile a come la dichiarazione di una funzione inizia con la parola chiave funzione). Ogni dichiarazione di proprietà contenuta in una classe deve essere preceduta da una parola chiave var. Le proprietà possono essere qualsiasi tipo di dato supportato da PHP e possono essere considerate variabili con piccole differenze. Le dichiarazioni di proprietà sono seguite da dichiarazioni di metodo, che sono molto simili alle tipiche dichiarazioni di funzioni definite dall'utente.

Secondo le regole generalmente accettate, i nomi delle classi OOP iniziano con una lettera maiuscola e tutte le parole nei nomi dei metodi, tranne la prima, iniziano con una lettera maiuscola (la prima parola inizia con una lettera minuscola). Naturalmente puoi usare qualunque notazione ritieni conveniente; la cosa principale è scegliere uno standard e attenervisi.

Classe di esempio in PHP:

// Crea una nuova classe Coor:
classe Coor (
// dati (proprietà):
var$nome;
var $add;

// metodi:
funzione Nome() (
eco "

John

" ;
}

}


$oggetto = nuovaCoor;
?>

Accesso a classi e oggetti in PHP

Abbiamo esaminato come vengono descritte le classi e come vengono creati gli oggetti. Ora dobbiamo accedere ai membri della classe, a questo scopo in PHP esiste un operatore -> . Ecco un esempio:

// Crea una nuova classe Coor:
classe Coor (
// dati (proprietà):
var$nome;

// metodi:
funzione Ottieninome() (
eco "

John

" ;
}

}

// Crea un oggetto della classe Coor:
$oggetto = nuovaCoor;
// Ottieni l'accesso ai membri della classe:
$oggetto -> nome = "Alex";
echo $oggetto -> nome;
// Stampa "Alex"
// Ora accediamo al metodo della classe (di fatto, una funzione all'interno della classe):
$oggetto -> Getname();
// Stampa "John" in maiuscolo
?>

Per accedere ai membri della classe all'interno di una classe, è necessario utilizzare un puntatore $questo, che si riferisce sempre all'oggetto corrente. Metodo modificato Ottieninome():

funzione Ottieninome() (
echo $questo->nome;
}

Allo stesso modo, puoi scrivere un metodo Imposta nome():

funzione Impostanome($nome) (
$questo->nome = $nome;
}

Ora puoi utilizzare il metodo per modificare il nome Imposta nome():

$oggetto->Setname("Pietro");
$oggetto->Getname();

Ed ecco l'elenco completo del codice:

// Crea una nuova classe Coor:
classe Coor (
// dati (proprietà):
var$nome;

// metodi:
funzione Ottieninome() (
echo $questo -> nome;
}

funzione Impostanome($nome) (
$questo -> nome = $nome;
}

}

// Crea un oggetto della classe Coor:
$oggetto = nuovaCoor;
// Ora per cambiare il nome utilizziamo il metodo Setname():
$oggetto -> Setname("Nick");
// E per l'accesso, come prima, Getname():
$oggetto -> Getname();
// Lo script stampa "Nick"
?>

Puntatore $questo può essere utilizzato anche per accedere ai metodi, non solo all'accesso ai dati:

funzione Impostanome($nome) (
$questo->nome = $nome;
$this->Getname();
}

Costruttori

Molto spesso, quando si crea un oggetto, è necessario impostare i valori di alcune proprietà. Fortunatamente, gli sviluppatori della tecnologia OOP hanno tenuto conto di questa circostanza e l'hanno implementata nel concetto. Un costruttore è un metodo che imposta i valori di alcune proprietà (e può anche chiamare altri metodi). I costruttori vengono chiamati automaticamente quando vengono creati nuovi oggetti. Per rendere ciò possibile, il nome del metodo costruttore deve corrispondere al nome della classe che lo contiene. Esempio di costruttore:

pagina Web di classe(
var$bgcolore;
funzione Pagina Web($colore)(
$questo -> bgcolore = $colore;
}
}

// Chiama il costruttore della classe della pagina Web
$pagina = nuova pagina Web("marrone");
?>

In precedenza, la creazione dell'oggetto e l'inizializzazione delle proprietà venivano eseguite separatamente. I progettisti ti consentono di eseguire queste azioni in un unico passaggio.

Un dettaglio interessante: a seconda del numero di parametri passati possono essere chiamati diversi costruttori. Nell'esempio considerato, oggetti della classe Pagina web può essere creato in due modi. Innanzitutto, puoi chiamare un costruttore, che crea semplicemente l'oggetto ma non inizializza le sue proprietà:

$pagina = nuova pagina Web;

In secondo luogo, un oggetto può essere creato utilizzando un costruttore definito in una classe: in questo caso, crei un oggetto della classe Webpage e assegni un valore alla sua proprietà bgcolor:

$pagina = nuova pagina Web("marrone");

Distruttori

Non esiste un supporto diretto in PHP. Tuttavia, puoi facilmente simulare un distruttore chiamando la funzione PHP non settato(). Questa funzione distrugge il contenuto della variabile e restituisce al sistema le risorse che occupava. Con oggetti non settato() funziona allo stesso modo delle variabili. Diciamo che stai lavorando con un oggetto $ Pagina Web. Dopo aver finito di lavorare con questo particolare oggetto, la funzione si chiama:

non impostato($Pagina Web);

Questo comando cancella tutto il contenuto dalla memoria $ Pagina Web. Nello spirito dell'incapsulamento, puoi effettuare una chiamata non settato() in un metodo denominato distruggere() e poi chiamarlo:

$Sito Web->distruggi();

La necessità di chiamare distruttori sorge solo quando si lavora con oggetti che utilizzano una grande quantità di risorse, poiché tutte le variabili e gli oggetti vengono automaticamente distrutti al termine dello script.

Inizializzazione degli oggetti

A volte diventa necessario inizializzare un oggetto - per assegnare le sue proprietà ai valori iniziali. Supponiamo che il nome della classe sia Coor e contiene due proprietà: il nome della persona e la città di residenza. Puoi scrivere un metodo (funzione) che inizializzerà un oggetto, ad esempio Dentro():

// Crea una nuova classe Coor:
classe Coor (
// dati (proprietà):
var$nome;
var $città;

// Metodo di inizializzazione:
funzione Init($nome) (
$questo -> nome = $nome;
$ this -> città = "Londra";
}

}

// Crea un oggetto della classe Coor:
$oggetto = nuovaCoor;
// Per inizializzare l'oggetto, chiamare immediatamente il metodo:
$oggetto -> Init();
?>

La cosa principale è non dimenticare di chiamare la funzione immediatamente dopo aver creato l'oggetto o di chiamare qualche metodo tra la creazione (operator nuovo) oggetto e la sua inizializzazione (calling Dentro).

Affinché PHP sappia che un determinato metodo deve essere chiamato automaticamente quando viene creato un oggetto, è necessario assegnargli lo stesso nome della classe ( Coor):

funzione Coordina ($nome)
$questo->nome = $nome;
$questa->città = "Londra";
}

Il metodo che inizializza un oggetto è chiamato costruttore. Tuttavia, PHP non dispone di distruttori poiché le risorse vengono rilasciate automaticamente all'uscita degli script.

Accesso agli elementi della classe

È possibile accedere agli elementi della classe utilizzando l'operatore :: "doppio colon" Usando i "due punti doppi" puoi accedere ai metodi della classe.

Quando accede ai metodi delle classi, il programmatore deve utilizzare i nomi di queste classi.

classe A(
esempio di funzione() (
eco "Questa è la funzione A::example() originale.
"
;
}
}

La classe B estende A (
esempio di funzione() (
eco "Questa è una sostituzione di B::example().
"
;
A::esempio();
}
}

// Non è necessario creare un oggetto di classe A.
// Restituisce quanto segue:
// Questa è la funzione A::example() originale.
A::esempio();

// Crea un oggetto di classe B.
$b = nuovo forum del portale PHP B. SU

Le variabili che sono membri di una classe sono chiamate "proprietà". Vengono chiamati anche utilizzando altri termini come "attributi" o "campi", ma per gli scopi di questa documentazione li chiameremo proprietà. Sono definiti utilizzando parole chiave pubblico, protetto O privato, seguendo le regole per la corretta dichiarazione delle variabili. Questa dichiarazione può contenere un'inizializzazione, ma questa inizializzazione deve essere un valore costante, ovvero il valore deve essere calcolato in fase di compilazione e non deve dipendere dalle informazioni ricevute in fase di esecuzione per calcolarle.

La pseudo variabile $this è disponibile all'interno di qualsiasi metodo di classe quando tale metodo viene chiamato dal contesto di un oggetto. $questo è un riferimento all'oggetto che viene chiamato (solitamente l'oggetto a cui appartiene il metodo, ma eventualmente un altro oggetto se il metodo viene chiamato staticamente dal contesto di un secondo oggetto).

Esempio n.1 Definizione delle proprietà

classe SimpleClass
{
public $var1 = "ciao " . "mondo" ;
pubblico $var2 =<<Ciao mondo
EOD;
// corretta definizione delle proprietà a partire da PHP 5.6.0:
pubblico $var3 = 1 + 2 ;
// definizione della proprietà errata:
public $var4 = self::myStaticMethod();
pubblico $var5 = $miaVar ;

// definizione corretta della proprietà:
public $var6 = miacostante;
public $var7 = array(vero, falso);

// corretta definizione delle proprietà a partire da PHP 5.3.0:
pubblico $var8 =<<<"EOD"
Ciao mondo
EOD;
}
?>

Commento:

A partire da PHP 5.3.0 e nowdocs possono essere utilizzati in qualsiasi contesto di dati statici, inclusa la definizione delle proprietà.

Esempio n. 2 Esempio di utilizzo di nowdoc per inizializzare le proprietà

classe foo(
// Con PHP 5.3.0
pubblico $bar =<<<"EOT"
sbarra
EOT;
pubblico $baz =<<baz
EOT;
}
?>

Commento:

Il supporto Nowdoc e Heredoc è stato aggiunto in PHP 5.3.0.

7 anni fa

Nel caso in cui questo faccia risparmiare tempo a qualcuno, ho passato anni a capire perché quanto segue non funzionava:

classe La mia classe
{
privato $foo = FALSO;


{
$questo->$foo = VERO;

Echo($questo->$foo);
}
}

$bar = nuova MiaClasse();

dando "Errore fatale: impossibile accedere alla proprietà vuota in ...test_class.php sulla riga 8"

La sottile modifica della rimozione di $ prima degli accessi a $foo risolve questo problema:

classe La mia classe
{
privato $foo = FALSO;

Funzione pubblica __construct()
{
$questo->pippo = VERO;

Echo($questo->foo);
}
}

$bar = nuova MiaClasse();

Immagino perché nel primo esempio tratta $foo come una variabile, quindi provare a chiamare $this->FALSE (o qualcosa del genere) non ha senso. È ovvio una volta che te ne sei reso conto, ma lì non ci sono esempi di accesso a questa pagina che lo dimostrino.

4 anni fa

Puoi accedere ai nomi delle proprietà contenenti trattini (ad esempio, perché hai convertito un file XML in un oggetto) nel modo seguente:

$rif = nuova StdClass();
$ref ->( "ref-type" ) = "Articolo di giornale";
var_dump ($rif);
?>

8 anni fa

$questo può essere trasmesso all'array. Ma così facendo, antepone ai nomi delle proprietà/alle nuove chiavi dell'array determinati dati a seconda della classificazione della proprietà. I nomi delle proprietà pubbliche non vengono modificati. Le proprietà protette hanno il prefisso "*" riempito con spazio. Le proprietà private hanno il prefisso con il nome della classe preceduto da spazi...

Prova di classe
{
pubblico $var1 = 1;
protetto $var2 = 2 ;
privato $var3 = 3;
statico $var4 = 4;

Funzione pubblica toArray()
{
return (array) $questo;
}
}

$t = nuovo test;
print_r($t -> toArray());

/* restituisce:

Vettore
=> 1
[*var2] => 2
[ prova var3] => 3
)

*/
?>
Questo è un comportamento documentato quando si converte qualsiasi oggetto in un array (vedipagina manuale PHP). Tutte le proprietà, indipendentemente dalla visibilità, verranno visualizzate durante il cast di un oggetto nell'array (con l'eccezione di alcuni oggetti incorporati).

Per ottenere un array con tutti i nomi delle proprietà inalterati, utilizzare la funzione "get_object_vars($this)" in qualsiasi metodo all'interno dell'ambito della classe per recuperare un array di tutte le proprietà indipendentemente dalla visibilità esterna, oppure "get_object_vars($object)" all'esterno dell'ambito della classe per recuperare un array di sole proprietà pubbliche (vedi:pagina manuale PHP).

9 anni fa

Non confondere la versione delle proprietà di php con le proprietà di altri linguaggi (ad esempio C++). In php, le proprietà sono uguali agli attributi, variabili semplici senza funzionalità. Dovrebbero essere chiamate attributi, non proprietà.

Le proprietà hanno funzionalità di accesso e mutatore implicite. Ho creato una classe astratta che consente la funzionalità delle proprietà implicite.

Classe astratta PropertyObject
{
funzione pubblica __get ($nome)
{
if (metodo_esiste ($questo, ($metodo = "get_". $nome)))
{
restituisce $questo -> $metodo ();
}
altrimenti ritorna;
}

Funzione pubblica __isset ($nome)
{
if (metodo_esiste ($questo, ($metodo = "isset_". $nome)))
{
restituisce $questo -> $metodo ();
}
altrimenti ritorna;
}

Funzione pubblica __set ($nome, $valore)
{
if (metodo_esiste ($questo, ($metodo = "set_". $nome)))
{
$questo -> $metodo ($valore);
}
}

Funzione pubblica __unset ($nome)
{
if (metodo_esiste ($questo, ($metodo = "unset_". $nome)))
{
$questo -> $metodo();
}
}
}

?>

dopo aver esteso questa classe, è possibile creare accessori e mutatori che verranno chiamati automaticamente, utilizzando i metodi magici di php, quando si accede alla proprietà corrispondente.

5 anni fa

Aggiornato il metodo objectThis() per transtypage proprietà dell'array di classe o array per stdClass.

Spero che ti aiuti.

funzione pubblica objectThis($array = null) (
se (!$array) (
foreach ($this as $property_name => $property_values) (
if (is_array($property_values) && !empty($property_values)) (
$this->($property_name) = $this->objectThis($property_values);
) altrimenti se (is_array($property_values) && vuoto($property_values)) (
$this->($property_name) = new stdClass();
}
}
) altro (
$oggetto = nuova stdClass();
foreach ($array come $indice => $valori) (
if (is_array($valori) && vuoto($valori)) (
$oggetto->($indice) = new stdClass();
) altrimenti se (is_array($valori)) (
$oggetto->($indice) = $questo->oggettoQuesto($valori);
) altro (
$oggetto->($indice) = $valori;
}
}
restituire $oggetto;
}
}

  • Traduzione

Oggi gli oggetti vengono utilizzati in modo molto attivo, anche se questo era difficile da immaginare dopo il rilascio di PHP 5 nel 2005. A quel tempo sapevo ancora poco delle potenzialità di questa lingua. La quinta versione di PHP è stata confrontata con la precedente, la quarta, e il vantaggio principale della nuova versione era un nuovo modello a oggetti molto potente. E oggi, dieci anni dopo, circa il 90% di tutto il codice PHP contiene oggetti che non sono cambiati da PHP 5.0. Ciò suggerisce fortemente il ruolo svolto dall’introduzione del modello a oggetti, che è stato ripetutamente migliorato negli anni successivi. In questo post vorrei parlare di come funziona tutto “sotto il cofano”. In modo che le persone comprendano l'essenza dei processi - perché è stato fatto in questo modo e non altrimenti - e utilizzino meglio e più pienamente le capacità della lingua. Toccherò anche il tema dell'utilizzo della memoria da parte degli oggetti, anche rispetto ad array equivalenti (quando possibile).

Parlerò usando l'esempio di PHP 5.4, e le cose che descrivo valgono per 5.5 e 5.6, perché lì la struttura del modello a oggetti non ha subito quasi nessuna modifica. Tieni presente che nella versione 5.3 le cose non sono altrettanto buone in termini di funzionalità e prestazioni generali.

In PHP 7, che è ancora in fase di sviluppo, il modello a oggetti non è stato riprogettato molto, sono state apportate solo piccole modifiche. Semplicemente perché comunque tutto funziona bene e il meglio è nemico del bene. Sono state aggiunte funzionalità che non influenzano il kernel, ma questo non verrà discusso qui.

A titolo dimostrativo, inizierò con benchmark sintetici:

Classe Foo ( public $a = "foobarstring"; public $b; public $c = ["alcuni", "valori"]; ) for ($i=0; $i<1000; $i++) { $m = memory_get_usage(); ${"var".$i} = new Foo; echo memory_get_usage() - $m"\n"; }
Qui dichiariamo una classe semplice con tre attributi e quindi creiamo 1000 oggetti di questa classe in un ciclo. Nota come viene utilizzata la memoria in questo esempio: quando crei un oggetto della classe Foo e una variabile per memorizzarlo, vengono allocati 262 byte di memoria dinamica PHP.

Sostituiamo l'oggetto con un array equivalente:

Per ($i=0; $i<1000; $i++) { $m = memory_get_usage(); ${"var".$i} = [["some", "values"], null, "foobarstring"]; echo memory_get_usage() - $m . "\n"; }
In questo caso vengono utilizzati gli stessi elementi: l'array stesso, null e la variabile stringa foobarstring . Ma sono già consumati 1160 byte di memoria, ovvero 4,4 volte di più.

Ecco un altro esempio:

$classe =<<<"CL" class Foo { public $a = "foobarstring"; public $b; public $c = ["some", "values"]; } CL; echo memory_get_usage() . "\n"; eval($class); echo memory_get_usage() . "\n";
Poiché la classe viene dichiarata in fase di compilazione, utilizziamo l'operatore eval() per dichiarare e misurare la memoria utilizzata (utilizzando il gestore della memoria PHP). In questo caso, non vengono creati oggetti in questo codice. La quantità di memoria utilizzata (memoria differenziale) è 2216 byte.

Ora diamo un'occhiata a come funziona tutto questo nelle profondità di PHP, supportando la teoria con osservazioni pratiche.

Tutto inizia con le lezioni

All'interno di PHP, una classe è rappresentata utilizzando la struttura zend_class_entry:

Struct _zend_class_entry ( char type; const char *name; zend_uint name_length; struct _zend_class_entry *parent; int refcount; zend_uint ce_flags; HashTable function_table; HashTable property_info; zval **default_properties_table; zval **default_static_members_table; zval **static_members_table; HashTable costante s_table; int default_properties_count; int default_static_members_count; union _zend_function *costruttore; union _zend_function *destructor; union _zend_function *clone; union _zend_function *__get; union _zend_function *__set; union _zend_function *__unset; union _zend_function *__isset; union _zend_function *__call; union _zend_function *__callstatic ; union _zend_function *__tostring; union _zend_function *serialize_func; union _zend_function *unserialize_func; zend_class_iterator_funcs iterator_funcs; /* gestori */ zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC); zend_object_iterator *(*get_iterator)(zend voce _class_ *ce, zval * oggetto, int by_ref TSRMLS_DC); int (*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type TSRMLS_DC); /* una classe implementa questa interfaccia */ union _zend_function *(*get_static_method)(zend_class_entry *ce, char* metodo, int metodo_len TSRMLS_DC); /* callback del serializzatore */ int (*serialize)(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC); int (*unserialize)(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC); zend_class_entry **interfacce; zend_uint numero_interfacce; zend_class_entry **tratti; zend_uint num_traits; zend_trait_alias **trait_aliases; zend_trait_precedence **trait_precedences; union ( struct ( const char *nomefile; zend_uint line_start; zend_uint line_end; const char *doc_comment; zend_uint doc_comment_len; ) utente; struct ( const struct _zend_function_entry *builtin_functions; struct _zend_module_entry *module; ) internal; ) info; );
La dimensione della struttura, basata sul modello LP64, è 568 byte. Cioè, ogni volta che PHP dichiara una classe, è costretto a creare una zend_class_entry , utilizzando più di mezzo kilobyte di memoria dinamica solo per questo scopo. Naturalmente la questione non si limita a questo: come avete notato, la struttura contiene molti puntatori che necessitano anche di essere inseriti in memoria. Cioè, le classi stesse consumano molta più memoria di tutti gli oggetti successivamente creati da esse.

Tra le altre cose, le classi contengono attributi (statici e dinamici) e metodi. Tutto ciò richiede anche memoria. Per quanto riguarda i metodi, è difficile calcolare la relazione esatta, ma una cosa è vera: più grande è il corpo del metodo, più grande è il suo OPArray, il che significa più memoria consuma. A questo si aggiungono le variabili statiche che possono essere dichiarate in un metodo. Poi vengono gli attributi, poi anch'essi verranno inseriti in memoria. La dimensione dipende dai valori predefiniti: quelli interi occuperanno poco, ma un array statico di grandi dimensioni consumerà molta memoria.

È importante conoscere un altro punto relativo a zend_class_entry: i commenti PHP. Sono anche conosciuti come annotazioni. Queste sono variabili stringa (in C, char* buffer), che devono essere anch'esse inserite in memoria. Per il linguaggio C, che non utilizza Unicode, a differenza di PHP, la regola è molto semplice: un carattere = un byte. Più annotazioni hai in una classe, più memoria verrà utilizzata dopo l'analisi.

Per zend_class_entry, il campo doc_comment contiene annotazioni di classe. Anche i metodi e gli attributi dispongono di un campo simile.

Classi utente e interne

Una classe utente è una classe definita utilizzando PHP, mentre una classe interna viene definita inserendo il codice sorgente nel PHP stesso o utilizzando un'estensione. La differenza più grande tra questi due tipi di classi è che le classi definite dall'utente operano sulla memoria allocata su richiesta, mentre le classi interne operano sulla memoria “persistente”.

Ciò significa che quando PHP termina l'elaborazione della richiesta HTTP corrente, ripulisce e distrugge tutte le classi personalizzate in preparazione all'elaborazione della richiesta successiva. Questo approccio è noto come “architettura non condividere nulla”. Questo è stato integrato in PHP fin dall'inizio e non ci sono ancora piani per cambiarlo.

Pertanto, ogni volta che viene generata una richiesta e le classi vengono analizzate, viene allocata memoria per esse. Dopo aver utilizzato una classe, tutto ciò ad essa associato viene distrutto. Quindi assicurati di utilizzare tutte le classi dichiarate, altrimenti la memoria verrà sprecata. Utilizza i caricatori automatici, ritardano l'analisi/dichiarazione in fase di runtime quando PHP deve utilizzare la classe. Sebbene l'esecuzione sia più lenta, il caricatore automatico fa un buon uso della memoria perché non verrà eseguito finché la classe non sarà effettivamente necessaria.

Con le classi interne è diverso. Vengono archiviati in modo permanente nella memoria, indipendentemente dal fatto che vengano utilizzati o meno. Cioè, vengono distrutti solo quando PHP stesso smette di funzionare, dopo che tutte le richieste sono state elaborate (ovvero web SAPI, ad esempio PHP-FPM). Pertanto, le classi interne sono più efficienti di quelle personalizzate (solo gli attributi statici vengono distrutti alla fine della richiesta, nient'altro).

If (EG(full_tables_cleanup)) ( zend_hash_reverse_apply(EG(function_table), (apply_func_t) clean_non_persistent_function_full TSRMLS_CC); zend_hash_reverse_apply(EG(class_table), (apply_func_t) clean_non_persistent_class_full TSRMLS_CC); ) else ( zend_hash_reverse_apply( EG( function_table), (apply_func_t) clean_non_persistent_function TSRMLS_CC); zend_hash_reverse_apply(EG(class_table), (apply_func_t) clean_non_persistent_class TSRMLS_CC); ) static int clean_non_persistent_class(zend_class_entry **ce TSRMLS_DC) ( return ((*ce)->type == ZEND_INTERNAL_CLASS) ? ZEND_HASH_APPLY_STOP: ZEND_ HASH_APPLY_REM OVE; )
Tieni presente che anche con la memorizzazione nella cache del codice operativo come OPCache, la classe viene creata e distrutta a ogni richiesta, proprio come con le classi personalizzate. OPCache accelera semplicemente entrambi questi processi.

Come hai notato, se attivi molte estensioni PHP, ognuna delle quali dichiara molte classi, ma ne usi solo un numero limitato, la memoria viene sprecata. Ricorda che le estensioni PHP dichiarano le classi all'avvio di PHP, anche se le richieste successive non utilizzeranno tali classi. Pertanto, è sconsigliato mantenere attive le estensioni se non sono attualmente in uso, altrimenti si sprecherà memoria. Soprattutto se queste estensioni dichiarano molte classi, sebbene possano intasare la memoria con qualcos'altro.

Classi, interfacce o tratti: non importa

Per gestire classi, interfacce e caratteristiche in PHP, viene utilizzata la stessa struttura: zend_class_entry. E come hai già visto, questa struttura è piuttosto macchinosa. A volte gli sviluppatori dichiarano le interfacce nel loro codice in modo da poter utilizzare i loro nomi nei blocchi catch. Ciò ti consente di catturare solo un certo tipo di eccezione. Ad esempio, in questo modo:

Interfaccia BarException ( ) classe MyException estende l'eccezione implementa BarException ( ) try ( $foo->bar(): ) catch (BarException $e) ( )
Non è molto positivo che qui vengano utilizzati 912 byte solo per dichiarare l'interfaccia BarException.

$classe =<<<"CL" interface Bar { } CL; $m = memory_get_usage(); eval($class); echo memory_get_usage() - $m . "\n"; /* 912 bytes */
Non voglio dire che questo sia brutto o stupido, non sto cercando di incolpare nessuno o qualcosa. Sto solo attirando la tua attenzione su questo punto. Dal punto di vista della struttura interna di PHP, classi, interfacce e caratteristiche vengono utilizzate esattamente allo stesso modo. Non puoi aggiungere attributi all'interfaccia; il parser o il compilatore semplicemente non ti permetteranno di farlo. Tuttavia, la struttura zend_class_entry è ancora lì, è solo che un certo numero di campi, incluso static_members_table , non saranno puntatori allocati in memoria. Dichiarare una classe, un tratto equivalente o un'interfaccia equivalente richiederà la stessa quantità di memoria poiché utilizzano tutti la stessa struttura.

Vincolante di classe

Molti sviluppatori non pensano all'associazione di classi finché non iniziano a chiedersi come funzionano effettivamente le cose. L'associazione di classi può essere descritta come "il processo mediante il quale la classe stessa e tutti i dati associati vengono preparati per il pieno utilizzo da parte dello sviluppatore". Questo processo è molto semplice e non richiede molte risorse se parliamo di una classe che non ne estende un'altra, non utilizza tratti e non implementa un'interfaccia. Il processo di associazione per tali classi avviene interamente in fase di compilazione e durante l'esecuzione non vengono sprecate risorse. Tieni presente che stavamo parlando di vincolare una classe dichiarata dall'utente. Per le classi interne, lo stesso processo viene eseguito quando le classi vengono registrate con il core PHP o le estensioni, appena prima che vengano eseguiti gli script utente - e questo viene fatto solo una volta durante l'intero runtime PHP.

Le cose diventano molto complicate quando si tratta di implementare interfacce o ereditarietà di classi. Quindi, durante l'associazione delle classi, assolutamente tutto viene copiato dagli oggetti genitore e figlio (sia classi che interfacce).

/* Classe singola */ case ZEND_DECLARE_CLASS: if (do_bind_class(CG(active_op_array), opline, CG(class_table), 1 TSRMLS_CC) == NULL) ( return; ) table = CG(class_table); rottura;
Nel caso di una semplice dichiarazione di classe, eseguiamo do_bind_class() . Questa funzione registra semplicemente una classe completamente definita nella tabella delle classi per un uso successivo in fase di esecuzione e controlla anche possibili metodi astratti:

Void zend_verify_abstract_class(zend_class_entry *ce TSRMLS_DC) ( zend_abstract_info ai; if ((ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) && !(ce->ce_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) ( memset(&ai, 0, sizeof(ai)); z end_hash_app ly_con_argomento(&ce - >function_table, (apply_func_arg_t) zend_verify_abstract_class_function, &ai TSRMLS_CC); if (ai.cnt) ( zend_error(E_ERROR, "La classe %s contiene %d metodo astratto%s e deve pertanto essere dichiarata astratta o implementare i restanti metodi (" MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT ")", ce->nome, ai.cnt, ai.cnt > 1 ? "s" : "", DISPLAY_ABSTRACT_FN(0), DISPLAY_ABSTRACT_FN(1), DISPLAY_ABSTRACT_FN(2)); ) ) )
Non c'è niente da aggiungere qui, un caso semplice.

Quando si associa una classe che implementa un'interfaccia, è necessario effettuare le seguenti operazioni:

  • Controlla se l'interfaccia è già stata dichiarata.
  • Verificare se la classe richiesta è realmente una classe e non un'interfaccia essa stessa (come detto sopra, dal punto di vista della struttura interna sono strutturate allo stesso modo).
  • Copia le costanti dall'interfaccia alla classe, controllando possibili collisioni.
  • Copia i metodi dall'interfaccia alla classe, controllando possibili collisioni e incoerenze nella dichiarazione (ad esempio, trasformando i metodi dell'interfaccia in statici nella classe figlia).
  • Aggiungi l'interfaccia e tutte le possibili interfacce madri all'elenco delle interfacce implementate dalla classe.
Per “copiare” non intendiamo una copia completa e profonda. Per costanti, attributi e funzioni viene effettuato un ricalcolo per determinare quante entità in memoria li utilizzano.

ZEND_API void zend_do_implement_interface(zend_class_entry *ce, zend_class_entry *iface TSRMLS_DC) ( /* ... ... */ ) else ( if (ce->num_interfaces >= current_iface_num) ( if (ce->type == ZEND_INTERNAL_CLASS) ( ce ->interfaces = (zend_class_entry **) realloc(ce->interfaces, sizeof(zend_class_entry *) * (++current_iface_num)); ) else ( ce->interfaces = (zend_class_entry **) erealloc(ce->interfaces, sizeof (zend_class_entry *) * (++current_iface_num)); ) ) ce->interfaces = iface; zend_hash_merge_ex(&ce->constants_table, &iface->constants_table, (copy_ctor_func_t) zval_add_ref, sizeof(zval *), (merge_checker_func_t) do_inherit_constant_check, iface ); zend_hash_merge_ex(&ce->function_table, &iface->function_table, (copy_ctor_func_t) do_inherit_method, sizeof(zend_function), (merge_checker_func_t) do_inherit_method_check, ce); do_implement_interface(ce, iface TSRMLS_CC); zend_do_inherit_interfaces(ce, iface TS RMLS_CC); ) )
Notare la differenza tra classi interne e classi utente. Il primo utilizzerà realloc() per allocare memoria, il secondo utilizzerà erealloc(). realloc() alloca memoria "persistente", mentre erealloc() opera sulla memoria "su richiesta".

Puoi vedere che quando due tabelle costanti (interfaccia-1 e classe-1) vengono unite, lo fanno utilizzando il callback zval_add_ref. Non copia le costanti da una tabella all'altra, ma ne condivide i puntatori, semplicemente sommando il numero di riferimenti.

Per ciascuna delle tabelle di funzioni (metodi), viene utilizzato do_inherit_method:

Static void do_inherit_method(zend_function *function) ( function_add_ref(function); ) ZEND_API void function_add_ref(zend_function *function) ( if (function->type == ZEND_USER_FUNCTION) ( zend_op_array *op_array = &function->op_array; (*op_array->refcount )++; if (op_array->static_variables) ( HashTable *static_variables = op_array->static_variables; zval *tmp_zval; ALLOC_HASHTABLE(op_array->static_variables); zend_hash_init(op_array->static_variables, zend_hash_num_elements(static_variables), NULL, ZVAL_PTR_DTOR, 0 ); zend_hash_copy(op_array->static_variables, static_variables, (copy_ctor_func_t) zval_add_ref, (void *) &tmp_zval, sizeof(zval *)); ) op_array->run_time_cache = NULL; ) )
Un refcount viene aggiunto all'OPArray della funzione e tutte le possibili variabili statiche dichiarate nella funzione (qui un metodo) vengono copiate utilizzando zval_add_ref. Quindi l'intero processo di copia richiede molte risorse di calcolo perché sono coinvolti molti cicli e controlli. Ma viene utilizzata poca memoria. Sfortunatamente, l'associazione dell'interfaccia oggi è completamente trapelata in fase di esecuzione e lo sentirai ad ogni richiesta. Forse gli sviluppatori lo cambieranno presto.

Per quanto riguarda l'ereditarietà, qui, in linea di principio, tutto è uguale a quando si implementa un'interfaccia. Sono coinvolti solo più “partecipanti”. Ma voglio notare che se PHP conosce già la classe, l'associazione viene eseguita in fase di compilazione e, se non lo sa, in fase di esecuzione. Quindi è meglio dichiararlo così:

/* buono */ classe A ( ) classe B estende A ( )
invece di:

/* difettoso */ la classe B estende A ( ) la classe A ( )
A proposito, la procedura di routine dell'associazione di classi può portare a comportamenti molto strani:

/* funziona */ la classe B estende A ( ) la classe A ( )

/* ma non lo è */ Errore fatale: classe "B" non trovata */ classe C extends B ( ) classe B extends A ( ) classe A ( )

Nella prima opzione, l'associazione della classe B viene posticipata fino al momento dell'esecuzione, perché quando il compilatore arriva alla dichiarazione di questa classe, non sa ancora nulla della classe A. Quando inizia l'esecuzione, l'associazione della classe A avviene senza problemi, perché è già compilato, essendo una classe singleton. Nel secondo caso, tutto è diverso. L'associazione della classe C viene posticipata in fase di esecuzione perché il compilatore non sa ancora nulla di B quando tenta di compilarla. Ma quando la classe C viene associata in fase di esecuzione, cerca B, che non esiste perché non è compilata perché B è complementare. Appare il messaggio “La classe B non esiste”.

Oggetti

Quindi ora sappiamo che:
  • Le lezioni occupano molta memoria.
  • Le classi interne sono ottimizzate molto meglio delle classi utente perché queste ultime devono essere create e distrutte ad ogni richiesta. Le classi interiori esistono continuamente.
  • Classi, interfacce e tratti utilizzano la stessa struttura e procedure, le differenze sono molto piccole.
  • Durante l'ereditarietà o la dichiarazione, il processo di associazione richiede molto tempo della CPU, ma usa poca memoria perché molte cose vengono condivise anziché duplicate. Inoltre, è meglio eseguire l'associazione di classi in fase di compilazione.

Ora parliamo di oggetti. Il primo capitolo mostra che la creazione di un oggetto "classico" (una classe utente "classica") richiede pochissima memoria, circa 200 byte. Riguarda la classe. Anche l'ulteriore compilazione della classe consuma memoria, ma questo è meglio perché per creare un singolo oggetto sono necessari meno byte. Essenzialmente, un oggetto è un piccolo insieme di minuscole strutture.

Gestione dei metodi degli oggetti

A livello del motore, metodi e funzioni sono la stessa cosa: la struttura zend_function_structure. Differiscono solo i nomi. I metodi vengono compilati e aggiunti all'attributo function_table in zend_class_entry . Pertanto, in fase di esecuzione, viene presentato ogni metodo, si tratta solo di trasferire il puntatore all'esecuzione.

Typedef union _zend_function ( tipo zend_uchar; struct ( tipo zend_uchar; const char *nome_funzione; zend_class_entry *scope; zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; zend_uint require_num_args; zend_arg_info *arg_info; ) common; zend_op_array op_array; zend_in funzione_terna funzione_interna; ) funzione_zend ;
Quando un oggetto tenta di chiamare un metodo, il motore per impostazione predefinita cerca nella tabella delle funzioni della classe dell'oggetto. Se il metodo non esiste, viene chiamato __call(). Viene inoltre verificata la visibilità – pubblica/protetta/privata – a seconda che vengano intraprese le seguenti azioni:

Unione statica _zend_function *zend_std_get_method(zval **object_ptr, char *method_name, int metodo_len, const zend_literal *key TSRMLS_DC) ( zend_function *fbc; zval *object = *object_ptr; zend_object *zobj = Z_OBJ_P(object); ulong hash_value; char * lc_method_name; ALLOCA_FLAG(use_heap) if (EXPECTED(key != NULL)) ( lc_method_name = Z_STRVAL(key->costante); hash_value = key->hash_value; ) else ( lc_method_name = do_alloca(method_len+1, use_heap); zend_str_tolower_copy( lc_method_name, Method_name, Method_len); hash_value = zend_hash_func(lc_method_name, Method_len+1); ) /* Se il metodo non viene trovato */ if (UNEXPECTED(zend_hash_quick_find(&zobj->ce->function_table, lc_method_name, Method_len+1, hash_value , (void **)&fbc) == FAILURE)) ( if (UNEXPECTED(!key)) ( free_alloca(lc_method_name, use_heap); ) if (zobj->ce->__call) ( /* se la classe ha un Gestore __call() */ return zend_get_user_call_function(zobj->ce, Method_name, Method_len); /* chiama il gestore __call() */ ) else ( return NULL; /* altrimenti restituisce NULL, che probabilmente porterà a un errore fatale: metodo non trovato */ ) ) /* Controlla il livello di accesso */ if (fbc->op_array.fn_flags & ZEND_ACC_PRIVATE) ( zend_function *updated_fbc; aggiornato_fbc = zend_check_private_int(fbc , Z_OBJ_HANDLER_P(oggetto, get_class_entry)(oggetto TSRMLS_CC), lc_method_name, Method_len, hash_value TSRMLS_CC); if (EXPECTED(updated_fbc != NULL)) ( fbc = aggiornato_fbc; ) else ( if (zobj->ce->__call) ( fbc = zend_get_user_call_function(zobj->ce, nome_metodo, metodo_len); ) else ( zend_error_noreturn(E_ERROR, "Chiama al metodo %s %s::%s() dal contesto "%s"", zend_visibility_string(fbc->common.fn_flags ), ZEND_FN_SCOPE_NAME(fbc), nome_metodo, EG(ambito) ? EG(ambito)->nome: ""); ) ) ) else ( /* ... ... */ )
Potresti aver notato una cosa interessante, guarda le prime righe:

If (EXPECTED(key != NULL)) ( lc_method_name = Z_STRVAL(key->costante); hash_value = key->hash_value; ) else ( lc_method_name = do_alloca(method_len+1, use_heap); /* Crea un zend_copy_str_tolower(dest, src, src_length); */ zend_str_tolower_copy(lc_method_name, Method_name, Method_len); hash_value = zend_hash_func(lc_method_name, Method_len+1); )
Questa è una manifestazione dell'insensibilità alle maiuscole e minuscole di PHP. Il sistema deve prima scrivere in minuscolo ciascuna funzione (zend_str_tolower_copy()) prima di chiamarla. Non esattamente tutti, ma quelli in cui è presente l'istruzione if. La variabile chiave impedisce l'esecuzione della funzione che converte in minuscolo (la parte else) - questo fa parte dell'ottimizzazione PHP implementata nella versione 5.4. Se la chiamata al metodo non è dinamica, il compilatore ha già calcolato la chiave e vengono sprecate meno risorse in fase di esecuzione.

Classe Foo (funzione pubblica BAR() ( ) ) $a = nuovo Foo; $b = "barra"; $a->barra(); /* chiamata statica: buona */ $a->$b(); /* chiamata dinamica: pessima */
Quando la funzione/metodo viene compilato, viene immediatamente convertito in minuscolo. La funzione BAR() di cui sopra viene trasformata in bar() dal compilatore quando si aggiunge un metodo alla tabella di classi e funzioni.

Nell'esempio sopra, la prima chiamata è statica: il compilatore ha calcolato la chiave per la stringa “bar”, e quando arriva il momento di chiamare il metodo, ha meno lavoro da fare. La seconda chiamata è già dinamica, il compilatore non sa nulla di “$b” e non può calcolare la chiave per chiamare il metodo. Quindi, in fase di esecuzione, dobbiamo convertire la stringa in minuscolo e calcolare il suo hash (zend_hash_func()), che non ha il miglior impatto sulle prestazioni.

Per quanto riguarda __call() , non riduce molto le prestazioni. Tuttavia, questo metodo spreca più risorse rispetto alla chiamata di una funzione esistente.

Gestione degli attributi degli oggetti

Ecco cosa succede:

Come puoi vedere, quando vengono creati più oggetti della stessa classe, il motore reindirizza ciascun attributo allo stesso puntatore come nel caso degli attributi di classe. Per tutta la sua vita, una classe memorizza non solo i propri attributi statici, ma anche gli attributi degli oggetti. Nel caso di classi interne - durante l'intero tempo di esecuzione di PHP. La creazione di un oggetto non implica la creazione dei suoi attributi, quindi questo è un approccio abbastanza veloce ed economico. Solo quando un oggetto sta per cambiare uno dei suoi attributi il ​​motore ne crea uno nuovo, presupponendo che tu stia cambiando l'attributo $a di Foo #2:

Quindi, quando creiamo un oggetto, creiamo "semplicemente" una struttura zend_object da 32 byte:

Typedef struct _zend_object ( zend_class_entry *ce; HashTable *properties; zval **properties_table; HashTable *guards; /* protegge da __get/__set ... ricorsione */ ) zend_object;
Questa struttura viene aggiunta all'archivio oggetti. E questa, a sua volta, è la struttura zend_object_store. Questo è il registro globale degli oggetti del motore Zend, un luogo in cui tutti gli oggetti vengono raccolti e archiviati in un'unica istanza:

ZEND_API zend_object_value zend_objects_new(zend_object **object, zend_class_entry *class_type TSRMLS_DC) ( zend_object_value retval; *object = emalloc(sizeof(zend_object)); (*object)->ce = class_type; (*object)->properties = NULL; ( *object)->properties_table = NULL; (*object)->guards = NULL; /* Aggiungi l'oggetto al negozio */ retval.handle = zend_objects_store_put(*object, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) zend_objects_free_object_stor age, NULL TSRMLS_CC ); retval.handlers = &std_object_handlers; return retval; )
Successivamente, il motore crea un vettore di funzionalità per il nostro oggetto:

ZEND_API void object_properties_init(zend_object *object, zend_class_entry *class_type) ( int i; if (class_type->default_properties_count) ( object->properties_table = emalloc(sizeof(zval*) * class_type->default_properties_count); for (i = 0; i< class_type->default_properties_count; i++) ( oggetto->properties_table[i] = class_type->default_properties_table[i]; if (class_type->default_properties_table[i]) ( #if ZTS ALLOC_ZVAL(oggetto->properties_table[i]); MAKE_COPY_ZVAL(&class_type->default_properties_table [i], oggetto->properties_table[i]); #else Z_ADDREF_P(oggetto->properties_table[i]); #endif ) ) oggetto->properties = NULL; ) )
Come puoi vedere, abbiamo allocato una memoria tabella/vettore (come in C) per zval* in base alle proprietà dichiarate della classe oggetto. Nel caso di PHP non thread-safe, aggiungiamo semplicemente refcount all'attributo e se viene utilizzato Zend thread-safe (ZTS, Zend thread safety), dobbiamo copiare completamente zval . Questo è uno dei tanti esempi che confermano le basse prestazioni e l'elevata intensità di risorse della modalità ZTS rispetto al PHP non ZTS.

Probabilmente hai due domande:

  • Qual è la differenza tra Properties_table e Properties nella struttura zend_object?
  • Se inseriamo gli attributi del nostro oggetto nel vettore C, come possiamo recuperarli? Sfogliare il vettore ogni volta (il che riduce le prestazioni)?

La risposta a entrambe le domande è fornita da zend_property_info .

Typedef struct _zend_property_info ( zend_uint flags; const char *name; int name_length; ulong h; int offset; const char *doc_comment; int doc_comment_len; zend_class_entry *ce; ) zend_property_info;
Ogni dichiarato un attributo (proprietà) del nostro oggetto ha informazioni sulla proprietà corrispondenti aggiunte al campo property_info in zend_class_entry . Questo viene fatto durante la compilazione degli attributi dichiarati nella classe:

Class Foo ( public $a = "foo"; protected $b; private $c; ) struct _zend_class_entry ( /* ... ... */ HashTable function_table; HashTable Properties_info; /* ecco le informazioni sulle proprietà su $a, $b e $c */ zval **default_properties_table; /* e qui troveremo $a, $b e $c con i loro valori predefiniti */ int default_properties_count; /* questo avrà il valore 3: 3 proprietà */ /* ... ... */
Properties_infos è una tabella che indica a un oggetto se l'attributo richiesto esiste. E se esiste, passa il suo numero di indice nell'array oggetto->proprietà. Quindi controlliamo la visibilità e l'accesso all'ambito (pubblico/protetto/privato).

Se l'attributo non esiste e dobbiamo scriverlo, possiamo provare a chiamare __set() . In caso di fallimento, creiamo un attributo dinamico che verrà memorizzato nel campo object->property_table.

Property_info = zend_get_property_info_quick(zobj->ce, membro, (zobj->ce->__set != NULL), chiave TSRMLS_CC); if (EXPECTED(property_info != NULL) && ((EXPECTED((property_info->flags & ZEND_ACC_STATIC) == 0) && property_info->offset >= 0) ? (zobj->properties ? ((variable_ptr = (zval** )zobj->properties_table) != NULL) : (*(variable_ptr = &zobj->properties_table) != NULL)) : (EXPECTED(zobj->properties != NULL) && EXPECTED(zend_hash_quick_find(zobj->properties, property_info- >name, property_info->name_length+1, property_info->h, (void **) &variable_ptr) == SUCCESS)))) ( /* ... ... */ ) else ( zend_guard *guard = NULL; if (zobj->ce->__set && /* la classe ha un __set() ? */ zend_get_property_guard(zobj, property_info, member, &guard) == SUCCESS && !guard->in_set) ( Z_ADDREF_P(object); if (PZVAL_IS_REF( object)) ( SEPARATE_ZVAL(&object); ) guard->in_set = 1; /* impedisce l'impostazione circolare */ if (zend_std_call_setter(object, member, value TSRMLS_CC) != SUCCESS) ( /* call __set() */ ) guard ->in_set = 0; zval_ptr_dtor(&oggetto); /* ... ... */
Finché non scrivi su un oggetto, il suo consumo di memoria non cambia. Dopo la registrazione, occupa più spazio (fino a quando non viene distrutto), poiché contiene tutti gli attributi scritti su di esso.

Gli oggetti si comportano come riferimenti grazie all'archiviazione degli oggetti

Gli oggetti non sono riferimenti. Ciò è dimostrato in un piccolo script:

Funzione foo($var) ( $var = 42; ) $o = nuova MiaClasse; foo($o); var_dump($o); /* questo è ancora un oggetto, non l'intero 42 */
Tutti ora diranno che “in PHP 5, gli oggetti sono collegamenti”, anche il manuale ufficiale lo menziona. Tecnicamente questo è completamente falso. Tuttavia, gli oggetti possono comportarsi allo stesso modo dei riferimenti. Ad esempio, quando passi una variabile che è un oggetto a una funzione, quella funzione può modificare lo stesso oggetto.

Questo perché zval passato come funzione non passa l'oggetto stesso, ma il suo identificatore univoco, che viene utilizzato per cercare nell'archivio oggetti condivisi. E il risultato è lo stesso. È possibile allocare tre diversi zval in memoria e tutti possono contenere lo stesso handle di oggetto.

Object(MyClass)#1 (0) ( ) /* #1 è l'handle dell'oggetto (numero), è univoco */

Zend_object_store consente di archiviare gli oggetti in memoria una volta. L'unico modo per scrivere nello store è creare un nuovo oggetto con la parola chiave new, la funzione unserialize(), l'API di riflessione o la parola chiave clone. Nessun'altra operazione ti consentirà di duplicare o creare un nuovo oggetto nel negozio.

Typedef struct _zend_objects_store ( zend_object_store_bucket *object_buckets; zend_uint top; zend_uint size; int free_list_head; ) zend_objects_store; typedef struct _zend_object_store_bucket ( zend_bool destructor_activated; zend_bool valid; zend_uchar apply_count; union _store_bucket ( struct _store_object ( void *object; zend_objects_store_dtor_t dtor; zend_objects_free_object_storage_t free_storage; zend_objects_store_clone_t clone; const zend_object_handlers *handlers; zend_uint refcount; gc_root_buffer *buffered; ) obj; struct ( int next; ) free_list; ) bucket; ) zend_object_store_bucket;

Cos'è $questo?

Capire $this non è poi così difficile, ma ci sono pezzi di codice associati a questo strumento in diversi punti del motore: nel compilatore, nel codice per ottenere variabili in fase di esecuzione, ecc. $questo appare e scompare secondo necessità, assegnandosi automaticamente all'oggetto corrente - in generale, una cosa "magica". E il codice interno ne permette perfettamente la gestione.

Innanzitutto, il compilatore non consentirà la scrittura su $this . Per fare ciò, controllerà ogni assegnazione effettuata e, se trova un'assegnazione su $this , genererà un errore fatale.

/* ... ... */ if (opline_is_fetch_this(last_op TSRMLS_CC)) ( zend_error(E_COMPILE_ERROR, "Impossibile riassegnare $this"); ) /* ... ... */ static zend_bool opline_is_fetch_this(const zend_op *opline TSRMLS_DC) ( if ((opline->opcode == ZEND_FETCH_W) && (opline->op1_type == IS_CONST) && (Z_TYPE(CONSTANT(opline->op1.constant)) == IS_STRING) && ((opline-> valore_esteso & ZEND_FETCH_STATIC_MEMBER) != ZEND_FETCH_STATIC_MEMBER) && (Z_HASH_P(&CONSTANT(opline->op1.constant)) == THIS_HASHVAL) && (Z_STRLEN(CONSTANT(opline->op1.constant)) == (sizeof("this")- 1)) && !memcmp(Z_STRVAL(CONSTANT(opline->op1.constant)), "this", sizeof("this"))) ( return 1; ) else ( return 0; ) )
Come viene gestito $this? Il suo utilizzo è possibile solo all'interno di un metodo, durante la chiamata del quale il compilatore genera OPCode INIT_METHOD_CALL . Il motore sa chi sta chiamando il metodo, nel caso di $a->foo() è $a . Quindi il valore $a viene recuperato e archiviato nello spazio condiviso. Successivamente, il metodo viene chiamato utilizzando OPCode DO_FCALL. A questo punto, il valore memorizzato viene nuovamente recuperato (l'oggetto chiama il metodo) e assegnato al puntatore interno globale $this - EG(This) .

If (fbc->type == ZEND_USER_FUNCTION || fbc->common.scope) ( dovrebbe_cambiare_ambito = 1; EX(current_this) = EG(This); EX(current_scope) = EG(scope); EX(current_call_scope) = EG( call_scope); EG(This) = EX(object); /* recupera l'oggetto preparato nel precedente codice operativo INIT_METHOD e lo influenza su EG(This) */ EG(scope) = (fbc->type == ZEND_USER_FUNCTION || !EX (oggetto)) ? fbc->common.scope: NULL; EG(ambito_chiamato) = EX(chiamata)->ambito_chiamato; )
Ora, quando viene chiamato un metodo, se nel suo corpo usi $this per agire su una variabile o chiamare un metodo (ad esempio $this->a = 8), ciò risulterà in OPCode ZEND_ASSIGN_OBJ , che a sua volta recupererà $ questo di ritorno da EG(This) .

Statico zend_always_inline zval **_get_obj_zval_ptr_ptr_unused(TSRMLS_D) ( if (EXPECTED(EG(This) != NULL)) ( return &EG(This); ) else ( zend_error_noreturn(E_ERROR, "Utilizzo di $this quando non nel contesto dell'oggetto"); return NULLO; ) )
Nel caso in cui tu abbia utilizzato $this per chiamare un metodo (ad esempio, $this->foo()) o passato un'altra chiamata di funzione ($this->foo($this);), il motore tenterà di estrarre $this da le tabelle dei simboli correnti come fa per ciascuna variabile standard. Ma qui viene eseguita una preparazione speciale durante la creazione dello stack frame della funzione corrente:

If (op_array->this_var != -1 && EG(This)) ( Z_ADDREF_P(EG(This)); if (!EG(active_symbol_table)) ( EX_CV(op_array->this_var) = (zval **) EX_CV_NUM(execute_data , op_array->last_var + op_array->this_var); *EX_CV(op_array->this_var) = EG(This); ) else ( if (zend_hash_add(EG(active_symbol_table), "this", sizeof("this"), &EG (Questo), sizeof(zval *), (void **) EX_CV_NUM(execute_data, op_array->this_var))==FAILURE) ( Z_DELREF_P(EG(This)); ) ) )
Quando chiamiamo un metodo, il motore cambia l'ambito:

If (fbc->type == ZEND_USER_FUNCTION || fbc->common.scope) ( /* ... ... */ EG(scope) = (fbc->type == ZEND_USER_FUNCTION || !EX(oggetto)) fbc->common.scope: NULL; /* ... ... */ )
EG(scope) è di tipo zend_class_entry . Questa è la classe che possiede il metodo che stai richiedendo. E verrà utilizzato per qualsiasi operazione sull'oggetto che eseguirai nel corpo del metodo dopo aver verificato la visibilità del motore:

Statico zend_always_inline int zend_verify_property_access(zend_property_info *property_info, zend_class_entry *ce TSRMLS_DC) ( switch (property_info->flags & ZEND_ACC_PPP_MASK) ( case ZEND_ACC_PUBLIC: return 1; case ZEND_ACC_PROTECTED: return zend_check_protected(property_info->ce , EG(ambito)); case ZEND_ACC_PRIVATE: if ((ce==EG(ambito) || property_info->ce == EG(ambito)) && EG(ambito)) ( return 1; ) else ( return 0; ) break; ) return 0; )
Ecco come puoi accedere ai membri privati ​​di oggetti che non ti appartengono, ma sono figli del tuo ambito attuale:

Classe A ( private $a; public function foo(A $obj) ( $this->a = "foo"; $obj->a = "bar"; /* sì, questo è possibile */ ) ) $a = nuova A; $b = nuova A; $a->foo($b);
Questa funzionalità ha causato un gran numero di segnalazioni di bug da parte degli sviluppatori. Ma è così che funziona il modello a oggetti in PHP: infatti, impostiamo l'ambito in base non all'oggetto, ma alla classe. Nel caso della nostra classe “Foo”, puoi lavorare con qualsiasi Foo privato di qualsiasi altro Foo, come mostrato sopra.

A proposito del distruttore

I distruttori sono pericolosi, non fare affidamento su di loro poiché PHP non li chiama nemmeno in caso di errore fatale:

Classe Foo (funzione pubblica __destruct() ( echo "ciao ciao foo"; ) ) $f = nuovo Foo; questa funzione non esiste (); /* errore fatale, funzione non trovata, il distruttore di Foo NON viene eseguito */
Che dire dell'ordine in cui vengono chiamati i distruttori se vengono chiamati comunque? La risposta è chiaramente visibile nel codice:

Void shutdown_destructors(TSRMLS_D) ( zend_try ( int simboli; do ( simboli = zend_hash_num_elements(&EG(symbol_table)); zend_hash_reverse_apply(&EG(symbol_table), (apply_func_t) zval_call_destructor TSRMLS_CC); ) while (symbols != zend_hash_num_elements(&EG(symbol_table) ) ); zend_objects_store_call_destructors(&EG(objects_store) TSRMLS_CC); ) zend_catch ( /* se non siamo riusciti a distruggere in modo pulito, contrassegna comunque tutti gli oggetti come distrutti */ zend_objects_store_mark_destructed(&EG(objects_store) TSRMLS_CC); ) zend_end_try(); ) static int zval_call_destructor (zval **zv TSRMLS_DC) ( if (Z_TYPE_PP(zv) == IS_OBJECT && Z_REFCOUNT_PP(zv) == 1) ( return ZEND_HASH_APPLY_REMOVE; ) else ( return ZEND_HASH_APPLY_KEEP; ) )
Le tre fasi di chiamata di un distruttore sono dimostrate qui:

  • Scorre all'indietro la tabella dei simboli globale e chiama i distruttori per gli oggetti che hanno refcount = 1.
  • Quindi la direzione della ciclicità cambia e vengono chiamati i distruttori per tutti gli altri oggetti, con refcount > 1.
  • Se si verifica un problema in una delle fasi precedenti, la chiamata ai restanti distruttori viene interrotta.
Cosa comporta questo:

Classe Foo (funzione pubblica __destruct() ( var_dump("Foo distrutto"); ) )
class Bar (funzione pubblica __destruct() ( var_dump("barra distrutta"); ) )

Esempio uno:

$a = nuovo Foo; $b = nuova barra; "Bar distrutto" "Foo distrutto"
Stesso esempio:

$a = nuova barra; $b = nuovo Foo; "Foo distrutto" "Bar distrutto"
Esempio due:

$a = nuova barra; $b = nuovo Foo; $c = $b; /* incrementa il conteggio degli oggetti di $b */ "Bar distrutto" "Foo distrutto"
Esempio tre:

Class Foo ( public function __destruct() ( var_dump("distrutto Foo"); die();) ) /* nota die() qui */ class Bar ( public function __destruct() ( var_dump("distrutto Bar"); ) ) $a = nuovo Foo; $a2 = $a; $b = nuova barra; $b2 = $b; distrutto Foo
Questa procedura è stata scelta per un motivo. Ma se non ti va bene, allora è meglio distruggere tu stesso i tuoi oggetti. Questo è l'unico modo per controllare le chiamate a __destruct() . Se lasci che PHP lo faccia per te, non arrabbiarti con i risultati. Hai sempre la possibilità di distruggere manualmente i tuoi oggetti per avere il controllo completo sull'ordine.

PHP non chiama distruttori in caso di errori fatali. Il fatto è che in questo caso Zend è instabile e la chiamata ai distruttori porta all'esecuzione di codice utente, che può accedere a puntatori errati e, di conseguenza, mandare in crash PHP. È meglio mantenere la stabilità del sistema: ecco perché le chiamate ai distruttori sono bloccate. Forse qualcosa cambierà in PHP 7.

Per quanto riguarda le ricorsioni, sono scarsamente protette in PHP e questo vale solo per __get() e __set() . Se distruggi il tuo oggetto da qualche parte nello stack frame del distruttore, finirai in un ciclo ricorsivo infinito che mangerà tutte le risorse dello stack del tuo processo (di solito 8 kB, ulimit -s) e romperà PHP.

Class Foo ( public function __destruct() ( new Foo; ) /* andrai in crash */ )
Per riassumere, non fidarti dei distruttori con codice critico, come il controllo del meccanismo di blocco, perché PHP potrebbe non chiamare il distruttore o potrebbe chiamarlo in una sequenza incontrollata. Se il codice importante viene ancora elaborato da un distruttore, almeno controlla tu stesso il ciclo di vita degli oggetti. PHP chiamerà il distruttore quando il refcount del tuo oggetto scende a zero, il che significa che l'oggetto non è più in uso e può essere distrutto in sicurezza.

Conclusione

Spero che ora ti sia diventato più chiaro molto nel lavoro quotidiano con gli oggetti. Non consumano molta memoria e la loro implementazione a livello di motore è ben ottimizzata. Prova a utilizzare un caricatore automatico ben progettato per migliorare l'utilizzo della memoria. Dichiara le classi in ordine di ereditarietà logica e, se trasformi le più complesse in estensioni C, puoi ottimizzare molti processi e persino aumentare ulteriormente le prestazioni complessive di tali classi.

In questo tutorial imparerai le basi della programmazione orientata agli oggetti in PHP. Imparerai i principi dell'OOP in generale e imparerai come scrivere semplici script in PHP.

Benvenuti alla prima di una serie di lezioni sull'OOP in PHP! Completando tutte le lezioni di questa serie, imparerai i principi e i concetti di base dell'OOP e imparerai come creare rapidamente e facilmente applicazioni utili in PHP.

In questo tutorial inizierò ad aggiornarti e a parlarti dei concetti di base dell'OOP. Imparerai:

  • cos'è l'OOP
  • come l'OOP ti aiuterà a creare script PHP migliori
  • alcuni concetti base come classi, oggetti, metodi, variabili di classe
  • da dove iniziare a scrivere uno script PHP

Sei pronto a tuffarti nel mondo degli oggetti PHP? Allora vai avanti!

Cos'è la programmazione orientata agli oggetti?

Se hai mai creato e utilizzato funzioni personalizzate in PHP, hai utilizzato uno stile di programmazione procedurale. Nella programmazione procedurale, in genere si creano strutture dati: numeri, stringhe, array, ecc. - memorizzare alcuni dati e quindi elaborare queste strutture con funzioni speciali che manipolano questi dati.

La programmazione orientata agli oggetti, o OOP, è andata avanti perché qui memorizziamo le strutture dati e le funzioni che le elaborano in un'unica entità chiamata oggetto. Invece di elaborare i dati con qualche funzione, carichi i dati in un oggetto e poi chiami i suoi metodi per manipolarli e ottenere il risultato desiderato.

Molto spesso, gli oggetti creati utilizzando l'OOP riflettono entità reali. Ad esempio, se stai creando un forum per il tuo sito, creerai un oggetto Membro che memorizzerà le informazioni su ciascun membro del forum (nome, accesso, email, password, ecc.), nonché i metodi che elaboreranno queste informazioni ( registrazione, autorizzazione, logout, ban, ecc.).

Perché utilizzare l'OOP?

Procedurale e orientato agli oggetti sono due modi diversi di fare la stessa cosa. Questo non vuol dire che uno sia migliore dell'altro: ognuno scrive come preferisce, quindi puoi anche combinare facilmente questi due approcci in un unico script.

Tuttavia, ecco alcuni vantaggi dell'OOP per gli sviluppatori:

  • È più facile riflettere situazioni reali: come ho notato sopra, gli oggetti riflettono entità reali: persone, prodotti, carte, articoli di blog, ecc. Ciò semplifica notevolmente il compito quando stai appena iniziando a progettare la tua applicazione, poiché lo scopo di ciascun oggetto è come un obiettivo, le relazioni tra gli oggetti saranno chiare e comprensibili.
  • È più semplice scrivere programmi modulari: l'OOP implica la scrittura di moduli. Dividendo il codice in moduli, sarà più semplice gestirlo, eseguirne il debug ed estenderlo.
  • È più semplice scrivere codice che verrà utilizzato più volte: scrivere codice che può essere utilizzato più di una volta farà risparmiare tempo durante la scrittura di un'applicazione e, nel tempo, potrai persino creare un'intera libreria di questo tipo di moduli che potrai utilizzare in molti altri modi. applicazioni. Con l'OOP, diventa relativamente più semplice scrivere tale codice poiché le strutture dei dati e le funzioni sono incapsulate in un singolo oggetto che può essere utilizzato un numero qualsiasi di volte.

Alcuni concetti base

Prima di iniziare a scrivere script, è necessario comprendere bene i concetti di classe, oggetto, variabile di classe e metodo.

Classi

Una classe è una struttura per un oggetto. Questo è un pezzo di codice che definisce:

  • Tipi di dati che conterranno gli oggetti della classe creata
  • Funzioni che conterranno questi oggetti.

Quando crei un'applicazione OOP, in genere creerai diverse classi che rappresenteranno i diversi tipi di entità nella tua applicazione. Ad esempio, per creare un forum, puoi creare le classi Forum, Argomento, Post e Membro.

Oggetti

Un oggetto è un tipo speciale di variabile creato tramite una classe. Contiene dati reali e funzioni per manipolarlo. Puoi creare tutti gli oggetti che desideri da una singola classe. Ogni funzione di un oggetto è indipendente da un altro oggetto, anche se creati dalla stessa classe.

Per il confronto con entità reali:

  • Una classe è la struttura di un'auto: definisce come apparirà e si comporterà, ma è ancora un'entità astratta
  • Un oggetto è un'auto reale creata da un wireframe: ha proprietà (come la velocità) e comportamenti reali (come l'accelerazione o la frenata).

Nota: un oggetto è spesso chiamato l'essenza di una classe e il processo di creazione di un oggetto di una classe è chiamato implementazione.

Variabili di classe

I valori dei dati memorizzati in un particolare oggetto vengono scritti in variabili speciali chiamate variabili di classe. Le variabili della classe sono strettamente associate al suo oggetto. Anche se tutti gli oggetti di una classe hanno le stesse variabili, i loro valori possono differire.

Metodi

Le funzioni definite in una classe e utilizzate sugli oggetti di quella classe sono chiamate metodi. Non sono molto diverse dalle funzioni normali: puoi passare loro dei valori, possono contenere variabili locali e restituire valori. Tuttavia, i metodi funzionano più spesso con variabili oggetto. Ad esempio, il metodo login() per registrare gli utenti nel tuo forum potrebbe impostare la variabile della classe loggedIn su true.

Come creare una classe in PHP?

Ora che sai già cosa sono le classi, i metodi, le variabili di classe e gli oggetti, è il momento di creare un paio di classi e oggetti nel codice PHP.

Per prima cosa, vediamo come creare effettivamente una classe. Fondamentalmente, lo script per creare una classe è simile al seguente:

Classe NomeClasse ( // (definizione della classe) )

Ad esempio, se stai creando una classe Membro per il tuo forum, dovresti scrivere questo:

Membro della classe ( // (definizione della classe) )

È abbastanza semplice. Naturalmente, questa classe non farà nulla finché non le aggiungerai variabili e metodi. Tuttavia, il codice precedente crea una classe PHP valida che può essere utilizzata.

Una buona regola pratica: posizionare ciascuna classe in un file separato con lo stesso nome del nome della classe. Ad esempio, inserisci la classe Member nel file Member.php e memorizzala in una cartella, ad esempio, classi.

Come creare oggetti in PHP?

Puoi creare un oggetto usando la nuova parola chiave:

Nuovo nomeclasse()

Questo codice creerà un oggetto della classe ClassName. Dovrai utilizzare questo oggetto in seguito, quindi dovrai memorizzarlo in una variabile. Ad esempio, creiamo un oggetto della classe Member e memorizziamolo nella variabile $member:

$membro = nuovo membro();

Possiamo anche creare un altro oggetto della stessa classe:

$membro2 = nuovo membro();

Anche se abbiamo creato questi due oggetti dalla stessa classe, le variabili $member e $member2 sono indipendenti l'una dall'altra.

Creare variabili di classe

Ora che sappiamo già come creare classi e oggetti di classe, vediamo come creare variabili di classe. Esistono 3 funzioni di accesso per le variabili di classe che possono essere aggiunte a una classe:

  • Variabili di classe pubblica (public): accessibili - ad es. possono essere letti e/o modificati - ovunque nello script, indipendentemente da dove si trova questo codice - all'interno o all'esterno della classe
  • Variabili di classe private (private): accessibili solo ai metodi di classe. È meglio rendere private le variabili di classe per separare gli oggetti dal resto del codice.
  • Variabili di classe protette: disponibili per i metodi della tua stessa classe, nonché per i metodi di classi ereditate (parleremo dell'ereditarietà più tardi).

Per creare una variabile di classe, scrivi la parola chiave public, private o protected, quindi inserisci il nome della variabile:

Classe ClassName ( public $propertyName; private $propertyName; protected $propertyName; )

Aggiungiamo una variabile di classe pubblica alla nostra classe Member per memorizzare il nome utente:

Membro della classe (public $nomeutente = "";)

Nota che abbiamo inizializzato la nostra variabile di classe, il suo valore è la stringa vuota, "". Ciò significa che quando viene creato un nuovo utente, il nome utente verrà impostato automaticamente su una stringa vuota. Proprio come nel caso delle variabili regolari in PHP, le variabili di classe non devono essere inizializzate, ma è meglio non essere pigri. Se non inizializzi una variabile di classe, il suo valore predefinito è null.

Accesso alle variabili di classe

Per accedere a una variabile di un oggetto, utilizzare l'operatore ->:

$oggetto->nomeproprietà

Proviamo. Scriviamo uno script che dichiari una classe Membro e una variabile di classe, crei un oggetto di questa classe, quindi imposti il ​​valore della variabile di classe e lo visualizzi sullo schermo:

nome utente = "Fred"; echo $membro->nome utente; // Stampa "Fred" ?>

Esegui questo codice, verrà visualizzata la stringa "Fred", il valore della variabile di classe $ membro-> nome utente. Come puoi vedere, operi su una variabile oggetto proprio come una variabile normale: puoi impostarla su un valore e leggerla.

Aggiunta di metodi a una classe

Che ne dici di creare metodi? Come ho detto prima, i metodi sono semplicemente funzioni regolari che fanno parte di una classe. Quindi potresti non essere sorpreso dal fatto che vengano creati utilizzando la stessa parola chiave funzione. L'unica differenza rispetto alla creazione di funzioni regolari è che puoi anche aggiungere uno degli identificatori di accesso (pubblico, privato, protetto) alla sua dichiarazione. In questo modo, i metodi sono simili alle variabili di classe:

Classe NomeClasse ( funzione pubblica nomemetodo() ( // (codice) ) funzione privata nomemetodo() ( // (codice) ) funzione protetta nomemetodo() ( // (codice) ) )

Nota: proprio come nel caso delle variabili di classe, i metodi pubblici possono essere chiamati da qualsiasi luogo, i metodi privati ​​possono essere chiamati solo all'interno della classe e i metodi protetti possono essere chiamati dalla classe stessa e dal suo discendente.

Proviamo ad aggiungere alcuni metodi e variabili di classe alla nostra classe:

  • variabile di classe privata $loggedIn per identificare l'utente, ad es. sia che sia entrato o no,
  • login(), che accederà al forum impostando la variabile della classe $loggedIn su true,
  • logout(), che disconnetterà dal forum impostando la variabile della classe $loggedIn su false,
  • isLoggedIn(), che restituirà il valore della variabile di classe $loggedIn.

Ecco il nostro codice:

loggatoIn = vero; ) funzione pubblica logout() ( $this->loggedIn = false; ) funzione pubblica isLoggedIn() ( return $this->loggedIn; ) ) ?>

Potresti aver notato che abbiamo utilizzato una nuova parola chiave $this. Nel contesto dei metodi di un oggetto, la variabile speciale $this si riferisce all'oggetto stesso. Utilizzando $this in un metodo dell'oggetto, il metodo può accedere a qualsiasi variabile di classe e metodo dell'oggetto.

Ad esempio, il metodo login() può accedere alla variabile di classe $loggedIn dell'oggetto tramite $this->loggedIn.

A proposito, la nostra variabile di classe è privata, quindi non può essere chiamata da nessuna parte dello script, ma solo dai metodi login(), logout() e isLoggedIn(). Questo è un buon approccio perché le parti interne dell'oggetto (ad esempio il modo esatto in cui registra se l'utente ha effettuato l'accesso o meno) sono separate dal resto del codice. Quando possibile, prova a utilizzare variabili di classe private in modo che i tuoi oggetti siano autonomi, mobili e protetti.

Nota: la variabile di classe $username nel nostro esempio è pubblica. L'ho fatto solo per dimostrare come è possibile accedere alle variabili di classe di un oggetto. Nei progetti reali, preferiresti rendere questa variabile privata e creare variabili speciali di classe pubblica per impostare i valori del nome utente, se necessario.

Utilizzo dei metodi

Per chiamare un metodo su un oggetto, usa l'operatore ->, con cui hai già acquisito familiarità.

$oggetto->nomemetodo()

Funziona proprio come chiamare una funzione normale. Puoi passare argomenti tra parentesi (supponendo ovviamente che siano necessari argomenti) e la chiamata al metodo può anche restituire valori specifici che puoi quindi utilizzare.

loggatoIn = vero; ) funzione pubblica logout() ( $this->loggedIn = false; ) funzione pubblica isLoggedIn() ( return $this->loggedIn; ) ) $membro = nuovo membro(); $membro->nomeutente = "Fred"; echo $membro->nome utente . "È". ($membro->
"; $member->login(); echo $member->username . " è " . ($member->isLoggedIn() ? "loggato" : "disconnesso") . "
"; $member->logout(); echo $member->username . " è " . ($member->isLoggedIn() ? "loggato" : "disconnesso") . "
"; ?>

Questo script mostrerà quanto segue:

Fred è disconnesso Fred è loggato Fred è disconnesso

Ecco come funziona:

  1. Dopo aver descritto la classe Member, abbiamo creato il suo oggetto e lo abbiamo memorizzato nella variabile $member. Abbiamo anche dato alla variabile di classe $username di questo oggetto il valore "Fred".
  2. Abbiamo quindi chiamato il metodo $member->isLoggedIn() per determinare se l'utente ha effettuato l'accesso o meno. Questo metodo restituisce semplicemente il valore della variabile di classe $ loggedIn. Poiché il valore predefinito di questa variabile di classe è false, il risultato della chiamata a $member->isLoggedIn() sarà false, quindi verrà visualizzato il messaggio "Fred è disconnesso".
  3. Quindi chiamiamo il metodo login(). Imposterà la variabile della classe $ loggedIn su true.
  4. Ora, quando si chiama il metodo $member->isLoggedIn(), verrà restituito true e verrà visualizzato il messaggio "Fred è loggato".
  5. Chiamiamo il metodo logout(), che imposta il valore della proprietà $loggedIn su false.
  6. Chiamiamo il metodo $member->isLoggedIn() una terza volta. Ora restituirà false perché la proprietà $loggedIn è nuovamente impostata su false. Quindi verrà nuovamente visualizzato il messaggio “Fred è disconnesso”.

Nota: nel caso lo avessi visto per primo: ?: è un operatore ternario. Questa è una versione semplificata dei blocchi if...else. Puoi conoscere questi tipi di operatori.

conclusioni

In questo tutorial hai imparato le basi dell'OOP in PHP. Hai imparato cose come:

  • Cos'è l'OOP e perché è utile?
  • concetti di classi, oggetti, variabili di classe e metodi
  • come creare classi e oggetti
  • come creare e utilizzare le variabili di classe
  • concetti di identificatori di accesso pubblici, privati ​​e protetti
  • come creare e utilizzare metodi di classe

Hai già imparato molto a riguardo e imparerai molto di più nelle lezioni seguenti. Tuttavia, se hai seguito tutti gli esempi che ho fornito, hai delle basi solide. Puoi iniziare a creare applicazioni utilizzando OOP.

I migliori articoli sull'argomento