Statička svojstva i metode u PHP-u. Zašto neki PHP programeri preferiraju statičke API-je? Php statičke metode

Late Static Binding (LSB) je bila vruća tema diskusije u posljednje tri godine u krugovima PHP razvojnih (i konačno smo ga dobili u PHP 5.3). Ali zašto je to potrebno? U ovom članku ćemo ispitati kako tačno kasno statičko vezivanje može uvelike pojednostaviti vaš kod.

Na sastanku PHP programera održanom u Parizu u novembru 2005. godine, glavni razvojni tim je formalno raspravljao o temi kasnog statičkog povezivanja. Dogovorili su se da ga provedu, uz mnoge druge teme koje su bile na dnevnom redu. O detaljima je trebalo da se dogovorimo putem otvorenih diskusija.

Pošto kasno statičko vezivanje je najavljen kao nadolazeći igrani film, prošle su dvije godine. I konačno je LSB postao dostupan za upotrebu u PHP 5.3. Ali ovaj događaj je prošao nezapaženo od strane programera koji koriste PHP, od napomena samo na stranici u priručniku.

Ukratko, nova funkcija kasnog statičkog povezivanja omogućava objektima da i dalje nasljeđuju metode iz roditeljskih klasa, ali pored toga dozvoljava naslijeđenim metodama da imaju pristup statičkim konstantama, metodama i svojstvima podređene klase, a ne samo roditeljske klase. Pogledajmo primjer:

Klasa Pivo ( const NAME = "Pivo!"; javna funkcija getName() ( vrati self::NAME; ) ) klasa Ale proširuje Pivo ( const NAME = "Ale!"; ) $beerDrink = novo pivo; $aleDrink = novo pivo; echo "Pivo je: " . $beerDrink->getName() ."\n"; echo "Ale je: " . $aleDrink->getName() ."\n";

Ovaj kod će proizvesti sljedeći rezultat:

Pivo je: Pivo! Pivo je: Pivo!

Klasa Ale naslijeđena metoda getName(), ali istovremeno self i dalje ukazuje na klasu u kojoj se koristi (u ovom slučaju klasu Pivo). Ovo je ostalo u PHP 5.3, ali je reč dodata statički. Pogledajmo još jednom primjer:

Klasa Pivo ( const NAME = "Pivo!"; javna funkcija getName() ( vrati self::NAME; ) javna funkcija getStaticName() ( return static::NAME; ) ) klasa Ale proširuje Pivo ( const NAME = "Ale!" ) $beerDrink = novo pivo; $aleDrink = novo pivo; echo "Pivo je: " . $beerDrink->getName() ."\n"; echo "Ale je: " . $aleDrink->getName() ."\n"; echo "Pivo je zapravo: " . $beerDrink->getStaticName() ."\n"; echo "Ale je zapravo: " . $aleDrink->getStaticName() ."\n";

Nova ključna riječ statički označava da je potrebno koristiti konstantu naslijeđene klase umjesto konstante koja je definirana u klasi u kojoj je metoda deklarirana getStaticName(). Riječ statički je dodat za implementaciju nove funkcionalnosti i za kompatibilnost unatrag self radi isto kao u prethodnim verzijama PHP-a.

Interno, glavna razlika (i, zapravo, razlog zašto se vezivanje naziva kasnim) između ova dva pristupa je u tome što će PHP definirati vrijednost za self::NAME tokom "kompilacije" (kada se PHP karakteri konvertuju u mašinski kod koji će obraditi Zend engine), i za statički::NAME vrijednost će biti određena pri pokretanju (u trenutku kada se strojni kod izvršava u Zend motoru).

Ovo je još jedan alat za PHP programere. U drugom dijelu ćemo pogledati kako se može iskoristiti za dobro.

Počevši od PHP 5.3.0, postojala je karakteristika koja se zove kasno statičko vezivanje koja se može koristiti za dobijanje reference na pozivnu klasu u kontekstu statičkog nasleđa.

Preciznije, kasno statičko vezivanje čuva ime klase specificirane u posljednjem "neproslijeđenom pozivu". U slučaju statičkih poziva, ovo je eksplicitno specificirana klasa (obično lijevo od operatora :: ); u slučaju nestatičkih poziva, ovo je klasa objekta. "Preusmjereni poziv" je statički poziv koji počinje sa ja::, roditelj::, statički::, ili, ako krenemo gore po hijerarhiji klasa, forward_static_call(). Funkcija get_called_class() može se koristiti za dobivanje stringa s imenom pozvane klase, i statički:: predstavlja njen obim.

Sam naziv "kasno statičko povezivanje" odražava internu implementaciju ove karakteristike. "Kasno uvezivanje" odražava činjenicu koja se probija statički:: neće se izračunati u odnosu na klasu u kojoj je definisana pozvana metoda, već će se izračunati na osnovu informacija u vremenu izvođenja. Ova karakteristika je takođe nazvana "statičko povezivanje" jer se može koristiti (ali ne mora) u statičkim metodama.

Ograničenja ja::

Primjer #1 Upotreba ja::

klasa A (
echo __CLASS__ ;
}
javna statička funkcija
test()(
self::who();
}
}

klasa B produžava A (
javna statička funkcija who() (
echo __CLASS__ ;
}
}

B::test();
?>

Korištenje kasnog statičkog povezivanja

Kasnije statičko vezivanje pokušava da prevaziđe ovo ograničenje tako što daje ključnu reč koja upućuje na klasu koja se poziva direktno u vreme izvođenja. Jednostavno rečeno, ključna riječ koja će vam omogućiti povezivanje B od test() u prethodnom primjeru. Odlučeno je da se ne uvede nova ključna riječ, već da se koristi statički, koji je već rezervisan.

Primjer #2 Jednostavan za korištenje statički::

klasa A (
javna statička funkcija who() (
echo __CLASS__ ;
}
javna statička funkcija
test()(
static::who(); // Ovdje se primjenjuje kasno statičko vezivanje
}
}

klasa B produžava A (
javna statička funkcija who() (
echo __CLASS__ ;
}
}

B::test();
?>

Rezultat izvršenja ovaj primjer:

Komentar:

U nestatičnom kontekstu, pozvana klasa će biti ona kojoj instanca objekta pripada. Zbog $this->će pokušati pozvati privatne metode iz istog obim, upotreba statički:: može dati različite rezultate. Druga razlika je u tome statički:: može se odnositi samo na statička polja klase.

Primjer #3 Upotreba statički:: u nestatičnom kontekstu

klasa A (
privatna funkcija foo() (
echo "uspjeh!\n" ;
}
test javnih funkcija() (
$this -> foo();
static::foo();
}
}

klasa B produžava A (
/* foo() će biti kopiran u B, stoga je njegov opseg i dalje A,
i poziv će biti uspješan*/
}

klasa C produžava A (
privatna funkcija foo() (
/* originalna metoda zamijenjena; opseg nove C metode */
}
}

$b = novi B();
$b -> test();
$c = novi C();
$c -> test(); //nije istina
?>

Rezultat pokretanja ovog primjera:

uspjeh! uspjeh! uspjeh! Fatalna greška: Poziv privatnoj metodi C::foo() iz konteksta "A" u /tmp/test.php na liniji 9

Komentar:

Područje za rješavanje kasnog statičkog vezivanja će biti fiksirano statičkim pozivom koji ga izračunava. S druge strane, statički pozivi koji koriste direktive kao što su roditelj:: ili ja:: informacije o preusmjeravanju poziva.

Primjer #4 Preusmjereni i nepreusmjereni pozivi

Nije tajna da ljudi vole da postavljaju škakljiva pitanja tokom intervjua. Ne uvijek adekvatno, nije uvijek povezano sa stvarnošću, ali činjenica ostaje činjenica - pitaju se. Naravno, pitanje je drugačije, a ponekad pitanje koje vam se na prvi pogled čini glupim zapravo ima za cilj da provjeri koliko dobro poznajete jezik na kojem pišete.

Pokušajmo jedno od ovih pitanja razdvojiti "komad po dio" - Šta znači riječ “statičan” u PHP-u i zašto se koristi?

Ključna riječ static ima tri različita značenja u PHP-u. Pogledajmo ih hronološkim redom, kako su se pojavili u jeziku.

Prva vrijednost je statička lokalna varijabla

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

U PHP-u, varijable su lokalne. To znači da promenljiva definisana i data vrednost unutar funkcije (metoda) postoji samo tokom izvršavanja te funkcije (metode). Kada metoda izađe, lokalna varijabla se uništava, a kada ponovo uđe, kreira se iznova. U kodu iznad, takva lokalna varijabla je varijabla $a - ona postoji samo unutar funkcije foo() i kreira se iznova svaki put kada se ova funkcija pozove. Povećanje varijable u ovom kodu je besmisleno, jer će u sljedećem redu koda funkcija završiti svoj rad i vrijednost varijable će biti izgubljena. Bez obzira koliko puta pozovemo funkciju foo(), ona će uvijek ispisati 0...

Međutim, sve se mijenja ako stavimo ključnu riječ static ispred zadatka:

Funkcija foo() ( statički $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 foo(); // 1 foo(); // 2

Statična ključna riječ, napisana prije dodjeljivanja vrijednosti lokalnoj varijabli, ima sljedeće efekte:

  1. Dodjela se obavlja samo jednom, pri prvom pozivu funkcije
  2. Vrijednost varijable označene na ovaj način se pohranjuje nakon završetka funkcije.
  3. Prilikom narednih poziva funkcije, umjesto dodjele, varijabla prima prethodno pohranjenu vrijednost
Ova upotreba riječi statički se naziva statička lokalna varijabla.
Zamke statičkih varijabli
Naravno, kao i uvijek u PHP-u, postoje neke zamke.

Prvi kamen je da se samo konstante ili konstantni izrazi mogu dodijeliti statičkoj varijabli. Evo koda:
statički $a = bar();
će neizbježno dovesti do greške parsera. Srećom, počevši od verzije 5.6, postalo je moguće dodijeliti ne samo konstante, već i konstantne izraze (na primjer, “1+2” ili “”), odnosno izraze koji ne ovise o drugom kodu i mogu se izračunati u fazi kompilacije

Drugi kamen je da metode postoje u jednoj kopiji.
Ovdje je sve malo komplikovanije. Da biste razumjeli suštinu, evo koda:
klasa A ( javna funkcija foo() ( statički $x = 0; echo ++$x; ) ) $a1 = novo A; $a2 = novo A; $a1->foo(); // 1 $a2->foo(); // 2 $a1->foo(); // 3 $a2->foo(); // 4
Suprotno intuitivnom očekivanju „različiti objekti - različite metode“, u ovom primjeru jasno vidimo da se dinamičke metode u PHP-u „ne množe“. Čak i ako imamo stotinu objekata ove klase, metoda će postojati samo u jednoj instanci, samo će joj se sa svakim pozivom ubaciti drugačiji $this.

Ovo ponašanje može biti neočekivano za programera koji nije spreman za to i može biti izvor grešaka. Treba napomenuti da nasljeđivanje klasa (i metoda) dovodi do stvaranja nove metode:

Klasa A ( javna funkcija foo() ( statički $x = 0; echo ++$x; ) ) klasa B proširuje A ( ) $a1 = novo A; $b1 = novo B; $a1->foo(); // 1 $b1->foo(); // 1 $a1->foo(); // 2 $b1->foo(); // 2

Zaključak: Dinamičke metode u PHP-u postoje u kontekstu klasa, a ne objekata. I samo u vremenu izvođenja dolazi do zamjene “$this = current_object”.

Drugo značenje su statička svojstva i metode klasa

U PHP objektnom modelu moguće je postaviti svojstva i metode ne samo za objekte - instance klase, već i za klasu u cjelini. Za ovo se također koristi ključna riječ static:

Klasa A ( public static $x = "foo"; javna statička funkcija test() ( return 42; ) ) echo A::$x; // "foo" echo A::test(); // 42
Za pristup takvim svojstvima i metodama koriste se konstrukcije sa dvostrukom dvotočkom (“Paamayim Nekudotayim”), kao što su CLASS_NAME::$Variablename i CLASS_NAME::Methodname().

Podrazumijeva se da statička svojstva i statičke metode imaju svoje karakteristike i zamke koje morate znati.

Prva karakteristika je banalna - nema $ovo. Zapravo, ovo proizilazi iz same definicije statičke metode - budući da je povezana sa klasom, a ne sa objektom, $this pseudo-varijabla, koja ukazuje na trenutni objekat u dinamičkim metodama, nije dostupna. Što je potpuno logično.

Međutim, morate znati da, za razliku od drugih jezika, PHP ne otkriva situaciju “$this ispised in a static method” u fazi raščlanjivanja ili kompilacije. Ovakva greška se može pojaviti samo u vrijeme izvođenja ako pokušate izvršiti kod s $this unutar statičke metode.

Kod ovako:
klasa A ( public $id = 42; statička javna funkcija foo() ( echo $this->id; ) )
neće uzrokovati nikakve greške, sve dok ne pokušate koristiti metodu foo() na neprikladan način:
$a = novo A; $a->foo(); (i odmah dobijete “Fatalna greška: korištenje $this kada nije u kontekstu objekta”)

Druga karakteristika je da statika nije aksiom!
klasa A ( statička javna funkcija foo() ( echo 42; ) ) $a = novo A; $a->foo();
To je to, da. Statička metoda, ako ne sadrži $this u kodu, može se pozvati u dinamičkom kontekstu, kao objektna metoda. Ovo nije greška u PHP-u.

Obrnuto nije sasvim tačno:
klasa A ( javna funkcija foo() ( echo 42; ) ) A::foo();
Dinamička metoda koja ne koristi $this može se izvršiti u statičkom kontekstu. Međutim, dobićete upozorenje "Nestatička metoda A::foo() ne treba se zvati statički" na nivou E_STRICT. Na vama je da odlučite hoćete li striktno slijediti standarde koda ili potisnuti upozorenja. Prvi je, naravno, poželjniji.

I usput, sve gore napisano vrijedi samo za metode. Korištenje statičkog svojstva preko "->" je nemoguće i dovodi do fatalne greške.

Treće značenje, koje se čini najteže - kasno statičko vezivanje

Programeri PHP jezika nisu se zaustavili na dvije vrijednosti ključna riječ“statičan”, au verziji 5.3 dodali su još jednu “karakteristiku” jezika, koja je implementirana istom riječju! To se zove "kasno statično povezivanje" ili LSB (kasno statičko povezivanje).

Najlakši način da shvatite suštinu LSB-a je na jednostavnim primjerima:

Model klase ( public static $table = "table"; javna statička funkcija getTable() (vrat self::$table; ) ) echo Model::getTable(); // "stol"
Ključna riječ self u PHP-u uvijek znači "ime klase u kojoj je ova riječ napisana." U ovom slučaju, self se zamjenjuje klasom Model, a self::$table sa Model::$table.
Ova jezička karakteristika se naziva "rano statičko povezivanje". Zašto rano? Zato što se povezivanje selfa i specifičnog imena klase ne dešava u runtime-u, već u ranijim fazama - raščlanjivanju i kompajliranju koda. Pa, “statična” - jer mi pričamo o tome o statičkim svojstvima i metodama.

Promenimo malo naš kod:

Model klase (javni statički $table = "tablica"; javna statička funkcija getTable() (vraćanje self::$table; ) ) klasa Korisnik proširuje model (javni statički $table = "users"; ) echo User::getTable() ; // "stol"

Sada razumete zašto se PHP ponaša neintuitivno u ovoj situaciji. self je bio povezan sa klasom Model kada se ništa nije znalo o klasi User, i stoga ukazuje na Model.

Sta da radim?

Da bi se riješila ova dilema, izmišljen je mehanizam "kasnog" povezivanja u fazi izvođenja. Radi vrlo jednostavno - samo napišite "static" umjesto riječi "self" i veza će se uspostaviti s klasom koja poziva ovaj kod, a ne s onom u kojoj je napisan:
class Model ( public static $table = "table"; javna statička funkcija getTable() ( return static::$table; ) ) class Korisnik proširuje model ( public static $table = "users"; ) echo User::getTable() ; // "korisnici"

Ovo je tajanstveno “kasno statičko vezivanje”.

Treba napomenuti da radi veće pogodnosti u PHP-u, pored reči „statičan“ postoji i reč posebna funkcija get_called_class(), koji će vam reći koja je klasa u kontekstu ovog trenutka vaš kod radi.

Srećni intervjui!

Odavno sam htela da pišem na ovu temu. Prvi poticaj bio je članak Miška Heveryja "Statičke metode su smrt za provjeru". Napisao sam odgovor na članak, ali ga nikad nisam objavio. Ali nedavno sam vidio nešto što se može nazvati “Programiranje orijentirano na klasu”. Ovo je osvježilo moje interesovanje za temu i ovo je rezultat.

"Programiranje orijentirano na klasu" je kada se koriste klase koje se sastoje samo od statičkih metoda i svojstava, a instanca klase se nikada ne kreira. U ovom članku ću govoriti o:

  • ne pruža nikakve prednosti u odnosu na proceduralno programiranje
  • ne odustajte od predmeta
  • prisustvo statičnih članova klase = smrt testovima!
Iako je ovaj članak o PHP-u, koncepti se primjenjuju i na druge jezike.

Zavisnosti

Tipično, kod ovisi o drugom kodu. Na primjer:

$foo = substr($bar, 42);
Ovaj kod zavisi od varijable $bar i funkcije substr. $bar je samo lokalna varijabla definirana malo više u istoj datoteci i u istom opsegu. substr je osnovna funkcija PHP-a. Ovdje je sve jednostavno.

Sada, ovaj primjer:

Class BloomFilter ( ... javna funkcija __construct($m, $k) ( ... ) javna statička funkcija getK($m, $n) ( return ceil(($m / $n) * log(2)); ) ... )
Ova mala pomoćna funkcija jednostavno pruža omotač za određeni algoritam koji pomaže izračunati dobar broj za argument $k koji se koristi u konstruktoru. Jer mora se pozvati prije kreiranja instance klase, mora biti statična. Ovaj algoritam nema eksterne zavisnosti i malo je verovatno da će biti zamenjen. Koristi se ovako:

$m = 10000; $n = 2000; $b = novi BloomFilter($m, BloomFilter::getK($m, $n));
Ovo ne stvara nikakve dodatne zavisnosti. Klasa zavisi od sebe.

  • Alternativni konstruktor. Dobar primjer je klasa DateTime ugrađena u PHP. Njegovu instancu mogu kreirati dvije Različiti putevi:

    $date = new DateTime("2012-11-04"); $date = DateTime::createFromFormat("d-m-Y", "04-11-2012");
    U oba slučaja, rezultat će biti DateTime instanca, au oba slučaja kod je vezan za klasu DateTime na ovaj ili onaj način. Statička metoda DateTime::createFromFormat je alternativni konstruktor objekata koji vraća istu stvar kao novi DateTime, ali s dodatnom funkcionalnošću. Gdje možete napisati novu Class, možete također napisati Class::method() . Ovo ne stvara nikakve nove zavisnosti.

  • Druge upotrebe statičkih metoda utiču na vezivanje i mogu stvoriti implicitne zavisnosti.

    Nekoliko riječi o apstrakciji

    Čemu sva ova gužva sa ovisnostima? Sposobnost apstrakcije! Kako vaš proizvod raste, povećava se i njegova složenost. A apstrakcija je ključ za upravljanje složenošću.

    Na primjer, imate klasu Application koja predstavlja vašu aplikaciju. Razgovara sa klasom User, koja je reprezentacija korisnika. Koji prima podatke iz baze podataka. Klasi Database je potreban DatabaseDriver. DatabaseDriver-u su potrebni parametri veze. I tako dalje. Ako jednostavno pozovete Application::start() statički, koji će pozvati User::getData() statički, koji će pozvati DB statički i tako dalje, i nada se da će svaki sloj riješiti svoje ovisnosti, možete završiti s užasan nered ako nešto krene naopako ne ovako. Nemoguće je pretpostaviti da li će pozivanje Application::start() raditi jer uopće nije očigledno kako će se ponašati interne zavisnosti. Što je još gore, jedini način da utičete na ponašanje Application::start() je promena izvor ovu klasu i šifru klasa koje ona poziva i šifru klasa koje zovu te klase... u kući koju je Jack napravio.

    Najefikasniji pristup pri kreiranju složenih aplikacija je kreiranje zasebnih dijelova na kojima kasnije možete graditi. Dijelovi o kojima možete prestati razmišljati, u koje možete biti sigurni. Na primjer, kada pozivate statičku Database::fetchAll(...) , ne postoji garancija da je veza s bazom podataka već uspostavljena ili će biti uspostavljena.

    Funkcija (baza podataka $database) ( ... )
    Ako se kod unutar ove funkcije izvrši, to znači da je instanca baze podataka uspješno proslijeđena, što znači da je instanca objekta baze podataka uspješno kreirana. Ako je klasa Database ispravno dizajnirana, onda možete biti sigurni da prisustvo instance ove klase znači mogućnost izvođenja upita baze podataka. Ako ne postoji instanca klase, tijelo funkcije se neće izvršiti. To znači da funkcija ne treba da brine o stanju baze podataka, klasa će to učiniti sama. Ovaj pristup vam omogućava da zaboravite na ovisnosti i koncentrišete se na rješavanje problema.

    Bez mogućnosti da se ne razmišlja o zavisnostima i zavisnostima tih zavisnosti, gotovo je nemoguće napisati bilo kakvu složenu aplikaciju. Baza podataka može biti mala klasa omotača ili ogromno višeslojno čudovište sa gomilom zavisnosti, može početi kao mali omotač i vremenom mutirati u ogromno čudovište, možete naslijediti klasu baze podataka i proslijediti je funkciji potomaka , ništa od ovoga nije važno za vašu funkciju (baza podataka $ baza podataka) sve dok se javni interfejs baze podataka ne promijeni. Ako su vaše klase pravilno odvojene od ostatka aplikacije pomoću injekcije zavisnosti, možete testirati svaku od njih koristeći stubove umjesto njihovih ovisnosti. Nakon što ste dovoljno testirali klasu da biste bili sigurni da radi kako se očekuje, možete ukloniti nagađanje jednostavno znajući da trebate koristiti instancu baze podataka za rad s bazom podataka.

    Klasno orijentisano programiranje je glupo. Naučite koristiti OOP.

    Dijeli