Na navigaci | Klávesové zkratky

Translate to English… Ins Deutsche übersetzen…

PHP4: zbavme se ampersandů II.

Nedávno jsem naznačoval způsob, jak v PHP4 předávat objekty bez všudypřítomných ampersandů. Nechci se opakovat a vysvětlovat, k čemu je to dobré – to si prosím nastudujte jinde.

Podařilo se mi najít nové a zcela originální řešení. Má i pár záporů, ty proberu níže. Nejprve se podívejte na tento kód:

class MyClass {
    var $parent;
    var $children = array();

    // všimněte si pětkového konstruktoru
    function __construct($parent = null)
    {
        if ($parent !== null) {
            $this->parent = $parent;
            $parent->addChild($this);
        }
    }


    function addChild($child)
    {
        $this->children[] = $child;
    }
}


$parent = new MyClass();
$child1 = new MyClass($parent);
$child2 = new MyClass($parent);

V PHP5 dle očekávání vytvoří takovouto hierarchii:

V PHP4 k tomu nedojde. Především kvůli absenci ampersandů, ale také kvůli pojmenování konstruktoru. Proto použijeme Trik.

Realizace triku

Ještě než jej popíši, ukáži, jak velmi jednoduše se implementuje. Stačí jen pozměnit definici třídy:

class MyClass extends compatClass4 { ... }

Tedy stačí, aby každý objekt vycházel ze třídy compatClass4. A v ní je ukrytá veškerá magie.

V okamžiku, kdy přejdu na PHP5, tak z kódu ostraním ‚extends compatClass4‘. Nebo nadefinuji zcela prázdnou třídu tohoto jména. Zde prezentované řešení funguje tak, že podle verze PHP buď vytvoří onu magickou třídu s Trikem, nebo třídu prázdnou.

Princip triku

Využívá se následující vlastnost PHP:

// všimněte si, že nejde o předávání referencí
function changeFirst($arr) {
    $arr[0] = 'CHANGED';
}

// následující příkaz nezmění pole
$arr = array('first', 'second');
changeFirst($arr);
// $arr = array('first', 'second')


// ale nyní už ano:
$arr = array('first', 'second');
$x = 'test';
$arr[0] = &$x;
changeFirst($arr);
// $arr = array('CHANGED', 'second')


// a logicky poté i
$arr = array('first', 'second');
$x = & $arr[0];
changeFirst($arr);
// $arr = array('CHANGED', 'second')


// a stejně to funguje,
// když array v příkladech zaměníte za object

Tak pojďme rovnou ke třídě compatClass4. Ta zjednodušeně vypadá takto:

class compatClass4 {


    // nový PHP5 konstruktor
    function __construct()
    {
    }


    // starý PHP4 konstruktor
    function compatClass4()
    {
        // magie řešení - vygeneruj reference
        foreach ($this as $key => $foo)
            $this->__HIDDEN__[] = & $this->$key;

        // volání PHP5 konstruktoru
        $args = func_get_args();
        call_user_func_array(array(&$this, '__construct'), $args);
    }

}

Kód v plné polní je k dispozici ke stažení. Je také doplněn o funkci klonování.

Princip triku spočívá v tom, že ačkoliv dochází ke kopírování objektů, proměnné uvnitř se předávají referencí. Aby se takto PHP chovalo, je třeba ke každé proměnné alespoň jednu referenci vytvořit. No a poté je zavolán tzv. pětkový konstruktor.

Klonování

Taktéž se emuluje klonování, včetně kompatibilní syntaxe:

....
$parent = new MyClass();
$dolly = clone ($parent);
$child1 = new MyClass($parent);
$child2 = new MyClass($dolly);
....

V PHP5 existuje klíčové slovo clone, zatímco PHP4 používá funkci clone() – proto je třeba používat závorky.

Klonovací funkce vypadá takto:

function clone($obj)
{
    // pole referencí bude znovu vytvořeno
    unset($obj->__HIDDEN__);
    foreach ($obj as $key => $value) {

        $obj->$key = & $value;
        $obj->__HIDDEN__[] = & $value;
        unset($value);
    }

    // případné volání metody __clone
    if (is_callable(array(&$obj, '__clone')))
        $obj->__clone();

    return $obj;
}

Výhody řešení:

Není nutné psát ampersand při

  • vytváření objektu $object = &new MyClass()
  • přiřazení objektu $objectB = & $objectA
  • předávání objektu funkci function doIt(& $object)
  • vracení objektu funkcí function &getInstance()
  • a volání takové funkce $object = &getInstance()
  • u callbacku array(&$this, 'method')

A je možné používat pětkové konstruktory __construct().

Nevýhody řešení:

  1. nelze za běhu vytvářet nebo rušit nové proměnné objektů
  2. nelze do přomenných objektů přiřazovat reference
  3. skript je paměťově náročnější a mírně pomalejší

ad 1: Lze tedy u objektů používat jen ty proměnné ($objekt->promenna), které jsou deklarované ve třídě (var $promenna). Případně i ty, které jsou vytvořené za běhu v konstruktoru. Z pohledu zkušenějších programátorů to vlastně nedostatek ani není. Každý proměnná by totiž měla být deklarovaná a vhodně okomentovaná.

ad 2: Tedy nelze přiřadit referenci $objekt->promenna = & $jinapromenna. V případě potřeby lze nedostatek obejít takto: $objekt->promenna = array( & $jinapromenna ).

ad 3: Je to logický důsledek, že…

Jde o celkem čerstvý nápad a ještě jsem jej nestačil pořádně odzkoušet, ale myslím, že by to vůbec nemuselo být špatné :-)

Komentáře

  1. Vaclav #1

    A proc se vlastne zatezovat psanim kodu pro zastarale PHP4, kdyz uz je davno k dispozici petka a je i rozsirena mezi komercnimi webhostingy?

    před 11 lety | reagoval [2] David Grudl
  2. David Grudl http://davidgrudl.com #2

    avatar

    #1 Vaclave, kdyby rozšířena byla, tak takové věci neřeším.

    před 11 lety
  3. Llaik http://llaik.blogspot.com #3

    treba i proto, ze tva aplikace je chte nechte ve ctyrce napsana a ty ji potrebujes jen mirne rozsirit (tj. rovnou i refaktorizovat)?

    Nemluve o tom, ze zase tak moc rozsirena neni.

    před 11 lety
  4. Vaclav #4

    Mam hafo aplikaci psane pod ctyrkou (i objekty) a chodi bez problemu i na petce. Nejsou to ale buhvijake projekty o milionech radku, takze je mozne, ze nekomu to problemy delat muze.

    Nicmene dovolim si tvrdit, ze PHP5 uz rozsirene je a pokud ho tvuj webhosting nepodporuje a nechce nabidnout, je mozna cas poohlednout se po jinem. Muj hosting nabizi napr. volbu mezi PHP4 a PHP5 a myslim, ze neni ojedinely.

    před 11 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.