Почему трейт не работает в абстрактном классе?

При использовании в абстрактном классе трейт работает некорректно.

Рассмотрим следующий код:

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 и использую черту напрямую, я ожидал результата.

Я не могу найти никакой информации о конкретном использовании трейтов в абстрактных классах. Может ли кто-нибудь объяснить мне, почему мой код не работает так, как я ожидал?

Думаю, это больше связано с использованием свойств static, чем черт.

Nigel Ren 11.03.2018 19:08

@NigelRen Я не понял. Что ты имеешь в виду?

FreeLightman 11.03.2018 19:22

@FreeLightman, ты видел мой ответ? Объясняет ли это вам, почему ваш код не работает так, как вы ожидали?

Jacek Dziurdzikowski 12.03.2018 14:45

@JacekDziurdzikowski частично. Я понял это до того, как вы ответили. Но это похоже на ошибку в php. Если бы я хотел, чтобы $mockClass был привязан к C, я бы использовал self, который предназначен для этого. Но у меня нет возможности присвоить значение унаследованной статической опоре в A, потому что она относится к C, поэтому просто дублирует self. Один из способов решить эту проблему - определить статическую опору в унаследованных классах A и B. Но у меня есть родительский класс, который должен делать это за меня.

FreeLightman 12.03.2018 14:53

Да, я был весьма удивлен, когда понял, что (читайте об этом в книге Мэтта Зандстры «Php - объекты, шаблоны и практика»), и в примерах, которые пришли с этим знанием, были примеры, всегда повторно объявляющие переменные / функции, которые должны быть используется на основе ключевого слова static. Поскольку я не уверен в аналогичном поведении на других языках, трудно обсуждать различные возможные реализации этих функциональных возможностей, также я думаю, в конце концов, это не ошибка, а просто следствие того, как разработан язык.

Jacek Dziurdzikowski 12.03.2018 15:07

Также очень характерно, что наше видение того, как должна работать конкретная функциональность языка, отличается от того, как он работает на самом деле :) Поразмыслив, я понял, что это не может быть нежелательным поведением, потому что если вы используете ключевое слово self вместо static вы явно лишаете наследующие классы возможности использовать эту функциональность, но если вы используете static, вы даете им возможность использовать его. Итак, теперь вы видите, что это определенно не ошибка;)

Jacek Dziurdzikowski 12.03.2018 15:24

@JacekDziurdzikowski Вы видите, что static не работает должным образом. Так о какой необязательности вы говорите? self иногда бывает хорош. Пару раз пользовался. Но статика мне кажется немного глючной. Мой пример тому пример.

FreeLightman 12.03.2018 15:32

Нет, я только что сделал два больших комментария, чтобы объяснить свое мнение по этому поводу. Я определенно не говорю, что статика ошибочна - она ​​просто отличается от того, что вы хотели бы видеть.

Jacek Dziurdzikowski 12.03.2018 16:26
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
8
886
2

Ответы 2

Как упоминалось в комментарии, это больше связано со свойствами 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

По-прежнему не думаю, что это хорошее использование черт, но я оставлю это на ваше усмотрение.

Я знаю о статических свойствах. Вы полностью изменили дизайн (заменили инстансные реквизиты инстансовыми). И не объяснил, почему мой способ не работает. Это как обходной путь. Но не ответ.

FreeLightman 11.03.2018 19:38

Извините - мысль, что «статические свойства являются общими для всех экземпляров класса», была ответом на вашу проблему. Поскольку все ваши классы расширяют C, все они будут иметь одно и то же значение $mockClass.

Nigel Ren 11.03.2018 19:40

Собственно мне нужен вопрос, почему код не работает. И что сделать, чтобы он работал, находясь слишком близко к моему исходному коду. Если мой код недействителен или неверен - просто скажи. Но обходной путь - это окончательный вариант.

FreeLightman 11.03.2018 19:52

Что касается вашего редактирования. Я сказал, что использование черт напрямую решает проблему.

FreeLightman 11.03.2018 19:53

То, как у вас есть существующее, вообще не будет работать, я попытался объяснить, почему и как вы можете заставить его работать.

Nigel Ren 11.03.2018 19:54

Причина, по которой ваш код не работает так, как вы ожидаете, заключается в следующем:

Во время создания экземпляра 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 и почему тогда он не является нулевым. Ответ очевиден: так устроен язык.

Jacek Dziurdzikowski 11.03.2018 20:49

Другие вопросы по теме