Problém s Nette\Object – ukradené protected proměnné

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
honza martinek
Člen | 22
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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.

jasir
Člen | 746
+
0
-

Jojo, člověk se furt učí. Tohle jsem fakt nevěděl. Chová se tak i Java a podobné OOP jazyky?

David Grudl
Nette Core | 8228
+
0
-

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
+
0
-

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();
}