Опубликован: 02.10.2012 | Доступ: свободный | Студентов: 1860 / 70 | Длительность: 11:55:00
Теги: joomla, mvc, php, xhtml, xml
Лекция 6:

Архитектура MVC в компонентах Joomla

< Лекция 5 || Лекция 6: 123 || Лекция 7 >
Просмотр одного вопроса

Код для отображения одного вопроса аналогичен коду для отображения списка вопросов. Создайте файл /components/com_myquestions/views/question/view.html.php:

<?php
defined('_JEXEC') or die('Restricted access');
jimport('joomla.application.component.view');
class QuestionViewQuestion extends JView
{
  function display($tpl=null)
  {
if ($tpl !== 'form')
    {
      global $option;
      $model=&$this->getModel();
      $question=$model->getQuestion();
      $question->date=JHTML::Date($question->date);
     
      $this->assignRef('question', $question);
       $this->assignRef('option', $option);

      $this->assignRef('link_cat',JRoute::_('index.php?option='.$option.'&
      id='.$question->id_cat.'&view=category&task=show'));
    }  
     parent::display($tpl);
  }
}
?>
            

Представление question будет соответствовать двум шаблонам - один для отображения вопроса, второй для вывода формы для отправки вопроса. Для первого шаблона необходимы данные о вопросе, которые мы получаем из модели. Для второго шаблона не требуется никаких данных кроме имени пользователя, которое мы определим в контроллере.

Напишем шаблон для отображения одного вопроса. Создайте файл /components/com_myquestions/views/question/tmpl/default.php:

<?php
  defined('_JEXEC') or die('Restricted access');
  global $option;
  echo "<a href=\"".JRoute::_('index.php?option='.$option.'&view=question&task=showform')."\">"
.JText::_('COM_MYQUESTIONS_ADD_QUESTION')."</a>";
?>
<table width="100%">
  <tr>
    <td><i><?=$this->question->name?></i></td>
    <td><i><u><?=$this->question->email?></u></i></td>
    <td><i><?=JHTML::_('date', $this->question->date,
JText::_('DATE_FORMAT_LC3'))?></i></td>
    <td><i><?=$this->question->city?></i></td>
  </tr>
  <tr>
    <td colspan="4"><a href="<?=$this->
link_cat?>"><?=$this->question->name_cat?></a></td>
  </tr>
  <tr>
    <td colspan="4"><b><?=$this-
>question->question?></b></td>
  </tr>
  <tr>
    <td colspan="4"><?=$this->question->answer?></td>
  </tr>
</table>
            

Добавим другой шаблон, отображающий форму для написания вопроса. Создайте файл /components/com_myquestions/views/question/tmpl/default_form.php:

<?php
  defined('_JEXEC') or die('Restricted access');
?>
<form action="<?=JRoute::_('index.php')?>" method="post">
  <table>
    <tr>
      <td width="100">
         <?php echo JText::_('COM_MYQUESTIONS_AUTHOR');?>:
      </td>
      <td>
        <input class="text_area" type="text" name="name" 
id="name" size="50" maxlength="255" 
value="<?php echo $this->user_name;?>"/>
      </td>
    </tr>  
    <tr>
      <td width="100">
        <?php echo JText::_('COM_MYQUESTIONS_CITY');?>:
      </td>
      <td>
        <input class="text_area" type="text" 
name="city" id="city" size="50" maxlength="50"/>
      </td>
    </tr>
    <tr>
      <td width="100">
        <?php echo JText::_('COM_MYQUESTIONS_EMAIL');?>:
      </td>
      <td>
        <input class="text_area" type="text"
 name="email" id="email" size="50" maxlength="50"/>
      </td>
    </tr>          
    <tr>
      <td width="100">
        <?php echo JText::_('COM_MYQUESTIONS_QUESTION');?>:
      </td>
      <td>
        <textarea name='question' id='question' class='inputbox' rows='15' cols='38'></textarea>
      </td>
    </tr>
    <tr>
      <td width="100">
        <?php echo JText::_('COM_MYQUESTIONS_PUBLISHED');?>:
      </td>
      <td>
        <input type="hidden" name="published" value="0"/>
        <input type="checkbox" name="published" id="published" value="1"/>
      </td>
    </tr>
  </table>
  <input type="hidden" name="task" 
value="addquestion"/>
  <input type="hidden" name="option" 
value="<?=JRequest::getVar("option","")?>"/>
  <input type="submit" class="button" id="button"
 value="<?php echo JText::_('COM_MYQUESTIONS_SENDBUTTON');?>"/>
</form>
            
Листинг .

Создание контроллера

Создайте файл /components/com_myquestions/controller.php (метод addQuestion() скопируйте из файла /components/com_myquestions/myquestions.php, убрав параметр $option):

<?php
defined('_JEXEC') or die('Restricted access');
jimport('joomla.application.component.controller');
class QuestionController extends JController
{
  function display()
  {
    $document =& JFactory::getDocument();
    $viewName = JRequest::getVar('view', 'all');
    $viewType = $document->getType();
    $view = &$this->getView($viewName, $viewType);
    $model =& $this->getModel($viewName, 'ModelMyQuestions');
    if (!JError::isError($model))
    {
      $view->setModel($model, true);
    }
    $view->setLayout('default');
    $view->display();
  }
  function showForm()
  {
    $document =& JFactory::getDocument();
    $viewName = JRequest::getVar('view', 'question');
    $viewType = $document->getType();
    $view = &$this->getView($viewName, $viewType);
    $user =&JFactory::getUser();
    if($user->name)
      $view->user_name = $user->name;
    else
      $view->user_name = '';
    $view->display('form'); 
  }
  function addQuestion()
  {
    …
  }
}
?>
        

В методе display() мы получаем название запрашиваемого представления и тип текущего документа, который одновременно является и типом представления. Затем получаем ссылку на соответствующее представление и ссылку на одноименную модель. Добавляем модель к представлению, назначив ее по умолчанию. Задаем имя макета - default и вызываем метод JView::display(), который выполнит скрипт /components/com_myquestions/views/all/tmpl/default.php.

В методе showForm() мы также получаем объект-представитель текущего пользователя JFactory::getUser(), чтобы подставить его имя в форму для написания вопроса. Выражение $view->display('form') отображает шаблон из файла default_form.php (т.е. имя файла в данном случае строится по схеме "default"+"_"+tpl, где tpl - параметр функции display()).

Метод addQuestion() добавляет новый вопрос в базу данных точно так же, как это делалось ранее. Обратите внимание на то, что название этого метода совпадает со значением, которое хранилось в скрытом элементе task формы для добавления вопроса:

<input type="hidden" name="task" value="addquestion"/>
        

Напишем код для создания объекта контроллера. Откройте файл /components/com_myquestions/myquestions.php и замените существующий код следующим:

<?php
defined('_JEXEC') or die('Restricted access');
require_once(JPATH_COMPONENT.DS.'controller.php');
JTable::addIncludePath(JPATH_ADMINISTRATOR.
DS.'components'.DS.'com_myquestions'.DS.'tables');
$controller = new QuestionController();
$controller->execute(JRequest::getVar('task'));
$controller->redirect();
?>
        

С помощью строки require_once(JPATH_COMPONENT.DS.'controller.php') подключается содержимое файла, содержащего код класса контроллера.

Изменение шаблона SEF-ссылок

Шаблон SEF-ссылок, использовавшийся нами до сих пор, не годится для применения в компоненте MVC, т.к. включает только переменные task и id. Для компонента MVC в URL должно быть задано еще по меньшей мере значение view.

Возможно, вы заметили, что в коде фронтенда, переделанном с учетом модели MVC, мы строили URL по шаблону option/view/task/id при включенных SEF и option=com_myquestions&view=value1&task=value2&id=value3 в противном случае. Для наглядности ниже приведено несколько примеров таких ссылок ( таблица 6.1).

Таблица 6.1. Примеры ссылок, использующихся в MVC-версии компонента myquestions
Ссылка view task id Значение
/myquestions/category/show/1 category show 1 Просмотр категории #1
/myquestions/question/show/1 question show 1 Просмотр вопроса #1
/myquestions/category/show/all category show all Просмотр вопросов из всех категорий
/myquestions/all/show all show - Просмотр списка всех категорий
/myquestions/question/showform question showform - Вывод формы для написания вопроса

Изменим функции генерации и декодирования SEF-ссылок. Откройте файл /components/com_myquestions/router.php и измените код функции MyQuestionsBuildRoute() следующим образом:

function MyQuestionsBuildRoute(&$query)
{
  $segments = array();
    if (isset($query['view']))
  {
    $segments[] = $query['view'];
    unset($query['view']);
  }
  if (isset($query['task']))
  {
    $segments[] = $query['task'];
    unset($query['task']);
  }
  if (isset($query['id']))
  {
    $segments[] = $query['id'];
    unset($query['id']);
  }
  return $segments;
}
        

В том же файле замените функцию MyQuestionsParseRoute() следующей:

function MyQuestionsParseRoute ($segments)
{
  $vars = array();
  $vars['view'] = $segments[0];
  if (count($segments) > 1)
  {
         $vars['task'] = $segments[1];
    if (count($segments) > 2)
      $vars['id'] = $segments[2];
  }
  return $vars;
}
        

Как видите, теперь мы предполагаем, что первый элемент в массиве segments - это view, второй - task, а третий - id.

Добавление контроллера к коду бэкенда

Бэкенд не нуждается в большом контроле над форматом вывода, поэтому его можно не переводить на архитектуру MVC. Добавим только контроллер, чтобы исключить выражение switch().

Создайте файл /administrator/components/com_myquestions/controller.php. В нем мы объявим класс QuestionController. В конструкторе этого контроллера регистрируются задачи, взятые из старого кода переключателя switch из файла admin.myquestions.php.

<?php
defined('_JEXEC') or die('Restricted access');
jimport('joomla.application.component.controller');

class QuestionController extends JController
{
  function __construct($default = array())
  {
    parent::__construct($default);
    
    $this->registerTask('reply', 'replyToQuestion');
    $this->registerTask('save', 'saveQuestion');
    $this->registerTask('apply', 'saveQuestion');
    $this->registerTask('remove', 'removeQuestions');
    $this->registerTask('sendToExpert', 'send');
    $this->registerTask('sendAnswer', 'send');
    
    $this->registerTask('showCat', 'showCategories');
    $this->registerTask('addCat', 'editCategory');
    $this->registerTask('editCat', 'editCategory');
    $this->registerTask('saveCat', 'saveCategory');
    $this->registerTask('applyCat', 'saveCategory');
    $this->registerTask('removeCat', 'removeCategories');
  }
}
?>
        

Все функции из файла admin.myquestions.php перейдут в класс QuestionController в качестве методов практически без изменений, за исключением одного аспекта. Отказ от выражения switch ведет к невозможности передавать переменные непосредственно в методы класса контроллера. Поэтому необходимо либо добавлять в класс контроллера новые поля, либо получить переменные из переменных HTTP-запроса или других источников непосредственно в коде каждого метода. В нашем примере почти все методы используют значения переменных option и task. Теперь эти значения будут не передаваться как параметры, а извлекаться из HTTP-запроса с помощью функции JRequest(). Например, первые строки функции saveQuestion() примут вид:

function saveQuestion()
{
  $option = JRequest::getVar('option');
  $task = JRequest::getVar('task');
  $row = $this->save();
  ...
}
        

Итак, перенесите в класс QuestionController функции replyToQuestion(), save(), saveQuestion() и др. Затем замените содержимое файла admin.myquestions.php следующим кодом:

<?php defined('_JEXEC') or die('Restricted access'); require_once(JApplicationHelper::getPath('admin_html')); 
require_once(JPATH_COMPONENT.DS.'controller.php'); JTable::addIncludePath(JPATH_COMPONENT.DS.'tables'); 
$controller = new QuestionController(array('default_task' => 'showQuestions')); $controller->execute(JRequest::getVar('task')); 
$controller->redirect(); ?>
        

Как вы уже заметили, конструктор нашего контроллера в бэкенде имеет параметр default. При вызове конструктора мы передаем в него массив, который хранит значение default_task, равное showQuestions. Таким путем задано название задачи, которая будет выполнена по умолчанию.

Ключевые термины

JController - абстрактный класс для реализации контроллеров.
JModel - абстрактный класс для реализации моделей.
JView - абстрактный класс для реализации представлений.
Регистрация задачи - сопоставление ее какому-либо методу класса, производного от JController.

Краткие итоги

Joomla поддерживает архитектуру MVC для компонентов. Модели, представления и контроллеры реализуются соответственно с помощью абстрактных классов JModel, JView и JController. В компоненте могут быть созданы классы, производные от всех или некоторых из этих классов.

Вот простейшая схема взаимодействия модели, представления и контроллера.

В файле, который находится в корневой папке компонента и называется так же, как компонент, находится код для создания контроллера и вызова его методов execute() и redirect(). Метод execute() вызывает метод контроллера, который называется так же, как и заданная задача.

Класс контроллера, производный от JController, содержит методы для каждой задачи, которую должен выполнять компонент. Метод JController::display(), который вызывается по умолчанию, вызывает методы getView(), getModel(), а также метод display() заданного представления.

В классе представления, производном от JView, может быть перегружен метод display() для вызова метода класса модели для загрузки данных и сохранения результата в какой-либо переменной. Затем с помощью метода JView::assignRef() эта переменная связывается с текущим представлением и вызывается метод базового класса JView::display(), который загружает файл заданного шаблона при помощи перехвата выходного потока.

В коде шаблона осуществляется вывод на экран переменных текущего представления.

Вопросы

  1. Какие классы Joomla позволяют реализовать элементы архитектуры MVC?
  2. Опишите схему взаимодействия модели, представления и контроллера.
  3. Что такое регистрация задачи?

Упражнения

Адаптируйте код из раздела "Практика" для своего варианта (см. список вариантов в "Варианты заданий для лабораторных работ" ).

< Лекция 5 || Лекция 6: 123 || Лекция 7 >
Кирилл Гусаров
Кирилл Гусаров

В разделе "Первые папки и файлы. Добавление пунктов меню"

предлагается создать две файла:

- myquestions.php;

- admin.myquestions.php

с соответствуюшими адресами:

/components/com_myquestions/myquestions.php;

- /administrator/components/com_myquestions/admin.myquestions.php;

Так вот, при создании файла "admin.myquestions.php" В админке выдает ошибку - "Компонент не найден", а при переименовании его на  "myquestions.php" в последующем шаге, в админке не выводятся кнопки редактирования. 
Проверил кодировку, проверил правильность пути к файлам, пересохранил указанный код. Скажите что я делаю не так или в чем может быть причина?

Антон Коломыцев
Антон Коломыцев
Россия
Андрей Квартник
Андрей Квартник
Россия, Ростов-на-Дону, РАНХиГС, 2004