Proprietăți și metode statice în PHP. De ce unii dezvoltatori PHP preferă API-urile statice? Metode statice PHP

Late Static Binding (LSB) a fost un subiect fierbinte de discuție în ultimii trei ani în cercurile de dezvoltare PHP (și în sfârșit l-am prins în PHP 5.3). Dar de ce este nevoie? În acest articol, vom examina exact cum legarea statică tardivă vă poate simplifica foarte mult codul.

La o întâlnire a dezvoltatorilor PHP care a avut loc la Paris în noiembrie 2005, subiectul legăturii statice târzii a fost discutat oficial de echipa de dezvoltare de bază. Au fost de acord să-l pună în aplicare, împreună cu multe alte subiecte care erau pe ordinea de zi. Detaliile urmau să fie convenite prin discuții deschise.

De cand legarea statică tardivă a fost anunțat ca o funcție viitoare, au trecut doi ani. Și în cele din urmă LSB a devenit disponibil pentru utilizare în PHP 5.3. Dar acest eveniment a trecut neobservat de dezvoltatorii care folosesc PHP, din note doar o pagină din manual.

Pe scurt, noua funcționalitate de legare statică tardivă permite obiectelor să moștenească în continuare metode din clasele părinte, dar în plus permite metodelor moștenite să aibă acces la constantele statice, metodele și proprietățile clasei copil, nu doar clasa părinte. Să ne uităm la un exemplu:

Class Beer ( const NUME = "Bere!"; funcția publică getName() ( return self::NAME; ) ) clasa Ale extinde Bere ( const NUME = "Ale!"; ) $bereBăutură = bere nouă; $aleDrink = Ale noua; ecou "Bere este: " . $beerDrink->getName() ."\n"; echo "Ale este: " . $aleDrink->getName() ."\n";

Acest cod va produce următorul rezultat:

Berea este: berea! Ale este: Bere!

Clasă Ale metoda moștenită getName(), dar in acelasi timp de sine indică în continuare clasa în care este folosit (în acest caz clasa Bere). Aceasta a rămas în PHP 5.3, dar cuvântul a fost adăugat static. Să ne uităm din nou la exemplu:

Class Beer ( const NAME = "Bere!"; funcția publică getName() ( return self::NAME; ) public function getStaticName() ( return static::NAME; ) ) clasa Ale extinde Beer ( const NAME = "Ale!" ; ) $beerDrink = bere noua; $aleDrink = Ale noua; ecou "Bere este: " . $beerDrink->getName() ."\n"; echo "Ale este: " . $aleDrink->getName() ."\n"; ecou "Bere este de fapt: " . $beerDrink->getStaticName() ."\n"; echo "Ale este de fapt: " . $aleDrink->getStaticName() ."\n";

Cuvânt cheie nou static indică faptul că este necesar să se folosească o constantă a unei clase moștenite, în locul unei constante care a fost definită în clasa în care este declarată metoda getStaticName(). Cuvânt static a fost adăugat pentru a implementa noi funcționalități și pentru compatibilitate cu versiunea anterioară de sine funcționează la fel ca în versiunile anterioare de PHP.

Pe plan intern, principala diferență (și, de fapt, motivul pentru care legarea este numită tardivă) dintre aceste două metode de acces este că PHP va defini valoarea pentru self::NAMEîn timpul „compilării” (când caracterele PHP sunt convertite în cod de mașină care va fi procesat de motorul Zend) și pentru static::NUME valoarea va fi determinată la pornire (în momentul în care codul mașinii este executat în motorul Zend).

Acesta este un alt instrument pentru dezvoltatorii PHP. În a doua parte ne vom uita la modul în care poate fi folosit pentru totdeauna.

Începând cu PHP 5.3.0, a existat o caracteristică numită late static binding care poate fi folosită pentru a obține o referință la o clasă apelabilă în contextul moștenirii statice.

Mai exact, legarea static tardivă păstrează numele clasei specificate în ultimul „apel neredirecționat”. În cazul apelurilor statice, aceasta este o clasă specificată în mod explicit (de obicei la stânga operatorului :: ); în cazul apelurilor nestatice, aceasta este clasa obiectului. Un „apel redirecționat” este un apel static care începe cu de sine::, mamă::, static::, sau, dacă urcăm în ierarhia clasei, forward_static_call(). Funcţie get_called_class() poate fi folosit pentru a obține un șir cu numele clasei apelate și static:: reprezintă domeniul său de aplicare.

Numele „legare statică tardivă” în sine reflectă implementarea internă a acestei caracteristici. „Legarea tardivă” reflectă faptul că apelează static:: nu va fi calculat în raport cu clasa în care este definită metoda apelată, ci va fi calculat pe baza informațiilor din timpul de execuție. Această caracteristică a fost numită și „legare statică” deoarece poate fi folosită (dar nu trebuie) în metodele statice.

Restricții de sine::

Exemplul #1 Utilizare de sine::

clasa a (
ecou __CLASS__ ;
}
funcţie publică statică
Test()(
sine::cine();
}
}

clasa B se extinde pe A (
funcție publică statică who() (
echo __CLASS__ ;
}
}

B::test();
?>

Utilizarea Legăturii statice tardive

Legarea statică ulterioară încearcă să depășească această limitare prin furnizarea unui cuvânt cheie care face referire la o clasă numită direct în timpul execuției. Pur și simplu, un cuvânt cheie care vă va permite să vă conectați B din Test()în exemplul anterior. S-a decis să nu se introducă un nou cuvânt cheie, ci să se utilizeze static, care este deja rezervat.

Exemplul #2 Ușor de utilizat static::

clasa a (
funcție publică statică who() (
ecou __CLASS__ ;
}
funcţie publică statică
Test()(
static::cine(); // Legarea statică tardivă se aplică aici
}
}

clasa B se extinde pe A (
funcție publică statică who() (
echo __CLASS__ ;
}
}

B::test();
?>

Rezultatul executiei acest exemplu:

cometariu:

Într-un context non-static, clasa apelată va fi cea căreia îi aparține instanța obiectului. Deoarece $acest-> va încerca să apeleze metode private de la aceeași domeniul de aplicare, utilizare static:: poate da rezultate diferite. O altă diferență este că static:: se poate referi numai la câmpurile statice ale unei clase.

Exemplul #3 Utilizare static::într-un context non-static

clasa a (
funcție privată foo() (
echo "succes!\n" ;
}
test de funcționare publică() (
$this -> foo();
static::foo();
}
}

clasa B se extinde pe A (
/* foo() va fi copiat în B, prin urmare domeniul său este încă A,
iar apelul va avea succes*/
}

clasa C se extinde pe A (
funcție privată foo() (
/* metoda originală înlocuită; domeniul de aplicare al noii metode C */
}
}

$b = nou B();
$b -> test();
$c = nou C();
$c -> test(); //neadevarat
?>

Rezultatul rulării acestui exemplu:

succes! succes! succes! Eroare fatală: Apelați la metoda privată C::foo() din contextul „A” din /tmp/test.php pe linia 9

cometariu:

Regiunea de rezolvare a legării statice întârziate va fi fixată de apelul static care o calculează. Pe de altă parte, apelurile statice folosind directive precum mamă:: sau de sine:: redirecționează informațiile despre apel.

Exemplul #4 Apeluri redirecționate și neredirecționate

Nu este un secret pentru nimeni că oamenilor le place să pună întrebări dificile în timpul interviurilor. Nu întotdeauna adecvat, nu întotdeauna legat de realitate, dar faptul rămâne un fapt - se întreabă. Desigur, întrebarea este diferită și, uneori, o întrebare care la prima vedere ți se pare stupidă are de fapt scopul de a testa cât de bine cunoști limba în care scrii.

Să încercăm să demontăm una dintre aceste întrebări „bucată cu bucată” - Ce înseamnă cuvântul „static” în PHP și de ce este folosit?

Cuvântul cheie static are trei semnificații diferite în PHP. Să le privim în ordine cronologică, așa cum au apărut în limbă.

Prima valoare este o variabilă locală statică

function foo() ( $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 foo(); // 0 foo(); // 0

În PHP, variabilele sunt locale. Aceasta înseamnă că o variabilă definită și dată cu o valoare într-o funcție (metodă) există doar în timpul execuției acelei funcție (metodă). Când metoda iese, variabila locală este distrusă, iar când reintră, este creată din nou. În codul de mai sus, o astfel de variabilă locală este variabila $a - există numai în interiorul funcției foo() și este creată din nou de fiecare dată când această funcție este apelată. Creșterea unei variabile în acest cod este lipsită de sens, deoarece chiar pe următoarea linie de cod funcția își va termina activitatea și valoarea variabilei se va pierde. Indiferent de câte ori numim funcția foo(), aceasta va scoate întotdeauna 0...

Totuși, totul se schimbă dacă punem cuvântul cheie static înainte de atribuire:

Funcția foo() ( static $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 foo(); // 1 foo(); // 2

Cuvântul cheie static, scris înainte de a atribui o valoare unei variabile locale, are următoarele efecte:

  1. Atribuirea se efectuează o singură dată, la primul apel la funcție
  2. Valoarea unei variabile marcate în acest fel este salvată după terminarea funcției.
  3. La apelurile ulterioare la funcție, în loc de atribuire, variabila primește valoarea stocată anterior
Această utilizare a cuvântului static se numește variabilă locală statică.
Capcanele variabilelor statice
Desigur, ca întotdeauna în PHP, există câteva capcane.

Prima piatră este că numai constante sau expresii constante pot fi atribuite unei variabile statice. Iată codul:
static $a = bar();
va duce inevitabil la o eroare de analiză. Din fericire, începând cu versiunea 5.6, a devenit posibilă alocarea nu numai de constante, ci și de expresii constante (de exemplu, „1+2” sau „”), adică expresii care nu depind de alt cod și pot fi calculate la etapa de compilare

A doua piatră este că metodele există într-o singură copie.
Aici totul este puțin mai complicat. Pentru a înțelege esența, iată codul:
clasa A (funcția publică foo() ( static $x = 0; echo ++$x; ) ) $a1 = nou A; $a2 = A nou; $a1->foo(); // 1 $a2->foo(); // 2 $a1->foo(); // 3 $a2->foo(); // 4
Spre deosebire de așteptările intuitive „diferite obiecte - metode diferite”, vedem clar în acest exemplu că metodele dinamice din PHP „nu se înmulțesc”. Chiar dacă avem o sută de obiecte din această clasă, metoda va exista doar într-o singură instanță; doar că un $this diferit va fi aruncat în ea cu fiecare apel.

Acest comportament poate fi neașteptat pentru un dezvoltator care nu este pregătit pentru el și poate fi o sursă de erori. Trebuie remarcat faptul că moștenirea clasei (și a metodei) duce la crearea unei noi metode:

Clasa A (funcția publică foo() ( static $x = 0; echo ++$x; ) ) clasa B extinde A ( ) $a1 = nou A; $b1 = nou B; $a1->foo(); // 1 $b1->foo(); // 1 $a1->foo(); // 2 $b1->foo(); // 2

Concluzie: Metodele dinamice în PHP există în contextul claselor, nu al obiectelor. Și numai în timpul de execuție apare substituția „$this = current_object”.

Al doilea sens este proprietățile statice și metodele claselor

În modelul obiect PHP, este posibil să setați proprietăți și metode nu numai pentru obiecte - instanțe ale unei clase, ci și pentru clasă în ansamblu. Cuvântul cheie static este folosit și pentru aceasta:

Clasa A ( public static $x = "foo"; public static function test() ( return 42; ) ) echo A::$x; // „foo” echo A::test(); // 42
Pentru a accesa astfel de proprietăți și metode, sunt folosite construcții cu două puncte („Paamayim Nekudotayim”), cum ar fi CLASS_NAME::$Variablename și CLASS_NAME::Methodname().

Este de la sine înțeles că proprietățile statice și metodele statice au propriile lor caracteristici și capcane pe care trebuie să le cunoașteți.

Prima caracteristică este banală - nu există $this. De fapt, aceasta provine din însăși definiția unei metode statice - deoarece este asociată cu o clasă, nu cu un obiect, această pseudovariabilă $, care indică obiectul curent în metodele dinamice, nu este disponibilă. Ceea ce este complet logic.

Totuși, trebuie să știți că, spre deosebire de alte limbi, PHP nu detectează situația „$this is written in a static method” la etapa de analiză sau compilare. O eroare ca aceasta poate apărea numai în timpul rulării dacă încercați să executați cod cu $this într-o metodă statică.

Cod astfel:
clasa A ( public $id = 42; funcția publică statică foo() ( echo $this->id; ) )
nu va provoca erori, atâta timp cât nu încercați să utilizați metoda foo() în mod necorespunzător:
$a = A nou; $a->foo(); (și imediat obțineți „Eroare fatală: Folosind $this când nu este în contextul obiectului”)

A doua caracteristică este că static nu este o axiomă!
clasa A (funcție publică statică foo() ( echo 42; ) ) $a = nou A; $a->foo();
Asta e, da. O metodă statică, dacă nu conține $this în cod, poate fi apelată într-un context dinamic, ca o metodă obiect. Aceasta nu este o eroare în PHP.

Reversul nu este în întregime adevărat:
clasa A (funcția publică foo() ( echo 42; ) ) A::foo();
O metodă dinamică care nu utilizează $this poate fi executată într-un context static. Cu toate acestea, veți primi un avertisment „Metoda non-statică A::foo() nu trebuie apelată static” la nivelul E_STRICT. Depinde de dvs. să decideți dacă respectați cu strictețe standardele de cod sau suprimați avertismentele. Prima, desigur, este de preferat.

Și apropo, tot ce este scris mai sus se aplică doar metodelor. Utilizarea unei proprietăți statice prin „->” este imposibilă și duce la o eroare fatală.

Al treilea sens, care pare a fi cel mai dificil - legarea statică târzie

Dezvoltatorii limbajului PHP nu s-au oprit la două valori cuvânt cheie„static” și în versiunea 5.3 au adăugat o altă „funcție” a limbajului, care este implementată cu același cuvânt! Se numește „legare statică târzie” sau LSB (legare statică târzie).

Cel mai simplu mod de a înțelege esența LSB este cu exemple simple:

Model de clasă ( public static $table = "tabel"; funcția publică statică getTable() ( return self::$table; ) ) echo Model::getTable(); // "masa"
Cuvântul cheie self în PHP înseamnă întotdeauna „numele clasei în care este scris acest cuvânt”. În acest caz, self este înlocuit cu clasa Model și self::$table cu Model::$table.
Această caracteristică de limbă se numește „legare statică timpurie”. De ce devreme? Deoarece legarea lui self și a unui anumit nume de clasă nu are loc în timpul de execuție, ci în etapele anterioare - analizarea și compilarea codului. Ei bine, „static” - pentru că despre care vorbim despre proprietăți și metode statice.

Să ne schimbăm puțin codul:

Model de clasă ( public static $table = "tabel"; funcția publică statică getTable() ( return self::$table; ) ) class User extinde Model (public static $table = "utilizatori"; ) echo User::getTable() ; // "masa"

Acum înțelegeți de ce PHP se comportă neintuitiv în această situație. self a fost asociat cu clasa Model atunci când nu se știa nimic despre clasa User și, prin urmare, indică Model.

Ce ar trebuii să fac?

Pentru a rezolva această dilemă, în etapa de execuție a fost inventat un mecanism de legare „târzie”. Funcționează foarte simplu - scrieți doar „static” în loc de cuvântul „self” și conexiunea se va stabili cu clasa care apelează acest cod, și nu cu cea în care este scris:
clasă Model ( public static $table = "tabel"; public static function getTable() ( return static::$table; ) ) class User extinde Model (public static $table = "utilizatori"; ) echo User::getTable() ; // "utilizatori"

Aceasta este misterioasa „legare statică târzie”.

Trebuie remarcat faptul că, pentru o mai mare comoditate în PHP, pe lângă cuvântul „static” există și functie speciala get_called_class(), care vă va spune ce clasă se află în context acest moment codul tău funcționează.

Interviuri fericite!

Îmi doream de mult să scriu pe această temă. Primul imbold a fost articolul lui Miško Hevery „Metodele statice sunt moartea la testabilitate”. Am scris un articol de răspuns, dar nu l-am publicat niciodată. Dar recent am văzut ceva care se poate numi „Programare orientată spre clasă”. Acest lucru mi-a reîmprospătat interesul față de subiect și acesta este rezultatul.

„Class-Oriented Programming” este atunci când sunt folosite clase care constau numai din metode și proprietăți statice, iar o instanță a clasei nu este niciodată creată. În acest articol voi vorbi despre:

  • nu oferă niciun avantaj față de programarea procedurală
  • nu renunta la obiecte
  • prezența membrilor clasei statice! = moarte la teste
Deși acest articol este despre PHP, conceptele se aplică și altor limbi.

Dependente

De obicei, codul depinde de alt cod. De exemplu:

$foo = substr($bar, 42);
Acest cod depinde de variabila $bar și de funcția substr. $bar este doar o variabilă locală definită puțin mai sus în același fișier și în același domeniu. substr este o funcție de bază PHP. Totul este simplu aici.

Acum, acest exemplu:

Clasa BloomFilter ( ... function public __construct($m, $k) ( ... ) public static function getK($m, $n) ( return ceil(($m / $n) * log(2)); ) ... )
Această mică funcție de ajutor oferă pur și simplu un wrapper pentru un algoritm specific care ajută la calcularea unui număr bun pentru argumentul $k folosit în constructor. Deoarece trebuie apelat înainte ca instanța de clasă să fie creată, trebuie să fie static. Acest algoritm nu are dependențe externe și este puțin probabil să fie înlocuit. Se foloseste astfel:

$m = 10000; $n = 2000; $b = nou BloomFilter($m, BloomFilter::getK($m, $n));
Acest lucru nu creează dependențe suplimentare. Clasa depinde de ea însăși.

  • Constructor alternativ. Un exemplu bun este clasa DateTime încorporată în PHP. Instanța sa poate fi creată de doi căi diferite:

    $date = new DateTime("2012-11-04"); $date = DateTime::createFromFormat("d-m-Y", "04-11-2012");
    În ambele cazuri, rezultatul va fi o instanță DateTime și, în ambele cazuri, codul este legat de clasa DateTime într-un fel sau altul. Metoda statică DateTime::createFromFormat este un constructor de obiect alternativ care returnează același lucru ca nou DateTime, dar cu funcționalitate suplimentară. Acolo unde puteți scrie o nouă clasă, puteți scrie și Class::method() . Acest lucru nu creează noi dependențe.

  • Alte utilizări ale metodelor statice afectează legarea și pot crea dependențe implicite.

    Un cuvânt despre abstracție

    De ce toată această agitație cu dependențe? Abilitatea de a abstractiza! Pe măsură ce produsul tău crește, complexitatea acestuia crește. Iar abstractizarea este cheia gestionării complexității.

    De exemplu, aveți o clasă Aplicație care reprezintă aplicația dvs. Vorbește cu clasa User, care este o reprezentare a utilizatorului. Care primește date de la baza de date. Clasa Database are nevoie de un DatabaseDriver. DatabaseDriver are nevoie de parametri de conexiune. Și așa mai departe. Dacă doar apelați Application::start() static, care va apela User::getData() static, care va apela DB static și așa mai departe și sperați că fiecare strat își va sorta dependențele, puteți ajunge cu o mizerie groaznică dacă ceva nu merge bine în acest fel. Este imposibil de ghicit dacă apelarea Application::start() va funcționa, deoarece nu este deloc evident cum se vor comporta dependențele interne. Și mai rău, singura modalitate de a influența comportamentul Application::start() este schimbarea sursă această clasă și codul claselor pe care le numește și codul claselor care numesc acele clase... în casa pe care a construit-o Jack.

    Cea mai eficientă abordare atunci când creați aplicații complexe este de a crea părți separate pe care să le puteți construi mai târziu. Părți la care poți să nu te mai gândești, în care poți avea încredere. De exemplu, atunci când apelați o bază de date statică::fetchAll(...) , nu există nicio garanție că o conexiune la baza de date a fost deja stabilită sau va fi stabilită.

    Funcție(Bază de date $bază de date) ( ... )
    Dacă codul din această funcție este executat, înseamnă că instanța Bază de date a fost trecută cu succes, ceea ce înseamnă că instanța obiectului Bază de date a fost creată cu succes. Dacă clasa Database este proiectată corect, atunci puteți fi sigur că prezența unei instanțe a acestei clase înseamnă capacitatea de a efectua interogări baze de date. Dacă nu există nicio instanță a clasei, corpul funcției nu va fi executat. Aceasta înseamnă că funcției nu ar trebui să-i pese de starea bazei de date; clasa Database va face asta singură. Această abordare vă permite să uitați de dependențe și să vă concentrați pe rezolvarea problemelor.

    Fără capacitatea de a nu te gândi la dependențe și la dependențele acelor dependențe, este aproape imposibil să scrii vreo aplicație complexă. Baza de date poate fi o clasă mică de înveliș sau un monstru uriaș cu mai multe straturi, cu o grămadă de dependențe, poate începe ca un mic înveliș și poate muta în timp într-un monstru uriaș, puteți moșteni clasa Database și o puteți transmite unei funcții descendente. , nimic din toate acestea nu contează pentru funcția dvs. (baza de date $ baza de date), atâta timp cât interfața publică a bazei de date nu se modifică. Dacă clasele dvs. sunt separate corect de restul aplicației folosind injecția de dependență, puteți testa fiecare dintre ele folosind stub-uri în loc de dependențele lor. Odată ce ați testat clasa suficient pentru a vă asigura că funcționează conform așteptărilor, puteți elimina presupunerile din ea pur și simplu știind că trebuie să utilizați o instanță a bazei de date pentru a lucra cu baza de date.

    Programarea orientată spre clasă este stupidă. Învață să folosești OOP.

    Acțiune