Mirin webspace

Nejbohatší život má ten, kdo žije s minimem nároků

26. 11. 2007 - Komentáře (4) PHP

Late static bindings v PHP 5.3 - neužívat vnitřně

V dalším článku něco o late static bindings (LSB), věc, která by objektové programování v PHP měl a posunout opět o něco dál. V podstatě lze říci, že LSB je o statické dědičnosti, která v PHP nefungovala tak, jak by si člověk zvyklý z jiných jazyků představoval. Lecos vypadá v PHP 5.3 lépe, ale neradujme se předčasně.

Jedna z prvních zmíněk o LSB vyplula na povrch zejména při prezentaci o připravovaném Zend Frameworku - viz Zend Webcast z roku 2005. Z komunity se ozvaly hlasy, že implementace ActiveRecordu prezentovaná na konferenci není v PHP možná. V PHP 5.3 by se to mělo změnit, ale pozor, neradujme se předčasně.

Late static bindings - základy

Nebudu se zmiňovat do detailů o co v LSB jde, už nyní je možné nakouknout do manuálu. Nejlépe si to ukážeme na příkladu.


<?php
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        self::who();      
    }  
}  

class B extends A {   
    public static function who() {
         echo __CLASS__;
    }  
}   
    
B::test(); // Vypise: A
?>

Následující kód je klasickou ukázkou toho, když se někdo pokusí o statickou dědičnost pomocí self, self a __CLASS__ nás odkáže na třídu ke které volaná metoda patří podle definice. V našem případě tedy B::test() zavolá A::who(), nikoli B::who(), jak bychom se mohli domnívat. V PHP 5.3 to již bude možné, následující kód nám již vypíše písmeno B.


<?php
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        static::who(); // Tady doslo ke zmene.    
    }  
}  

class B extends A {   
    public static function who() {
         echo __CLASS__;
    }  
}   

B::test(); // Vystup: "B"
?>

Pomocí static::who() se uskuteční vazba na správnou metodu dle aktuální reference až v době běhu skriptu, takže se opravdu zavolá B::who(). Teď bychom ještě uvítali takovéto polymorfní runtime chování i pro __CLASS__, abychom nemuseli pořád opisovat metodu who() do všech zděděných tříd, bez toho by chování LSB nebylo úplné. Ukážeme si to na příkladě již zmiňovaného ActiveRecordu. Základní koncepce ActiveRecordu vypadá takto:


<?php

class ActiveRecord {
    public static function findByPk($id) 
    {
        // implementace
    }
}

class Blog extends ActiveRecord {}

Blog::findByPk(1);
?>

Má to fungovat tak, že jméno zděděné třídy - Blog - bude určovat tabulku, nad kterou se budou provádět dané operace, třeba hledání záznamu dle primárního klíče. V PHP až do verze 5.2 nebylo možné (resp. byl nějaký hack přes debug_backtrace()) získat staticky název třídy, ke která právě volaná statická funkce patří. V PHP 5.3 to již půjde:


<?php
class ActiveRecord 
{
    public static function getClass()
    {
        return __CLASS__;
    }
    
    public static function findByPk($id) 
    {
        $calledClass = static::getClass();
        //tady se uplatni LSB
    }
}

class Blog extends ActiveRecord 
{
    public static function getClass()
    {
        return __CLASS__;
    }
}

Blog::findByPk(1);
?>

V proměnné $calledClass bude naše očekávané Blog. Hurá, ale opět tady máme neustále přepisování getClass() v potomcích. PHP 5.3 nám nabídne toužebně očekávanou funkci get_called_class(), která nám poskytne kýžený polymorfní runtime prostředek pro získání názvu třídy.


<?php
class ActiveRecord 
{
    public static function findByPk($id) 
    {
        $calledClass = get_called_class();
        //LSB pro get_class() / __CLASS__
    }
}

class Blog extends ActiveRecord {}

Blog::findByPk(1);
?>

Není všechno zlato, co se třpytí

To vypadá zdánlivě výborně, teď bychom si do naší třídy Blog, chtěli přidat třeba kód pro logování, uděláme to nějak takto:


<?php

class ActiveRecord 
{
    public static function findByPk($id) 
    {
        $calledClass = get_called_class();
    }
}

class Blog extends ActiveRecord 
{
    public static function findByPk($id) 
    {
        //kod pro logovani
        
        // Then the parent should do the magic.
        parent::findByPk($id);
    }
}

Blog::findByPk(1);
?>

A tady právě narazíme, v $calledClass bude ActiveRecord, nikoli Blog. A to jako proč?? Chyba je ve volání parent::findByPk(). Parent:: nám totiž dá jednoznačnou referenci na ActiveRecord a LSB se neuplatní! Hm, dá se to sice obejít:


<?php
class ActiveRecord
{
    public static function findByPk($id, $calledClass = null)
    {
          //dostaneme nazev tridy v parametru
          if ($calledClass === null) {
                 $calledClass = get_called_class();
          }
    }
}

class Blog extends ActiveRecord
{
    public static function findByPk($id)
    {
        //kod pro logovani
       
        //hack - volame parent s nazem nasi tridy
        parent::findByPk($id, __CLASS__);
    }
}

Blog::findByPk(1);
?>

Ale to je zase hack. Uvidíme, jak to dopadne, u mě tedy začíná počáteční nadšení pomalu opadat.


Komentáře (4)

  1. Techi - 12. 12. 2007 18:00

    třpytit se je vyjmenované slovo a píšeme ho s tvrdým Y :D

  2. koubel - 13. 12. 2007 10:43

    Opraveno, já jsem na tom s tou češtinou dost bídně, o AJ už ani nemluvě, ach jo.

  3. paranoiq - 30. 1. 2008 20:24

    au, to bolí:
    zvyklí = zvyklý (podle vzoru mladý)

  4. koubel - 31. 1. 2008 07:49

    Díky, ja sem prostě dobra češtin, opraveno

Komentáře jsou uzavřeny.