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.