Prośba o wyjaśnienie działania kodu - użycie static w kontekście niestatycznym

0

Przykład wzięty z manual'a PHP.

class A {
    private function foo() {
        echo "success!\n";
    }
    public function test() {
        $this->foo();
        static::foo();
    }
}

class B extends A {
   /* foo() will be copied to B, hence its scope will still be A and
    * the call be successful */
}

class C extends A {
    private function foo() {
        /* original method is replaced; the scope of the new one is C */
    }
}

$b = new B();
$b->test();
$c = new C();
$c->test();   //fails

Rezultat :

success!
success!
success!
Fatal error: Call to private method C::foo() from context 'A' in /tmp/test.php on line 9

Nie rozumiem tego : "foo() will be copied to B, hence its scope will still be A and the call be successfu". W jakim sensie skopiowane? A jeśli skopiowane to dlaczego zakresem foo nadal jest A ? Czy jest ktoś w stanie to szczegółowo wyjaśnić?
https://www.php.net/manual/en/language.oop5.late-static-bindings.php

1
class A {
    private function foo() {
        echo "success!\n";
    }
    public function test() {
        $this->foo();
        static::foo(); // (1)
    }
}
class B extends A {
   /* foo() will be copied to B, hence its scope will still be A and
    * the call be successful */
}
class C extends A {
    private function foo() { // (2)
        /* original method is replaced; the scope of the new one is C */
    }
}
$b = new B();
$b->test();
$c = new C();
$c->test();   //fails

Odpaliłeś metodę foo z kontekstu A.
Odpalasz C::foo() z A::test().

Napisane jest, że klasa C usuwa metodę foo() z klasy A. Dalej wywołuje foo() z kontekstu A, natomiast tam tej funkcji już nie ma. Nadpisując metodę z klasy A, tak to działa, że static pójdzie do klasy korzennej extends A.

Jesteś w zakresie A.

Zasięg A {
// jestem tutaj i tu jest koniec mojego zasięgu, próbuję odpalić funkcję foo stąd, lecz nie ma sygnału
}

Zasięg C {
function foo() {

}
}

3

Straszne to są hacki, jak dla mnie mógłbyś się tego w ogóle nie uczyć i nie używać.

Takie calle nie są niczym dobrym i jedynie mogą Ci zaciemnić kod.

0

Nadal nie rozumiem czemu $b->test() jest wywoływane bez błędu. Przecież static::foo() zamienia się na wywołanie foo z zakresu B, pierwsza sprawa, że w B nie ma takiej metody, nawet jakby została "skopiowana" to nadal ma modyfikator private. Więc jakim cudem?

0
Mostek87 napisał(a):

Zgadzam się. Używać nie planuję, a uczę się tylko z ciekawości i w celu dogłębnego zrozumienia języka. Inna sprawa, że chcąc nie chcąc możemy mieć do czynienia z takim kodem napisanym przez kogoś innego....

Okej, no to skoro tak :

Odpal sobie ten kod (zmieniłem widocznośc funkcji foo() w C na public):

<?php
class A {
    public function test()
    {
        static::foo();
    }

    private function foo()
    {
        echo "calling A:foo()\n";
    }
}

class B extends A {}

class C extends A {
    public function foo()
    {
        echo "calling C:foo()\n";
    }
}

(new A())->test();
(new B())->test();
(new C())->test();

Zobaczysz:

calling A:foo()
calling A:foo()
calling C:foo()

I teraz powiedz mi, czemu jak zmienię foo() w C na private przykładzie to dostanę "access error".

0

Czy znajdzie się jakaś miła osoba i będzie w stanie wyjaśnić dlaczego w klasie B nie wywala błędu?

1
Mostek87 napisał(a):

Czy znajdzie się jakaś miła osoba i będzie w stanie wyjaśnić dlaczego w klasie B nie wywala błędu?

No zależy czy pytasz o powód taki prawdziwy, ludzki, argumentacyjny; czy o wyjaśnienie czemu tak działa ten element języka?

Jeśli pytasz o powódw ludzki: To to działa dlatego że ludzie którzy projektują PHP nie przemyśleli tego, w efekcie czego język jest pełen dziur i jest bardzo słaby.

Jeśli chodzi o wyjaśnienie elementu języka; to różnica polega na innym działaniu $this->foo() vs static:foo(). Kiedy wołasz $this->foo(), to to działa tak samo niezależnie z jakiej instancji (A, B czy C strzelasz).

Natomiast static używa dokładnej klasy aktualnej instancji do wybrania metody.

Kiedy wołasz (new C())->test();, a w klasie A masz takie coś:

class A {
    public function test() {
        static::foo();
    }
}

to to jest dokładnie to samo co jakbyś zrobił

class A {
    public function test() {
        C::foo(); // zakładając że aktualną instancją jest C
    }
}

I jak odpalisz C:foo() z wewnątrz A, kiedy foo w C jest public, to wszystko działa. Jednak kiedy zmienisz foo w C na private to wtedy A się próbuje dobić do prywatnej metody w C i to nie śmiga.

Natomiast odpowiadając na pytanie, czemu z B to działa, bo jak odpalisz (new B())->test(), to to jest to samo co jakbyś zrobił

class A {
    public function test() {
        B::foo(); // zakładając że aktualną instancją jest B
    }
}

Tylko że z kontekstu A masz wjazd do prywatnej metody foo() w B, dlatego że to prywatne foo() tak na prawdę należy do A, nie do B.

Żeby z kolei zrozumieć ten argument, to musiałbyś rozważyć inny przypadek. To już jest zachowanie występujące w każdym języku, nie tylko w PHP. Zobacz na kod niżej:

class A {
  private $secret;
  function __construct(string $secret) {
    $this->secret = $secret;
  }
}

$tajemnica = new A('my secret'); // czy tej zmiennej już nie można odczytać?
$tajemnica->secret; // brak wjazdu

Niespodzianka

class A {
  private $secret;
  function __construct(string $secret) {
    $this->secret = $secret;
  }

  public function hackuj(A $ofiara) {
    echo "Jego tajny sekret to " . $ofiara->secret;
  }
}

$atakujący = new A('');
$atakujący->hackuj($tajemnica);

to samo da się oczywiście zrobić, z dziedziczeniem - nie ma powodu czemu dziedziczenie z B miałoby temu zapobiec (tak długo jak nie nadpiszesz pola w B).

class B extends A{}

$atakujący = new B('');
$atakujący->hackuj($tajemnica);

Czyli w skrócie. Dwie instancje tej samej klasy mają dostęp do swoich prywatnych zmiennych. Oraz, dodatkowo, dwie instancje mające tego samego parenta (np new B() oraz new A()) również mają dostęp do swoich prywatnych pól i metod.

Mówiąc krócej, kiedy z A robisz static::foo(), to zarówno new A() jak i new B() będzie strzelało do metody A::foo(). Kiedy nadpiszesz foo() w C, to static:foo() strzela do "aktualnej" klasy czyli do C:foo() - to już jest inna metoda z innym poziomem dostępu, do którego A już nie ma wjazdu (mimo że ma taką samą nazwę).

Jeszcze innymi słowy, static::foo() działa tak jak ($aktualnaClasa)::foo(). Jeśli nie nadpisujesz nigdzie funkcji, to $aktualnaClasa to zawsze jest A, jeśli ją nadpiszesz np w C to $aktualnaClasa to jest C. Z A do A możesz się dobić, z A do C już nie. Wołając B:foo(), tak na prawdę wołasz A:foo(), czyli możesz z B się dobić do A.

Podsumowując

Nie używaj static. To zło.

0

@TomRiddle: Dzięki, że miałeś czas i ochotę na szczegółowe rozpisanie się co do tego zagadnienia. Tu chyba jeden szczegół mi brakuje.

Dwie instancje tej samej klasy mają dostęp do swoich prywatnych zmiennych. Oraz, dodatkowo, dwie instancje mające tego samego parenta (np new B() oraz new A()) również mają dostęp do swoich prywatnych pól i metod.

Po pierwsze jak rozumiem należałoby dopisać, że owszem mają dostęp, ale tylko jeżeli są odczytywane w odpowiednim kontekście. Metoda "hackuj" działa gdy jest wywoływana przez instancję klasy B ponieważ owa metoda jest zdefiniowana w klasie A. Gdyby "hackuj" była zdefiniowana w B to już byłby błąd.

Ponadto nawiązując do mego przykładu podanego całkiem na początku można orzec, iż : Klasa B dziedzicząca z klasy A ma dostęp do prywatnych pól w klasie A ale tylko jeżeli wykonywany kod jest zdefiniowany w klasie A. Można by powiedzieć, że to takie leksykalne podejście.

1
Mostek87 napisał(a):

@TomRiddle: Dzięki, że miałeś czas i ochotę na szczegółowe rozpisanie się co do tego zagadnienia. Tu chyba jeden szczegół mi brakuje.

Dwie instancje tej samej klasy mają dostęp do swoich prywatnych zmiennych. Oraz, dodatkowo, dwie instancje mające tego samego parenta (np new B() oraz new A()) również mają dostęp do swoich prywatnych pól i metod.

Po pierwsze jak rozumiem należałoby dopisać, że owszem mają dostęp, ale tylko jeżeli są odczytywane w odpowiednim kontekście. Metoda "hackuj" działa gdy jest wywoływana przez instancję klasy B ponieważ owa metoda jest zdefiniowana w klasie A. Gdyby "hackuj" była zdefiniowana w B to już byłby błąd.

No tak; ale wtedy nie była by w parencie; ergo nie byłaby wspólna dla dwóch klas, ergo byłaby normalną metodą która normalnie respektuje enkapsulację.

Ponadto nawiązując do mego przykładu podanego całkiem na początku można orzec, iż : Klasa B dziedzicząca z klasy A ma dostęp do prywatnych pól w klasie A ale tylko jeżeli wykonywany kod jest zdefiniowany w klasie A. Można by powiedzieć, że to takie leksykalne podejście.

Nooo, trochę tak, trochę nie. Klasa B dziedzicząca z klasy A ma dostęp do prywatnych pól w klasie A ale tylko jeżeli to tak na prawdę nie klasa B ma tutaj dostęp, tylko klasa A. Instancja klasy B nadal jest instancją klasy A. Instancja klasy B to nie to samo co klasa B.

Relacje pomiędzy instancjami dwóch klas x i y, to nie sa te same relacje co pomiędzy klasami x i y.

1

@TomRiddle:

instancja klasy B nadal jest instancją klasy A

Czyli z tego wynika, że instancja klasy B jest jednocześnie instancją klasy A i B.

1
Mostek87 napisał(a):

@TomRiddle:

instancja klasy B nadal jest instancją klasy A

Czyli z tego wynika, że instancja klasy B jest jednocześnie instancją klasy A i B.

Tak i nie. To pytanie ktore zadałeś jest nieprecyzyjne, więc nie na ma nie jednoznacznej odpowiedzi.

Czy można jej użyć tam gdzie klasy A (a'la Liscov Substitution Principle)? Tak.

Czy get_class($b) === A:class? Nie, bo to będzie B:class.

0

@TomRiddle: Faktycznie, nieprecyzyjnie się wyraziłem. Bardziej chodzi mi o takie abstrakcyjne podejście : wyjaśniam sobie to tak. Klasa A to zwierzę a klasa B to kot. (oczywiście kot dziedziczy ze zwierzę) Każdy kot to jednocześnie kot i zwierzę. Jednak zwierzę nie jest dokładnie tym samym co kot stąd właśnie Twa ostatnia linijka w poprzednim poście.

1
Mostek87 napisał(a):

@TomRiddle: Faktycznie, nieprecyzyjnie się wyraziłem. Bardziej chodzi mi o takie abstrakcyjne podejście : wyjaśniam sobie to tak. Klasa A to zwierzę a klasa B to kot.

Bzdura.

Wszystkie takie "odniesienia" do prawdziwego świata nie ma nic związanego z programowaniem.

Każdy kot to jednocześnie kot i zwierzę. Jednak zwierzę nie jest dokładnie tym samym co kot stąd właśnie Twa ostatnia linijka w poprzednim poście.

Dziedziczenie to po prostu zabieg w programowaniu który dodaje interfejs, pola i metody z nad-scope do pod-scope, tyle. Takie pojęcia jak class Kot jest class Animal to tylko jakieś wyobrażenia, które nie mają odniesienia w programowaniu.

Nie korzystaj z dziedziczenia; nic to raczej nie da. Używaj raczej kompozycji i domknięć; i skup się na jakiejś innej dziedzinie, np na TDD. To że istnieje taki feature w języku, jak extends to nie oznacza wcale że to jest użyteczne narzędzie.

0

@TomRiddle:

Dziedziczenie to po prostu zabieg w programowaniu który dodaje interfejs, pola i metody z nad-scope do pod-scope, tyle

Czyli można powiedzieć, że po prostu przenosi lub kopiuje wszystko to co jest w A do B, ale między innymi z takim zastrzeżeniem, że te metody prywatne mogą być uruchamiane tylko w kontekście A.

1
Mostek87 napisał(a):

@TomRiddle:

Dziedziczenie to po prostu zabieg w programowaniu który dodaje interfejs, pola i metody z nad-scope do pod-scope, tyle

Czyli można powiedzieć, że po prostu przenosi lub kopiuje wszystko to co jest w A do B, ale między innymi z takim zastrzeżeniem, że te metody prywatne mogą być uruchamiane tylko w kontekście A.

No możesz sobie tak to tłumaczyć, chociaż nie do końca, bo istnieją takie króczki właśnie jak static::, albo to że jak zrobisz B extends A, to tam gdzie jest type-hint na A, można użyć B (LSP).

Ale to i tak jest bliżej prawdy niż takie wmawianie sobie że "A jest B".

0

@TomRiddle: Pomimo, iż zapewne sam problem opisany na początku zapewne rzadko kiedy będzie się pojawiał to i tak uważam, że dzięki tej dyskusji nauczyłem się czegoś pożytecznego. Dzięki.

1 użytkowników online, w tym zalogowanych: 0, gości: 1