Часть 03 - поддержка меню и обработка ошибок
Во фреймворке Joomla компоненты исполняются при помощи пунктов меню. Кроме того, используя пункт меню, компонент может устанавливать собственный загловок страницы в браузере, заголовок страницы компонента, а также использовать различные параметры. Если вы зайдете в менеджер меню и попытаетесь добавить пункт меню для нашего компонента, то не найдете в списке предложенных вариантов компонента HelloWorld!.
Добавление пункта меню
Добавить недостающий функционал довольно легко. Просто создайте файл site/views/helloworld/tmpl/default.xml с кодом:
<?xml version="1.0" encoding="utf-8"?> <metadata> <layout title="COM_HELLOWORLD_HELLOWORLD_VIEW_DEFAULT_TITLE"> <message>COM_HELLOWORLD_HELLOWORLD_VIEW_DEFAULT_DESC</message> </layout> </metadata>
Теперь в списке будет появляться наш HelloWorld!:
Правда на данный момент в административной части строки так и останутся не переведенными. В одной из следующих частей мы рассмотрим, как добавить поддержку мультиязычности и строки будут выглядеть более привлекательно.
Добавление параметров в меню
Сейчас наш компонент отображает только одно сообщение - Hello World!. Joomla 2.5 предоставляет возможность добавлять параметры в типы меню, тем самым мы можем расширить количество сообщений. В нашем случае, это делается в том же самом файле site/views/helloworld/tmpl/default.xml:
<?xml version="1.0" encoding="utf-8"?> <metadata> <layout title="COM_HELLOWORLD_HELLOWORLD_VIEW_DEFAULT_TITLE"> <message>COM_HELLOWORLD_HELLOWORLD_VIEW_DEFAULT_DESC</message> </layout> <fields name="request"> <fieldset name="request"> <field name="id" type="list" label="COM_HELLOWORLD_HELLOWORLD_FIELD_GREETING_LABEL" description="COM_HELLOWORLD_HELLOWORLD_FIELD_GREETING_DESC" default="1" > <option value="1">Hello World!</option> <option value="2">Good bye World!</option> </field> </fieldset> </fields> </metadata>
Здесь важно отметить, что request группа полей обозначает обязательные параметры. Мы добавили поле с типом list и список сообщений для этого поля.
Теперь необходимо изменить модель, чтобы она могла переключаться между разными сообщениями (которые выбираются пользователем в определенном выше поле):
site/models/helloworld.php
<?php // Запрет прямого доступа. defined('_JEXEC') or die; // Подключаем библиотеку modelitem Joomla. jimport('joomla.application.component.modelitem'); /** * Модель сообщения компонента HelloWorld. */ class HelloWorldModelHelloWorld extends JModelItem { /** * Получаем сообщение. * * @param int $id Id сообщения. * * @return string Сообщение, которое отображается пользователю. */ public function getItem($id = null) { // Если id не установлено, то получаем его из состояния. $id = (!empty($id)) ? $id : (int) $this->getState('message.id'); if (!isset($this->_item)) { switch ($id) { case 2: $this->_item = 'Good bye World!'; break; case 1: default: $this->_item = 'Hello World!'; break; } } return $this->_item; } /** * Метод для авто-заполнения состояния модели. * * Заметка. Вызов метода getState в этом методе приведет к рекурсии. * * @return void */ protected function populateState() { $app = JFactory::getApplication(); // Получаем Id сообщения из Запроса. $id = $app->input->getInt('id', 0); // Добавляем Id сообщения в состояние модели. $this->setState('message.id', $id); parent::populateState(); } }
Обратите внимание, что мы используем метод populateState()
для автозаполнения состояния модели. Состояние модели - это объект JObject, который хранится в свойстве $state класса JModel. Метод populateState()
вызывается единожды при первой попытке установить состояние модели через метод setState(), которая происходит в методе getModel() контроллера.
Пока мы заполняем состояние модели лишь одной переменной - Id сообщения. Таким образом, мы можем получать доступ к текущему Id сообщения, не запрашивая его постоянно из объекта Запроса.
Поддержка настроек пункта меню
Для того, чтобы компонент мог изменять заголовок страницы в браузере, заголовок страницы компонента и метаданные в зависимости от настроек в пункте меню, нам необходимо добавить эту поддержку в представление site/views/helloworld/view.html.php:
<?php // Запрет прямого доступа. defined('_JEXEC') or die; // Подключаем библиотеку представления Joomla. jimport('joomla.application.component.view'); /** * HTML представление сообщения компонента HelloWorld. */ class HelloWorldViewHelloWorld extends JViewLegacy { /** * Сообщение. * * @var string */ protected $item; /** * Параметры. * * @var object */ protected $params; /** * Переопределяем метод display класса JViewLegacy. * * @param string $tpl Имя файла шаблона. * * @return void */ public function display($tpl = null) { try { // Получаем сообщение из модели. $this->item = $this->get('Item'); // Получаем параметры приложения. $app = JFactory::getApplication(); $this->params = $app->getParams(); // Подготавливаем документ. $this->_prepareDocument(); // Отображаем представление. parent::display($tpl); } catch (Exception $e) { JFactory::getApplication()->enqueueMessage(JText::_('COM_HELLOWORLD_ERROR_OCCURRED'), 'error'); JLog::add($e->getMessage(), JLog::ERROR, 'com_helloworld'); } } /** * Подготавливает документ. * * @return void */ protected function _prepareDocument() { $app = JFactory::getApplication(); $menus = $app->getMenu(); $title = null; // Так как приложение устанавливает заголовок страницы по умолчанию, // мы получаем его из пункта меню. $menu = $menus->getActive(); if ($menu) { $this->params->def('page_heading', $this->params->get('page_title', $menu->title)); } else { $this->params->def('page_heading', JText::_('COM_HELLOWORLD_DEFAULT_PAGE_TITLE')); } // Получаем заголовок страницы в браузере из параметров. $title = $this->params->get('page_title', ''); if (empty($title)) { $title = $app->getCfg('sitename'); } elseif ($app->getCfg('sitename_pagetitles', 0) == 1) { $title = JText::sprintf('JPAGETITLE', $app->getCfg('sitename'), $title); } elseif ($app->getCfg('sitename_pagetitles', 0) == 2) { $title = JText::sprintf('JPAGETITLE', $title, $app->getCfg('sitename')); } if (empty($title)) { $title = $this->item; } // Устанавливаем заголовок страницы в браузере. $this->document->setTitle($title); // Добавляем поддержку метаданных из пункта меню. if ($this->params->get('menu-meta_description')) { $this->document->setDescription($this->params->get('menu-meta_description')); } if ($this->params->get('menu-meta_keywords')) { $this->document->setMetadata('keywords', $this->params->get('menu-meta_keywords')); } if ($this->params->get('robots')) { $this->document->setMetadata('robots', $this->params->get('robots')); } } }
Теперь представление, используя метод getParams()
получает параметры Приложения (которые включают в себя параметры меню), и пытается установить заголовок страницы компонента, заголовок страницы в браузере и метаданные исходя из имеющихся настроек в активном пункте меню.
Но откуда у Приложения появился метод getParams()
? В классе JApplication такого метода нет. Хитрость в том, что метод JApplication::getInstance() возвращает объект текущего приложения, класс которого располагается в файле /includes/application.php. Для публичной части - это JSite
, а для административной части - JAdministrator
. Метод getParams()
находится в классе JSite
.
И откуда у представления взялось свойство document
, c помощью которого мы манипулируем страницей? Если мы посмотрим на класс JView, то не обнаружим там такого свойства. Так как же оно попало в представление? А попало это свойство в представление из метода display() контроллера, который назначает его представлению, передавая объект класса JDocument. А точнее объект одного из дочерних классов: JDocumentHtml, JDocumentRaw и т.п. В нашем случае - это JDocumentHtml.
Изменям шаблон сообщения
Добавим вывод заголовка страницы компонента в шаблон site/views/helloworld/tmpl/default.php:
<?php // Запрет прямого доступа. defined('_JEXEC') or die; ?> <?php if ($this->params->get('show_page_heading')) : ?> <h1> <?php echo $this->escape($this->params->get('page_heading')); ?> </h1> <?php endif; ?> <h2><?php echo $this->item; ?></h2>
Здесь мы воспользовались методом escape(), чтобы экранировать спецсимволы HTML и избежать возможной XSS атаки.
Обработка ошибок
В Joomla по умолчанию используется свой внутренний механизм обработки ошибок с помощью классов JError и JException. Но начиная с версии Платформы 12.1 они отмечены как устаревшие и указано, что стоит использовать встроенный в PHP механизм исключений.
Наш компонент как можно меньше должен строится на устаревших классах или методах, поэтому в методе display()
задействован класс Exception
. Если возникло исключение, то мы делаем две вещи:
- Отображаем сообщение об ошибке пользователю с помошью метода enqueueMessage().
- Логируем ошибку с помощью метода JLog::add(). В первом параметре мы передаем ошибку, которую получаем из объекта исключения. Во втором параметре передаем уровень ошибки JLog::ERROR. А в третьем параметре указываем, что категория ошибки com_helloworld.
Но есть одно но. Чтобы использовать Exception
, необходимо установить свойство $legacy класса JError в значение false
. Самым лучшим местом для этого конечно же будет точка входа компонента site/helloworld.php. Добавьте сразу после подключения логирования следущий код:
// Устанавливаем обработку ошибок в режим использования Exception. JError::$legacy = false;
Собираем пакет установки компонента
Не забудьте поменять номер версии в файле helloworld.xml:
<version>0.0.3</version>
helloworld.xml
<?xml version="1.0" encoding="utf-8"?> <extension type="component" version="2.5.0" method="upgrade"> <name>Hello World!</name> <!-- Следующие элементы необязательны --> <creationDate>Июль 2012</creationDate> <author>Вася Пупкин</author> <authorEmail>Ваш e-mail</authorEmail> <authorUrl>Ваш сайт</authorUrl> <copyright>Информация о копирайте</copyright> <license>Информация о лицензии</license> <!-- Версия записывается в таблицу компонентов --> <version>0.0.3</version> <!-- Описание необязательно --> <description>Описание компонента Hello World! ...</description> <!-- Запускается при обновлении --> <update> <schemas> <schemapath type="mysql">sql/updates/mysql</schemapath> </schemas> </update> <!-- Раздел основных файлов сайта --> <!-- Обратите внимание на значение аттрибута folder: Этот аттрибут описывает папку нашего пакета-установщика из которой должны копироваться файлы. Поэтому указанные в этом разделе файлы будут скопированы из папки /site/ нашего пакета-установщика в соответствующую папку установки. --> <files folder="site"> <filename>index.html</filename> <filename>controller.php</filename> <filename>helloworld.php</filename> <folder>models</folder> <folder>views</folder> </files> <!-- Администрирование --> <administration> <!-- Раздел Меню --> <menu>Hello World!</menu> <!-- Раздел основных файлов администрирования --> <!-- Обратите внимание на значение аттрибута folder: Этот аттрибут описывает папку нашего пакета-установщика из которой должны копироваться файлы. Поэтому указанные в этом разделе файлы будут скопированы из папки /admin/ нашего пакета-установщика в соответствующую папку установки. --> <files folder="admin"> <filename>index.html</filename> <filename>helloworld.php</filename> <folder>sql</folder> </files> </administration> </extension>
Содержимое директории с кодом:
helloworld.xml
site/index.html
site/controller.php
site/helloworld.php
site/models/index.html
site/models/helloworld.php
site/views/index.html
site/views/helloworld/index.html
site/views/helloworld/view.html.php
site/views/helloworld/tmpl/index.html
site/views/helloworld/tmpl/default.php
site/views/helloworld/tmpl/default.xml
admin/index.html
admin/helloworld.php
admin/sql/index.html
admin/sql/updates/index.html
admin/sql/updates/mysql/index.html
admin/sql/updates/mysql/0.0.1.sql
Запакуйте директорию в архивный файл (zip, tar, tar.gz, bz2) или скачайте напрямую с GitHub. Далее установите его, используя менеджер расширений Joomla. Теперь вы можете добавить и настроить пункт меню для компонента, и выбрать сообщение, которое будет отображаться пользователю.
В следущей части мы добавим поддержку базы данных.
Код для этой части
Скачать com_helloworld часть 3
Актуальный код части 3 на GitHub