O nové verzi PHP se šušká již více než rok, letos se však konečně dočkáme, betaverze je již nyní k dispozici. Přestože se v PHP5 objeví několik nových vlastností a změn, tou hlavní a nejrozsáhlejší je nepochybně změna objektového modelu. Podívejme se spolu, co přesně to znamená a jaké výhody to uživatelům přinese.

Řada dnešních uživatelů PHP možná ani neví, že PHP podporuje práci s objekty, a to dokonce již od verze 3.0. Patříte-li mezi menšinu, která si je existence objektů v PHP vědoma, jistě také víte, že implementace objektů v PHP není zrovna šťastná, koneckonců, přiznávají to i sami autoři. V tomto článku srovnáme současný stav s novými možnostmi a podíváme se, co vše bude s PHP5 snazší.

Poznámka: PHP5 je stále ve stadiu vývoje a některé vlastnosti se mohou měnit. Mluvilo se například o tom, že bude podporována vícenásobná dědičnost, ale pravděpodobně tomu tak nebude. Podobně se ve starších návrzích nezmiňují statické proměnné, které pravděpodobně implementovány budou.

Volání hodnotou

Základním problémem současného objektového modelu v PHP je, že objekt není z hlediska jazyka nic jiného než jakákoli jiná proměnná. Při volání funkce, při předávání návratové hodnoty atd. je tedy předávaný objekt kopírován, nikoli předán přímo. To s sebou nese celou řadu závažných problémů:

  • zbytečně složité konstrukce, pokud chci například objekt uvnitř funkce měnit
  • nelze implementovat destruktory, neboť není zřejmé, kdy životnost objektu končí
  • zpomalení běhu aplikace kvůli zbytečnému kopírování velkého množství dat

Ukažme si to na příkladu:

class Ucet {
  var $cislo;
  var $majitel;
  var $stav;
  …
}
function Nastav($objekt, $cislo, $stav) {
  $objekt->cislo = $cislo;
  $objekt->stav = $stav;
}
function Prevod($odkud, $kam, $castka) {
  if ($odkud->stav >= $castka) {
    $odkud->stav -= $castka;
    $kam->stav += $castka;
  }
}
$ucet_a = new Ucet();
$ucet_b = new Ucet();
$ucet_a->stav = 20000;
$ucet_b->stav = 5000;
Nastav($ucet_a, „42-0001457889“, 50000);
echo „Částka na účtu A: {$ucet_a->stav}<br>Částka na účtu B: {$ucet_b->stav}<p>“;
Prevod($ucet_a, $ucet_b, $castka);
echo „Částka na účtu A: {$ucet_a->stav}<br>Částka na účtu B: {$ucet_b->stav}<p>“;

Máme jednoduchou třídu Ucet (v našem příkladu nás v podstatě zajímá pouze její vlastnost stav). Nejprve vytvoříme dva nové účty v proměnných $ucet_a a $ucet_b. Poté voláme metodu Nastav(), jež nastavuje číslo účtu a aktuální zůstatek. Funkce sice proběhne, ale stav účtu A se nezmění! Je to proto, že při volání funkce dojde interně k vytvoření kopie předávaného objektu a všechny operace jsou prováděny na této kopii. Dochází tedy ke zbytečnému kopírování všech dat o účtu a jeho majiteli, ale především není dosaženo požadovaného efektu. V tomto případě je ještě pomoc relativně jednoduchá, stačí metodu Nastav() zahrnout do příslušného objektu:

class Ucet {
  var $cislo;
  var $majitel;
  var $stav;
  function Nastav($cislo, $stav) {
    $this->cislo = $cislo;
    $this->stav = $stav;
  }
}
$ucet_a->Nastav("42-0001457889", 50000);

V některých případech to však možné není. Zde si lze pomoci tak, že si vynutíme předávání odkazem pomocí &, tato konstrukce je však značně krkolomná:

function & Nastav($objekt, $cislo, $stav) {
  $objekt->cislo = $cislo;
  $objekt->stav = $stav;
  return $objekt;
  echo $objekt->stav;
}
$ucet_a = & Nastav($ucet_a, "42-0001457889", 50000);

Po předchozím výkladu už vás zřejmě nepřekvapí, že ani funkce Prevod() nefunguje tak, jak má, a stavy účtů A a B (resp. členských proměnných objektů $ucet_a a $ucet_b) se nezmění. Tentokrát nám však ani nepomůže předání objektů hodnotou.

Volání odkazem

Základem pro změnu objektového modelu v PHP je tedy oddělení objektů od běžných proměnných. Místo objektů se pracuje s odkazem na objekt (object handle), a tento odkaz je také předáván do funkcí a zpět. Interně se jedná o číslo udávající index objektu v tabulce, podobně jako se například pracuje s otevřenými soubory. Výsledkem je rychlejší kód (není třeba neustále kopírovat všechna data při každém předávání) a srozumitelnější (není třeba vynucovat si předávání odkazem a dokonce se uvažuje, že bude tato konstrukce časem z jazyka odstraněna – v PHP5 je zatím označena za zastaralou, deprecated).

Konstruktory a destruktory

V PHP3 je konstruktorem metoda, jež má stejné jméno jako třída. Tato definice zní logicky, ale má vážné trhliny, neboť konstruktorem se může stát i metoda, u níž jste to nezamýšleli, a někdy naopak konstruktor chybí.

class A {
  function A() {
    // konstruktor třídy A
  }
}
class B extends A {
  // nemá konstruktor, metoda B() není definována
}

Třída B neobsahuje metodu B(), a proto nemá žádný konstruktor – teprve od PHP4 je v této situaci volán konstruktor předka, pokud existuje. Pokud ale třída A bude obsahovat metodu B(), bude tato metoda použita jako konstruktor pro třídu B, o níž třída A nemá ani zdání!

V PHP4 je konstruktorem metoda, jež má stejné jméno jako třída, v níž je definována. To znamená, že pro naši třídu Ucet bychom použili konstruktor Ucet() a díky dodatku o tom, že se daná metoda musí vyskytnout v téže třídě, nedojde ke kolizím jako v PHP3. S podobným označením konstruktoru se můžete setkat i v dalších programovacích jazycích. Problém vznikne pouze tehdy, když v konstruktoru odvozené třídy voláte konstruktor předka a potřebujete třídu přesunout jinam v objektové hierarchii – pak je nezbytné zasahovat do kódu přesouvané třídy a příslušně upravit odkaz na konstruktor předka.

V PHP5 jako konstruktor slouží metoda jménem __construct() v příslušné třídě. Tím odpadají veškeré problémy, neboť například konstruktor předka lze volat pomocí parent::__construct() bez ohledu na jméno předka. Z důvodů zpětné kompatibility bude interpret v případě, že metodu __construct() nenajde, hledat také metodu se stejným názvem jako příslušná třída, takže stávající kód není při migraci na novou verzi nezbytně třeba měnit.

Již jsem se zmiňoval, že díky implementaci odkazů na objekty je možné také definovat destruktor objektu, a to jako metodu __destruct(). V této metodě je možné uvést kód automaticky vykonávaný při zániku objektu – uvolnění zdrojů, zápis dat do logů a podobně.

Poznámka: Všimněte si, že konstruktor i destruktor začínají dvěma podtržítky, podobně jako další „magické“ metody, __sleep() a __wakeup(), jež existují již v PHP4 a jsou používány při serializaci objektů. Rozhodně byste se měli vyvarovat deklarace vlastních metod začínajících dvěma podtržítky kvůli možným budoucím problémům s kompatibilitou. V PHP5 se dále můžete setkat s magickými metodami __clone() pro vytvoření kopie objektu (viz dále) a __call() pro obsluhu situace, kdy je volána neexistující metoda příslušné třídy.

V současné verzi PHP4 destruktory používat nelze, neboť se nedá dost dobře poznat, kdy vlastně objekt skutečně zanikl. Místo destruktorů lze v omezené míře využít funkci register_shutdown_function(), která může základní „úklid“ provést, ale jde o značně neobjektové řešení.

Klonování objektů

V předcházejících odstavcích jste se dočetli, že PHP5 odbouralo kopírování objektů při každé příležitosti a objekty jsou místo toho předávány odkazem. Někdy však může nastat situace, kdy skutečně chceme samostatnou kopii objektu. Pro tento účel se používá již zmíněná metoda __clone(). Standardně tato metoda vytváří přesnou kopii objektu včetně všech jeho vlastností (pokud bychom od ní požadovali jiné chování, je možné ji překrýt).

Dereference objektů

Podstatně vylepšeno bude také dereferencování objektů. Problémy vznikaly především tehdy, pokud funkce vracela nějaký objekt, jehož vlastnost či metodu jsme chtěli použít, případně pokud byl takový objekt vrácen metodou jiného objektu. Nyní je tedy možné například psát:

foo()->x->bar();

…zatímco v PHP4 bylo třeba využít meziproměnné:

$tmp = foo();
$tmp = $tmp->x;
$tmp->bar();

Kromě toho, že jde o jistě vítanou změnu pro všechny aplikace využívající objekty, toto významně zpřehledňuje komunikaci s rozhraními jako Java či COM, které popsaný způsob běžně používají.

Modifikátory proměnných a metod

Další podstatnou novinkou, jež přibližuje PHP objektově orientovaným programovacím jazykům, je možnost úpravy přístupu k vlastnostem a metodám uvnitř objektu. Dosud byly všechny vlastnosti objektu kompletně přístupné i zvenku, nebylo možné definovat soukromé (private) vlastnosti či metody, stejně jako nebylo možné definovat statické vlastnosti. Bylo možné používat statické metody, a to použitím Třída::metoda() místo běžné dereference $Objekt->metoda(), ale příslušnou metodu bylo vždy možné volat i původním způsobem.

PHP5 přidává modifikátory private, protected, static a abstract, jež fungují podobně jako v běžných programovacích jazycích podporujících OOP. Pomocí modifikátoru private lze vlastnost či třídu označit jako soukromou, což znamená, že nelze použít vně příslušné třídy:

class A {
  private $tajne;
  private function soukrome() {
    // soukromá funkce
    $tajne += 2;
  }
  function test() {
    $tajne = 1; // volání ok
    soukrome(); // volání ok
  }
}
$oA = new A();
$oA->tajne = 42; // chyba!
$oA->soukrome(); // chyba!

Pozor, v současné implementaci PHP5 bohužel při odvozování tříd dojde k tomu, že se soukromé vlastnosti stanou v odvozené třídě veřejnými. Předpokládám, že se to však do finální verze změní a soukromé vlastnosti se budou chovat tak, jak mají.

Modifikátor protected funguje podobně jako modifikátor private, ovšem použití vlastnosti či metody není omezeno pouze na objekty příslušné třídy, ale také na objekty tříd od ní odvozených. To znamená, že příslušná vlastnost je běžně přístupná i v metodách třídy-potomka bez nutnosti tuto vlastnost definovat znovu.

Nově PHP umožňuje statické vlastnosti. Jak jsem se již zmínil, již v předchozích verzích bylo možné metody objektu použít jako statické metody třídy použitím operátoru ::, přičemž pro dereferenci nebyl použit identifikátor konkrétního objektu, ale název třídy. Nešlo však o skutečné statické metody, ale pouze o jiný způsob volání jedné a téže metody. Stejně tak nebyl prostor pro statické proměnné. To se mění právě s PHP5, kde je možné pomocí modifikátoru static deklarovat metody i vlastnosti jako statické, tj. společné pro všechny instance téže třídy. Vhodným příkladem může být například výše úroků, která je společná všem instancím účtů stejného typu a není tedy třeba ji uchovávat odděleně v každém objektu. Podobně statické metody musejí být schopny pracovat bez konkrétní instance objektu (která dosud nemusí být ani vytvořena) a nepoužívat tedy žádné lokální metody či vlastnosti vyjma statických.

Pomocí modifikátoru abstract lze deklarovat metody třídy jako abstraktní, tedy bez konkrétní implementace. Tímto způsobem může jeden vývojář stanovit určité rozhraní pro další odvozené třídy, jež jsou nuceny metodu implementovat, jinak ji v odvozených třídách, respektive instancovaných objektech, nelze použít. Modifikátor abstract samozřejmě nemá smysl pro vlastnosti.

Shrnutí

Pokusme se na závěr shrnout novinky, jež PHP5 nabízí a způsob, jakým byla příslušná situace řešena dosud, případně jak tato změna ovlivní stávající programy (viz tabulka).

Vlastnost PHP4 PHP5 Kompatibilita
Předávání objektů hodnotou (kopírování) odkazem Ve funkcích může nečekaně dojít ke změně objektu, naopak bylo-li změny třeba, bylo zároveň nutné použít předávání odkazem pomocí &, což je nyní zbytečné.
Konstruktor metoda s názvem shodným se jménem příslušné třídy metoda __construct() Není-li nalezena metoda __construct(), hledá se konstruktor podle původního schématu.
Destruktor neexistují metoda __destruct() Funkčnost destruktorů bylo možné částečně nahradit pomocí funkce register_shutdown_function().
Klonování automaticky při každém předání či kopírování na vyžádání pomocí metody __clone() K problémům může dojít pouze tehdy, máte-li v kódu metodu __clone() sloužící jinému účelu.
Dereference objektů ve složitějších situacích pouze s využitím pomocných proměnných takřka libovolně, např. $objekt->metoda()->metoda()->vlastnost Bez problémů, vlastnost je nová.
Soukromé vlastnosti a metody nelze modifikátor private či protected Bez problémů, vlastnost je nová.
Statické vlastnosti a metody pouze statické metody odlišným způsobem adresace statické vlastnosti či metody jsou odlišeny pomocí modifikátoru static Bez problémů, vlastnost je nová.
Abstraktní metody neexistují modifikátor abstract Bez problémů, vlastnost je nová.

Jak jste se mohli sami přesvědčit, novinky v oblasti objektového modelu jsou skutečně zásadní, umožní vytvářet rychlejší a srozumitelnější aplikace. Navíc se tato změna prakticky nedotkne stávajícího kódu. Máte-li zájem o vyzkoušení zmiňovaných novinek, můžete si aktuální betaverzi PHP5 stáhnout z adresy snaps.php.net.

Odkazy a zdroje

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět