A PHP programozási nyelv

Objektum-orientált programozás

Osztályok - PHP4

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++).

class Kosar{ var $dolgok; // A kosárban levő dolgok function berak ($sorsz, $db){ $this->dolgok[$sorsz] += $db; } function id (){ echo "kosár vagyok"; } } Kosar::id(); $kosar = new Kosar;

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:

function destruktor($object){ eval ("global \$" . $object . ";"); ${$object}->destruktor(); unset($GLOBALS[$object]); }

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.

class Gazdas_Kosar extends Kosar{ var $tulaj; function tulajdonosa ($nev){ $this->tulaj = $nev;
} function id () { echo "gazdás "; parent::id(); // ugyanaz, mint Kosar::id(); } }

A PHP5 új objektum modellje

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ó.

Az új modell leegyszerűsítve

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:

$object = new MyClass(); $object->method();

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.

A régi és új modellek összehasonlítása

Tekintsük az alábbi kódot:

class MyClass{ function setMember($value){ $this->member = $value; } function getMember(){ return $this->member; } } function foo($obj){ $obj->setMember(’foo’); } $object = new MyClass(); $object->setMember(’bar’); foo($object); print $object->getMember();
A régi modell

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 modell

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.

Megjegyzés

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.

Az új objektum modellel kapcsolatos változások

Függvénybeli objektum-visszaadási mechanizmus

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:

function FactoryMethod($class_type){ switch ($class_type){ case "foo": $obj = new MyFoo(); break; case "bar": $obj = new MyBar(); break; } return $obj; } $object = FactoryMethod("foo");

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.

Dereferencia-képzés

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ó:

$tmp =& $object->method(); $tmp =& $tmp->method(); $tmp->method();
Az új modellnek hála a handle-k segítségével a PHP5 támogatja a dereferencia-képzést és természetesen objektum duplikációk képződése nélkül, így mostmár a következő kód is működik $object->method()->method()->member = 5;

Objektumok összehasonlítása

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.

Konstruktorok

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:

class BaseClass{ function __construct(){ print "In BaseClass constructor\n"; } } class SubClass extends BaseClass{ function __construct(){ parent::__construct(); print "In SubClass constructor\n"; } } $obj = new BaseClass(); $obj = new SubClass();

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:

Fatal error: Call to protected A::__construct() from context '' in /usr/local/www/htdocs/hello.php on line 13

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:

public function __construct(){ $args_num = func_num_args(); switch ($numargs){ case ’0’: /* ... */ break; case ’1’: first_arg = func_get_arg(0); /* ... */break; /* ... */ } }

Destruktorok

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:

class MyDestructableClass{ function __construct(){ print "In constructor\n"; $this->name = "MyDestructableClass"; } function __destruct(){ print "Destroying " . $this->name . "\n"; } } $obj = new MyDestructableClass();

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.

delete metódus

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.

Copy Konstruktorok (Klónozás)

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:

$copy_of_object = $object->__clone();


Példa:
class SubObject{
static $instances = 0;
public $instance;

public function __construct(){
$this->instance = ++self::$instances;
}

public function __clone(){
$this->instance = ++self::$instances;
}
}

class MyCloneable{
public $object1;
public $object2;

function __clone(){
// Force a copy of this->object, otherwise
// it will point to same object.
$this->object1 = clone($this->object1);
}
}

$obj = new MyCloneable();
$obj->object1 = new SubObject();
$obj->object2 = new SubObject();
$obj2 = clone $obj;

print("Original Object:\n");
print_r($obj);
print("Cloned Object:\n");
print_r($obj2);
Példa kimenete:
Eredeti Objektum:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 1
)

[object2] => SubObject Object
(
[instance] => 2
)

)

Másolt Objektum:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 3
)

[object2] => SubObject Object
(
[instance] => 2
)

)
Ha nincs definiált __clone() metódus akkor az alapértelmezett bitről-bitre másolás történik meg.

Private, Protected és Public adattagok

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.

private $hello, $szia, $pisztacia;

A PHP 5.0.4 től érdekes jelenség működik, nevezetesen: a szülők elérik a gyermekeik protected dolgait.
<?php
class A{
public function getProtected(){
return B::$protected;
}
}

class B extends A{
protected static $protected = 'Hi, I am protected property of class B!';
}

$a = new A();
$e = $a->getProtected();
print "$e";
?>

Kimenet:
Hi, I am protected property of class B!


Példa:
class MyClass{
private $Hello = "Hello, World!\n";
protected $Bar = "Hello, Bar!\n";
protected $Foo = "Hello, Foo!\n";

function printHello(){
print "MyClass::printHello() " . $this->Hello;
print "MyClass::printHello() " . $this->Bar;
print "MyClass::printHello() " . $this->Foo;
}
}

class MyClass2 extends MyClass{
protected $Foo;

function printHello(){
MyClass::printHello(); /* Kiírja az adattagok értékét */
print "MyClass2::printHello() " . $this->Hello; /* Nem ír ki semmit */
print "MyClass2::printHello() " . $this->Bar;  /* Kiírja a Bar értékét*/
print "MyClass2::printHello() " . $this->Foo;  /* Kiírja a Foo értékét, de az felül lett definiálva így üres, tehát semmit nem ír ki */
}
}

$obj = new MyClass();
print $obj->Hello; /* Fatal Error */
print $obj->Bar; /* Fatal Error */
print $obj->Foo; /* Fatal Error */
$obj->printHello(); /* Kiír mindent*/

$obj = new MyClass2();
print $obj->Hello;  /* Fatal Error */
print $obj->Bar; /* Fatal Error */
print $obj->Foo; /* Fatal Error */
$obj->printHello();

Private, Protected és Public tagfüggvények

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:

class Foo{
private function aPrivateMethod(){
echo "Foo::aPrivateMethod() called.\n";
}

protected function aProtectedMethod(){
echo "Foo::aProtectedMethod() called.\n";
$this->aPrivateMethod();
}
}

class Bar extends Foo{
public function aPublicMethod(){
echo "Bar::aPublicMethod() called.\n";
$this->aProtectedMethod();
}
}
$o = new Bar;
$o -> aPublicMethod();

A static kulcsszó

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:

class Foo{
public static $my_static = 'foo';

public function staticValue(){
return self::$my_static;
}
}
class Bar extends Foo{

public function fooStatic(){
return parent::$my_static;
}
}

print Foo::$my_static . "\n";

$foo = new Foo();
print $foo->staticValue() . "\n";
print $foo->my_static . "\n"; // my_static nem definiált, hiszen nem adattag

print Bar::$my_static . "\n";
$bar = new Bar();
print $bar->fooStatic() . "\n";

Amit esetleg más nyelvekben megszokhattunk, hogy egy osztályszintű adattagot vagy függvényt elérünk a példányain keresztül is, itt nem működik.

Példa:

<?php
class A{
public static $foo = "bar";
public static $foo2 = "baz";
}

$a = new A();
print A::$foo;
print $a->$foo2;
?>
Kimenet:
bar
Fatal error: Cannot access empty property in /usr/local/www/htdocs/hello.php on line 14

Osztály konstansok

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:

class MyClass{
const constant = 'constant value';

function showConstant(){
echo  self::constant . "\n";
}
}

echo MyClass::constant . "\n";

$class = new MyClass();
$class->showConstant();
/* echo $class::constant;  nem engedélyezett */

Instanceof

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.

class baseClass{ }

$a = new baseClass;
if ($a instanceof baseClass){
echo "Hello I am an Instance";
}
else{
echo "Oh no";
}

Osztály típusú paraméter típusának megadása

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:

function foo(ClassName $object){ //... }
ezentúl ekvivalens az alábbival:
function foo($object){ if (!($object instanceof ClassName)){ die("Argument 1 must be an instance of ClassName"); } }

Absztrakt osztályok

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:

abstract class AbstractClass{

/* Force Extending class to define this method*/
abstract protected function getValue();

/* Common method */
public function printOut(){
print $this->getValue();
}

}

class ConcreteClass1 extends AbstractClass{

protected function getValue(){
return "ConcreteClass1";
}

}

class ConcreteClass2 extends AbstractClass{

protected function getValue(){
return "ConcreteClass2";
}
}

$class1 = new ConcreteClass1;
$class1->printOut();

$class2 = new ConcreteClass2;
$class2->printOut();

Azon régi kódok, amelyekben nincsen a felhasználó által definiált abstract nevű osztály, illetve függvény, módosítás nélkül futtathatók PHP5-ben.

Interface

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:

interface ITemplateA{ public function getHtml($template); } interface ITemplateB{ public function setVariable($name, $var); } class Template implements ITemplateA, ITemplateB{ private $vars = array(); public function setVariable($name, $var){ $this->vars[$name] = $var; } public function getHtml($template){ foreach($this->vars as $name => $value){ $template = str_replace('{'.$name.'}', $value, $template); } return $template; } }

Az öröklődés és az interfész implementáció nem zárják ki egymást. Azon régi kódok, amelyekben nincsen a felhasználó által definiált implements és interface nevű osztály, illetve függvény, módosítás nélkül futtathatók PHP5-ben.

Beépített interface-ek

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.

ArrayAccess

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.

class DbaReaderimplements ArrayAccess { protected $db = NULL; function __construct($file, $handler) { if (!($this->db = dba_open($file, 'cd', $handler))) { throw new exception('Could not open file '. $file); } } function __destruct() { dba_close($this->db); } function offsetExists($offset) { return dba_exists($offset, $this->db); } function offsetGet($offset) { return dba_fetch($offset, $this->db); } function offsetSet($offset, $value) { return dba_replace($offset, $value, $this->db); } function offsetUnset($offset) { return dba_delete($offset, $this->db); } }

Final kulcsszó

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:

class BaseClass{
public function test(){
echo "BaseClass::test() called\n";
}

final public function moreTesting(){
echo "BaseClass::moreTesting() called\n"
; }
}

class ChildClass extends BaseClass{
public function moreTesting(){
echo "ChildClass::moreTesting() called\n";
}
}
// Fatal error: Nem definiálhatjuk felül a final method BaseClass::moreTesting()-et

Ezenkívül osztályokra is alkalmazható ez a kulcsszó, de itt a további öröklődés akadályozható meg, mégpedig a final kulcsszóval ellátott osztálynak nem lehetnek gyerekosztályai, azaz nem specializálhatók. Ekkor a metódusokat nem kell finalként definiálni.

final class Foo{
// class definition
}
class Bork extends Foo{}
// Fatal error: Nem örököltethetünk osztályt a final class Fooból!

Objektum Iteráció

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.

Egyszerű Iteráció

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:

class MyClass{
public $var1 = 'value 1';
public $var2 = 'value 2';
public $var3 = 'value 3';

protected $protected = 'protected';
private $private = 'private';

}

$class = new MyClass();

foreach($class as $key => $value){
print "$key => $value\n";
}
Ebben a példában csak a public adattagokat éri el a foreach, hiszen a fenti esetben, számára csak ezek láthatók.

2. Példa:
class testIterator{
private $private1;
private $private2;
public $public1;
public $public2;
protected $protected1;
protected $protected2;

public function __construct(){
$this->private1="private 1";
$this->private2="private 2";
$this->public1="public 1";
$this->public2="public 2";
$this->protected1="protected 1";
$this->protected2="protected 2";
foreach ($this as $key=>$value){
print "$key : $value< br />";
}
}

}

$test=new testIterator;
Ebben a példában minden adattagot elér a foreach, mert objektumon belül van.

Iteratorral való iteráció

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:

class ObjectIterator implements Iterator{ private $obj; private $num; function __construct($obj){ $this->obj = $obj; } function rewind(){ $this->num = 0; } function hasMore(){ return $this->num < $this->obj->max; } function key(){ return $this->num; } function current(){ switch($this->num){ case 0: return "1st"; case 1: return "2nd"; case 2: return "3rd"; default: return $this->num."th"; } } function next(){ $this->num++; } } class Object implements IteratorAggregate{ public $max = 3; function getIterator(){ return new ObjectIterator($this); } } $obj = new Object; // this foreach ... foreach($obj as $key => $val){ echo "$key = $val\n"; } // a fentiek felhasználásával $it = $obj->getIterator(); for($it->rewind(); $it->hasMore(); $it->next){ $key = $it->current(); $val = $it->key(); echo "$key = $val\n"; } unset($it);

Beépített iterátor osztályok

További iterátor osztályok itt: http://www.php.net/manual/en/spl.iterators.php

Dinamikus metódusok és attribútumok

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.

Dinamikus attribútumok

void __set ( string name, mixed value);
void __get ( mixed name);

Az osztály definiálása során nem definiált adattagokhoz, a fenti két speciális metódus segítségével rendelhetünk testreszabott kódot. A $name paraméter az elérni kívánt adattag neve. A __set() metódus $value paramétere reprezentálja azt az értéket, amit a $name-nek kell felvennie.

Példa:
class Setter{
public $n;
private $x = array("a" => 1, "b" => 2, "c" => 3);


function __get($nm){
print "Getting [$nm]\n";
if (isset($this->x[$nm])){
$r = $this->x[$nm];
print "Returning: $r\n";
eturn $r;
} else{
print "Nothing!\n";
}
}

function __set($nm, $val){
print "Setting [$nm] to $val\n";
if (isset($this->x[$nm])){
$this->x[$nm] = $val;
print "OK!\n";
} else{
print "Not OK!\n";
}
}
}

$foo = new Setter();
$foo->n = 1;
$foo->a = 100;
$foo->a++;
$foo->z++;
var_dump($foo);

Az eredmény:
Setting [a] to 100
OK!
Getting [a]
Returning: 100
Setting [a] to 101
OK!
Getting [z]
Nothing!
Setting [z] to 1
Not OK!
object(Setter)#1 (2){
["n"]=>
int(1)
["x:private"]=>
array(3) {
["a"]=>
int(101)
["b"]=>
int(2)
["c"]=>
int(3)
}
}

Dinamikus metódusok

mixed __call ( string name, array arguments)
Az osztály metódusok az adattagokhoz hasonlóan dinamikussá tehetők, csak itt a __call() metódus felelős ezért. A $name paraméter az elérni kívánt metódus nevét tartalmazza. A $arguments tömb pedig az elérni kívánt metódus argumentumainak értékét tartalmazza a megfelelő sorrendben. A __call() visszatérő értéke lesz a meghívott metódus visszatérő értéke.

Példa:
class Caller{
private $x = array(1, 2, 3);

function __call($m, $a){
print "Method $m called:\n";
var_dump($a);
return $this->x;
}
}

$foo = new Caller();
$a = $foo->test(1, "2", 3.4, true);
var_dump($a);

Szerializálás

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á.

$alma = new Alma(); $befott = serialize( $alma ); ... $fonyadt = unserialize($befott);

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.

Hatókör feloldó operátor (::)

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):

class MyClass{
const CONST_VALUE = 'A constant value';
}
echo MyClass::CONST_VALUE;
Két speciális kulcsszó a self és a parent segítségével érhetjük el osztályon belülről ezeket a speciális adattagokat és metódusokat.

Példa (elérés az osztály definíción belülről):
class OtherClass extends MyClass{
public static $my_static = 'static var';

public static function doubleColon(){
echo parent::CONST_VALUE . "\n";
echo self::$my_static . "\n";
}
}

OtherClass::doubleColon();

Reflection



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 ] {
    }
  }
}

Reflector

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

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ő.

ReflectionFunction

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

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

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:

Ezeken kívül következő műveleteket el tudjuk végezni egy ReflectionClass objektummal:

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.

ReflectionObject

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

ReflectionMethod

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

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

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ályok bővítése

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!

Polimorfizmus PHP-ben

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.

Egy kisérlet szimulációjára

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:

Számunkra ez csupán annyit jelent, hogy amennyiben olyan metódus kerül meghívásra, akkor mi értesülhetünk róla a __call metódust felülírva és valami mást futtathatunk le a nem létező metódus helyett. Másrészt elérhetjük az átadott paramétereket is, ami azt jelenti lehetőségünk van típusuk meghatározására például isString használata segítségével. Így megtehetjük azt, hogy paraméterek típusaitól függően más-más funkcionalítást hajtjuk végre. Pl.:

   
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!

Traitek

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.

trait ezcReflectionReturnInfo { function getReturnType() { /*1*/ } function getReturnDescription() { /*2*/ } } class ezcReflectionMethod extends ReflectionMethod { use ezcReflectionReturnInfo; /* ... */ } class ezcReflectionFunction extends ReflectionFunction { use ezcReflectionReturnInfo; /* ... */ }

Precedencia

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.

class Base { public function sayHello() { echo 'Hello '; } } trait SayWorld { public function sayHello() { parent::sayHello(); echo 'World!'; } } class MyHelloWorld extends Base { use SayWorld; } $o = new MyHelloWorld(); $o->sayHello();

Kimenet:

Hello World!

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.

trait HelloWorld { public function sayHello() { echo 'Hello World!'; } } class TheWorldIsNotEnough { use HelloWorld; public function sayHello() { echo 'Hello Universe!'; } } $o = new TheWorldIsNotEnough(); $o->sayHello();

Kimenet:

Hello Universe!

Többszörös traitek

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.

trait Hello { public function sayHello() { echo 'Hello '; } } trait World { public function sayWorld() { echo 'World'; } } class MyHelloWorld { use Hello, World; public function sayExclamationMark() { echo '!'; } } $o = new MyHelloWorld(); $o->sayHello(); $o->sayWorld(); $o->sayExclamationMark();

Kimenet:

Hello World!

Konfliktus feloldás

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.

trait A { public function smallTalk() { echo 'a'; } public function bigTalk() { echo 'A'; } } trait B { public function smallTalk() { echo 'b'; } public function bigTalk() { echo 'B'; } } class Talker { use A, B { B::smallTalk insteadof A; A::bigTalk insteadof B; } } class Aliased_Talker { use A, B { B::smallTalk insteadof A; A::bigTalk insteadof B; B::bigTalk as talk; } }

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.

Láthatóság változtatás

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.

trait HelloWorld { public function sayHello() { echo 'Hello World!'; } } // Change visibility of sayHello class MyClass1 { use HelloWorld { sayHello as protected; } } // Alias method with changed visibility // sayHello visibility not changed class MyClass2 { use HelloWorld { sayHello as private myPrivateHello; } }

Traitekből álló traitek

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.

trait Hello { public function sayHello() { echo 'Hello '; } } trait World { public function sayWorld() { echo 'World!'; } } trait HelloWorld { use Hello, World; } class MyHelloWorld { use HelloWorld; } $o = new MyHelloWorld(); $o->sayHello(); $o->sayWorld();

Kimenet:

Hello World!

Absztrakt trait tagok

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.

trait Hello { public function sayHelloWorld() { echo 'Hello'.$this->getWorld(); } abstract public function getWorld(); } class MyHelloWorld { private $world; use Hello; public function getWorld() { return $this->world; } public function setWorld($val) { $this->world = $val; } }

Statikus trait tagok

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.

trait StaticExample { public static function doSomething() { return 'Doing something'; } } class Example { use StaticExample; } Example::doSomething();

Tulajdonságok

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.

trait PropertiesTrait { public $same = true; public $different = false; } class PropertiesExample { use PropertiesTrait; public $same = true; // Strict Standards public $different = true; // Fatal error }

Autoloading

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:

function __autoload($class_name) { include $class_name . '.php'; } $obj = new MyClass1(); $obj2 = new MyClass2();

Magic metódusok

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.

__sleep(), __wakeup()

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.

class Connection { protected $link; private $server, $username, $password, $db; public function __construct($server, $username, $password, $db) { $this->server = $server; $this->username = $username; $this->password = $password; $this->db = $db; $this->connect(); } private function connect() { $this->link = mysql_connect($this->server, $this->username, $this->password); mysql_select_db($this->db, $this->link); } public function __sleep() { return array('server', 'username', 'password', 'db'); } public function __wakeup() { $this->connect(); } }

__toString()

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).

class TestClass { public $foo; public function __construct($foo) { $this->foo = $foo; } public function __toString() { return $this->foo; } } $class = new TestClass('Hello'); echo $class;

__invoke()

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.

class CallableClass { public function __invoke($x) { var_dump($x); } } $obj = new CallableClass; $obj(5); var_dump(is_callable($obj));

Kimenet:

int(5) bool(true)

__set_state()

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.

class A { public $var1; public $var2; public static function __set_state($an_array) // As of PHP 5.1.0 { $obj = new A; $obj->var1 = $an_array['var1']; $obj->var2 = $an_array['var2']; return $obj; } } $a = new A; $a->var1 = 5; $a->var2 = 'foo'; eval('$b = ' . var_export($a, true) . ';'); // $b = A::__set_state(array( // 'var1' => 5, // 'var2' => 'foo', // )); var_dump($b);

Kimenet:

object(A)#2 (2) { ["var1"]=> int(5) ["var2"]=> string(3) "foo" }

__call() és __callStatic

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.

public static mixed __callStatic ( string $name , array $arguments )
public static mixed __call ( string $name , array $arguments )

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.

class MethodTest { public function __call($name, $arguments) { // Megjegyzés: a $name, vagyis a metódus neve nagybetű érzékeny! echo "A '$name' nevű függvény hívása" . implode(', ', $arguments). "\n"; } /** PHP 5.3.0-tól */ public static function __callStatic($name, $arguments) { echo "A '$name'" . implode(', ', $arguments). "\n"; } } $obj = new MethodTest; $obj->runTest('objektum kontextusából'); MethodTest::runTest('statikus metódus hívása'); // PHP 5.3.0-tól

A fenti példa a következő kimenetet generálja:

A 'runTest' nevű függvény hívása objektum kontextusából A 'runTest' statikus metódus hívása
Megjegyzések

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.