Часть I: Теория

Позднее Статическое Связывание (Late Static Binding, LSB) является бурно темой обсуждений последние три года в кругах разработчиков PHP (и наконец мы его получили в PHP 5.3). Но зачем оно нужно? В данной статье, как раз и будет рассматриваться, как позднее статическое связывание может значительно упростить ваш код.

На встрече разработчиков PHP, которая проходила в Париже в ноябре 2005 года, тема позднего статического связывания официально обсуждалась основной командой разработчиков. Они согласились реализовать его, наряду со многими другими темами, которые стояли на повестке дня. Детали должны были быть согласованы в ходе открытых дискуссий.

С тех пор как позднее статическое связывание было объявлено как грядущая фишка, прошло два года. И вот наконец LSB стало доступно для использования в PHP 5.3. Но это событие прошло незаметно для разработчиков использующих PHP, из заметок только страничка в мануале.

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

class Beer {
const NAME = ‘Beer!’;
public function getName() {
return self::NAME;
}
}

class Ale extends Beer {
const NAME = ‘Ale!’;
}

$beerDrink = new Beer;
$aleDrink = new Ale;

echo «Beer is: » . $beerDrink->getName() .»n»;
echo «Ale is: » . $aleDrink->getName() .»n»;

Этот код выдаст такой результат: ( https://3v4l.org/l1e6G )

Beer is: Beer!
Ale is: Beer!

Класс Ale унаследовал метод getName(), но при этом self все еще указывает на класс в котором оно используется (в данном случае это класс Beer). Это осталось и в PHP 5.3, но добавилось слово static. И снова рассмотрим пример:

class Beer {

const NAME = ‘Beer!’;

public function getName() {
return self::NAME;
}

public function getStaticName() {
return static::NAME;
}
}

class Ale extends Beer {
const NAME = ‘Ale!’;
}

$beerDrink = new Beer;

$aleDrink = new Ale;

echo «Beer is: » . $beerDrink->getName() .»n»;
echo «Ale is: » . $aleDrink->getName() .»n»;
echo «Beer is actually: » . $beerDrink->getStaticName() .»n»;
echo «Ale is actually: » . $aleDrink->getStaticName() .»n»;

Результат: (см. запуск тут: https://3v4l.org/TclEs )

Beer is: Beer!
Ale is: Beer!
Beer is actually: Beer!
Ale is actually: Ale!

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

Внутренне, основное отличие (и, собственно, причина почему связывание назвали поздним) между этими двумя способами доступа, в том, что PHP определят значение для self::NAME во время «компиляции» (когда симовлы PHP преобразуются в машинный код, который будет обрабатываться движком Zend), а для static::NAME значение будет определено в момент запуска (в тот момент, когда машинный код будет выполнятся в движке Zend).

Это еще один инструмент для PHP-разработчиков. Во второй части рассмотрим как его можно использовать во благо.

Часть II: Практика

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

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

class ArticleEntry extends Storable {
public function __construct($id = null) {
if (!is_null($id)) {
$id = array(‘id’ => $id);
}
parent::__construct(‘articleEntry’, ‘*’, $id);
}
}

// вывод текста вхождения:
// Выборка записи из таблицы articleEntry для которой id = 10;
$entry = new ArticleEntry(10);
echo $entry->html(‘articleBody’); // вывод тела загруженного вхождения

// обновляем запись:
$entry[‘ts’] = time(); // устанавливаем время в NOW
$entry->save(); // Обновляем запись

Можете пропустить подробности конструктора, он приведен только для того, чтобы вы представляли как работает класс Storable. Как вы уже успели понять, это сэкономит нам немного времени и позволит не тратить его на такие мелочи как простые запросы SELECT, INSERT и UPDATE.

В нашей системе, помимо основной таблицы Статьи (ArticleEntry), будут использоваться еще несколько таблиц, которые содержат мета-данные (в отношении многие-к-одному), например: теги, вложения.

Мне также нужен простой способ удаления данных из под-таблиц, прежде чем обновлять данные в основной таблице (проще удалить мета-данные и создать заново, чем беспокоится о синхронизации данных). Итак, чтобы смоделировать код наиболее приближенный к схеме базы данных, я остановился на такой реализации:

abstract class ArticleEntryAbstract extends Storable {
public function __construct($table, $id = null) {
if (!is_null($id)) {
$id = array(‘id’ => $id);
}
parent::__construct($table, ‘*’, $id);
}

public function purge() {
$s = $this->db->prepare(‘DELETE FROM ‘ . $this->tableName . ‘ WHERE pulseEntryId = ?’);
$s->execute(array($this->data[‘entryId’]));
}
}

Весь фокус в методе purge(). Свойство $this->tableName является защищенным свойством, которое мы получили от класса Storable. Вот пример использования:

class ArticleEntryAttachment extends ArticleEntryAbstract {
public function __construct($id = null) {
parent::__construct(‘articleEntryAttachment’, $id);
}
}

У меня есть куча таких мелких классов для работы с таблицами мета-данных. К сожалению, поскольку наша система использует PHP версии 5.2, я не мог использовать функциональность LSB описанную в первой части статьи. И чтобы удалять мета-данные, я должен писать:

$attach = new ArticleEntryAttachment(10); // SELECTS from the articleEntryAttachment table WHERE entryId = 10
$attach->purge();

Если посмотрите выше, как определен метод purge(), то увидите, что tableName он получает из класса Storable, который получает его из конструкторов классов-потомков. Помимо этих тривиальных (но абсолютно необходимых) данных, а также получения объекта базы данных в $this->db, мы не достигли ничего создавая объект класса articleentryattachment. Код был бы намного понятнее и чище (и конечно эффективнее), если бы можно было бы вызвать метод purge() статически. Рассмотрим такой код:

abstract class ArticleEntryAbstract extends Storable {

public function __construct($table, $id = null) {
if (!is_null($id)) {
$id = array(‘id’ => $id);
}
parent::__construct(static::TABLE_NAME, ‘*’, $id);

}

static function purge($entryId) {
$db = Db::get(); // получаем синглетон базы данных
$s = $db->prepare(‘DELETE FROM ‘ . static::TABLE_NAME . ‘ WHERE pulseEntryId = ?’);
$s->execute(array($entryId));
}
}

class ArticleEntryAttachment extends ArticleEntryAbstract {
const TABLE_NAME = ‘articleAttachment’;
}

Первое, что, я надеюсь, вы заметили, это то что ArticleEntryAttachment стал намного проще. Теперь нет необходимости переопределять конструктор для подклассов, потому, что конструктор родительского класса самодостаточен. И теперь можно использовать метод purge() (используя LSB):

ArticleEntryAttachment::purge(10);

Поскольку, теперь purge() может получать имя таблицы, которое определяется в момент выполнения, мы можем сделать его статическим. В итоге, код — чище, выполнение — эффективней, поддержка (например, добавление новых под-классов) — пустяковая, потому как избыточность полностью убрана. Спасибо разработчикам PHP за то что это стало возможным!

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

Источники:

(1) https://habr.com/ru/post/23066/

(2) https://habr.com/ru/post/23119/

Tags

Нет комментариев

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.