Все ленты — последние статьи

Новая объектная модель

Данил Миронов

2003-11-29

Примечание от администратора: Описанные возможности доступны только в PHP 5. Использование допустимо исключительно в тестовых целях

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

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

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

 

Закрытые члены

В Zend Engine 2.0 вводятся закрытые свойства. Отметим, что при несанкционированном доступе к закрытому атрибуту сообщение об ошибке не выводится: во избежание замедления работы.

Пример 1: Закрытые члены

<?php
class MyClass {
private $Hello = "Hello, World!n";

function printHello() {
print $this->Hello;
}
}

class MyClass2 extends MyClass {
function printHello() {
MyClass::printHello(); /* Есть вывод */
print $this->Hello; /* Нет вывода */
}
}

$obj = new MyClass();
print $obj->Hello; /* Нет вывода */
$obj->printHello(); /* Есть вывод */

$obj = new MyClass2();
print $obj->Hello; /* Нет вывода */
$obj->printHello();
?&gt;

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

В Zend Engine 1.0 при копировании объектов пользователь не мог решать, что конструктор должен копировать, а что нет. Версия 1.0 производила побитное копирование объекта и давала на выходе полную копию всех его свойств.

Однако абсолютное копирование объекта с полным набором свойств — это не всегда то, что нам нужно. Хороший тому пример — у вас есть объект, представляющий собой окно GTK и информация обо всех элементах интерфейса этого окна также хранится в объекте. Вам может понадобиться создать новое окно (копию первого) и хранить данные по всем элементам интерфейса нового окна в новом объекте. Другой вариант — в вашем объекте содержится ссылка на другой объект, который как-либо им используется. Клонируя ваш родительский объект, вы тем самым создаёте копию другого объекта и таким образом, у дубликата вашего объекта есть своя копия того используемого объекта.

Копия объекта создаётся методом __clone() этого объекта.

Когда разработчик вызывает создание клона какого-либо объекта, Zend Engine проверяет, был ли определён метод __clone(). Если метод не был определён, то вызывается __clone() по умолчанию, и все свойства объекта дуплицируются полностью. Если же метод был определён, то именно он перечисляет свойства объекта, которые будут перенесены в его копию. Для удобства использования Zend Engine предоставит функцию, которая импортирует все свойства копируемого объект: таким образом, они будут в копии по значению, а разработчику останется лишь переписать свойства, которые нам нужно изменить [В данной версии эта функция ещё не реализована].

Пример 2: Клонирование объектов

<?php
class MyCloneable {
static $id = 0;

function MyCloneable() {
$this->id = self::$id++;
}

function __clone() {
$this->name = $clone->name;
$this->address = 'New York';
$this->id = self::$id++;
}
}

$obj = new MyCloneable();

$obj->name = 'Hello';
$obj->address = 'Tel-Aviv';

print $obj->id . "n";

$obj = $obj->__clone();

print $obj->id . "n";
print $obj->name . "n";
print $obj->address . "n";
?&gt;

Принудительное уничтожение объектов

Zend Engine 1.0 не содержала средств принудительного уничтожения объекта, если ссылки на него ещё существовали. Оператор delete, введённый в новой версии, вызывает деструктор объекта и освобождает занимаемую объектом память, даже если в других местах системы содержатся ссылки на него. Все ссылки на уничтоженный объект становятся устаревшими, и попытки получить доступ через них приводят к появлению ошибки (fatal error).

Стоит иметь в виду, что если в вашем старом скрипте содержится определённая пользователем функция delete(), то в Zend Engine 2.0 такой скрипт выдаст вам ошибку синтаксиса (parse error), поскольку 'delete' является отныне зарезервированным словом.

Вложенные классы (пространство имён)

В Zend Engine 1.0 существовало только три области действия: глобальная, область действия-класс и область действия-функция. Все области кроме класса могли содержать переменные; только область-класс и глобальная область могли содержать функции и только глобалльная область могла содержать константы и классы. Это означает, что во всех версиях Zend Engine 1.0 метода обзора были ограничены во избежание конфликтов пространства имён.

В Zend Engine 2.0 вводится концепция вложенных классов для решения проблемы именных коллизий альтернативным методом: появляется возможность определить несколько таблиц имён, содержащих любые типы символов. Zend Engine всегда следит, в области какого класса находится выполнение, выставляя по умолчанию глобальную область. Каждый класс может содержать свой набор констант, функций и статических переменных. Для доступа к идентификаторам внутри класса используется оператор доступа к классу self::, например, возможна такая строка: self::$my_static_name = "Hello". С функциями, равно как и с константами, если вы не определили класс, которому они принадлежат, то сначала поиск будет произведён внутри текущего класса, затем, если поиск не принёс результата, будет исследована глобальная область действия. Если вам нужно произвести поиск только в глобальной области действия, вы можете задать это эксплицитно, через оператор доступа main::. Например, используя, main::strlen(), вы уверены, что вы вызываете именно strlen() из глобальной области действия. Таким образом, использовать main:: вам придётся лишь в случаях, когда вы создаёте метод, имеющий одинаковое имя с глобальной функцией. Синтаксис для доступа к константам тот же: self::MY_CONSTANT или main::MY_CONSTANT.

Иногда вам может не понравиться получать доступ к константам, переменным и классам через операторы доступа (например, MyClass::): вам приходится прибегать к ним слишком уж часто, а скорость набора у вас страдает. В таком случае вы можете импортировать функции, классы и константы из других классов через ключевое слово import. Название говорит само за себя, также ниже приведено несколько примеров.

Классы могут включать в себя классы


Пример 3: Вложенные классы

<?php
class DB::MySQL {
var $host = '';

function db_connect($user) {
print "Connecting to MySQL database '$this->host' as $usern";
}
}

class DB::Oracle {
var $host = 'localhost';

function db_connect($user) {
print "Connecting to Oracle database '$this->host' as $usern";
}
}

$MySQL_obj = new DB::MySQL();
$MySQL_obj->db_connect('Susan');

$Oracle_obj = new DB::Oracle();
$Oracle_obj->db_connect('Barbara');
?&gt;

Классы могут cодержать константы


Пример 4: константы

<?php
class foo {
const hey = 'hello';
}

print foo::hey;
?&gt;

Поиск функций и констант проводится сначала в таблице имён текущего класса.

Пример 5: таблица имён класса

<?php
define('foo', 'bar');

class FooClass {
const foo = 'foobar';

function printFoo() {
print foo;
}
}
?&gt;

Код, приведённый ниже, выводит не "foo", а "foobar", поскольку константа класса переписывает одноимённую константу глобальной области.

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

Пример 6: таблица имён класса

<?php
class FooClass {
function foo() {
$this->bar();
bar();
}

function bar() {
print "foobarn";
}
}

$obj = new FooClass;
$obj->foo();
$obj->foo();
?&gt;

"foobar" выводится дважды, поскольку метод bar() уже существует в данном пространстве имён.

Существует созможность "импортировать" имена из одного пространства в другое:

Пример 7: импорт имён

<?php
class MyClass {
class MyClass2 {
function hello() {
print "Hello, World in MyClass2n";
}
}

function hello() {
print "Hello, Worldn";
}
}

import function hello, class MyClass2 from MyClass;

MyClass2::hello();
hello();
?&gt;

Пример 8: импорт имён

<?php
class MyOuterClass {
class MyInnerClass {
function func1() {
print "func1()n";
}

function func2() {
print "func2()n";
}
}
}

import class * from MyOuterClass;
import function func2 from MyOuterClass::MyInnerClass;

MyInnerClass::func1();
func2();
?&gt;

Пример 9: импорт имён

<?php
class MyOuterClass {
const Hello = "Hello, Worldn";
}

import const Hello from MyOuterClass;
print Hello;
?&gt;

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

Унифицированные конструкторы

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

В Zend Engine 1.0, методы-конструкторы представляли собой методы класса одноимённые с ним. Из-за широко распространённой практики вызывать конструктор родительского класса из производных классов, такая особенность Zend Engine 1.0 превращала перемещение от одного класса (при сложной иерархии классов) к другому в громоздкую и медленную работу. И если какой-либо производный класс будет перемещён к другому родителю, то имя конструктора родительского класса, само собой, изменится, и код вызова этого конструктора из производного класса придётся менять.

В Zend Engine 2.0 вводится новый унифицированный способ объявления метода-конструктора: называть конструктор именем __construct().

Пример 10: Унифицированные конструкторы

<?php
class BaseClass {
function __construct() {
print "In BaseClass constructorn";
}
}

class SubClass extends BaseClass {
function __construct() {
parent::__construct();
print "In SubClass constructorn";
}
}

$obj = new BaseClass();
$obj = new SubClass();
?&gt;

Для сохранения совместимости с предыдущими версиями, если Zend Engine 2.0 не найдёт метода с именем __construct() для данного класса, будет произведён поиск конструктора, объявленного по старым правилам, то есть одноимённого с классом. Проблема совместимости со скриптами предыдущих версий может возникнуть только в одном случае: если ваш класс содержит метод __construct(), используемый для других целей.

Унифицированные деструкторы

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

В Zend Engine 1.0 не было никаких средств поддержки деструкторов, хотя PHP уже поддерживал функции ведения лога, которые вызывались при остановке запроса.

В Zend Engine 2.0 вводится концепция деструктора, схожая с деструкторами в объектно-ориентированными языками, например, в Java: когда уничтожается последняя ссылка на объект, перед освобождением занимаемой им памяти вызывается деструктор. Последний представляет собой метод класса с именем __destruct() и не принимающий параметров.

Пример 11: Унифицированные деструкторы

<?php
class MyDestructableClass {
function __construct() {
print "In constructorn";
$this->name = 'MyDestructableClass';
}

function __destruct() {
print 'Destroying ' . $this->name . "n";
}
}

$obj = new MyDestructableClass();
?&gt;

Как и конструкторы, деструктор родителя автоматически не вызывается. Для этой операции вам необходимо явно вызвать parent::__destruct() в теле деструктора.

Исключения

В Zend Engine 1.0 управление исключениями не поддерживалось. В Zend Engine 2.0 вводится модель исключений сходная с подобными моделями в других языках.

Пример 12: Исключения

<?php
class MyException {
function __construct($exception) {
$this->exception = $exception;
}

function Display() {
print "MyException: $this->exceptionn";
}
}

class MyExceptionFoo extends MyException {
function __construct($exception) {
$this->exception = $exception;
}

function Display() {
print "MyException: $this->exceptionn";
}
}

try {
throw new MyExceptionFoo('Hello');
}

catch (MyException $exception) {
$exception->Display();
}
?&gt;

Скипты предыдущих версий без определённых пользователем функций 'catch', 'throw', 'try' будут работать без изменений.

Разыменовывание объектов, полученных из функций.

Пример 13: Разыменовывание объектов

<?php
class Circle {
function draw() {
print "Circlen";
}
}

class Square {
function draw() {
print "Squaren";
}
}

function ShapeFactoryMethod($shape) {
switch ($shape) {
case 'Circle': return new Circle();
case 'Square': return new Square();
}
}

ShapeFactoryMethod('Circle')->draw();
ShapeFactoryMethod('Square')->draw();
?&gt;

Инициализация статических членов классов.

Пример 14: Инициализация статических членов

<?php
class foo {
static $my_static = 5;
}

print foo::$my_static;
?&gt;

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

Пример 15: Значения по умолчанию

<?php
function my_function(&$var = null) {
if ($var === null) {
die('$var needs to have a value');
}
}
?&gt;

Встроенная обратная трассировка.

Пример 16: Значения Обратная трассировка

<?php
$backtrace = debug_backtrace();

foreach ($backtrace as $step) {
$class = isset($step['class']) ? $step['class'] . '::' : '';

printf(
"%s [%s:%s]n",
$step['function'],
$step['file'],
$step['line']
);
}
?&gt;