Класне питання підняв, тримай +1.
Добряче звивини довелося напружити, щоб знайти істину
Вся фішка у компіляторі.
Класи PHP в С зберігаються у списку.
Коли код компілюється цей список наповнюються класами які зустрічаються при компіляції.
Всі класи які зустрічаються компілюються у вже готовий opcode, але без traits, implements, extends.
Але є один нюанс якщо у класа є extends на клас який вже у списку є, код парента зразу вставиться у дочірній клас
Усі traits, implements, і extends які не попали у виняток розширюють клас вже під час виконання opcode
Саме тому даний код буде працювати:
<?php
namespace NameA;
class A extends \NameB\B {}
namespace NameC;
class C {}
namespace NameB;
class B extends \NameC\C {}
Класи \NameC\C, \NameB\B будуть повноціно скомпільовані (готові до використанням) станом на початок виконання скрипту. І тому при виконанні "A extends \NameB\B" віртуальна машина зможе вже нормально розширити клас А.
<?php
namespace NameA;
class A extends \NameB\B {}
namespace NameB;
class B extends \NameC\C {}
namespace NameC;
class C {}
Тут тільки клас \NameC\C будуть повноціно скомпільований станом на початок виконання скрипту. І тому при виконанні "A extends \NameB\B" віртуальна машина знатиме про клас \NameB\B, але також знатиме що він ще неготовий до використання (щоб він був готовий треба спочатку його розширити класом NameC\C, а про це вже віртуальна машина не знає бо це інструкція Opcode яка йде далі)