- 1. Введение в ООП
- 2. Свойства и методы объекта
- 3. Ключевое слово this (обращение к свойству объекта из метода)
- 4. Псевдо-константы
- 5. Магические методы
- 6. Копирование объектов
- 7. Наследование
- 8. Исключения в php\Обработка исключений
- 9. Абстрактные классы
- 10. Интерфейсы
- 11. Константы и статические члены класса
- 12. Функция __autoload
- 13. Модификаторы доступа
- 14. Магические методы __set, __get, __call, callstatic, __toString, __invoke
- 15. Сериализация объектов в php и магические методы __sleep() и __wakeup
- 16. Финальные классы и методы
- 17. Множественное наследование/ или Типажи(traits)
- 18. Уточнение типа данных в PHP
- 19. Анонимные классы
Введение в ООП
ООП — это идея, способ, парадигма, которая выражает собой объединение данных и кода, который их обрабатывает, в одном объекте.
Объект — некая сущность, у которой есть свойства (параметры, которые хранят состояние, данные) и методы (функции, определяющие, что объект может сделать. Как вариант, можно воспринимать методы как список приказов, которые может понять и выполнить объект). Можно привести такой пример — под объектом выступает человек и у него есть свойства:
- рост,
- вес,
- цвет кожи,
- возраст,
- и т.д.
У него есть методы:
- ходить,
- говорить,
- кидать камень,
- кодить на php,
- и т.д.
Три ключевых понятия объектно-ориентированного программирования:
- Инкапсуляция — механизм языка программирования, который ограничивает доступ к составляющим объект компонентам (методам и переменным), делает их приватными, т.е. доступными только внутри объекта.
- Наследование — механизм языка, позволяющий создать новый класс на основе уже существующего (родительского, базового) класса. Тем самым позволяет повторно использовать существующий код, а не описывать его снова.
- Полиморфизм — возможность класса-потомка менять реализацию класса-родителя, сохраняя при этом его интерфейс.
В ООП все построено на классах. Класс (Class) можно воспринимать как чертеж, на основе которого будут созданы будущие объекты. Следовательно, объект, созданный на основе класса, называется экземпляром класса. Другими словами, у вас есть чертеж вентилятора. Там будут свойства, такие как: цвет, вес, тип металла, диаметр лопастей и т.д., и методы такие как: вращать лопасти, остановить лопасти, повернуть голову вентилятора вправо, повернуть голову вентилятора влево.
Пример создания класса
class Fans { //Fans - имя класса //Описание свойств //Описание методов } //Создание экземпляра класса //Экземпляр создается при помощи ключевого слова new $fan = new Fans(); /*Экземпляр класса представляет собой ни что иное, как тип данных. А именно тип данных object.*/ class Fans{ //Описание свойств //Описание методов } $fan = new Fans(); echo gettype($fan);//object
Свойства и методы объекта
Свойства
По своей сути, свойства — это переменные внутри класса. Но со своими особенностями. Во-первых, у свойств есть модификаторы доступа, которые указываются перед именем свойства и определяют область видимости свойства. Это:
- public,
- protected,
- private.
Подробнее о модификаторах доступа тут.
Во-вторых, свойство принадлежит именно объекту. Это означает, что если создать экземпляры класса Pet, например cat и dog., то и у объекта cat и у dog будет набор свойств, объявленных в рамках класса.
Пример создания свойств
class Pet{ public $age = 3; //можно определять значение public $name; //можно не определять значение }
Так как тут указан модификатор public, к свойству будет иметь доступ любой экземпляр данного класса. Как на чтение значения, так и на запись.
$cat = new Pet(); $dog = new Pet();
Присваивание значения в свойство объекта
$cat->name = ‘Murzik’; //теперь в свойстве name объекта cat, значение Murzik $dog->name = ‘Tuzik’; //а в свойстве name объекта dog, значение Tuzik
Чтение значения из свойства объекта
echo $cat->name;// Murzik echo $dog->name;// Tuzik
Методы
По своей сути это обычная функция, которая описывает поведение объекта. Декларируется (объявляется) такая функция внутри тела класса. У метода по аналогии со свойствами есть модификаторы доступа. По умолчанию метод всегда public. Подробнее тут .
Пример
class Pet{ public $age; public $name; function say($x){ echo "Object said $x"; } } $cat = new Pet(); $dog = new Pet();
В данном примере, у нас есть 2 объекта cat и dog. У каждого из них теперь есть поведение. Для того, чтобы обратиться к методу, используется аналогичная со свойством конструкция.
$dog->say(‘Gav’); // Object said Gav $cat->say(‘Meow’); // Object said Meow
Теперь объекты умеют «говорить». Т.е. мы определили их поведение.
Ключевое слово this (обращение к свойству объекта из метода)
Для того, чтобы внутри метода обратиться к свойству используется ключевое слово this.
Пример
class Pet{ public $name; function say($x){ //осуществляем доступ к свойству echo "Object $this->name said $x”; } } $cat = new Pet(); $cat->name = "Murzik"; $cat->say(‘Meow’); //Object Murzik said Meow $dog = new Pet(); $dog->name = ‘Tuzik’; $dog->say(‘Gav’); //Object Tuzik said Gav
Слово this означает — «этого объекта» или «того объекта, который вызывает метод», «принадлежащее объекту, который вызывает данный метод». Т.е. мы записали в свойство name, объекта cat, значение Murzik. В свойстве name у объекта dog будет значение Tuzik. Следовательно, если бы в методе say было написано так:
function say($x){ echo "Object $name said $x"; }
тогда при вызове этого метода у любого из существующих экземпляров класса, интерпретатор не смог бы понять, чей именно name нужно использовать. Ключевое слово this как раз и говорит, что нужно обратиться к свойству того объекта, который вызвал этот метод.
Также из метода можно обратиться к другому методу этого же объекта. Для этого, по аналогии, используется ключевое слово this.
Например
class Pet{ public $name; function say($x){ echo "Object $this->name said $x"; $this->drawLine(); //осуществляем доступ к методу } function drawLine(){ echo "<hr>"; } }
Псевдо-константы
В ООП есть несколько псевдо-констант. Эти константы отличаются от обычных тем, что значение в них меняется.
Пример
class SuperClass{ function functionName(){ echo "<p>Вызвана функция " . __FUNCTION__; } function className(){ echo "<p>Используем класс " . __CLASS__; } function methodName(){ echo "<p>Вызван метод " . __METHOD__; } } // Создание объекта $obj = new SuperClass(); // Используем псевдо-константы $obj->functionName(); // вызвана функция functionName $obj->className(); // используем класс SuperClass $obj->methodName(); // вызван метод SuperClass::methodName
Магические методы
(Мануал https://www.php.net/manual/ru/language.oop5.magic.php)
В ООП есть ряд магических методов. Суть всех этих методов в том, что они вызываются НЕ классическим способом $obj_name->method(), а вызывают их те или иные события, происходящие с экземпляром класса.
Существуют следующие магические методы:
__construct — вызывается при создании объекта.
class Pet{ public $name; function __construct($x){ echo "class object " . __CLASS__ ." said - $x"; } } //создаем объект и провоцируем, тем самым, вызов метода __construct. $cat = new Pet('I created');
ВАЖНО!
При создании объекта, после имени класса, указываются круглые скобки. Пример $cat = new Pet(). Эти скобки не являются обязательными и экземпляр можно создать без них. Но их использование предназначено именно для того, чтобы передать аргумент в магический метод __construct.
Может подходить для ситуаций, в которых, например, нужно послать cookie или соединиться с базой данных, при создании нового пользователя. И для многих других.
__destruct — данный метод является логической противоположностью методу __construct. Т.е. он вызывается при удалении объекта. Т.е. либо при unset($obj) или в конце выполнения скрипта.
Пример
class SuperClass{ public $number; function __construct($number){ $this->number = $number; } function __destruct(){ echo "object in $this->number number deleted"; } } $obj = new SuperClass(1); unset($obj);
Важно
Существует еще ряд методов, относящихся к магическим, но они будут разобраны как самостоятельные темы.
Копирование объектов
(Ур. 3 Д. 1 тайм код 0.58.13)
При копировании всех типов данных, кроме объекта, php работает двумя способами. Первый:
$x = 10; $y = $x; echo $y; //10
В данном примере в памяти создается новая ячейка и туда копируется значение переменной x. В результате $y будет полностью самостоятельной копией.
Второй:
Это способ не создавать самостоятельную копию, а получать новую ссылку на тоже значение.
Пример
$x = 10; $y = &$x;
В данном примере в переменную y будет скопировано не значение, а ссылка на значение, т.е. в памяти так и останется только одно значение 10. Иными словами и X, и Y суть одно и тоже, ссылки на ячейку памяти со значением 10.
Когда речь идет об объектах, то копирование всегда происходит по ссылке. При этом знак & ставить не нужно.
Пример
// Создание объекта $objX = new MyClass(10); // $objX - ссылка на объект в памяти $objY = $objX; // $objY - ссылка на тот же объект, на который ссылается $objX
Для более глубокого представления процесса копирования объекта разберем такой пример.
Шаг 1 мы создаем экземпляр класса.
$objX = new MyClass(10);
Тут у нас есть два действия. Первое — это new и второе — присваивание =.
Так вот, new создаст в памяти объект и установит значение, например 10.
После этого произойдет присваивание ссылки на этот объект переменной $objX.
Далее, при попытке копирования $objY = $objX, будет создана еще одна ссылка на этот объект.
Как результат:
- изменить значение объекта можно как по ссылке objX, так и по ссылке objY,
- при удалении любой из этих ссылок объект продолжит существовать,
- объект будет помечен к удалению только после того, как величина имеющихся на него ссылок будет равна нулю.
Как видим, объекты скопировать невозможно. Мы лишь получаем один и тот же объект под разными именами.
Важно!
Основываясь на вышесказанном, можно описать то, как объекты будут вести себя при сравнении. При сравнении двух переменных с ссылками на один и тот же объект, они всегда будут равны.
class Myclass{ //Описание свойств //Описание методов } $a = new MyClass(); $b = $a; if($a === $b){ echo "Ссылки на один и тот же экземпляр всегда равны"; }
А вот если сравнить две переменные с ссылками на разные на объекты, то они всегда неравны. Даже если содержание объектов идентично!
class Myclass{ public $color = 'red'; } $a = new MyClass(); $b = new MyClass(); if($a !== $b){ echo "Два разных объекта всегда неравны!"; }
Клонирование объектов
В php все-таки есть способ клонировать объекты. Для этого применяется ключевое слово clone.
$objY = clone $objX; //$objY копия $objX
Как результат, мы получим отдельный/самостоятельный экземпляр класса. Важно учитывать, что при клонировании не срабатывает __construct. Если все-таки необходимо сделать так, чтобы при клонировании автоматически срабатывал метод, нужно в классе, экземпляром которого является клонируемый объект, прописать метод __clone.
Пример:
class MyClass{ public $value; public $cloneValue; function __construct($value){ $this->value = $value; } function __clone(){ $this->cloneValue = $this->value + 1; } } $a = new MyClass(1); $b = clone $a; echo $b->cloneValue; //2
Важно!
Все, что будет сделано при помощи метода __clone, не будет существовать в оригинальном экземпляре.
class MyClass{ public $value; public $cloneValue = 0; function __construct($value){ $this->value = $value; } function __clone(){ $this->cloneValue = $this->value + 1; } } $a = new MyClass(1); $b = clone $a; echo $b->cloneValue; //2 echo $a->cloneValue; //0 несмотря на то, что мы обращаемся к свойству $cloneValue уже после того, как отработал clone, значение в свойстве останется 0.
Наследование
(Ур. 3 Д.1 тайм код 1.09)
Это одно из ключевых понятий ООП. Позволяет повторно использовать весь существующий функционал класса, а также все свойства и расширять его новым функционалом, при необходимости. Т.е., у нас может быть чертеж дома. Некая базовая концепция дома. Фундамент, стены, пол, крыша, окна и т.д. А далее, у нас появляется заказчик, который говорит: «ок, дом хорош, но я хочу еще и камин.» Так вот, в данном случае мы и будем использовать наследование. Мы наследуем от базового чертежа дома все свойства и методы и просто дополним его камином.
Пример
class BaseHouse{ public $model; public $square = 0; public $floors = 0; public $color = 'none'; function __construct($model, $square, $floors, $color){ $this->model = $model; $this->square = $square; $this->floors = $floors; $this->color = $color; } function startProject(){ echo "Start. Model: {$this->model}\n"; } function stopProject(){ echo "Stop. Model: {$this->model}\n\n"; } function build(){ echo "Build. House: {$this->square}x{$this->floors}\n"; } function paint(){ echo "Paint. Color: {$this->color}\n"; } } //Создание простого дома $house = new BaseHouse('t1000', 120, 2, 'white'); $house->startProject(); $house->build(); $house->paint(); $house->stopProject(); class DerivedHouse extends BaseHouse{ //Все свойства и методы перешли по наследству в этот класс. При помощи слова extends public $fireplace = true; //Мы просто добавили камин function fire(){ //И расширили класс методом розжига камина if($this->fireplace){ echo "Fueled fireplace\n"; } } } //Создание супер дома $superHouse = new DerivedHouse('t1000', 120, 2, 'white'); $superHouse->startProject(); //Пример использования метода наследника, который не описан в классе DerivedHouse $superHouse->build(); $superHouse->paint(); $superHouse->fire(); $superHouse->stopProject();
Важно!
Наследоваться можно только от одного класса. Множественное наследование в php недоступно.
Тут может возникнуть справедливый вопрос: как сделать так, чтобы наследуемый метод работал немного не так, как в базовом классе, т.е. реализовать полиморфизм. Сделать это предельно просто. При помощи перегрузки/переопределения метода.
Пример
class A{ function test(){ echo 1 + 1; } } class B extends A{ function test(){ // Мы перегружаем метод в классе-наследнике, просто описав его с аналогичным именем, что позволяет изменить и тело метода. echo 2 + 2; } } $b = new B(); $b->test(); //4 Без переопределения было бы 2
Если понадобится, чтобы при переопределении отработал и код, метода test, из родительского класса, необходимо использовать ключевое слово parent.
Пример
class A{ function test(){ echo 1 + 1; } } class B extends A{ function test(){ echo 2 + 2; $a = parent::test();//тут произойдет вызов этого метода из класса родителя. } } $b = new B(); $b->test(); //4 и 2
Примечание
Если parent::test() присвоить переменной при условии, что в родительском методе не будет return, ей присваивается NULL. В тех же случаях, когда в методе родителя есть return, в переменную присваивается возвращаемое значение.
Пример
class A { function test(){//Пример без return echo 1 + 1; } } class B extends A { function test(){ echo 2 + 2; $curiosity = parent::test(); //Присваиваем выражение переменной. Тут выполнится //метод test из родительского класса, а в переменную присваивается NULL echo gettype($curiosity); } } $b = new B(); $b->test(); //вызываем без return и получим 4 2 NULL.
Пример 2
class A{ function test(){ //Пример с return echo 1 + 1; return 1; } } class B extends A{ function test(){ $curiosity = parent::test();//Присваиваем выражение переменной echo "<br>"; echo 2 + 2 + $curiosity; } } $b = new B(); $b->test(); //Вызываем с return. Получим 2 5
Как результат, наследование дает преимущество в виде повторного использования данных класса.
Примечание!
Если вы переопределяете некоторый метод или свойство в производном классе, то
должны указать у него такой же модификатор доступа, либо менее строгий.
Исключения в php\Обработка исключений
(Ур. 3 Д. 1 тайм код 1.32.50)
Идея данного механизма представляет из себя следующее. У нас есть кусок кода, в котором возможна ошибка или исключительная ситуация. Эту часть кода заключаем в некий логический блок. Именуется данный блок try. Далее внутри этого блока, в случае появления исключительной ситуации, выполняется создание экземпляра класса и перебрасывания ссылки на него в другой логический блок. Важно, что выполнение скрипта останавливается на той строчке, где был создан объект и переброшена ссылка в другой блок. Название второго блока — это catch.
Пример
function funcName($value){ try{ echo 'Тут начинается некий код' . '\n'; if(!$value){ //если false, значит это исключительная ситуация echo 'раз уж мы сюда зашли, значит “это” случилось.'. '<br>'; throw new Exception('Тут можно описать суть исключения.'. '<br>'); //Создание объекта Exception //и переброс ссылки на него //в блок catch, при помощи ключевого слова throw } echo 'А если исключительной ситуации не случилось,'. '<br>'; echo 'Тут может отрабатывать другой код'. '<br>'; echo 'Я отработаю ТОЛЬКО если не случилось исключение.'. '<br>'; }catch(Exception $error){//Этот блок ловит посланную ему ссылку //и присваивает ее в переменную внутри круглых скобок после //указания имени класса. echo '<strong>'. $error->getMessage() . '</strong>';//Выведет то описание, которое мы послали при создании объекта Exception. echo '<strong>'. $error->getFile() . '</strong><br>';//Выведет имя файла echo '<strong>'. $error->getLine() . '</strong><br>';//Выведет строку на, которой случилось исключение } } funcName(false);//Посылаем false, чтобы сработало исключение
Пример ситуации, в которой может быть полезно использовать обработку исключений. Представим себе, что у нас есть цепочка функций, выполняющих код, для которого нужен результат следующей функции, а той — следующей и т.д.
function a(){ return 1 + b(); } function b(){ return 2 + c(); } function c(){ return 1; } //и т.д.
Например, у нас цепочка из 25 функций. Так вот, если в 25-й функции случится ошибка, нам придется 25 раз передавать сообщение об этом. Или мы можем использовать перехватчик исключений.
Пример
function a(){ try{ return 1 + b(); }catch(Exception $error){ echo $error->getMessage(); echo $error->getLine(); } } function b(){ return 2 + c(); } //... function c($param = 0){ if($param) return 1; throw new Exception('в двадцать пятую функцию пришел false'); } echo a();
Наследование класса Exception
Так как Exception — это класс, значит его можно наследовать. А, следовательно, мы можем расширить класс или переопределить методы.
Пример
//Объявляем свой класс class myException extends Exception{ public $message; function __construct($message){ $this->message = $message; } function myGetMessage(){ echo $this->message; } function myNewCode(){ return 1;//Тут может быть любой код } } function a(){ try{ return 1 + b(); }catch(myException $error){ echo $error->myGetMessage(); echo $error->myNewCode(); } } function b(){ return 2 + c(); } //... function c($param = 0){ if($param) return 1; throw new myException('в двадцать пятую функцию пришел false'); } echo a();
Важно!
Можно использовать более одного блока catch. Т.е. оригинальный и любую комбинацию наследников. Но важно, чтобы оригинальный catch был последним в списке.
Пример
//Объявляем свой класс class myException extends Exception { public $message; function __construct($message){ $this->message = $message; } function myGetMessage(){ echo $this->message; } function myNewCode(){ return 1; } } //Объявляем блок try и два catch $a = 'Exception'; try{ echo 'начало кода' . '<br>'; if($a == 'myException') throw new myException('ситуация 1'); if($a == 'Exception') throw new Exception('ситуация 2'); echo 'Исключений не случилось'; }catch(myException $error){ echo $error->myGetMessage(); }catch(Exception $error){ //Exception с оригинальным классом объявляем последним echo $error->getMessage(); }
Блок Finally
К вышеописанной паре блоков try и catch можно добавлять блок finally. Этот блок отработает в любом случае.
Так же важно, что все эти блоки могут что-то возвращать в скрип.
Пример
function test($value){ try{ echo 'start' . '<br>'; if(!$value){ throw new Exception('ля-ля-ля тополя' . '<br>'); } return 1; }catch(Exception $e){ echo $e->getMessage(); return 2; }finally{ echo 'какой-то код в finally' . '<br>'; return 3; } echo 'конец кода'; } echo test(1);
Разница между try\catch и error handler
(Ур. 1 Д. 1 1:54:02 )
В завершении разберем разницу в работе try/catch и error handler.
Отработка error handler:
- выполняется код до строки, где происходит ошибка (например, строка 5),
- эта ошибка принимается функцией и обрабатывается,
- выполнение кода продолжается с шестой строки (рис. 1).
Рисунок №1 — Разница между работой try_catch и error hendler 1
Отработка try/catch:
- выполняется код до строки, где происходит исключение (например, строка 5),
- ключевое слово throw перебрасывает ссылку в блок catch (например 20 строка)
- выполняется блок catch
- код идет далее
- как результат строки с шестой по 20 не выполняются (рис. 2)*
Рисунок №2 — Разница между работой try_catch и error handler 2
Абстрактные классы
(Ур. 1 Д. 1 тайм код 1:56:55 )
Это в некотором роде набросок класса. Он описывает основную идею и не позволяет создавать экземпляр (объект). Абстрактный класс может иметь абстрактные методы, но НЕ ОБЯЗАТЕЛЬНО. У этих методов нет реализации, нет тела. Они выражают идею того, что класс ОБЯЗАТЕЛЬНО должен иметь такой-то и такой функционал, но конечную реализацию оставляют для класса наследника.
Пример
abstract class MyAbstractClass { //имеет дополнительное слово abstract public $color = 'red'; public $propArr; function getColor(){ return $this->color; } abstract function sortProp(); // Объявляем абстрактный метод. Но без реализации. abstract function mySumm($v, $v2); //Если мы укажем определенный набор аргументов //тогда они обязательно должны быть в том же количестве в классе наследнике //при реализации } class MyClass extends MyAbstractClass{ function sortProp(){ //Реализуем обязательный метод echo 'что-то сортируем'; } function mySumm($v, $v2){ return $v + $v2; } } $obj = new MyClass(); echo $obj->getColor(); //red echo $obj->mySumm(2, 2); // 4 $obj->sortProp(); // что-то сортируем //При попытке создать экземпляр класса от абстрактного класса получим ошибку. $a = new myAbstractClass();// Fatal error: Cannot instantiate abstract class myAbstractClass
Интерфейсы
(Ур. 1 Д. 1 тайм код 2:11:00)
Это абстрактные классы, которые содержат только абстрактные методы, а также могут содержать константы. В данном типе классов, при создании, не нужно писать слово abstract, а используется слово interface. Интерфейсы применяются в основном для стандартизации содержания и поведения класса. Иными словами, позволяют архитектору определить набор функций без реализации, набор аргументов и тем самым ответить на вопрос «что мы делаем?» без ответа на вопрос «как?».
Создать объект от интерфейса невозможно. Так как в интерфейсе все методы абстрактные, а при попытке создать свойство возникнет ошибка (Interfaces may not include member variables) , наследовать собственно нечего. В следствие этого interface не наследуются, а реализуются. Основное отличие от абстрактных классов это то, что можно реализовывать больше одного интерфейса. Как результат, класс потомок может наследовать один класс и реализовывать сколько угодно интерфейсов.
Пример
interface MyInterface{ function test($v); //писать abstract не нужно. } class myClass implements MyInterface{ //реализация осуществляется при помощи слова implements function test($v){ //В реализующем интерфейс классе, //обязательно должен быть описан абстрактный метод интерфейса. } }
Иногда в работе с интерфейсами, да и с классами в общем, может понадобиться узнать, есть ли в цепочке предков тот или иной класс или интерфейс в частности. Например, у нас есть абстрактный класс здание. От этого класса наследуется класс жилой дом и класс склад. Дополнительно, у нас есть интерфейс, описывающий методы окрашивания. И допустим этот интерфейс реализуется в классе «жилой дом» и не реализуется в классе «склад». Так как склад, например, не должен краситься вообще. При этом у нас может быть программист, который не имеет отношения к созданиям классов и интерфейсов. Он будет просто получать экземпляр класса «жилой дом» или «склад». Также он знает, что есть или будет интерфейс «окрашивание». Так вот, он может просто проверять есть ли в цепочке предков тот или иной интерфейс или класс. И если есть, вызывать метод покраски, а если нет — идти дальше.
Пример:
//Абстрактный класс здание abstract class Building { public $color = 'none'; public $model; public $wallColor; abstract function start_building(); //... abstract function stop_building(); } //Интерфейс окрашивание interface Paintable { function paint(); } //Класс жилой дом class House extends Building implements Paintable { function start_building(){ echo "Начинаем строительство"; } function stop_building(){ echo "Остановка строительства\n"; } function __construct($color){ $this->color = $color; } function paint(){ //устанавливаем определенный цвет здания $this->wallColor = $this->color; } } //Класс склад class Stock extends Building{ function start_building(){ echo "Начинаем строительство\n"; } function stop_building(){ echo "Остановка строительства\n"; } } //Создание экземпляров классов $house = new House('red'); $stock = new Stock(); //Код конечного программиста //... if($house instanceof Paintable){//Оператор instanceof проверяет есть ли в цепочке предков //объекта $house, класс/интерфейс по имени Paintable. Если есть тогда //возвращается true. $house->paint(); } if($stock instanceof Paintable){ $stock->paint(); } echo $house->color;//red echo $stock->color;//none
Константы и статические члены класса
(Ур. 1 Д. 1 2:27:25)
Константы
Внутри класса, так же как и в глобальной области видимости, могут использоваться константы. Объявляются они следующим образом.
const NAME = «значение константы»;
В отличии от свойств, константа принадлежит именно классу, а не экземпляру класса. Т.е. константа одна, для всех объектов этого класса. Как результат, при обращении к константе из метода используется уже не this , а ключевое слово self и двойное двоеточие «Paamayim Nekudotayim» ::, а далее имя константы NAME. Для обращения к константе из-за пределов класса также используется двойное двоеточие, className::CONST_NAME. Следовательно, к константе можно обратиться не создавая экземпляра класса.
Пример
class myClass{ const NAME = 'my name is Vitaliy'; function getValueConst(){ echo self::NAME; //Обращаемся к константе из метода } } echo myClass::NAME; //Обращаемся к константе за пределом класса без создания экземпляра
Статические свойства
доп. мат. — https://habr.com/ru/post/259627/
Также как и в обычных функциях можно объявлять статические переменные, внутри класса можно создавать статические свойства. Они являются свойствами самого класса.
Объявляются при помощи ключевого слова static. При обращении из метода к статическому свойству используется ключевое слово self, далее двойное двоеточие — :: и имя свойства — $name.
Пример
class MyClass{ public static $count = 0; function __construct(){ $this->counter(); } function counter(){ self::$count++; //Знак доллара указывается именно с именем свойства. //В противном случае это было бы обращение к константе. } } $a = new MyClass(); $b = new MyClass(); $c = new MyClass(); echo MyClass::$count; //В основном это может применяться для создания счетчиков. // например, для подсчета количества созданных экземпляров класса.
Статические методы
В объектно-ориентированной модели существуют еще и статические методы. Данные методы принадлежат классу. Поэтому обращение к статическому методу осуществляется через слово self и :: methodName() или $ClassName::methodName() при обращении за рамками класса.
Для объявления статического метода используется слово static.
class MyClass { static function test(){ //объявляем статический метод //Внутри статического метода не должно быть $this echo 1; } function test2(){ self::test();//Обращаемся к статическому методу из другого метода. } } MyClass::test(); //1. Вызываем статический метод извне класса $a = new MyClass(); $a->test2();//1
Важно!
Если статический метод не содержит в теле $this, его можно вызвать и в стандартном динамическом контексте.
class MyClass{ static function test(){ echo 'какой-то результат'; } } $a = new MyClass(); $a->test();//какой-то результат
Обратиться же к статическому свойству в динамическом контексте невозможно.
Позднее статическое связывание
LSB (Late Static Binding, LSB) дает возможность унаследованным методам обращаться к статическим свойствам, методам и константам класса потомка. Без этого функционала, если наследуемый метод обратится к статическому свойству, он получит его из класса родителя, даже если в классе наследнике свойство переопределено.
Пример без LSB
class Beer { public static $name = 'Beer!'; public function getDrinkName() { return self::$name; } } class Ale extends Beer { public static $name = 'Ale!'; //Переопределяем статическое свойство } $beerDrink = new Beer; $aleDrink = new Ale; echo "Beer is: " . $beerDrink->getDrinkName() ."\n"; //Beer is: Beer! echo "Ale is: " . $aleDrink->getDrinkName() ."\n"; //Ale is: Beer!
Не смотря на то, что метод getDrinkName унаследован классом Ali, он все же продолжит обращаться к статическому методу родителя. Т.е. его self продолжает указывать на класс, в котором объявлен изначально.
Для тех случаев, когда нам нужно, чтобы наследуемый метод все-таки обращался к своему статическому свойству, мы используем LSB через ключевое слово static.
Пример с LSB
class Beer { public static $name = 'Beer!'; public function getDrinkName() { return static::$name; //Заменяем self на static } } class Ale extends Beer { public static $name = 'Ale!'; //Переопределяем статическое свойство } $beerDrink = new Beer; $aleDrink = new Ale; echo "Beer is: " . $beerDrink->getDrinkName() ."\n";//Beer is: Beer! echo "Ale is: " . $aleDrink->getDrinkName() ."\n";//Ale is: Ale!
Интересно!
В качестве альтернативы, можно просто переопределить метод getDrinkName в классе наследнике. В этом случае, мы получим аналогичный результат.
class Beer { public static $name = 'Beer!'; public function getDrinkName() { return self::$name; } } class Ale extends Beer { public static $name = 'Ale!'; //Переопределяем статическое свойство public function getDrinkName_NEW() { return self::$name; } } $beerDrink = new Beer; $aleDrink = new Ale; echo "Beer is: " . $beerDrink->getDrinkName() ."\n"; //Beer is: Beer! echo "Ale is: " . $aleDrink->getDrinkName_NEW() ."\n"; //Ale is: Ale!
Функция __autoload
Ур. 3 Д. 1 тайм код 3:03:00
Данная функция срабатывает при попытке создать объект от необъявленного в скрипте класса.
Представим ситуацию. Мы имеем такой код
$a = new NoneExistentClass();
Больше в файле ничего нет. Т.е., мы пытаемся создать экземпляр от несуществующего класса и как результат получим ни что иное, как Fatal error: Class ‘NoneExistentClass’ not found.
Но перед тем, как вывести ошибку, интерпретатор проверит, а не объявили ли мы функцию __autoload. И если объявили, тогда передает туда имя класса, от которого пытаемся создать объект.
Пример
__autoload($name){ //Событие 2 echo $name; } $a = new NoneExistentClass(); //Событие 1
На вопрос «а зачем это нужно?», можно ответить так — позволяет задать путь к каталогу с классами и автоматически подключать классы при обращении к ним в теле программы. Это значит, что каждый отдельный класс мы описываем в отдельном файле. Имена файлов должны соответствовать имени самого класса. Как результат, мы получаем удобную в использовании декомпозицию кода. Декомпозиция — разделение некоего общего целого на части.
Например, у нас есть 100 классов и их используют в разной пропорции 3 скрипта. Нам не нужно писать в каждом скрипте десятки include\require. Мы используем __autoload.
Пример
__autoload($name){ // require_once "class/$name.php"; //Представим, что у нас есть папка class, а в ней все //файлики с отдельными классами. } $a = new ExistingClass();
Теперь уже мы не получим ошибку. А получим экземпляр класса, описанного в файле ExistingClass.php.
ВАЖНО!
Начиная с версии 7.2 объявлена устаревшей. Рекомендуется использовать функцию spl_autoload_register. Описание стандартной библиотеки SPL.
Модификаторы доступа
(Ур. 1 Д. 1 тайм код 3:11:15)
доп. мат — https://myrusakov.ru/modifikatory-dostupa-php.html
Модификаторы доступа — это ни что иное, как представление инкапсуляции, в языке php. Инкапсуляция — это способ скрыть реализацию и упаковка данных в единый компонент. Всего у нас есть три модификатора. Это:
- public — к свойству/методу с этим модификатором можно обращаться из любой части скрипта.
- protected — к свойству/методу с этим модификатором можно обращаться только из класса, в котором объявлено свойство и/или из класса наследника.
- private — свойство/метод доступно только в рамках класса, в котором объявлено.
Иными словами, это три параметра, определяющих область видимости свойств и методов, которые позволяют обеспечить или ограничить доступ к тем или иным свойствам/методам.
У метода по умолчанию стоит public. А вот для определения метода как protected или private, нужно по аналогии со свойством использовать ключевые слова. Допустим, у нас есть свойства, которые нужно защитить от перезаписи.
Пример:
class User{ //создаем приватные свойства private $name; private $login; private $password; private $sex; public function __construct($name, $login, $password){ $this->name = $name; $this->name = $login; $this->name = $password; } //Теперь, для того, чтобы //конечный программист, из внешнего кода, мог получить доступ на чтение и запись //к свойствам этого класса, мы создадим такие //методы как "гетеры" и "сеттеры". public function getName(){ return $this->name; } public function getlogin(){ return $this->login; } public function getPassword(){ return $this->password; } public function getSex(){ return $this->sex; } public function setSex($value){ $this->sex = $value; } } $user = new User('Ivan', 'T1000', 'qwerty');
Попытка обратиться на чтение или запись к любому из свойств приведет к ошибке.
$user->name = 'Anton'; //Fatal error: Cannot access private property User::$name echo $this->password; //Using $this when not in object context
Тем самым, мы стандартизировали заполнение основных свойств.
И создали в классе метод setSex, позволяющий нам записывать значение в свойство sex.
$user->setSex(‘male’); //Применяем сеттер echo $user->getSex(); //male. Применяем гетер
Магические методы __set, __get, __call, callstatic, __toString, __invoke
Для полной инкапсуляции, приватности объекта мы можем отслеживать и реагировать на такие события как:
- попытка чтения/записи свойства, которого не существует,
- попытка вызова метода, который не существует,
- попытка вызова статического метода.
Применяются для этого магические методы __set, __get, __call и callstatic
Магический метод __set будет вызван после того, как произойдет попытка установить значение в свойство, которое не было объявлено или является приватным.
Пример:
class MyClass{ private $name; private $age; function __set($name, $value){ try{ //если свойств мало, можно поступить следующим образом switch($name){ case 'name': $this->name = $value;break; case 'age': $this->age = $value;break; default: throw new Exception('Вы пытаетесь установить значение в свойство, которое не объявлено в классе. <br> Имя свойства :' . $name . '<br>' . 'Значение :' . $value); } }catch(Exception $e){ echo $e->getMessage(); } } } $user = new MyClass (); $user->password = 12345; //записываем в необъявленное свойство. echo gettype($user->password); //NULL
Ошибка не произойдет. Свойство НЕ будет создано. После того, как мы попытаемся записать значение в несуществующее свойство, интерпретатор проверит, а не объявлен ли магический метод __set и если объявлен, передаст ему имя и значение свойства и вызовет его. Результатом кода, описанного выше, мы позволим обращаться только к свойствам $name и $age, а записывать что-либо в необъявленные свойства запретим. А вот если мы НЕ опишем метод __set, мы получим совсем другую картину.
Пример:
class MyClass{ private $name; private $age; } $user = new MyClass (); $user->password = 12345; //записываем в необъявленное свойство. echo $user->password; // 12345
Свойство будет создано и ошибки также не будет.
Магический метод __get будет вызван в том случае, если произойдет запрос на чтение свойства, которое не объявлено.
Пример:
class MyClass{ private $name; private $age; function __get($name){ try{ //если свойств мало, можно поступить следующим //образом switch($name){ case 'name': echo $this->name;break; case 'age': echo $this->age;break; default: throw new Exception('Вы пытаетесь считать значение свойства, которого не объявлено в классе. <br> Имя свойства : ' . $name); } }catch(Exception $e){ echo $e->getMessage(); } } } $user = new MyClass (); echo $user->password; //Обращение на чтение к несуществующему свойству. Результатом кода, описанного выше, мы позволим считывать только свойства $name и $age, а считывать с несуществующих запретим.
Магический метод __call — будет вызван при попытке вызвать несуществующий метод.
Пример
class MyClass { private $name; private $age; public function getName(){ return $this->name; } public function getAge(){ return $this->age; } function __call($methodName, $arrayArg){ try{ switch($name){ case 'getName': $this->getName();break; case 'getAge': $this->getAge();break; default: throw new Exception('Вы пытаетесь вызвать метод, который не объявлен в классе. <br> Имя метода : ' . $methodName); } }catch(Exception $e){ echo $e->getMessage(); } } } $user = new MyClass(); echo $user->foo(); //Вызываем несуществующий метод.
Метод __callStatic — срабатывает при вызове статических методов, не объявленных в классе.
Пример
class MyClass { private $name; private $age; public function getName(){ return $this->name; } public function getAge(){ return $this->age; } function __callStatic($methodName, $arrayArg){ echo 'Вы обращаетесь к несуществующему статическому методу ' . $methodName . '<br>' . 'Со списком аргументов : '; print_r($arrayArg); } } $user = new MyClass(); echo MyClass::foo(1,2,3,4,5); //Вызываем несуществующий статический метод. Вернет //Вы обращаетесь к несуществующему статическому методу foo //Со списком аргументов : Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 )
Как результат, мы запретили вызывать несуществующие статические методы.
Магический метод __toString — вызывается при попытке приведения объекта к строке. Например, echo $obj или strval($obj). После выполнения __toString() дальнейший код не выполняется.
class MyClass { private $x; private $y; public $z = 'Secret'; public static $count; function __construct($x, $y){ $this->x = $x; $this->y = $y; } function __toString(){ print_r($this); //$this - тоже самое что и print_r($obj) $arr = []; foreach($this as $k=>$v){ //может перебирать свойства объекта. $arr[$k] = $v; echo '<pre>'; print_r($arr); //Array([x] => 10[y] => 20[z] => Secret) echo '</pre>'; } } $obj = new MyClass(10, 20); strval($obj);
Магический метод __invoke — вызывается при обращении к объекту, как к функции — $obj(2, 2, 3);
Пример
class MyClass { public $x; function __invoke($a, $b, $c){ echo $a + $b - $c;//1 } } $obj = new MyClass(); $obj(2, 2, 3);
Сериализация объектов в php и магические методы __sleep() и __wakeup
Сериализация необходима для того, чтобы преобразовать объект к строке и сохранить эту строку в БД или файле. В последствии, восстановить объект из этой строки. Это может понадобиться при работе с сессиями или при переходах между скриптами. После запуска функции serialize($obj), сериализуются только те свойства, которые будут указаны в магическом методе __sleep() внутри класса, если он конечно описан. В противном случае, сериализуются все свойства объекта. Рассмотрим пример, когда может понадобиться __sleep. Например, у нас есть экземпляр класса User. У него среди прочих есть свойство password. Так вот мы хотим, чтобы на жестком диске/хостинге/БД не хранилась строка с паролем и его значением. Для этого мы и применим __sleep. Он выступит в качестве фильтра ,который пропустит только те свойства, которые будут указаны.
Пример без __sleep
class User { public $name; public $login; public $password; public $time; function __construct($name, $login, $password){ $this->name = $name; $this->login = $login; $this->password = $password; $this->time = time(); } } $obj = new User('Ivan', 'Batman', 'qwerty'); $string = serialize($obj); echo $string; //O:4:"User":4:{s:4:"name";s:4:"Ivan";s:5:"login";s:6:"Batman";s:8:"password";s:6:"qwerty";s:4:"time";i:1564747562;} $objNew = unserialize($string); print_r($objNew); //User Object ( [name] => Ivan [login] => Batman [password] => qwerty [time] => 1564747562 )
Как видно и сама строка, и новый объект содержат значение свойства password, что не всегда хорошо.
Пример с применением __sleep
class User{ public $name; public $login; public $password; public $time; function __construct($name, $login, $password){ $this->name = $name; $this->login = $login; $this->password = $password; $this->time = time(); } function __sleep(){ return array('name', 'login', 'time'); //Указываем имена свойств, которые необходимо //пропустить на сериализацию. } } $obj = new User('Ivan', 'Batman', 'qwerty'); $string = serialize($obj);//Перед тем, как отработает serialize, будет вызван магический метод __sleep. echo $string; //O:4:"User":3:{s:4:"name";s:4:"Ivan";s:5:"login";s:6:"Batman";s:4:"time";i:1564747606;} $objNew = unserialize($string); print_r($objNew); //User Object ( [name] => Ivan [login] => Batman [password] => [time] => 1564747606 )
Теперь ни в строке, ни в новом объекте не хранится значение свойства password. Также могут возникнуть ситуации, когда после восстановления объекта необходимо инициализировать, например, соединение с БД или обновить данные в свойстве. Для этого применяется метод __wakeup.
Пример с применением __wakeup
class User{ public $name; public $login; public $password; public $time; function __construct($name, $login, $password){ $this->name = $name; $this->login = $login; $this->password = $password; $this->time = time(); } function __sleep(){ return array('name', 'login', 'time'); } function __wakeup(){ //В обьекте инициализированом после сериализации //мы обновим значение свойства time. Создадим для наглядности //искусственную задержку на 5 сек. $timeStart = time(); $timeEnd = $timeStart + 5; while($timeEnd >= time()){ $this->time = time(); } } } $obj = new User('Ivan', 'Batman', 'qwerty'); print_r($obj);//[time] => текущая метка времени $string = serialize($obj); $objNew = unserialize($string);//unserialize спровоцирует вызов __wakeup print_r($objNew);//В объекте инициализированом после сериализации значение обновлено [time] => текущая метка времени + 5 сек
Финальные классы и методы
(Ур. 1 Д. 1 тайм код 4:02:24)
Финальный класс — это противоположность абстрактным классам. Их нельзя наследовать, а можно только создавать экземпляры. Для обозначения класса, как финальный, используется слово final.
Пример
final class MyClass{ //... }
Финальный метод нельзя переопределять. Т.е., если в классе родителе есть метод с словом final, в классе наследнике каким бы то ни было образом переопределить данный метод нельзя.
Пример:
class MyClass{ final function test(){ //тело метода } } class A extends MyClass{ final function test(){ echo 'Переопределим метод'; //Cannot override final method MyClass::test() } }
Множественное наследование/ или Типажи(traits)
(Ур. 1 Д. 1 тайм код 4:02:24)
доп. мат — https://php5.kiev.ua/php7/language.oop5.traits.html
Так как в php не используется множественное наследование в качестве фичи или костыля, существуют разные мнения, с версии 5.4.0 были добавлены трейты.
Трейт — это механизм дополняющий основное наследование. Создать экземпляр класса от трейта нельзя.
Пример:
trait A{ //Объявляется трейт при помощи ключевого слова trait function getText(){ echo "Я есть текст"; } } trait B{ function getNumber(){ echo 2 + 2; } } class C{ use A, B; //"подмешивание" трейта в класc осуществляется при помощи слова use } $obj = new C(); $obj->getText(); //Я есть текст $obj->getNumber(); //4
При попытке создать экземпляр класса от трейта получим такой результат:
$objTraite = new A(); //Fatal error: Uncaught Error: Cannot instantiate trait A
Помимо подмешивания в класс, трейты могут наследовать другие трейты.
Пример
trait A{ function getText(){ return "text"; } } trait B{ use A; } class C{ use B; } $obj = new C; echo $obj->getText(); //text
Трейты позволяют подмешивать в класс не только методы, но и свойства. Проблема в том, что при работе с трейтами могут возникнуть конфликты. В результате того, что в двух или более трейтах могут быть как свойства, так и методы с одинаковыми именами.
Пример:
trait A{ function test(){ echo 'тело'; } } trait B{ function test(){ echo 'тело 2'; } } class C{ use A, B; //Trait method test has not been applied, because there are collisions with other trait methods on C }
Как видим, мы получили ошибку. Для того, чтобы выйти из этой ситуации, данный механизм имеет следующее решение.
Пример:
trait A{ function test(){ echo 'Я из трейта А' . '<br>'; } } trait B{ function test(){ echo 'Я из трейта B' . '<br>'; } } class C{ use A, B{ A::test as aliasTest;//устанавливаем псевдоним при помощи оператора as. B::test insteadof A;//Оператор insteadof говорит, что если мы дернем метод test //то обратиться нужно к трейту B вместо A. } } $obj = new C(); $obj->test(); $obj->aliasTest();
В результате мы можем использовать оба метода, несмотря на то, что у них изначально одинаковые имена.
Изменение модификаторов доступа при помощи трейтов.
При использовании трейтов возможны возникновения следующих ситуаций.
Пример:
trait A{ private function test{ return 'какое-то значение'; } } class B{ use A{ test as public; //Данная команда означает, что метод test теперь //используется как public. Что нарушает логику инкапсуляции. } }
Уточнение типа данных в PHP
(Ур. 3 Д. 1 тайм код 4:20:31)
доп. мат. — https://habr.com/ru/post/259991/
Псевдо тип callable — псевдотип данных, смысл которого “что-то, что может быть вызвано как функция”. Наиболее часто используемый в PHP вариант callable — это ни что иное, как анонимная функция.
Пример:
$a = function($x){ //объявляем анонимную функцию return $x + 2; }; if(is_callable($a)){ echo 'значение переменной может быть вызвано в качестве функции'; };
Также в PHP строки могут быть типом callable. При этих обстоятельствах интерпретатор просто будет искать обычную функцию, чье имя совпадает с указанной строкой.
Пример:
function methodName($x){ return $x + 2; } $a = 'methodName'; if(is_callable($a)){ echo 'значение переменной может быть вызвано в качестве функции'; }
При работе с классами уточнение типа может дать нам возможность, во-первых, указать, что функция может принимать только объект и от конкретного класса.
Пример:
class A{ public $name; } class B{ public $name; } $objA = new A(); $objB = new B(); function foo(B $obj){ //теперь функция примет в качестве аргумента только объект и при этом экземпляр класса B. echo 'какой-то код'; } foo($objB); //Отработает foo($objA); // Uncaught TypeError: Argument 1 passed to foo() must be an instance of B, instance of A given, called…
Во-вторых, в качестве callable можем использовать строковое представление статического метода вида ‘ClassName::method’.
Пример
class className{ static function methodName($x){ return $x + 2; } } $a = 'className::methodName'; if(is_callable($a)){ echo 'значение переменной может быть вызвано в качестве функции'; }; echo call_user_func($a, 2); // Вторым аргументом передаем аргумент для анонимной функции/статического метода
Важно!
Вызвать статический метод, имя которого является типом callable можно только через функцию call_user_func().
Анонимные классы
Доп. мат (php7 в подлиннеке, стр. 468)
Начиная с PHP 7, по аналогии с анонимными функциями доступны анонимные классы.
Пример
class Dumper { public static function print($obj) { print_r($obj); } } Dumper::print( new class { public $title; public function __construct(){ $this->title = "Hello world!"; } }); //результат class@anonymous Object ( [title] => Hello world! )
Данный подход позволяет получать объекты «на лету».
На тот случай, если нам понадобиться создать анонимный класс, внутри другого класса и при этом иметь доступ к его свойствам (с модификатором доступа protected), мы можем расширить анонимный класс, наследуясь от класса в теле которого находимся.
Пример
class Container { private $title = "Класс Container"; protected $id = 1; public function anonym() { return new class($this->title) extends Container { private $name; public function __construct($title) { $this->name = $title; } public function print() { echo "{$this->name} ({$this->id})"; } }; } } $obj = new Container; $obj->anonym()->print(); //допустим и такой синтакисис (new Container)->anonym()->print();
Автор Виталий Сухомлинов
практикующий веб разработчик
и seo-специалист