Tacsiazuma
Tacsiazuma A letscode.hu alapitója, több, mint egy évtized fejlesztői tapasztalattal. Neovim függő hobbi pilóta.

It’s a kind of __magic()!

A PHP nyelvben objektumainknak akadnak olyan metódusai, melyeket nem tudunk direkt módon
hivatkozással elérni, hanem egy bizonyos működéshez kötődnek. Ezeket a függvényeket mágikus metódusoknak (igen, magic method) nevezzük és minden esetben __ -el (dupla alsóvonás) kezdődnek. Álljon itt egy lista róluk és arról, hogy mikor és hogy is hívódnak meg:Its_a_kind_of_magic_by_MindStep

Kezdjük az egyik legalapvetőbbel, amivel bárki összefutott már, aki példányosított objektumot:

__construct()

A fenti mágikus függvény egy objektum példányosításakor hívódik meg, amiről már beszéltem korábban az OOP-s cikkemben. Roppant egyszerű a dolog:

1
$akarmilyenosztaly = new AkarmilyenOsztaly($ide_johetnek, $az, $argumentumok);

Amikor mi ezt beírjuk, akkor igazából az

1
AkarmilyenOsztaly::__construct($ide_johetnek, $az, $argumentumok)

-ra hivatkozunk, amit hiába erőszakolunk, az aktuális osztály egy példányát fogja visszaadni értékül. Itt végezhetjük el az objektumunk előkészítését, hogy igazán “ready for action” legyen, de lépjünk tovább valami kevésbé unalmasra.

__destruct()

Hát igen, ha kocsmát valamit építeni akarunk, akkor előtte le kell dózerolni a butikot annak helyet is kell teremteni, úgy hogy az esőerdők se bánják meg. Ahogy a __construct az objektum példányosításakor hívódik meg, úgy a destruct akkor, mikor az adott scope-ból visszalépünk és az objektumra mutató referenciát nem viszünk tovább, a programunk véget ér, vagy mi lépünk ki az exit() paranccsal. Itt takaríthatjuk el az ideiglenes fájlokat, netán pont lementhetünk egy aktuális állapotot, stb. Ellenben kivételt nem dobhatunk, csak egy csudaszép fatal error-ért cserébe.

__destruct() közben ne hívjuk meg az exit()-et, mert az ott rögtön véget vet a programunk futásának.

__call()

Ez a függvény párban jár, mint a lányok a középiskola WC-jére, így mindkettejükről muszáj lesz írnom. A __call() mágikus metódus akkor hívódik meg, mikor sikeresen olyan metódust hívtunk meg, amit az adott contextből nem érnénk el (például kívülről egy private metódust). A __callStatic() ugyanezt teszi, csak statikus metódusok esetében, de nézzünk ide is egy példát:

1
2
3
4
5
6
7
8
9
class BSB_The_Call {
    public function __call($name, $args) {
        echo "Ezt szeretted volna: ".'$this'. // okkal zártam aposztrófok közé, de mindjárt szóba kerül az érintett metódus is
"->$name(".implode(", ",$args)."); ?";
    }
    public static function __callStatic($name, $args) {
       echo "Igen, statikusban is megy: ". get_called_class()."::$name(". implode(", ", $args)."); !";
    }
}

A fentiekből jól látható, hogy ez egyfajta hibaelhárításra szolgál, hogy a rendszer ne boruljon fel, ha kívülről szeretnénk piszkálni azt, amit előtte (nyílván okkal) levédtünk access modifierekkel, de az igazat megvallva én még sosem használtam őket, aki igen, az tegye fel a kezét a poszt végén.

__set(), __get()

Ha már az access modifiereknél tartunk, akkor itt a PHP válasza a kismillió setter/getter fügvényre. A __set és __get függvények akkor hívódnak meg, mikor olyan változókra hivatkozunk, amik nem léteztek/nem elérhetőek az adott contextből. Tehát ha létrehozunk egy jól szituált __set(), __get() párost, és privát változókat használunk, akkor megspórolhatunk egy halom setter/getter metódust (amit a legtöbb IDE magától is legenerál, de sebaj 🙂 és simán

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$objektum = new SetGet();
$objektum->valtozo = $ertek; // $objektum->__set('valtozo', $ertek) kerül meghívásra
echo $objektum->masikValtozo; // $objektum->__get('masikValtozo') hívódik meg

class SetGet {
    private $valtozo, $masikValtozo;

    public function __set($name, $ertek) {
        $this->$name = $ertek; // variable variables és máris kész a setterünk
    }
    public function __get($name) {
        return $this->$name; // ez pedig a getterünk lesz
    }
}

viszont azt ne feledjük, hogy ha ezt a módszert használjuk, akkor elesünk attól a védelemtől, amit a setter(), getter() függvények nyújtanak, pl. if-else esetében egy ottfelejtett értékadásnál.

__isset(), __unset()

Ez a két függvény pontosan azt a célt szolgálja, amire első ránézésre gondolnánk is.

Amikor meghívjuk

1
2
3
$ojjektum = new HaromSzazezer();
isset($ojjektum->drotkoszoru); // $ojjektum->__isset('drotkoszoru');
unset($ojjektum->drotkoszoru); // $ojjektum->__unset('drotkoszoru');

Akkor igazából a kommentelt parancsok futnak le, amennyiben a $drotkoszoru változónk nem érhető el az adott contextből.

__sleep(), __wakeup Neo()

Na ha valaki most a többszálúságra és hasonlókra gondol, azt bizony el kell keserítsem, mert ennek a mágikus metódusnak semmi köze a thread-ekhez, ennél félrevezetőbb nevet nem is találhattak volna neki. E metódus akkor hívódik meg, mikor az objektumunkat a serialize függvénnyel szeretnénk feldolgozni. Ez ugye alapesetben tömböket alakít string típusúvá, így könnyen tárolható adatbázisban, stb. Na de mikor egy objektumon hajtjuk végre, akkor röfögne egy sort, hogy valami nincs rendjén, mivel ezt a metódust nem találja. Ha viszont létrehozzuk, akkor egy tömböt kell visszaadjunk, az objektumunk elemeivel, amin aztán csak lefut az a serialize. A wakeup pedig az unserialize esetében fog meghívódni.

1
2
3
4
5
6
7
8
9
class EzEgyExcelTabla { // nem a legjobb példája a value object-eknek, de most megteszi
     private $table = array(1,2,3,4,5,6,7); // csináltunk valami egyszerűt, amit vissza lehet adni
     public function __sleep() {
          return $this->table; // a tömbünket adjuk vissza, amit aztán simán leserialize-ol.
     }
     public function __wakeup() {
          $this->table = array(1,2,3,4,5,6,7); // Neo felébredt
     }
}

A sleep metódus akkor jön jól, mikor egy nagy osztályunknak csupán egy részét szeretnénk elmenteni, netán rendszerezni akarjuk a benne lévő adatokat. A wakeup az unserialize esetén hívódik meg, így ha bármit is félbehagytunk, itt újra összeépíthetjük azt.

__toString()

A Java programozók most izgalomba jöhetnek, mert ismerős szöveget látnak. Igen, PHP-ben mágikus metódusként érkezett a toString metódus, aminek egyetlen világmegváltó célja az, hogyha valaki egy szimpla echo-val megjelenítené az objektumot, ne egy hibaüzenet fogadja a kedves ügyfelet, miszerint objektumot nem illik string-é alakítani, hanem a metódus keretein belül foglalt csudaszép szöveg. Ha pl. csinálunk egy $form objektumot, amibe beledobáljuk az elemeket, akkor ezt átadhatjuk egy az egyben a view-nak és ki lehet echo-zni, a függvényben pedig tudjuk, hogy az egyes elemeket hogy és miként kell megjeleníteni.

1
2
3
4
5
6
7
8
9
class Money {
     private $amount = 1000;
     private $format = "$";
    public function __toString() {
         return $this->amount." ". $this->format;
   }
}
$zsozso = new Money();
echo $zsozso; // 1000 $-t fog kiírni a drága.

__invoke(), azaz idézzünk szellemeket

Az invoke() metódusa akkor hívódik meg, mikor osztályunkra nem változóként, hanem függvényként hivatkozunk. Ezzel lehet érdekes dolgokat csinálni és esetleg megspórolni függvényeket.

Vegyünk pl. egy ZF2 layoutból lopott viewhelpert:

1
$this->HeadLink()->appendFile($src);

Ugye itt az aktuális object context-ből hívunk meg egy metódust, ami visszaad egy osztályt és azon meghívunk még egy metódust.

Method chaining: Akkor beszélhetünk róla, mikor egy meghívott függvény egy objektumot szolgáltat vissza (gyakran önmagát), ami referenciaként szolgál egy következő függvény meghívásához és így tovább.

Alábbi esetet kétféleképpen tudjuk elérni, az egyik, ha az osztályunkban van egy

private function HeadLink() {

return HeadLink::getInstance(); // vagy épp ami, a lényeg, hogy a hívó kontextusában kell definiálni.

}

a másik verzió, ha a meghívott objektumon belül biztosítjuk az ilyesfajta elérést és az osztály egy példányát tároljuk egy HeadLink nevű változóban a hívó contextusában:

1
2
3
4
5
6
7
8
9
10
// ez lesz a fenti példában a $this context
class Layout {
    private $HeadLink = HeadLink::getInstance();
}
// ez hívódik meg
class HeadLink extends Singleton { // az extend csak az előbbi példa okán
    public function __invoke() {
    return $this;
   }
}

Persze nem csak önmagát szolgáltathatja vissza, lehet egyfajta konstruktor is (habár csak objektumPÉLDÁNYON hívható meg), vagy igazából bármit tehetünk az osztályunkban.

__set_state()

Ez a metódus a var_export esetében (ami majdnem ugyanaz, mint a var_dump, azt leszámítva, hogy ez VALID, VÉGREHAJTHATÓ php kódot eredményez) siet a segítségünkre, paraméterként pedig a var_export-nak átadott paramétereket kapja meg, egy asszociatív tömb formájában. Ezen paraméterek a példányváltozók név => érték formában vannak rendezve benne.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class VarExportable {

    public static function __set_state($array) {
         $export = new self(); // csinálunk még egyet magunkból
         foreach ($array as $key => $value) { //végigszuttyogjuk a tömböt és a kulcsokat példányváltozóként hozzáadjuk
            $export->$key = $value;
         
         }
         return $export; // aztán visszapasszoljuk az objektumot
    }

}

$a = new VarExportable();
$a->dara = "nein";
$a->para = "foo";

var_export($a, true); // ez a következőt teszi: VarExportable::__set_state(['dara' => "nein", 'para' => "foo" ]);

__clone()

Végre valami, amit valamivel gyakrabban fogunk használni. A __clone() metódus akkor kerül meghívásra, mikor a clone kulcsszóval lemásolunk egy már létező objektumot, annak minden változójával együtt. A __clone() metódusunk egyfajta event handlerként működik, (ahogy sok más mágikus metódus is) így a klónozáskor ha bármit tennénk (változtatásokat eszközölnénk az osztályon, referenciákat hagynánk itt-ott, stb), azt itt végre tudjuk hajtani, nem kell a hívó fél oldalán mahinálnunk.

__debugInfo()

Ez a metódus (PHP 5.6 és afelett) a var_dump függvény működését hivatott felülbírálni, hiszen egy nagy kompozit (az osztály viselkedését nem örökléssel, hanem több más osztálypéldány/referencia magába foglalásával érjük el) osztálypéldány esetében a var_dump igen csúnya outputot tud produkálni, arról nem is beszélve, hogy annak előállítása akár perceket is igénybe vehet.

Így ha tudjuk, hogy egyes osztályaink esetében milyen infókra lenne szükségünk egy var_dump() esetén, szimplán összeszedegetjük őket pl. egy asszociatív tömbbe, jól érthető kulcsok használatával és kiíratjuk azt.

comments powered by Disqus