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 настаивает, чтобы мы моделировали пост веб-блога. Для этого нам нужно реализовать следующие шаги:

  1. Создать новый модуль «Weblog»
  2. Создать таблицу базы данных для нашей модели
  3. Добавить информацию о модели Blogpost к config.xml
  4. Для модели Blogpost добавить информацию о Model Resource в config.xml
  5. Для модели Blogpost добавить Read Adapter (читатель) в config.xml
  6. Для модели Blogpost добавить Write Adapter (записыватель) к config.xml
  7. Добавить файл с php-классом для модели Blogpost
  8. Добавить файл с php-классом для ресурса модели Blogpost
  9. Создать экземпляр модели

 

 

Создаем 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 есть три отдельные вещи, которые мы должны настроить для модели:

  1. Включение моделей в нашем модуле
  2. Включение ресурсов модели (Model Resource) в нашем модуле
  3. Добавить «сущность» (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

 
 

 

Share

Последние посты

Ричард Бах

Ни одно желание не дается тебе отдельно от силы, позволяющей его осуществить. Хотя, возможно, для… Читать далее

16/04/2024

Стивен Кинг

Жизнь – это непрерывный опыт, и даже самые плохие моменты занимают свое место в пазле… Читать далее

15/04/2024

неизвестный автор

Люди, которые любят одиночество, дорого заплатили за дружбу с кем-то... неизвестный автор   Читать далее

14/04/2024

Наполеон Хилл

Что разум человека может постигнуть и во что он может поверить, того он способен достичь… Читать далее

13/04/2024

Дейл Карнеги

Веди себя так, будто ты уже счастлив, и ты действительно станешь счастливее Дейл Карнеги   Читать далее

12/04/2024

Борьба с курением на ярких примерах социальной рекламы

Каждый год множество людей преждевременно умирает от сердечно-сосудистых заболеваний, рака, диабета, хронических заболеваний органов дыхания… Читать далее

11/04/2024