ProfiPHPProfiPHP
  1. Главная
  2. Самоучитель PHP 7
  3. Классы и объекты

Классы и объекты

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

Объектно-ориентированное программирование наряду с предопределенными типами (такими как integer, boolean, double, string) позволяет формировать собственные абстрактные пользовательские типы переменных - классы. Экземпляры классов или переменные их типа называются объектами. Создавая собственные типы данных, можно оперировать не машинными терминами (переменная), а объектами реального мира, поднимаясь тем самым на новый абстрактный уровень.

Нельзя выполнять совместные операции с машинами и деревьями, однако низкоуровневый код запросто позволит совершить такую логическую ошибку, тогда как при использовании абстрактных типов данных эта операция становится невозможной или, по крайней мере, сильно затрудненной. Абстрактные типы данных созданы для реализации возможности вводить в программу переменные с желаемыми свойствами. Класс описывает состав объекта - переменные и функции, которые обрабатывают переменные и тем самым определяют поведение объекта.

Создание класса


В языке программирования PHP класс объявляется при помощи ключевого слова class, после которого следуют уникальное имя класса и тело класса в фигурных скобках. Название класса не зависит от регистра и может содержать любое количество цифр, букв и символов подчеркивания, однако не может начинаться с цифры. Стандарт кодирования PSR требует, чтобы имя класса должно быть объявлено с использованием так называемой СаmеlСаsе: каждое слово начинается с большой буквы, между словами нет разделителей. Более подробно можно узнать из статьи Стандарты, стили и правила оформления кода PHP.
<?php
class MyClass
{
// Переменные класса
}
РНР позволяет включать скрипты в документ при помощи тегов <?php и ?>. Один документ может содержать множество включений этих тегов, однако класс должен объявляться в одном неразрывном блоке <?php и ?>. Не удастся механически разбить класс и при помощи инструкции Include или Require.

В теле класса могут быть объявлены переменные, которые называются переменными класса. Например, для задания точки координат можно создать класс Map, содержащий две координаты и :
<?php
class Map
{
public $x;
public $y;
}
В скрипте возможно лишь один раз определить класс. Попытка повторного определения класса приведет к генерации сообщения об ошибке "Fatal error: Cannot declare class Map, because the name is already in use". Данную ошибку довольно легко диагностировать, то есть оба класса находятся в одном и том же файле. Однако на практике под каждый класс выделяется отдельный файл, а в большом проекте, состоящей из десятков классов, использование инструкций подключения файлов может привести к ситуации, когда один и тот же файл включается повторно, что приведет также к ошибке. Ситуация может усложняться тем, что внутри включаемых файлов могут быть расположены другие конструкции, включающие другие файлы. Чтобы решить проблему с повторным определением классов необходимо использовать Include_once или Require_once. Их отличие от оригинальных инструкций состоит в том, что они включают файл только один раз, попытки повторного включения файла игнорируются.

Создание объекта


После создания класса можно переходить к созданию объекта данного класса путем объявления при помощи ключевого слова new, за которым следует имя класса. Стандарт кодирования PSR рекомендует хранить код класса и использующий его код в разных файлах. Чтобы воспользоваться классом, файл, содержащий его определение, следует включить при помощи инструкции require_once:
<?php
require_once 'map.php';
$map = new Map;
$map->x = 1;
$map->y = 2;
echo $map->x; // 1
Объект $map класса Map создается при помощи ключевого слова new. Для обращения к переменным объекта и , следует воспользоваться специальным оператором "->". В отличие от стандартных переменных РНР, символ $ после -> при обращении к переменным объекта не указывается. После инициализации объекта создается обычная переменная $map из специфичными свойствами. Как и любой другой переменной, объекту можно присваивать новое значение.
<?php
require_once 'map.php';
$map = new Map; // object
$map = 'str'; // string(3)
В данном примере объекту $map присваивается строковое значение, и он становится переменной строкового типа.

Как и обычная переменная, объект существует до конца времени выполнения скрипта или пока не будет уничтожен явно при помощи конструкции Unset. Использование конструкции Unset может быть полезным, если объект занимает большой объем оперативной памяти, и ее следует освободить, чтобы не допустить переполнения.

Спецификаторы доступа


Переменные класса объявляются при помощи спецификаторов public, private или protected, которые указывают на доступы к переменным объекта извне. Открытые члены класса объявляются спецификатором доступа public и доступны как внутри класса, так и внешнему по отношению к классу коду. Закрытые методы и члены класса объявляются при помощи спецификатора private и доступны только в рамках класса. Спецификатор protected используется при наследовании.
<?php
class PrivateMap
{
public $x;
private $y;
}
// Объявляем объект класса PrivateMap
$map = new PrivateMap;

// Присваиваем значения переменным объекта
$map->x = 1;
$map->y = 2; // Fatal error: Uncaught Error: Cannot access private property PrivateMap
В данном примере класс содержит открытую переменную и закрытую . Обращение к закрытому члену класса завершится ошибкой "Fatal error: Uncaught Error: Cannot access private property PrivateMap".

Статические переменные класса


Каждый объект обладает своим набором переменных, независимых от других объектов. Но возможно создать статические переменные на уровне класса, которые объявляются при помощи ключевого слова static. Особенностью таких переменных является возможность их инициализации прямо в классе при объявлении:
<?php
class MyStatic
{
public static $staticvar = 50;
}
Обращаться к таким переменным можно без создания объектов при помощи оператора разрешения области видимости "::":
echo MyStatic::$staticvar; // 50

Ссылки на переменные


Если присваивать переменным одинаковые значения при помощи оператора получаются две независимые переменные:
<?php
$first = $second = 1;
$first = 2;
echo $second; // 1
При работе с объектами ситуация совершенно другая. Оператор присваивания = не приводит к созданию новой копии объекта: и старый, и новый объект указывают на одну и ту же область памяти:
<?php
require_once 'map.php';

$first = new Map;
$first->x = 3;
$first->y = 3;

$second = $first;
$second->x = 5;
$second->y = 5;
echo "x: {$first->x}, y: {$first->y}";
Присвоение новых значений переменным одного объекта приводит к тому, что эти же значения получает и второй объект. То есть переменные $first и $second ссылаются на один и тот же объект.

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

Клонирование объектов


Если необходимо создать копию текущего объекта, используется специальная операция - клонирование. Она выполняется при помощи ключевого слова clone, которое располагается непосредственно перед объектом клонирования:
<?php
require_once 'map.php';

$first = new Map;
$first->x = 3;
$first->y = 3;

$second = clone $first;
$second->x = 5;
$second->y = 5;

echo "x: {$first->x}, y: {$first->y}"; // x: 3, y: 3

Методы


Наиболее полезным свойством классов является возможность объявления внутри них функций, которые могут выполнять операции над данными объекта, вызывать другие функции объекта. Такие функции называются методами.

Определение метода


Помимо переменных в классы можно включать методы, которые представляют собой обычные функции РНР. Приведем пример класса Hello, в состав которого входит метод Greet, возвращающий сообщение "Hello, world!".
<?php
class Hello
{
public function greet()
{
return 'Hello, world!';
}
}
Обратиться к методу можно, как и в случае переменных, при помощи оператора ->:
$obj = new Hello;
echo $obj->greet(); // Hello, world!

Обращение к переменным объекта


Для того чтобы обратиться к переменным или другим методам внутри метода, используется специальная переменная $this, после которой следуют оператор -> и название переменной или класса. Для объявления закрытых переменных, доступных только внутри класса или объекта, используется спецификатор доступа Private, в то время как для открытых - спецификатор доступа Public. Спецификаторы могут использоваться и в отношении методов.

Приведем пример использования закрытых методов класса:
<?php
class Sum
{
private $x;

public function setX($x)
{
$this->x = $x;
}

public function getX()
{
return $this->x;
}

public function result()
{
return $this->getX() + 2;
}
}
$sum = new Sum;
$sum->setX(2);
echo $sum->result(); // 4
Так как переменная доступна только внутри методов класса Sum, то обратиться напрямую через объект echo $sum->x уже не получится, и пользователь увидит ошибку: Fatal error: Uncaught Error: Cannot access private property Sum. Для этого можно использовать метод-аксессор, который устанавливает новое значение переменной :
void setX(int $x)
Кроме выше описанного метода, в примере используется дополнительный метод Result, который добавляет к значению переменной $x значения 2.

Статические методы


До текущего момента использовались методы объекта, которые можно было вызывать только после того, как объект класса создавался при помощи конструкции New. Даже когда методы вызывают другие методы при помощи $this->, они все равно обращаются к текущему объекту и не могут быть вызваны ранее, чем после создания объекта. Однако РНР предоставляет способ использования метода без объектов. Для этого метод следует объявить статическим при помощи ключевого слова Static:
<?php
class Greet
{
public static function hello()
{
return 'Hello, world!';
}
}
РНР допускает любой порядок следования спецификаторов доступа и ключевого слова Static перед именем функции. Однако согласно требованиям PSR ключевое слово Static всегда располагается после спецификаторов доступа.

Для вызова статического метода необходимо предварить его именем класса и оператором разрешения области видимости :: :
echo Greet::hello(); // Hello, world!

Ключевое слово self


Внутри метода класса, для того чтобы сослаться на статическую переменную этого же класса, не обязательно использовать имя класса перед оператором разрешения области видимости. Вместо него может использоваться ключевое слово self:
<?php
class Greet
{
public static function hello()
{
return 'Hello, world!';
}
public static function result()
{
return self::hello();
}
}
echo Greet::result(); // Hello, world!
Вариант с Self позволяет переименовывать класс без многочисленных исправлений и, как правило, более короток в написании.

Специальные методы


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

Конструктор __construct()


При создании объекта до вызова всех остальных нестатических методов класса можно автоматически выполнить специальный метод класса под названием конструктор, который используется главным образом для инициализации объекта, обеспечивая согласованное состояние объекта перед началом работы. Для объявления конструктора в классе необходимо создать метод с именем __construct(). Приведем пример, где при объявления класса Constructor, содержащего конструктор, будет выведено сообщение "Вызов конструктора" и инициализирует закрытый член $var.
<?php
class Constructor
{
private $var;

public function __construct()
{
echo 'Вызов конструктора';
$this->var = 5;
}
}
$obj = new Constructor;
print_r($obj);
Результатом работы скрипта являются следующие строки:
Вызов конструктора
Constructor Object
(
[var:Constructor:private] => 5
)
Конструктор выполняется автоматически во время выполнения оператора New. Это позволяет разработчику класса быть уверенным, что переменные класса получат корректную инициализацию.

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

Параметры конструктора


При объявлении объекта в круглых скобках после имени класса можно указать параметры, которые может принимать конструктор точно так же, как и любой другой метод. Для примере продемонстрируем класс Map, который имеет закрытую переменную и конструктор, инициализирующий закрытый член класса:
<?php
class Map
{
private $x;

public function __construct($x)
{
$this->x = $x;
}
public function getX()
{
return $this->x;
}
}
$obj = new Map(5);
echo $obj->getX();
Важно отметить, что если не указать аргумент при объявлении объекта, то произойдет ошибка "Fatal error: Uncaught ArgumentCountError: Too few arguments to function Map::__construct()". Однако можно использовать необязательные параметры. В следующем примере закрытая переменная получает значение 0, если ее значение не устанавливается через параметры конструктора.
public function __construct($x = 0)
{
$this->x = $x;
}

Дecтpyктop __destruct()


Деструктор - это специальный метод класса, который автоматически выполняется в момент уничтожения объекта. Данный метод вызывается всегда самым последним и используется главным образом для корректного освобождения зарезервированных конструктором ресурсов. Для объявления деструктора в классе необходимо создать метод с именем __destruct():
<?php
class Destructor
{
public function __destruct()
{
echo 'Вызов деструктора';
}
}
$obj = new Destructor();
Как можно видеть, деструктор выполняется в последнюю очередь и уничтожает объект при завершении работы скрипта. На практике деструктор используется довольно редко, даже в тех ситуациях, где он мог быть полезным. Дело в том, что деструктор вызывается лишь в том случае, когда объект штатно собирается сборщиком мусора. Это событие может произойти далеко не сразу или вообще не наступить, если возникает ошибка, исключительная ситуация или скрипт завершается извне.

Методы-аксессоры __set() и __get()


Использование преимущественно закрытых переменных, доступ к которым осуществляется через открытые методы, позволяет скрыть внутреннюю реализацию класса. PHP предоставляет возможность использовать специальные методы __set() и __get() - аксессоры, обращение к которым выглядит точно так же, как к открытым переменным класса.

Метод __get() предназначен для реализации чтения свойства, принимает единственный параметр, который служит ключом. Метод __get() позволяет присвоить свойству новое значение и принимает два параметра, первый из которых является ключом, а второй - значением свойства.

Приведем пример, в котором при помощи метода __set() объекту присваиваются новые свойства, которые помещаются в массив $this->arr, а перегруженный метод __get() позволяет извлечь их из массива.
<?php
class Accessor
{
private $arr = [];

public function __get($key)
{
return $this->arr[$key];
}

public function __set($key, $value)
{
$this->arr[$key] = $value;
}
}

$obj = new Accessor();

$obj->name = 'Hello, world!';
$obj->color = 'Red';

echo $obj->name;

echo "<pre>";
print_r($obj);
echo "</pre>";
Результатом работы скрипта являются следующие строки:
Hello, world!
Accessor Object
(
[arr:Accessor:private] => Array
(
[name] => Hello, world!
[color] => Red
)

)
Класс Accessor перехватывает все обращения к свойствам объекта и создает соответствующий элемент в закрытом массиве $arr. Любая попытка присвоить свойству значения приводит к созданию нового элемента закрытого массива $arr.

Динамические методы


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

При помощи специального метода __call() можно эмулировать методы с переменным количеством параметров. Для примера создадим класс MinМax, который предоставляет пользователю два метода: min() и max(), принимающие произвольное количество числовых параметров и определяющие минимальное и максимальное значения соответственно.
<?php
class MinMax
{
public function __call($method, $arg)
{
if (!is_array($arg)) {
return false;
}
$value = $arg[0];
if ($method == 'min') {
for ($i = 0; $i < count($arg); $i++)
{
if ($arg[$i] < $value) {
$value = $arg[$i];
}
}
}
if ($method == 'max') {
for ($i = 0; $i < count($arg); $i++) {
if ($arg[$i] > $value) {
$value = $arg[$i];
}
}
}
return $value;
}
}

$obj = new MinMax();
echo $obj->min(3, 7, 1, 2, 9, 5); // 1
echo '<br />';
echo $obj->max(3, 7, 1, 2, 9, 5); // 9
В зависимости от вызываемого метода - min() или max() - используются два разных алгоритма для поиска результата. Кроме того, для класса MinМax допустим вызов метода с произвольным именем, и, если оно отлично от min или max, будет возвращаться первый аргумент. Независимо от имени метода, если не передано ни одного аргумента, возвращается значение False.

По аналогии со специальным методом __call() существует статический вариант __callStatic(), позволяющий динамически создавать статические методы. Приведем пример реализации класса MinМax, в котором методы min() и max() определяются как статические:
<?php
class MinMax
{
public static function __callStatic($method, $arg)
{
if (!is_array($arg)) {
return false;
}
$value = $arg[0];
if ($method == 'min') {
for ($i = 0; $i < count($arg); $i++)
{
if ($arg[$i] < $value) {
$value = $arg[$i];
}
}
}
if ($method == 'max') {
for ($i = 0; $i < count($arg); $i++) {
if ($arg[$i] > $value) {
$value = $arg[$i];
}
}
}
return $value;
}
}

$obj = new MinMax();
echo $obj::min(3, 7, 1, 2, 9, 5); // 1
echo '<br />';
echo $obj::max(3, 7, 1, 2, 9, 5); // 9

Интерполяция объекта


Специальный мeтoд __toString() позволяет интерполировать (подставлять) объект в строку. Аналогично переменным, для подстановки значений которых необходимо заключить строку в двойные кавычки, такого же поведения можно добиться и от объекта, если реализовать в его классе метод __toString(), который преобразует объект в строку. Следует обратить внимание. что метод __toString() выводит результат при помощи конструкции return, а не echo.
public function __toString() {
return "({$this->x})";
}
Следует отметить, что вызов объекта в строковом контексте возможен, только если его класс содержит реализацию метода __toString, в противном случае попытка использовать объект в строке будет заканчиваться ошибкой "Recoverable fatal error: Object of class Point could not be converted to string".

Наследование


Использование повторного кода является одной из главных целей объектно-ориентированного подхода и одним из механизмов реализации заключается в использовании наследования.

Суть наследования заключается в возможности создания нового класса на основе уже существующего, автоматически включив в новый класс переменные и методы старого. При этом "старый" класс называется базовым, а вновь создаваемый класс - производным. При объявлении производного класса необходимо указать имя базового класса с помощью ключевого слова extends. Приведем пример, в котором будет создан класс Base и класс Derived, который будет наследовать первый класс.
<?php
class Base
{
public $first;
public function printFirst()
{
echo $this->first;
}
}
class Derived extends Base
{
public $second;
public function printSecond()
{
echo $this->second;
}
}

$obj = new Derived;

$obj->first = 100;
$obj->second = 200;

$obj->printFirst(); // 100
$obj->printSecond(); // 200
В базовом классе Base содержатся переменная $first и метод printFirst. От класса Base наследуется класс Derived, содержащий переменную $second и метод printSecond. Производный класс, в свою очередь, может выступать в качестве базового для других классов. В результате можно получить разветвленную иерархию классов, расширяя функциональность без повторного создания переменных и методов, используя уже имеющиеся из существующих классов.

Спецификаторы доступа и наследование


Все переменные и методы со спецификаторами доступа public и private при наследовании ведут себя по отношению к производному классу точно так же, как и внешний код в отношении объекта. Это означает, что из производного класса доступны все переменные и методы, объявленные со спецификатором доступа public, и не доступны компоненты, объявленные как private.

Иногда удобно, чтобы переменная или метод базового класса, оставаясь закрытыми для внешнего кода, были открыты для производного класса. В этом случае прибегают к спецификатору доступа protected. Снабженные им компоненты классы называют защищенными.

Перегрузка методов


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

Позднее статическое связывание


При наследовании классов надо уделять особое внимание статическим методам. Ключевое слово self позволяет ссылаться на статический метод и всегда ссылается на текущий класс, при помощи его нельзя обратиться к статическим методам базового класса. РНР предоставляет специальное ключевое слово static, которое можно задействовать вместо self и обращаться к статическим методам базового класса.

Добавить комментарий

Имя:
Текст комментария: