Tento článek je překladem anglického originálu Reference vs Copying od Johana Perssona a vztahuje se k PHP 4. V PHP 5 se objekty vždy předávají referencí.
Toto je obvykle hlavním zdrojem chyb. I když si v PHP dokumentaci přečtete, že PHP 4 pro objekty používá kopírování místo referencí (jako většina ostatních OO jazyků), tak vás to nakonec stejně dostane nějakým nepředvídatelným způsobem.
Následující dvě sekce ukazují dva rafinované příklady, kde vás může kopírovací sémantika překvapit.
Důležitá věc, kterou musíte mít na paměti, je skutečnost, že proměnná s objektem není ukazatel na objekt, ale objekt sám. Většina problémů pramení z mylné představy, že přiřazovací operátor (=) vrátí alias objektu, přičemž ve skutečnosti vrátí novou kopii. Předpokládejme například, že $myObj
je instance nějakého objektu a že má metodu Set()
. Potom následující příklad nemusí udělat to, co by C++ (nebo Java) programátor očekával.
<?php function SomeFunction($aObj) { $aObj->Set(10); } SomeFunction($myObj); ?>
Nyní bychom se mohli domnívat, že metoda Set()
zavolaná ve funkci změní $myObj
. Špatně.
Ve skutečnosti se stane to, že $myObj
se zkopíruje do parametru $aObj
, který se stane identickou kopií původního objektu. Když je potom zavolaná metoda Set()
, tak ovlivní pouze místní kopii a neovlivní původní parametr $myObj
.
K obměnám uvedeného problému může dojít kdekoliv, kde dojde k explicitnímu nebo implicitnímu (jako v uvedeném příkladě) přiřazení.
Aby funkce dělala to, co pravděpodobně očekáváte, musíte PHP říct, aby objekt předalo referencí, a to změnou deklarace na function SomeFunction(&$myObj)
.
Pokud znovu vyzkoušíte uvedený kód, zjistíte, že metoda Set()
ovlivní původní parametr a to proto, že nyní jsme vytvořili alias na $myObj
pojmenovaný $aObj
.
Ale musíte dávat pozor, protože ani operátor & neřeší všechny problémy, jak bude uvedeno níže.
Řekněme, že máte následující kód:
<?php $myObject = new SomeClass(); $myRefToObject = &$myObject; ?>
Co uděláme, pokud z nějakého důvodu chceme kopii vytvořené reference? Protože $myRefToObject
už obsahuje referenci, někoho by lákalo napsat
<?php $myCopyRefToObject = $myRefToObject; ?>
Bude to fungovat? Ne! Sémantika PHP vytvoří novou kopii objektu, kterého je $myRefToObject
reference. Pokud chcete zkopírovat referenci objektu, musíte napsat
<?php $myCopyRefToObject = &$myRefToObject; ?>
Např. v C++ obdoba tohoto příkladu vytvoří odkaz na odkaz. Ale v PHP to takhle nefunguje. Pro zkušeného C++ programátora je takovéto chování velice neintuitivní a může zapříčinit těžko odhalitelné chyby v PHP programu.
Dejte si pozor na implicitní (předáváním parametrů) nebo explicitní problémy způsobené tímto chováním.
Nejlepší způsob, jak se vyvarovat těchto sémantických „vychytávek“ je vždy předávat a přiřazovat objekty referencí. Jednak to zvýší rychlost (kopíruje se méně dat) a jednak to učiní sémantiku mnohem předvídatelnější.
Obvyklý vzor v konstruktoru objektu je inicializace nově vytvořeného objektu jako observer nějakého jiného objektu. Dá se to udělat např. následujícím způsobem:
<?php class Battery { function Battery() { } function AddObserver($method, &$obj) { $this->obs[] = array($obj, $method); } } class Display { function Display(&$batt) { $batt->AddObserver("BatteryNotify", $this); } function BatteryNotify() { } } ?>
V PHP 4 to ale nebude fungovat, pokud objekt inicializujete jako
<?php $myBattery = new Battery(); $myDisplay = new Display($myBattery); ?>
Co je na tom špatně, je to, že volání new nevrátí stejný objekt jako ten, na který se odkazuje $this v konstruktoru. Místo toho se vrátí kopie nově vytvořeného objektu. Je to jiný objekt než původní objekt, který byl předán při volání AddObserver()
.
Když se třída Battery
pokusí upozornit všechny své posluchače (zavoláním jejich upozorňovacích metod), nezavolá to náš nově vytvořený objekt $myDisplay
, ale původní objekt označený jako $this. Takže když metoda BatteryNotify()
změní nějaké vlastnosti objektu, tak se změní původní instance a ne – jak bychom mohli předpokládat – naše instance (která je kopií) třídy Display
.
Aby to fungovalo, je potřeba PHP donutit, aby vracelo stejný objekt, který v konstruktoru reprezentovalo $this. To se zařídí přidání operátoru & při vytváření instance třídy Display
takhle:
<?php $myDisplay = &new Display($myBattery); ?>
Jako přímý důsledek této skutečnosti musí každý uživatel třídy Display
vědět o jejích implementačních detailech! Ve skutečnosti lze argumentovat tím, že by s „nadbytečným“ operátorem & měly být vytvářeny všechny objekty. Co mohu říct, tak je to vždy bezpečné, ale jeho vynechání může mít nežádoucí účinky, jak bylo předvedeno výše.
V JpGraphu je pro vyřešení tohoto problému zvolen jiný přístup. Místo používání modifikovaného operátoru new používá objekt, který se potřebuje zaregistrovat, dvoufázové vytvoření použitím další metody Init()
, která může bezpečně používat referenci na $this. (Pouze v konstruktoru se reference nechová tak, jak by se dalo předpokládat, protože new vrací kopii objektu.) Takže v uvedeném příkladě by to bylo implementováno takhle:
<?php $myBattery = new Battery(); $myDisplay = new Display(); $myDisplay->Init($myBattery); ?>
Pro příklad tohoto obratu se podívejte na třídu LinerScale
v jpgraph.php
.
Diskuse je zrušena z důvodu spamu.