Расширение статических классов в PHP. Избегайте совместного использования переменных для нескольких классов в расширенном классе.

Кажется, у меня проблемы с расширением статических классов в PHP.

PHP-код:

<?php
    class InstanceModule {
        public static $className = 'None';
        public static function PrintClassName() {
            echo self::$className . ' (' . __CLASS__ . ')<br />';
        }
    }

    class A extends InstanceModule {
        public static function Construct() {
            self::$className = "A";
        }
    }

    class B extends InstanceModule {
        public static function Construct() {
            self::$className = "B";
        }
    }
?>

Мой код вызова и что я ожидаю:

<?php
    //PHP Version 5.3.14

    A::PrintClassName(); //Expected 'None' - actual result: 'None'
    B::PrintClassName(); //Expected 'None' - actual result: 'None'

    A::Construct();

    A::PrintClassName(); //Expected 'A' - actual result: 'A'
    B::PrintClassName(); //Expected 'None' - actual result: 'A'

    B::Construct();

    A::PrintClassName(); //Expected 'A' - actual result: 'B'
    B::PrintClassName(); //Expected 'B' - actual result: 'B'

    A::Construct();

    A::PrintClassName(); //Expected 'A' - actual result: 'A'
    B::PrintClassName(); //Expected 'B' - actual result: 'A'
?>

Фактический полный вывод:

None (InstanceModule)
None (InstanceModule)
A (InstanceModule)
A (InstanceModule)
B (InstanceModule)
B (InstanceModule)
A (InstanceModule)
A (InstanceModule)

Итак, что здесь происходит (из того, что кажется), так это то, что как только я устанавливаю self::$className в любом из расширяющих классов, он переопределяет переменную из другого класса. Я предполагаю, что это связано с тем, что я использую статические классы, и может быть только один класс InstanceModule вместо того, чтобы просто копировать его как в A, так и в B, как это было в моем предыдущем понимании extends. Вместо этого я пытался использовать ключевое слово static::$className, но, похоже, это не имеет значения.

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

Правка. Чтобы уточнить, этот код делает то, что я хочу, но, очевидно, это ужасный обходной путь, поскольку он разрушит всю идею расширения и повторного использования функций:

<?php
    class A {
        public static $className = 'None';
        public static function PrintClassName() {
            echo self::$className . ' (' . __CLASS__ . ')<br />';
        }
        public static function Construct() {
            self::$className = "A";
        }
    }

    class B {
        public static $className = 'None';
        public static function PrintClassName() {
            echo self::$className . ' (' . __CLASS__ . ')<br />';
        }
        public static function Construct() {
            self::$className = "B";
        }
    }
?>

person h2ooooooo    schedule 24.06.2012    source источник


Ответы (4)


Поскольку $className является статическим и находится внутри родительского класса, когда вы устанавливаете className в пределах A или B, он изменяет переменную внутри родителя, и то же самое делается при чтении переменной. Если вы не переопределите className в своих расширенных классах, вы будете хранить и извлекать информацию из одного и того же места в памяти, изначально определенного в InstanceModule.

Если вы переопределяете className в A/B, вы можете получить доступ к className, используя parent:: или self:: из InstanceModule или A/B соответственно. В зависимости от того, что вы пытаетесь сделать, абстрактные классы также могут играть значительную роль.

См. Статическое ключевое слово или Абстракция класса в Руководстве по PHP5.

person sammoore    schedule 24.06.2012
comment
Абстракция класса, похоже, не имеет значения для меня (хотя, возможно, я делаю это неправильно). Как упоминалось в OP, я уже пытался использовать ключевое слово static и, наконец, переопределить className в обоих расширяющих классах (public static $className = 'None') actually results in PrintClassName()` для постоянного вывода None, хотя self::$className все еще определяется. - person h2ooooooo; 24.06.2012
comment
Это действительно помогло мне понять, как публичная статика использует наследование. - person Steve Bauman; 13.01.2020

Похоже, что все еще это проблема в php 7.3 (через 7 лет после того, как здесь был опубликован исходный вопрос).

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

Вы можете заменить статическую переменную статическим массивом:

<?php
    class InstanceModule {
        public static $className = [];
        public static function PrintClassName() {
            $calledClass = get_called_class();
            if(empty(self::$className[$calledClass])){
              $thisClassName = "none";
            }else{
              $thisClassName = self::$className[$calledClass];
            }
            echo $thisClassName . ' (' . __CLASS__ . ')<br />';
        }
    }

    class A extends InstanceModule {
        public static function Construct() {
            $calledClass = get_called_class();
            self::$className[$calledClass] = "A";
        }
    }

    class B extends InstanceModule {
        public static function Construct() {
            $calledClass = get_called_class();
            self::$className[$calledClass] = "B";
        }
    }
?>

Таким образом, «статическое» значение для каждого дочернего класса InstanceModule хранится под ключом, имеющим имя класса, из которого он происходит.

Мне кажется ошибкой PHP, что родственные классы будут совместно использовать статические свойства... этого никогда не должно происходить, особенно когда родитель абстрактен. Этот обходной путь не очень красив, но это единственный не слишком сложный метод, который мне удалось найти.

С помощью этого обходного пути вы получите желаемые результаты:

<?php   
    A::PrintClassName(); //Expected 'None' - actual result: 'None'
    B::PrintClassName(); //Expected 'None' - actual result: 'None'

    A::Construct();

    A::PrintClassName(); //Expected 'A' - actual result: 'A'
    B::PrintClassName(); //Expected 'None' - actual result: 'None'

    B::Construct();

    A::PrintClassName(); //Expected 'A' - actual result: 'A'
    B::PrintClassName(); //Expected 'B' - actual result: 'B'

    A::Construct();

    A::PrintClassName(); //Expected 'A' - actual result: 'A'
    B::PrintClassName(); //Expected 'B' - actual result: 'B'
?>
person Skeets    schedule 25.06.2019

Я бы хотел, чтобы базовый класс хранил репозиторий экземпляров подкласса, чтобы вы могли правильно отделить данные, принадлежащие каждому классу, а не извлекать эти данные из статической переменной базового класса.

Вы можете использовать магический метод __callStatic() в базовом классе для вызова методов в несуществующих подклассах, как показано ниже. К сожалению, статическая переменная репозитория должна быть объявлена ​​общедоступной из-за видимости этого волшебного метода.

abstract class Base
{
    public static $repo = array();

    public static function __callStatic($name, $args)
    {
        $class = get_called_class();

        if (!isset(self::$repo[$class])) {
                echo "Creating instance of $class\n";
                self::$repo[$class] = new $class();
        }

        return call_user_func_array(array(self::$repo[$class], $name), $args);
    }

        protected function PrintClassName()
        {
                echo __CLASS__, " (", get_called_class(), ")\n";
        }

        protected abstract function Construct($a);
}

class A extends Base
{
        protected function Construct($a)
        {
                echo __CLASS__, ": setting x := $a\n";
        }
}

class B extends Base
{
        protected function Construct($a)
        {
                echo __CLASS__, ": setting y := $a\n";
        }
}

A::PrintClassName();
B::PrintClassName();

A::Construct('X');
B::Construct('Y');

Выход:

Creating instance of A
Base (A)
Creating instance of B
Base (B)
A: setting x := X
B: setting y := Y
person Ja͢ck    schedule 25.06.2012

Я думаю, что лучшим ответом на вашу ситуацию будет использование get_called_class() вместо вашей текущей переменной $className, которая вернет имя класса поздней статической привязки вместо __CLASS__ или get_class(), которые вернут только текущее имя класса.

Если вы изменили свою функцию PrintClassName() так, чтобы она просто выводила то, что возвращала get_called_class(), ваш вывод был бы следующим. Теперь вам просто нужно включить значение по умолчанию, которое, конечно же, будет общим для всех классов, поэтому вам нужно будет иметь этот флаг в обоих классах, если вы собираетесь продолжать использовать статические методы.

A
B
A
B
A
B
A
B
person nickb    schedule 24.06.2012
comment
Конечно, A и B — это просто форма демонстрации того, что происходит. В моем реальном сценарии я имею дело с разными классами extends InstanceModule, но у них разные функции (и их общие функции находятся в InstanceModule). Это означает, что когда я создаю класс B и пытаюсь получить доступ к A::FunctionInAClass, я получаю сообщение об ошибке, поскольку этой функции не существует (поскольку на данный момент у нее есть только A::FunctionInBClass, хотя эта функция была объявлена ​​в B, а не A ( но A был перезаписан B, как в моем примере ОП). - person h2ooooooo; 24.06.2012
comment
Действительно похоже, что вам лучше изменить эти классы, чтобы они были статическими, чтобы вы могли создавать на них объекты, что затем дало бы наследование, которое вы ищете. - person nickb; 24.06.2012
comment
Дело в том, что на самом деле это моя попытка создать более глобальный способ вызова классов (и экземпляров). Конечно, я мог бы вызвать $db = DB::GetInstance(); $db->Connect();, но я пытался сделать в своем коде оболочку, которая просто использует текущий экземпляр, и когда вы затем запускаете DB::Connect, он делает это в фоновом режиме. Отсюда возникает эта проблема. Я думаю, мне нужно решить либо использовать $GLOBALS (гадость), либо получать экземпляр всякий раз, когда мне нужно вызвать класс. - person h2ooooooo; 24.06.2012
comment
Вы должны изучить либо внедрение зависимостей, либо фабричные шаблоны проектирования. Либо, либо сможет помочь вам с последним (получение экземпляров, когда вам нужно вызвать класс). - person nickb; 24.06.2012