Чтобы по-настоящему понять, о чем мы говорим, когда говорим о self
против $this
, нам нужно действительно разобраться в том, что происходит на концептуальном и практическом уровне. Я действительно не чувствую, что какой-либо из ответов делает это должным образом, поэтому вот моя попытка.
Давайте начнем с разговора о том, что такое класс и объект .
Классы и объекты, концептуально
Итак, что есть a класс ? Многие люди определяют его как проект или шаблон для объекта. Более того, вы можете прочитать о классах в PHP здесь . И в какой-то степени это то, что есть на самом деле. Давайте посмотрим на класс:
class Person {
public $name = 'my name';
public function sayHello() {
echo "Hello";
}
}
Как вы можете сказать, в этом классе есть свойство с именем $name
и метод (функция) с именем sayHello()
.
Это очень важно отметить, что класс является статической структурой. Это означает, что класс Person
, когда-то определенный, всегда один и тот же везде, где бы вы ни смотрели.
С другой стороны, объект - это то, что называется экземпляром класса. Это означает, что мы берем «план» класса и используем его для создания динамической копии. Эта копия теперь специально привязана к переменной, в которой она хранится. Поэтому любые изменения в экземпляре являются локальными для этого экземпляра.
$bob = new Person;
$adam = new Person;
$bob->name = 'Bob';
echo $adam->name; // "my name"
Мы создаем экземпляров класса, используя оператор new
.
Поэтому мы говорим, что Класс является глобальной структурой, а Объект является локальной структурой. Не беспокойтесь об этом забавном синтаксисе ->
, мы немного углубимся в это.
Еще одна вещь, о которой мы должны поговорить, это то, что мы можем проверить , если экземпляр является instanceof
конкретным классом: $bob instanceof Person
, который возвращает логическое значение, если экземпляр $bob
был создан с использованием Person
класс, или ребенок Person
.
Определение состояния
Итак, давайте немного углубимся в то, что на самом деле содержит класс. Класс содержит 5 типов «вещей»:
Свойства - Думайте о них как о переменных, которые будет содержать каждый экземпляр.
class Foo {
public $bar = 1;
}
Статические свойства - Рассматривайте их как переменные, которые используются на уровне класса. Это означает, что они никогда не копируются каждым экземпляром.
class Foo {
public static $bar = 1;
}
Методы - Это функции, которые каждый экземпляр будет содержать (и работать с экземплярами).
class Foo {
public function bar() {}
}
Статические методы - Это функции, которые являются общими для всего класса. Они не работают на экземплярах, а только на статических свойствах.
class Foo {
public static function bar() {}
}
Константы - Константы с разрешением класса. Здесь не буду углубляться, но добавлю для полноты:
class Foo {
const BAR = 1;
}
Итак, в основном, мы храним информацию о классе и контейнере объектов, используя «подсказки» о static , которые определяют, является ли информация общей (и, следовательно, статической) или нет (и, следовательно, динамической).
Состояние и методы
Внутри метода экземпляр объекта представлен переменной $this
. Текущее состояние этого объекта есть, и изменение (изменение) любого свойства приведет к изменению этого экземпляра (но не других).
Если метод вызывается статически, переменная $this
не определена . Это потому, что нет никакого экземпляра, связанного со статическим вызовом.
Интересно то, как выполняются статические вызовы. Итак, давайте поговорим о том, как мы получаем доступ к состоянию:
Состояние доступа
Итак, теперь, когда мы сохранили это состояние, нам нужно получить к нему доступ. Это может быть немного сложно (или way больше, чем немного), поэтому давайте разделим это на две точки зрения: снаружи экземпляра / класса (скажем, из обычного вызова функции или из глобальной области видимости) и внутри экземпляра / класса (изнутри метода объекта).
Снаружи экземпляра / класса
Снаружи экземпляра / класса наши правила довольно просты и предсказуемы. У нас есть два оператора, и каждый сразу сообщает нам, имеем ли мы дело с экземпляром или классом static:
->
- объект-оператор - Это всегда используется, когда мы обращаемся к экземпляру.
$bob = new Person;
echo $bob->name;
Важно отметить, что вызов Person->foo
не имеет смысла (поскольку Person
- это класс, а не экземпляр). Следовательно, это ошибка разбора.
::
- оператор разрешения области действия - всегда используется для доступа к статическому свойству или методу класса.
echo Foo::bar()
Кроме того, мы можем вызвать статический метод для объекта таким же образом:
echo $foo::bar()
Очень чрезвычайно важно отметить, что когда мы делаем это извне , экземпляр объекта скрывается от метода bar()
. Это означает, что это то же самое, что и бег:
$class = get_class($foo);
$class::bar();
Следовательно, $this
не определен в статическом вызове.
Изнутри экземпляра / класса
Здесь все немного меняется. Используются те же операторы, но их значение становится значительно размытым.
Оператор объекта ->
по-прежнему используется для вызова состояния экземпляра объекта.
class Foo {
public $a = 1;
public function bar() {
return $this->a;
}
}
Вызов метода bar()
для $foo
(экземпляр Foo
) с использованием объекта-оператора: $foo->bar()
приведет к версии экземпляра $a
.
Так мы и ожидаем.
Значение оператора ::
хотя и меняется. Это зависит от контекста вызова текущей функции:
В статическом контексте
В статическом контексте любые вызовы, сделанные с использованием ::
, также будут статическими. Давайте посмотрим на пример:
class Foo {
public function bar() {
return Foo::baz();
}
public function baz() {
return isset($this);
}
}
Вызов Foo::bar()
вызовет метод baz()
статически, и поэтому $this
будет не заполняться. Стоит отметить, что в последних версиях PHP (5.3+) это вызовет ошибку E_STRICT
, потому что мы вызываем нестатические методы статически.
В контексте экземпляра
С другой стороны, в контексте экземпляра вызовы, сделанные с использованием ::
, зависят от получателя вызова (метода, который мы вызываем). Если метод определен как static
, он будет использовать статический вызов. Если это не так, он отправит информацию об экземпляре.
Итак, глядя на приведенный выше код, вызов $foo->bar()
вернет true
, поскольку «статический» вызов происходит внутри контекста экземпляра.
Имеет смысл? Я так не думал. Это сбивает с толку.
Ключевые слова быстрого доступа
Поскольку связывать все вместе, используя имена классов, довольно грязно, PHP предоставляет 3 основных "горячих" ключевых слова, чтобы упростить разрешение области видимости.
self
- Это относится к имени текущего класса. Таким образом, self::baz()
совпадает с Foo::baz()
в классе Foo
(любой метод в нем).
parent
- Это относится к родителю текущего класса.
static
- Это относится к вызываемому классу. Благодаря наследованию дочерние классы могут переопределять методы и статические свойства. Поэтому вызов их с использованием static
вместо имени класса позволяет нам определить источник вызова, а не текущий уровень.
Примеры
Самый простой способ понять это - начать смотреть на некоторые примеры. Давайте выберем класс:
class Person {
public static $number = 0;
public $id = 0;
public function __construct() {
self::$number++;
$this->id = self::$number;
}
public $name = "";
public function getName() {
return $this->name;
}
public function getId() {
return $this->id;
}
}
class Child extends Person {
public $age = 0;
public function __construct($age) {
$this->age = $age;
parent::__construct();
}
public function getName() {
return 'child: ' . parent::getName();
}
}
Теперь мы также смотрим на наследование здесь. На мгновение проигнорируйте, что это плохая объектная модель, но давайте посмотрим, что происходит, когда мы играем с этим:
$bob = new Person;
$bob->name = "Bob";
$adam = new Person;
$adam->name = "Adam";
$billy = new Child;
$billy->name = "Billy";
var_dump($bob->getId()); // 1
var_dump($adam->getId()); // 2
var_dump($billy->getId()); // 3
Таким образом, счетчик идентификаторов является общим для обоих экземпляров и дочерних элементов (поскольку мы используем self
для доступа к нему. Если мы использовали static
, мы могли бы переопределить его в дочернем классе).
var_dump($bob->getName()); // Bob
var_dump($adam->getName()); // Adam
var_dump($billy->getName()); // child: Billy
Обратите внимание, что мы каждый раз выполняем метод Person::getName()
instance . Но мы используем parent::getName()
, чтобы сделать это в одном из случаев (дочерний случай). Вот что делает этот подход мощным.
Слово предостережения # 1
Обратите внимание, что вызывающий контекст определяет, используется ли экземпляр. Поэтому:
class Foo {
public function isFoo() {
return $this instanceof Foo;
}
}
Не всегда верно.
class Bar {
public function doSomething() {
return Foo::isFoo();
}
}
$b = new Bar;
var_dump($b->doSomething()); // bool(false)
Теперь это действительно странно здесь. Мы вызываем другой класс, но $this
, который передается методу Foo::isFoo()
, является экземпляром $bar
.
Это может вызвать всевозможные ошибки и концептуальные WTF-ошибки. Поэтому я настоятельно рекомендую избегать использования оператора ::
из методов экземпляра для чего-либо, кроме этих трех виртуальных «сокращенных» ключевых слов (static
, self
и parent
).
Слово предостережения № 2
Обратите внимание, что статические методы и свойства доступны всем. Это делает их в основном глобальными переменными. Со всеми теми же проблемами, которые идут с глобалами. Поэтому я бы не решался хранить информацию в статических методах / свойствах, если вы не уверены, что она действительно глобальная.
Слово предостережения # 3
Как правило, вы хотите использовать то, что известно как Late-Static-Binding, используя static
вместо self
. Но обратите внимание, что это не одно и то же, поэтому выражение «всегда использовать static
вместо self
действительно недальновидно. Вместо этого остановитесь и подумайте о вызове, который вы хотите сделать, и подумайте, хотите ли вы, чтобы дочерние классы были возможность переопределить этот статический разрешенный вызов.
TL / DR
Жаль, вернись и прочитай это. Это может быть слишком долго, но это так долго, потому что это сложная тема
TL / DR # 2
Хорошо, хорошо. Вкратце, self
используется для ссылки на имя текущего класса внутри класса, где $this
относится к текущему объекту экземпляр . Обратите внимание, что self
является ярлыком копирования / вставки. Вы можете смело заменить его своим именем класса, и оно будет работать нормально. Но $this
- это динамическая переменная, которая не может быть определена заранее (и даже не может быть вашим классом).
TL / DR # 3
Если используется объект-оператор (->
), то вы всегда знаете, что имеете дело с экземпляром. Если используется оператор scope-resolution-operator (::
), вам потребуется дополнительная информация о контексте (мы уже в объект-контексте? Мы вне объекта? И т. Д.).