Denis.in.ua

Блог имени Меня

Позднее статическое связывание в PHP (Часть II: Практика)

4 comments

phpПервую часть читайте здесь.

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  class ArticleEntry extends Storable {
      public function __construct($id = null) {
          if (!is_null($id)) {
              $id = array('id' => $id);
          }
          parent::__construct('articleEntry', '*', $id);
      }
  }
   
  // вывод текста вхождения:
  $entry = new ArticleEntry(10); // Выборка записи из таблицы articleEntry для которой id = 10;
  echo $entry->html('articleBody'); // вывод тела загруженного вхождения
   
  // обновляем запись:
  $entry['ts'] = time(); // устанавливаем время в NOW
  $entry->save(); // Обновляем запись

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
  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. Вот пример использования:

1
2
3
4
5
  class ArticleEntryAttachment extends ArticleEntryAbstract {
      public function __construct($id = null) {
          parent::__construct('articleEntryAttachment', $id);
      }
  }

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  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):

1
ArticleEntryAttachment::purge(10);

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

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

Оригинал: Late Static Binding: a practical example

Written by Денис Солошенко

Апрель 8th, 2008 at 2:24 дп

Posted in PHP