Executia unei metode mostenite in contextul gresit in PHP

Salutare dragi colegi!
De curand am avut “placerea” de a constata ca in PHP executia unei metode mostenite se face tot in contextul parintelui, sau cel putin asa o inteleg eu.
In acest PasteBin: http://pastebin.com/yaAGXn4x veti gasit un exemplu de metoda definita intr-o clasa parinte si care incearca sa acceseze o variabila privata.
Daca executam codul putem observa ca desi codul este executat din contextul copilului codul acceseaza tot variabila din parinte.
In plus, comportamentul difera in cazul metodelor. Asadar, o metoda declarata private nu va fi accesibila in copil.
Mai mult de atat daca urmariti output-ul din var_dump($bObj) veti vedea ca acel obiect are doua variabile private numite $x si avand valori diferite.
Ma puteti ajuta sa inteleg ce imi scapa de face ca acest cod sa fie valabil in PHP?

O zi faina,
Denis

1 Like

Păi fix așa functionează viziblitatea în cadrul obiectelor din PHP. Folosește protected în clasa părinte, în loc de private, ca să poți accesa/pastra/suprascrie valoarea lui x.

2 Likes

buna! din pacate nu e posibil sa schimb vizibilitatea acelei proprietati si nu asta e rezolvarea. Cel putin nu pentru mine. :slight_smile: Am trecut prin acel link trimis de tine de cateva zeci de ori numai in ultimele zile de cand ma zbat sa inteleg de ce este acest comportament. Stiu despre felul in care PHP foloseste obiectele, am experienta in OOP, dar nu reusesc sa inteleg acest comportament.
Parerea mea e ca ar fi trebuit ca metoda, desi declarata in parinte, fiind folosita in contextul copilului sa foloseasca proprietatile copilului si nu a parintelui.
Pe langa PHP am observat ca si in C++ este acelasi comportament.
Pe mine ma intereseaza sa inteleg de ce este ceva normal si predictibil acest comportament. Ce imi scapa mie din rationament?

1 Like

Mie mi se pare foarte normal ce se întâmplă. Variabila fiind declarată private nu poate fi accesată din afara clasei (nici de părinte: Base::testBase(), nici de copil: B::test())

Cum a zis @redecs pentru ce vrei to există protected.

Presupun că e vorba de vreo libarir/pachet third party (de nu poți schimba visibilitatea) caz în care sunt șanse mari ca variabla respectiva să fie private din motive bine întemeiate. :slight_smile:

3 Likes

buna!
Revin cu aceeasi precizare. Problema mea este sa inteleg de ce acest comportament este “normal” din punctul vostru de vedere. Ce nu sunt interesat, sunt solutii alternative. Va rog sa nu intelegeti nimic altceva prin cele spuse anterior.
Stiu ca sunt alternative si probabil ca cei ce au scris acea librarie au avut cele mai bune motive sa faca acea proprietate private. Nu este scopul acestei discutii sa identifice acele motive sau solutii alternative. Din acest motiv am dat acest exemplu basic si nu codul exact unde am gasit aceasta problema.
Sunt strict interesat de parerea voastra ca programatori daca este un comportament corect. Iar daca sunteti de acord ca e corect sa argumentati. Vreau doar sa inteleg de ce mi nu mi se pare ca e corect, doar atat.
Sunt le fel de interesat si daca aveti experienta cu alte limbaje de programare OOP unde acest comportament este diferit sau la fel.

@Dan, zici corect. O variabila fiind privata nu poate fi accesibila. Asta e definitia vizibilitatii ca private. Insa, daca observi codul este executat in contexul unui alt obiect insa proprietatea folosita e cea a parintelui. Tocmai de asta mi se pare ca acest principiu de protectie a vizibilitatii nu este respectat. Si ma intereseaza sa inteleg de ce. E ceva dorit sau un bug scapat?

Eu pe codul tau la metoda testBase rulata din bObj primesc eroare de vizibilitate pentru propritatea x din BClass, care este mostenitor. Nu asa ar fi trebuit sa fie?

Se dă exemplul:


Dacă dai dump pe obiect-ul B, var_dump îți arată că există și proprietatea $x de pe obiectul părinte A (var_dump este o funcție de debugging până la urmă). Dacă încerci să folosești $x, nu poți pentru că el nu există în obiectul B.
Deci nu este vorba de un bug, cred că pe @denisrendler l-a indus un pic în eroare output-ul dat de var_dump.

buna, colegilor!

First, @redecs: Nu m-a indus in eroare output-ul pentru ca stiu sa folosesc si sa citesc var_dump() si output-ul din xdebug. Ce ma intriga pe mine la acest output era faptul ca la un dump al unui child aveai vizibilitate la o proprietate privata a parintelui. Si mai ciudat era ca nu exista name-collesion avand in vedere ca ambele aveau acelasi nume. In al doilea rand, daca te uiti pe exemplul meu, definitia metodei se afla in clasa parinte si nu in child, dar inteleg point-ul la care ai vrut sa ajungi.

Acesta este output-ul pe care-l primesc pe un PHP 5.6

string(30) "Aclass::test is ran as: Bclass"
class Bclass#1 (2) {
  private $x =>
  string(6) "Bclass"
  private $x =>
  string(6) "Aclass"
}
string(6) "Aclass"
===========
string(30) "Aclass::test is ran as: Aclass"
string(30) "Aclass::test is ran as: Bclass"
===========
string(34) "Base::testBase is ran from: Aclass"
PHP Fatal error:  Cannot access private property Aclass::$x in /var/www/c.php on line 9
PHP Stack trace:
PHP   1. {main}() /var/www/c.php:0
PHP   2. Base->testBase() /var/www/c.php:44

Second, @noah: Da, primesti eroarea respectiva! Intrebarea la care incercam sa gasesc un raspuns e de ce primesti eroare cand, si ma voi repeta, executia codul este in contextul in care variabila $x este prezenta si, cel putin teoretic, este accesibila.

Finally, as dori sa opresc aici aceast thread deoarece se pare ca ne invartim in jurul altor probleme si mai putin asupra a ceea ce ma intereseaza pe mine si scopul acestui post.

Va multumesc tuturor pentru raspunsuri, sugestii si idei. Daca voi reusi sa inteleg voi reveni cu detalii.

Nu ne lasa asa :smiley:

Daca vrei sa iei valoarea lui $x din Aclass in testBase pe considerentul ca ai o metoda publica care face return cred ca o faci gresit. Ar trebui apelata metoda in testBase si facut return acolo pentru ca testBase nu poate accesa $x fiind privat.

class Base
{
    public function testBase()
    {
        //var_dump(__METHOD__ . ' is ran from: ' . get_class($this));
        

        return $this->test();
    }
}
 
class Aclass extends Base
{
    private $x = __CLASS__;
 
    public function test()
    {
        //var_dump(__METHOD__ . ' is ran as: ' . get_class($this));
 
        return $this->x;
    }
}
 
class Bclass
    extends Aclass
{
    private $x = __CLASS__;
}
 
 
$bObj = new Bclass;
echo $bObj->testBase(); // Output: Aclass
echo '<pre>';
//var_dump($bObj, $bObj->test());
 
 
\Closure::bind(function() { return $this->test(); }, new Aclass(), $bObj)->__invoke();
//force a context change
\Closure::bind(function() { return $this->test(); }, $bObj, $bObj)->__invoke();
 
 echo '</pre>';
$aObj = new Aclass();
$aObj->testBase();
1 Like

Ai folosit get_class($this). $this va fi tot timpul obiectul copil (clasa care moștenește). Dacă ai folosi get_class() fără argumente ți-ar arăta contextul real. Am modificat puțin codul tău și rezultatele arată altfel (vezi la final în comentariu din cod): PHP inheritance - Pastebin.com

Știu că nu vrei soluții, dar există metode să accesezi variabile private (closure, reflections )

Există vreun limbaj un se întâmplă așa cu variabilele private? Cu protected merge (vezi exemplu meu de cod).

Confuzia ta cred că e cauzată de modul greșit în care ai încercat să evaluezi contextul. Mi se pare normal ca orice declari private să nu fie accesibil în afara clasei respective.

1 Like