Pull to refresh

Comments 34

Это не внутри и вне класса, а внутри и вне созданного объекта этого класса. Если не инстанцировать то изнутри класса всё тоже прекрасно работает.
Все дело в том, как в php внутри устроен $this. На удивление, это очень похоже на JavaScript =)

Эта история тянется со времен php4, когда не было четкого указания статического вызова в объявлении метода. Например, такой прием можно встретить, если покопаться в коде 15-летней давности:

class A {
     function foo() {
         if (!isset($this) || get_class($this) !== __CLASS__) {
              $self = new A;
              return $self->foo();
         }
         // do smth
     }
}
A::foo();


С тех пор изменились только внешние декорации.

Вот такая прелесть бросает E_STRICT, но до сих пор работает:

<?php
class A {
    function x() {
        var_dump(get_class($this));
    }
}
class B {
    function __construct() {
        A::x();
    }
}
new B;

PHP Strict standards:  Non-static method A::x() should not be called statically, assuming $this from incompatible context in test.php on line 9
string(1) "B"

Deprecated этот, очевидно, делает то самое банальное сравнение (перепишем поумнее на LSB)
isset($this) && $this instanceof get_called_class()

поскольку если сделать так
class A {
    public function __construct() {
        A::x();
    }
    function x() {
        var_dump(get_class($this));
    }
}
new A;

или даже так
class A {
    public function __construct() {
        A::x();
    }
    function x() {
        var_dump(get_class($this));
    }
}
class B extends A {
    function __construct() {
        A::x();
    }
}
new B;

то никакого E_STRICT не будет: $this-то «правильный» ;)

Так что ООП тут никаким местом, это просто очередная особенность реализации php.
Спасибо за разъяснения и приобретённый опыт. Сделаю выводы. Думаю многие, кто читает ваш комментарий и не знают этой особенности также приобретут новые знания. Но всё же, мне кажется, что разработчикам стоило бы задокументировать неочевидное поведение интерпретатора. Ведь если наперёд не знать, что это капризы PHP, то программист, в особенности новичок, может потратить не один час в тщетных поисках ошибки. Зная такую особенность, программисты смогут избегать, а ещё лучше, учитывать это при использовании такого рода конструкций.
Так задокументировано ведь.

The pseudo-variable $this is available when a method is called from within an object context. $this is a reference to the calling object (usually the object to which the method belongs, but possibly another object, if the method is called statically from the context of a secondary object).


Большой красной таблички «АХТУНГ, ЗДЕСЬ ПРОИСХОДИТ Н.Ё.Х.» не хватает, это согласен.
Перевод: «Псевдо-переменная $this доступна в том случае, если метод был вызван в контексте объекта. $this является ссылкой на вызываемый объект. Обычно это тот объект, которому принадлежит вызванный метод, но может быть и другой объект, если метод был вызван статически из контекста другого объекта.»

Мой мозг не мог провести аналогию с этим абзацем, до того как вы не объяснили причины такого поведения в предыдущем комментарии. Спасибо.

Да, вот именно таблички и не хватает, а жаль. Серьёзно, для меня не было очевидным такое поведение $this. Но это обозначает лишь только, что есть куда развиваться.
Поправьте меня, если я неправильно понял… Но получается не-статический метод вполне реально вызвать статически (с использованием ::)?
<?php
class A
{
	public function DynamicMethod()
	{
		var_dump(debug_backtrace());
		return 'I am Dynamic';
	}
}
echo A::DynamicMethod();
/*
Вывод:
array (size=1)
  0 => 
    array (size=6)
      'file' => string 'C:\OpenServer\domains\test1.ru\index.php' (length=40)
      'line' => int 11
      'function' => string 'DynamicMethod' (length=13)
      'class' => string 'A' (length=1)
      'type' => string '::' (length=2)
      'args' => 
        array (size=0)
          empty

I am Dynamic
*/
?>
Можно, но не нужно — E_STRICT будет: включите error_reporting=E_ALL. Или обновите php :)

Рано или поздно это окончательно выпилят.
Мне кажется так было всегда, вспомнить хотя бы parent::__construct();
Более того, можно похожим способом вызывать метод «дедушки», если знать его имя:

<?php
class A { function test() { echo $this->property . " A\n"; } }
class B extends A { function test() { echo $this->property . " B\n"; } }
class C extends B {
  public $property;
  function test() { echo $this->property . " C\n"; }
  function run() { $this->property = "TEST"; A::test(); }
}

$obj = new C;
$obj->run();


Выведет «TEST A»
Извините, не вижу в этом коде, за исключением вышеупомянутых странностей, ничего необычного.
Это просто потому, что с вышеупомянутыми странностями вряд ли что-то может сравниться :). Каюсь, не прочитал до конца все комментарии перед тем, как написать свой пример. Тем не менее, мне кажется, мой пример хотя бы может иметь какую-то отдаленную практическую ценность :).
мой пример хотя бы может иметь какую-то отдаленную практическую ценность

Чтобы окончательно запутать людей, которые будут поддерживать код после вас — да.

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

Согласен, лучше воздержаться от использования такого кода. Тем не менее, в очень редких случаях это бывает удобным и оправданным (например, если вы пишете «одноразовый» класс/скрипт, которому такое нужно :))
Вот вам еще немножко статических пхп ужасов:
class A {
    static $i;
    public static function i(){
        return self::$i? self::$i: self::$i = new static;
    }
    function say(){ echo get_class($this) . "\n"; }
}

class B extends A{}
class C extends A{}

B::i()->say();
C::i()->say();

выдаст
B
B

но
class A {
    public static function i(){
        static $i;
        return $i? $i: $i = new static;
    }
    function say(){ echo get_class($this) . "\n"; }
}

class B extends A{}
class C extends A{}

B::i()->say();
C::i()->say();

выдаст
B
C


Кто расскажет почему?
Создается впечатление, что статические методы, вместо наследования, тупо копируются.
Так задумано, self — это compile time static binding, static — runtime static binding.
php.net/LSB
ОК. пойдем дальше
class A {
    public static function i(){
        $f = function($name){
            static $i;
            return $i? $i: $i = new $name;
        };
        return $f(get_called_class());
    }
    function say(){ echo get_class($this) . "\n"; }
}

class B extends A{}
class C extends A{}

B::i()->say();
C::i()->say();

B
C


function j($name){
    static $i;
    return $i? $i: $i = new $name;
}

class A {
    public static function i(){
        return j(get_called_class());
    }
    function say(){ echo get_class($this) . "\n"; }
}

class B extends A{}
class C extends A{}

B::i()->say();
C::i()->say();

B
B


правильно ли я понимаю, что статические переменные внутри функций не имеют ничего общего с статическими переменными внутри методов класса? иначе что за static binding происходит внутри функции?
Почему бы и нет? Просто у self, parent и static разный способ поведения.
По-порядку (разбираю второй вариант):
1. Выполняем
B::i()

а). Т.к. $i — не присвоено значение, то при выполнении тернарного оператора NULL, при преобразовании в булев контекст будет равен FALSE. Следовательно $i=new static; Где static — это контекст класса B. Следовательно $i — экземпляр (объект)
класса B.
б). Функция $i->Say(), где $i — объект класса B совершенно логично выполнит get_class($this). Т.е. вернёт имя класса, к которому принадлежит $this — в данном случае B.
2. Выполняем
C::i()

а). Т.к. $i — не присвоено значение (то, что оно помечено статическим — ничего не значит, т.к. по сути B::i() и C::i() не зависят друг от друга в плане инициализации свойств), то при выполнении тернарного оператора NULL, при преобразовании в булев контекст будет равен FALSE. Следовательно $i=new static; Где static — это контекст класса C. Следовательно $i — экземпляр (объект)
класса C.
б). Функция $i->Say(), где $i — объект класса C совершенно логично выполнит get_class(). Т.е. вернёт имя класса, к которому принадлежит $this — в данном случае C.

Надеюсь это то, что вы хотели услышать
Я хотел лишь узнать, почему при вызове одного и того же метода статическая переменная внутри этого метода «теряет» свое значение.
Я хотел сказать, что метод то один, почему статических перемнных стало две?
Это же не свойство класса, а статическая переменная внутри метода?
Она не теряет своего значения, B::i()!=C::i() (они наследуются от A, но переменные внутри методов имеют разное значение ($i1!=$i2)). Ну не может терять значение то, что никогда не было инициализировано. Проверьте var_dump'ом.
class A {
    public static function i(){
        static $i;
		var_dump($i);
        return $i? $i: $i = new static;
    }
    function say(){ echo get_class($this) . "\n"; }
}

class B extends A{}
class C extends A{}

B::i()->say();
C::i()->say();

/*
Вывод:
null
B

null
C 
*/


Я в прошлом комментарии описался, прошу прощения, естественно имел ввиду переменную $i внутри метода, а не свойство класса.
хочется понять, почему B::i()!=C::i() когда метод один. Ну и зачем так сделано
<?php
class A {
    public static function Method($value)
	{
		static $v;
			$v=$v ? : $value;
		echo'Я метод класса '.get_called_class().' $v='.$v.'<br>'.PHP_EOL;
    }
}

class B extends A{}
class C extends A{}

B::Method(5);
C::Method(8);
?>


Метод не один. Поймите. Как бы вам объяснить. Ну вот откройте документ любой у себя на компьютере, например в формате .docx. Теперь возьмите и распечатайте две копии на принтере. А теперь возьмите и напишите ручкой на одном из распечатанных документов «PHP». Теперь переведите взгляд на второй распечатанный документ. Там появилось слово «PHP»?
Так вот, документ на компьютере — это класс A, две копии, распечатанные на принтере, это классы B и C.

Так и тут, методы B::i() и C::i() — это два образца основанные на одном прототипе. Но друг от друга они не зависят.
Скажите пожалуйста: при наследовании наследства (извините за тавтологию), вашей горячо любимой троюродной прабабушки, копируется ли оно, или нет?
Так вот, к чему это я? К тому, что каждый понимает программные абстракции по своему. Аналогии ложны, а вашему принтеру стоило быть более экономным, чтобы сберечь леса. Истинным, в данном случае, может быть только мануал. Мне хотлось узнать, где можно почитать, как внутри устроено наследование и как оно превращается в копирование
UFO just landed and posted this here
К сожалению, в мануале ничего не написано про механизмы наследования. О том что методы при наследовании копируются. Пока это мои догадки. Хочется быть уверенным.
Я надеялся на ссылки на какую-нибудь статью, с описаниями механизмов. Но, похоже, придется лезть в код и самому писать.
А если вообще конкретизировать, начните чтение с различий между ключевыми словами self и static
UFO just landed and posted this here
Вы зря иронизируете. Я хотел просто более понятным, «человеческим» языком, объяснить, как это выглядит и с чем можно провести аналогию. Видимо, я потерпел в этом полный крах. Помню в то время, когда заинтересовался языком C++, я открыл книгу Джесса Либерти — «C++ за 21 день». Так вот там, автор, для наглядности, абстрактный класс сравнивает со словом «автомобиль» в целом, обычный класс, который наследуется, от выше упомянутого абстрактного, сравнивает с маркой машины, а объект — с конкретным, выпущенным на конвейере. (Пишу по памяти, если немного приврал — уж простите, но суть от этого не меняется). Где-то ещё видел как классы сравнивали со зверями. В общем, по-моему, такие аналогии даже полезны, поскольку помогают понять основные моменты ООП-парадигмы, основная суть в которой, если вы не забыли, это описание классов (описание методов, обозначение свойств, полиморфизм и инкапсуляция) и манипулирование с экземплярами этих классов.
Уважаемые хабра-минусующие, поясните свою позицию, пожалуйста. Что вам не понравилось — материал статьи, форма изложения, оформление. Это мой первый пост и если я и сделал какие-то ошибки, вы не молчите, я не экстрасенс, не хочется упасть лицом в лужу в следующий раз.
заглушки __call и __callStatic работают правильно. Попробую объяснить почему.
Когда вы ведете обращение к несуществующему методу из созданого объекта, то механизм объекта не принимает решение статический метод или объектный не существует — для него все несуществующие только обэктные.
Sign up to leave a comment.

Articles

Change theme settings