Це стара версія документу!
Зміст
Рівень абстракції NyanORM
Навіщо це все?
Робить НЯКУ з вашою базою. І надає дуже зручні засоби, щоб ви докладали мінімум зусиль і мозкових звивин при роботі з БД Ubilling. Ви зможете описувати ваші моделі даних, і роботу з ними, більше не малюючи в ручну SELECT `something` FROM `tablename`… та інші остогидлі вам конструкції. Також NyanORM візьме на себе всю вашу рутинну роботу з попередньої підготовки і трансформації ваших даних. Завданням NyanORM не бути всеосяжним монстром, який реалізує всі можливі кейси роботи з вашими даними. Завданням NyanORM є - бути простим та зручним. З самого початку, він задумувався для того, щоб скоротити обсяги рутинного, нудного й однотипного коду, з якого складається робота із сутностями в 90-95% прикладних модулів. Власне тільки ці кейси він і повинен перекривати, не накладаючи на вас жодних додаткових обмежень.
Особливості
Чому саме NyanORM, а не будь-яка інша стороння бібліотека з приставкою ORM у назві? А з таких причин:
- Не накладає жодних обмежень на іменування табличок БД.
- Не диктує жодних умов щодо полів і вмісту цих табличок.
- Дозволяє без проблем працювати з уже наявними структурами даних.
- Дозволяє міксувати код як з його використанням так і в старому стилі.
- Не вимагає довгого і клопіткого опису моделей, десь в окремому місці, перед тим як почати працювати.
- Ідеальний для надшвидкого прототипування бізнес-логіки прикладних модулів.
- Доступний у будь-який момент і завжди з будь-якого вашого модулю.
- Написаний у наскільки це можливо мінімалістичному стилі з максимальним використанням наявних механік Ubilling.
- Мінімізує оверхеди за пам'яттю і коллбеками. Але це не точно.
- Дозволяє наслідуванням модифікувати як завгодно будь-яку модель і методи роботи з нею, взагалі ніяк себе не обмежуючи.
- Не виставляє жодних вимог і засобів щодо фільтрації даних. Ви можете використовувати взагалі що захочете.
- Працює однаково відмінно на PHP5 і PHP7 та на PHP8. Легасі так.
- Максимально очевидний синтаксис і механіки вимагають для свого розуміння мінімального рівня 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();
ну або якось так без чорної магії, в лоб:
$switches = new NyanORM('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);


