При использовании в абстрактном классе трейт работает некорректно.
Рассмотрим следующий код:
abstract class C {
static $class;
use T;
}
trait T {
static $mockClass;
public function __construct() {
static::$mockClass = static::$class;
}
}
class A extends C{
static $class = 'A1';
}
class B extends C{
static $class = 'B1';
}
$a = new A();
$b = new B();
dump($a::$mockClass); // B1, should be A1
dump($b::$mockClass); // B1, should be B1
Вы видите, что при сбросе mockClass в обоих случаях присутствует B1. Почему? Когда я не исхожу из C и использую черту напрямую, я ожидал результата.
Я не могу найти никакой информации о конкретном использовании трейтов в абстрактных классах. Может ли кто-нибудь объяснить мне, почему мой код не работает так, как я ожидал?
@NigelRen Я не понял. Что ты имеешь в виду?
@FreeLightman, ты видел мой ответ? Объясняет ли это вам, почему ваш код не работает так, как вы ожидали?
@JacekDziurdzikowski частично. Я понял это до того, как вы ответили. Но это похоже на ошибку в php. Если бы я хотел, чтобы $mockClass был привязан к C, я бы использовал self, который предназначен для этого. Но у меня нет возможности присвоить значение унаследованной статической опоре в A, потому что она относится к C, поэтому просто дублирует self. Один из способов решить эту проблему - определить статическую опору в унаследованных классах A и B. Но у меня есть родительский класс, который должен делать это за меня.
Да, я был весьма удивлен, когда понял, что (читайте об этом в книге Мэтта Зандстры «Php - объекты, шаблоны и практика»), и в примерах, которые пришли с этим знанием, были примеры, всегда повторно объявляющие переменные / функции, которые должны быть используется на основе ключевого слова static. Поскольку я не уверен в аналогичном поведении на других языках, трудно обсуждать различные возможные реализации этих функциональных возможностей, также я думаю, в конце концов, это не ошибка, а просто следствие того, как разработан язык.
Также очень характерно, что наше видение того, как должна работать конкретная функциональность языка, отличается от того, как он работает на самом деле :) Поразмыслив, я понял, что это не может быть нежелательным поведением, потому что если вы используете ключевое слово self вместо static вы явно лишаете наследующие классы возможности использовать эту функциональность, но если вы используете static, вы даете им возможность использовать его. Итак, теперь вы видите, что это определенно не ошибка;)
@JacekDziurdzikowski Вы видите, что static не работает должным образом. Так о какой необязательности вы говорите? self иногда бывает хорош. Пару раз пользовался. Но статика мне кажется немного глючной. Мой пример тому пример.
Нет, я только что сделал два больших комментария, чтобы объяснить свое мнение по этому поводу. Я определенно не говорю, что статика ошибочна - она просто отличается от того, что вы хотели бы видеть.






Как упоминалось в комментарии, это больше связано со свойствами static. Статические свойства являются общими для всех экземпляров класса (вот почему вы не можете использовать $this для ссылки на них). Поскольку все ваши классы расширяют C, все они будут иметь одно и то же значение $mockClass. Вы обнаружите, что все экземпляры будут иметь последнее значение, присвоенное $mockClass, если вы поменяли местами создание двух объектов $a и $b, оба будут отображать A1.
Рабочая версия, использующая обычные свойства ...
trait T {
public $mockClass;
public function __construct() {
$this->mockClass = static::$class;
}
}
abstract class C {
static $class;
use T;
}
class A extends C{
static $class = 'A1';
}
class B extends C{
static $class = 'B1';
}
$a = new A();
$b = new B();
print_r($a->mockClass); // A1, should be A1
print_r($b->mockClass); // B1, should be B1
Я хотел бы добавить, что наличие конструктора в трейте обязательно вызовет проблемы в какой-то момент, поэтому не лучший вариант для начала.
Обновлять:
Как уже упоминалось, все ваши классы расширяют C, который использует черту T, если вы переместили use T; в производные классы A и B ...
abstract class C {
static $class;
}
trait T {
static $mockClass;
public function __construct() {
static::$mockClass = static::$class;
}
}
class A extends C{
static $class = 'A1';
use T;
}
class B extends C{
static $class = 'B1';
use T;
}
$a = new A();
$b = new B();
print_r($a::$mockClass); // A1, should be A1
print_r($b::$mockClass); // B1, should be B1
По-прежнему не думаю, что это хорошее использование черт, но я оставлю это на ваше усмотрение.
Я знаю о статических свойствах. Вы полностью изменили дизайн (заменили инстансные реквизиты инстансовыми). И не объяснил, почему мой способ не работает. Это как обходной путь. Но не ответ.
Извините - мысль, что «статические свойства являются общими для всех экземпляров класса», была ответом на вашу проблему. Поскольку все ваши классы расширяют C, все они будут иметь одно и то же значение $mockClass.
Собственно мне нужен вопрос, почему код не работает. И что сделать, чтобы он работал, находясь слишком близко к моему исходному коду. Если мой код недействителен или неверен - просто скажи. Но обходной путь - это окончательный вариант.
Что касается вашего редактирования. Я сказал, что использование черт напрямую решает проблему.
То, как у вас есть существующее, вообще не будет работать, я попытался объяснить, почему и как вы можете заставить его работать.
Причина, по которой ваш код не работает так, как вы ожидаете, заключается в следующем:
Во время создания экземпляра A: $a = new A();,
эта часть кода выполняется: static::$mockClass = static::$class;
где static::$class имеет значение 'A1' и, очевидно, происходит из класса A, НО
пока $mockClass не будет повторно объявлен в дочернем классе, он будет находиться в классе C.
Даже когда вы используете ключевое слово static::так как, эта конкретная часть кода static::$mockClass = static::$class;, отвечающая за создание экземпляра, выполняется в классе C.
Самая важная часть, которую вы, кажется, не понимаете: в PHP (я не уверен в других языках), когда некоторая часть кода определена в классе A, а класс B наследуется от класса A и имеет дополнительную часть кода - если вы выполнить любую часть кода на B, которая не была переопределена в B, но предназначена для A - вы можете представить, что код на самом деле выполняется в родительском классе A, откуда он исходит.
Поэтому, когда в следующий раз во время создания экземпляра B код static::$mockClass = static::$class; выполнится снова - static::$class теперь имеет значение 'B1', но переопределяет значение static::$class, которое все еще находится в C.
Это немного изменило ваш пример кода, чтобы вы поняли, что происходит (и он работает, как ожидалось):
abstract class C {
static $class;
use T;
}
trait T {
static $mockClass;
public function __construct() {
static::$mockClass = static::$class;
}
}
class A extends C{
static $class = 'A1';
static $mockClass;
}
class B extends C{
static $class = 'B1';
static $mockClass;
}
$a = new A();
$b = new B();
var_dump($a::$mockClass); // A1, should be A1
var_dump($b::$mockClass); // B1, should be B1
Вы также можете спросить, почему тогда static::$class не исходит от C и почему тогда он не является нулевым. Ответ очевиден: так устроен язык.
Думаю, это больше связано с использованием свойств
static, чем черт.