ООП в PHP

5
(1)

Введение в ООП 

ООП — это идея, способ, парадигма, которая выражает собой объединение данных и кода, который их обрабатывает,  в одном объекте.

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

  • рост,
  • вес,
  • цвет кожи,
  • возраст,
  • и т.д.

У него есть методы:

  • ходить,
  • говорить,
  • кидать камень,
  • кодить на php,
  • и т.д.

Три ключевых понятия объектно-ориентированного программирования:

  • Инкапсуляция — механизм языка программирования, который ограничивает доступ к составляющим объект компонентам (методам и переменным), делает их приватными, т.е. доступными только внутри объекта.
  • Наследование — механизм языка, позволяющий создать новый класс на основе уже существующего (родительского, базового) класса. Тем самым позволяет повторно использовать существующий код, а не описывать его снова.
  • Полиморфизм — возможность класса-потомка менять реализацию класса-родителя, сохраняя при этом его интерфейс. 

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

Пример создания класса

class Fans { //Fans - имя класса
//Описание свойств 
//Описание методов
}
//Создание экземпляра класса
$fan = new Fans(); //Экземпляр создается при помощи ключевого слова new

/*Экземпляр класса представляет собой ни что иное, как тип данных. А именно тип данных 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";
    }
}
$cat = new Pet('I created'); //создаем объект и провоцируем, тем самым, вызов метода  __construct.

ВАЖНО!

При создании объекта, после имени класса, указываются круглые скобки. Пример $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!

Функция  __autoload 

Ур. 1 Д. 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 объявлена устаревшей.

Модификаторы доступа 

(Ур. 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

(Ур. 1 Д. 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().


Автор Виталий Сухомлинов
практикующий веб разработчик
и seo-специалист

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 5 / 5. Количество оценок: 1

Оценок пока нет. Поставьте оценку первым.