- Введение
- Общее описание
- Шаг 1 Загрузка формы
- Шаг 2 Подготовка данных для предварительного заполнения
- Шаг 3 Вывод формы в HTML
- Шаг 4 Отправка формы пользователем
- Шаг 5 Обработка данных HTTP POST
- Образец формы 1
- MVC и другие соображения
- Образец формы 2
Введение
Это одно из серии руководств по API, цель которых - помочь понять, как использовать API Joomla, предоставляя подробные объяснения и примеры кода, которые можно легко установить и запустить.
В Joomla класс Form
и API позволяют легко разрабатывать HTML формы и обрабатывать присылаемые пользователем данные.
Ниже описано общее взаимодействие с классом Joomla Form
и приведен код простого компонента, который можно установить для демонстрации использования этого API. В сопутствующем руководстве по использования форм в Joomla (Advanced form guide) рассматриваются более продвинутые аспекты класса Joomla Form
.
Сама Joomla также имеет особый способ проектирования функциональности форм, и это руководство включает обучающий материал, который поможет понять и разработать собственный компонент в соответствии с этими принципами.
Общее описание
На диаграмме ниже показано основное использование класса Joomla Form
.
Во-первых, вам необходимо определить вашу форму в XML, используя стандартные типы полей формы Joomla. Для примера ниже приведён код компонента. В основных терминах, каждый элемент поля в вашем XML-файле соответствует элементу HTML (в основном "input
") в форме, с атрибутами поля XML, соответствующими атрибутам элемента input
. Многие из возможных атрибутов поля перечислены в разделе Текстовый тип поля формы.
Шаг 1 Загрузка формы
Пользователь переходит по URL вашей формы и Joomla направляет HTTP GET запрос к вашему коду компонента. Нужно вызвать Form::getInstance()
, передав имя специального XML-файла с определением формы из которой идёт запрос. Код формы Joomla создает экземпляр формы, а затем (в Form::loadFile()
) считывает файл в память (в качестве PHP SimpleXMLElement
) и анализирует XML, чтобы убедиться в его корректности. Основными параметрами, передаваемыми в getInstance()
, являются:
name
- строка, определяющая идентификатор, который будет присвоен этой форме - он должен быть уникальным, чтобы не пересекаться с другими формами Joomla на той же веб-странице.data
- имя XML-файла, в котором содержится определение обрабатываемой формыoptions
- массив параметров, причем элементcontrol
указывает имя массива, в котором будут храниться POST-параметры.- Например, в примере кода
array("control"=>"myform"
) означает, что у элементов ввода HTML атрибутыname
будут установлены в значенияmyform[message]
,myform[email]
и т.д.. Таким образом, они будут переданы в POST-параметрах, и затем их очень легко получить в массиве PHP.
- Например, в примере кода
Шаг 2 Подготовка данных для предварительного заполнения
Получение значения для любого элемента формы по своему усмотрению. Например, если эта форма используется для редактирования записи в базе данных, то предварительно заполните ее значениями существующих полей из базы данных. Можно подготовить значения, создав ассоциативный массив $data
и передав его в метод Form::bind($data)
, а класс Form
Joomla затем сохранит эти данные локально в экземпляре формы.
Шаг 3 Вывод формы в HTML
Вызов renderField(fieldName)
на экземпляре формы позволяет получить HTML, который можно вывести с помощью простой функции PHP echo
. Joomla обрабатывает XML-представление формы, чтобы получить раздел, относящийся к переданному полю fieldName
, генерирует HTML для этого HTML-элемента и включает атрибут value
на основе предварительно заполненных данных, которые были переданы в шаге 2. При выводе формы также необходимо окружить элементы ввода элементом <form>
и добавить кнопку отправки.
Шаг 4 Отправка формы пользователем
Пользователь вводит данные в подготовленную и выведенную на экран HTML-форму и нажимает на кнопку отправки. Браузер генерирует HTTP POST запрос на URL, указанный в элементе <form>
, и передает серверу в POST параметрах значения, введенные пользователем; каждый параметр определяется атрибутом name
HTML элемента input
.
Joomla направляет этот POST в компонент на обработку. Поскольку это новый HTTP-запрос, предыдущий экземпляр формы больше не существует, поэтому нужно снова вызвать Form::getInstance()
, передав имя XML-файла формы, и Joomla (как и раньше) создает экземпляр формы и считывает полученные данные в память.
Шаг 5 Обработка данных HTTP POST
На этом этапе происходит обработка полученных данных. Это включает в себя:
- Получение POST-данных с помощью
Factory::getApplication->input->get()
. Обычно атрибутыname
элементов ввода определены так, что POST-параметры отображаются в виде массива, и можно использовать фильтр ARRAY из полученных данных запроса с помощью JInput, чтобы считать их непосредственно в массив PHP. Однако это означает, что отдельные элементы вообще не фильтруются. - Фильтрация. Здесь применяется фильтрация к каждому из вводимых значений. Фильтр, применяемый к полю, определяется атрибутом
filter=...
для этого поля в XML-файле формы, а если он отсутствует, то фильтр по умолчанию будет удалять HTML-теги и т.д. из значений данных. Возможные фильтры находятся в методеfilterField()
классаForm
(см. https://joomla.stackexchange.com/questions/5764/what-are-possible-filters-in-joomla-form-fields). Обратите внимание, что эти фильтры полей формы отличаются от фильтровjinput
. - Валидация. Проверка данных, которые ввел пользователь, происходит вызовом
validate($data)
в обрабатываемом экземпляре формы, путём передачи ассоциативного массива (отфильтрованных) данных пользователя. Joomla сравнивает введенные пользователем данные с проверкой, которая определена в XML-файле формы, и генерирует ошибки для полей, которые не прошли проверку.
Если есть ошибки валидации, то необходимо отобразить эти ошибки пользователю и заново отобразить форму, предварительно заполнив поля теми (отфильтрованными) данными, которые пользователь ввел ранее.
Если ошибок нет, то можно подтвердить успешное получение данных пользователю и показать ему соответствующее сообщение.
Образец формы 1
Ниже приведен код небольшого компонента, который можно установить для проверки и понимания работы базового использования форм Joomla. Для этого нужно скопировать следующие 3 файла в папку под названием com_sample_form1
. Затем заархивировать папку, чтобы получить файл com_sample_form1.zip
и установить его в качестве компонента на сайте с CMS Joomla.
com_sample_form1.xml
Файл манифеста для компонента:
<?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="3.1.0" method="upgrade">
<name>com_sample_form1</name>
<version>1.0.0</version>
<description>Sample form 1</description>
<administration>
</administration>
<files folder="site">
<filename>sample_form1.php</filename>
<filename>sample_form.xml</filename>
</files>
</extension>
sample_form.xml
Файл, содержащий XML для определения формы:
<?xml version="1.0" encoding="utf-8"?>
<form>
<field
name="message"
type="text"
label="Enter message"
size="40"
class="inputbox"
required="true" />
<field name="email"
type="email"
label="Enter email"
required="true"
size="40"
class="inputbox" />
<field name="telephone"
type="tel"
label="Enter telephone number"
required="true"
size="40"
class="inputbox"
validate="tel" />
</form>
sample_form1.php
Код компонента:
<?php
defined('_JEXEC') or die('Restricted access');
use Joomla\CMS\Form\Form;
use Joomla\CMS\Factory;
$form = Form::getInstance("sample", __DIR__ . "/sample_form.xml", array("control" => "myform"));
$prefillData = array("email" => ".@.");
if ($_SERVER['REQUEST_METHOD'] === 'POST')
{
$app = JFactory::getApplication();
$data = $app->input->post->get('myform', array(), "array");
echo "Message was " . $data["message"] .
", email was " . $data["email"] .
", and telephone was " . $data["telephone"] . "<br>";
$filteredData = $form->filter($data);
$result = $form->validate($filteredData);
if ($result)
{
echo "Validation passed ok<br>";
}
else
{
echo "Validation failed<br>";
$errors = $form->getErrors();
foreach ($errors as $error)
{
echo $error->getMessage() . "<br>";
}
// в повторно отображаемой форме показывать то, что ввел пользователь (после фильтрации данных)
$prefillData = $filteredData;
}
}
$form->bind($prefillData);
?>
<form action="<?php echo JRoute::_('index.php?option=com_sample_form1'); ?>"
method="post" name="sampleForm" id="adminForm" enctype="multipart/form-data">
<?php echo $form->renderField('message'); ?>
<?php echo $form->renderField('email'); ?>
<?php echo $form->renderField('telephone'); ?>
<button type="submit">Submit</button>
</form>
После установки нужно перейти на сайт и добавить следующий параметр в URL: &option=com_sample_form1
. После этого отобразится форма с 3 обязательными полями:
- поле ввода общего текста для сообщения
- поле ввода электронной почты, предварительно заполненное строкой
.@.
- поле ввода номера телефона.
и, используя средства разработки браузера, можно сравнить атрибуты html с атрибутами в определении формы XML.
Обратите внимание, что современные браузеры выполняют некоторую проверку вводимых пользователем значений, в частности, они проверяют адрес электронной почты и заставляют пользователя вводить что-то в поля с установленным атрибутом required
, но не выполняют (в настоящее время) проверку полей телефонных номеров.
Как только пользователь введёт правильные данные в поля и нажмет кнопку Submit, данные будут отправлены на сервер и будет запущена обработка POST кода примера. Это запускает процедуры фильтрации и валидации. Если есть ошибки валидации, код выводит сообщения об ошибках и заполняет форму данными, которые ввел пользователь, после чего заново выводит ее на экран.
MVC и другие соображения
То, как написан приведенный выше код, не является лучшим подходом, если разрабатывается настоящий компонент Joomla. Вместо этого необходимо следовать тому, как разработан код ядра Joomla. В частности, разделять разрабатываемый компонент на контроллеры, модели, представления и шаблоны, и пересмотренный код компонента в следующем разделе следует этому подходу. В оставшейся части этого раздела описаны эти и другие проектные решения, которые должны облегчить понимание кода примера.
Реализация MVC в Joomla
В общих чертах Joomla разделяет компоненты на отдельные типы функциональности:
- контроллер содержит "бизнес-логику" приложения, включая принятие решения о том, какое представление и модель использовать;
- модель обеспечивает доступ к данным;
- представление решает, какие данные необходимо вывести на веб-страницу, и получает эти данные из модели;
- шаблон выводит HTML и включает в вывод данные, которые были собраны представлением. Шаблон работает в контексте представления и поэтому имеет прямой доступ к переменным кода представления.
Шаблоны Post/Request/Get
В Joomla весь вывод HTML (например, отображение формы) выполняется в ответ на HTTP GET, следуя паттерну программирования https://en.wikipedia.org/wiki/Post/Redirect/Get. Приведенный выше пример кода не следует этому паттерну программирования, а вместо этого выводит ошибки проверки и повторно отображает форму в ответ на запрос HTTP POST.
Чтобы следовать стилю программирования Joomla, в коде, который обрабатывает POST, необходимо включить HTTP GET перенаправление на URL формы. Поскольку этот GET будет новым HTTP запросом/ответом, необходимо сохранить в сессии пользователя данные, которые будут показаны при повторном отображении формы:
- сообщения об ошибках валидации сохраняются и выводятся с помощью метода
enqueueMessage()
(который автоматически сохраняет данные в сессии пользователя) - данные, введенные пользователем, сохраняются с помощью
setUserState()
и извлекаются с помощьюgetUserState()
, а ключом служитcontext
, который должен быть уникальным для данной формы.- Код, предоставляющий данные для операции
bind()
формы, должен сначала проверить с помощьюgetUserState()
, есть ли в сессии данные предварительного заполнения. - И если пользователь вводит данные, которые успешно проходят проверку, то необходимо вызвать
setUserState()
, чтобы очистить данные предварительного заполнения в сессии, иначе они будут появляться всякий раз, когда пользователь в следующий раз запросит вывод формы.
- Код, предоставляющий данные для операции
Отдельные контроллеры
В ответ на запросы GET или POST, Joomla всегда запускает один и тот же файл компонента, sample_form1.php
в примере компонента выше и верхний уровень sample_form2.php
в примере ниже. Код sample_form2.php
ниже следует стилю основных компонентов Joomla, и содержит код, который передает управление различным методам в различных контроллерах на основе значения HTTP параметра task
. Этот параметр устанавливается JavaScript ядра Joomla на основе кнопки Submit, например, в примере ниже:
onclick="Joomla.submitbutton('myform.submit')"
Параметр task
в этом примере имеет значение myform.submit
. В общем случае параметр task
имеет вид firstpart.secondpart
и для компонента с именем com_example
Joomla попытается запустить метод экземпляра secondpart()
класса контроллера ExampleControllerFirstpart
в файле firstpart.php
в каталоге controllers
.
Если параметр task
не задан, то Joomla попытается запустить метод display()
класса ExampleController
, который она ожидает найти в файле controller.php
.
Что касается кода:
$controller = JControllerLegacy::getInstance('Sample_form2');
используется параметр task
для определения соответствующего файла контроллера для включения, а затем создает экземпляр этого класса контроллера.
$controller->execute($input->getCmd('task'));
выполняется соответствующий метод (secondpart()
- или, в приведенном ниже случае, submit()
- или display())
этого класса.
Таким образом, функциональность контроллера, обрабатывающая GET (обычно в файле controller.php
), отделена от функциональности, обрабатывающей POST (обычно в файлах в папке controllers
).
Классы MVC в Joomla
Joomla предоставляет многофункциональные классы контроллеров, представлений и моделей, от которых могут наследоваться контроллеры, представления и модели создаваемых компонентов. Приведенный ниже код модели наследуется от FormModel
, который в некоторой степени защищает API форм Joomla. В данном случае наша модель вызывает FormModel::loadForm()
, которая затем выполняет обратный вызов нашей loadFormData()
, чтобы предоставить данные для bind()
формы.
Также можно использовать классы FormController
и AdminController
, но они предполагают, что разрабатываемый компонент использует таблицу базы данных для хранения данных. В руководстве по разработке компонентов Joomla MVC этот подход рассматривается в J3.x:Developing an MVC Component/Adding backend actions.
Токен безопасности
Joomla использует маркер безопасности в формах для предотвращения CSRF-атак (см. https://en.wikipedia.org/wiki/Cross-site_request_forgery). Токен выводится в файле шаблона
<?php echo JHtml::_('form.token'); ?>
и проверяется в контроллере, обрабатывающем POST:
$this->checkToken();
Если токен оказывается недействительным, то checkToken()
выводит предупреждение и перенаправляет пользователя на предыдущую страницу.
Валидация на стороне клиента
В дополнение к проверке на стороне сервера, включенной через XML-определение формы, Joomla также предоставляет способ включения JavaScript, который выполняет проверку на стороне клиента в браузере. Это не включено ниже, так как выходит за рамки данного руководства, но вы можете найти подробности об этом в J3.x:Developing an MVC Component/Adding verifications и Client-side form validation.
Образец формы 2
Этот второй пример компонента включает в себя описанные выше дизайнерские решения и структурирует код в соответствии с парадигмой Joomla. Общая структура файлов показана на картинке ниже.
Начиная с нижней части этой картинки и поднимаясь вверх, содержимое файлов выглядит следующим образом:
com_sample_form2/sample_form2.php
Точка входа для компонента. Это первый файл, который запускает Joomla:
<?php
defined('_JEXEC') or die('Restricted access');
// Загрузка соответствующего класса контроллера
$controller = JControllerLegacy::getInstance('Sample_form2');
$input = JFactory::getApplication()->input;
// Запуск метода task или display(), если параметр task отсутствует
$controller->execute($input->getCmd('task'));
$controller->redirect();
com_sample_form2/controller.php Контроллер для обработки HTTP GET запросов:
<?php
defined('_JEXEC') or die('Restricted access');
use Joomla\CMS\MVC\Controller\BaseController;
class Sample_form2Controller extends BaseController
{
// Joomla будет искать этот класс в файле controller.php
// Она (по умолчанию) вызовет метод display(), и обнаружит, что он находится в классе BaseController
}
com_sample_form2/sample_form2.xml Файл манифеста для компонента:
<?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="3.1.0" method="upgrade">
<name>com_sample_form2</name>
<version>1.0.0</version>
<description>Пример работы форм №2 в Joomla</description>
<administration>
</administration>
<files folder="site">
<filename>sample_form2.php</filename>
<filename>controller.php</filename>
<folder>controllers</folder>
<folder>views</folder>
<folder>models</folder>
</files>
</extension>
com_sample_form2/views/form/view.html.php Файл представления для отображения формы:
Функция display()
контроллера создаст экземпляры модели и представления и вызовет функцию display()
представления ниже.
<?php
defined('_JEXEC') or die('В доступе отказано');
use Joomla\CMS\MVC\View\HtmlView;
use Joomla\CMS\Factory;
class Sample_form2ViewForm extends HtmlView
{
public function display($tpl = null)
{
if (!$this->form = $this->get('form'))
{
echo "Не могу загрузить форму<br>";
return;
}
parent::display($tpl); // это включит файл макета edit.php
}
}
com_sample_form2/views/form/tmpl/edit.php Файл шаблона для отображения формы:
<?php
defined('_JEXEC') or die('Restricted access');
?>
<form action="<?php echo JRoute::_('index.php?option=com_sample_form2&view=form&layout=edit'); ?>"
method="post" name="adminForm" id="adminForm" enctype="multipart/form-data">
<?php echo $this->form->renderField('message'); ?>
<?php echo $this->form->renderField('email'); ?>
<?php echo $this->form->renderField('telephone'); ?>
<button type="button" class="btn btn-primary" onclick="Joomla.submitbutton('myform.submit')">Submit</button>
<input type="hidden" name="task" />
<?php echo JHtml::_('form.token'); ?>
</form>
com_sample_form2/models/form.php Модель, связанная с отображением формы:
Представление get('form')
сопоставляется с вызовом метода модели getForm()
.
<?php
defined('_JEXEC') or die('Restricted access');
use Joomla\CMS\MVC\Model\FormModel;
class Sample_form2ModelForm extends FormModel
{
public function getForm($data = array(), $loadData = true)
{
$form = $this->loadForm(
'com_sample_form2.sample', // просто уникальное имя для идентификации формы
'sample_form', // имя файла определения формы XML
// Joomla будет искать этот файл в папке models/forms
array(
'control' => 'jform', // имя массива для POST параметров
'load_data' => $loadData // требуется TRUE
)
);
if (empty($form))
{
$errors = $this->getErrors();
throw new Exception(implode("\n", $errors), 500);
}
return $form;
}
protected function loadFormData()
{
// Провка сессии на наличие ранее введенных данных формы.
$data = JFactory::getApplication()->getUserState(
'com_sample_form2.sample', // уникальное имя для идентификации данных в сессии
array("telephone" => "0") // предварительное заполнение данных, если в сессии не найдено никаких данных
);
return $data;
}
}
com_sample_form2/models/forms/sample_form.xml XML-файл, содержащий определение формы:
<?xml version="1.0" encoding="utf-8"?>
<form>
<field
name="message"
type="text"
label="Введите сообщение"
size="40"
class="inputbox"
required="true" />
<field name="email"
type="email"
label="Введите email"
required="true"
size="40"
class="inputbox" />
<field name="telephone"
type="tel"
label="И телефончик оставьте!"
required="true"
size="40"
class="inputbox"
validate="tel" />
</form>
com_sample_form2/controllers/Myform.php Контроллер, который обрабатывает HTTP POST:
<?php
defined('_JEXEC') or die('Restricted access');
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Factory;
class Sample_form2ControllerMyform extends BaseController
{
public function submit($key = null, $urlVar = null)
{
$this->checkToken();
$app = JFactory::getApplication();
$model = $this->getModel('form');
$form = $model->getForm($data, false);
if (!$form)
{
$app->enqueueMessage($model->getError(), 'error');
return false;
}
// имя массива 'jform' должно соответствовать строке 'control' => 'jform' в коде модели
$data = $this->input->post->get('jform', array(), 'array');
// Это validate() из класса FormModel, а не из класса Form
// Form FormModel::validate() вызывает методы Form::filter() и Form::validate()
$validData = $model->validate($form, $data);
if ($validData === false)
{
$errors = $model->getErrors();
foreach ($errors as $error)
{
if ($error instanceof \Exception)
{
$app->enqueueMessage($error->getMessage(), 'warning');
}
else
{
$app->enqueueMessage($error, 'warning');
}
}
// Сохранить данные формы в сессии, используя уникальный идентификатор
$app->setUserState('com_sample_form2.sample', $data);
}
else
{
$app->enqueueMessage("Data successfully validated", 'notice');
// Очистить данные формы в сессии
$app->setUserState('com_sample_form2.sample', null);
}
// Перенаправление обратно на форму во всех случаях
$this->setRedirect(JRoute::_('index.php?option=com_sample_form2&view=form&layout=edit', false));
}
}
После создания файлов, как описано выше, нужно заархивировать папку com_sample_form2
, чтобы создать com_sample_form2.zip
и установить этот компонент стандартным установщиком расширений Joomla (в админке сайта). Затем нужно перейти на сайт Joomla и добавить в URL параметры &option=com_sample_form2&view=form&layout=edit
. Это должно отобразить форму и работать аналогично предыдущему образцу формы, за исключением того, что ошибки и т.д. будут появляться в сообщениях предусмотренных в Joomla.
Перевод с английского официальной документации Joomla:
https://docs.joomla.org/Basic_form_guide
Заберите ссылку на статью к себе, чтобы потом легко её найти!
Раз уж досюда дочитали, то может может есть желание рассказать об этом месте своим друзьям, знакомым и просто мимо проходящим?
Не надо себя сдерживать! ;)