+Базовая версия пакета форм

master
Andrey Pokidov 4 years ago
commit 35cc11bc59

@ -0,0 +1,12 @@
{
"name": "artmark/forms",
"description": "Form Manager",
"type": "library",
"authors": [
{
"name": "Andrey Pokidov",
"email": "pokidov@e-traffic.ru"
}
],
"require": {}
}

@ -0,0 +1,254 @@
<?php
namespace Artmark\Forms;
/**
* Description of AbstractChoiceField
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
abstract class AbstractChoiceField extends AbstractVisibleField
{
/**
* Список опций
* @var AbstractChoiceOption[]
*/
private $options = [];
/**
* Список выбранных опций
* @var AbstractChoiceOption[]
*/
private $selectedOptions = [];
private $multiselect = false;
/**
* Возвращает, можно ли делать множественный выбор
* @return boolean
*/
public function isMultiSelect()
{
return $this->multiselect;
}
/**
* Переключает поле в режим множественного выбора
* @return $this
*/
public function setMultiSelect()
{
$this->multiselect = true;
return $this;
}
/**
* Переключает поле в режим единственного выбора
* @return $this
*/
public function setSingleSelect()
{
$this->multiselect = false;
if (count($this->selectedOptions) > 0) {
$this->selectedOptions = [$this->selectedOptions[0]];
}
return $this;
}
/**
* Возвращает значение выделенной опции
* @return mixed
*/
public function value()
{
if (empty($this->selectedOptions)) {
return null;
}
if (!$this->multiselect) {
return $this->selectedOptions[0]->value();
}
$values = [];
foreach ($this->selectedOptions as $option) {
$values[] = $option->value();
}
return $values;
}
/**
* Задаёт новое значение для select поля
* @param mixed $newValue
* @return $this
*/
public function setValue($newValue)
{
if (is_array($newValue)) {
$values = self::getUniqueValues($newValue);
}
else {
$values = [$newValue];
}
$this->selectedOptions = [];
foreach ($values as $value) {
foreach ($this->options as $option) {
if ($option->value() == $value && !in_array($option, $this->selectedOptions)) {
$this->selectedOptions[] = $option;
continue 2;
}
}
}
return $this;
}
private static function getUniqueValues(array $values)
{
$unquie = [];
foreach ($values as $value) {
if (!in_array($value, $unquie)) {
$unquie[] = $value;
}
}
return $unquie;
}
/**
* Метод возвращает список всех опций
* @return AbstractChoiceOption[]
*/
public function options()
{
return $this->options;
}
/**
* Метод возвращает список выбранных опций
* @return AbstractChoiceOption[]
*/
public function selection()
{
return $this->selectedOptions;
}
/**
* Устанавливает новый набор опций для поля
* @param array $options
* @return $this
*/
public function setOptions(array $options)
{
$this->options = [];
$this->selectedOptions = [];
foreach ($options as $key => $data) {
$this->registerOption($key, $data);
}
return $this;
}
private function registerOption($key, $data)
{
if (is_string($data) || is_numeric($data)) {
$this->addOption($key, $data);
return;
}
if (!is_array($data)) {
return;
}
$value = array_key_exists('value', $data) ? $data['value'] : $key;
$text = array_key_exists('text', $data) ? $data['text'] : '';
$option = $this->addOption($value, $text);
if ((array_key_exists('disabled', $data) && !empty($data['disabled']))
|| (array_key_exists('disable', $data) && !empty($data['disable']))) {
$option->disable();
}
if ((array_key_exists('selected', $data) && !empty($data['selected']))
|| (array_key_exists('select', $data) && !empty($data['select']))) {
$this->select($option);
}
}
/**
* Метод добавляет опцию со значением $value и текстом $text
* @param string|int|float $value
* @param string $text
* @param array $parameters
* @return AbstractChoiceOption
*/
public function addOption($value, $text)
{
$option = $this->createOption($value, $text);
$this->options[] = $option;
return $option;
}
/**
* Проверяет, выбрана ли данная опция
* @param AbstractChoiceOption $option
* @return boolean
*/
public function isSelected(AbstractChoiceOption $option)
{
return in_array($option, $this->selectedOptions);
}
/**
*
* @param AbstractChoiceOption $option
* @return boolean
*/
public function select(AbstractChoiceOption $option)
{
if (!in_array($option, $this->options)) {
return false;
}
if (!$this->multiselect) {
$this->selectedOptions = [$option];
return true;
}
if (!in_array($option, $this->selectedOptions)) {
$this->selectedOptions[] = $option;
}
return true;
}
public function getAssociativeAttributes()
{
$attributes = parent::getAssociativeAttributes();
$this->appendVisibleAttributes($attributes);
$this->appendSizeAttribute($attributes);
return $attributes;
}
/**
* Создаёт новый экземпляр опции
* @return AbstractChoiceOption
*/
protected abstract function createOption($value, $text);
public abstract function appendAttributesToOption(AbstractChoiceOption $option, array & $attributes);
}

@ -0,0 +1,115 @@
<?php
namespace Artmark\Forms;
use Artmark\Forms\Attributes\DisabledAttribute;
/**
* Description of AbstractChoiceOption
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
abstract class AbstractChoiceOption extends AbstractVisibleHtml
{
use DisabledAttribute;
/**
* The value of the option
* @var string|int|float
*/
private $value = '';
/**
* The label of the option
* @var string
*/
private $text = '';
public function __construct($value, $text)
{
$this->value = self::correctValue($value);
$this->text = strval($text);
}
/**
* Метод возвращает поле типа AbstractChoiceField
* @return AbstractChoiceField
*/
public abstract function field();
/**
* Возвращает значение опции
* @return string|int|float
*/
public function value()
{
return $this->value;
}
/**
* Возвращает текст опции
* @return string
*/
public function text()
{
return $this->text;
}
/**
* Задёт новый текст для опции
* @param string $newText
* @return $this
*/
public function setText($newText)
{
$this->text = self::correctValue($newText);
return $this;
}
/**
* The methods allows to determine is the option selected or not
* @return boolean
*/
public function isSelected()
{
return $this->field()->isSelected($this);
}
/**
* Отмечает опцию как выбранную
* @return $this
*/
public function select()
{
$this->field()->select($this);
return $this;
}
public function getAssociativeAttributes()
{
$attributes = parent::getAssociativeAttributes();
$attributes['value'] = $this->value;
$this->appendVisibleAttributes($attributes);
$this->appendDisabledAttribute($attributes);
$this->field()->appendAttributesToOption($this, $attributes);
return $attributes;
}
private static function correctValue($value)
{
if (is_string($value) || is_numeric($value)) {
return $value;
}
if (is_null($value)) {
return '';
}
return strval($value);
}
}

@ -0,0 +1,180 @@
<?php
namespace Artmark\Forms;
/**
* Description of AbstractField
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
abstract class AbstractField extends AbstractHtml
{
/**
* Форма, к которой относится поле
* @var Form or null
*/
private $form;
/**
* Текстовая метка поля
* @var Label
*/
private $label;
/**
* Тип поля
* @var string
*/
private $type;
/**
* Название поля
* @var string
*/
private $name;
protected function __construct($form, $type, $name)
{
self::checkName($name);
self::checkType($type);
self::checkForm($form);
$this->label = new Label($this);
$this->form = $form;
$this->type = $type;
$this->name = Utils::cleanString($name);
}
/**
*
* @return Form or null
*/
public function form()
{
return $this->form;
}
/**
*
* @return string
*/
public function type()
{
return $this->type;
}
/**
*
* @return string
*/
public function name()
{
return $this->name;
}
public function nameWithFormPrefix()
{
if (!is_null($this->form) && $this->form->hasNamePrefix()) {
return $this->form->namePrefix() . $this->name;
}
return $this->name;
}
public function idWithFormPrefix()
{
if (!$this->hasId()) {
return '';
}
if (!is_null($this->form) && $this->form->hasIdPrefix()) {
return $this->form->idPrefix() . $this->id();
}
return $this->id();
}
/**
* Возвращает ранее заданное значение поля
* @return mixed
*/
public abstract function value();
/**
* Устанавливает новое значение для поля
* @param mixed $newValue новое значение
* @return $this
*/
public abstract function setValue($newValue);
/**
* Возвращает текстовую метку поля
* @return Label
*/
public function label()
{
return $this->label;
}
/**
* Устанавливает текст для метки поля
* @param string $text
* @return $this
*/
public function setLabelText($text)
{
$this->label->setText($text);
return $this;
}
/**
*
* @return array
*/
public function getAssociativeAttributes()
{
$attributes = parent::getAssociativeAttributes();
$attributes['name'] = $this->nameWithFormPrefix();
if ($this->hasId()) {
$attributes['id'] = $this->idWithFormPrefix();
}
return $attributes;
}
public static function checkForm($form)
{
if (is_null($form) || $form instanceof Form) {
return;
}
throw new AttributeException('The name of a field must be a non-empty string');
}
public static function checkName($name)
{
if (!self::isCorrectName($name)) {
throw new AttributeException('The name of a field must be a non-empty string');
}
}
public static function isCorrectName($name)
{
return is_numeric($name) || Utils::isNonEmptyString($name);
}
public static function checkType($type)
{
if (!self::isCorrectType($type)) {
throw new AttributeException('The type of a field must be a non-empty string with of english letters, numbers, underline and dash symbols');
}
}
public static function isCorrectType($type)
{
return is_string($type) && preg_match('/^[0-9a-z_\-]+$/ui', trim($type));
}
}

@ -0,0 +1,24 @@
<?php
namespace Artmark\Forms;
use Artmark\Forms\Attributes\IdAttribute;
/**
* Description of AbstractHtml
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
abstract class AbstractHtml implements Html
{
use IdAttribute;
public function getAssociativeAttributes()
{
$attributes = [];
$this->appendIdAttribute($attributes);
return $attributes;
}
}

@ -0,0 +1,65 @@
<?php
namespace Artmark\Forms;
use Artmark\Forms\Attributes\SizeAttribute;
use Artmark\Forms\Attributes\MaxLengthAttribute;
/**
* Description of AbstractTextField
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
abstract class AbstractTextField extends AbstractVisibleField
{
use SizeAttribute, MaxLengthAttribute;
private $value = '';
protected function __construct($form, $type, $name, $value = '')
{
parent::__construct($form, $type, $name);
$this->setValue($value);
}
/**
* Возвращает значение поля
* @return string
*/
public function value()
{
return $this->value;
}
/**
* Задаёт новое значение для текстового поля
* @param string|int|float $newValue
* @return $this
*/
public function setValue($newValue)
{
if (is_string($newValue) || is_numeric($newValue)) {
$this->value = strval($newValue);
}
return $this;
}
/**
* Ассоциативный массив аттрибутов текстового поля
* @return array
*/
public function getAssociativeAttributes()
{
$attributes = parent::getAssociativeAttributes();
$this->appendSizeAttribute($attributes);
$this->appendMaxLengthAttribute($attributes);
$attributes['value'] = $this->value;
return $attributes;
}
}

@ -0,0 +1,37 @@
<?php
namespace Artmark\Forms;
use Artmark\Forms\Attributes\VisibleAttributes;
use Artmark\Forms\Attributes\RequiredAttribute;
use Artmark\Forms\Attributes\ReadOnlyAttribute;
use Artmark\Forms\Attributes\DisabledAttribute;
/**
* Description of AbstractVisibleField
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
abstract class AbstractVisibleField extends AbstractField
{
use VisibleAttributes, RequiredAttribute, ReadOnlyAttribute, DisabledAttribute;
/**
*
* @return array
*/
public function getAssociativeAttributes()
{
$attributes = parent::getAssociativeAttributes();
$this->appendVisibleAttributes($attributes);
$this->appendRequiredAttribute($attributes);
$this->appendReadOnlyAttribute($attributes);
$this->appendDisabledAttribute($attributes);
return $attributes;
}
}

@ -0,0 +1,24 @@
<?php
namespace Artmark\Forms;
use Artmark\Forms\Attributes\VisibleAttributes;
/**
* Description of AbstractVisibleHtml
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
abstract class AbstractVisibleHtml extends AbstractHtml
{
use VisibleAttributes;
public function getAssociativeAttributes()
{
$attributes = parent::getAssociativeAttributes();
$this->appendVisibleAttributes($attributes);
return $attributes;
}
}

@ -0,0 +1,82 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of ActionAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait ActionAttribute
{
/**
*
* @var string
*/
private $action = '';
/**
* HTML action attribute of the tag
* @return string
*/
public function action()
{
return $this->action;
}
/**
*
* @return boolean
*/
public function hasAction()
{
return $this->action !== '';
}
/**
*
* @param string $newAction
* @return $this
*/
public function setAction($newAction)
{
self::checkAction($newAction);
$this->action = trim($newAction);
return $this;
}
/**
*
* @return $this
*/
public function removeAction()
{
$this->action = '';
return $this;
}
protected function appendActionAttribute(array & $attributes)
{
if ($this->hasAction())
{
$attributes['action'] = $this->action;
}
return $attributes;
}
public static function checkAction($action)
{
if (!self::isCorrectAction($action)) {
throw new AttributeException('Incorrect action value: `' . strval($action) . '`');
}
}
public static function isCorrectAction($action)
{
return is_string($action);
}
}

@ -0,0 +1,16 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of AttributeException
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
class AttributeException extends \Exception
{
public function __construct(string $message = "", int $code = 0, \Throwable $previous = NULL)
{
parent::__construct($message, $code, $previous);
}
}

@ -0,0 +1,172 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of CssClassAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait CssClassAttribute
{
private $classes = [];
/**
* Массив названий CSS классов
* @return string[]
*/
public function cssClasses()
{
return $this->classes;
}
/**
* Проверяет, что список CSS классов не пуст
* @return boolean
*/
public function hasCssClasses()
{
return count($this->classes) > 0;
}
/**
* Проверяет, имеется ли такой класс
* @param string $className
* @return boolean
*/
public function hasCssClass($className)
{
if (!is_string($className)) {
return false;
}
$cleaned = trim($className);
return $cleaned !== '' && in_array($cleaned, $this->classes);
}
/**
* Удаляет все CSS классы
* @return $this
*/
public function clearCssClasses()
{
$this->classes = [];
return $this;
}
/**
* Добавляет CSS классы в начало списка
* @param string|array $classes
* @return $this
*/
public function prependCssClasses($classes)
{
if (!is_array($classes) && !is_string($classes)) {
return $this;
}
$classList = self::filterClasseserClasses(self::parseClasses($classes));
foreach ($this->classes as $className) {
if (!in_array($className, $classList)) {
$className[] = $className;
}
}
$this->classes = $className;
return $this;
}
/**
* Добавляет CSS классы в конец списка
* @param string|array $classes
* @return $this
*/
public function appendCssClasses($classes)
{
if (!is_array($classes) && !is_string($classes)) {
return $this;
}
foreach (self::filterClasseserClasses(self::parseClasses($classes)) as $className) {
if (!in_array($className, $this->classes)) {
$this->classes[] = $className;
}
}
return $this;
}
/**
* Удаляет CSS классы из списка
* @param string|array $classes
* @return $this
*/
public function removeCssClasses($classes)
{
if (!is_array($classes) && !is_string($classes)) {
return $this;
}
$i = 0;
foreach (self::filterClasseserClasses(self::parseClasses($classes)) as $className) {
$keys = array_keys($this->classes, $className);
foreach ($keys as $key) {
unset($this->classes[$key]);
$i++;
}
}
if ($i > 0) {
$this->classes = array_values($this->classes);
}
return $this;
}
protected function appendCssClassAttribute(array & $attributes)
{
if ($this->hasCssClasses()) {
$attributes['class'] = implode(' ', $this->classes);
}
}
private static function filterClasseserClasses(array $classList)
{
$filteredClasses = [];
foreach ($classList as $className) {
if (self::isCorretClassName($className) && !in_array($className, $filteredClasses)) {
$filteredClasses[] = $className;
}
}
return $filteredClasses;
}
private static function parseClasses($classes) {
if (is_array($classes)) {
return $classes;
}
return preg_split('/[ \r\n\t\b]+/', $classes);
}
public static function checkClassName($className)
{
if (!self::isCorretClassName($className)) {
throw new FieldException('Incorrect class name: `' . strval($className) . '`');
}
}
public static function isCorretClassName($className)
{
return is_string($className) && preg_match('/^[a-z0-9_]+(-[a-z0-9_]+)*$/i', trim($className));
}
}

@ -0,0 +1,91 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of DirectionAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait DirectionAttribute
{
/**
*
* @var string
*/
private $direction = '';
/**
* HTML dir attribute of the tag
* @return string
*/
public function direction()
{
return $this->direction;
}
/**
*
* @return boolean
*/
public function hasDirection()
{
return $this->direction !== '';
}
/**
*
* @param string $newDirection
* @return $this
*/
public function setDirection($newDirection)
{
self::checkDirection($newDirection);
$this->direction = self::normalizeString($newDirection);
return $this;
}
/**
*
* @return $this
*/
public function removeDirection()
{
$this->direction = '';
return $this;
}
protected function appendDirectionAttribute(array & $attributes)
{
if ($this->hasDirection())
{
$attributes['dir'] = $this->direction;
}
}
public static function checkDirection($direction)
{
if (!self::isCorrectDirection($direction)) {
throw new AttributeException('Incorrect direction value: `' . strval($direction) . '`');
}
}
public static function isCorrectDirection($direction)
{
if (!is_string($direction)) {
return false;
}
$corrected = self::normalizeString($direction);
return $corrected === '' || $corrected === 'ltr' || $corrected === 'rtl';
}
private static function normalizeString($value)
{
return strtolower(trim($value));
}
}

@ -0,0 +1,64 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of DisabledAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait DisabledAttribute
{
/**
*
* @var boolean
*/
private $disabled = false;
/**
* Выключено ли поле
* @return boolean
*/
public function isDisabled()
{
return $this->disabled;
}
/**
* Включено ли поле
* @return boolean
*/
public function isEnabled()
{
return !$this->disabled;
}
/**
* Выключает поле
* @return $this
*/
public function disable()
{
$this->disabled = true;
return $this;
}
/**
* Включает поле
* @return $this
*/
public function enable()
{
$this->disabled = false;
return $this;
}
protected function appendDisabledAttribute(array & $attributes)
{
if ($this->disabled) {
$attributes['disabled'] = null;
}
}
}

@ -0,0 +1,82 @@
<?php
namespace Artmark\Forms\Attributes;
use Artmark\Forms\Utils;
/**
* Description of IdAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait IdAttribute
{
/**
*
* @var string
*/
private $id = '';
/**
* HTML Id attribute of the tag
* @return string
*/
public function id()
{
return $this->id;
}
/**
*
* @return boolean
*/
public function hasId()
{
return $this->id !== '';
}
/**
*
* @param string $newId
* @return $this
*/
public function setId($newId)
{
self::checkId($newId);
$this->id = Utils::cleanString($newId);
return $this;
}
/**
*
* @return $this
*/
public function removeId()
{
$this->id = '';
return $this;
}
protected function appendIdAttribute(array & $attributes)
{
if ($this->hasId())
{
$attributes['id'] = $this->id;
}
}
public static function checkId($id)
{
if (!self::isCorrectId($id)) {
throw new AttributeException('Incorrect id value: `' . strval($id) . '`');
}
}
public static function isCorrectId($id)
{
return is_numeric($id) || (is_string($id) && !preg_match('/[ \r\n\t\b]+/', trim($id)));
}
}

@ -0,0 +1,86 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of LanguageAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait LanguageAttribute
{
/**
*
* @var string
*/
private $language = '';
/**
* HTML lang attribute of the tag
* @return string
*/
public function language()
{
return $this->language;
}
/**
*
* @return boolean
*/
public function hasLanguage()
{
return $this->language !== '';
}
/**
*
* @param string $newLanguage
* @return $this
*/
public function setLanguage($newLanguage)
{
self::checkLanguage($newLanguage);
$this->language = trim($newLanguage);
return $this;
}
/**
*
* @return $this
*/
public function removeLanguage()
{
$this->language = '';
return $this;
}
protected function appendLanguageAttribute(array & $attributes)
{
if ($this->hasLanguage())
{
$attributes['lang'] = $this->language;
}
}
public static function checkLanguage($language)
{
if (!self::isCorrectLanguage($language)) {
throw new AttributeException('Incorrect language value: `' . strval($language) . '`');
}
}
public static function isCorrectLanguage($language)
{
if (!is_string($language)) {
return false;
}
$cleaned = trim($language);
return $cleaned === '' || preg_match('/^[A-Za-z]{2}([_\-][A-Za-z]{2}){0,1}$/', $cleaned);
}
}

@ -0,0 +1,70 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of MaxLengthAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait MaxLengthAttribute
{
/**
*
* @var int
*/
private $maxlength = 0;
/**
* HTML site attribute of the input tag
* @return int
*/
public function maxLength()
{
return $this->maxlength;
}
/**
*
* @return boolean
*/
public function hasMaxLength()
{
return $this->maxlength > 0;
}
/**
*
* @param int $newMaxLength
* @return $this
*/
public function setMaxLength($newMaxLength)
{
$this->maxlength = intval($newMaxLength);
if ($this->maxlength < 0) {
$this->maxlength = 0;
}
return $this;
}
/**
*
* @return $this
*/
public function removeMaxLength()
{
$this->maxlength = 0;
return $this;
}
protected function appendMaxLengthAttribute(array & $attributes)
{
if ($this->hasMaxLength())
{
$attributes['maxlength'] = $this->maxlength;
}
}
}

@ -0,0 +1,86 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of MethodAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait MethodAttribute
{
/**
*
* @var string
*/
private $method = 'POST';
/**
* HTML method attribute of the tag
* @return string
*/
public function method()
{
return $this->method;
}
/**
*
* @return boolean
*/
public function isPost()
{
return $this->method === 'POST';
}
/**
*
* @return boolean
*/
public function isGet()
{
return $this->method === 'GET';
}
/**
*
* @param string $newMethod
* @return $this
*/
public function setMethod($newMethod)
{
self::checkMethod($newMethod);
$this->method = self::normalizeMethod($newMethod);
return $this;
}
protected function appendMethodAttribute(array & $attributes)
{
$attributes['method'] = $this->method;
}
public static function checkMethod($method)
{
if (!self::isCorrectMethod($method)) {
throw new AttributeException('Incorrect method value: `' . strval($method) . '`');
}
}
public static function isCorrectMethod($method)
{
if (!is_string($method)) {
return false;
}
$corrected = self::normalizeMethod($method);
return $corrected === 'POST' || $corrected === 'GET';
}
private static function normalizeMethod($method)
{
return strtoupper(trim($method));
}
}

@ -0,0 +1,55 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of MultipartAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait MultipartAttribute
{
/**
*
* @var boolean
*/
private $multipart = false;
/**
* Есть ли в форме файлы
* @return boolean
*/
public function isMultipart()
{
return $this->multipart;
}
/**
* Помечает форму как форму с файлами
* @return $this
*/
public function setMultipart()
{
$this->multipart = true;
return $this;
}
/**
* Помечает форму как форму без файлов
* @return $this
*/
public function setSinglePart()
{
$this->multipart = false;
return $this;
}
protected function appendMultipartAttribute(array & $attributes)
{
if ($this->multipart) {
$attributes['enctype'] = 'multipart/form-data';
}
}
}

@ -0,0 +1,64 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of ReadOnlyAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait ReadOnlyAttribute
{
/**
*
* @var boolean
*/
private $readonly = false;
/**
* Закрыто ли изменение поля
* @return boolean
*/
public function isReadOnly()
{
return $this->readonly;
}
/**
* Доступно ли поле для изменения
* @return boolean
*/
public function isEditable()
{
return !$this->readonly;
}
/**
* Помечает поле недоступным для изменения
* @return $this
*/
public function setReadOnly()
{
$this->readonly = true;
return $this;
}
/**
* Помечает поле доступным для изменения
* @return $this
*/
public function setEditable()
{
$this->readonly = false;
return $this;
}
protected function appendReadOnlyAttribute(array & $attributes)
{
if ($this->readonly) {
$attributes['readonly'] = null;
}
}
}

@ -0,0 +1,64 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of RequiredAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait RequiredAttribute
{
/**
*
* @var boolean
*/
private $required = false;
/**
* Is the field required
* @return boolean
*/
public function isRequired()
{
return $this->required;
}
/**
* Is the field required
* @return boolean
*/
public function isOptional()
{
return !$this->required;
}
/**
* Помечает поле обязательным для заполнения
* @return $this
*/
public function setRequired()
{
$this->required = true;
return $this;
}
/**
* Помечает поле необязательным для заполнения
* @return $this
*/
public function setOptional()
{
$this->required = false;
return $this;
}
protected function appendRequiredAttribute(array & $attributes)
{
if ($this->required) {
$attributes['required'] = null;
}
}
}

@ -0,0 +1,70 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of SizeAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait SizeAttribute
{
/**
*
* @var int
*/
private $size = 0;
/**
* HTML site attribute of the input tag
* @return int
*/
public function size()
{
return $this->size;
}
/**
*
* @return boolean
*/
public function hasSize()
{
return $this->size > 0;
}
/**
*
* @param int $newSize
* @return $this
*/
public function setSize($newSize)
{
$this->size = intval($newSize);
if ($this->size < 0) {
$this->size = 0;
}
return $this;
}
/**
*
* @return $this
*/
public function removeSize()
{
$this->size = 0;
return $this;
}
protected function appendSizeAttribute(array & $attributes)
{
if ($this->hasSize())
{
$attributes['size'] = $this->size;
}
}
}

@ -0,0 +1,82 @@
<?php
namespace Artmark\Forms\Attributes;
use Artmark\Forms\Utils;
/**
* Description of StyleAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait StyleAttribute
{
/**
*
* @var string
*/
private $style = '';
/**
* HTML style attribute of the tag
* @return string
*/
public function style()
{
return $this->style;
}
/**
*
* @return boolean
*/
public function hasStyle()
{
return $this->style !== '';
}
/**
*
* @param string $newStyle
* @return $this
*/
public function setStyle($newStyle)
{
self::checkStyle($newStyle);
$this->style = Utils::cleanString($newStyle);
return $this;
}
/**
*
* @return $this
*/
public function removeStyle()
{
$this->style = '';
return $this;
}
protected function appendStyleAttribute(array & $attributes)
{
if ($this->hasStyle())
{
$attributes['style'] = $this->style;
}
}
public static function checkStyle($style)
{
if (!self::isCorrectStyle($style)) {
throw new AttributeException('Incorrect style value: `' . strval($style) . '`');
}
}
public static function isCorrectStyle($style)
{
return is_string($style);
}
}

@ -0,0 +1,80 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of TargetAttribute
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait TargetAttribute
{
/**
*
* @var string
*/
private $target = '';
/**
* HTML target attribute of the tag
* @return string
*/
public function target()
{
return $this->target;
}
/**
*
* @return boolean
*/
public function hasTarget()
{
return $this->target !== '';
}
/**
*
* @param string $newTarget
* @return $this
*/
public function setTarget($newTarget)
{
self::checkTarget($newTarget);
$this->target = trim($newTarget);
return $this;
}
/**
*
* @return $this
*/
public function removeTarget()
{
$this->target = '';
return $this;
}
protected function appendTargetAttribute(array & $attributes)
{
if ($this->hasTarget())
{
$attributes['target'] = $this->target;
}
}
public static function checkTarget($target)
{
if (!self::isCorrectTarget($target)) {
throw new AttributeException('Incorrect target value: `' . strval($target) . '`');
}
}
public static function isCorrectTarget($target)
{
return is_string($target);
}
}

@ -0,0 +1,21 @@
<?php
namespace Artmark\Forms\Attributes;
/**
* Description of VisibleAttributes
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
trait VisibleAttributes
{
use DirectionAttribute, LanguageAttribute, StyleAttribute, CssClassAttribute;
protected function appendVisibleAttributes(array & $attributes)
{
$this->appendDirectionAttribute($attributes);
$this->appendLanguageAttribute($attributes);
$this->appendCssClassAttribute($attributes);
$this->appendStyleAttribute($attributes);
}
}

@ -0,0 +1,16 @@
<?php
namespace Artmark\Forms;
/**
* Description of FieldException
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
class FieldException extends FormException
{
public function __construct(string $message = "", int $code = 0, \Throwable $previous = NULL)
{
parent::__construct($message, $code, $previous);
}
}

@ -0,0 +1,53 @@
<?php
namespace Artmark\Forms\Fields;
use Artmark\Forms\AbstractVisibleField;
/**
* Description of TextField
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
class CheckboxField extends AbstractVisibleField
{
private $checked = false;
public function __construct($form, $name)
{
parent::__construct($form, 'checkbox', $name);
}
/**
* Возвращает ранее заданное значение поля
* @return boolean Метод возвращает true, если поле отмечено флагом, иначе - false
*/
public function value()
{
return $this->checked;
}
/**
* Устанавливает новое значение для поля
* @param boolean $newValue Необходимо передать true, если необходимо установить флаг, false, чтобы убрать флаг
* @return $this
*/
public function setValue($newValue)
{
$this->checked = !empty($newValue);
return $this;
}
public function getAssociativeAttributes()
{
$attributes = parent::getAssociativeAttributes();
$attributes['value'] = 1;
if ($this->checked) {
$attributes['checked'] = null;
}
return $attributes;
}
}

@ -0,0 +1,18 @@
<?php
namespace Artmark\Forms\Fields;
use Artmark\Forms\AbstractTextField;
/**
* Description of EmailField
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
class EmailField extends AbstractTextField
{
public function __construct($form, $name, $value = '')
{
parent::__construct($form, 'email', $name, $value);
}
}

@ -0,0 +1,54 @@
<?php
namespace Artmark\Forms\Fields;
use Artmark\Forms\AbstractField;
/**
* Description of HiddenField
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
class HiddenField extends AbstractField
{
private $value;
public function __construct($form, $name, $value = '')
{
parent::__construct($form, 'hidden', $name);
$this->setValue($value);
}
/**
* Возвращает значение поля
* @return string
*/
public function value()
{
return $this->value;
}
/**
* Задаёт новое значение для поля типа hidden
* @param string|int|float $newValue
* @return $this
*/
public function setValue($newValue)
{
if (is_string($newValue) || is_numeric($newValue)) {
$this->value = strval($newValue);
}
return $this;
}
public function getAssociativeAttributes()
{
$attributes = parent::getAssociativeAttributes();
$attributes['value'] = $this->value;
return $attributes;
}
}

@ -0,0 +1,24 @@
<?php
namespace Artmark\Forms\Fields;
use Artmark\Forms\AbstractTextField;
/**
* Description of PasswordField
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
class PasswordField extends AbstractTextField
{
public function __construct($form, $name)
{
parent::__construct($form, 'password', $name, '');
}
public function setValue($newValue)
{
//No value need to be written
return $this;
}
}

@ -0,0 +1,54 @@
<?php
namespace Artmark\Forms\Fields;
use Artmark\Forms\AbstractChoiceField;
use Artmark\Forms\AbstractChoiceOption;
/**
* Description of RadioField
*
* @author Andrey Pokidov <andrey.pokidov@gmail.com>
*/
class RadioField extends AbstractChoiceField
{
public function __construct($form, $name)
{
parent::__construct($form, 'radio', $name);
}
/**
* Создаёт новый экземпляр опции
* @return RadioOption
*/
protected function createOption($value, $text)
{
return new RadioOption($this, $value, $text);
}
public function appendAttributesToOption(AbstractChoiceOption $option, array & $attributes)
{
if ($this->isSelected($option)) {
$attributes['checked'] = null;
}
if (!$option->hasCssClasses()) {
$this->appendCssClassAttribute($attributes);
}
if (!$option->hasStyle()) {
$this->appendStyleAttribute($attributes);
}
if ($this->isDisabled()) {
$this->appendDisabledAttribute($attributes);
}
$attributes['name'] = $this->name();
if (!$option->hasId()) {
$option->setId($this->name() . '_' . $option->value());
$attributes['id'] = $option->id();
}
}
}

@ -0,0 +1,35 @@
<?php
namespace Artmark\Forms\Fields;
use Artmark\Forms\AbstractChoiceOption;
/**
* Description of RadioOption
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
class RadioOption extends AbstractChoiceOption
{
/**
* The field which is the owner of the option
* @var RadioField
*/
private $field;
public function __construct(RadioField $field, $value, $text)
{
parent::__construct($value, $text);
$this->field = $field;
}
/**
* Метод возвращает поле типа RadioField
* @return RadioField
*/
public function field()
{
return $this->field;
}
}

@ -0,0 +1,52 @@
<?php
namespace Artmark\Forms\Fields;
use Artmark\Forms\AbstractChoiceField;
use Artmark\Forms\AbstractChoiceOption;
use Artmark\Forms\Attributes\SizeAttribute;
/**
* Description of TextField
*
* @author Andrey Pokidov <andrey.pokidov@gmail.com>
*/
class SelectField extends AbstractChoiceField
{
use SizeAttribute;
public function __construct($form, $name)
{
parent::__construct($form, 'select', $name);
}
public function getAssociativeAttributes()
{
$attributes = parent::getAssociativeAttributes();
$this->appendSizeAttribute($attributes);
if ($this->isMultiSelect()) {
$attributes['multiple'] = null;
}
return $attributes;
}
/**
* Создаёт новый экземпляр опции
* @return SelectOption
*/
protected function createOption($value, $text)
{
return new SelectOption($this, $value, $text);
}
public function appendAttributesToOption(AbstractChoiceOption $option, array & $attributes)
{
if ($this->isSelected($option)) {
$attributes['selected'] = null;
}
}
}

@ -0,0 +1,35 @@
<?php
namespace Artmark\Forms\Fields;
use Artmark\Forms\AbstractChoiceOption;
/**
* Description of SelectionOption
*
* @author Andrey Pokidov <andrey.pokidov@gmail.com>
*/
class SelectOption extends AbstractChoiceOption
{
/**
* The field which is the owner of the option
* @var SelectField
*/
private $field;
public function __construct(SelectField $field, $value, $text)
{
parent::__construct($value, $text);
$this->field = $field;
}
/**
* Метод возвращает поле типа SelectField
* @return SelectField
*/
public function field()
{
return $this->field;
}
}

@ -0,0 +1,18 @@
<?php
namespace Artmark\Forms\Fields;
use Artmark\Forms\AbstractTextField;
/**
* Description of TextField
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
class TextField extends AbstractTextField
{
public function __construct($form, $name, $value = '')
{
parent::__construct($form, 'text', $name, $value);
}
}

@ -0,0 +1,293 @@
<?php
namespace Artmark\Forms;
use Artmark\Forms\Attributes\TargetAttribute;
use Artmark\Forms\Attributes\ActionAttribute;
use Artmark\Forms\Attributes\MethodAttribute;
use Artmark\Forms\Attributes\MultipartAttribute;
use Artmark\Forms\Fields\HiddenField;
use Artmark\Forms\Fields\TextField;
use Artmark\Forms\Fields\EmailField;
use Artmark\Forms\Fields\PasswordField;
use Artmark\Forms\Fields\CheckboxField;
use Artmark\Forms\Fields\SelectField;
use Artmark\Forms\Fields\RadioField;
/**
* Description of Form
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
class Form extends AbstractVisibleHtml
{
const METHOD_POST = 'POST';
const METHOD_GET = 'GET';
use TargetAttribute, ActionAttribute, MethodAttribute, MultipartAttribute;
/**
* Ассоциативный список полей формы
* @var AbstractField[]
*/
private $fields = [];
/**
* Префикс для идентификаторов полей
* @var string
*/
private $idPrefix = '';
/**
* Префикс для имен полей
* @var string
*/
private $namePrefix = '';
public function load(array $values)
{
if (empty($values)) {
return;
}
foreach ($this->fields as $field) {
if (array_key_exists($field->name(), $values)) {
$field->setValue($values[$field->name()]);
}
}
}
/**
* Количество полей формы
* @return int
*/
public function fieldAmount()
{
return count($this->fields);
}
/**
* Возвращает поле с именем $name, если такое поле есть
* @param string $name
* @return AbstractField Если поля нет, то возвращает null
*/
public function field($name)
{
return array_key_exists($name, $this->fields) ? $this->fields[$name] : null;
}
/**
* Создаёт в форме скртое поле с именем $name и значением $value
* @param string $name
* @param string $value
* @return HiddenField
*/
public function newHidden($name, $value = '')
{
$field = new HiddenField($this, $name, $value);
$this->registerField($field);
return $field;
}
/**
* Создаёт в форме текстовое поле с именем $name и значением $value
* @param string $name
* @param string $value
* @return TextField
*/
public function newText($name, $value = '')
{
$field = new TextField($this, $name, $value);
$this->registerField($field);
return $field;
}
/**
* Создаёт в форме поле для адреса электронной почты с именем $name и значением $value
* @param string $name
* @param string $value
* @return EmailField
*/
public function newEmail($name, $value = '')
{
$field = new EmailField($this, $name, $value);
$this->registerField($field);
return $field;
}
/**
* Создаёт в форме поле для ввода пароля с именем $name и значением $value
* @param string $name
* @param string $value
* @return PasswordField
*/
public function newPassword($name, $value = '')
{
$field = new PasswordField($this, $name, $value);
$this->registerField($field);
return $field;
}
/**
* Создаёт в форме поле-флаг с именем $name
* @param string $name
* @return CheckboxField
*/
public function newCheckbox($name)
{
$field = new CheckboxField($this, $name);
$this->registerField($field);
return $field;
}
/**
* Создаёт в форме select с именем $name
* @param string $name
* @return SelectField
*/
public function newSelect($name)
{
$field = new SelectField($this, $name);
$this->registerField($field);
return $field;
}
/**
* Создаёт в форме radio button с именем $name
* @param string $name
* @return RadioField
*/
public function newRadio($name)
{
$field = new RadioField($this, $name);
$this->registerField($field);
return $field;
}
private function registerField(AbstractField $field)
{
$this->fields[$field->name()] = $field;
}
/**
* Возвращает значение префикса для идентификаторов полей формы
* @return string
*/
public function idPrefix()
{
return $this->idPrefix;
}
/**
* Метод сообщает, задан ли префикс для идентификаторов полей формы
* @return boolean Если префикс задан, то возвращает true, иначе false
*/
public function hasIdPrefix()
{
return $this->idPrefix !== '';
}
/**
* Метод позволяет задать новое значение для префикса для идентификаторов полей формы
* @param string $newPrefix
* @return $this
*/
public function setIdPrefix($newPrefix)
{
if (!is_string($newPrefix)) {
throw new FormException('ID prefix must be a string');
}
$this->idPrefix = trim($newPrefix);
return $this;
}
/**
* Убирает префикс для идентификаторов полей формы, если он был ранее задан
* @return $this
*/
public function removeIdPrefix()
{
$this->idPrefix = '';
return $this;
}
/**
* Возвращает значение префикса для названий полей формы
* @return string
*/
public function namePrefix()
{
return $this->namePrefix;
}
/**
* Метод сообщает, задан ли префикс для названий полей формы
* @return boolean Если префикс задан, то возвращает true, иначе false
*/
public function hasNamePrefix()
{
return $this->namePrefix !== '';
}
/**
* Метод позволяет задать новое значение для префикса для названий полей формы
* @param string $newPrefix
* @return $this
*/
public function setNamePrefix($newPrefix)
{
if (!is_string($newPrefix)) {
throw new FormException('Name prefix must be a string');
}
$this->namePrefix = trim($newPrefix);
return $this;
}
/**
* Убирает префикс для названий полей формы, если он был ранее задан
* @return $this
*/
public function removeNamePrefix()
{
$this->namePrefix = '';
return $this;
}
/**
* Возвращает ассоциативный массив с HTML аттрибутами формы
* @return stirng[]
*/
public function getAssociativeAttributes()
{
$attributes = parent::getAssociativeAttributes();
$this->appendMethodAttribute($attributes);
$this->appendActionAttribute($attributes);
$this->appendTargetAttribute($attributes);
$this->appendMultipartAttribute($attributes);
return $attributes;
}
}

@ -0,0 +1,16 @@
<?php
namespace Artmark\Forms;
/**
* Description of FormException
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
class FormException extends \Exception
{
public function __construct(string $message = "", int $code = 0, \Throwable $previous = NULL)
{
parent::__construct($message, $code, $previous);
}
}

@ -0,0 +1,92 @@
<?php
namespace Artmark\Forms;
use Illuminate\Support\Facades\Blade;
/**
* Description of FormRenderer
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
class FormRenderer
{
/**
*
* @var FormRenderer
*/
private static $renderer = null;
private static $registered = false;
private function __construct()
{
}
/**
*
* @return FormRenderer
*/
public static function instance()
{
if (is_null(self::$renderer)) {
self::$renderer = new FormRenderer();
}
return self::$renderer;
}
public static function register()
{
if (self::$registered) {
return;
}
self::$registered = true;
Blade::directive('form_begin', function ($expression) { return '<?php echo \Artmark\Forms\FormRenderer::instance()->beginForm(' . $expression . '); ?>'; });
Blade::directive('form_end', function ($expression) { return '<?php echo \Artmark\Forms\FormRenderer::instance()->endForm(' . $expression . '); ?>'; });
Blade::directive('form_label', function ($expression) { return '<?php echo \Artmark\Forms\FormRenderer::instance()->renderLabel(' . $expression . '); ?>'; });
Blade::directive('form_field', function ($expression) { return '<?php echo \Artmark\Forms\FormRenderer::instance()->renderField(' . $expression . '); ?>'; });
}
public function beginForm(Form $form)
{
return '<form' . $this->encodeHtmlAttributes($form->getAssociativeAttributes()) . ">\n";
}
public function renderLabel(AbstractField $field, $suffix = ':')
{
return '<label' . $this->encodeHtmlAttributes($field->label()->getAssociativeAttributes()) . '>' . e($field->label()->text()) . $suffix . '</label>';
}
public function renderField(AbstractField $field)
{
return view('accessories.forms.' . $field->type(), [
'renderer' => $this,
'field' => $field,
]);
}
public function endForm()
{
return "</form>\n";
}
public function encodeHtmlAttributes(array $attributes)
{
$html = '';
foreach ($attributes as $name => $value)
{
$html .= ' ' . e($name);
if (!is_null($value)) {
$html .= '="' . e($value) . '"';
}
}
return $html;
}
}

@ -0,0 +1,17 @@
<?php
namespace Artmark\Forms;
/**
* Description of BasicHtml
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
interface Html
{
/**
*
* @return array
*/
public function getAssociativeAttributes();
}

@ -0,0 +1,61 @@
<?php
namespace Artmark\Forms;
/**
* Description of Label
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
class Label extends AbstractVisibleHtml
{
/**
* Поле, к которому привязана текстовая метка
* @var AbstractField
*/
private $field = null;
private $text = '';
public function __construct(AbstractField $field)
{
$this->field = $field;
}
/**
* Текст метки поля
* @return string
*/
public function text()
{
return $this->text;
}
/**
* Устанавливает новый текст для метки поля
* @param string $newText
* @throws FieldException
*/
public function setText($newText)
{
if (!is_string($newText) && !is_numeric($newText)) {
throw new FieldException('Incorrect field label text: `' . strval($newText) . '`');
}
$this->text = Utils::cleanString($newText);
}
public function getAssociativeAttributes()
{
$attributes = parent::getAssociativeAttributes();
if ($this->field->hasId()) {
$attributes['for'] = $this->field->idWithFormPrefix();
}
else {
$attributes['for'] = $this->field->nameWithFormPrefix();
}
return $attributes;
}
}

@ -0,0 +1,26 @@
<?php
namespace Artmark\Forms;
/**
* Description of Utils
*
* @author Andrey Pokidov <pokidov@e-traffic.ru>
*/
class Utils
{
public static function isEmptyString($value)
{
return $value === '';
}
public static function isNonEmptyString($value)
{
return is_string($value) && $value !== '';
}
public static function cleanString($value)
{
return trim(strval($value));
}
}
Loading…
Cancel
Save