A PHP 4-es verziójában jelent meg igazán az objektum-orientáltság / objektum orientált programozás. Ez a verzió még nem tartalmazott mindent OOP-s eszközt (pl. láthatóság), de lehetőség volt már például öröklődéssel újrahasznosítani az egyes osztályok kódjait.
A más nyelvekben megszokott polimorfizmust a PHP nem támogatja, de mivel azonban a nyelv gyengén típusos, így ez a hiányosság nem jelent túlságosan nagy problémát a felhasználóknak.
Az osztályokat a már más nyelvekben megszokott class kulcsszóval lehet bevezetni a PHP-ben is. Kezdetben az osztály attribútumait csak a var kulcsszóval lehetett jelölni, ám ez megegyezik a PHP 5-től bevezetett public láthatósággal (azaz szabadon elérhető bárhonnan).
Destruktorok kezdetben még nem voltak, de lehetőség volt már az osztályoknak konstruktorokat definiálni, különböző paraméterekkel.
Konstruktor nevének ekkor (a PHP 4-ben) még kötelezően meg kellett egyezni annak az osztálynak a nevével, amelyben definiálták.
Ha egy leszármazott osztálynak nincs saját konstruktora, a példányosításnál automatikusan meghívódik a szülő osztályának konstruktora (ha van).
PHP3-ban ez nem működött, ott nem kereste meg az értelmező a szülőosztály konstruktorát.
Osztályszintű (statikus) attribútumok ekkor (PHP 4-ben) még nem voltak támogatottak, csak az objektumokhoz kapcsolódhattak.
Objektumot a new utasítással tudunk létrehozni. A metódusokban használhatjuk a $this változót, amely az objektumpéldányra mutat. Olyan metódust, amely nem használja az osztály attribútumait és $this-t, hívhatunk objektum nélkül is a :: operátorral (lásd pl. C++).
Destruktor: mivel PHP 5 előtt nem volt alapértelmezett destruktor, ezért nekünk kellett létrehoznunk azt (ha szeretnénk, hogy legyen).
Péda egy automatizált destruktorra:
A példa egy globális utasításon éri el az objektumot, amiben végrehajtja a destruktor() függvényt, aztán feloldja az objektumot.
Ha azt szerettük volna, hogy a destruktor automatikusan fusson le a szkript végén, használnunk kellett a register_shutdown_function() függvényt. A leállítási függvény az összes változó megsemmisítése előtt és az utolsó output elküldése után fut le, ezért a destruktor függvényekben az echo, vagy print utasítások nem szerepelhetnek.
Az extends kulcsszóval az öröklődést jelölhetjük, többszörös öröklésre nincs lehetőség. A szülőre hivatkozáskor használhatjuk a parent kulcsszót, de használhatunk explicit típusmegjelölést is a :: scope operátorral.
A Zend Enigne 1.0 objektum modelljében, a példányosított objektumok nyelvi szintű elemek. Ez azt jelenti, hogy amikor a programozók műveleteket hajtanak végre az objektumokkal, akkor a nyelv az egyszerű típusokhoz nagyon hasonlóan kezeli az objektumokat. Szemantikusan ez azt jelenti, hogy ilyenkor az egész objektum lemásolódik. A Java megközelítése teljesen más hiszen az objektumokat egy 'handle-n', azaz kezelőn keresztül éri el, ezt úgy képzelhetjük el, mint az objektumoknak kiosztott egyedi azonosító.
A létrehozott objektum modellt nagyban befolyásolta a Java objektum modellje. Általánosan, ha egy új objektumot példányosítunk, akkor az objektumhoz egy handle-t kapunk ahelyett, hogy önmagát az objektumot kapnánk meg. Amikor ezt a handle-t függvény bemenő paraméterként adjuk át, illetve más egyéb esetekben is, csak és kizárólag a handle az, ami átadódik az objektum maga soha! Az objektum maga soha nem másolódik le, és nem is duplikálódik, aminek eredményeként egy objektum minden handle-je mindig ugyanarra az objektumra mutat. Ezen változtatások nyomán, az objetumok alapvető használata majdnem teljesen megegyezik ez Engine-k korábbi változataiban találhatóval, azonban nem fogunk kínos és követhetetlen objektum-másolásba és -megsemmisítésbe ütközni. Hogy példányosítsunk és használjunk egy objektumot, a következő kódot írjuk:
A kódban létrehozunk egy Myclass példányt, amelynek a handle-jét hozzárendeljük a $object változóhoz, ami után meghívjuk a példány egy metódusát.
Tekintsük az alábbi kódot:
Az új Java-szerű handle-k nélkül, a 20. sorban az objektum $member adattagja a "bar" stringet veszi fel értékül. A Zend Engine 1.0-ban az objektum belső reprezentációja miatt, az Engine az objektumot referenciaként jegyzi fel és amikor a foo() függvénynek bemenő paraméterként átadjuk, az objektum duplikálódik (!). Így a foo() függvényen belüli $obj->setMember("foo") hívás, a duplikátum setMember() tagfüggvény hívását fogja eredményezni és nem az eredeti objektum tagfüggvény hívását. A fentiek miatt a 22. sor eredményeképpen "bar"-t fogunk látni kiírva a képernyőre és nem pedig foo-t.
Az új objektum modell a kód sokkal intuitívebb implementációját teszi lehetővé. A 21. sorban az objektum 'handle-je' (ID) értékként adódik át a foo() függvénynek. Emiatt a foo() függvényen belül, az objektum a 'handle'-jén keresztül érhető el, így a setMember() hívás az eredeti példány tagfüggvény hívását eredményezi. Végül a 22. sor eredményeképpen 'foo'-t fogunk látni kiírva a képernyőre és nem pedig 'bar'-t, mint az előző esetben.
Ez egy nagyon egyszerűsített leírása az új modell lényegének, azaz hogy hogyan lett feloldva az objektumok kellemetlen viselkedése, illetve, hogy hogyan lett egyszerűbb és hatékonyabb az objektumok kezelése. A többi, modellbeli újdonság, amit a következőkben tárgyalunk, mind erre az alapra épülnek.
A könnyebb váltás érdekében a 2.0-ás Engine támogatja az opcionális 'auto-clone' funkciót, amelyik akkor lép életbe, ha az 1.0-s Engine esetén az objektum duplikálódna. Beállítható az is, hogy egy E_NOTICE üzenetet kapjunk, amikor az 'auto-clone' életbe lép.
Mivel az 1.0-s Engine objektum modellje nem a megfelelő alapokon nyugszik, ezért az objektum visszatérő értékként való felhasználása kifejezetten kényelmetlen volt. Az alábbit kellett alkalmaznunk $foo = &bar() , ha azt akartuk, hogy a bar() függvény az objektumot referenciaként adja vissza. Ráadásul nem is volt lehetséges egy objektumot referencia szerint visszaadni egy referencia szerint átadott aktuális argumentumnak, az eddigi objektum modell szemantikai jellemzői miatt. Az objektum orientált tervminták, mint például a Gyár (Factory) megkívánják a tiszta és konzisztens OO API implementációt, és az objektumok egymással való egyértelmű kapcsolatát. A hasonló feladatokat ellátó tervminták implementációjánál döntő a könnyű és egyértelmű szintakszis, az objektum függvényből való referenciakénti visszaadása esetén. Az új Engine esetén a fenti kívánalmak egy kellemes mellékhatásként teljesülnek, azaz az objektumokat függvényvisszatérő értékként ugyanúgy lehet visszaadni, mint az egyszerű típusokat.
Tekintsük az alábbi kódot:
A fenti kód végrehajtásakor, az eddig leírtak szerint az elvárt eredményt kapjuk, azaz a $object tényleg a MyFoo objektum FactoryMethod-ban létrehozott példányát fogja jelenteni. A megoldás az Engine 1.0 esetén az alábbi volt: function &FactoryMethod(class_$type) és a 15. sort át kellett írni $object =&FactoryMethod("foo");-ra. A fentiek mellett az új Engine-ben a function FactoryMethod($object_type, &$resulting_object) is minden probléma nélkül megy.
Az objektumok dereferencia képzése az alábbi $object->method()->method() vagy akár func()->method()->method()->method() kóddal illusztrálható könnyen. A korábbi objektum modell hiányosságai miatt a dereferencia-képzés nem volt megvalósítható, pedig ez sokszor jobban átlátható kódhoz vezet. A régi modellben a $object->method()->method()->method() így volt megoldható:
A PHP5-ben az objektumok összehasonlítása sokkal összetettebb, mint a PHP4-ben és sokkal inkább azt nyújtja, mint amit egy Objektum Orientált Nyelvtől várunk. Amikor az == operátort használjuk, akkor egyszerűbb módon vizsgálja a nyelv az egyezést mégpedig: Két objektum példány egyenlő, ha egy osztályból valók, ugyanazok az attribútumaik és az attribútumok értéke is megegyezik. Másrészről, ha az === operátorral hasonlítjuk össze őket, akkor két objektum-változó egyenlő, ha ugyanazon osztály ugyanazon példányára hivatkoznak.
A PHP4-ben a konstruktor az osztály egy metódusa volt, mégpedig az osztály nevével megegyező névvel. Ha a származtatott osztályok konstruktorában meghívjuk a szülő osztály konstruktorát, akkor a PHP4 nyújtotta megoldás kényelmetlen volt és egy nagy osztályhierarchiánál követhetetlen is. Ha egy osztály egy másik szülő alá került a származtatási fában, akkor a szülő konstruktorának a neve is változott, így a kódunkat is konzisztensen át kellett írnunk. Igen, csak ez újabb nehezen felderíthető hibák forrásává vált. A PHP5 bevezet egy teljesen új konstruktor deklarációs módot, amivel ezen problémák kikerülhetők: a function __construct() a konstruktor deklarációja. Ezen kívül a parent kulcsszóval hivatkozhatunk a szülőosztályra. A szülőosztály konstruktora nem hívódik meg implicit módon a gyermekosztály konstruktorában. A szülőosztály konstruktora parent:: __construct() kóddal hívható meg.
Példa:
A visszamenő kompatibilitás érdekében, ha a PHP5 nem talál egy __construct() függvényt a szóban forgó osztályban, akkor keresni fogja a régi stílusú konstruktort. Ez azt jelenti, hogy csak akkor fogunk kompatibilitási problémába ütközni, ha az osztályunknak létezik egy __construct() metódusa és ez más szemantikát takar, mint a konstruktor.
A konstruktoroknak és a destruktoroknak public láthatóságúnak kell lenniük. Esetleg egy abstract osztályban nem muszáj, ott a leszármazott osztályokból hívhatjuk őket meg a parent::__construct()-al. Erre érdemes odafigyelni, mert bár a default láthatóság public, ha ettől eltérünk:
Természetesen adódik a kérdés, hogy ha a konstruktornak a __construct() nevet kell viselnie, akkor hogyan írhatunk több konstruktort?
(Akad néhány nem túl szép objektumorientált megoldás, például a func_get_args() és a func_num_args() függvény hívásával.)
Példa:
A lehetőség, hogy a destruktorokat a fejlesztők átdefiniálhassák nagyon fontos, hiszen a destruktorok végezhetik el a naplózási feladatokat, szakíthatják meg az adatbázis kapcsolatot vagy végezhetnek egyéb "tisztítási utómunkát". A PHP4-ben nem létezik objektum destruktor mechanizmus. A PHP5 bevezeti a destruktor fogalmát más OOP-t támogató nyelvekhez hasonlóan és a destruktor meghívása sem tér el a megszokottaktól: amikor az utolsó hivatkozás is megszűnik az objektumra, akkor meghívódik a destruktor, még mielőtt a memóriából kitörlődne az objektum. A destruktor minden osztályban a function __destruct() névvel definiálható.
Destruktorban soha se dobjunk kivételt! Nem csak PHPban, más nyelvekben sem (pl. C++).
Példa:
A konstruktorhoz hasonlóan a szülőosztály destruktora nem hívódik meg implicit módon a gyermekosztály destruktorában. A szülőosztály destruktora parent::__destruct() kóddal hívható meg.
Az előző objektum modellben nem volt lehetőség kikényszeríteni egy objektum megszűnését, míg arra volt referencia. A PHP5-ben már megtalálható a delete metódus, ami ezt a ritkán szükséges szolgáltatást nyújtja. Ha meghívjuk ezt a metódust egy objektumon, akkor az meghívja az objektum destruktorát és felszabadítja az objektumot, még akkor is, ha arra akár több helyen is hivatkoznak még. Ekkor ezek a hivatkozások elromlanak, használatuk fatal error-t vált ki.
A PHP4 nem nyújtott módot arra, hogy a fejlesztők eldöntsék, hogy milyen másoló kontruktor fusson le, ha egy objektum duplikálódik. A PHP4 a másolás során bitről bitre lemásolja az érintett objektumot. Mint ismeretes, az ilyen lemásolás nem minden esetben a megfelelő, főleg akkor, ha erőforrás is tartozik az objektumhoz, illetve, ha más objektumokkal is kapcsolatban van. Egy egyszerű példa: Fejelemes Láncolt Lista. Az objektum másolásához az objektum __clone() metódusát kell meghívnunk:
A PHP 5 bevezette a private és protected adattagokat, amelyek láthatósága osztályszintű, azaz a private adattagokat csak az osztályon belül lehet látni, a protected adattagokat pedig az öröklődés során a gyermekosztályok is látják. Amelyik adattag elé nem írunk se private, se protected minősítőt akkor az automatikus public minősítőt kap.
Ha megegyezik a láthatósága két adattagnak, írhatjuk őket egy sorba (mivel nem írjuk ki a típusukat, nem is különbözhetnek).
Pl.
A tagfüggvények esetén is bevezetésre kerültek a láthatósági minősítők, az adattagoknál leírtak itt is érvényesek a láthatóságra. Ha nem adunk egy metódusnak láthatósági minősítőt, akkor alapértelmezetten public elérhetőségű lesz.
Példa:
Egy adattag vagy egy objektum statikusként(static) való definiálása elérhetővé teszi őt az objektum kontextusán kívülről is, azaz osztályszintre emeli! A statikusként definiált adattag illetve metódus nem érhető el mint egy egyszerű adattag és nem definiálható újra az öröklődés során! A statikusnak való definiálásnak a láthatósági deklaráció után kell lennie. Ha nincsen láthatósági deklaráció, akkor alapértelmezés szerint public staticként lesz definiálva. Mivel a statikus tagfüggvények hívhatók anélkül, hogy az objektumosztályból példányosítottunk volna, ezért a $this pszeudo változó nem érhető el a statikus metódusokból, helyette a $self::$mező vagy $self::metódus () illetve osztálynev::$mező illetve osztálynev::metódus () használandó.
Példa:
Példa:
Lehetőség van konstansok definiálására osztályszinten. A konstansok abban különböznek a normál változóktól, hogy nincs szükség a $ szimbólumra a deklarálásukhoz illetve a használatukhoz. Akár a statikus változók, a konstansok sem érhetők el objektum példányokon belülről, csak a `self ::` segítségével!
Példa:
PHP 5 előtt lényegében csak az is_a() és a get_class() függvények segítségével tudtuk megvizsgálni hogy egy adott objektum típusát. Ezen függvények azonban használhatatlanok voltak a származtatás, valamint a kiterjesztés vizsgálatára, így PHP 5-ben bevezetésre került az instanceof operátor, amely segítségével megtudhatjuk, hogy egy az objektum az adott osztály egy példánya, kiterjesztése, vagy esetleg egy interface-t implementál-e. Az instanceof akkor ad vissza igaz értéket, ha az objektum egy olyan osztály példánya, amely a vizsgált osztály öröklődési fájában van.
A PHP5 bevezette a lehetőséget, hogy egy függvény deklarációjában megadhatjuk, hogy a várt argumentum milyen osztályba tartozik. Beépített típusokra nem terjeszthető ki ez a lehetőség, csak osztály típusokra használható!
Példa:
Szintén bevezetésre kerültek az absztrakt osztályok és metódusok. Az absztraktnak definiált osztályok nem példányosíthatók. Egy osztályt absztraktnak kell definiálnunk, ha létezik legalább egy absztrakt metódusa. Az absztraktnak definiált metódusok csak deklarálják a metódus szignatúráját, de az implementációt nem definiálhatják. Az absztrakt metódust implementáló osztály a metódus láthatóságát köteles nem erősíteni. Azaz, ha az absztrakt metódus protectednek lett definiálva, akkor az implementáció során a protected illetve a private is megengedett. Az absztraktnak definiálás kulcsszava az abstract.
Példa:
Az objektum interfészek lehetővé teszik a programozóknak, hogy olyan kódot írjanak, amely leírja, hogy egy osztálynak milyen metódusokat kell implementálnia, anélkül, hogy meghatároznák, hogy ezek a metódusok mit csinálnak. Az interfészeket az interface kulcsszóval definiáljuk, teljesen megegyező módón mint a standard osztályokat, de anélkül, hogy definiálnánk a függvénytörzseket, illetve az attribútumokat. Azon osztályok, amelyek egy interfészt definiálnak, az implements kulcsszóval tehetik meg ezt, és minden interfészbeli metódust kötelesek definiálni! Ha egy osztály nem implementál minden ilyen metódust, akkor 'fatal error' hibaüzenettel áll le a kód végrehajtása, és az üzenettel együtt megkapjuk, hogy mely metódusokat nem implementáltuk. Egy osztály több interface-t is implementálhat.
Az interface-ek megvalósítása feltűnően hasonlít a Java nyelvben megvalósított interface-ekre. Lényegi különbség, hogy a PHP-ben lehet konstruktor az interface-ben, viszont átlapolás nem lehetséges. Azaz nem lehet két olyan inteface-t megvalósítani egy osztállyal, melyekben van két azonos nevű metódus.
Kivéve persze akkor, ha a metódusok szignatúrája megegyezik, vagy egymásnak megfeleltethető. Például az egyik interface metódusa paraméter nélküli, míg a másik metódusának van egy opcionális paramétere. Az interface-t megvalósító osztályról is az mondható el, hogy a megvalósított metódusnak lehetnek a kötelezőkön kívül opcionális paraméterei alapértelmezett értékkel, azonban kötelezően nem várhat olyan paramétert, amely az interface-ben nem szerepel, és nyilvánvalóan el sem hagyhat az interface által elvárt paramétereket.
Példa:
A PHP5 tartalmaz beépített interface-eket, amelyekkel meghatározhatjuk az objektumok kezelésének módját.
A Standard PHP Library (SPL) további interface-eket és osztályokat biztosít, lásd az iterátoroknál.
Olyan objektumok létrehozását teszi lehetővé, amelyek elérhetőek a tömbindexelés szintaxisával. Az Iterator interface-szel kombinálva tömbként viselkedő, de speciális tulajdonságokkal rendelkező objektumokat konstruálhatunk.
ArrayAccess interface metódusai:
Az interface egy lehetséges megvalósítása: szeretnénk, ha bizonyos változókat meg tudnánk osztani különböző folyamatok között. Ezért konstruálunk egy olyan osztályt, melynek példányai úgy viselkednek, mint egy asszociatív tömb, ám a háttérben nem egyszerű kulcs–érték alapú tárolás történik, hanem az osztály egy adatbázisból (DBM fájlból) éri el az adatokat, amelyet mindegyik folyamat használni tud. Az osztály implementálja az ArrayAccess interface metódusait, amelyek végrehajtódnak a megfelelő, szögletes zárójellel történő elemlekérdezésekkor.
A final kulcsszóval elérhetjük, hogy a származtatás során a gyermek osztályok az ezen kulcsszóval ellátott metódusokat illetve változókat nem definiálhatják felül.
Példa:
Az iterációnak két módját különböztetjük meg, az egyszerű iterációt és az Iteratorral való iterációt.
Ezen iteráció során az éppen aktuális láthatósági hatókörben látható objektum attribútumokon lépked végig az iteráció. Az iteráció például a foreach-csel hajtható végre.
1. Példa:
A PHP5 beépített IteratorAggregate Interface és Iterator Interface implementációjával egy lépéssel tovább mehetünk, ugyanis ekkor mi dönthetjük el, hogy az iteráció hogyan történjen. Közülük az elsőben csak egy getIterator() metódust kell implemetálnunk, ami egy tömböt vagy egy objektumot ad vissza, mely objektum vagy az Iterator Interface-t implementálja, vagy egy iterálható osztály példánya.
Minden iterálható osztály megvalósítja a Traversable interface-t – a foreach ciklus az összes ilyen objektumot kezelni tudja. A Traversable-t azonban saját osztállyal nem implementálhatjuk, csak a belőle származtatott Iterator vagy IteratorAggregate interface-t. Ez a két interface abban különbözik, hogy belső vagy külső iterátort használnak. Belső (vagy aktív) iterátornak azt nevezzük, amely módosítja az objektumot magát, külső (vagy passzív) iterátornak pedig azt, amely csupán egy másik objektumra mutat, és azt nem módosítja.
Az IteratorAggregate-et olyan objektumok valósítják meg, amelyek külső iterátort tartalmaznak, míg az Iterator interface belső bejárhatóságot vagy külső iterátor használatát biztosítja.
Példa:
További iterátor osztályok itt: http://www.php.net/manual/en/spl.iterators.php
A PHP5 egyik legnagyszerűbb újítása hogy a metódusok és adattagok dinamikussá tehetők. A __call(), __get(), __set() metódusok segítségével érhetjük el a dinamizmust. Ezek a metódusok alapértelmezés szerint minden osztálynak tagjai, és csak azon esetben hívja meg őket a nyelv, ha egy olyan metódust illetve attribútumot akarunk elérni, amelyet nem definiáltunk az osztályunkban.
A PHP nyelv támogatja az objektumok bitfolyammá való be- és kicsomagolását. Ennek a technológia segítségével az objektumainkat eltárolhatjuk fájlban, adatbázisban, illetve más programoknak is átadhatjuk. A serialize() függvény segítségével tudjuk eltárolni az objektumokat késöbbi használatra, míg az unserialize() függvénnyel tudunk egy bitsorozatot visszalakítani objektummá.
Szerializálásnál lehetőségünk van konstruktor illetve destruktor szerű kiegészítő tevékenységet elvégezni, amennyiben megvalósítjuk az adott osztályban a __sleep() és a __wakeup() metódusokat. Használat során ezek a serialize() illetve az unserialize() függvények lefutásakor automatikusan meghívódnak.
A hatókör feloldó operátor (másnéven Paamayim Nekudotayim [héberül a dupla kettőspont]) vagy egyszerűbb néven a dupla kettőspont, egy token, amely lehetővé teszi, hogy a statikus, a konstans és a felüldefiniált adattagokat illetve metódusokat elérjünk. A felüldefiniálás az öröklődés során lép fel.
Példa (elérés az osztály definíción kívülről):
PHP 5 rengeteg újdonságot hozott magával. Egyik ilyen újdonság az ún. reflection, ami már eléggé ismert fogalom azok számára, akik java vagy c# nyelvben dolgoztak. Röviden szólva, reflection lehetőséget ad egy objektumnak, hogy futási időben lekérdezze mindenféle információt saját vagy más osztályról. Egy lényeges különbség a c#/Java reflection és php5-beli reflection közt, hogy a php5-beli reflection lehetőségeket nyújt dokumentációs kommentek futási idejű lekérdezésére.
Fizikaliag php5-ben reflection nem más mint következő osztályokat/interfészeket tartalmazó objektum orientált bővítmény a Zend-Engine -hez:
<?php class Reflection { } interface Reflector { } class ReflectionException extends Exception { } class ReflectionFunction extends ReflectionFunctionAbstract implements Reflector { } class ReflectionParameter implements Reflector { } class ReflectionMethod extends ReflectionFunctionAbstract implements Reflector { } class ReflectionClass implements Reflector { } class ReflectionObject extends ReflectionClass { } class ReflectionProperty implements Reflector { } class ReflectionExtension implements Reflector { } ?>
Ezekkel részletesebben foglalkozunk a továbbiakban!
Legegyszerűbb példa a reflection használatára a beépített export statikus metódus használata:
<?php Reflection::export(new ReflectionClass('Exception')); ?>
Ami következő kimenetet fog produkálni:
Class [class Exception ] { - Constants [0] { } - Static properties [0] { } - Static methods [0] { } - Properties [6] { Property [ protected $message ] Property [ private $string ] Property [ protected $code ] Property [ protected $file ] Property [ protected $line ] Property [ private $trace ] } - Methods [9] { Method [ final private method __clone ] { } Method [ public method __construct ] { - Parameters [2] { Parameter #0 [ $message ] Parameter #1 [ $code ] } } Method [ final public method getMessage ] { } Method [ final public method getCode ] { } Method [ final public method getFile ] { } Method [ final public method getLine ] { } Method [ final public method getTrace ] { } Method [ final public method getTraceAsString ] { } Method [ public method __toString ] { } } }
Először a Reflector interfésszel foglalkozunk, mert minden reflection-osztály implementálja a Reflector interfészt!
<?php interface Reflector{ public string __toString(); public static string export(); } ?>
Amint láthatjuk maga a Reflector nem szükségel túl nagy funkcionalítást azon osztályoktól, amik őt implementáljak. Egyetlen megkötés az, hogy a __toString mindenképpen újraimplementálandó és hogy a Reflector-t implementáló osztálynak legyen statikus export metódusa!
ReflectionException osztály nem más, mint Exception leszármazottja, és kizárólag azt a célt szolgálja, hogy a kezelése egyszerűbb legyen (el tudunk kapni minden reflection-kivételt és figyelmen kívül hagyni minden mást!). Így semmi újat nem definiál, sima Exception-ként kezelhető.
Nevéből is sejthetően a ReflectionFunction osztály függvények adatainak lekérdezésére használható.
<?php class ReflectionFunction extends ReflectionFunctionAbstract implements Reflector{ final private __clone() public void __construct(string name) public string __toString() public static string export(string name, bool return) public string getName() public bool isInternal() public bool isDisabled() public bool isUserDefined() public string getFileName() public int getStartLine() public int getEndLine() public string getDocComment() public array getStaticVariables() public mixed invoke([mixed args [, ...]]) public mixed invokeArgs(array args) public bool returnsReference() public ReflectionParameter[] getParameters() public int getNumberOfParameters() public int getNumberOfRequiredParameters() } ?>
Deklarációjából látható, hogy egy ReflectionFunction objektum létrehozásakor meg kell adni paraméterül a vizsgálandó függvényt. Ezután lehetőségünk van arra hogy:
A szülő osztálya, ReflectionAbstractFunction ugyanazokat a metódusokat tartalmazza, kivéve invoke(), invokeArgs(), export() és isDisabled()-t. Továbbá érdemes megjegyezni, hogy getNumberOfParameters() és getNumberOfRequiredParameters() metódusok az 5.0.3 verzió óta használhatóak, és invokeArgs() az 5.1.0-s verziótól kezdve.
ReflectionParameter osztályt egy metódus vagy függvény egy paraméteréhez kapcsolódó információk eléréséhez használhatjuk. Strukturája a következő:
<?php class ReflectionParameter implements Reflector{ final private __clone() public void __construct(string function, string parameter) public string __toString() public static string export(mixed function, mixed parameter, bool return) public string getName() public ReflectionClass getDeclaringClass() public ReflectionClass getClass() public bool isArray() public bool allowsNull() public bool isPassedByReference() public bool isOptional() public bool isDefaultValueAvailable() public mixed getDefaultValue() public int getPosition() } ?>
ReflectionParameter objektumait általában úgy kapjuk, hogy ReflectionMethod vagy ReflectionFunction getParameters metódusát hívjuk meg.
Egy ReflectionParameter objektum következő funkcionalítással bír:
ReflectionClass lehetőséget nyújt arra, hogy az osztályokkal futási időben dolgozzunk, és osztályokra vonatkozó információkat elérjük.
Megjegyzésre szorul, hogy a beépített típusok nem osztályok, vagy legalábbis nem kompatibilisek a miáltalunk definiált osztályokkal, így ReflectionClass nem alkalmazható rájuk: new ReflectionClass('int') nem működik!
<?php class ReflectionClass implements Reflector{ final private __clone() public void __construct(string name) public string __toString() public static string export(mixed class, bool return) public string getName() public bool isInternal() public bool isUserDefined() public bool isInstantiable() public bool hasConstant(string name) public bool hasMethod(string name) public bool hasProperty(string name) public string getFileName() public int getStartLine() public int getEndLine() public string getDocComment() public ReflectionMethod getConstructor() public ReflectionMethod getMethod(string name) public ReflectionMethod[] getMethods() public ReflectionProperty getProperty(string name) public ReflectionProperty[] getProperties() public array getConstants() public mixed getConstant(string name) public ReflectionClass[] getInterfaces() public bool isInterface() public bool isAbstract() public bool isFinal() public int getModifiers() public bool isInstance(stdclass object) public stdclass newInstance(mixed args) public stdclass newInstanceArgs(array args) public ReflectionClass getParentClass() public bool isSubclassOf(ReflectionClass class) public array getStaticProperties() public mixed getStaticPropertyValue(string name [, mixed default]) public void setStaticPropertyValue(string name, mixed value) public array getDefaultProperties() public bool isIterateable() public bool implementsInterface(string name) public ReflectionExtension getExtension() public string getExtensionName() } ?>
ReflectionClass jogosan nevezhető a reflection-osztályok legbonyolultabb osztályának. Egy objektumát vizsgálandó osztály nevének megadásával hozhatjuk létre és ezután lekérdezhetjük az osztályra vonatkozó információkat:
Egy érdekes dolog az, hogy, amint a fentiekből is látható, interfészek adatainak lekérdezése szintén ReflectionClass osztály segítségével történik. Ez okot ad feltételezni, hogy php5-ben az interfészek és az osztályok között kevesebb különbség van, mint más nyelvekben.
ReflectObject osztály ReflectClass osztályból származik és arra ad lehetőséget, hogy objektumokkal is dolgozzunk
<?php class ReflectionObject extends ReflectionClass{ final private __clone() public void __construct(mixed object) public string __toString() public static string export(mixed object, bool return) } ?>
A ReflectionClasshoz hasonló funkcionalítást ad
Függvényekhez hasonlóan metódusokat is tudjuk "reflect"-álni. A ReflectionFunction-nal közös osztályból származó ReflectionMethod ad lehetőségeket erre
<?php class ReflectionMethod extends ReflectionFunctionAbstract implements Reflector{ public void __construct(mixed class, string name) public string __toString() public static string export(mixed class, string name, bool return) public mixed invoke(stdclass object [, mixed args [, ...]]) public mixed invokeArgs(stdclass object, array args) public bool isFinal() public bool isAbstract() public bool isPublic() public bool isPrivate() public bool isProtected() public bool isStatic() public bool isConstructor() public bool isDestructor() public int getModifiers() public ReflectionClass getDeclaringClass() // Inherited from ReflectionFunctionAbstract final private __clone() public string getName() public bool isInternal() public bool isUserDefined() public string getFileName() public int getStartLine() public int getEndLine() public string getDocComment() public array getStaticVariables() public bool returnsReference() public ReflectionParameter[] getParameters() public int getNumberOfParameters() public int getNumberOfRequiredParameters() } ?>
ReflectionFunction-hoz képest következő extralehetőségeket ad a ReflectionMethod:
ReflectionProperty osztály lehetőséget ad egy osztálybeli adattagok tulajdonságainak vizsgálatára. Itt érdemes megjegyezni, hogy php-ben property szót használnak más nyelvekben adattagoknak nevezett elemekre!
<php class ReflectionProperty implements Reflector{ final private __clone() public void __construct(mixed class, string name) public string __toString() public static string export(mixed class, string name, bool return) public string getName() public bool isPublic() public bool isPrivate() public bool isProtected() public bool isStatic() public bool isDefault() public void setAccessible() /* As of PHP 5.3.0 */ public int getModifiers() public mixed getValue(stdclass object) public void setValue(stdclass object, mixed value) public ReflectionClass getDeclaringClass() public string getDocComment() } ?>
ReflectionProperty egy objektumát kétféleképpen hozhatjuk létre, vagy ReflectionClass osztály getProperty(name)/getProperties() segítségével, vagy explicit módon, pl.: new ReflectionProperty('String','length').
Következőket tudjuk elvégezi ReflectionProperty osztály egy példányával:
Megjegyzés: ha egy private láthatóságú adattag értékét szeretnénk elérni és nem állítjuk be a hozzáférhetőséget, akkor kivételt fogunk kapni!
ReflectionExtension segítségével bővítményekhez kapcsolódó információkhoz is hozzáférhetünk.
<?php class ReflectionExtension implements Reflector{ final private __clone() public void __construct(string name) public string __toString() public static string export(string name, bool return) public string getName() public string getVersion() public ReflectionFunction[] getFunctions() public array getConstants() public array getINIEntries() public ReflectionClass[] getClasses() public array getClassNames() public string info() } ?>
Konstruktorban meg kell adni a bővítmény nevét - így szükség lehet az összes aktuális bővítmény nevére, ami get_loaded_extensions() függvény segítségével könnyen megoldható.
Reflection osztályait alkalmazhatjuk származtatáskor, azaz nem "final" kulcsszóval ellátott osztályok. Ez lehetőséget ad arra, hogy saját specializált osztályokat hozzunk létre. Megjegyzendő, hogy ha egy reflection-osztályból szeretnénk származtatni, akkor a gyerek konstruktorában meg kell hívnunk a szülő konstruktorát, különben kivételt fogunk kapni!
PHP nyelv egyik erőssége és gyengesége egyaránt a gyenge típusosság. Objektum orientált elvek pedig egyre jobban épülnek be a nyelvbe.
Objektum orientált programozás egyik jellegzetessége a polimorfizmus, ami egyszerűsítve azt jelenti, hogy két azonos névvel rendelkező metódust a várt paraméterek típusa alapján különböztetjük meg és típustól függően a fordító (vagy interpereter) megfelelő függvényt hívja meg. PHP gyenge típusossága miatt ezt nem tudjuk kihasználni, pedig némelyik esetben nagyon hasznos lenne. Most megmutatjuk, hogy reflection és "magic methods" használatával hogy szimulálható a polimorfizmus.
Amint az előző fejezetekben olvashattunk, amennyiben az interpreter nem találja meg a meghívott metódust, akkor az adott objektum __call metódusát hívja meg (amennyiben definiálva van) következő paraméterekkel:
public function __call($method, $params){ if ($method === "WriteIsString" && count($params) == 1){ if (isString($params[0])) echo "String"; else echo "not a String"; } else parent::__call($method, $params) }
Így már egy lépéssel előrébb kerültünk ahhoz, hogy egy függvénynévvel két (vagy több) különböző függvényt hívhatunk meg paraméterek típusától függően.
Mivel php-ben nem definiálhatunk több azonos nevű metódust ezért a várt paraméterek típusát valahol máshol kell tárolni. Egyszerűség kedvéért most nevében fogjuk tárolni a paraméterektől elvárt típust, azaz például egy Write függvény ha egy int típusú paramétert vár, akkor Write_Int legyen a neve, ha string típusú paramétert, akkor Write_String.
Ezzel a konvencióval ez előző példát felhasználva csak annyi feladatunk maradt polimorfizmus eléréséhez, hogy a __call függvényen belül megállapítsunk minden paraméter típusát, megkeressünk olyan metódust, ami ezeket a paramétereket várja és a neve (típusinformáció nélkül) megegyezik a hívott metódus nevével, és ha ilyen létezik meghívni őt. Minden lépés egyszerűnek tűnik, de mindegyikben van valami előre nem feltételenül látható bonyolultság.
Paraméterek típusainak megállapításakor 2 problémával is szembe kell néznünk - beépített típusokat nem kezelhetjük úgy, mint a definiált osztályokat, külön meg kell vizsgálni, hogy beépített típusú e egy paraméter. Második probléma az lehet, hogy a gettype minden objektumra "object"-et ad vissza, így a típusuk továbbra is rejtély marad... Egyik lehetőség a beépített get_declared_classes(), illetve is_a() függvények használata. Nem túl szép a műveletigénye ennek a módszernek, hiszen végig kell néznünk rossz esetben az összes létező osztályt, de most eltekintünk a hatékonyságtól. Tehát egy típuskikereső metódus így nézhet ki:
static private function getClassName($arg){ if (is_int($arg)) $result = 'Int'; else if (is_array($arg)) $result = 'Array'; else if (is_bool($arg)) $result = 'Bool'; else if (is_float($arg)) $result = 'Float'; else if (is_string($arg)) $result = 'String'; else{ foreach(get_declared_classes() as $class){ if (is_a($arg, $class)){ $result = xstring::capitalize($class); break; } } } return $result; }
Következő lépésben (megfelelő névvel és paraméterek típusával rendelkező metódus kikeresése) elsőre szintén egyszerűnek tűnik. ReflectionClass getMethods metódusával lekérjük az összes hozzátartozó metódust és megnézünk, hogy milyen típusú paramétereket vár egy-egy függvény. A típusvizsgálatra legegyszerűbb megoldás látszólag a típusnevek összehasonlítása stringként. Ez jól is működik bizonyos egyszerű példákon, de az objektum orientált világban használhatatlan, hiszen az osztály nevéből nem derül ki a szülője neve. Pedig abban az esetben is megfelelő a metódus, ha a paraméterként kapott objektum szülőjét várja paraméterül. Ez pedig azt jelenti, hogy a vizsgálathoz szükségünk van ReflectionClass isSubclassOf metódusára. Viszont ha erre írjuk át az összehasonlító függvényünket, akkor újabb hibát tapasztalhatunk - alaptípusokra nem működik az eljárásunk, mégpedig azért, mert beépített típusokra nem tudjuk létrehozni a ReflectionClass objektumot. Szerencsénkre származtatni sem tudunk belőlük, így elég ha megnézzünk, hogy a két vizsgált típus között szerepel e beépített típus és ha igen, akkor string-két hasonlítjuk össze, ha nem, akkor ReflectionClass segítségével. Így az összehasonlítás így nézhet ki:
static private function isUsableAs($className, $baseClassName){ if (in_array($className, self::$innerTypes) || in_array($baseClassName, self::$innerTypes)){ return $className == $baseClassName; } else{ $class = new ReflectionClass($className); $neededClass = new ReflectionClass($baseClassName); return $class->getname() == $neededClass->getName() || $class->isSubclassOf($neededClass); } }
Ezután valóban egyszerű a feladatunk, csak meg kell találnunk a megfelelő metódust és meghívni őt, vagy exception-t dobni, ha olyan nem létezik!
A PHP 5.4.0-ben bevezetésre kerültek a Traitek. A trait jelentése tükörfordításban: jelleg, jellegzetesség. A Traitek a kód újrafelhasználásra biztosítanak számunkra lehetőséget. Ez egy olyan eljárás, amelyet főleg az olyan egyszeri öröklődést támogató nyelvekben használnak, mint a PHP. A Traitek célja, hogy csökkentsék az egyszeres öröklődéshez társuló bizonyos megszorításokat oly módon, hogy megengedik a fejlesztőknek azt, hogy bizonyos függvények halmazát szabadon újra felhasználhassák különböző egymástól független osztályokban, különböző osztályhierarchiákban. A Traitek és osztályok kombinációjának szemantikája olyan módon van definiálva, hogy csökkenjen a komplexitás és el tudjunk kerülni olyan tipikus problémákat, amely a többszörös öröklődéshez kapcsolhatóak.
Egy Trait hasonlít az osztályokhoz, azonban a célja csak annyi, hogy bizonyos funkciókat csoportosítson konzisztens módon. Trait példányosítása önmagában nem lehetséges. Ez egy adalék a hagyományos öröklődéshez és viselkedések horizontális kompozíciójára ad lehetőséget.
A precedencia Traitek esetében érdekes kérdés és érdemes vele foglalkozni. Nem biztos, hogy magától értetődő.
Abban az esetben, ha egy osztályon belül van egy olyan származtatott tagunk, aminek a neve megegyezik egy, az osztályon belül használt Traiten belüli tag nevével, akkor a Traiten belüli tag felülírja az osztály származtatott tagját.
Kimenet:
Abban az esetben viszont, ha egy osztályon belüli tag neve megegyezik egy, az osztályon belül használt Traiten belüli tag nevével, de ez a tag nem származtatott tag, akkor az osztály felülírja a Traiten belüli tag viselkedését.
Kimenet:
Egy osztály több Traitet is fel tud használni. Ebben az esetben a használni kívánt Traiteket vesszővel elválasztva fel kell sorolni a use kulcsszó után.
Kimenet:
Több Trait használatánál belefuthatunk egy olyan klasszikus hibába, mint a névütközés. Semmi sem zárja ki ugyanis, hogy különböző Traitekben ne lehessenek ugyan olyan nevű függvények. Emiatt könnyen előfordulhat az, hogy ha egy osztályban két olyan Traitet használunk, amelyekben van legalább egy azonos nevű függvény, akkor fatális hibát kapunk.
A konfliktus feloldására a PHP két lehetőséget kínál.
Az első lehetőség az insteadof operátor használata. Ezzel azt tudjuk megadni, hogy az ütköző nevű függvények esetében melyik Traitben lévőt használjuk.
Bár az insteadof operátor megold egy problémát, de rögtön be is hoz egy másikat. Ugyanis mit tehetünk akkor, ha névütközés van, de mi mind a két Traitben lévő megoldást el szeretnénk érni? Erre biztosít lehetőséget az as operátor, amivel álnevet adhatunk a megfelelő függvénynek.
A PHP az as kulcsszó segítségével lehetőséget ad nekünk arra, hogy egy osztályon belül használt Trait tetszőleges függvényének megváltoztathassuk a láthatóságát.
Ahogyan az osztályok tudnak Traiteket használni, úgy a Traiteknek is lehetőségük van erre. Ezzel a nyelvi eszközzel lehetőséget kapunk arra, hogy létrehozzuk tetszőleges Traitek kompozícióját. Ezek után a kompozíciós Traitet használva a kompozícióban szereplő minden Trait függvénye elérhető a kompozíciót felhasználó osztályból.
Kimenet:
A Traitek támogatják az asztrakt metódusok használatát. Absztrakt metódus esetében a felhasználó osztálynak az absztrakt metódust definiálnia kell.
Statikus változók létrehozhatóak Trait metódusokon belül, azonban metódusokon kívül erre nincs lehetőségünk. Lehetőségünk van azonban statikus metódusokat létrehozni, amelyek úgy viselkednek, mintha a felhasználó osztály statikus metódusai lennének.
A Traitek lehetőséget biztosítanak tulajdonságok definiálására. Ezek a felhasználó osztályban úgy viselkednek, mintha a felhasználó osztály tulajdonságai lennének.
Abban az esetben, ha egy Trait definiál egy tulajdonságot, akkor a felhasználó osztály nem definiálhat azonos névvel rendelkező tulajdonságot, különben hibát kapunk. Ez a hiba lehet egy E_STRICT, ha a felhasználó osztályban lévő tulajdonságdefiníció kompatibilis a Traitben lévővel (= azonos láthatóság, azonos kezdőérték), egyébként pedig fatális hibát kapunk.
A legtöbb objektum-orientált alkalmazást író fejlesztő a különböző osztályokat különböző fájlokban deklarálja, illetve definiálja. Ennek az a következménye, hogy azokon a helyeken, ahol az osztályt használni szeretnénk, be kell emelni az osztályt leíró fájlt. Viszonylag zavaró tud lenni, ha sok olyan fájl van, amit kézzel kell beemelgetni.
A PHP 5 lehetőséget biztosít ennek elkerülésére. Definiálhatunk ugyanis egy __autoload() nevű függvényt, amely automatikusan meghívódik abban az esetben, ha olyan osztályt vagy interfacet szeretnénk használni, amely az adott környezetben még nincs definiálva. Ennek a függvénynek a meghívásával a scripting engine ad egy utolsó lehetőséget az osztály betöltésére, mielőtt hibát dobna.
Megjegyzés:
Az 5.3.0 verziót megelőzően az __autoload() függvényen belül kiváltott kivételeket nem lehetett catch blokkban elkapni, így fatális hibát eredményeztek.
Az 5.3.0-ás verzió felett az __autoload() függvényen belül kiváltott kivételeket már el lehet kapni catch blokkban egy kikötéssel.
Egyéni kivétel dobásánál az egyéni kivétel osztálya elérhető kell, hogy legyen.
Az __autolad() függvény rekurzívan használható arra, hogy automatikusa betöltsük az egyéni kivétel osztályát.
Érdemes lehet a paraméterben megkapott sztringet ellenőrizni, hogy megfelelően használható-e fájl betöltésére. Előfordulhat ugyanis, hogy fájl betöltése szempontjából olyan veszélyes alsztringet tartalmazhat, mint a „../”. Példa egy __autoload függvényre:
Az alábbi függvénynevek „mágikusak” a PHP osztályokban: __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state() és __clone().
Ilyen nevű függvényeket csak akkor használhatunk az osztályokon belül, ha a hozzájuk kapcsolt „mágikus funkcionalitást” akarjuk megszerezni.
A serialize() függvény ellenőrzi, hogy egy osztály rendelkezik-e __sleep() függvénnyel. Ha igen, akkor ez a függvény fut le mindenféle szerializáció előtt. Megtisztíthatja az osztályt és vissza kell térnie egy olyan tömbbel, ami azoknak a változóknak a nevét tartalmazza, amelyeket szerializálni kell. Abban az esetben, ha nem ad vissza semmit, akkor NULL fog szerializálódni és figyelmeztetést kapunk.
Az unserialize() függvény a __wakeup() jelenlétét ellenőrzi. Létezése esetén ez a funkció tudja újrakonstruálni azokat az erőforrásokat, amelyek az objektumhoz tartozhatnak.
A tervezett használata ennek a függvények az, hogy újra felépítsen minden adatbáziskapcsolatot, amelyek a szerializáció folyamán elveszhettek, illetve egyéb újrainicializáló műveletek futtatása.
A __toString() metódus lehetőséget ad az osztálynak, hogy meghatározza miként fog viselkedni abban az esetben, ha sztringként kezelik. Ilyen eset lehet például, ha echo-val ki szeretnénk iratni az osztályt. Ennek a függvénynek egy sztringet kell visszaadnia, különben E_RECOVERABLE_ERROR hiba váltódik ki.
Érdemes megjegyezni, hogy az 5.2.0-ás PHP előtt a __toString() csak abban az esetben hívódott meg, amikor az echo(), illetve a print() volt használva. PHP 5.2.0 óta azonban minden sztring kontextusban meghívódik (pl. printf() az %s módosítóval).
Az __invoke() metódus abban az esetben fut le, amikor egy szkript úgy próbálja meg meghívni az objektumot, mintha függvény lenne.
Kimenet:
Ez egy statikus metódus. Akkor hívódik meg, amikor egy osztályt exportálunk a var_export()-tal. 5.1.0-ás verzió óta elérhető.
Az egyetlen paramétere ennek a függvénynek egy tömb az exportált tulajdonságokkal.
Kimenet:
A __call() metódus akkor kerül meghívásra, ha olyan objektum-metódust hívunk meg, amely nem létezik.
A __callStatic metódus akkor, ha statikus kontextusból, vagyis osztály szintű metódust hívunk meg - amely ugyancsak nincs definiálva.
A PHP filozófiájában a rugalmasság eme defenzív megoldás kiváló eszköz lehet MVC keretrendszerekben - például Controller-ek
implementálására -, ahol a hibás felhasználói kéréseket kellő hajlékonysággal szeretnénk kezelni. E két mágikus metódus segítségével
olyan komponensek védhetünk meg a hibás működéstől, amelyek tartalmaznak valamilyen generált kódot - mondjuk a függvény-hívás
közvetve, de felhasználói oldalról érkezik.
Minkét mágikus függvényt szignatúráját analóg módon adjuk meg: szükségünk van egy sztring típusú változóra, melynek az interpreter
a meghívott ál-metódus nevét adja meg, valamint egy tömbre, mely a híváskor átadott paramétereket tartalmazza.
A következő példa, mely másolás után könnyen futtatható, e függvények működését mutatja be.
A fenti példa a következő kimenetet generálja:
A PHP ezt a megoldást használja a túlterhelés megoldására. PHP-ban a függvények szignatúráját pusztán a neve határozza meg, így
nem alkalmazható a túlterhelés a konvencionális értelmében. Tulajdonképpen, ha ragaszkodunk a túlterhelés programnyelvi fogalmához,
akkor itt nem túlterhelésről van szó, hanem egy olyan technikáról, amelyet úgy neveznek, hogy interpreter-horog (interpreter hook).
Annyi történik, hogy egy vészhelyzeti kezelő függvényt adunk az interpreter számára, olyan esetekre, amikor a szemantikus ellenőrző
elhasal.
Továbbá megjegyzendő, hogy az ilyen mágikus metódusok használatakor, mint például a __get, __set, __call, a fejlesztői környezetünktől
nem várhatunk el olyan kisegítő eszközöket, mint az autocomplete, highlighting vagy dokumentáció, amely meglehetősen megnehezíti a
kód karbantartását!
Ahogy a többi mágikus metódus esetében, a __call és __callStatic argumentumai is átadhatóak referencia szerint.