Magic-methoden ontcijferen in PHP

PHP biedt een aantal 'magische' methoden waarmee je behoorlijk aardige trucs kunt doen in objectgeoriënteerd programmeren. Deze methoden, geïdentificeerd door een onderscore-voorvoegsel (__), fungeren als interceptors die automatisch worden opgeroepen wanneer aan bepaalde voorwaarden is voldaan. Magische methoden bieden enkele uiterst nuttige functies en deze zelfstudie zal het gebruik van elke methode demonstreren.


Voordat we beginnen

Om magische methoden volledig te begrijpen, is het handig om ze in actie te zien. Laten we beginnen met een basisset van zeer eenvoudige klassen. Hier definiëren we twee klassen: apparaat en batterij.

connection = 'resource'; echo $ this-> naam. ' verbonden' . PHP_EOL;  beveiligde functie disconnect () // veilig ontkoppelen van netwerk $ this-> connection = null; echo $ this-> naam. ' verbinding verbroken' . PHP_EOL;  klasse Batterij private $ charge = 0; openbare functie setCharge ($ charge) $ charge = (int) $ charge; if ($ kosten < 0)  $charge = 0;  elseif($charge > 100) $ charge = 100;  $ this-> charge = $ charge; ?>

Als woorden als 'methode' en 'eigenschap' u vreemd klinken, wilt u dit misschien eerst lezen.

Apparaatobjecten bevatten een naam, een batterijobject, een array met gegevens en een handvat voor een externe bron. Ze hebben ook methoden voor het verbinden en ontkoppelen van de externe bron. Batterijobjecten slaan eenvoudigweg een lading op in een privé-eigendom en hebben een methode om de lading in te stellen.

Deze tutorial gaat ervan uit dat je een basiskennis hebt van objectgeoriënteerd programmeren. Als woorden als "methode" en "eigenschap" vreemd klinken, wil je daar misschien eerst wat over lezen.

Deze lessen zijn vrij nutteloos, maar ze zijn een goed voorbeeld voor elk van de magische methoden. Dus nu we onze eenvoudige klassen hebben gemaakt, kunnen we de magische methoden uitproberen.


Constructors & Destructors

Constructors en destructors worden aangeroepen wanneer een object respectievelijk wordt gemaakt en vernietigd. Een object wordt "vernietigd" wanneer er geen verwijzingen meer naar zijn, hetzij omdat de variabele die deze vasthoudt, werd uitgeschakeld / opnieuw toegewezen of het script de uitvoering beëindigde.

__construct ()

De __construct () methode is veruit de meest gebruikte magische methode. Dit is waar u een initialisatie uitvoert die u nodig hebt wanneer een object wordt gemaakt. U kunt hier een willekeurig aantal argumenten definiëren, die worden doorgegeven bij het maken van objecten. Elke retourwaarde wordt doorgegeven via de nieuwe trefwoord. Eventuele uitzonderingen die in de constructor worden gegooid, zullen het maken van objecten stoppen.

class Device // ... public function __construct (Battery $ battery, $ name) // $ batterij kan alleen een geldig Battery-object $ this-> battery = $ battery; $ this-> name = $ name; // maak verbinding met het netwerk $ this-> connect ();  // ...

Het declareren van de constructormethode 'privé' voorkomt dat externe code rechtstreeks een object maakt.

Hier hebben we een constructor verklaard die twee argumenten accepteert, een batterij en een naam. De constructor wijst elk van de eigenschappen toe die de objecten nodig hebben om te functioneren en voert de aansluiten() methode. Met de constructor kunt u ervoor zorgen dat een object alle benodigde stukjes heeft voordat het kan bestaan.

Tip: Het declareren van de constructormethode 'privé' voorkomt dat externe code rechtstreeks een object maakt. Dit is handig voor het maken van singleton-klassen die het aantal objecten dat kan bestaan ​​beperken.

Met de bovenstaande constructor op zijn plaats, hier is hoe je een Device maakt genaamd 'iMagic':

$ device = new Device (nieuwe batterij (), 'iMagic'); // iMagic connected echo $ device-> name; // iMagic

Zoals u ziet, worden argumenten die aan de klasse worden doorgegeven, feitelijk doorgegeven aan de constructormethode. U kunt ook zien dat de verbindingsmethode is aangeroepen en de name $ property was gevuld.

Laten we zeggen dat we vergeten een naam door te geven. Dit is wat er gebeurt:

$ device = new Device (nieuwe batterij ()); // Result: PHP Warning: Missing argument 2 for Device :: __ construct ()

__vernietigen()

Zoals de naam al aangeeft, de __vernietigen() methode wordt aangeroepen wanneer het object wordt vernietigd. Het accepteert geen argumenten en wordt vaak gebruikt om opruimacties uit te voeren, zoals het sluiten van een databaseverbinding. In ons geval gebruiken we deze om de verbinding met het netwerk te verbreken.

class Device // ... public function __destruct () // verbreekt de verbinding met het netwerk $ this-> disconnect (); echo $ this-> naam. ' was vernietigd' . PHP_EOL;  // ...

Met de bovenstaande destructor op zijn plaats, is dit wat er gebeurt als een Device-object wordt vernietigd:

$ device = new Device (nieuwe batterij (), 'iMagic'); // iMagic connected unset ($ device); // iMagic disconnected // iMagic is vernietigd

Hier hebben we het object vernietigd met unset (). Voordat het wordt vernietigd, noemt de destructor de Loskoppelen() methode en drukt een bericht af, dat u in de opmerkingen kunt zien.


Overbelasting van eigendommen

Notitie: PHP's versie van "overloading" is niet helemaal hetzelfde als de meeste andere talen, hoewel dezelfde resultaten kunnen worden bereikt.

Deze volgende reeks magische methoden gaat over het omgaan met toegang tot onroerend goed, en definieert wat er gebeurt wanneer u probeert toegang te krijgen tot een eigenschap die niet bestaat (of niet toegankelijk is). Ze kunnen worden gebruikt om pseudo-eigenschappen te creëren. Dit wordt overbelasting in PHP genoemd.

__krijgen()

De __krijgen() methode wordt aangeroepen wanneer code probeert toegang te krijgen tot een eigenschap die niet toegankelijk is. Het accepteert een argument, dat is de naam van het onroerend goed. Het zou een waarde moeten teruggeven, die wordt behandeld als de waarde van het onroerend goed. Herinner de $ data eigendom in onze klasse Device? We slaan deze 'pseudo-eigenschappen' op als elementen in de gegevensmatrix en we kunnen gebruikers van onze klas hier via toegang toe laten __krijgen(). Hier is hoe het eruit ziet:

class Device // ... public function __get ($ name) // controleer of de named key in onze array aanwezig is (array_key_exists ($ name, $ this-> data)) // retourneer vervolgens de waarde uit de arrayretour $ this-> data [$ name];  return null;  // ...

Een populair gebruik van de __krijgen() methode is om het toegangsbeheer uit te breiden door "alleen-lezen" -eigenschappen te maken. Neem bijvoorbeeld onze batterijklasse, die een privé-eigendom heeft. We kunnen het privé toestaan $ lading eigenschap die moet worden gelezen van de externe code, maar niet wordt gewijzigd. De code zou er als volgt uitzien:

klasse Batterij private $ charge = 0; publieke functie __get ($ naam) if (isset ($ this -> $ name)) return $ this -> $ name;  return null;  // ...

Houd in dit voorbeeld rekening met het gebruik van variabele variabelen om dynamisch toegang te krijgen tot een eigenschap. Ervan uitgaande dat de waarde 'gebruiker' voor name $, $ This -> $ name vertaald naar $ This-> gebruiker.

__set ()

De __set () methode wordt aangeroepen als code probeert de waarde te wijzigen van een eigenschap die niet toegankelijk is. Het accepteert twee argumenten, die de naam van het eigendom en de waarde zijn. Dit is hoe dat eruit ziet voor de array "pseudo-variabelen" in onze klasse Device:

class Device // ... public function __set ($ name, $ value) // gebruikt de eigenschapnaam als de array-sleutel $ this-> data [$ name] = $ value;  // ...

__isset ()

De __isset () methode wordt aangeroepen wanneer code aanroept isset () op een eigenschap die niet toegankelijk is. Het accepteert een argument, dat is de naam van het onroerend goed. Het zou een Booleaanse waarde moeten teruggeven die het bestaan ​​van een waarde vertegenwoordigt. Opnieuw met behulp van onze variabele array, hier is hoe dat eruit ziet:

class Device // ... public function __isset ($ name) // je zou ook isset () kunnen gebruiken hier return array_key_exists ($ name, $ this-> data);  // ...

__unset ()

De __unset () methode wordt aangeroepen als code probeert unset () een eigenschap die niet toegankelijk is. Het accepteert een argument, dat is de naam van het onroerend goed. Dit is wat ons ziet eruit als:

class Device // ... public function __unset ($ name) // stuur het unset () door naar ons array-element unset ($ this-> data [$ name]);  // ...

Overbelasting van eigendommen in actie

Hier zijn alle eigendommen gerelateerde magische methoden die we hebben verklaard:

class Device // ... public $ data = array (); // winkels misc. data in een array // ... public function __get ($ name) // controleer of de named key in onze array aanwezig is (array_key_exists ($ name, $ this-> data)) // retourneer vervolgens de waarde uit de array return $ this-> data [$ name];  return null;  public function __set ($ name, $ value) // gebruik de eigenschapnaam als de array-sleutel $ this-> data [$ name] = $ value;  public function __isset ($ name) // je zou ook isset () kunnen gebruiken hier return array_key_exists ($ name, $ this-> data);  public function __unset ($ name) // stuur het unset () door naar ons array-element unset ($ this-> data [$ name]);  // ...

Met de bovenstaande magische methoden, is dit wat er gebeurt als we proberen toegang te krijgen tot een eigenschap genaamd naam. Vergeet niet dat er niet echt een is name $ eigenschap aangegeven, hoewel je dat nooit zou weten zonder de interne klassencode te zien.

$ device-> user = 'Steve'; echo $ device-> gebruiker; // Steve

We hebben de waarde van een niet-bestaande eigenschap ingesteld en met succes opgehaald. Waar wordt het dan opgeslagen??

print_r ($ apparaat-> data); / * Array ([user] => Steve) * /

Zoals u kunt zien, de $ data property bevat nu een 'naam'-element met onze waarde.

var_dump (isset ($ apparaat-> user)); // bool (waar)

Hierboven is het resultaat van bellen isset () op het nep-eigendom.

unset ($ apparaat-> gebruiker); var_dump (isset ($ apparaat-> user)); // bool (false)

Hierboven is het resultaat van het uitschakelen van de nepeigenschap. Om er zeker van te zijn, hier is onze lege data-array:

print_r ($ apparaat-> data); / * Array () * /

Objecten als tekst vertegenwoordigen

Soms wilt u misschien een object converteren naar een tekenreeksrepresentatie. Als u eenvoudig een object probeert af te drukken, krijgt u een foutmelding, zoals die hieronder:

$ device = new Device (nieuwe batterij (), 'iMagic'); echo $ apparaat; // Result: PHP Catchable fatal error: Object of class Device kon niet worden geconverteerd naar een string

__toString ()

De __toString () methode wordt aangeroepen wanneer code probeert een object als een tekenreeks te behandelen. Het accepteert geen argumenten en zou een string moeten teruggeven. Hiermee kunnen we definiëren hoe het object wordt weergegeven. In ons voorbeeld maken we een eenvoudige samenvatting:

class Device ... public function __toString () // zijn we verbonden? $ connected = (isset ($ this-> connection))? 'connected': 'disconnected'; // hoeveel gegevens hebben we? $ count = count ($ this-> data); // plaats alles bij elkaar geef $ this-> naam. 'is'. $ verbonden. 'met'. $ tellen. 'items in memory'. PHP_EOL;  ...

Met de bovenstaande methode gedefinieerd, is dit wat er gebeurt als we proberen een Device-object te printen:

$ device = new Device (nieuwe batterij (), 'iMagic'); echo $ apparaat; // iMagic is verbonden met 0 items in het geheugen

Het Device-object wordt nu weergegeven door een korte samenvatting met de naam, status en het aantal opgeslagen items.

__set_state () (PHP 5.1)

Het statische __set_state () methode (beschikbaar vanaf PHP versie 5.1) wordt aangeroepen wanneer de var_export () functie wordt aangeroepen op ons object. De var_export () functie wordt gebruikt om een ​​variabele naar PHP-code te converteren. Deze methode accepteert een associatieve array die de eigenschapswaarden van het object bevat. Voor de eenvoud, gebruik het in onze Battery-klasse.

klasse Batterij // ... openbare statische functie __set_state (array $ array) $ obj = new self (); $ Obj-> setCharge ($ scala [ 'charge']); return $ obj;  // ...

Onze methode maakt eenvoudigweg een instantie van de bovenliggende klasse en stelt in om te laden naar de waarde in de gepasseerde array. Met de bovenstaande methode gedefinieerd, is dit wat er gebeurt als we gebruiken var_export () op een Device-object:

$ device = new Device (nieuwe batterij (), 'iMagic'); var_export ($ apparaat-> batterij); / * Batterij :: __ set_state (array ('charge' => 0,)) * / eval ('$ battery ='. Var_export ($ device-> battery, true). ';'); var_dump ($ batterij); / * object (batterij) # 3 (1) ["charge: private"] => int (0) * /

De eerste opmerking laat zien wat er daadwerkelijk gebeurt, namelijk dat var_export () gewoon oproepen Batterij :: __ set_state (). De tweede opmerking laat zien dat we de batterij met succes opnieuw hebben gemaakt.


Objecten klonen

Objecten worden standaard doorgegeven door verwijzing. Als u dus andere variabelen aan een object toewijst, wordt het object niet daadwerkelijk gekopieerd, maar wordt eenvoudig een nieuwe verwijzing naar hetzelfde object gemaakt. Om een ​​object echt te kopiëren, moeten we de kloon trefwoord.

Dit 'referentiebeleid' is ook van toepassing op objecten in objecten. Zelfs als we een object klonen, worden alle onderliggende objecten die het bevat, niet gekloond. Dus we zouden eindigen met twee objecten die hetzelfde onderliggende object delen. Hier is een voorbeeld dat illustreert dat:

$ device = new Device (nieuwe batterij (), 'iMagic'); $ device2 = $ apparaat klonen; $ Device-> batterij-> setCharge (65); echo $ device2-> batterij-> laden; // 65

Hier hebben we een Device-object gekloond. Vergeet niet dat alle Device-objecten een batterij-object bevatten. Om aan te tonen dat beide klonen van het apparaat dezelfde batterij delen, heeft de wijziging die we hebben aangebracht in de batterij van $ apparaat, zijn weerslag in de batterij van $ device2.

__clone ()

De __clone () methode kan worden gebruikt om dit probleem op te lossen. Het wordt aangeroepen op de kopie van een gekloond object nadat het klonen heeft plaatsgevonden. Hier kun je onderliggende objecten klonen.

class Device ... public function __clone () // kopie ons Battery-object $ this-> battery = clone $ this-> batterij;  ...

Met deze methode verklaard, kunnen we er nu zeker van zijn dat de gekloonde apparaten elk hun eigen batterij hebben.

$ device = new Device (nieuwe batterij (), 'iMagic'); $ device2 = $ apparaat klonen; $ Device-> batterij-> setCharge (65); echo $ device2-> batterij-> laden; // 0

Wijzigingen in de batterij van één apparaat hebben geen invloed op de andere.


Objectserialisatie

Serialisatie is het proces dat gegevens omzet in een tekenreeksindeling. Dit kan worden gebruikt om hele objecten op te slaan in een bestand of database. Wanneer u de opgeslagen gegevens ongedaan maakt, heeft u het originele object precies zoals het eerder was. Een probleem met serialisatie is echter dat niet alles geserialiseerd kan worden, zoals databaseverbindingen. Gelukkig zijn er enkele magische methoden die ons in staat stellen om dit probleem aan te pakken.

__slaap()

De __slaap() methode wordt aangeroepen wanneer de serialize () functie wordt aangeroepen op het object. Het accepteert geen argumenten en moet een array van alle eigenschappen retourneren die in serie moeten worden geplaatst. U kunt ook alle openstaande taken of opschoning voltooien die mogelijk nodig zijn in deze methode.

Tip: Vermijd alles wat destructief is __slaap() omdat dit van invloed is op het live-object en u er misschien niet altijd klaar mee bent.

In ons Device-voorbeeld staat de eigenschap connection voor een externe resource die niet geserialiseerd kan worden. Zo onze __slaap() methode retourneert eenvoudig een array met alle eigenschappen behalve $ verbinding.

class Device public $ name; // de naam van de publieke $ batterij van het apparaat; // houdt een batterij-object openbaar $ data = array (); // winkels misc. gegevens in een array openbare $ -verbinding; // bevat een of andere verbindingsbron // ... public function __sleep () // lijst van de eigenschappen om retourarray te bewaren ('naam', 'batterij', 'data');  // ...

Onze __slaap() geeft eenvoudigweg een lijst met de namen van eigenschappen die moeten worden bewaard.

__wakker worden()

De __wakker worden() methode wordt aangeroepen wanneer de unserialize () functie wordt aangeroepen op het opgeslagen object. Het accepteert geen argumenten en hoeft niets terug te geven. Gebruik het om elke databaseverbinding of -resource opnieuw te herstellen die verloren ging in serialisatie.

In ons Device-voorbeeld moeten we gewoon onze verbinding herstellen door ons te bellen aansluiten() methode.

class Device // ... public function __wakeup () // maak opnieuw verbinding met het netwerk $ this-> connect ();  // ...

Methode Overbelasting

Deze laatste twee methoden zijn voor het omgaan met methoden. Dit is hetzelfde concept als de methode voor overbelasting van onroerend goed (__krijgen(), __set (), enz.), maar toegepast op methoden.

__call ()

De __call () wordt aangeroepen wanneer code pogingen doet om ontoegankelijke of niet-bestaande methoden aan te roepen. Het accepteert twee argumenten: de naam van de methode genaamd en een array van argumenten. U kunt deze informatie gebruiken om dezelfde methode bijvoorbeeld in een onderliggend object te gebruiken.

In de voorbeelden noteert u het gebruik van de call_user_func_array () functie. Met deze functie kunnen we dynamisch een benoemde functie (of methode) aanroepen met de argumenten die in een array zijn opgeslagen. Het eerste argument identificeert de functie om te bellen. In het geval van naamgevingsmethoden is het eerste argument een array met een klassenaam of objectinstantie en de naam van de eigenschap. Het tweede argument is altijd een geïndexeerde reeks argumenten die moet worden doorgegeven.

In ons voorbeeld geven we de methodeaanroep door aan ons $ verbinding eigenschap (waarvan we aannemen dat het een object is). We zullen het resultaat daarvan direct terugsturen naar de belcode.

class Device // ... public function __call ($ name, $ arguments) // zorg dat ons child-object deze methode heeft als (method_exists ($ this-> connection, $ name)) // de oproep doorstuurt naar ons kind object return call_user_func_array (array ($ this-> connection, $ name), $ arguments);  return null;  // ...

De bovenstaande methode zou aangeroepen worden als we de iDontExist () methode:

$ device = new Device (nieuwe batterij (), 'iMagic'); $ Apparaat-> iDontExist (); // __call () stuurt dit door naar $ device-> connection-> iDontExist ()

__callStatic () (PHP 5.3)

De __callStatic () (beschikbaar vanaf versie 5.3 van PHP) is identiek aan __call () behalve dat het wordt aangeroepen als code pogingen doet om ontoegankelijke of niet-bestaande methoden aan te roepen in een statische context.

De enige verschillen in ons voorbeeld is dat we de methode als verklaren statisch en we verwijzen naar een klassenaam in plaats van naar een object.

class Device // ... public static function __callStatic ($ name, $ arguments) // zorg ervoor dat onze klasse deze methode heeft als (method_exists ('Connection', $ name)) // doorstuur de statische oproep naar onze klas terug call_user_func_array (array ('Connection', $ name), $ arguments);  return null;  // ...

De bovenstaande methode zou worden aangeroepen als we de statische verbinding proberen te maken iDontExist () methode:

Inrichting :: iDontExist (); // __callStatic () stuurt dit door naar Connection :: iDontExist ()

Objecten gebruiken als functies

Soms wilt u een object als een functie gebruiken. Als u een object als een functie kunt gebruiken, kunt u functies in andere talen doorgeven als argumenten zoals u dat kunt.

__invoke () (PHP 5.3)

De __beroep doen op() (beschikbaar vanaf PHP versie 5.3) wordt aangeroepen wanneer code probeert het object als een functie te gebruiken. Alle argumenten die in deze methode zijn gedefinieerd, worden gebruikt als functieargumenten. In ons voorbeeld zullen we gewoon het argument afdrukken dat het ontvangt.

class Device // ... public function __invoke ($ data) echo $ data;  // ...

Met het bovenstaande gedefinieerd, is dit wat er gebeurt als we een apparaat als een functie gebruiken:

$ device = new Device (nieuwe batterij (), 'iMagic'); $ Apparaat ( 'test'); // equiv to $ device -> __ invoke ('test') // Uitgangen: test

Bonus: __autoload ()

Dit is geen magische methode, maar het is nog steeds erg handig. De __autoload () functie wordt automatisch aangeroepen wanneer naar een klasse wordt verwezen die niet bestaat. Het is bedoeld om u nog een laatste kans te geven om het bestand met de klassenverklaring te laden voordat uw script faalt. Dit is handig, omdat je niet altijd elke klas wilt laden voor het geval je het nodig hebt.

De functie accepteert één argument: de naam van de klasse waarnaar wordt verwezen. Stel dat u elke klas in een bestand met de naam 'classname.class.php' in de directory 'inc'. Hier is hoe uw autoload eruit zou zien:

function __autoload ($ class_name) $ class_name = strtolower ($ class_name); include_once './inc/'. $ class_name. '.Class.php'; 

Conclusie

Magische methoden zijn uiterst nuttig en bieden krachtige hulpmiddelen voor het ontwikkelen van flexibele applicatiekaders. Ze brengen PHP-objecten dichter bij die in andere objectgeoriënteerde talen doordat je sommige van hun nuttigere functies kunt reproduceren. Je kunt de PHP-handleidingpagina's over magische methoden hier lezen. Ik hoop dat deze tutorial nuttig was en de concepten duidelijk heeft uitgelegd. Als u vragen heeft, aarzel dan niet om te vragen in de comments. Bedankt voor het lezen.