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í:
- nelze za běhu vytvářet nebo rušit nové proměnné objektů
- nelze do přomenných objektů přiřazovat reference
- 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
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?
David Grudl #2
#1 Vaclave, kdyby rozšířena byla, tak takové věci neřeším.
Llaik #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.
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.
Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.