new self() vs. new static() and early/late static binding

Първо, нека да изсним терминът „binding“, що е то и има ли почва у нас.

Binding значи когато извикаш даден метод на обект, да е зададено кой код да се изпълни. Демек, зад дадаено име, параметри и връщана стойност (сигнъчъра), да се знае кой код да стои. Kaто под „кой код“ се има предвид адрес от паметта, от където започва въпросният код, като да не забравяме, че трябва да имаме и адрес в паметта, където да се върнем, след като сме изпълнили кода на даденият метод.

Binding е един вид „мостът“ между сигнъчърът (комплексно понятие от името, параметрите с техните типове, и разбира се – типът на връщаният резултат). Това е един вид уникалният идентификатор за даден метод, не в буквалният смисъл, а по-скоро по име, по параметри и техните типове, както и типът на връщаният резултат, изобщо сигнъчърът.

Как може да стане това?
Или динамично, или статично. В какъв смисъл? В смисъл, че кой точно код стои зад даденият сигнъчър може да се зададе compiletime (твърдо), или runtime, демек по време на изпълнеие.

На каква база?

На база сигнъчъра, демек че може методи с еднакви имена да имат различни параметри, или тип на връщан резултат. И при извикване на метод по име, да се реши кой точно код да се изпълни, на базата освен на името му, но и на гореспоменатите неща (сигнъчъра).

И тук идва самата идея на т.н. static и dynamic binding.

Static `binding /или още early или compiletime/ е когато още по време на компилация е ясно при извикване на кой „сигнъчър“, кой код да се изпълни. Твърдо. Това е т.н. overloading, тоест когато предварително зад кой сигнъчър, компилаторът знае кой код съответства. Дори и имената на методите да са еднакви.
Но както знаем overloading не се поддържа от PHP, и до колкото знам няма и тенденция да се поддържа.

Да разгледаме следният пример:

class GrandMother
{
    public static function getSelf(): self
    {
        return new self();
    }

    public static function getStatic(): self
    {
        return new static();
    }
}

class Mother extends GrandMother
{
}

class Child extends Mother
{
}

var_dump(GrandMother::getSelf());     // object(GrandMother)#1 (0) {}
var_dump(GrandMother::getStatic());   // object(GrandMother)#1 (0) {}

echo "\n\n";

var_dump(Mother::getSelf());          // object(GrandMother)#1 (0) {}
var_dump(Mother::getStatic());        // object(Mother)#1 (0) {}

echo "\n\n";

var_dump(Child::getSelf());           // object(GrandMother)#1 (0) {}
var_dump(Child::getStatic());         // object(Child)#1 (0) {}

Тоест, static() сочи къде методът е бил ИЗВИКАН, a self() – къде е ДЕФИНИРАН.

Ето още един пример:

class Car
{
    protected static $helloString = 'I am a car';

    public static function print(): void
    {
        echo self::$helloString;
    }
}

class FlyingCar extends Car
{
    protected static $helloString = 'I am a flying car';
}

FlyingCar::print();

Ще изведе ‘I am a car’, защото като нямаме print() за FlyingCar, PHP ще извика този на родителя – Car, и понеже имаме self, това значи „използвай този клас, защото аз, print(), съм дефиниран тук, а не в наследника“, ще използваме съответно и пропъртито $helloString на Car.
Независимо, че сме извикали print() през наследника. За self е важно къде сме го дефинирали, не от къде сме го извикали.

Но вече, ако беше echo static::$helloString; тогава вече ще вземем $helloString на класа, през който викаме print(). Затова ще изведе ‘I am a flying car’.

Затова първият пример се казва „Early static binding„, а вторият – „Late static binding„.

При early имаме задаване на стойностите на пропъртитата още „at compile time“, при late – „at execution time“.

Къде е тънкостта?
В принципната разлика между self и static.

Защо?
Защото self още при компилиране твърдо задава, че е binded (обвързано, сочи към) с класа, В КОЙТО Е ДЕФИНИРАН.
А static не се „обвързва“ твърдо със своя клас, при него динамично се решава с кой клас да се обвърже.

Защо хем static, хем late?
„Late binding“ comes from the fact that static:: will not be resolved using the class where the method is defined but it will rather be computed using runtime information. It was also called a „static binding“ as it can be used for (but is not limited to) static method calls.

Литература

https://www.educative.io/answers/what-are-early-binding-and-late-binding-functions-in-cpp