PHP jelentősebb változásai napjainkig - Kezdetektől a trait-ekig

Emlékszem, mikor még a PHP 4 volt számomra az egyetlen verzió. Majd igen késve belekóstoltam a PHP 5-be és tetszett, amit láttam. Azóta is folyamatosan fejlődött. Kapott új függvényeket is, de mégsem volt olyan látványos a változás. Hozzáteszem, a unicode változónevek használata sem jelentéktelen, ami már 5.3 előtt is létezett, de a valódi haszna mégis megkérdőjelezhető. Majd az 5.3 -as verzióban többek között bevezették a névtereket. Persze nem nagyon volt még akkor olyan szerver, ahol ez a verzió futott volna. És mivel névterek nélkül is jól megvoltam addig, ezt a funkciót inkább nem is használtam. De az idő nem állt meg és elértünk az 5.2-es széria támogatottságának végéig. Amikor már mindenkit az 5.3-ra való átállásra ösztönöznek a PHP nyelv fejlesztői. Egyelőre még mindig több szerveren csak 5.2 van. Többek között a rimelek.hu szolgáltatóján is. Tervezik viszont ők is, hogy bevezetnek 5.3-as szervert is, amire kérésre át lehet majd kerülni. Van ingyenes szolgáltató, ami már korábban váltott. Ilyen a Freeweb.hu is. Lehet több is van. Nem néztem utána. Már 5.3.8 -nál tart ez a széria. És bár korábban sokan hallhattak a 6-os verzióról, a következő az 5.4 lesz.

A PHP 5.4 ismét olyan újításokat vezet be, ami miatt megint lehet egy kicsit dicsérni a nyelvet. De hogyan jutottunk ide?

PHP 1

Az első PHP verzió. Rasmus Lerdorf-tól. Itt még nem az volt a cél, hogy egy széles körben használt szkriptnyelv legyen. Akik vettek könyvet PHP tanuláshoz, azok olvashattak róla, hogy Rasmus ezt egy könnyítésnek szánta csak személyes weboldalakhoz. Volt alkalmam belenézni a forráskódjába. A maiakhoz képest érthető módon sokkal olvashatóbb forrása van. Mindössze 6 fejlécfájlból, 8 C forrásfájlból, 2 szövegfájlból és egy Makefile-ból állt. Olyan függvényei voltak a C forrásnak, mint például:

  1. /*
  2.  * ShowEmailPasswordForm
  3.  *
  4.  * Prompt user for both an email address and a password
  5.  */
  6. void ShowEmailPasswordForm(char *cmd, char *filename) {
  7.         puts("<html><head><title>Email Address and Password Required</title></head>");
  8.         puts("<body><center><h1>Email and Password Required</h1></center>");
  9.         puts("This document is protected by a password.  You must know this");
  10.         puts("password in order to continue.  You are also asked to enter your");
  11.         puts("email address.  If either of these fields are not filled out, you");
  12.         puts("will not be granted access.<p>");
  13.         printf("<form action=\"%s?%s\" method=\"POST\">\n",cmd,filename);
  14.         printf("<input type=\"hidden\" name=\"http_referer\" value=\"%s\">\n",referer);
  15.         printf("<center><tt>Email Address</tt><br><input type=\"text\" name=\"email\" size=40 value=\"%s\"></center><p>\n",email_addr);
  16.         puts("<center><tt>Password</tt><br><input type=\"password\" name=\"password\"></center><p>");
  17.         puts("<center><input type=\"submit\" value=\" SEND \"></center><hr>");
  18.         printf("<font size=-1><i><a href=\"%s\">PHP Tools</a> Copyright &copy 1995 Rasmus Lerdorf</i></font><br>\n",PHPURL);
  19.         puts("</body></html>");
  20. }

Ezt nagyon magyarázni sem kell. Ez volt a start. És nem állt meg.

PHP 2

A forrásfájlok száma és így a funkciók is bővültek. Már lehet látni reguláris kifejezéseket, képkezelést, fájlkezelést. Van példa benne email küldésre is, de még nem a mail függvénnyel, hanem a rendszer sendmail programját használva. Volt benne adatbázis kezelés. Főként mSQL-t látni benne. Amit egy idő után felváltott a mySQL. A letölthető csomagban egyébként található a példafájlokon kívül egy dokumentáció is. Itt még bőven elég volt egyetlen html fájlban összegyűjteni a lényeget.

PHP 3

Külön windows specifikus megoldásokkal gyarapodott. Dinamikus könyvtárbetöltési lehetőséggel, amire a dl() függvény szolgált. Itt már látni osztályokat is:

  1. class foo {
  2.   var $a;
  3.   var $b;
  4.   cfunction display() {
  5.         echo "This is class foo\n";
  6.     echo "a = ".$this->a."\n";
  7.     echo "b = ".$this->b."\n";
  8.   }
  9.   cfunction mul() {
  10.     return $this->a*$this->b;
  11.   }
  12. };
  13.  
  14. class bar extends foo {
  15.   var $c;
  16.   cfunction display() {  /* alternative display function for class bar */
  17.     echo "This is class bar\n";
  18.     echo "a = ".$this->a."\n";
  19.     echo "b = ".$this->b."\n";
  20.     echo "c = ".$this->c."\n";
  21.   }
  22. };

A cfunction kulcsszó php4-ig létezett csak.

Aki szeretné megnézni az első 3 verziót, letöltheti a forráskódokat a museum.php.net oldalról.

PHP 4

Az első 3 verzió olyan régi, hogy az már történelem. És nem is volt alkalmam programozni bennük. A 4-es viszont még sokak emlékeiben élhet. Az volt az első verzió, amivel dolgoztam. Az első könyv, amiből tanultam, még a 4.2 telepítését írta le. Akkoriban több könyvben is hibás példákat lehetett olvasni, mert adott szerverbeállításoknál azok a hibaüzenetek nem jelentek meg. Gondolok itt a tömbindexek idézőjelezésének elhagyására és űrlap használatakor annak ellenőrzésére, hogy az űrlap el lett-e küldve egyáltalán. Így aztán hosszú ideig lehetett olyan bejegyzésekkel találkozni fórumon, hogy „De hát a könyvben így írták” Persze az, hogy ez már ritkább, nem a PHP fejlődésének köszönhető, hanem inkább a könyvek szerzőinek.

Osztályok, objektumok

Az osztályok még mindig igen kezdetlegesek voltak. Akkoriban mégis nagy szó volt számomra, hogy már ismerem ezt a „modern” technológiát. Közel sem tudtam még azonban, hogy mi is a valódi jelentősége az objektumorientált programozásnak.

Osztály php 4-ben

  1. class A
  2. {
  3.         var $prop;
  4.        
  5.         function A($prop)
  6.         { //konstruktor
  7.                 $this->prop = $prop;
  8.         }
  9.        
  10.         function method()
  11.         {
  12.                 return $this->prop;
  13.         }
  14. }
  15.  
  16. $a = new A(54);
  17. echo $a->prop;

Nem volt viszont lehetőség adattagok elrejtésére. A konstruktor az osztálynévvel megegyező nevű metódus volt. Ráadásul akkor még az objektumok nem referencia szerint adódtak át. Vagyis ha nem tettünk & jelet az objektum elé, mikor átadtuk egy másik változónak, vagy egy metódusnak, függvénynek, akkor az objektumról másolat készült és azzal dolgozhattunk tovább.

PHP 5.0 - 5.2

Ennek a verziónak legnagyobb vívmánya az OO lehetőségek továbbfejlesztése volt. Az objektumorientált szemlélet főbb alapelveit már megvalósította, így egyre könnyebb lett bizonyos problémák megoldása és elmondhattuk, hogy PHP-ben igenis lehet objektumorientáltan is programozni. Ez kezdetben még a fizetős tárhelytulajdonosok kiváltsága volt. Majd megjelentek ingyenes tárhelyek is az 5-ös verzióval. Érdekesség, hogy még mindig vannak olyan tárhelyek, mint például az uw.hu, ahol még a muzeális PHP 4.4.7 -es verzió van. De mik is lettek az újdonságok OO területen?

Láthatóság

A 4-es verziónál írt példámnál a tulajdonság előtt a var kulcsszó áll. A metódusok előtt pedig semmi. Bármelyik tulajdonság és metódus bárhonnan elérhető, módosítható volt. Erre a php 5 bevezetett 3 kulcsszót. public, protected, private.

A public gyakorlatilag a var álneve. De mégis többet mond a tulajdonság jellegéről.

A protected-del elérhető lett, hogy egy metódus, vagy tulajdonság ne legyen elérhető máshonnan, csak az őt tartalmazó osztály metódusaiból, vagy a belőle leszármaztatott osztályokban. Lényeges, hogy itt osztályokról beszélünk és nem objektumokról. Ugyanis egy adott osztály szabadon elérheti egy másik példány bármely védett metódusát is, ha az saját magában, vagy egy szülőjében van definiálva.

  1. class A
  2. {
  3.         protected function prot() {
  4.                 return 'Védett';
  5.         }
  6. }
  7.  
  8. class B extends A
  9. {
  10.  
  11. }
  12.  
  13. class C extends A
  14. {
  15.         public function test()
  16.         {
  17.                 $b = new B();
  18.                 return $b->prot();
  19.                
  20.         }
  21. }
  22.  
  23. $c = new C();
  24. echo $c->test(); //Védett

A private-tel jelölt metódusokat pedig csak az őt tartalmazó osztály érheti el. Szintén érvényes a fenti kijelentésem, mi szerint osztályokról beszélhetünk továbbra is, nem objektumokról.

Type hinting

Erősen típusos nyelvekben a változók, tulajdonságok, metódusok előtt meg kell adni a típust is. Legyen az primitív, vagy osztály. PHP 5-ben csak lehetőség és csak függvényekben, metódusokban ( vagy catch blokkban. Erről később. ) a paraméterek előtt. Valamint csak array és osztálynév, interfésznév állhat típusként.

  1. class Person
  2. {
  3.         public $name = 'Név';
  4. }
  5.  
  6. function showName(Person $person)
  7. {
  8.         echo '<b>'.$person->name.'</b>';
  9. }
  10.  
  11. showName(new Person()); //<b>Név</b>

Absztrakt osztály és metódus

A abstract kulcsszó lehetővé tette, hogy olyan osztályokat definiáljunk, amikből példány nem hozható létre. Kizárólag az ősosztály szerepét töltik be az osztályhierarchiában. Illetve olyan metódusokat, amiknek nem adjuk meg a törzsét, csak a fejrészét. Ezeket a metódusokat aztán kötelező kifejteni a gyermekosztályban. Következik ebből, hogy absztrakt, vagy absztrakt metódust tartalmazó osztály nem példányosítható. Annak statikus, kifejtett metódusai viszont használhatók.

  1. abstract class D
  2. {
  3.         abstract function test();
  4.         public static function x()
  5.         {
  6.                 echo 'ize';
  7.         }
  8. }
  9. D::x(); //ize

Interfészek

Az interfészekkel egy olyan felületet lehet meghatározni, amivel az adott osztály kommunikálhat a külvilággal. Tehát egy olyan metóduslista gyakorlatilag, aminek minden metódusa publikus. És az őt megvalósító osztály is köteles ezeket publikusnak definiálni. Bizonyos szempontból tehát hasonló az absztrakt osztályhoz, de itt csak metódusok fejrésze adható meg és azok is csak publikusan. Mindkettő a típus szerepét is betölti, így a type hintinggel együtt remekül használható. Az adott objektummal dolgozó metódus pedig biztos lehet benne, hogy mindig rendelkezésére állnak majd a kellő metódusok.

  1. <?php
  2. interface IProgrammer
  3. {
  4.         public function createProgram();
  5. }
  6.  
  7. class PHPProgrammer implements IProgrammer
  8. {
  9.         public function createProgram()
  10.         {
  11.                 return '<?php echo "Program"; ?>';
  12.         }
  13. }
  14.  
  15. class JavaProgrammer implements IProgrammer
  16. {
  17.         public function createProgram()
  18.         {
  19.                 return '
  20.                         class Test {
  21.                                 public static void main(String[] args) {
  22.                                         System.out.println("Hello Batman!");
  23.                                 }
  24.                         }';
  25.         }      
  26. }
  27.  
  28. class Employer
  29. {
  30.         public function testAProgrammer(IProgrammer $programmer)
  31.         {
  32.                 return ($this->programIsWorking($programmer->createProgram()));
  33.         }
  34.        
  35.         private function programIsWorking($sourcecode)
  36.         {
  37.                 //Minden programot elfogadó munkáltató
  38.                 return true;
  39.         }
  40. }
  41.  
  42. $phpProgrammer = new PHPProgrammer();
  43. $javaProgrammer = new JavaProgrammer();
  44.  
  45. $employer = new Employer();
  46. if($employer->testAProgrammer($phpProgrammer)) {
  47.         echo 'Felvesszük!';
  48. } else {
  49.         echo 'Nem vesszük fel';
  50. }
  51. if($employer->testAProgrammer($javaProgrammer)) {
  52.         echo 'Felvesszük!';
  53. } else {
  54.         echo 'Nem vesszük fel!';
  55. }

Ezek a programozók természetesen nem csak programozni tudnak, de ezt a képességüket kell tudnia leellenőrizni a munkáltatónak, így ennek mindenképpen létezni kell. Ha elhagyom a createProgram metódust bármelyik programozónál, akkor értelmezés közben hibaüzenettel leáll a program.

Fatal error: Class JavaProgrammer contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (IProgrammer::createProgram) in /home/***/index.php on line X

Ha pedig nem is implementálom a programozó osztályában az interfészt, akkor a munkáltató nem fogadja el programozónak és meg sem próbálja letesztelni.

Catchable fatal error: Argument 1 passed to Employer::testAProgrammer() must implement interface IProgrammer, instance of JavaProgrammer given, called in /home/***/index.php on line X and defined in /home/***/index.php on line X

Felülírás megtiltása

A final kulcsszó is igen hasznos lehet, ha azt akarjuk, hogy egy ezzel jelzett metódust további leszármazott osztályokban ne lehessen felülírni. Biztosítva ezzel, hogy annak funkcióját ne lehessen megváltoztatni. Osztály elé írva pedig az adott osztályból még örököltetni sem lehet további osztályokat.

  1. class A
  2. {
  3.         final public function fin()
  4.         {
  5.                 return 'Na, te engem csak ne írj felül!';
  6.         }
  7.        
  8.         public function test()
  9.         {
  10.                 return $this->fin();
  11.         }
  12. }
  13.  
  14. class B extends A
  15. {
  16.         public function fin() //Fatal error: Cannot override final method A::fin() in /home/***/index.php on line X
  17.         {
  18.                 return 'Jól kitolok az A osztállyal. Hiába mondta, hogy "'.parent::fin().'"';
  19.         }
  20. }
  21.  
  22. $b = new B();
  23. echo $b->test();
  1. final class C { }
  2. class D extends C { } //Fatal error: Class D may not inherit from final class (C) in /home/***/index.php on line X

Mágikus metódusok

Egy sor olyan metódus létezik, ami valamilyen eseménynek hatására fut le. Nem közvetlen hívással. Ezek a __construct(), __destruct(), __call(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __set_state(), __clone() metódusok. Mindet nem is magyaráznám meg részletesen. A konstruktorról már írtam. Itt külön neve van, de még a régi módszer is használható. A __destruct() pedig akkor fut le, amikor az objektum megszűnik. A __get(), __set(), __call() metódusok segítségével dinamikusan, futásidőben tehetünk elérhetővé olyan tulajdonságokat, metódusokat, amelyek nem is léteznek. Ami remek lehetőség olyan modell osztályok írására, amik egy adatbázis tábla mezői alapján kapnak tulajdonságokat. Az __isset(), __unset() metódusok a hasonló nevű nyelvi konstrukciókkal (isset, unset) függnek össze. Mivel a nem létező tulajdonságokra másképp az isset hamisat adna, az unset pedig nem is törölne. A __sleep() és __wakeup() megint érdekesek, bár élesben még soha nem kellett használnom. Előbbit a serialize() függvény idézi elő, utóbbit pedig az unserialize(). Normál esetben, ha egy objektumot sorosítunk, minden állapota megőrződik változatlanul. Ha viszont tartalmazza az osztály a __sleep() metódust, akkor ez a metódus döntheti el, mely állapotokat kell megjegyezni „elalvás előtt”. Azon tulajdonságainak nevét egy tömbben kell visszaadnia, amit nem akar elfelejteni. Minden más visszaáll alapra. Majd ébredéskor el lehet dönteni, mi legyen ezekkel az állapotokkal. Mutatok egy életszerű helyzetre egy forráskódot.

  1. class Person
  2. {
  3.         private $name;
  4.         private $hungry = false;
  5.         private $sleepy = false;
  6.        
  7.         public function __construct($name)
  8.         {
  9.                 $this->name = $name;
  10.         }
  11.        
  12.         public function eat()
  13.         {
  14.                 echo 'Eszem<br />';
  15.                 $this->hungry = false;
  16.         }
  17.        
  18.         public function work()
  19.         {
  20.                 echo 'Dolgozom<br />';
  21.                 $this->hungry = true;
  22.                 $this->sleepy = true;
  23.         }
  24.        
  25.         public function howAreYou()
  26.         {
  27.                 echo '<span style="color: red;">Én, '.$this->name.',<br />',
  28.                         ($this->hungry) ? 'éhes vagyok' : 'nem vagyok éhes','<br />',
  29.                         ($this->sleepy) ? 'álmos vagyok' : 'nem vagyok álmos', '<br /></span>';      
  30.         }
  31.  
  32.         public function __sleep()
  33.         {
  34.                 echo 'Alszom<br />';
  35.                 return array('name');
  36.         }
  37.        
  38.         public function __wakeup()
  39.         {
  40.                 echo 'Felkelek és nyújtózom<br />';
  41.                 $this->hungry = true;
  42.         }      
  43. }
  44.  
  45. $person = new Person('Ákos');
  46. $person->howAreYou();
  47.  
  48. $person->work();
  49. $person->howAreYou();
  50.  
  51. $person->eat();
  52. $person->howAreYou();
  53.  
  54. $person = serialize($person);
  55. $person = unserialize($person);
  56.  
  57. $person->howAreYou();

Itt a személy alapból nem is éhes és nem is álmos. Teszi a napi, szokott dolgait, majd valamilyen állapotba kerül. Elálmosodik és lefekszik aludni. Mint ahogy az lenni szokott, az ember alvás után is megőrzi a nevét. A többi változhat. Ezért nem is kell megjegyezni. Ébredéskor általában megéhezik.
Az eredmény:

Én, Ákos,
nem vagyok éhes
nem vagyok álmos
Dolgozom
Én, Ákos,
éhes vagyok
álmos vagyok
Eszem
Én, Ákos,
nem vagyok éhes
álmos vagyok
Alszom
Felkelek és nyújtózom
Én, Ákos,
éhes vagyok
nem vagyok álmos

A __toString() metódus pedig 5.2 előtt akkor futott le, amikor echo-val vagy print-tel a képernyőre lett küldve az objektum. És így annak általunk meghatározott string reprezentációját lehetett kiírni. 5.2 óta pedig bármilyen implicit vagy explicit string konverzió hatására lefut. Kibővítve tehát a Person osztályt:

  1. public function __toString()
  2. {
  3.         return $this->name;
  4. }

Már a személy nevének kiírása ennyiből áll: print $person;

Klónozás

Az előző pontból kimaradt a __clone() metódus magyarázata. A PHP 4-es verziójában, mint említettem, minden objektum érték szerint, azaz másolatként lett átadva újabb változóknak. Ez viszont az 5-ös verzióban végre megváltozott. És az alapértelmezett a referencia szerinti átadás lett. Így viszont ha épp az objektum másolatára lenne szükség, már nem elég a $person2 = $person;, hanem kell a clone kulcsszó is. $person2 = clone $person; Így a két változó már nem ugyanarra az objektumra mutat. De mi van akkor, ha nem teljesen tökéletes másolatot szeretnénk, hanem csak egy nagyban egyezőt? Ilyenkor kell a __clone() metódus, ahol még változtatni lehet a tulajdonságokon.

  1. //...
  2. public function __clone()
  3. {
  4.         $this->name .= ' klónja';
  5. }
  6. //...
  1. $person2 = clone $person;
  2. $person2->work();
  3. $person2->howAreYou();
  4. //Dolgozom
  5. //Én, Ákos klónja,
  6. //éhes vagyok
  7. //álmos vagyok

Egyelőre túl gyakran klónoznom sem kellett, de ettől függetlenül a lehetőséget nagyon jónak tartom. És úgy egyáltalán a PHP 5 összes újdonságát, ami az OOP-t illeti. Úgy, ahogy a következő pontban leírtakat is.

Kivételek

Már mire bevezették a kivételeket, programoztam C# -ban. Onnan ismertem meg a kivételkezelés alapjait. És ezért ami furcsa volt elsőre PHP-ban, hogy nincsenek olyan kivételek, amiket a rendszer maga dobna. Helyette mindig a különböző szintű hibaüzenetek jelentek meg. Például egy nullával való osztásnál C# -ban kivétel keletkezik, amit kezelni lehet. Míg PHP-ben magamnak kellene minden osztás művelet előtt vizsgálnom az osztó értékét. És szükség esetén kivételt dobni. Valahogy így:

  1. class DivideByZeroException extends Exception { }
  2.  
  3. function div($a, $b)
  4. {
  5.         if ($b == 0) {
  6.                 throw new DivideByZeroException('Nullával nem lehet osztani!');
  7.         }
  8.         return $a / $b;
  9. }
  10.  
  11. try {
  12.         echo div(10, 0);
  13. } catch(DivideByZeroException $e ) {
  14.         echo $e->getTraceAsString();
  15. }

Erről hosszan lehetne írni. Vannak, akik megkérdőjelezik a hasznukat. Ők főleg a kevésbé tapasztaltabbak. Az a helyzet, hogy egy nullával való osztásnál kevésbé látszik a jelentősége. De egy összetettebb alkalmazásnál igen nagy előny, hogy egy hibát ( kivételt ) a program több szintjén el lehet kapni és kezelni. Valamint a hiba típusától függően, többszintű kivételhierarchia esetén általánosabb és speciálisabb „választ” is lehet adni rá. Bizonyos nyelvekben létezik a try és catch blokkon kívül egy finally is. Ezért vannak, akik azt hiszik, hogy PHP-ban is így van. De nincs. PHP-ban nincs finally blokk.

Automatikus osztálybetöltés

PHP 4-ben minden fájlt be kellett hívni a programozónak a forrásban, mielőtt a benne levő osztályt használta volna. Majd, ha meggondolta magát és más osztályra lett volna szüksége, akkor a fájl include-okat is át kellett írni. Ez ugye valljuk be, igen kényelmetlen. És ráadásul attól, hogy egy programban szükség lehet egy osztályra, még nem biztos, hogy szükség is lesz. Az a lehetőség persze még adott volt, hogy az osztályok betöltésére saját betöltő megoldást írunk és a hozzá tartozó fájlokat is az a metódus vagy függvény illeszti be, ha szükséges. De erre jött a megváltás az __autoload() függvény formájában. Ami megfelelő, következetes fájlnevek és könyvtárszerkezet mellett automatikusan betöltheti azokat a fájlokat, amikhez tartozó osztályokat valahol megpróbáljuk használni. Ez azért is jó, mert rákényszeríti a programozót arra, hogy olyan osztály- és fájlneveket találjon ki, amivel ez megvalósítható. Ezzel a program struktúrája is átláthatóbb lesz.

  1. function __autoload($class)
  2. {
  3.         require_once dirname(__FILE__).'/'.str_replace('_','/',$class).'.php';
  4. }
  5.  
  6. $object = new Path_Classname();
  7. //Path/Classname.php

Ez persze csak a legegyszerűbb és nem a legjobb megoldás. Így az osztályok linux szervereken érzékenyek lesznek a kis és nagybetűk különbségére az osztálynévben. De ezen egy strtolower függvény segít. Ha minden fájl és mappa megállapodás szerint kisbetűs. Viszont kétséget kizáróan hasznos.

SPL

A PHP híres arról, hogy rengeteg beépített függvénye van. Habár kap olyan kritikákat, hogy azok nem következetes elnevezésűek és paraméterezésűek. Ebben van is igazság. De túl lehet lépni rajta. Az 5-ös verzióban pedig bevezették az SPL-t. Azaz a Standard PHP Library-t. Olyan osztályok, interfészek gyűjteménye, amivel speciális adatstruktúrákat vagy bejárási módszereket valósíthatunk meg. És nem utolsósorban előre definiált kivételosztályokat tartalmaz. PHP 5.2 -ben még kikapcsolható volt a használata.

SPL-hez kapcsolódóan van még egy említésre méltó megoldás. Ez pedig az SPL Types. Type Hinting-nél említettem, hogy a PHP gyengén típusos nyelv. És a type hinting segítségével sem lehet az egyszerű típusokra korlátozni egy változó értékét. Ezzel a kiegészítéssel viszont már ezt is lehet. Egy rövid példa:

  1. $int = new SPLint(30);
  2. $int = 54;
  3. $int = 'szöveg vagyok';
  4. echo $int;

Ez például a harmadik sorra kivételt dobna. Mert nem csak azt kényszeríti ki, hogy egy metódusban ne lehessen átadni más típust, hanem azt is hogy egyáltalán ne kaphasson más típusú értéket, ha egyszer már SPLint volt. Ennek főleg ez a jelentősége, hiszen egy osztályt, aminek csak konstruktora van és egy értéket tárol, mi is bármikor meg tudnánk írni.

Próbáltam létrehozni az SplType -ból örökölve új típusokat. De ez nem sikerült. Ha valaki összehozza, ossza meg velem is. :)

Reflection osztályok

Zárásként úgy gondoltam, ezt is megemlítem ebben a verzióban. Mert nagyon sokszor hasznos lehet. Különféle Reflection osztályok vannak PHP-ban. Ezekkel futásidőben lehet elemezni egy osztályt vagy objektumot.

Még hosszan sorolhatnám valószínűleg a hasznos eszközöket, de valahol meg kell húznom a határt. Ha csak ennyit hallanék a PHP-ról, már az is felkeltené az érdeklődésemet. És még nincs vége.

PHP 5.3

Néhány függvény „deprecated” jelzőt kapott és természetesen új függvények is megjelentek. Innentől kezdve az SPL már nem volt kikapcsolható. És újabb ötletekkel is előállt, amik megkönnyítik a kódolást.

Névterek

A névterek bevezetését jó ötletnek tartottam. Bár azon meglepődtem, hogy azok elválasztására az egyébként escapelésre használt „\” jelet szavazták meg. De hát ez van, ezt kell szeretni. És szerintem van is rá okunk. Mert mi volt régen? Tegyük fel, volt két fejlesztő. Az egyik létrehozott egy System osztályt a webalkalmazásnak. De szerette volna használni egy másik fejlesztő vendégkönyv szkriptjét, aminek szintén volt System osztálya. Vagy átnevezte a vendégkönyv osztályát és minden ráhivatkozást is ennek megfelelően, vagy lemondott a vendégkönyvről. Ha okos volt a vendégkönyv fejlesztője, akkor egy GuestBook_System osztályt hozott létre. Igen ám, de így minden alkalommal ki kell írni az egész osztálynevet. És mivel minden programozó hivatásától fogva --, de egészséges értelemben -- lusta, ezt nem szeretjük.

5.3 előtt

  1. class System { }
  2. class GuestBook_System { }

5.3 óta

  1. namespace {
  2.       class System {}
  3. }
  4.  
  5. namespace GuestBook {
  6.     class System { }
  7. }

Itt azt tudni kell, hogy az első névtér blokk a globális névtér. A gyökér. Ezen kívül nem kötelező blokkokként létrehozni a névtereket. Lehet akár egy fájl egész tartalma egy névtér alá tartozó. ilyenkor elég a fájl elején, de tényleg a legelején ennyi:

namespace GuestBook;

Lényeges, hogy egy fájlban nem lehet vegyesen használni a két módszert. Vagy a fájl elején van egy névtér definíció, ami után levő tartalom a következő definícióig adott névtérhez tartozik, vagy minden névtér blokként van megadva.

Az előző megjegyzésből adódik, hogy a nem blokkos megoldást használva gyökér névtérben nem definiálhatunk már semmit.

Itt azért még mindig felvetődik a kérdés, mi van, ha több GuestBook névtér van. Mert hát miért ne nevezhette volna el két fejlesztő ugyanúgy a vendégkönyve névterét. Főleg, ha ilyen triviális név. A megoldás, hogy a fejlesztő, vagy fejlesztőcsapat saját névteret definiál, és minden fejlesztését abba teszi. Például ha rólam lenne szó, akkor:

namespace rimelek\GuestBook { }
Ezzel lenne egy rimelek névterem, és azon belül a GuestBook névtér. Így a névütközések elkerülhetők. Egy adott névtéren belül, az ugyanabba a névtérbe tartozó osztályok és további névterek közvetlenül elérhetők anélkül, hogy a teljes névtér útvonalat kiírnánk. Olyan ez, mint html-ben a base tag. A fájl elején, vagy a blokkban definiált névtér megadja a default útvonalat. Ha pedig a gyökértől kell hivatkozni egy alsóbb névtérre, akkor „\” jellel kezdve a gyökér névtértől lehet hivatkozni az adott elemre.

Egy másik hasznos dolog, a use kulcsszó. Ennek php-ben több jelentősége is van. Névterek szempontjából az útvonalak egyszerűbb megadásában rejlik.

  1. $object = new \ez\egy\nevter\utvonal\Osztaly();
  2. //vagy
  3. use \ez\egy\nevter\utvonal;
  4. $object = new \utvonal\Osztaly();
  5. //vagy
  6. use \ez\egy\nevter\utvonal\Osztaly;
  7. $object = new Osztaly();

Na most ezzel így a névütközések elkerülésének látszólag lőttek, ha van egy globális Osztaly nevű osztály is. De itt jön képbe a másik nagyszerű lehetőség. A névterek és osztályok átnevezése. Ami pedig már önmagában megoldhatná a névütközéseket, ha a abból a megoldásból indulunk ki, amikor a fejlesztő a vendégkönyv System osztályát átnevezi. Mert itt nem kell mindenhol átírni a hivatkozásokat.

  1. use \ez\egy\nevter\utvonal\Osztaly as AtnevezettOsztaly;
  2. $object = new AtnevezettOsztaly();
  3. //vagy
  4. use \ez\egy\nevter\utvonal as AtnevezettUtvonal;
  5. $object = new AtnevezettUtvonal\Osztaly();

Fontos, hogy az átnevezendő elemet teljes névtér meghatározással kell megadni. Akkor is, ha az az aktuális névtérben szerepel. Egyéb esetben a gyökér névtérben keresi az osztályt vagy névteret a program.

Függvények átnevezése nem lehetséges, de egy saját névtérben használhatunk ugyanolyan nevű függvényt, mint ami a gyökér névtérben létezik.

Végül pedig csak említés szinten írok a konstansokról. Ugyanis 5.3-ban a névterekhez tartozhatnak ugyanúgy const kulcsszóval definiált konstansok, mint osztályokon belül.

Nekem ugyan hiányzik az egymásba ágyazhatóság, amivel strukturáltabb formában lehetne a névtér definíciókat elhelyezni, de ettől függetlenül is egy olyan lehetőséget kaptunk, amit egyszerűen bűn nem alkalmazni. Illetve korábban írtam az __autoload függvényről, ami a névterek használatával kicsit módosulhat. Ugyanis az osztálynévbe ilyenkor beletartozik a névtere is. Azaz az aláhúzás karakterek „/” jelre cserélése helyett vagy mellett lehet a „\” jeleket cserélni. Ennek megfelelően strukturálva a fájlokat a névterek használatával, hasonlóan áttekinthető forrást írhatunk, mint például javaban, ahol a névterekhez hasonló package-ek léteznek.

Mágikus metódusok

Az 5.2-es verziónál 2 mágikus metódust kihagytam, ami csak 5.3 óta létezik.

A egyik a __callStatic(). A __call() metódus statikus párja. Ami igen hasznos, hiszen ha egy objektumnak lehet futásidőben definiált metódusa, egy osztálynak miért ne lehetne statikus formában? Mutatok majd példát is rá, hogy mikor érdekes.

A másik pedig az __invoke(), ami egy objektumot tesz függvénykényt hívhatóvá. Hogy mi a jelentősége? Többféleképpen is fel lehet használni. De például a konstruktor tartalmát ebbe másolva, és a konstruktorban csak meghívva megoldható, hogy az objektum konstruktorban átadott adatait bármikor átértékeljük. De használható akár arra is, hogy az objektumot megfeleltessük egy értéknek. Ami a __toString() -ben visszaadható értékhez képest nem csak string lehet. És most jöjjön egy példa arra, mit lehet alkotni ezekkel a metódusokkal. A példa az egyszerűbb matematikai műveleteket valósítja meg, statikus és példányszintű megvalósítást is nyújt egyszerre.

  1. class Math
  2. {
  3.         private $value;
  4.        
  5.         private static $operations = array(
  6.                 'add' => '+',
  7.                 'sub' => '-',
  8.                 'mul' => '*',
  9.                 'div' => '/',
  10.                 'mod' => '%'
  11.         );
  12.        
  13.         public function __construct($number)
  14.         {
  15.                 $this->value = $number;
  16.         }
  17.        
  18.         private static function operation($a, $b, $method)
  19.         {
  20.                 return eval(sprintf('return %f %s %f;',$a, self::$operations[$method], $b));
  21.         }
  22.        
  23.         public function __call($method, $args)
  24.         {
  25.                 array_unshift($args, $this());
  26.                 $this(self::__callStatic($method, $args));
  27.                 return $this;
  28.         }
  29.        
  30.         public static function __callStatic($method, $args)
  31.         {
  32.                 $method = strtolower($method);
  33.                 if (!isset(self::$operations[$method])) {
  34.                         throw new BadMethodCallException('Nincs '.$method.' nevű művelet!');
  35.                 }
  36.                
  37.                 $args[] = $method;
  38.                 return call_user_func_array(array(__CLASS__, 'operation'), $args);
  39.         }
  40.        
  41.         public function __invoke($value=null)
  42.         {
  43.                 if (!is_null($value)) {
  44.                         $this->value = $value;
  45.                 }
  46.                 return $this->value;
  47.         }
  48.        
  49.         public function __toString() {
  50.                 return (string)$this->value;
  51.         }
  52. }
  53.  
  54.  
  55. echo Math::add(5, 8)."<br />";
  56.  
  57. $math = new Math(56);
  58. echo $math->add(5)."<br />";
  59. echo $math->sub(12)."<br />";
  60.  
  61. echo $math->add(4)->sub(2)->div(2)->mul(3)->sub(0.5)->mod(3);

Névtelen függvények

A névtelen függvények szintén ismerősek lehetnek JavaScript-ből sokaknak. És PHP-ben is létezett egy lehetőség már rá korábban, de igen csúnya megoldás volt az. A paraméterek listáját és a forráskódot is stringként kellet átadni. egy függvénynek, ami elkészítette a névtelen függvényt és így az változóba tölthető lett. Semmi más lehetőséget nem adott. És ezzel a megoldással a szerkesztésidejű szintaktikaelemzésről is le kellett mondanunk a forráskódszínezéssel együtt. Majd jött az 5.3 és beépítette a szintaktikájába a névtelen függvény lehetőségét. Ami ráadásul egy objektumot hozott létre, nem is csak függvényt. Bár függvényszerűen volt hívható, ami az előző pontban említett __invoke() mágikus metódusnak köszönhető..

5.3 előtt

  1. $func = create_function('$a, $b', 'return $a+$b;');
  2. echo $func(7, 9);

5.3 óta

  1. $func = function($a, $b) {
  2.         return $a + $b;
  3. };
  4. echo $func(1, 6);

Lényeges, hogy a függvény záró kapcsos zárójele után pontosvessző kell!

Mivel ez egy objektum, ami a Closure osztály példánya, így a type hinting-gel is együtt használható. Azaz ki lehet kötni, hogy kizárólag névtelen függvényeket lehessen átadni.

  1. function test(Closure $func)
  2. {
  3.         echo $func(8, 9);
  4. }

És ismét megjelenik a use kulcsszó. Ismét JavaScript-re hivatkoznék. Abban ugyanis a névtelen függvényben elérhetők a külső változók is. Itt viszont nem. Ha mégis szeretnénk elérni őket, két lehetőség van. Vagy a global kulcsszóval beemeljük a változókat, ami nem javasolt és olyankor a függvényben az eredeti változó is megváltoztatható, vagy használjuk a szuper use kulcsszót.

  1. $x = 10;
  2. $y = 15;
  3. $z = 26;
  4.  
  5. $f = function($x) use ($y, &$z) {
  6.         var_dump($x++, $y++, $z++);
  7. };
  8.  
  9. $f(8);
  10. $f(9);

Első ránézésre felfoghatnánk úgy is, mint két további paraméterét a függvénynek. De a trükk abban van, hogy a függvényt akárhol meghívhatom, ahol a use-ban megadott két paraméter nem létezik, ha a függvénydefiníciónál létezett.

Késői statikus kötés

Ennek a lényege, hogy míg egy objektumnál az öröklési lánc bármely osztályában hivatkozhatunk a $this változóval bármely osztályban definiált metódusra, tulajdonságra, úgy azt nem tehetjük meg statikusan. Vagyis nem tehettük meg 5.3 előtt. Az osztályokra a self és parent kulcsszavakkal lehetett hivatkozni. Az első az aktuális osztályra vonatkozik, míg a második a szülőre. De egyik sem hivatkozik a majdan esetleg valamelyik gyerekben deklarált, avagy felülírt metódusra. 5.3 -ban erre jó a static kulcsszó. Aminek egyéb funkcióit már ismerjük. Ezzel együtt megjelenik a get_called_function() nevű metódus is, ami a static által hivatkozott osztály nevét adja vissza. Na most ennek haszna a következő. Korábban egy egyke osztály így nézett ki:

  1. class A
  2. {
  3.         private static $instance = null;
  4.  
  5.         private function __construct() {
  6.                
  7.         }
  8.  
  9.         public static function instance()
  10.         {
  11.                 if (is_null(self::$instance))
  12.                 {
  13.                         self::$instance = new self();
  14.                 }
  15.                
  16.                 return self::$instance;
  17.         }
  18. }

Bár ezt már régen is lehetett php-ben sokkal rövidebben:

  1. class A
  2. {
  3.         private function __construct() {
  4.                
  5.         }
  6.  
  7.         public static function instance()
  8.         {
  9.                 static $instance;
  10.                 if (!isset($instance)) {
  11.                         $instance = new self();
  12.                 }
  13.                 return $instance;
  14.         }
  15. }

De ezt az összes osztályban, ami singleton, meg kellett írni. Ezen lehetett segíteni ugyan a következő módon:

  1. abstract class Singleton
  2. {
  3.         final private function __construct() {
  4.                  
  5.         }
  6.  
  7.         public static function instance()
  8.         {
  9.                 if (is_null(self::$instance))
  10.                 {
  11.                         self::$instance = new self();
  12.                 }
  13.                
  14.                 return self::$instance;
  15.         }
  16. }
  17.  
  18. class A extends Singleton
  19. {
  20.         public static function instance() {
  21.                 return parent::instance(__CLASS__);
  22.         }
  23.        
  24.         public function tesztB()
  25.         {
  26.                 return __METHOD__;
  27.         }
  28. }

De így is a programozóra van bízva, hogy az instance metódust felülírja. Ezen segít a static kulcsszó és a get_called_class() metódus:

  1. abstract class Singleton
  2. {
  3.         final protected function __construct() {
  4.                
  5.         }
  6.                
  7.         public static function instance()
  8.         {
  9.                 static $instance;
  10.                 $class = get_called_class();
  11.                 if (!isset($instance[$class])) {
  12.                         $instance[$class] = new static();
  13.                 }
  14.                 return $instance[$class];
  15.         }
  16. }
  17.  
  18. class A extends Singleton
  19. {
  20.         public function tesztA()
  21.         {
  22.                 return __METHOD__;
  23.         }
  24. }

Heredoc / Nowdoc

A heredoc szintaxis már korábbi találmány. Arra való, hogy egy nagyobb stringet, amiben ráadásul idézőjelek is lehetnek, ne kelljen idézőjelbe tenni és a belső idézőjeleket escapelni, hanem egy választott azonosító segítségével ezt ki lehessen kerülni.

  1. $valtozoval = 10;
  2.  
  3. $text = <<<T
  4. Ez egy szöveg $valtozoval.
  5. T;
  6. //Ez egy szöveg 10

A nyitó és záró kulcsszó megegyezik. Valamint a záró kulcsszó mindig a sor elején van. Előtte nem lehet space sem!

Mint látható, ez olyan, mintha normál idézőjelbe tettem volna a szöveget. És így a változók is kiértékelődnek benne. De ha nem ez a cél, akkor segít a nowdoc szintaxis. Ami annyiban különbözik, hogy a nyitó kulcsszó körül ugyanannak az idézőjelpárnak kell lennie, mint amibe az érintett stringet szeretnénk tenni.

  1. $valtozoval = 10;
  2.  
  3. $text = <<<'T'
  4. Ez egy szöveg $valtozoval.
  5. T;
  6. //Ez egy szöveg $valtozoval

PHP Archívum

Na ez egy érdekes dolog megint. Mert gyakorlatilag a ZipArchive osztályon alapul, de a célja, hogy egy alkalmazást be lehessen csomagolni egy archívumba, ami egyben futtatható is. A Phar extension-nek igen sok lehetősége van, de én csak egy rövid ( tényleg ) példát írok.

  1. <?php
  2. try {
  3.     $phar = new Phar('myphar.phar');
  4.     $phar['index.php'] = '<?php echo "Ez a főoldal"; ?>';
  5.     $phar['other.php'] = '<?php echo "Ez nem a főoldal"; ?>';
  6.     $phar->setStub('<?php
  7. Phar::webPhar();
  8. __HALT_COMPILER(); ?>');
  9. } catch (Exception $e) {
  10.         echo $e->getMessage(),"\n";
  11.     echo $e->getTraceAsString();
  12. }

Ezzel létrehoz egy myphar.phar nevű archívumot, amibe belerak két fájlt. Ezt aztán lehet újra kitömöríteni valahova:

  1. $phar = new Phar('myphar.phar');
  2. $phar->extractTo(__DIR__.'/phar',null, true);

Vagy épp lefuttatni: include 'phar://myphar.phar/index.php';. ezen kívül persze sok egyebet lehet vele. De valószínű, az egy másik cikk témája lesz.

PHP 5.4

Elértünk az utolsó fejezethez. Már ami a cikket illeti. A PHP természetesen tovább fog fejlődni. Ebben a verzióban a jelentős újítás főleg a trait-ek. Amikről később írok is. De vannak kisebb újdonságok is. Ezek mind hozzájárulnak ahhoz, hogy azt mondjam, újra beleszerettem a PHP-be.

Egész számok jelölése

Bevezették az egész számok bináris alakban történő megadását. Eddig ugye lehetett decimálisan, oktálisan és hexa-ban megadni. Ehhez jön most a bináris alak. Amit egy „0b”, azaz „nulla bé” prefix előz meg.

PHP 5.4 előtt

  1. $decimal = 15;
  2. $hexa = 0x0F;
  3. $octal = 017;
  4. echo $decimal."<br />"; //15
  5. echo $hexa."<br />";    //15
  6. echo $octal."<br />";   //15

PHP 5.4 -ben

  1. $binary = 0b1111;
  2. echo $binary."<br />"; //15

Tömbök

Ez nem egy hatalmas csoda, de a tömbök deklarációja egyszerűsödik. Aki programozott már JavaScriptben, annak nem lesz újdonság.

5.4 előtt:

  1. $array1 = array(1,2,3);
  2. $array2 = array(
  3.         'index1' => 1,
  4.         'index2' => 2
  5. );

5.4 -ben

  1. $array = [1,2,3];
  2. $array = [
  3.         'index1' => 1,
  4.         'index2' => 2
  5. ];

Ide sorolnám még az általam rég várt lehetőséget, hogy a függvények visszatérési értékét közvetlenül lehet indexelni anélkül, hogy előbb azt átadnánk egy változónak.

Függvény, ami tömböt ad vissza

  1. function coords()
  2. {
  3.         return array(
  4.                 'x' => 12,
  5.                 'y' => 34,
  6.                 'z' => 78
  7.         );
  8. }

5.4 előtt

  1. $coords = coords();
  2. $z = $coords['z'];

5.4-ben

$z = coords()['z'];

Névtelen függvények

5.3-nál már írtam a névtelen függvényekről. És azt is írtam, hogy ott a függvényen kívüli változók nem elérhetők. Ilyennek számít a $this is, ha objektumon belül lett létrehozva a függvény. 5.4-ben annyiban változott a helyzet, hogy a $this változó, ami az objektumra hivatkozik, már elérhető. Valamint újabb metódusokat kapott a Closure osztály. Amivel utólag is változtatható, hogy a létrehozott függvény melyik objektumhoz tartozzon. És így a $this kulcsszó melyikre hivatkozzon.

  1. class A {
  2.         private $test = '11';
  3.         public function test()
  4.         {
  5.                 return function()
  6.                 {
  7.                         return $this->test;
  8.                 };
  9.         }
  10.        
  11.         public function setTest($test)
  12.         {
  13.                 $this->test = $test;
  14.         }
  15. }
  16.  
  17. $aa = new A();
  18. $aa->setTest(5555);
  19.  
  20. $a = new A();
  21. $f = $a->test();
  22. echo $f().'<br />'; //11
  23. $f = $f->bindTo($aa);
  24. // /*vagy: */ $f = Closure::bind($aa, $f);
  25. echo $f().'<br />'; //5555

Hogy ere gyakorlatban mikor lehet szükség, arra még nem jöttem rá. De mindenesetre adott a lehetőség.

Visszahívható függvények

Itt sem történt hatalmas változás, de a call_user_func() függvény használata elhagyhatóvá vált. Mert felváltotta egy újabb lehetőség, ahol a tömb is hívható lett.

$callback = array($object, 'method');

5.4 előtt

echo call_user_func($callback);

5.4 ben

echo $callback();

Beépített webszerver

Az 5.4 bevezetett egy beépített webszervert, ahol tetszőleges mappában indítható tetszőleges hoston és porton egy webszerver. A document root megadása nélkül az éppen aktuális mappa lesz a document root, amiből a szerver indítva lett. Ez jó, mert nem olyan egyszerű egyszerre két php verziót futtatni. Így viszont én is a jelenlegi 5.3-asom mellé feltelepítettem az 5.4-et. Az apache-csal az 5.3 indul, de indíthatok mellé 5.4-et. És párhuzamosan tesztelhetem a kettőt. Így tudtam leellenőrizni, mi az, ami egyikben még létezett, a másikban már nem. Vagy fordítva. A szerver indítása legegyszerűbben:

php -S localhost:8000

Ekkor elérhető lesz a root könyvtár a http://localhost:8000 oldalról. Persze ilyenkor nem apache fut alatta, tehát a speciális apache változókat és függvényeket el lehet felejteni.

Trait-ek

És elértünk a kedvencemhez. A trait-ek ötvözik a már korábban bevezetett megoldásokat. Mégis adnak újat. PHP-ben nincsen többszörös öröklődés. Azaz ha A osztály örököl B-től, nem örökölhet C-től is. Csak ha C-t B örökölte. Ez viszont nem mindig oldható meg. Főleg, ha nem mi írtuk a másik osztályt. Az osztályokhoz hasonlóan így bevezették a Trait fogalmát. Ami arra szolgál, hogy olyan osztályszerű, de mégsem osztály struktúrát hozhassunk létre, amit a use kulcsszóval egy valódi osztályban jelezhetünk, hogy annak a metódusaira és tulajdonságaira szükség lesz. Olyan ez kicsit, mintha fognánk egy osztály pár metódusát és bemásolnánk a sajátunkba. Csak ezt így megoldja helyettünk az értelmező. Nem történik tehát öröklés, az eredmény mégis ugyanaz. A korábbi konstansok mellett bevezették a __TRAIT__ konstanst is. Ami adott traiten belül tartalmazza a trait nevét. A __METHOD__ konstans ugyan a trait nevével tartalmazza a metódust, de a __CLASS__ már annak az osztálynak a nevét tartalmazza, amiben a trait fel lett használva.

  1. trait TraitA
  2. {
  3.         public function getInfo()
  4.         {
  5.                 return array(__CLASS__, __METHOD__, __TRAIT__);
  6.         }
  7. }
  8.  
  9. class A {
  10.         use TraitA;
  11. }
  12.  
  13. $a = new A();
  14. var_dump($a->getInfo());

Mivel nem öröklés történt, az instanceof operátor nem használható annak ellenőrzésére, hogy az osztály megvalósít-e egy trait-et. A korábban említett reflection osztályok viszont ebben is segítenek.

  1. $ref = new ReflectionClass('A');
  2. if (in_array('TraitA',$ref->getTraitNames())) {
  3.         echo 'TraitA trait megvalósítva!';
  4. }

Trait-eket is fel lehet használni singleton osztályokhoz

  1. trait Singleton
  2. {
  3.         final private function __construct() {
  4.                
  5.         }
  6.        
  7.         public static function instance()
  8.         {
  9.                 static $instance;
  10.                 if (!isset($instance)) {
  11.                         $instance = new self();
  12.                         if (is_callable($instance))
  13.                         {
  14.                                 $instance();
  15.                         }
  16.                 }
  17.                 return $instance;
  18.         }
  19.        
  20.         public function __clone()
  21.         {
  22.                 throw new \BadMethodCallException('Nem klónozható osztály: '.get_called_class());
  23.         }
  24. }
  25.  
  26. class A
  27. {
  28.         use Singleton;
  29.        
  30.         private function __invoke()
  31.         {
  32.                 echo 'teszt';
  33.         }
  34. }
  35.  
  36. $a = A::instance();

És ahogy említettem, többszörös öröklést is helyettesíti, mivel akárhány trait felhasználható:

  1. trait PublicGetters
  2. {
  3.         public function __get($prop)
  4.         {
  5.                 return $this->$prop;
  6.         }
  7. }
  8.  
  9. class A
  10. {
  11.         use Singleton, PublicGetters;
  12.  
  13.         private $private = 'privát';
  14.        
  15.         private function __invoke()
  16.         {
  17.                
  18.         }
  19. }
  20.  
  21. $a = A::instance();
  22.  
  23. echo $a->private;
  24. $a->private = 'ez nem fog működni';

Ha véletlenül két felhasznált trait-ben ugyanaz a metódus létezik, akkor az egyiket át lehet nevezni. És meg lehet mondani, hogy az eredeti nevén melyik trait metódusa legyen meghívva. Kicsit továbbfejlesztve már azt is lehet, hogy az eredeti név ne legyen elérhető, mert van lehetőség a láthatóság módosítására is. Mindezt a use és as kulcsszóval lehet megtenni.

  1. trait FavoriteFruit
  2. {
  3.         public function name()
  4.         {
  5.                 return 'Alma';
  6.         }
  7. }
  8.  
  9. trait FavoriteProgLang
  10. {
  11.         public function name()
  12.         {
  13.                 return 'PHP';
  14.         }
  15. }
  16.  
  17. class Favorite
  18. {
  19.         use FavoriteFruit, FavoriteProgLang
  20.         {
  21.                 FavoriteFruit::name as FavoriteFruit;
  22.                 FavoriteProgLang::name as FavoriteProgLang;
  23.                 FavoriteProgLang::name insteadof FavoriteFruit;
  24.                 FavoriteProgLang::name as private;
  25.         }
  26. }
  27.  
  28. $fav = new Favorite();
  29.  
  30. echo $fav->favoriteProgLang().'<br />';
  31. echo $fav->favoriteFruit().'<br />';

Jó, de mi van azokkal az osztályokkal, amiket nem trait-ként írt meg a fejlesztője, de én mégis szeretném többszörös örökléssel felhasználni? Erre is ad megoldást a trait, ugyanis a trait-ek örökölhetnek osztályoktól. Innentől kezdve pedig minden ugyanúgy működik, mintha eleve traitek lettek volna az osztályok:

  1. class ParentClassMethod
  2. {
  3.         public function method()
  4.         {
  5.                 return __METHOD__;
  6.         }
  7. }
  8.  
  9. class ParentClassName
  10. {
  11.         public function name()
  12.         {
  13.                 return __CLASS__;
  14.         }
  15. }
  16.  
  17. trait TParentClassMethod extends ParentClassMethod { }
  18. trait TParentCLassName extends ParentClassName { };
  19.  
  20. class A
  21. {
  22.         use TParentClassMethod, TParentClassName;
  23. }
  24.  
  25. $a = new A();
  26. var_dump($a->method(), $a->name());

Ez a funkció csak a fejlesztői verzióban volt benne. Mint kiderült, ez egy hiba volt. És javítás címén törölték a traitek osztályoktól való öröklésének lehetőségét. Várható, hogy mutatok erre egy alternatív megoldást egy új cikkben.

Összefoglalás

Indultunk tehát egy nagyon alap megoldástól, ami személyes honlapok készítését volt hivatott segíteni. Kicsit összetettebb problémákat már nem tudott volna megoldani. Nem is az volt a feladata. Majd elkezdet fejlődni, és ahogy rájöttek, hogy ez jó, idővel nőtt a fejlesztők száma. Ezzel együtt a lehetőségek száma és a C forráskód mérete is. Felismerve hiányosságait, a fejlesztők egyre több modern technológiát építettek be és ezt pár év késéssel a PHP programozók is elkezdték felhasználni. Ki előbb, ki később. Most pedig ott tartunk, hogy olyan lehetőségek rejlenek benne, amit sokan nem is ismernek. Köztük én is, aki akkor döbbentem rá, mi mindent nem tudok, amikor elkezdtem kutatni a cikkhez szükséges anyagok után.

Jelen állás szerint szeptember 8-án jelenik meg hivatalosan az 5.4 béta 1-es verzió. Majd szép lassan jönnek az RC-k. A TODO lista szerint még van pár dolog, ami nincs megoldva benne. Viszont arra nem jöttem rá, lesz-e scalar type hinting. Ugyanis némely áthúzott elem már létezik 5.4 -ben. A weben sok helyen lehet olvasni róla. Viszont egy bugreport szerint, amit 5.4 -hez jegyeztek be, és válaszolt rá egy moderátor, nem létezik 5.4-ben sem. Írnak még pár újításról, ami a jelenlegi elérhető 5.4 -es verzióban még nincs. Nem gondolnám, hogy egy bétaközeli állapotból még sokat változna egy verzió. De ezt majd megtudjuk, ha kijött a végleges. Addig pedig örüljünk annak, amink van :) És a lehetőségnek, hogy még mik lehetnek egyszer.

Kategóriák: 
Megosztás/Mentés

Hozzászólások

Brown ügynök képe

Nagyon jó összefoglaló, köszönet érte. A traitek nekem új volt. Most a namespacet próbáltram munkára fogni, de valamiért nem működik, talán nem értelmeztem helyesen a működését. A példakód:
nevter mappa :

A.php fájl :

  1. namespace nevter;
  2.  
  3. class A {
  4.  
  5.  function FA() {
  6.  
  7.   return __METHOD__;
  8.  
  9.  }
  10.  
  11. }

Core.php fájl :

  1. namespace nevter;
  2. use nevter\A;
  3.  
  4. $object  =  new A;

Fatal error: Class 'nevter\A' not found in E:\easyphp\www\nevter\Core.php on line 7

Köszönöm a segítséget.

Rimelek képe

Örülök, ha jól sikerült a cikk.

A névterek használata önmagában nem elég. Az osztályokat be is kell tölteni. Vagyis a fájlokat. Ezt megtetted? Írtam autoload megoldást, csak a névtereshez nem írtam forráskódot. De kb ennyi:

  1. function __autoload($class)
  2. {
  3.         require_once dirname(__FILE__) . '/ '. str_replace(array('_', '\\'),array('/', '/'), $class) . '.php';
  4. }

Persze tetszés szerint lehet ezt változtatni. Attól függően, hol van a betöltő függvényed. Vagy netán külön osztály egy metódusában van. Ahhoz a spl_autoload_register() függvény kellene még. De ez az elve.

UI: A <code> helyett a <php> -t használd inkább php-hez. Szebb :) ( módosítottam )

UI 2: Hozzáteszem, neked a use -ra nem is lenne szükséged, mert mindkét fájlod ugyanabban a névtérben van.Így az osztály eleve elérhető. Akkor lenne értelme, ha a core-od maradna a globális névtérben. Akkor kellhetne a use-zal rövidíteni a meghívást.

Brown ügynök képe

A névterek használata önmagában nem elég. Az osztályokat be is kell tölteni. Ezt megtetted?

Nem. A Symfony 2 keretrendszerben nem kellett a fájlokat betölteni, legalábbis nekem nem kellett beilleszteni (ezek szerint a rendszer megtette). Ezért is hittem azt, hogy a namespace jelöli a fájl helyét, a use pedig a használatát.

Rimelek képe

Mert ott is van valamilyen autoload megoldás ( már nem emlékszem milyen ), de az a saját, meghatározott nevű osztályaira vonatkozik. Arra, amit te hozol létre, nem. Ezért külön kell írnod hozzá egy autoload-ot.

Javaslom, előbb próbálgasd külön php fájlokban. Keretrendszer nélkül. És ha már minden világos, akkor próbáld alkalmazni keretrendszerben. De inkább nem ajánlom, hacsak nem saját keretrendszert írsz. Tartsd be a keretrendszer szabályait.

StarZ3r0 képe

Kiváló cikk. Köszi az összefoglalót.

Krisz képe

Tenyleg eleg jo kis osszefoglalo! Ritka a magyar anyagok kozott. Szep munka

Új hozzászólás