Magento для PHP MVC розробників – Моделі та основи ORM (ч.4/11)
Реалізація «Рівня моделей» є значною частиною будь-якої MVC системи. Вона репрезентує дані з вашого додатку, а більшість додатків без даних є марними. Magento Моделі відіграють ще більшу роль, оскільки вони, як правило, містять “бізнес-логіку”, яку часто (правильно чи неправильно) відносять до методів Контролера або Хелпера в інших PHP MVC-фреймворках.
Традиційні PHP MVC моделі
Якщо визначення MVC нечітке, то визначення моделі (Model) є ще більш розмитим. До широкого впровадження MVC підходу, доступ до даних, як правило, відбувався за сирим SQL запитом і/або SQL-абстракцією. Розробники писали запити і особливо не задумувались про об’єкти, які були модельовані.
В наші дні сирий SQL, в основному, не схвалюється, але багато PHP фреймворків і досі є SQL центричними. Моделі будуть об’єктами, які надаватимуть певний шар абстракції, але деякі розробники і досі пишуть SQL і/або викликають SQL в якості методів абстракції для читання та запису своїх даних.
Інші фреймворки уникають сирого SQL і віддають перевагу підходу Об’єктно-реляційної проекції (ORM – Object Relational Mapping). В цьому випадку розробник має справу лише з Об’єктами. Властивості є набором, і коли метод збереження викликається в об’єкті, дані автоматично записуються в базу даних. Деякі ORM намагаються вгадати властивості об’єкта з бази даних, інші ж вимагають від користувача вказати їх певним чином (зазвичай з використанням абстрактної мови даних, такої як YAML). Одним з найвідоміших і популярних реалізацій цього підходу є ActiveRecord.
Надіюсь, що цього визначення ORM на даний момент буде достатньо для розуміння його суті.
Моделі Magento
Не дивуйтеся, що Magento надало перевагу ORM підходу. У той час як абстракції Zend Framework SQL є доступними, найбільший відсоток доступу до даних буде відбуватись вами через вбудовані в Magento Моделі та ті моделі, що побудуєте самі. Не повинно також бути несподіванкою, що Magento має високу гнучкість та високу абстрактність, що іноді може розмивати саме поняття Моделі.
Будова Magento Моделі
Більшість Magento Моделей можна класифікувати двома шляхами. Перший – це по типу ActiveRecord Моделі (один-об’єкт-одна-таблиця), та другий – це EAV (Entity Attribute Value) Модель. Кожна Модель також отримує колекцію моделей. Колекції – це PHP об’єкти, що використовуються для зберігання певного числа екземплярів окремих моделей Magento. Команда Мадженто реалізувала стандартну бібліотеку PHP інтерфейсів IteratorAggregate та Countable, що дозволяє кожному типу моделі мати свій власний тип колекції. Якщо ви не знайомі зі стандартною бібліотекою PHP, думайте про колекцію моделей як про масиви, що також мають прикріплені методи.
Моделі Magento не містять коду для підключення до бази даних. Натомість, кожна модель використовує клас modelResource
, що використовується для доступу до серверу бази даних (через одне читання та один запис у адаптері об’єкту). Відокремивши логічну модель та код, який спілкується з базою даних, теоретично можливо написати нові класи ресурсів для різних платформ та схем баз даних тримаючи, при цьому, свої моделі недоторканими.
Включення режиму розробника
Наступний крок ми будемо робити лише на період розробки, і ніколи не на працюючому сайті – це включення режиму розробника Magento. Цей режим, серед іншого, показує помилки коду у браузері, що буде корисним при налагодженні коду.
Увімкніть режим розробника за допомогою редагування файла .htaccess
у кореневій теці сайту. Додайте рядок на початку цього файлу:
SetEnv MAGE_IS_DEVELOPER_MODE "true"
Створення базової Моделі
Для початку ми створимо базову Модель Magento. Традиція PHP MVC наполягає, щоб ми моделювали пост веб-блогу. Для цього нам потрібно реалізувати наступні кроки:
- Створити новий модуль “Weblog”
- Створити таблицю бази даних для нашої Моделі
- Додати інформацію про модель Blogpost до
config.xml
- Для моделі Blogpost додати інформацію про Model Resource в config.xml
- Для моделі Blogpost додати Read Adapter (читач) до config.xml
- Для моделі Blogpost додати Write Adapter (записувач) до config.xml
- Додати файл з php-класом для моделі Blogpost
- Додати файл з php-класом для ресурсу моделі Blogpost
- Створити екземпляр моделі
Створюємо Weblog модуль
З попередніх статей цієї серії ви можете освіжити пам’ять щодо створення каркасу модулів, тому я пропущу подробиці і вважатиму, що ви вже в змозі створити порожній модуль з назвою Weblog
. Після того як ви це зробили, ми налаштуємо маршрут для index екшен контролера з назвою для дії (екшен) “testModel
“. Як завжди, наступні приклади належатимуть пакету модулів з ім’ям “Alanstormdotcom
” (ви ж повинні використовувати власний простір імен).
В файлі конфігу Alanstormdotcom/Weblog/etc/config.xml
встановлюємо наступну маршрутизацію:
<frontend> <routers> <weblog> <use>standard</use> <args> <module>Alanstormdotcom_Weblog</module> <frontName>weblog</frontName> </args> </weblog> </routers> </frontend>
і потім додайте index контролер у файлі Alanstormdotcom/Weblog/controllers/IndexController.php
:
class Alanstormdotcom_Weblog_IndexController extends Mage_Core_Controller_Front_Action { public function testModelAction() { echo 'Setup!'; } }
Очистіть кеш Magento і завантажте наступну адресу, щоб переконатися, що все це було налаштовано коректно:
VASH-DOMEN/weblog/index/testModel
Ви повинні побачити слово “Setup!” на білому фоні.
Створення таблиці бази даних
Magento має систему автоматичного створення і зміни схеми вашої бази даних але, на даний момент, ми вручну створимо таблицю для нашої моделі.
Використовуючи командний рядок, або свій улюблений додаток по роботі з MySQL, створимо таблицю з наступною схемою:
CREATE TABLE `blog_posts` ( `blogpost_id` int(11) NOT NULL auto_increment, `title` text, `post` text, `date` datetime default NULL, `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP, PRIMARY KEY (`blogpost_id`) )
і потім заповнимо цю нову таблицю деякими даними:
INSERT INTO `blog_posts` VALUES (1,'Заголовок','Це новий запис для блогу','2017-09-01 00:00:00','2017-09-02 23:10:00');
Глобальний Конфіг і створення Моделі
В нашому config.xml
є три окремі речі, які ми повинні налаштувати для моделі:
- Включення моделей в нашому модулі
- Включення ресурсів моделі (Model Resource) в нашому модулі
- Додати “сутність” (
entity
) налаштування таблиці до нашого Ресурсу Моделі.
При створенні екземпляра моделі в Magento, ви робите звернення на зразок цього:
$model = Mage::getModel('weblog/blogpost');
Перша частина URI передає в getModel
групову назву моделі (задається в config.xml). Оскільки слідування конвенції є гарною ідеєю, групова назва повинна бути назвою вашого модуля (в нижньому регістрі), або ж для захисту від конфліктів використовуйте назву пакету модулів та назву модуля (також у нижньому регістрі). Друга частина URI – це версія імені Моделі в нижньому регістрі.
Отож, давайте додамо нижченаведений XML до файлу config.xml
нашого модуля:
<global> <!-- ... --> <models> <weblog> <class>Alanstormdotcom_Weblog_Model</class> <!-- потрібно створити свій власний ресурс, не можна просто використовувати core_resource --> <resourceModel>weblog_resource</resourceModel> </weblog> </models> <!-- ... --> </global>
Зовнішній тег <weblog/>
– це групова назва моделі, повинна відповідати імені вашого модуля. <class/>
– це БАЗОВЕ ім’я всіх Моделей, які будуть в групі weblog (також називається префіксом класу). Тег <resourceModel/>
вказує, який Ресурс Моделі ця група Моделей weblog повинна використовувати. Нижче про це буде більше сказано, але поки-що будемо знати, що назва ресурсу моделі складається з назви групи, потім йде нижнє підкреслення і далі символьний рядок “resource
“.
Ми ще не завершили, але давайте подивимося, що станеться, якщо ми очистимо кеш Magento і спробуємо створити екземпляр Моделі blogpost. У методі testModelAction
контролера використайте наступний код:
public function testModelAction() { $blogpost = Mage::getModel('weblog/blogpost'); echo get_class($blogpost); }
і перезавантажте сторінку. Ви повинні побачити виняток, котрий виглядає приблизно так (якщо ви включили режим розробника на початку уроку):
Warning: include(Alanstormdotcom/Weblog/Model/Blogpost.php) [function.include]: failed to open stream: No such file or directory
Намагаючись отримати модель weblog/blogpost
, ви кажете Magento створити екземпляр класу з ім’ям
Alanstormdotcom_Weblog_Model_Blogpost
Magento намагається включити в __autoload
цю модель, але не може знайти відповідний файл класу. Тож давайте створимо його! Створіть клас за наступною адресою:
Файл: app/code/local/Alanstormdotcom/Weblog/Model/Blogpost.php class Alanstormdotcom_Weblog_Model_Blogpost extends Mage_Core_Model_Abstract { protected function _construct(){ $this->_init('weblog/blogpost'); } }
Перезавантажте сторінку і виключення повинне бути замінене ім’ям вашого класу.
Всі основні Моделі, що взаємодіють з базою даних, повинні розширювати клас Mage_Core_Model_Abstract
. Цей абстрактний клас змушує вас реалізувати один метод з ім’ям _construct
(ПРИМІТКА: це не конструктор PHP __construct
). Цей метод повинен викликати метод класу _init
з тим же ідентифікуючим URI, який ви будете використовувати при виклику методу Mage::getModel
.
Глобальний Конфіг і Ресурси
Отже, ми налаштували нашу Модель. Далі нам потрібно встановити наш Ресурс Моделі. Ресурси Моделі містять код, який безпосередньо спілкується з нашою базою даних. В останньому розділі конфігурації ми включили наступне:
<resourceModel>weblog_resource</resourceModel>
Значення в <resourceModel/>
буде використовуватися для створення екземпляра класу Ресурсу Моделі. Хоча ми ніколи не будемо викликати його самостійно, коли будь-яка модель в групі weblog потребує спілкування з базою даних, Magento оброблятиме наступний метод для отримання Ресурсу Моделі.
Mage::getResourceModel('weblog/blogpost');
Знову ж таки, weblog це ім’я Групи, а blogpost – це Модель. Метод Mage::getResourceModel
буде використовувати weblog/blogpost
URI, щоб оглянути глобальну конфігурацію і отримати значення <resourceModel>
(в даному випадку weblog_resource
). Потім клас моделі стане екземпляром з наступним URI:
weblog_resource/blogpost
Отже, якщо ви уважно слідували за уроком, то зрозуміли, що ресурсні моделі налаштовуються в тому ж розділі config.xml, що і звичайні Моделі. Це може ввести в оману як новачків, так і досвідчених користувачів.
Тож, пам’ятаючи це, давайте налаштуємо наш ресурс. У секції <models>
додамо
<global> <!-- ... --> <models> <!-- ... --> <weblog_resource> <class>Alanstormdotcom_Weblog_Model_Resource</class> </weblog_resource> </models> </global>
Ми додаємо тег <weblog_resource/>
, який є значенням тега <resourceModel/>
, котрий ми щойно встановили. Значення тегу <class/>
— це базове ім’я всіх ваших ресурсів моделей, і повинен бути іменований в такому форматі:
Packagename_Modulename_Model_Resource
Отже, ми маємо налаштований ресурс, давайте спробуємо завантажити деякі дані Моделі. Змініть екшен метод у контролері наступним чином:
public function testModelAction() { $params = $this->getRequest()->getParams(); $blogpost = Mage::getModel('weblog/blogpost'); echo("Loading the blogpost with an ID of ".$params['id']); $blogpost->load($params['id']); $data = $blogpost->getData(); var_dump($data); }
а потім завантажте наступну адресу у браузері (після очищення кеша Magento):
VASH-DOMEN/weblog/index/testModel/id/1
Ви повинні побачити виняток типу:
Warning: include(Alanstormdotcom\Weblog\Model\Resource\Blogpost.php) [function.include]: failed to open stream: No such file or directory in …
Ви, мабуть, інтуїтивно зрозуміли, що ми повинні додати клас ресурсів для нашої Моделі. Кожна Модель має свій власний клас ресурсів. Додайте такий клас за наступною адресою:
Файл: app/code/local/Alanstormdotcom/Weblog/Model/Resource/Blogpost.php class Alanstormdotcom_Weblog_Model_Resource_Blogpost extends Mage_Core_Model_Resource_Db_Abstract{ protected function _construct() { $this->_init('weblog/blogpost', 'blogpost_id'); } }
Знову ж таки, перший параметр init
методу це URL, що використовується для ідентифікації Моделі. Другий параметр – це поле бази даних, яке однозначно ідентифікує будь-яку конкретну колонку. У більшості випадків це повинно бути первинним ключем (primary key). Очистіть кеш, перезавантажте сторінку, і ви побачите:
Can't retrieve entity config: weblog/blogpost
Ще один виняток! Коли ми використовуємо weblog/blogpost
URI Моделі, ми кажемо Magento, що нам потрібна Група Моделей weblog
та сутність blogpost
. У контексті простих Моделей, що розширюють клас Mage_Core_Model_Resource_Db_Abstract
, сутність відповідає таблиці. У цьому випадку таблиця називається blog_post
, яку ми створили вище. Додамо цю сутність до нашого XML-файлу конфігурації.
<models> <!-- ... ---> <weblog_resource> <class>Alanstormdotcom_Weblog_Model_Resource</class> <entities> <blogpost> <table>blog_posts</table> </blogpost> </entities> </weblog_resource> </models>
Ми додали новий розділ <entities/>
до розділу ресурси Моделі у файл конфігурації модуля. А в цьому розділі є інший розділ, названий ім’ям нашої сутності (<blogpost/>
), який визначає назву таблиці бази даних, яку ми хочемо використовувати для цієї моделі.
Очистіть кеш Magento, схрестіть пальці, перезавантажте сторінку та…
Loading the blogpost with an ID of 1 array(5) { ["blogpost_id"]=> string(1) "1" ["title"]=> string(18) "Заголовок" ["post"]=> string(44) "Це новий запис для блогу" ["date"]=> string(19) "2017-09-01 00:00:00" ["timestamp"]=> string(19) "2017-09-02 23:10:00" }
Еврика! Нам вдалося витягти наші дані і, що більш важливо, повністю налаштувати Magento Модель.
Основні операції з Моделлю
Всі моделі Magento успадковані від класу Varien_Object
. Цей клас є частиною системної бібліотеки Magento і не частиною будь-якого модуля ядра (core) Magento. Ви можете знайти цей об’єкт в
lib/Varien/Object.php
Magento Моделі зберігають свої дані в захищених _data
властивостях. Клас Varian_Object
дає нам кілька методів, котрі ми можемо використовувати для доступу до цих даних. Ви вже бачили метод getData
, який повертає масив пари key/value
. Цьому методові також можна передати строкове значення ключа, щоб отримати значення конкретного поля.
$model->getData(); $model->getData('title');
Також є метод getOrigData
, який повертатиме дані моделі, у тому стані, коли об’єкт тільки був ініційований (звертається до захищеного методу _origData
).
$model->getOrigData(); $model->getOrigData('title');
Varien_Object
також реалізує деякі спеціальні методи через магічний php-метод __call
. Так, ви можете отримувати (get), встановлювати (set), виключати (unset), або перевіряти (has) наявність будь-яких властивостей, використовуючи методи, що починаються зі слова get
, set
, unset
або has
, і супроводжуються назвою властивості у верблюжому регістрі.
$model->getBlogpostId(); $model->setBlogpostId(25); $model->unsetBlogpostId(); if($model->hasBlogpostId()){...}
З цієї причини у майбутньому ви захочете називати всі стовпці у базі даних лише маленькими літерами (не прописними) і будете використовувати підкреслення для розділення слів у назві.
CRUD, шлях Magento
Моделі Magento підтримують основну функціональності CRUD (створення, читання, оновлення та видалення) методами load
, save
і delete
. Ви вже бачили завантаження цих методів в діях (action) контролера. Коли передається один параметр, метод load
поверне запис з таким id
поля (встановлюється в ресурсі Моделі), що порівнюється із значенням.
$blogpost->load(1);
Метод save
дозволить вам або вставляти (INSERT
) нову модель в базу даних, або оновлювати (UPDATE
) вже існуючу. Додайте до вашого контролера наступний метод:
public function createNewPostAction() { $blogpost = Mage::getModel('weblog/blogpost'); $blogpost->setTitle('Code Post!'); $blogpost->setPost('This post was created from code!'); $blogpost->save(); echo 'post with ID ' . $blogpost->getId() . ' created'; }
а далі виконуємо цей метод, завантажуючи у браузері наступний URL:
VASH-DOMEN/weblog/index/createNewPost
Після цього ви повинні побачити новий, доданий у таблицю blog_posts
бази даних, пост. Далі спробуйте наступний код у контролері для редагування першого посту:
public function editFirstPostAction() { $blogpost = Mage::getModel('weblog/blogpost'); $blogpost->load(1); $blogpost->setTitle("The First post!"); $blogpost->save(); echo 'post edited'; }
І, нарешті, ви можете видалити пост, використовуючи дуже схожий синтаксис:
public function deleteFirstPostAction() { $blogpost = Mage::getModel('weblog/blogpost'); $blogpost->load(1); $blogpost->delete(); echo 'post removed'; }
Колекції Моделі
Так, мати одну модель корисно, але іноді нам потрібно працювати з кількома подібними моделями. Замість того, щоб повертати звичайний масив моделей, кожен тип моделі Magento має унікальний, пов’язаний з ним об’єкт колекції. Ці об’єкти реалізуються php-інтерфейсами IteratorAggregate та Countable. Це означає, що вони можуть бути передані count
функції, і використовуватись в конструкціях foreach
.
В наступних статтях ми розкриємо Колекції більш повно. А зараз давайте поглянемо на типове налаштування і використання. Додайте наступний метод до контролера і завантажте його в своєму браузері
public function showAllBlogPostsAction() { $posts = Mage::getModel('weblog/blogpost')->getCollection(); foreach($posts as $blogpost){ echo '<h3>'.$blogpost->getTitle().'</h3>'; echo nl2br($blogpost->getPost()); } }
відкривши посилання:
VASH-DOMEN/weblog/index/showAllBlogPosts
Ви повинні побачити знайоме виключення (exception):
Warning: include(Alanstormdotcom\Weblog\Model\Resource\Blogpost\Collection.php) [function.include]: failed to open stream…
Ви ж не здивувались, побачивши виключення, чи не так? Нам потрібно додати php-файл класу, який визначить нашу колекцію Blogpost. Кожна модель має захищену (protected) властивість з іменем _resourceCollectionName
, яка містить URI, що використовується для ідентифікації нашої колекції.
protected '_resourceCollectionName' => string 'weblog/blogpost_collection'
За замовчуванням, це ж саме URI використовується для ідентифікації Ресурсу Моделі, з рядком _collection
доданим в кінець. Magento вважає Колекції частиною Ресурсу, отже цей URI конвертується в ім’я класу:
Alanstormdotcom_Weblog_Model_Resource_Blogpost_Collection
Тому додайте наступний PHP клас за адресою:
Файл: app/code/local/Magentotutorial/Weblog/Model/Resource/Blogpost/Collection.php class Alanstormdotcom_Weblog_Model_Resource_Blogpost_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract { protected function _construct() { $this->_init('weblog/blogpost'); } }
Так само, як і з іншими нашими класами, ми повинні ініціювати нашу Колекцію URI Моделі (weblog/blogpost
). Оновіть сторінку і ви повинні побачити інформацію всіх своїх постів.
Підсумок уроку
Вітаємо, ви створили і налаштували вашу першу Модель Magento. У наступній статті ми оглянемо Entity Attribute Value (EAV) Моделі Magento, які розширюють вже нами вивчене.
Автор: Alan Storm (http://alanstorm.com/magento_models_orm/)
Переклад українською: Ненько Сергій
Адаптація перекладу: SebWeo
- Magento для PHP MVC розробників (Alan Storm) – ч.1/11
- Magento для PHP MVC розробників – розбір Контролера (ч.2/11)
- Magento для PHP MVC розробників – Макети, Блоки та Шаблони (ч.3/11)
- Magento для PHP MVC розробників – Моделі та основи ORM (ч.4/11)
- Magento для PHP MVC розробників – Інсталювання Ресурсу (ч.5/11)
- Magento для PHP MVC розробників – Розширений ORM – EAV (ч.6/11)
- Magento для PHP MVC розробників – Особлива конфігурація системи (ч.7/11)
- Magento для PHP MVC розробників – Поглиблене налаштування системи (ч.8/11)
- Magento для PHP MVC розробників – Колекції Varien Data (ч.9/11)
- Magento для PHP MVC розробників – Перевизначення і оновлюваність системи (ч.10/11)
- Magento для PHP MVC розробників – Конфігурація системи за замовчуванням (ч.11/11)