2009/10/31

Как написать фреймворк за два часа, часть 1, про Сущности и Хранилища

Наше тело получает жизнь из пустоты. Существование там, где ничего нет, составляет смысл слов: "Форма есть пустота". Слова же: "Пустота есть форма" свидетельствуют о том, что пустота содержит в себе вещи. Не следует полагать, что пустота и вещи суть различны.
Сегодня решил написать не про модули, а про сущности и хранилища; просили.
Вспомним договорённости.
Подумаем про хранилища. Напишем:
interface CRUD
{
public function & create();
public function & read($criteria);
public function update();
public function delete($criteria);
}
почему так? рассказываю дальше:
abstract class StorageEngine implements CRUD
{
protected $__entity;
function __construct($what)
{ $this->__entity = $what; }

function __destruct()
{ $this->update(); }

// здесь ещё раз список методов, т.к. конкретную реализацию оставим для неабстрактных классов
}
ясно? или не полностью?
Возврат значений по ссылке обеспечивает обратную связь сущности (ещё не объявленной) с хранилищем, которое деструктором закрывает сущность, сохраняет её на физическом уровне.
Методы обеспечивают элементарный доступ к сущностям, сложенным в хранилище.
create обеспечивает динамическое создание копии сущности (пока только в памяти времени исполнения)
read читает коллекции сущностей из физического хранилища
update сохраняет коллекцию обратно
delete удаляет быстро и беспощадно
$criteria - признак выборки, XPath

Понимаете ведь, что это только здесь всё так красиво :) Внутри там конкретный аксессор к конкретному хранилищу и много других радостей. К которому можно подключить доп. драйвер и простенький шаблонизатор SQL для совсем уж чудных запросов. Для наших задач вполне хватает $accessor->query('SELECT _read(:criteria)', $array('citeria'=>$criteria));
Как кому-то уже понятно, в качестве драйвера используется PDO::PG, а сами выборки переложены на могучие плечи субд, на пользовательские функции. (Конкретнно чтение, выборку по критерию правильнее было бы оформить через VIEW, но я пошёл по пути унификации. И ещё: в процессе написания функций я обнаружил, что постгрес поддерживает перегрузку функций - функции с одним именем, но разным набором параметров)


Про сущности же подумаем так:
interface XML
{
function asXML();
function XPath();
}
Это два метода, которые нам необходимы. Первый для преобразования объекта к читабельному виду, второй для удобной работы. Да-да, XPath гораздо удобней, чем in_array(). Освоение множеств XML и преобразований требует некоторого мозга, но особого труда не составляет, будем работать с нормальными технологиями.
Кто использовал XML с PHP, конечно же, уже узнал этот интерфейс :) Да, это SimpleXML. Не будем изобретать сущности, извините за тавтологию, воспользуемся имеющейся.
class Entity extends SimpleXMLElement implements XML
{
// здесь меня ожидало несколько весьма занятных ээ... особенностей реализации языка. например, нельзя переопределить конструктор. пришлось изобретать конструкцию загрузки сущностей из схем и других источников, типа такой

static function load($source)
{
if (isXML($source)) $xml = $source;
if (is_file($source)) $xml = file_get_contents($source);
return new self($xml);
}
}
Со статическими методами тоже получилось интересно, почти неделю парился, уже думал, что пхп не поддерживает их перегрузку. Оказалось ещё хитрее ;) До ветки 5.3 self указывает не на текущий класс, а на класс, в котором был написан. То есть наследовать от Entity можно только с переопределением загрузчика. Но в принципе, особой нужды в этом нет, SimpleXML дал нам Entity::getName(), который выдаст имя корневого элемента (здесь необходимо вспомнить правила оформления XML;)

Ещё в сущностях есть пара вспомогательных статических методов, которые не несут принципиальной смысловой нагрузки, их можно посмотреть в коде. На этом, вроде, всё про систему хранения.

Вопросы?

зы: ах да, пример
$sth = new EntityStorage( Entity::load('scheme.xml') );
$sth->create();
или
$data_collection = & $sth->read('user/profile[name="USERNAME"]');
foreach ($data_collection as $user)
{
print $user->id;
print $user->profile->name;
}
$user->profile->name = 'LASTUSER';
$sth->update();

и замечание напоследок: нет, это не ORM, здесь отсутствует преобразование из плоской формы в объектную. На Observer похоже, но отсутствует подписка на изменения. Как назвать, не особенно важно, оно работает =%)
blog comments powered by Disqus