Problém s Nette\Object – ukradené protected proměnné
- honza martinek
- Člen | 22
Narazil jsem na nepříjemný problém s Nette\Object (testováno
s aktuální revizí 0.9, ale našel jsem to s nějakou 0.8 revizí, takže
je to asi všude), kdy si potomci stejné třídy (která je sama potomkem
Nette\Object) čtou vzájemně protected atributy, tj. není volán getter, ale
je přímo vrácena hodnota proměnné. (V kódu používám název proměnné
protected
pro větší přehlednost.) Díval jsem se na
ObjectMixin::get
, ale zrovna teď nemám dost času, abych
prokoumal jestli to jde napravit.
Přijde mi divné, že jsem tu o tom nenašel žádnou zmínku; vážně jsem se snažil :-)
Příklad:
<?php
//základní třída od které budu dědit
abstract class AbstractClass extends Object {
protected $protected = 'a';
public function getProtected() {
return 'b';
}
}
//shodný potomek
class X extends AbstractClass { }
//kontrolní třída dědící přímo od Object se shodnou implementací
class Y extends Object {
protected $protected = 'a';
public function getProtected() {
return 'b';
}
}
/* "zlodějská" testovací třída dědící z AbstractClass -
* volání protected na třídě X vrací hodnotu
* protected proměnné, zatímco v kontrolní třídě
* je správně volán getter
*/
class Z extends AbstractClass {
public function output() {
$x = new X;
echo $x->protected; //a
$y = new Y;
echo $y->protected; //b
}
}
/* kontrolní třída - pokud metody volám z jiné třídy,
* je vše OK
*/
class Q extends Object {
public function output() {
$x = new X;
echo $x->protected; //b
$y = new Y;
echo $y->protected; //b
}
}
//kód pro volání testu
...
$z = new Z;
$z->output(); //ab
$q = new Q;
$q->output(); //bb
...
?>
- honza martinek
- Člen | 22
Um, tak jsem to ještě zkusil bez dědění z Object a čisté PHP k tomu protected atributu přistoupí taky. Pro mě je to tedy jemně neintuitivní, ale asi je to tak správně.
Takže asi vyřešeno. (Nechávám to tu, protože mi to přijde divné, ale pokud je to zřejmé, klidně mě někdo smázněte.)
EDIT: Tak je to už staré a prý i běžné. Further reading: Surprising change in PHP’s protected visibility between 5.1 and 5.2
Editoval honza martinek (26. 8. 2009 13:34)
- jasir
- Člen | 746
Tak já se přiznám, že pro mě je to tedy novinka. Tady je tvůj kód, akorát jsem přejmenoval ty a/b, mátlo mě to. Kdyby si to někdo chtěl také vyzkoušet.
<?php
require_once dirname(__FILE__) . '/../../../libs/nette/loader.php';
//základní třída od které budu dědit
abstract class AbstractClass extends Object {
protected $protected = 'AbstractClass::$protected';
public function getProtected() {
return 'AbstractClass::getProtected()';
}
}
//shodný potomek
class X extends AbstractClass { }
//kontrolní třída dědící přímo od Object se shodnou implementací
class Y extends Object {
protected $protected = 'Y::$protected';
public function getProtected() {
return 'Y::getProtected()';
}
}
/* "zlodějská" testovací třída dědící z AbstractClass -
* volání protected na třídě X vrací hodnotu
* protected proměnné, zatímco v kontrolní třídě
* je správně volán getter
*/
class Z extends AbstractClass {
public function output() {
$x = new X;
echo 'Z::output()'."\n";
echo $x->protected."\n";;
$y = new Y;
echo $y->protected."\n";; //b
}
}
/* kontrolní třída - pokud metody volám z jiné třídy,
* je vše OK
*/
class Q extends Object {
public function output() {
$x = new X;
echo 'Q::output()'."\n";
echo $x->protected ."\n"; //b
$y = new Y;
echo $y->protected . "\n"; //b
}
}
$z = new Z;
$z->output(); //ab
$q = new Q;
$q->output(); //bb
?>
Výsledek je:
Z::output()
AbstractClass::$protected
Y::getProtected()
Q::output()
AbstractClass::getProtected()
Y::getProtected()
Jinak moje interpretace:
<?php
class Base { protected $prot='tajne'; }
class A extends Base {}
class B extends Base {
function test() {
$a=new A;
echo $a->prot;
}
}
$b = new B();
$b->test(); //vypíše 'tajne', wtf?
?>
pokud ale:
<?php
class Base { protected $prot='tajne v Base'; }
class A extends Base {protected $prot='tajne v A';}
class B extends Base {
function test() {
$a=new A;
echo $a->prot;
}
}
$b = new B();
$b->test(); //uff, na A::$prot v A už se nedostanem, vyhodí error o nepovolení přístupu
?>
Editoval jasir (26. 8. 2009 13:38)
- LM
- Člen | 206
Tohle je správné chování a nemá to nic společného s
Nette\Object
, objekt může přistupovat k protected
členům jiné instance stejné třídy (nebo potomka).
Zjednodušený příklad:
class Foo
{
protected $bar = 'bar';
public function printBar(Foo $foo)
{
var_dump($foo->bar); // přistupuje k protected členu jiné instance
}
}
$foo = new Foo;
$foo->printBar(new Foo); // string(3) "bar"
- honza martinek
- Člen | 22
LM: jj, už jsem to líp prozkoumal a objevil, kde je problém. Nechávám to tu, protože jsem o tom vůbec nevěděl a možná se to někomu může hodit. Kdyby se to vyskytl moderátor, prosím o přejmenování vlákna. Díky.
- David Grudl
- Nette Core | 8228
Chování je v pořádku, private členy jsou chráněny pro konkrétní třídu, protected i pro její potomky. Potomek tak může protected zcela ovládat a vytvořit k němu veřejný přístup (lze prvek doslova „přetypovat“ na public). Jinými slovy, protected je téměř něco jako public. Proto jsou konvence v některých knihovnách prefixovat protected členy podtržítkem nesmyslené a zavádějící.
- Foowie
- Člen | 269
Heh … tak v javě funguje i toto:
class Abstract {} // rodič
class A extends Abstract { // Potomek
protected String prot = "A:prot"; // Obsahuje svoji protected proměnnou
}
class B extends Abstract { // Potomek
public void echo() {
A a = new A();
System.out.println(a.prot); // Přistupuje k proměnné od sourozence :))
}
}
...
public static void main(String[] args) {
B b = new B();
b.echo();
}