23 нояб. 2011 г.

Новые возможности ООП в PHP5

Учитывая, что PHP на сегодняшней день является крайне популярной технологией программирования серверной части сайтов, а также то, что версия PHP 4, по утверждениям многих, была революционной по сравнению с предыдущими, новая пятая по счету версия вызывает обоснованный интерес среди веб-разработчиков.
Однако, по словам создателей PHP 5, ничего революционного в этот раз ожидать не стоит - текущие изменения носят всего лишь ”эволюционный” характер. Тем не менее, сделанные дополнения являются давно ожидаемыми, крайне полезными и весьма своевременными.
В первую очередь переработке подвергся весь механизм работы с объектами. И если в предыдущих версиях объектно-ориентированное программирование на PHP было возможно в минимальной степени, из-за чего и использовалось на практике не часто, то PHP 5 обладает великолепным потенциалом реализации объектного программирования. Кроме этого, PHP обогатился рядом ценных расширений для работы с XML, различными источниками данных, генерации графики и пр.




Новый механизм работы с объектами

Основное отличие обработки объектов в PHP 5 от PHP 4 заключается в том, что теперь присвоение объекта или его передача в качестве параметра функции происходит по умолчанию по ссылке, а не по значению, как в предыдущей версии.
И если в PHP 4 объекты обрабатывались также как и простые типы данных, что часто приводило к появлению нескольких копий одного и того же объекта, то в PHP 5 такого не происходит, так как каждый объект получает свой собственный числовой идентификатор (handle), который и используется при обращении к объекту.
Таким образом, представленный ниже код, выполненный в PHP 4 и в PHP 5, очевидно может продемонстрировать различия в обработке объектов.
<?php
     class MyClass {
         var $property;
     }
     $obj1 = new MyClass;
     $obj1->property = 1;
     $obj2 = $obj1;
     $obj2->property = 2;
     echo $obj1->property; // Выводит 1 в PHP 4 и 2 в PHP 5
     echo $obj2->property; // Выводит 2
?>

В PHP 4 $obj2 представляет собой копию объекта $obj1, а в PHP 5 и $obj1 и $obj2 указывают на один и тот же объект, так как оператор $obj2 = $obj1 копирует не сам объект, а только его идентификатор.
Различные механизмы обработки объектов имеют место по причине того, что Zend Engine 1, исполнявший сценарии в PHP 4, хранил значения всех типов одинаковым образом в специальной структуре, называемой zval (Zend VALue). В PHP 5 также используется zval, однако теперь в нем хранятся все типы данных, за исключением объектов, которые располагаются в новой структуре, получившей название Object Store. Zval же хранит только идентификаторы объектов, вот почему при присвоении или передачи в функцию передается не сам объект, а только его идентификатор.
Данное улучшение позволит значительно увеличить производительность PHP сценариев, где активно используется работа с объектами.


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

Итак, в PHP 5 объекты передаются по ссылке. Однако, если же необходимо провести именно копирование объекта, как это делалось в PHP 4, то в PHP 5 придется явно использовать новый метод __clone(). При этом объект копируется со всеми своими методами, свойствами и их значениями:

<?php
     class MyClass{
         var $property;
     }
     $obj1 = new MyClass;
     $obj1->property = 1;
     $obj2 = clone $obj1;
     echo $obj1->property; // Выводит 1
     echo $obj2->property; // Выводит 1
     $obj2->property = 2;
     echo $obj2->property; // Выводит 2
?>

Следует обратить внимание на то, что к методу __clone() нельзя обратиться непосредственно и для копирования объекта используется ключевое слово clone.
Метод __clone() необязательно описывать в самом классе, однако его явное определение, т.е. перегрузка, позволит изменить значения свойств копируемого объекта:
<?php
     class MyClass{
         var $property;
         function __clone() {
             $this->property = 2;
         }
     }
     $obj1 = new MyClass;
     $obj1->property = 1;
     $obj2 = clone $obj1;
     echo $obj1->property; // Выводит 1
     echo $obj2->property; // Выводит 2
?>

Метод __clone() не может принимать никакие аргументы, однако позволяет обратиться к исходному объекту через указатель $this и получаемому в результате копирования объекту через указатель $that. 


Доступность элементов класса

В PHP 5 введены спецификаторы доступа public, protected и private, которые позволяют указать степень доступа к свойствам и методам класса.
К общедоступным (public) свойствам и методам можно получить доступ без каких либо ограничений.

Защищенные (protected) элементы класса доступны внутри класса, в котором они объявлены, и в производных от него классах.
Частные (private) элементы доступны только в классе, в котором они объявлены.
<?php
     class MyClass {
         public $public = "общедоступный элемент";
         protected $protected = "защищенный элемент";
         private $private = "частный элемент";
         public function printPrivate() {
             echo $this->private;
         }
     }
     $obj1 = new MyClass;
     echo $obj1->public; // Выводит "общедоступный элемент"

     class MyClass1 extends myClass {
         public function printProtected() {
             echo $this->protected;
         }
     }
     $obj2 = new MyClass1();
     $obj2->printProtected(); // Выводит "защищенный элемент"

     $obj1->printPrivate(); //Выводит "частный элемент"

     echo $obj1->protected; // Вызывает ошибку доступа
     echo $obj1->private; // Вызывает ошибку доступа
?>

Если не указывать ни один из спецификаторов, то по умолчанию элемент будет иметь уровень доступа public. Такой же уровень доступа получают свойства, для объявления которых использовалось устаревшее и не рекомендуемое к использованию в PHP 5 ключевое слово var.

В PHP 5 введены конструкторы и деструкторы

Метод-конструктор вызывается автоматически при каждом создании объекта. И хотя конструктор появился в PHP давно (эту роль выполнял метод, названный именем класса), но в PHP 5 была изменена схема именования конструктора - метод __construct() является теперь конструктором класса.
Аналогично, при уничтожении объекта вызывается специальный метод __destruct() – деструктор класса.

<?php
     class MyClass {
         function __construct() {
             echo "Запущен конструктор";
         }
         function __destruct() {
             echo "Запущен деструктор";
         }
     }
     $obj = new MyClass(); // Выводит "Запущен конструктор"
     unset($obj); // Выводит "Запущен деструктор"
?>

Если же необходимо вызвать конструктор или деструктор базового класса, то необходимо это делать явно, через указатель parent.
<?php
     class MyClass {
         function __construct() {
             echo "Запущен конструктор базового класса";
         }
         function __destruct() {
             echo "Запущен деструктор базового класса";
         }
     }
     class MyClass1 extends MyClass {
        function __construct() {
            parent::__construct();
        }
        function __destruct() {
            parent::__destruct();
        }
    }
    $obj = new MyClass1(); // Выводит "Запущен конструктор
                                       // базового класса"
    unset($obj); // Выводит "Запущен деструктор базового класса"
?>

Для целей совместимости с предыдущей версией PHP 5 поступает следующем образом: если при создании объекта в классе не найдет конструктор __construct(), то PHP пытается выполнить метод, имя которого совпадает с именем класса.  Т.о. конструкторы PHP 4 будут работать с PHP 5 без каких-либо изменений кода.

В PHP 5 впервые введены абстрактные (abstract) классы и методы

Абстрактные методы имеют только объявление и не имеют реализации. Класс, который содержит такие методы, должен быть обязательно объявлен как абстрактный.
<?php
     abstract class MyClass {
         abstract public function abstrFunc();
     }
     class MyClass1 extends MyClass {
         public function abstrFunc() {
             echo 1;
         }
     }
     $obj = new MyClass1;
     $obj->abstrFunc(); // Выводит 1
?>

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

Итнерфейсы

Интерфейсами (interface) являются абстрактные классы, содержащие только абстрактные  методы и не имеющие никаких свойств.
Основное отличие интерфейсов от абстрактных классов заключается в том, что в PHP 5 класс не может быть порожден от нескольких классов, в том числе и абстрактных, но зато может быть создан на основе любого числа интерфейсов.

При этом в интерфейсе методы объявляются ключевым словом function без указания каких-либо спецификаторов, в том числе и abstract.
<?php
      interface Int1 {
          function func1();
      }
      interface Int2 {
          function func2();
      }
      class MyClass implements Int1, Int2 {
          public function func1() {
              echo 1;
          }
          public function func2() {
              echo 2;
          }
     }
     $obj = new MyClass;
     $obj->func1(); // Выводит 1
     $obj->func2(); // Выводит 2
?>

Таким образом, хотя множественное наследование (multiple inheritance) и не поддерживается в PHP 5, однако разработчики получили реальную возможность создавать классы на основе отдельно описанных интерфейсов.

Финальные методы и классы

В PHP 5 введена новая возможность определять методы класса и сами классы как финальные (final).
Метод, при определении которого используется ключевое слово final, не может быть переопределен в классах, производных от данного класса.
<?php
      class MyClass {
          final public function func() {
              // Код метода
          }
      }
      class MyClass1 extends MyClass {
          // Следующий код вызывает ошибку
          // переопределения финального метода
          // базового класса MyClass
          public function func() {
              // Код метода
          }
      }
?>

Кроме этого, если final используется при определении самого класса, то порождение от него других классов становится невозможным.
<?php
      final class MyClass {
          // Код описания класса
      }
      // Следующий код вызывает ошибку
      // порождения от финального класса
      class MyClass1 extends MyClass {
          // Код описания класса
      }
?>

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

Константы класса

В PHP 5 введен новый элемент класса – константа.
<?php
     class MyClass {
         const CONSTANT = "константа класса";
     }
     echo MyClass::CONSTANT; // Выводит "константа класса"
?>

Ранее написанные сценарии для PHP 4, не использующие функции или классы с именем const будут работать без модификации кода.

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

В PHP 5 возможно объявление статических свойств класса.
<?php
      class MyClass {
          static $static = 1;
      }
      echo MyClass::$static; // Выводит 1
?>

Статические свойства едины для всего класса и не могут принадлежать ни одному из объектов класса. Изменение такого свойства  в одном из методов любого объекта приводит к его изменению для всех остальных объектов данного класса. Кроме этого, становится возможным обратиться к такому свойству вне контекста объекта.
Статические методы классов в PHP 5, также как и статические свойства, принадлежат всему классу в целом. Это позволяет использовать такой метод без создания какого-либо объекта такого класса.
<?php
      class MyClass {
          static function statFunc() {
               echo "статический метод";
          }
      }
      MyClass::statFunc(); // Выводит "статический метод"
?>

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

Instanseof

Специальное ключевое слово instanceof в PHP 5 позволяет определять является ли объект экземпляром определенного класса, или же экземпляром класса производного от какого-либо класса.
<?php
      class MyClass { }
      $obj1 = new MyClass();
      if ($obj1 instanceof MyClass) {
          echo "\$obj1 - объект класса MyClass";
      }

      class MyClass1 extends MyClass { }
      $obj2 = new MyClass1();
      if ($obj2 instanceof MyClass) {
          echo "\$obj2 - объект класса, производного от MyClass";
      }

      interface Int { }
      class MyClass2 implements Int { }
      $obj3 = new MyClass2();
      if ($obj3 instanceof Int) {
          echo "\$obj3 - объект класса, созданного на основе интерфейса Int";
      }
?>

Также с помощью instanceof можно определить является ли объект экземпляром класса, созданного на основе определенного интерфейса.

__autoload()

Специальная функция __autoload() будет вызываться в PHP 5  в случае попытки создания объекта неопределенного класса.
<?php
     function __autoload($class) {
         echo "попытка создать объект неопределенного класса ", $class;
     }
     $obj = new MyClass;
?>

В качестве параметра функции  __autoload()  передается имя неопределенного класса.

Методы доступа к свойствам объекта

В PHP 5 возможна перегрузка доступа к свойствам объектов.
<?php
      class MyClass {
          private $properties;
          function __set($name, $value) {
              echo "задание нового свойства $name = $value";
              $this->properties[$name]=$value;
          }
          function __get($name) {
              echo "чтение значения свойства ", $name;
              return $this->properties[$name];
          }
     }
     $obj = new MyClass;
     $obj->property = 1; // Выводит "задание нового свойства property=1"
     $a = $obj->property; // Выводит "чтение значения свойства property"
     echo $a; // выводит 1;
?>

Новые методы доступа __get() и __set() позволяют легко проводить динамическое назначение свойств объектам. В качестве параметров этим методам передаются имена свойств.

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

Метод __set()  также получает и значение, которое устанавливается для свойства.
При вызове в PHP 5 несуществующего метода объекта автоматически вызывается специальный метод __call().

<?php
      class MyClass {
          function __call($name, $params) {
              echo "Вызван метод $name с параметром $params[0]";
          }
      }
      $obj = new MyClass;
      echo $obj->method(1); // Выводит "Вызван метод method
                                       // с параметром 1"
?>

В качестве параметров __call() принимает имя вызываемого метода и передаваемые этому методу параметры.

Итераторы

В PHP 5 возможен полный перебор всех свойств объекта в операторе foreach.
<?php
      class MyClass {
          public $a = 1;
          public $b = 2;
      }
      $obj = new MyClass;
      foreach ($obj as $name => $value) {
          echo "$name => $value "; // Выводит "a => 1 b => 2"
      }
?>

Специальные интерфейсы PHP 5 IteratorAggregate и Iterator позволяют указывать поведение класса при его использовании с оператором  foreach.