Введение.

Это одно из серии руководств по API, ель которых - помочь вам понять, как использовать API Joomla, предоставляя подробные объяснения и примеры кода, которые вы можете легко установить и запустить.

Класс Joomla Form и API позволяют вам легко разрабатывать HTML-формы и обрабатывать вводимые пользователем данные.

На этой странице описывается, как вы взаимодействуете в целом с классом Joomla Form, и приводится код простого компонента, который можно установить для демонстрации использования этого API. Сопутствующее руководство  Расширенное руководство по формам посвящено более продвинутым аспектам класса Joomla Form.

Сама Joomla также имеет особый способ проектирования функциональности форм, и эта страница включает обучающий материал, который поможет вам понять и разработать ваш компонент в соответствии с этими принципами.

Общее описание.

На схеме ниже показано базовое использование класса Joomla Form.

Using Joomla Forms

Во-первых, вам нужно определить вашу форму в XML, используя стандартные типы полей формы В качестве примера смотрите код компонента ниже. В основных терминах, каждый элемент поля в вашем XML-файле соответствует элементу HTML (в основном "input") в форме, при этом атрибуты XML-поля соответствуют атрибутам элемента (input). Многие из возможных атрибутов поля перечислены в разделе Тип поля текстовой формы.

Шаг 1 Загрузка формы.

Пользователь нажимает на URL вашей формы, и Joomla направляет HTTP GET запрос к коду вашего компонента. Вам нужно вызвать Form::getInstance(), передав имя вашего XML-файла с определением формы. Код Joomla Form создает экземпляр формы, а затем (в Form::loadFile()) считывает файл в память (как PHP SimpleXMLElement) и анализирует XML, чтобы убедиться в его достоверности. Основными параметрами, передаваемыми в getInstance(), являются:

  • name – строка, определяющая идентификатор, который будет присвоен этой форме - он должен быть уникальным, чтобы не пересекаться с другими формами Joomla на той же веб-странице
  • data – имя XML-файла, в котором содержится определение вашей формы
  • options – массив опций, в котором элемент "control" указывает имя массива, в котором будут храниться параметры POST. Например, пример кода имеет array("control"=>"myform"), что означает, что входные элементы HTML будут иметь атрибуты имени, заданные как "myform[message]", "myform[email]" и т.д. Таким образом, они будут передаваться в параметрах POST, и тогда их очень легко поместить в массив PHP.

Шаг 2 Предоставление данных перед заполнением.

Вы предоставляете значения для любого элемента формы по своему усмотрению. Например, если эта форма используется для редактирования записи в базе данных, то вы должны предварительно заполнить ее значениями существующих полей из базы данных. Вы можете предоставить значения, создав ассоциативный массив $data и передав его в метод Form::bind($data), а Joomla Form затем сохранит эти данные локально в экземпляре формы.

Шаг 3 Вывод формы в HTML.

Вы вызываете renderField(fieldName) на экземпляре Form, и вам возвращается HTML, который вы можете вывести на экран. Joomla обрабатывает XML-представление вашей формы, чтобы получить раздел, относящийся к переданному fieldName, генерирует HTML для этого HTML-элемента и включает атрибут "value" на основе предварительно заполненных данных, которые вы передали в шаге 2. При выводе формы необходимо также окружить элементы ввода элементом <form> и добавить кнопку отправки.

Шаг 4 Пользователь отправляет форму.

Пользователь вводит данные в HTML-форму и нажимает кнопку отправки. Браузер генерирует HTTP POST-запрос к URL-адресу, указанному в элементе <form>, и передает серверу в параметрах POST значения, введенные пользователем; каждый параметр имеет ключ атрибут «name» элемента ввода HTML.

Joomla направляет этот POST через ваш компонент. Поскольку это новый HTTP-запрос, предыдущий экземпляр Form больше не существует, поэтому вам придется снова вызвать Form::getInstance(), передав имя XML-файла формы, и Joomla (как и раньше) создает экземпляр Form и считывает этот файл в память.

Шаг 5 Обработка данных HTTP POST.

На этом шаге вы обрабатываете отправленные данные. Это включает в себя:

  1. Получение данных POST с помощью Factory::getApplication->input->get(). Обычно атрибуты "name" входных элементов определяются таким образом, чтобы параметры POST отображались в виде массива, и вы можете использовать фильтр ARRAY из  JInput - получение и фильтрация переменных запроса, для их считывания непосредственно в массив PHP. Однако это означает, что отдельные элементы вообще не фильтруются.
  2. Фильтрация. При этом применяется фильтрация к каждому из входных значений. Фильтр, примененный к полю, управляется параметром "filter=..." в этом поле в XML-файле формы или, если его нет, фильтр по умолчанию удалит HTML-теги и т. д. из значений данных. Возможные фильтры находятся в методе filterField() класса Form (см. https://joomla.stackexchange.com/questions/5764/what-are-possible-filters-in-joomla-form-fields). Обратите внимание, что эти фильтры полей форм отличаются от фильтров jinput.
  3. Валидация. Вы проверяете данные, которые ввел пользователь, вызывая validate($data) на вашем экземпляре формы, передавая ассоциативный массив (отфильтрованных) данных пользователя. Joomla сравнивает введенные пользователем данные с проверкой, которую вы определили в XML-файле вашей формы, и генерирует ошибки для полей, которые не прошли проверку.

Если есть ошибки валидации, то вы должны отобразить эти ошибки пользователю и заново отобразить форму, предварительно заполнив поля теми (отфильтрованными) данными, которые пользователь ввел ранее.

Если ошибок нет, вы можете подтвердить это пользователю и показать следующую веб-страницу.

Образец формы 1.

Ниже приведен код небольшого компонента, который вы можете установить для демонстрации базового использования форм Joomla. Поместите следующие 3 файла в папку под названием "com_sample_form1". Затем застегните папку, чтобы создать com_sample_form1.zip и установите его как компонент на вашем экземпляре 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, в частности, разделяя ваш компонент на контроллеры, модели, представления и макеты, и пересмотренный код компонента в разделе после этого следует этому подходу. В оставшейся части этого раздела описаны эти и другие проектные решения, которые должны облегчить понимание кода примера.

Разделение Joomla MVC.

В общих чертах Joomla разделяет компоненты на отдельные типы функциональности:

  • контроллер содержит "бизнес-логику" приложения, включая принятие решения о том, какой вид и модель использовать
  • модель обеспечивает доступ к данным
  • представление решает, какие данные необходимы для вывода на веб-страницу, и получает эти данные из модели
  • layout выводит HTML и включает в выходные данные данные, которые были сопоставлены с помощью представления. layout выполняется в контексте представления и, таким образом, имеет прямой доступ к переменным кода представления.

Шаблон 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() и обрабатываются контекстом, который должен быть уникальным для этой формы. Код, который предоставляет данные для операции привязки формы bind(), должен сначала проверить с помощью getUserState(), есть ли какие-либо данные предварительного заполнения в сеансе. И если пользователь вводит данные, которые успешно проходят проверку, то следует вызвать функцию setUserState(), чтобы очистить эти данные предварительного заполнения в сеансе, в противном случае они будут отображаться всякий раз, когда пользователь в следующий раз отобразит форму.

Отдельные контроллеры

В ответ на запрос GET или POST Joomla всегда запускает один и тот же файл компонента, sample_form1.php в приведенном выше примере компонента и верхнего уровня sample_form2.php в примере ниже. Приведенный ниже код sample_form2.php следует примеру основных компонентов Joomla и содержит код, который передает управление различным методам в разных контроллерах на основе значения параметра HTTP task. Этот параметр задается Joomla core javascript на основе кнопки отправки, например, в примере ниже:

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).

Классы Joomla MVC

Joomla предоставляет многофункциональные классы controller, view и model, от которых могут наследоваться вашими контроллерами компонентов, представлениями и моделями. Приведенный ниже код модели наследуется от FormModel, который в некоторой степени защищает Joomla Form API. В этом случае наша модель вызывает FormModel::loadForm(), а затем выполняет обратный вызов нашей функции loadFormData(), чтобы предоставить данные для привязки() к форме.

Вы также можете использовать классы FormController и AdminController, но они предполагают, что ваш компонент использует таблицу базы данных для хранения данных. В руководстве по разработке компонентов Joomla MVC используется этот подход в J3.x:Разработка компонента MVC/Добавление внутренних действий.

Токен безопасности

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:Разработка компонента MVC/Добавление проверок и проверка формы на стороне клиента.

Образец формы 2

Этот второй пример компонента включает в себя дизайнерские решения, описанные выше, и структурирует код в соответствии с парадигмой Joomla. Общая файловая структура показана на диаграмме ниже.

Sample Form 2 file structure.

Начиная с нижней части этой диаграммы и поднимаясь вверх, содержимое файла выглядит следующим образом:

com_sample_form2/sample_form2.php Точка входа для компонента. Это первый файл, который запускает Joomla.

<?php
defined('_JEXEC') or die('Restricted access');
// Load the appropriate controller class
$controller = JControllerLegacy::getInstance('Sample_form2');

$input = JFactory::getApplication()->input;
// Run the task method, or display() if no task parameter
$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>Sample form 2</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('Restricted access');

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 "Can't load form<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') сопоставляется с вызовом метода model 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="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>

 

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, а не класс формы
		// 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 и добавьте в URL параметры &option=com_sample_form2&view=форма&layout=редактировать. Это должно отобразить форму и должно работать аналогично предыдущему образцу формы, за исключением того, что ошибки и т.д. будут отображаться в части сообщений на дисплее Joomla.

Оригинал.