Web

Когда использовать self вместо $this?

В чем разница между ними?

 

Ответ 1

Короткий ответ

Используйте $this для ссылки на текущий объект, а self для ссылки на текущий класс. Другими словами, используйте $this->member для нестатических членов, а self::$member для статических.

 

Полный ответ

Ниже приведен пример правильного использования $this и self для нестатических и статических переменных-членов:

<?php

class X {

    private $non_static_member = 1;

    private static $static_member = 2;

    function __construct() {

        echo $this->non_static_member . ' '

           . self::$static_member;

    }

}

new X();

?>

 

Вот пример неправильного использования $this и self для нестатических и статических переменных-членов:

<?php

class X {

    private $non_static_member = 1;

    private static $static_member = 2;

    function __construct() {

        echo self::$non_static_member . ' '

           . $this->static_member;

    }

}

new X();

?>

 

Вот пример полиморфизма с $this для функций-членов:

<?php

class X {

    function foo() {

        echo 'X::foo()';

    }

    function bar() {

        $this->foo();

    }

}

class Y extends X {

    function foo() {

        echo 'Y::foo()';

    }

}

$x = new Y();

$x->bar();

?>

 

Вот пример подавления полиморфного поведения с помощью self функций-членов:

<?php

class X {

    function foo() {

        echo 'X::foo()';

    }

    function bar() {

        self::foo();

    }

}

class Y extends X {

    function foo() {

        echo 'Y::foo()';

    }

}

$x = new Y();

$x->bar();

?>

 

Идея заключается в том, что $this->foo() вызывает функцию-член foo() любого точного типа текущего объекта. Если объект имеет тип X, то вызывается X::foo(). Если объект имеет тип Y, то вызывается Y::foo(). Но при использовании self::foo() всегда вызывается X::foo().

 

Ответ 2

Ключевое слово self НЕ относится только к «текущему классу», по крайней мере, не так, чтобы ограничивать вас статическими членами. В контексте нестатического члена self также предоставляет возможность обойти vtable для текущего объекта. Как вы можете использовать parent::methodName() для вызова родительской версии функции, так вы можете использовать self::methodName() для вызова реализации метода текущего класса.

class Person {

    private $name;

    public function __construct($name) {

        $this->name = $name;

    }

    public function getName() {

        return $this->name;

    }

    public function getTitle() {

        return $this->getName()." the person";

    }

    public function sayHello() {

        echo "Hello, I'm ".$this->getTitle()."<br/>";

    }

    public function sayGoodbye() {

        echo "Goodbye from ".self::getTitle()."<br/>";

    }

}

 

class Geek extends Person {

    public function __construct($name) {

        parent::__construct($name);

    }

    public function getTitle() {

        return $this->getName()." the geek";

    }

}

$geekObj = new Geek("Ludwig");

$geekObj->sayHello();

$geekObj->sayGoodbye();

 

Это выведет:

Hello, I'm Ludwig the geek
Goodbye from Ludwig the person 

 

sayHello() использует указатель $this, поэтому для вызова Geek::getTitle() вызывается vtable. sayGoodbye() использует self::getTitle(), поэтому vtable не используется, а вызывается Person::getTitle(). В обоих случаях мы имеем дело с методом инстанцированного объекта и имеем доступ к указателю $this внутри вызываемых функций.

 

Ответ 3

 

НЕ ИСПОЛЬЗУЙТЕ self::, используйте static::

Есть еще один аспект self::, о котором стоит упомянуть. Досадно, что self:: относится к области видимости в точке определения, а не в точке выполнения. Рассмотрим простой класс с двумя методами:

class Person{

    public static function status() {

        self::getStatus();

    }

    protected static function getStatus() {

        echo "Person is alive";

    }

}

 

Если мы вызовем Person::status(), то увидим «Person is alive». Теперь рассмотрим, что произойдет, если мы создадим класс, который наследуется от него:

class Deceased extends Person {

    protected static function getStatus() {

        echo "Person is deceased";

    }

}

 

Вызывая Deceased::status(), мы ожидали бы увидеть «Person is deceased», однако мы видим «Person is alive», поскольку область видимости содержит исходное определение метода, когда был определен вызов self::getStatus().

В PHP 5.3 есть решение. Оператор разрешения static:: реализует «позднюю статическую привязку», что является интересным способом указать, что он привязан к области видимости вызываемого класса. Измените строку в status() на static::getStatus(), и результаты будут такими, как вы ожидаете. В более старых версиях PHP вам придется прибегнуть к хитрости, чтобы сделать это.

Итак, резюмируя…

$this-> относится к текущему объекту (экземпляру класса), тогда как static:: относится непосредственно к классу.

 

Ответ 4

Чтобы по-настоящему понять, о чем мы говорим, когда говорим о self в сравнении с $this, нам нужно, на самом деле, вникнуть в то, что происходит на концептуальном и практическом уровнях. Давайте начнем с разговора о том, что такое класс и объект.

 

Классы и объекты, концептуально

Итак, что такое класс? Многие люди определяют его как план или шаблон для объекта. Фактически вы можете узнать больше о классах в 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 типов «элементов»:

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

class Foo {

    public $bar = 1;

}

 

  1. Статические свойства воспринимайте их как переменные, которые разделяются на уровне класса. Это означает, что они никогда не копируются каждым экземпляром.

class Foo {

    public static $bar = 1;

}

 

  1. Методы это функции, которые будут содержаться в каждом экземпляре (и работать над экземплярами).

class Foo {

    public function bar() {}

}

 

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

class Foo {

    public static function bar() {}

}

 

  1. Константыкласс разрешает использование констант. Не будем углубляться, но добавим для полноты:

class Foo {

    const BAR = 1;

}

 

Итак, по сути, мы храним информацию о классе и контейнере объекта, используя «подсказки» о статике, которые определяют, является ли информация общей (и, следовательно, статической) или нет (и, следовательно, динамической). 

 

Состояние и методы

Внутри метода экземпляр объекта представлен переменной $this. В ней находится текущее состояние этого объекта, и мутирование (изменение) любого свойства приведет к изменению этого экземпляра (но не других).

Если метод вызывается статически, переменная $this не определяется. Это происходит потому, что со статическим вызовом не ассоциируется ни один экземпляр.

Интересным здесь является то, как выполняются статические вызовы. Поэтому давайте поговорим о том, как мы получаем доступ к состоянию.

 

Состояние доступа

Итак, теперь, когда мы сохранили это состояние, нам нужно получить к нему доступ. Это может оказаться немного сложным, поэтому давайте разделим это на две точки зрения: снаружи экземпляра/класса (скажем, из обычного вызова функции или из глобальной области видимости) и внутри экземпляра/класса (из метода объекта). 

 

Снаружи экземпляра/класса

С внешней стороны экземпляра/класса наши правила довольно просты и предсказуемы. У нас есть два оператора, и каждый из них сразу говорит нам, имеем ли мы дело с экземпляром или статическим классом:

  • ->объект-оператор всегда используется, когда мы обращаемся к экземпляру.

$bob = new Person;

echo $bob->name;

 

Важно отметить, что вызов Person->foo не имеет смысла (поскольку Person это класс, а не экземпляр). Следовательно, это ошибка синтаксического анализа.

  • ::scope-Resolution-operator всегда используется для доступа к статическому свойству или методу класса.

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

 

Таким образом, счетчик ID используется как для экземпляров, так и для потомков (потому что мы используем self для доступа. Если бы мы использовали static, то могли бы переопределить его в дочернем классе).

var_dump($bob->getName()); // Bob

var_dump($adam->getName()); // Adam

var_dump($billy->getName()); // child: Billy

 

Обратите внимание, что мы каждый раз выполняем метод экземпляра Person::getName(). Но мы используем parent::getName(), чтобы сделать это в одном из случаев (дочерний случай). Именно это и делает данный подход мощным.

Схожие статьи

Web

Как сделать так, чтобы все значения PHP echo попадали в таблицу HTML

Web

Как выполнить цикл по массиву в php с форматированным html оформлением?

Web

Как получить первый элемент массива в PHP

Web

Как установить расширения для Chrome в Opera?

×