Statiska egenskaper och metoder i PHP. Varför föredrar vissa PHP -utvecklare statiska API: er? Php statiska metoder

(Late Static Binding, LSB) har varit ett hett ämne de senaste tre åren i PHP -utvecklingscirklar (och slutligen fick vi det i PHP 5.3). Men varför behövs det? I den här artikeln kommer det bara att övervägas hur sen statisk länkning kan avsevärt förenkla din kod.

Vid PHP-utvecklarmötet i Paris i november 2005 diskuterades ämnet sen statisk länkning formellt av kärnutvecklingsteamet. De enades om att genomföra det, tillsammans med många andra ämnen som stod på agendan. Detaljerna skulle komma överens om genom öppna diskussioner.

Eftersom sen statisk länkning tillkännagavs som ett kommande inslag, två år senare. Slutligen blev LSB tillgänglig för användning i PHP 5.3. Men denna händelse gick obemärkt förbi för utvecklare som använder PHP, från anteckningarna bara en sida i manualen.

Kort sagt, den nya funktionaliteten för sen statisk bindning gör att objekt fortfarande kan ärva metoder från sina föräldraklasser, men utöver detta tillåter det ärvda metoder att ha tillgång till statiska konstanter, metoder och egenskaper hos ättlingsklassen, och inte bara föräldraklass. Låt oss ta ett exempel:

Class Beer (const NAME = "Beer!"; Public function getName () (return self :: NAME;)) class Ale extends Beer (const NAME = "Ale!";) $ BeerDrink = new Beer; $ aleDrink = ny Ale; eko "Öl är:". $ beerDrink-> getName (). "\ n"; echo "Ale är:". $ aleDrink-> getName (). "\ n";

Denna kod ger denna utdata:

Öl är: Öl! Ale är: Öl!

Klass Aleärvd metod hämta namn () men samtidigt själv pekar fortfarande på klassen där den används (i det här fallet är det klassen Öl). Detta förblev i PHP 5.3, men lade till ordet statisk... Låt oss titta på ett exempel igen:

Class Beer (const NAME = "Beer!"; Public function getName () (return self :: NAME;) public function getStaticName () (return static :: NAME;)) class Ale extends Beer (const NAME = "Ale!" ;) $ beerDrink = ny öl; $ aleDrink = ny Ale; eko "Öl är:". $ beerDrink-> getName (). "\ n"; eko "Ale är:". $ aleDrink-> getName (). "\ n"; eko "Öl är faktiskt:". $ beerDrink-> getStaticName (). "\ n"; eko "Ale är faktiskt:". $ aleDrink-> getStaticName (). "\ n";

Nytt sökord statisk indikerar att det är nödvändigt att använda konstanten för den ärvda klassen, istället för konstanten som definierades i klassen där metoden deklareras getStaticName ()... Ord statisk tillkom för att implementera ny funktionalitet och för bakåtkompatibilitet själv fungerar på samma sätt som i tidigare versioner av PHP.

Internt är den största skillnaden (och faktiskt anledningen till att bindningen kallades sen) mellan dessa två åtkomstmetoder att PHP kommer att definiera ett värde för jag :: NAME under "kompilering" (när PHP -symboler konverteras till maskinkod som kommer att bearbetas av Zend -motorn), och för statisk :: NAMN värdet bestäms vid start (i det ögonblick när den ursprungliga koden körs i Zend -motorn).

Detta är ett annat verktyg för PHP -utvecklare. I den andra delen ska vi titta på hur det kan användas för gott.

Från PHP 5.3.0 finns det en funktion som kallas sen statisk länkning som kan användas för att få en referens till en uppringbar klass i samband med statiskt arv.

Mer specifikt, sen statisk bindning behåller klassnamnet som specificerats i det senaste "icke-omdirigerade anropet". För statiska samtal är detta en uttryckligen specificerad klass (vanligtvis till vänster om operatören :: ); för icke-statiska samtal är detta objektklassen. Ett "omdirigerat samtal" är ett statiskt samtal som börjar med själv ::, förälder ::, statisk ::, eller, om du flyttar upp i klasshierarkin, forward_static_call ()... Fungera bli_kallad_klass () kan användas för att få en sträng med namnet på den kallade klassen och statisk :: representerar dess omfattning.

Själva namnet "sen statisk bindning" återspeglar den interna implementeringen av denna funktion. "Sen bindning" återspeglar det faktum som ringer igenom statisk :: utvärderas inte mot klassen i vilken metoden som anropas definieras, utan utvärderas utifrån information vid körning. Denna funktion har också kallats "statisk länkning" eftersom den kan (men inte nödvändigtvis) användas i statiska metoder.

Restriktioner själv ::

Exempel # 1 Användning själv ::

klass A (
eko __CLASS__ ;
}
offentlig statisk funktion
test () (
self :: who ();
}
}

klass B sträcker sig A (
offentlig statisk funktion som () (
eko __CLASS__;
}
}

B :: test ();
?>

Använder sen statisk bindning

Sen statisk länkning försöker åtgärda denna begränsning genom att tillhandahålla ett nyckelord som hänvisar till en klass som kallas direkt vid körning. Helt enkelt ett sökord som låter dig länka till B från test () i föregående exempel. Det beslutades att inte introducera ett nytt sökord, utan att använda det statisk som redan är reserverad.

Exempel # 2 Enkel användning statisk ::

klass A (
offentlig statisk funktion som () (
eko __CLASS__ ;
}
offentlig statisk funktion
test () (
statisk :: vem (); // Sen statisk bindning gäller här
}
}

klass B sträcker sig A (
offentlig statisk funktion som () (
eko __CLASS__;
}
}

B :: test ();
?>

Utförande resultat detta exempel:

Kommentar:

I ett icke-statiskt sammanhang är den uppringda klassen den som objektinstansen tillhör. I den mån som $ detta-> kommer att försöka ringa privata metoder från samma omfattning, användning statisk :: kan ge olika resultat. En annan skillnad är att statisk :: kan endast hänvisa till statiska fält i klassen.

Exempel # 3 Användning statisk :: i ett icke-statiskt sammanhang

klass A (
privat funktion foo () (
echo "framgång! \ n";
}
offentligt funktionstest () (
$ this -> foo ();
statisk :: foo ();
}
}

klass B sträcker sig A (
/ * foo () kommer att kopieras till B, därför är dess omfattning fortfarande A,
och samtalet kommer att lyckas * /
}

klass C sträcker sig A (
privat funktion foo () (
/ * originalmetod ersatt; omfattningen av den nya metoden C * /
}
}

$ b = nytt B ();
$ b -> test ();
$ c = nytt C ();
$ c -> test (); // inte sant
?>

Resultatet av detta exempel:

Framgång! Framgång! Framgång! Fatalt fel: Anrop till privat metod C :: foo () från sammanhanget "A" i /tmp/test.php på rad 9

Kommentar:

Det sena upplösningsområdet för statisk bindning kommer att fixas med det beräknade statiska anropet. Å andra sidan, statiska samtal med hjälp av direktiv som förälder :: eller själv :: omdirigera samtalsinformation.

Exempel # 4 Omdirigerade och icke-dirigerade samtal

Det är ingen hemlighet att de gillar att ställa knepiga frågor i intervjuer. Inte alltid tillräckligt, inte alltid relaterat till verkligheten, men faktum kvarstår - frågar de. Naturligtvis är frågan annorlunda än frågan, och ibland är en fråga som vid första anblicken verkar dum för dig faktiskt syftar till att testa hur väl du kan språket du skriver på.

Låt oss försöka demontera en av dessa frågor "bit för bit" - vad betyder ordet "statisk" i PHP och varför används det?

Det statiska sökordet har tre olika betydelser i PHP. Låt oss analysera dem i kronologisk ordning, som de förekom på språket.

Det första värdet är en statisk lokal variabel

funktion foo () ($ a = 0; eko $ a; $ a = $ a + 1;) foo (); // 0 foo (); // 0 foo (); // 0

I PHP är variablerna lokala. Detta innebär att en variabel som definierats och tilldelats ett värde inuti en funktion (metod) existerar endast under utförandet av denna funktion (metod). När metoden avslutas förstörs den lokala variabeln och när metoden matas in återskapas den. I koden ovan är en sådan lokal variabel variabeln $ a - den existerar bara inuti funktionen foo () och skapas på nytt varje gång denna funktion anropas. Att öka en variabel i den här koden är meningslöst, för på nästa kodrad kommer funktionen att slutföra sitt arbete och variabelns värde går förlorat. Oavsett hur många gånger vi anropar foo ()-funktionen kommer den alltid att mata ut 0 ...

Allt förändras dock om vi lägger det statiska sökordet före tilldelningen:

Funktion foo () (statisk $ a = 0; echo $ a; $ a = $ a + 1;) foo (); // 0 foo (); // 1 foo (); // 2

Det statiska sökordet, skrivet innan du tilldelar ett värde till en lokal variabel, har följande effekter:

  1. Tilldelningen utförs bara en gång, första gången funktionen anropas
  2. Värdet på variabeln som markerats på detta sätt sparas efter funktionens slut
  3. Vid efterföljande samtal till funktionen, istället för tilldelning, får variabeln det tidigare sparade värdet
Denna användning av ordet statisk kallas statisk lokal variabel.
Statiska variabla fallgropar
Naturligtvis, som alltid i PHP, finns det några fallgropar.

Den första stenen är att endast konstanter eller konstanta uttryck kan tilldelas en statisk variabel. Här är koden:
statisk $ a = bar ();
kommer oundvikligen att leda till ett analysfel. Lyckligtvis har det sedan version 5.6 blivit tillåtet att tilldela inte bara konstanter utan också konstanta uttryck (till exempel "1 + 2" eller ""), det vill säga sådana uttryck som inte är beroende av annan kod och kan beräknas vid sammanställningstiden

Den andra stenen - metoderna finns i en enda kopia.
Allt är lite mer komplicerat här. För att förstå essensen kommer jag att ge koden:
klass A (offentlig funktion foo () (statisk $ x = 0; eko ++ $ x;)) $ a1 = nytt A; $ a2 = nytt A; $ a1-> foo (); // 1 $ a2-> foo (); // 2 $ a1-> foo (); // 3 $ a2-> foo (); // 4
I motsats till den intuitiva förväntningen "olika objekt - olika metoder" kan vi tydligt se i detta exempel att dynamiska metoder i PHP "inte sprider sig". Även om vi har hundra objekt av denna klass, kommer metoden att existera i endast en instans, bara en annan $ som kommer att skickas in i den varje gång den anropas.

Detta beteende kan vara oväntat för en outbildad utvecklare och kan vara en källa till fel. Det bör noteras att ärvning av en klass (och en metod) leder till att en ny metod fortfarande skapas:

Klass A (offentlig funktion foo () (statisk $ x = 0; echo ++ $ x;)) klass B sträcker sig A () $ a1 = ny A; $ b1 = nytt B; $ a1-> foo (); // 1 $ b1-> foo (); // 1 $ a1-> foo (); // 2 $ b1-> foo (); // 2

Slutsats: dynamiska metoder i PHP finns inom ramen för klasser, inte objekt. Och endast under körning finns det en ersättning "$ this = current_object"

Andra betydelsen - statiska egenskaper och metoder för klasser

I PHP -objektmodellen är det möjligt att ange egenskaper och metoder inte bara för objekt - förekomster av en klass, utan också för klassen som helhet. Det statiska sökordet används också för detta:

Klass A (offentlig statisk $ x = "foo"; offentlig statisk funktionstest () (retur 42;)) eko A :: $ x; // "foo" eko A :: test (); // 42
För att komma åt sådana egenskaper och metoder används dubbel-kolon konstruktioner ("Paamayim Nekudotayim"), till exempel CLASS_NAME :: $ VariableName och CLASS_NAME :: MethodName ().

Det säger sig självt att statiska egenskaper och statiska metoder har sina egna särdrag och fallgropar att vara medvetna om.

Den första funktionen är banal - det finns inget detta. Egentligen härrör detta från själva definitionen av en statisk metod - eftersom den är associerad med en klass, inte ett objekt, är pseudovariabeln $ denna inte tillgänglig i den, vilket pekar på det aktuella objektet i dynamiska metoder. Vilket är helt logiskt.

Du måste dock veta att PHP, till skillnad från andra språk, inte upptäcker situationen "$ detta är skrivet i en statisk metod" vid analys eller sammanställning. Detta fel kan bara uppstå under körning om du försöker köra kod med $ detta i en statisk metod.

En kod som denna:
klass A (public $ id = 42; static public function foo () (echo $ this-> id;))
kommer inte att leda till några fel om du inte försöker använda metoden foo () på ett olämpligt sätt:
$ a = nytt A; $ a-> foo (); (och få omedelbart "Dödligt fel: Använda $ detta när det inte är i objektsammanhang")

Den andra funktionen - statisk är inte ett axiom!
klass A (statisk offentlig funktion foo () (eko 42;)) $ a = nytt A; $ a-> foo ();
Det stämmer, ja. En statisk metod, om den inte innehåller $ detta i din kod, kan kallas i ett dynamiskt sammanhang som en objektmetod. Detta är inte ett fel i PHP.

Det omvända är inte helt sant:
klass A (offentlig funktion foo () (eko 42;)) A :: foo ();
En dynamisk metod som inte använder $ detta kan exekveras i ett statiskt sammanhang. Du får dock en E_STRICT-varning "Icke-statisk metod A :: foo () ska inte kallas statiskt". Det är upp till dig att bestämma om du strikt vill följa kodstandarderna eller undertrycka varningar. Det förra är naturligtvis att föredra.

Och förresten, allt som skrivs ovan gäller endast metoder. Att använda en statisk egenskap via "->" är omöjligt och leder till ett fatalt fel.

Den tredje, till synes svåraste, betydelsen är sen statisk länkning.

PHP -utvecklare stannade inte vid två betydelser nyckelord"Statisk" och i version 5.3 lagt till ytterligare en "funktion" på språket, som implementeras av samma ord! Det kallas Late Static Binding eller LSB.

Det enklaste sättet att förstå essensen i LSB är att använda enkla exempel:

Klassmodell (public static $ table = "table"; public static function getTable () (return self :: $ table;)) echo Model :: getTable (); // "tabell"
Nyckelordet self i PHP betyder alltid "namnet på klassen där detta ord är skrivet". I det här fallet ersätts jaget med modellklassen och tabellen själv :: $ ersätts med tabellen Modell :: $.
Denna språkfunktion kallas tidig statisk bindning. Varför tidigt? Eftersom bindningen av jaget och ett specifikt klassnamn inte sker vid körning, utan i tidigare skeden - analys och kodning av koden. Tja, "statisk" - för det kommer om statiska egenskaper och metoder.

Låt oss ändra vår kod lite:

Klassmodell (public static $ table = "table"; public static function getTable () (return self :: $ table;)) class User extends Model (public static $ table = "users";) echo User :: getTable () ; // "tabell"

Nu förstår du varför PHP beter sig ointuitivt i denna situation. jag var associerad med modellklassen vid en tidpunkt då ingenting var känt om användarklassen, så det pekar på modell.

Hur man är?

För att lösa detta dilemma uppfanns en mekanism för att länka "sent", i körningsstadiet. Det fungerar väldigt enkelt - det räcker med att skriva "statisk" istället för ordet "jag" och anslutningen kommer att upprättas med klassen som kallar den angivna koden, och inte med den där den är skriven:
klass Modell (offentlig statisk $-tabell = "tabell"; offentlig statisk funktion getTable () (retur static :: $-tabell;)) klass Användare utökar Modell (offentlig statisk $-tabell = "användare";) echo User :: getTable () ; // "användare"

Detta är den kryptiska "sena statiska bindningen".

Det bör noteras att för mer bekvämlighet i PHP förutom ordet "statisk" finns det också särskild funktion get_called_class (), som kommer att berätta för dig - i samband med vilken klass i det här ögonblicket din kod fungerar.

Trevliga intervjuer!

Jag har länge velat skriva om detta ämne. Den första drivkraften var Miško Heverys artikel "Static Methods are Death to Testability". Jag skrev en svarsartikel men publicerade den aldrig. Men nyligen såg jag något som kan kallas "Klassorienterad programmering". Detta uppdaterade mitt intresse för ämnet och här är resultatet.

"Klassorienterad programmering" är när klasser används som endast består av statiska metoder och egenskaper, och klassen instansieras aldrig. I den här artikeln kommer jag att prata om:

  • det ger inga fördelar jämfört med procedurprogrammering
  • ge inte upp föremål
  • närvaro av statiska klassmedlemmar! = död till tester
Även om den här artikeln handlar om PHP, gäller begreppen även andra språk.

Beroenden

Vanligtvis beror koden på annan kod. Till exempel:

$ foo = substr ($ bar, 42);
Denna kod beror på variabeln $ bar och subberfunktionen. $ bar är bara en lokal variabel definierad något högre i samma fil och i samma omfattning. substr är en PHP-kärnfunktion. Allt är enkelt här.

Nu, ett exempel som detta:

Klass BloomFilter (... offentlig funktion __konstruktion ($ m, $ k) (...) offentlig statisk funktion getK ($ m, $ n) (returtak (($ m / $ n) * log (2)); ) ...)
Denna lilla hjälpfunktion tillhandahåller bara en omslag för en specifik algoritm som hjälper dig att beräkna ett bra tal för $ k -argumentet som används i konstruktorn. Eftersom den måste anropas innan klassen instansieras, den måste vara statisk. Denna algoritm har inga externa beroenden och kommer sannolikt inte att ersättas. Den används så här:

$ m = 10 000; $ n = 2000; $ b = nytt BloomFilter ($ m, BloomFilter :: getK ($ m, $ n));
Detta skapar inga ytterligare beroenden. Klassen beror på sig själv.

  • Alternativ konstruktör. Ett bra exempelär klassen DateTime inbyggd i PHP. Det kan instanseras i två olika sätt:

    $ date = new DateTime ("2012-11-04"); $ date = DateTime :: createFromFormat ("d-m-Y", "04-11-2012");
    I båda fallen blir resultatet en DateTime -instans, och i båda fallen är koden bunden till DateTime -klassen på ett eller annat sätt. Den statiska metoden DateTime :: createFromFormat är en alternativ objektkonstruktor som returnerar samma som ny DateTime, men med ytterligare funktionalitet. Där du kan skriva ny klass kan du skriva Class :: method (). Detta skapar inga nya beroenden.

  • Resten av användningsfallen för statiska metoder påverkar bindningen och kan bilda implicita beroenden.

    Ett ord om abstraktion

    Varför allt detta tjafs med beroenden? Abstraktion! När din produkt växer växer dess komplexitet. Och abstraktion är nyckeln till att hantera komplexitet.

    Till exempel har du en programklass som representerar din ansökan. Den kommunicerar med användarklassen, som är en representation av användaren. Som tar emot data från databasen. Databanklassen behöver en DatabaseDriver. DatabaseDriver behöver anslutningsparametrar. Etc. Om du bara kallar Application :: start () statiskt, vilket kallar User :: getData () statiskt, som kallar DB statiskt, och så vidare, i hopp om att varje lager kommer att hantera sina beroenden, kan du få en fruktansvärd röra om något går fel, inte på detta sätt. Det är omöjligt att gissa om Application :: start () -samtalet kommer att fungera, eftersom det inte alls är uppenbart hur de interna beroendena kommer att bete sig. Ännu värre, det enda sättet att påverka beteendet hos Application :: start () är att ändra källa denna klass och koden för klasserna som den kallar och koden för klasserna som kallar dessa klasser ... i huset som Jack byggde.

    Det mest effektiva tillvägagångssättet när du bygger komplexa applikationer är att skapa separata delar som du kan lita på i framtiden. De delar som du kan sluta tänka på, som du kan vara säker på. Till exempel, när du anropar static Database :: fetchAll (...) finns det ingen garanti för att anslutningen till databasen redan har upprättats eller kommer att upprättas.

    Funktion (Databas $ databas) (...)
    Om koden i den här funktionen körs betyder det att databasinstansen har skickats framgångsrikt, vilket innebär att databasobjektinstansen har skapats. Om databasklassen är korrekt utformad kan du vara säker på att ha en instans av den här klassen innebär möjligheten att utföra frågor till databasen. Om det inte finns någon instans av klassen kommer funktionens kropp inte att köras. Det betyder att funktionen inte behöver bry sig om databasens tillstånd, Databas -klassen kommer att göra det själv. Detta tillvägagångssätt låter dig glömma beroenden och koncentrera dig på att lösa problem.

    Utan förmågan att inte tänka på beroenden och beroenden för dessa beroenden är det nästan omöjligt att skriva någon komplex applikation. Databas kan vara en liten wrapperklass eller ett gigantiskt flerskiktsmonster med en massa beroenden, det kan börja som ett litet wrapper och mutera till ett jättemonster med tiden, du kan ärva Databasklassen och skicka en ättling till funktionen, allt detta är inte viktigt för din funktion (databas $ databas), så länge databasens offentliga gränssnitt inte ändras. Om dina klasser är korrekt åtskilda från resten av applikationen med hjälp av beroendeinjektion kan du testa var och en med hjälp av stubbar istället för deras beroenden. När du har testat klassen tillräckligt för att se till att den fungerar som förväntat kan du ta dig ur huvudet bara genom att veta att du måste använda en databasinstans för att arbeta med databasen.

    Klassorienterad programmering är nonsens. Lär dig att använda OOP.

    Dela detta