Уровень абстракции NyanORM

Зачем все это?

Делает НЯКУ с вашей базой. И предоставляет очень удобные средства, чтобы вы прилагали минимум усилий и мозговых извилин при работе с БД Ubilling. Вы сможете описывать ваши модели данных, и работу с ними, больше не рисуя в ручную SELECT * FROM `tablename`… и прочие осточертевшие вам конструкции. Также NyanORM возьмет на себя всю вашу рутинную работу по предварительной подготовке и трансформации ваших данных. Задачей NyanORM не является быть всеобъемлющим монстром реализующим все возможные кейсы работы с вашими данными. Изначально он задумывался для того, чтобы сократить объемы рутинного, скучного и однотипного кода, из которого состоит работа с сущностями в 90-95% прикладных модулей. Собственно только эти кейсы он и должен перекрывать, не накладывая на вас никаких дополнительных ограничений.

Особенности

Почему именно NyanORM а не любая другая сторонняя библиотека с приставкой ORM в названии? А по следующим причинам:

  • Не накладывает никаких ограничений на именование табличек БД.
  • Не диктует никаких условий по полям и содержанию этих табличек.
  • Позволяет без проблем работать с уже существующими структурами данных.
  • Позволяет миксовать код как с его использованием так и в старом стиле.
  • Не требует долгого и муторного описания моделей перед тем как начать работать.
  • Идеален для сверхбыстрого прототипирования бизнес-логики прикладных модулей.
  • Доступен в любой момент и всегда из любого вашего модуля.
  • Написан в насколько это возможно минималистском стиле с максимальным использованием существующих механик Ubilling.
  • Минимизирует оверхеды по памяти и коллбекам. Но это не точно.
  • Позволяет наследованием модифицировать как угодно любую модель и методы работы с ней вообще никак себя не ограничивая.
  • Не предоставляет никаких требований и средств по фильтрации данных. Вы можете использовать вообще что захотите.
  • Работает одинаково отлично на PHP5 и PHP7. Легаси да.
  • Максимально очевидный синтаксис и механики требующие для своего понимания минимального уровня IQ.
  • Зашкаливающий уровень кавая.

К сожалению, мы не нашли ни одного существующего решения, соответствующего всем особенностям/требованиям выше. Это сэкономило бы нам очень много усилий и времени, но нет. Поэтому NyanORM.

Если вам будет так спокойнее, то мы вдумчиво, посмотрели на то как работают Eloquent, Hibernate, Doctrine, Propel, RedBean… и сделали наоборот.

Это обязательно использовать?

Нет! Никто вас ни в чем не ограничивает и не заставляет использовать что-либо такое специфическое в вашем новом коде, и тем более даже не намекает, что вы должны переписать весь ваш старый код с использованием этого объекта. Возможно вам просто понравится как может выглядеть ваш код без ручной сборки запросов к БД, и с использованием высокоуровневой абстракции.

С чего начать?

Для примера, давайте рассмотрим очень типичный кейс. Нам нужно получить, например, платежи за текущий год, с суммой более нуля и типом платежей «наличка». Также мы хотим иметь их в ассоциативном массиве, где ключом будет id платежа. Как это бы выглядело в нашей старой реальности:

$query = "SELECT * from `payments` WHERE `summ`>'0' AND `date` LIKE '" . curyear() . "-%' AND `cashtypeid`='1'";
$all = simple_queryall($query);
$rawPayments = array();
if (!empty($all)) {
    foreach ($all as $io => $each) {
        $rawPayments[$each['id']] = $each;
    }
}
 
debarr($rawPayments);

Правда знакомая конструкция? Осточертело, правда же? А теперь давайте тоже самое но красиво. Для начала нам нужна модель для таблички payments. Для этого мы либо наследуем базовую модель используя имя таблички как имя класса:

class payments extends NyanORM {}
$payments = new payments();

либо делаем то же самое с использованием черной магии

$payments = new nya_payments(); //воу, здесь происходит магия ;)

Да, все после префикса nya_ будет развернуто в имя таблички as is и для нее будет автоматически сгенерирована модель.

Получаем все записи

$allPayments=$payments->getAll(); //получаем все записи из модели

Да. Это типа моделька для таблички payments в ловеркейсе. Также вы можете максимально прямолинейно выставить имя таблички с которой мы будем работать через конструктор класса. Например так:

$bablo=new NyanORM('payments');
debarr($bablo->getAll());

Мы что-то отвлеклись. Короче модель у нас есть. Что там с нашим изначальным планом по выборке вполне конкретных платежей? А вот что:

class payments extends NyanORM { } // создаем модель наследованием, так как это лучше дружит с автокомплитом некоторых IDE
$payments = new payments(); // создаем объект модели
$payments->where('summ', '>', '0'); //тут и дальше вешаем наши условия
$payments->where('date', 'LIKE', curyear() . '%');
$payments->where('cashtypeid', '=', '1'); // да, это наличка
$rawPayments = $payments->getAll('id'); // автоматическая группировка по id
debarr($rawPayments);

Давайте еще раз, на примере, но с чем то-другим. Пускай это будут свитчи. И пускай мы хотим просто получить все свитчи.

$switches = new nya_switches();
$allSwitches = $switches->getAll();

Куда уже проще?

О параметрах моделей

Как можно было заметить для установки параметров наших запросов к моделям мы можем использовать метод where() с тремя довольно очевидными параметрами. Также довольно очевидно, что несколько where() идущих последовательно будут сохранены в кумулятивных структурах и в дальнейшем интерпретированы как AND. А что если мы внезапно захотим кроме платежей с наличкой получать оные также с типом 4 (пускай это будут какие-то штуки из самообслуживания)? Очевидно, что мы хотим условие OR. А как его сделать? А очень просто.

$payments->where('cashtypeid', '=', '1');
$payments->orWhere('cashtypeid','=','4'); //Ага, все настолько очевидно.

Также если нам очень захочется пописать кусочки запросов руками вспомнив старое, либо если хочется сделать что нибудь такое «эдакое» чего из коробки не понятно как сделать при помощи конструктора NyanORM вы можете использовать whereRaw('expression')/orWhereRaw('expression') например так:

$payments = new nya_payments();
$payments->whereRaw("`summ`>'0' AND `date` LIKE '" . curyear() . "-%' AND `cashtypeid`='1'");
$rawPayments = $payments->getAll('id');
debarr($rawPayments);

Что даст нам вполне себе идентичный результат. Но не красиво же, правда?

О очистке параметров

Следует кстати заметить, что после выполнения методов типа getAll(), delete() и им подобных. Все ранее установленные вами параметры моделей, такие как where, limit, order, data будут сброшены. Это сделано в целях безопасности. Например вот выбираете вы эти платежи, рассматриваете их, а потом, уже забыв, что вы это делали несколькими экранами кода выше, вы решили по какой-то причине сделать delete(). И все. Вам результат не понравился. Именно поэтому по-умолчанию происходит автоматическая очистка этих параметров. Метод delete(), например, также свою очередь ничего не делает без указанных явно where и бросается в вас исключениями.
Если по какой-то причине, вы не хотите, чтобы происходила автоматическая очистка параметров моделей, вы можете установить параметр $flushParams этих очищающих методов в значение false.
Также в любой момент, вы можете самостоятельно очистить состояние любых параметров конкретной модели использовав соответствующий сеттер со всеми пустыми параметрами.

$payments->where();
$payments->limit();
$payments->orderBy();
$payments->data();
$payments->selectable();

Заметили, как ненавязчиво мы вам показали какие еще параметры (кумулятивные структуры) у моделей бывают? Да? ;)

Удаление данных

Вы не поверите. Все настолько же линейно и просто. Давайте будем удалять запись из таблички abstractdevices с id 666?

$devices = new nya_abstractdevices();
$devices->where('id','=','666');
$devices->delete();

Кумулятивная структура data()

Кумулятивная структура data предназначена для хранения данных которые будут в дальнейшем использованы при вызове методов create() или save(). Собственно имеет она только два параметра, а именно field и value. Довольно не трудно догадаться как ее использовать:

$object->data('somefield', 'new value');
$object->data('anotherfield', 'тоже какие-то данные');

Создание и изменение записей

Помните кумулятивную структуру data()? Она нам потребуется для создания записей в модели либо изменения существующих. Давайте создадим новую запись приблизительно для такой таблички:

CREATE TABLE IF NOT EXISTS `someobjects` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `text` text,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

Все очень прямолинейно.

$object = new nya_someobjects();
$object->data('name', 'а это типа имя');
$object->data('text', 'это типа текст записи');
$object->create();

Заметьте, мы не указывали ручками NULL для автоинкрементного поля id, как так? А так, что у метода create() по умолчанию установлен параметр $autoAiId=true делающий это неявно. Если в вашей табличке нету автоинкрементного поля `id` или другого подобного primary key, вы должны установить этот параметр в false. Собственно имя поля главного ключа таблички вы всегда можете переназначить при помощи наследования. Он содержится в протектед проперти defaultPk.

Окей, запись создать мы создали, а как получить ее id? Для этого есть удобный метод getLastId() получающий последний defaultPk из таблички. Вот как это работает:

deb($object->getLastId()); // ой... возвращает 15

Окей, допустим мы внезапно захотели теперь изменить все или какое-то из полей в этой табличке. как быть? Все точно так-же как и с create() только при помощи save() но теперь нам еще понадобиться where(). Допустим мы будем редактировать последнюю запись в этой табличке:

$idToModify=$object->getLastId();
$object->data('text', 'воу, это же новое значение для text!');
$object->where('id', '=', $idToModify);
$object->save();

Включение режима отладки

Мы знаем. Вы привыкли использовать для отладки ваших модулей всякие print_r/debarr. Все, отвыкаем. Теперь можно легко и непринужденно включить режим отладки или глубокой отладки и получить нормальный лог и вывод всего происходящего с моделью.

$model->setDebug(true);

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

$ tail -F exports/nyanorm.log

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

$model->setDebug(true,true);

О исключениях

Если вы совсем обнаглеете от вседозволенности NyanORM вам в лицо могут быть выброшены следующие исключения:

  • MEOW_WHERE_STRUCT_EMPTY - кумулятивная структура where пуста. А она нужна. Очень.
  • MEOW_DATA_STRUCT_EMPTY - кумулятивная структура data пуста. И она тоже кому-то очень нужна.
  • MEOW_JOIN_WRONG_TYPE - неверный тип JOIN. Допустимы только INNER, LEFT, RIGHT.
  • MEOW_NO_FIELD_NAME - не установлено обязательное имя поля.

Принципиальная схема

Это где-то вот настолько высокоуровневая штука.

Так что да, в модулях где скорость работы с данными может быть узким местом, возможно придется использовать более традиционный подход с использованием api.mysql.

Что еще?

Короче вот пока что вам практические примеры использования этого в виде хеллоуворлда. Но так как я хеллоуворлды писать не умею, вот вам тудушка. Как говорят умные люди - не умеешь писать хеллоуворлды - пиши тудушки.

Работать наш TODO-list будет на следующей табличке в БД:

CREATE TABLE IF NOT EXISTS `todo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `text` text,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

А вот и весь код нашего модуля:

$todo = new nya_todo(); // Создаем модель данных при помощи магии. 
                       //Собственно todo после префикса nya_ это и есть наша табличка.
 
$moduleBaseUrl = '?module=testing'; //базовый URL нашего модуля
$messages = new UbillingMessageHelper(); //хотим штатные красивые уведомления
$result = '';
 
//Рендерим формочку создания используя инлайновую сборку при помощи Astral.
$inputs = wf_TextInput('newtasktext', __('Task'), '', false, 40);
$inputs .= wf_Submit(__('Create'));
$creationForm = wf_Form('', 'POST', $inputs, 'glamour');
show_window(__('Create new task'), $creationForm);
 
//Ловим непустой newtasktext как сигнал для создания новой записи
if (ubRouting::checkPost(array('newtasktext'))) {
    //заполняем новыми данными структуру data предварительно отфильтровав их средствами ubRouting
    $todo->data('text', ubRouting::post('newtasktext', 'mres'));
    $todo->create(); //говорим модели что "создайся запись" на основании структуры выше.
    ubRouting::nav($moduleBaseUrl); //возвращаемся в морду модуля
}
 
//Ловим запрос на удаление тудушки, на сей раз GET-ом.
if (ubRouting::checkGet('deletetodo')) {
    //выставляем параметр where в удаляемую id, предварительно убедившись, что это будет циферка
    $todo->where('id', '=', ubRouting::get('deletetodo', 'int'));
    $todo->delete(); // говорим модели "удались"
    ubRouting::nav($moduleBaseUrl);
}
 
//Ловим запрос на изменение существующей записи. Для сигнализации о начале ждем не пустой текст и айдишку.
if (ubRouting::checkPost(array('edittodoid', 'edittodotext'))) {
    //Дальше ведь все очевидно, правда? Выставляем где, выставляем чего поменять, фильтруем, говорим "сохранись".
    $todo->where('id', '=', ubRouting::post('edittodoid', 'int'));
    $todo->data('text', ubRouting::post('edittodotext', 'mres'));
    $todo->save();
    ubRouting::nav($moduleBaseUrl);
}
 
//Показываем существующие задачи которые нам нужно сделать.
$todo->orderBy('id', 'DESC'); //хотим сортировку от свежим к древним
$allTodos = $todo->getAll(); //достаем вообще все что видим из модели.
 
if (!empty($allTodos)) {
    $cells = wf_TableCell(__('Text'));
    $cells .= wf_TableCell(__('Actions'));
    $rows = wf_TableRow($cells, 'row1');
    foreach ($allTodos as $io => $each) {
        $cells = wf_TableCell($each['text']);
        $actControls = wf_JSAlert($moduleBaseUrl . '&deletetodo=' . $each['id'], web_delete_icon(), $messages->getDeleteAlert());
        //Прямо тут, собираем формочку редактирования каждой задачи и пихаем ее в контролы.
        $editInputs = wf_HiddenInput('edittodoid', $each['id']);
        $editInputs .= wf_TextInput('edittodotext', __('Text'), $each['text'], false, 40);
        $editInputs .= wf_Submit(__('Save'));
        $editForm = wf_Form('', 'POST', $editInputs, 'glamour');
        $actControls .= wf_modalAuto(web_edit_icon(), __('Edit'), $editForm);
        //Фу так делать.
        $cells .= wf_TableCell($actControls);
        $rows .= wf_TableRow($cells, 'row5');
    }
    $result .= wf_TableBody($rows, '100%', 0, 'sortable');
} else {
    $result .= $messages->getStyledMessage(__('Nothing to show'), 'warning');
}
show_window(__('Sample TODO list'), $result);
nyanorm.txt · Последние изменения: 2019/11/21 13:26 — nightfly
 
За исключением случаев, когда указано иное, содержимое этой вики предоставляется на условиях следующей лицензии: CC Attribution-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki