Php наследование классов конструктор

Обновлено: 28.04.2024

Наследование (inheritance) представляет один из ключевых аспектов объектно-ориентированного программирования, который позволяет наследовать функциональность одного класса или базового класса (base class) в другом - производном классе (derived class).

Зачем нужно наследование? Рассмотрим небольшую ситуацию, допустим, у нас есть классы, которые представляют человека и работника предприятия:

В данном случае класс Employee фактически содержит функционал класса Person: свойства name и age и функцию display. И было бы не совсем правильно повторять функциональность одного класса в другом классе, тем более что по сути сотрудник предприятия в любом случае является человеком. Поэтому в этом случае лучше использовать механизм наследования. Унаследуем класс Employee от класса Person:

Для установки отношения наследования после название класса ставится двоеточие, затем идет название класса, от которого мы хотим унаследовать функциональность. В этом отношении класс Person еще будет называться базовым классом, а Employee - производным классом.

Перед названием базового класса также можно указать спецификатор доступа, как в данном случае используется спецификатор public , который позволяет использовать в производном классе все открытые члены базового класса. Если мы не используем модификатор доступа, то класс Employee ничего не будет знать о переменных name и age и функции display.

После установки наследования мы можем убрать из класса Employee те переменные, которые уже определены в классе Person. Используем оба класса:

Таким образом, через переменную класса Employee мы можем обращаться ко всем открытым членам класса Person.

Конструкторы

Стоит учитывать, что конструкторы при наследовании не наследуются. И если базовый класс содержит только конструкторы с параметрами, то производный класс должен вызывать в своем конструкторе один из конструкторов базового класса. Например, добавим в классы выше конструкторы:

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

Если бы мы не вызвали конструктор базового класса, то это было бы ошибкой.

Консольный вывод программы:

Таким образом, в строке

Вначале будет вызываться конструктор базового класса Person, в который будут передаваться значения "Bob" и 31. И таким образом будут установлены имя и возраст. Затем будет выполняться собственно конструктор Employee, который установит компанию.

Также мы могли бы определить конструктор Employee следующим обазом:

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

Спецификатор protected

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

Например, определим переменную name со спецификатором protected:

Таким образом, мы можем использовать переменную name в производном классе, например, в методе showEmployeeName, но извне мы к ней обратиться по-прежнему не можем.

Запрет наследования

Иногда наследование от класса может быть нежелательно. И с помощью спецификатора final мы можем запретить наследование:

После этого мы не сможем унаследовать другие классы от класса User. И, например, если мы попробуем написать, как в случае ниже, то мы столкнемся с ошибкой:

Каждое определение класса начинается с ключевого слова class , затем следует имя класса, и далее пара фигурных скобок, которые заключают в себе определение свойств и методов этого класса.

Именем класса может быть любое слово, при условии, что оно не входит в список зарезервированных слов PHP, начинается с буквы или символа подчёркивания и за которым следует любое количество букв, цифр или символов подчёркивания. Если задать эти правила в виде регулярного выражения, то получится следующее выражение: ^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$ .

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

class SimpleClass
// объявление свойства
public $var = 'значение по умолчанию' ;

// объявление метода
public function displayVar () echo $this -> var ;
>
>
?>

Псевдопеременная $this доступна в том случае, если метод был вызван в контексте объекта. $this - значение вызывающего объекта.

Вызов нестатического метода статически вызывает ошибку Error . До PHP 8.0.0 это привело бы к уведомлению об устаревании, и $this не была бы определена.

class A
function foo ()
if (isset( $this )) echo '$this определена (' ;
echo get_class ( $this );
echo ")\n" ;
> else echo "\$this не определена.\n" ;
>
>
>

$a = new A ();
$a -> foo ();

$b = new B ();
$b -> bar ();

Результат выполнения данного примера в PHP 7:

Результат выполнения данного примера в PHP 8:

Для создания экземпляра класса используется директива new . Новый объект всегда будет создан, за исключением случаев, когда он содержит конструктор, в котором определён вызов исключения в случае возникновения ошибки. Рекомендуется определять классы до создания их экземпляров (в некоторых случаях это обязательно).

Если с директивой new используется строка ( string ), содержащая имя класса, то будет создан новый экземпляр этого класса. Если имя находится в пространстве имён, то оно должно быть задано полностью.

Замечание:

В случае отсутствия аргументов в конструктор класса, круглые скобки после названия класса можно опустить.

// Это же можно сделать с помощью переменной:
$className = 'SimpleClass' ;
$instance = new $className (); // new SimpleClass()
?>

Начиная с PHP 8.0.0, поддерживается использование оператора new с произвольными выражениями. Это позволяет создавать более сложные экземпляры, если выражение представлено в виде строки ( string ). Выражения должны быть заключены в круглые скобки.

В данном примере мы показываем несколько вариантов допустимых произвольных выражений, которые представляют имя класса. Пример вызова функции, конкатенации строк и константы ::class .

class ClassA extends \ stdClass <>
class ClassB extends \ stdClass <>
class ClassC extends ClassB <>
class ClassD extends ClassA <>

function getSomeClass (): string
return 'ClassA' ;
>

var_dump (new ( getSomeClass ()));
var_dump (new ( 'Class' . 'B' ));
var_dump (new ( 'Class' . 'C' ));
var_dump (new ( ClassD ::class));
?>

Результат выполнения данного примера в PHP 8:

В контексте класса можно создать новый объект через new self и new parent .

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

$instance = new SimpleClass ();

$assigned = $instance ;
$reference =& $instance ;

$instance -> var = '$assigned будет иметь это значение' ;

$instance = null ; // $instance и $reference становятся null

var_dump ( $instance );
var_dump ( $reference );
var_dump ( $assigned );
?>

Результат выполнения данного примера:

Создавать экземпляры объекта можно двумя способами:

class Test
static public function getNew ()
return new static;
>
>

class Child extends Test
<>

$obj1 = new Test ();
$obj2 = new $obj1 ;
var_dump ( $obj1 !== $obj2 );

$obj3 = Test :: getNew ();
var_dump ( $obj3 instanceof Test );

$obj4 = Child :: getNew ();
var_dump ( $obj4 instanceof Child );
?>

Результат выполнения данного примера:

Обратиться к свойству или методу только что созданного объекта можно с помощью одного выражения:

Результатом выполнения данного примера будет что-то подобное:

Замечание: До PHP 7.1 аргументы не имели значения, если не определена функция конструктора.

Свойства и методы

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

public function bar () return 'метод' ;
>
>

$obj = new Foo ();
echo $obj -> bar , PHP_EOL , $obj -> bar (), PHP_EOL ;

Результат выполнения данного примера:

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

public function __construct () $this -> bar = function() return 42 ;
>;
>
>

echo ( $obj -> bar )(), PHP_EOL ;

Результат выполнения данного примера:

extends

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

Наследуемые константы, методы и свойства могут быть переопределены (за исключением случаев, когда метод или константа класса объявлены как final) путём объявления их с теми же именами, как и в родительском классе. Существует возможность доступа к переопределённым методам или статическим свойствам путём обращения к ним через parent::

Замечание: Начиная с PHP 8.1.0, константы можно объявлять окончательными (final).

class ExtendClass extends SimpleClass
// Переопределение метода родителя
function displayVar ()
echo "Расширенный класс\n" ;
parent :: displayVar ();
>
>

$extended = new ExtendClass ();
$extended -> displayVar ();
?>

Результат выполнения данного примера:

Правила совместимости сигнатуры

При переопределении метода его сигнатура должна быть совместима с родительским методом. В противном случае выдаётся фатальная ошибка или, до PHP 8.0.0, генерируется ошибка уровня E_WARNING . Сигнатура является совместимой, если она соответствует правилам контравариантности, делает обязательный параметр необязательным и если какие-либо новые параметры являются необязательными. Это известно как принцип подстановки Барбары Лисков или сокращённо LSP. Правила совместимости не распространяются на конструктор и сигнатуру private методов, они не будут выдавать фатальную ошибку в случае несоответствия сигнатуры.

class Base
public function foo ( int $a ) echo "Допустимо\n" ;
>
>

class Extend1 extends Base
function foo ( int $a = 5 )
parent :: foo ( $a );
>
>

class Extend2 extends Base
function foo ( int $a , $b = 5 )
parent :: foo ( $a );
>
>

$extended1 = new Extend1 ();
$extended1 -> foo ();
$extended2 = new Extend2 ();
$extended2 -> foo ( 1 );

Результат выполнения данного примера:

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

class Base
public function foo ( int $a = 5 ) echo "Допустимо\n" ;
>
>

class Extend extends Base
function foo ()
parent :: foo ( 1 );
>
>

Результат выполнения данного примера в PHP 8 аналогичен:

class Base
public function foo ( int $a = 5 ) echo "Допустимо\n" ;
>
>

class Extend extends Base
function foo ( int $a )
parent :: foo ( $a );
>
>

Результат выполнения данного примера в PHP 8 аналогичен:

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

class A public function test ( $foo , $bar ) <>
>

class B extends A public function test ( $a , $b ) <>
>

// Передача параметров согласно контракту A::test()
$obj -> test ( foo : "foo" , bar : "bar" ); // ОШИБКА!

Результатом выполнения данного примера будет что-то подобное:

::class

Ключевое слово class используется для разрешения имени класса. Чтобы получить полное имя класса ClassName , используйте ClassName::class . Обычно это довольно полезно при работе с классами, использующими пространства имён.

Результат выполнения данного примера:

Замечание:

Разрешение имён класса с использованием ::class происходит на этапе компиляции. Это означает, что на момент создания строки с именем класса автозагрузки класса не происходит. Как следствие, имена классов раскрываются, даже если класс не существует. Ошибка в этом случае не выдаётся.

Результат выполнения данного примера:

Начиная с PHP 8.0.0, константа ::class также может использоваться для объектов. Это разрешение происходит во время выполнения, а не во время компиляции. То же самое, что и при вызове get_class() для объекта.

Результат выполнения данного примера:

Методы и свойства Nullsafe

Начиная с PHP 8.0.0, к свойствам и методам можно также обращаться с помощью оператора "nullsafe": ?-> . Оператор nullsafe работает так же, как доступ к свойству или методу, как указано выше, за исключением того, что если разыменование объекта выдаёт null , то будет возвращён null , а не выброшено исключение. Если разыменование является частью цепочки, остальная часть цепочки пропускается.

Аналогично заключению каждого обращения в is_null() , но более компактный.

// Начиная с PHP 8.0.0, эта строка:
$result = $repository ?-> getUser ( 5 )?-> name ;

// Эквивалентна следующему блоку кода:
if ( is_null ( $repository )) $result = null ;
> else $user = $repository -> getUser ( 5 );
if ( is_null ( $user )) $result = null ;
> else $result = $user -> name ;
>
>
?>

Замечание:

Оператор nullsafe лучше всего использовать, когда null считается допустимым и ожидаемым значением для возвращаемого свойства или метода. Для индикации ошибки предпочтительнее выбрасывать исключение.

User Contributed Notes 13 notes

I was confused at first about object assignment, because it's not quite the same as normal assignment or assignment by reference. But I think I've figured out what's going on.

First, think of variables in PHP as data slots. Each one is a name that points to a data slot that can hold a value that is one of the basic data types: a number, a string, a boolean, etc. When you create a reference, you are making a second name that points at the same data slot. When you assign one variable to another, you are copying the contents of one data slot to another data slot.

Now, the trick is that object instances are not like the basic data types. They cannot be held in the data slots directly. Instead, an object's "handle" goes in the data slot. This is an identifier that points at one particular instance of an obect. So, the object handle, although not directly visible to the programmer, is one of the basic datatypes.

What makes this tricky is that when you take a variable which holds an object handle, and you assign it to another variable, that other variable gets a copy of the same object handle. This means that both variables can change the state of the same object instance. But they are not references, so if one of the variables is assigned a new value, it does not affect the other variable.

// Assignment of an object
Class Object public $foo = "bar" ;
>;

$objectVar = new Object ();
$reference =& $objectVar ;
$assignment = $objectVar

//
// $objectVar --->+---------+
// |(handle1)----+
// $reference --->+---------+ |
// |
// +---------+ |
// $assignment -->|(handle1)----+
// +---------+ |
// |
// v
// Object(1):foo="bar"
//
?>

$assignment has a different data slot from $objectVar, but its data slot holds a handle to the same object. This makes it behave in some ways like a reference. If you use the variable $objectVar to change the state of the Object instance, those changes also show up under $assignment, because it is pointing at that same Object instance.

$objectVar -> foo = "qux" ;
print_r ( $objectVar );
print_r ( $reference );
print_r ( $assignment );

//
// $objectVar --->+---------+
// |(handle1)----+
// $reference --->+---------+ |
// |
// +---------+ |
// $assignment -->|(handle1)----+
// +---------+ |
// |
// v
// Object(1):foo="qux"
//
?>

But it is not exactly the same as a reference. If you null out $objectVar, you replace the handle in its data slot with NULL. This means that $reference, which points at the same data slot, will also be NULL. But $assignment, which is a different data slot, will still hold its copy of the handle to the Object instance, so it will not be NULL.

$objectVar = null ;
print_r ( $objectVar );
print_r ( $reference );
print_r ( $assignment );

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

15. Объектно-ориентированное программирование

15.1. Классы и объекты. Поля и методы

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

Объект - это экземпляр класса.

В PHP каждое определение класса начинается с ключевого слова class, затем следует имя класса, и далее пара фигурных скобок, которые заключают в себе определение свойств и методов этого класса.

Именем класса может быть любое слово, при условии, что оно не входит в список зарезервированных слов PHP, начинается с буквы или символа подчёркивания и за которым следует любое количество букв, цифр или символов подчёркивания.

15.2. Области видимости

Для полей и методов можно задавать области видимости

  • public — свойства или методы, объявленные как public, могут быть доступны в любом месте.
  • protected — protected свойства и методы доступны внутри класса, а также в дочерних классах.
  • private — доступ к private свойствам и методам имеет только класс, в котором эти свойства или методы объявлены

15.3. Переменная $this

Для того, чтобы обратиться к свойству или методу класса внутри другого метода этого класса, вместо имени объекта следует писать специальную переменную $this.

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

Конструкторы - это обычные методы, которые вызываются при создании соответствующих объектов. Следовательно, они могут иметь произвольное количество аргументов, которые могут быть обязательными, могут быть типизированными и иметь значения по умолчанию. Аргументы конструктора указываются в круглых скобках после имени класса.

Конструкторы при наследовании

Конструкторы, определённые в классах-родителях не вызываются автоматически, если дочерний класс определяет собственный конструктор. Чтобы вызвать конструктор, объявленный в родительском классе, требуется вызвать parent::__construct() внутри конструктора дочернего класса. Если в дочернем классе не определён конструктор, то он может быть унаследован от родительского класса как обычный метод (если он не был определён как приватный).

Приватный конструктор. Паттерн Singleton

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

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

15.5. Динамический вызов свойств и методов

15.6. Статические свойства и методы. Константы

При работе с классами можно делать методы, которые для своего вызова не требуют создания объекта. Такие методы называются статическими.

Чтобы объявить метод статическим, нужно после модификатора доступа (то есть после public, private или protected) написать ключевое слово static.

Статические свойства принадлежат не какому-то объекту класса, а самому классу, хотя объекты класса и имеют доступ к этим свойствам. Внутри статических методов нельзя использовать $this .

15.7. Магические методы

Метод __set() будет выполнен при записи данных в недоступные (защищённые или приватные) или несуществующие свойства.

Метод __get() будет выполнен при чтении данных из недоступных (защищённых или приватных) или несуществующих свойств.

Метод __call() запускается при вызове недоступных методов в контексте объект.

Метод __toString() позволяет классу решать, как он должен реагировать при преобразовании в строку. Например, что вывести при выполнении echo $obj; .

Полный список магических методов: __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __serialize(), __unserialize(), __toString(), __invoke(), __set_state(), __clone() и __debugInfo()

Обращение к родительскому классу из наследника

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

Перезапись конструктора родительского класса

15.9. Наследование vs композиция и аггрегация

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

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

Один класс использует внутри своей реализации свойства или методы объекта другого класса. При этом используемый объект создается внутри класса. Пример:

Композиция - это по сути включение класса, внутрь другого класса с помощью создания объекта внутри этого класса.

У такого подхода есть один недостаток - сильная связанность, это значит, что, для того чтобы поменять класс A на A1, вам придется переписывать конструктор (new A1 вместо A).

Преимущество у такого способа, это то, что класс B, управляет временем жизни объекта A. Т.е. при удалении объекта B будет и удален, объект A который был создан внутри B.

Один класс использует внутри своей реализации свойства или методы объекта другого класса. При этом используемый объект создается вне класса. Пример:

В отличие от композиции, тут все наоборот. Преимущество: легко передать новый объект A1 без изменений в коде - слабая связанность. Из недостатков, пожалуй, следует отметить, что в больших системах это приводит к огромному кол-ву переменных в конструкторе

Композиция лучше наследования

Как говорится в известной книге "Шаблоны проектирования" Банды четырёх, по мере возможности нужно выбирать композицию, а не наследование. Есть много хороших причин использовать как наследование, так и композицию. Главная цель этой максимы заключается в том, если вы инстинктивно склоняетесь к наследованию, то постарайтесь представить, может ли композиция лучше решить вашу задачу. В каких-то случаях это действительно более подходящий вариант.

Вы спросите: "А когда лучше выбирать наследование?" Всё зависит от конкретной задачи, но можно ориентироваться на этот список ситуаций, когда наследование предпочтительнее композиции:

  • Ваше наследование — это взаимосвязь is-a, а не has-a. Пример: Человек → Животное vs. Пользователь → Детали пользователя (UserDetails).
  • Вы можете повторно использовать код из базовых классов. (Люди могут двигаться, как животные.)
  • Вы хотите внести глобальные изменения в производные классы, изменив базовый класс. (Изменение расхода калорий у животных во время движения.)

15.11. Трейты вместо множественного наследования

В PHP нельзя наследовать от нескольких классов сразу, только от одного.

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

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

Синтаксис трейта такой же как и у класса, за исключением того, что имя трейта нужно объявлять с помощью ключевого слова trait.

Экземпляр трейта нельзя создать - трейты предназначены только для подключения к другим классам.

15.12. Абстрактные классы и интерфейсы. Полиморфизм

Абстрактные классы представляют собой классы, предназначенные для наследования от них. При этом объекты таких классов нельзя создать.

Пусть у вас есть класс User, а от него наследуют классы Employee и Student.

При этом предполагается, что вы будете создавать объекты классов Employee и Student, но объекты класса User - не будете, так как сам класс User используется только для группировки общих свойств и методов своих наследников.

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

Для того, чтобы объявить класс абстрактным, нужно при его объявлении написать ключевое слово abstract

Абстрактные классы также могут содержать абстрактные методы.

Такие методы не должны иметь реализации, а нужны для того, чтобы указать, что такие методы должны быть у потомков.

А собственно реализация таких методов - уже задача потомков.

Для того, чтобы объявить метод абстрактным, при его объявлении следует написать ключевое слово abstract.

При наследовании от абстрактного класса, все методы, помеченные абстрактными в родительском классе, должны быть определены в дочернем классе.

При этом область видимости этих методов должна совпадать или быть менее строгой. Что значит менее строгой: например, если абстрактный метод объявлен как protected, то реализация этого метода должна быть protected или public, но не private.

Объявления методов также должны совпадать: количество обязательных параметром должно быть одинаковым. Однако класс-потомок может добавлять необязательные параметры, которые не были указаны при объявлении метода в родителе.

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

Фактически ваш родительский класс описывает интерфейс потомков, то есть набор их публичных методов, обязательных для реализации.

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

Интерфейсы, так же, как и классы, могут наследовать друг от друга с помощью оператора extends. Каждый класс может реализовывать любое количество интерфейсов. Для этого имена интерфейсов нужно перечислить через запятую после ключевого слова implements.

В PHP пространства имён используются для решения двух проблем, с которыми сталкиваются авторы библиотек и приложений при создании повторно используемых элементов кода, таких как классы и функции:

  • Конфликт имён между вашим кодом и внутренними классами/функциями/константами PHP или сторонними.
  • Возможность создавать псевдонимы (или сокращения) для Ну_Очень_Длинных_Имён, чтобы облегчить первую проблему и улучшить читаемость исходного кода.

Пространства имён PHP предоставляют возможность группировать логически связанные классы, интерфейсы, функции и константы.

Для класса Page из файла /admin/page.php укажем пространство имен Admin:

А для класса Page из файла файл /users/page.php укажем пространство имен Users:

Давайте теперь в файле /index.php создадим объект одного и второго класса Page:

Конструкция use позволяет подключить класс по его полному имени, и после этого можно будет обращаться к этому классу просто по имени класса. Можно использовать псевдонимы для классов с помощью конструкции as . Это полезно, если вы хотите использовать классы с одинаковыми именами из разных неймспейсов

15.14. Автозагрузка через spl_autoload_register

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

Composer - это менеджер зависимостей для PHP. Вы можете описать от каких библиотек зависит ваш проект и Composer установит нужные библиотеки за вас.

При установке php пакетов Composer не просто устанавливает их, он также устанавливает все зависимости, от которых эти пакеты зависят. Т.е., например, если загружаемая библиотека будет зависеть от 3 других пакетов, а каждая из них, ещё в свою очередь от нескольких, то Composer всё это установит автоматически.

Загрузку сторонних библиотек Composer выполняет в папку vendor , которую данный php скрипт создаёт в корневой директории проекта. Также composer создаёт специальный файл autoload.php . Если вы подключите этот файл в проекте, вы сразу сможете использовать все загруженные библиотеки.

Пример: вызываем в консоли команду composer require monolog/monolog

Появятся файлы composer.json , composer.lock и папка vendor .

Файл composer.json - это главный файл Composer. В нем содержится описание основных пакетов, включая требования к их версиям.

Файл composer.lock - это файл, содержащий уже не требования, а реальные версии пакетов, которые были установлены на компьютер пользователя. Основное назначение файла composer.lock заключается в полном сохранении среды, в которой осуществлялась разработка и тестирование проекта. Например, если вы захотите скопировать проект в какое-то другое место без переноса файла composer.lock , то выполнив в нём команду composer install , вы можете получить другие версии пакетов. Это может случиться из-за выхода новых версий основных пакетов, описанных в файле composer.json , их зависимостей, зависимостей их зависимостей и т.д.

Если же вы разворачиваете проект, включающий в себя composer.lock , то вы получите те же версии зависимостей, которые были при разработке и тестировании. Поэтому composer.lock необходимо добавлять в git.

В папке vendor располагаются исходники библиотек и файл autoload.php . Вы можете подключить autoload.php и начать использовать классы, которые эти библиотеки предоставляют:

15.16. Автозагрузка собственных классов с помощью composer. Стандарт PSR-4

Вы даже можете добавить свой код в автозагрузчик, добавив поле autoload в composer.json

После добавления поля autoload в composer.json необходимо повторно выполнить команду dump-autoload для повторной генерации файла vendor/autoload.php

Composer зарегистрирует автозагрузчик PSR-4 для пространства имен App .

В таблице ниже представлены примеры соответствий полностью определённого имени класса, префикса пространства имён, базового каталога и итогового пути к файлу.

Fully Qualified Class Name Namespace Prefix Base Directory Resulting File Path
\Acme\Log\Writer\File_Writer Acme\Log\Writer ./acme-log-writer/lib/ ./acme-log-writer/lib/File_Writer.php
\Aura\Web\Response\Status Aura\Web /path/to/aura-web/src/ /path/to/aura-web/src/Response/Status.php
\Symfony\Core\Request Symfony\Core ./vendor/Symfony/Core/ ./vendor/Symfony/Core/Request.php
\Zend\Acl Zend /usr/includes/Zend/ /usr/includes/Zend/Acl.php

15.17 Reflection API

Рефлексия (отражение) - процесс, во время которого программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения. PHP включает в себя полноценный Reflection API, который предоставляет возможность проводить интроспекцию классов, интерфейсов, функций, методов и модулей. Кроме того, Reflection API позволяет получать doc-блоки комментариев функций, классов и методов.

Класс Profile является черным ящиком. И если у нас нет доступа к коду, то посмотреть как он устроен можно используя ReflectionClass:

ReflectionClass похож на аналитика для класса Profile, и это основная идея Reflection API.

PHP дает ключи к закрытым параметрам, поэтому мы можем получить доступ к ним с помощью:

ReflectionClass: сообщает информацию о классе. ReflectionFunction: сообщает информацию о функции. ReflectionParameter: извлекает информацию о параметрах функции или метода. ReflectionClassConstant: сообщает информацию о константе класса.

Reflection API входит в состав языка, поэтому нет необходимости в дополнительной установке или конфигурации.

Механизм рефлексии широко используется генераторами документаций, а так же в фреймворках для конфигурирования роутингов, параметров сериализации, настройки прав доступа и т.д.

15.18 SPL - Standart PHP Library

Стандартная библиотека PHP (SPL) - это набор интерфейсов и классов, предназначенных для решения стандартных задач.

SPL предоставляет ряд стандартных структур данных, итераторов для итерирования объектов, интерфейсов, стандартных исключений, некоторое количество классов для работы с файлами и предоставляет ряд функций, например spl_autoload_register().

Данный набор входит в состав языка, поэтому нет необходимости в дополнительной установке или конфигурации.

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

Магические методы в ООП

Чтобы упростить использование объектов, PHP также предоставляет некоторое количество магических методов (или специальных методов), которые вызываются, когда в пределах объекта происходят определенные действия. Это позволяет разработчикам выполнять некоторые полезные операции с большей легкостью.

Использование Конструкторов и Деструкторов

Когда мы создаем новый объект, часто бывает желательно сразу произвести определенные действия. Для того, чтобы осуществить это, PHP предлагает магический метод __construct(), который вызывается автоматически всякий раз, когда создается новый экземпляр класса.

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

Перезагрузите страницу в браузере и Вы увидите такой результат:

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

При использовании деструктора после перезагрузки страницы мы получим следующий результат:

"Когда достигнут конец файла, PHP автоматически освобождает все ресурсы"

Чтобы явно вызвать деструктор, Вы можете уничтожить объект с помощью функции unset():

При обновлении страницы в браузере Вы увидите следующую картину:

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

Преобразование в Строку

Для того, чтобы избежать ошибки при попытке вывести объект как строку, следует использовать еще один магический метод - __toString().

Без его использования такая попытка приведет к фатальной ошибке:

Выполнение этого кода приведет к следующему:

Для того, чтобы избежать этой ошибки воспользуемся методом __toString():

В этом случае, попытка вывести объект как строку ведет к обращению к методу getProperty().

Следует помнить о том, что метод __toString() вызывается только, если объект встречается напрямую в echo() или print().

Обновив страницу, Вы увидите:

Примечание:
Помимо тех магических методов, которые мы рассмотрели, существуют и другие. Для того, чтобы увидеть весь список этих методов, обратитесь к руководству по PHP

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

Классы могут наследовать методы и свойства других классов благодаря использованию ключевого слова extends. Например, для того, чтобы создать второй класс, который наследует класс MyClass и который имеет внутри себя метод, нужно поступить следующим образом:

Перезагрузите страницу и Вы увидите следующее:

Именно наследование приводит к тому, что при создании экземпляра класса MyOtherClass мы можем обращаться как к методам родительского класса MyClass, так и к методам самого класса MyOtherClass (который является дочерним по отношению к MyClass).

Перезапись (перегрузка) унаследованных Свойств и Методов

Для того, чтобы изменить значение или поведение существующего свойства или метода в новом классе, Вы можете просто перезаписать (перегрузить) его, объявив его в новом классе:

Это приведет к следующим изменениям на странице:

Сохранение исходной функциональности Метода при его перегрузке

Чтобы добавить новые функциональные возможности к унаследованному методу, сохраняя при этом оригинальный метод нетронутым, используйте ключевое слово parent с оператором (::). Рассмотрим это на примере функции-конструктора в классе MyOtherClass:

Такой подход приведет к тому, что на экран будут выведены результаты выполнения как конструктра родительского класса MyClass, так и дочернего класса MyOtherClass:

P.S. Хотите двигаться дальше в освоении PHP и ООП? Обратите внимание на премиум-уроки по различным аспектам сайтостроения, включая программирование на PHP, а также на бесплатный курс по созданию своей CMS-системы на PHP с нуля с использованием ООП:

Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!

Перед изучением данной статьи вы можете прочитать предыдущую статью из этой серии - "Наследование в PHP".

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

Мы знаем, что методы getPlayLength() и getNumberOfPages() противоречат друг другу. Нам также известно, что нужно создавать разные реализации метода getSummaryLine().

Давайте используем эти различия как основу для создания двух производных классов.

Чтобы создать дочерний класс, необходимо использовать в объявлении класса ключевое слово extends. В данном примере мы создали два новых класса - BookProduct и СdProduct. Оба они расширяют класс ShopProduct.

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

Дочерние классы наследуют доступ ко всем методам типа public и protected родительского класса (но не к методами и свойствам типа private).

Это означает, что мы можем вызвать метод getProducer() для экземпляра объекта класса CdProduct, хотя метод определен в классе ShopProduct.

Таким образом, оба наших дочерних класса наследуют поведение общего родительского класса. И мы можем обращаться с объектом BookProduct так, как будто это объект типа ShopProduct.

Мы можем передать объект BookProduct или CdProduct методу write() класса ShopProductWriter, и все будет работать как надо.

Обратите внимание на то, что для обеспечения собственной реализации в обоих классах CdProduct и BookProduct переопределяется метод getSummaryLine(). Производные классы могут расширять и изменять функциональность родительских классов. И в то же время каждый класс наследует свойства родительского класса.

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

Наличие этого метода в суперклассе также гарантирует для клиентского кода, что во всех объектах типа ShopProduct будет присутствовать метод getSummaryLine().

Позже вы увидите, как можно выполнить это требование в базовом классе, не предоставляя никакой его реализации. Каждый дочерний объект класса ShopProduct унаследует все свойства своего родителя. В собственных реализациях метода getSummaryLine() для обоих классов CdProduct и BookProduct обеспечивается доступ к свойству $title.

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

Чтобы понять это, нужно размышлять с точки зрения поиска. При вызове $product2->getProducer() интерпретатор PHP не может найти такой метод в классе CdProduct. Поиск заканчивается неудачей, и поэтому используется стандартная реализация этого метода, заданная в классе ShopProduct.

С другой стороны, когда мы вызываем $product2->getSummaryLine(), то интерпретатор PHP находит реализацию метода getSummaryLine() в классе CdProduct и вызывает его.

То же самое верно и в отношении доступа к свойствам. При обращении к свойству $title в методе getSummaryLine() из класса BookProduct, интерпертатор PHP не находит определение этого свойства в классе BookProduct. Поэтому он использует определение данного свойства, заданное в родительском классе ShopProduct.

Поскольку свойство $title используется в обоих подклассах, оно должно определяться в суперклассе.

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

Так, конструктору класса BookProduct должен передаваться аргумент $numPages, значение которого заносится в одноименное свойство, а конструктор класса CdProduct должен обрабатывать аргумент и свойство $playLength. Чтобы добиться этого, мы определим методы конструктора в каждом дочернем классе.

Конструкторы и наследование

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

Чтобы вызвать нужный метод из родительского класса, вам понадобится обратиться к самому этому классу через его дескриптор. Для этого случая в PHP предусмотрено ключевое слово parent.

Чтобы обратиться к методу в контексте класса, а не объекта, следует использовать символы "::", а не "->". Поэтому конструкция parent::__construct() означает следующее: "Вызвать метод __construct() родительского класса."

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

Каждый дочерний класс вызывает конструктор своего родительского класса, прежде чем определять собственные свойства. Базовый класс теперь "знает" только о собственных данных. Дочерние классы - это обычно "специализации" родительских классов.

Следует избегать того, чтобы давать родителским классам какие-либо особые "знания" о дочерних классах.

Вызов переопределенного метода

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

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

Мы определили основные функции для метода getSummaryLine() в базовом классе ShopProduct. Вместо того, чтобы воспроизводить их в подклассах CdProduct и BookProduct, мы просто вызовем родительский метод, прежде чем добавлять дополнительные данные к итоговой строке.

Теперь, когда мы познакомились с основами наследования, можно, наконец, рассмотреть вопрос видимости свойств и методов в свете полной картины происходящего.

Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!

Читайте также: