From 35cc11bc59739b7c7ff4efe987380576eabebb42 Mon Sep 17 00:00:00 2001 From: Andrey Pokidov Date: Wed, 18 Mar 2020 16:23:08 +0700 Subject: [PATCH] =?UTF-8?q?+=D0=91=D0=B0=D0=B7=D0=BE=D0=B2=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D0=BF=D0=B0=D0=BA=D0=B5?= =?UTF-8?q?=D1=82=D0=B0=20=D1=84=D0=BE=D1=80=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 12 ++ src/AbstractChoiceField.php | 254 +++++++++++++++++++++++++++++ src/AbstractChoiceOption.php | 115 +++++++++++++ src/AbstractField.php | 180 +++++++++++++++++++++ src/AbstractHtml.php | 24 +++ src/AbstractTextField.php | 65 ++++++++ src/AbstractVisibleField.php | 37 +++++ src/AbstractVisibleHtml.php | 24 +++ src/Attributes/ActionAttribute.php | 82 ++++++++++ src/Attributes/AttributeException.php | 16 ++ src/Attributes/CssClassAttribute.php | 172 ++++++++++++++++++++ src/Attributes/DirectionAttribute.php | 91 +++++++++++ src/Attributes/DisabledAttribute.php | 64 ++++++++ src/Attributes/IdAttribute.php | 82 ++++++++++ src/Attributes/LanguageAttribute.php | 86 ++++++++++ src/Attributes/MaxLengthAttribute.php | 70 ++++++++ src/Attributes/MethodAttribute.php | 86 ++++++++++ src/Attributes/MultipartAttribute.php | 55 +++++++ src/Attributes/ReadOnlyAttribute.php | 64 ++++++++ src/Attributes/RequiredAttribute.php | 64 ++++++++ src/Attributes/SizeAttribute.php | 70 ++++++++ src/Attributes/StyleAttribute.php | 82 ++++++++++ src/Attributes/TargetAttribute.php | 80 ++++++++++ src/Attributes/VisibleAttributes.php | 21 +++ src/FieldException.php | 16 ++ src/Fields/CheckboxField.php | 53 ++++++ src/Fields/EmailField.php | 18 +++ src/Fields/HiddenField.php | 54 +++++++ src/Fields/PasswordField.php | 24 +++ src/Fields/RadioField.php | 54 +++++++ src/Fields/RadioOption.php | 35 ++++ src/Fields/SelectField.php | 52 ++++++ src/Fields/SelectOption.php | 35 ++++ src/Fields/TextField.php | 18 +++ src/Form.php | 293 ++++++++++++++++++++++++++++++++++ src/FormException.php | 16 ++ src/FormRenderer.php | 92 +++++++++++ src/Html.php | 17 ++ src/Label.php | 61 +++++++ src/Utils.php | 26 +++ 40 files changed, 2760 insertions(+) create mode 100644 composer.json create mode 100644 src/AbstractChoiceField.php create mode 100644 src/AbstractChoiceOption.php create mode 100644 src/AbstractField.php create mode 100644 src/AbstractHtml.php create mode 100644 src/AbstractTextField.php create mode 100644 src/AbstractVisibleField.php create mode 100644 src/AbstractVisibleHtml.php create mode 100644 src/Attributes/ActionAttribute.php create mode 100644 src/Attributes/AttributeException.php create mode 100644 src/Attributes/CssClassAttribute.php create mode 100644 src/Attributes/DirectionAttribute.php create mode 100644 src/Attributes/DisabledAttribute.php create mode 100644 src/Attributes/IdAttribute.php create mode 100644 src/Attributes/LanguageAttribute.php create mode 100644 src/Attributes/MaxLengthAttribute.php create mode 100644 src/Attributes/MethodAttribute.php create mode 100644 src/Attributes/MultipartAttribute.php create mode 100644 src/Attributes/ReadOnlyAttribute.php create mode 100644 src/Attributes/RequiredAttribute.php create mode 100644 src/Attributes/SizeAttribute.php create mode 100644 src/Attributes/StyleAttribute.php create mode 100644 src/Attributes/TargetAttribute.php create mode 100644 src/Attributes/VisibleAttributes.php create mode 100644 src/FieldException.php create mode 100644 src/Fields/CheckboxField.php create mode 100644 src/Fields/EmailField.php create mode 100644 src/Fields/HiddenField.php create mode 100644 src/Fields/PasswordField.php create mode 100644 src/Fields/RadioField.php create mode 100644 src/Fields/RadioOption.php create mode 100644 src/Fields/SelectField.php create mode 100644 src/Fields/SelectOption.php create mode 100644 src/Fields/TextField.php create mode 100644 src/Form.php create mode 100644 src/FormException.php create mode 100644 src/FormRenderer.php create mode 100644 src/Html.php create mode 100644 src/Label.php create mode 100644 src/Utils.php diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..3262428 --- /dev/null +++ b/composer.json @@ -0,0 +1,12 @@ +{ + "name": "artmark/forms", + "description": "Form Manager", + "type": "library", + "authors": [ + { + "name": "Andrey Pokidov", + "email": "pokidov@e-traffic.ru" + } + ], + "require": {} +} diff --git a/src/AbstractChoiceField.php b/src/AbstractChoiceField.php new file mode 100644 index 0000000..d85c7f2 --- /dev/null +++ b/src/AbstractChoiceField.php @@ -0,0 +1,254 @@ + + */ +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); +} diff --git a/src/AbstractChoiceOption.php b/src/AbstractChoiceOption.php new file mode 100644 index 0000000..5dd79ab --- /dev/null +++ b/src/AbstractChoiceOption.php @@ -0,0 +1,115 @@ + + */ +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); + } +} diff --git a/src/AbstractField.php b/src/AbstractField.php new file mode 100644 index 0000000..f833085 --- /dev/null +++ b/src/AbstractField.php @@ -0,0 +1,180 @@ + + */ +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)); + } +} diff --git a/src/AbstractHtml.php b/src/AbstractHtml.php new file mode 100644 index 0000000..a49cd40 --- /dev/null +++ b/src/AbstractHtml.php @@ -0,0 +1,24 @@ + + */ +abstract class AbstractHtml implements Html +{ + use IdAttribute; + + public function getAssociativeAttributes() + { + $attributes = []; + + $this->appendIdAttribute($attributes); + + return $attributes; + } +} diff --git a/src/AbstractTextField.php b/src/AbstractTextField.php new file mode 100644 index 0000000..54ee82c --- /dev/null +++ b/src/AbstractTextField.php @@ -0,0 +1,65 @@ + + */ +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; + } +} diff --git a/src/AbstractVisibleField.php b/src/AbstractVisibleField.php new file mode 100644 index 0000000..b648cb3 --- /dev/null +++ b/src/AbstractVisibleField.php @@ -0,0 +1,37 @@ + + */ +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; + } +} diff --git a/src/AbstractVisibleHtml.php b/src/AbstractVisibleHtml.php new file mode 100644 index 0000000..fc52bae --- /dev/null +++ b/src/AbstractVisibleHtml.php @@ -0,0 +1,24 @@ + + */ +abstract class AbstractVisibleHtml extends AbstractHtml +{ + use VisibleAttributes; + + public function getAssociativeAttributes() + { + $attributes = parent::getAssociativeAttributes(); + + $this->appendVisibleAttributes($attributes); + + return $attributes; + } +} diff --git a/src/Attributes/ActionAttribute.php b/src/Attributes/ActionAttribute.php new file mode 100644 index 0000000..7277e73 --- /dev/null +++ b/src/Attributes/ActionAttribute.php @@ -0,0 +1,82 @@ + + */ +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); + } +} diff --git a/src/Attributes/AttributeException.php b/src/Attributes/AttributeException.php new file mode 100644 index 0000000..3b50518 --- /dev/null +++ b/src/Attributes/AttributeException.php @@ -0,0 +1,16 @@ + + */ +class AttributeException extends \Exception +{ + public function __construct(string $message = "", int $code = 0, \Throwable $previous = NULL) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/src/Attributes/CssClassAttribute.php b/src/Attributes/CssClassAttribute.php new file mode 100644 index 0000000..c9e032d --- /dev/null +++ b/src/Attributes/CssClassAttribute.php @@ -0,0 +1,172 @@ + + */ +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)); + } +} diff --git a/src/Attributes/DirectionAttribute.php b/src/Attributes/DirectionAttribute.php new file mode 100644 index 0000000..5c056d2 --- /dev/null +++ b/src/Attributes/DirectionAttribute.php @@ -0,0 +1,91 @@ + + */ +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)); + } +} diff --git a/src/Attributes/DisabledAttribute.php b/src/Attributes/DisabledAttribute.php new file mode 100644 index 0000000..c142408 --- /dev/null +++ b/src/Attributes/DisabledAttribute.php @@ -0,0 +1,64 @@ + + */ +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; + } + } +} diff --git a/src/Attributes/IdAttribute.php b/src/Attributes/IdAttribute.php new file mode 100644 index 0000000..c15ff88 --- /dev/null +++ b/src/Attributes/IdAttribute.php @@ -0,0 +1,82 @@ + + */ +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))); + } +} diff --git a/src/Attributes/LanguageAttribute.php b/src/Attributes/LanguageAttribute.php new file mode 100644 index 0000000..d40b32d --- /dev/null +++ b/src/Attributes/LanguageAttribute.php @@ -0,0 +1,86 @@ + + */ +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); + } +} diff --git a/src/Attributes/MaxLengthAttribute.php b/src/Attributes/MaxLengthAttribute.php new file mode 100644 index 0000000..365ee3b --- /dev/null +++ b/src/Attributes/MaxLengthAttribute.php @@ -0,0 +1,70 @@ + + */ +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; + } + } +} diff --git a/src/Attributes/MethodAttribute.php b/src/Attributes/MethodAttribute.php new file mode 100644 index 0000000..2829ab2 --- /dev/null +++ b/src/Attributes/MethodAttribute.php @@ -0,0 +1,86 @@ + + */ +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)); + } +} diff --git a/src/Attributes/MultipartAttribute.php b/src/Attributes/MultipartAttribute.php new file mode 100644 index 0000000..cf24f17 --- /dev/null +++ b/src/Attributes/MultipartAttribute.php @@ -0,0 +1,55 @@ + + */ +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'; + } + } +} diff --git a/src/Attributes/ReadOnlyAttribute.php b/src/Attributes/ReadOnlyAttribute.php new file mode 100644 index 0000000..701efad --- /dev/null +++ b/src/Attributes/ReadOnlyAttribute.php @@ -0,0 +1,64 @@ + + */ +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; + } + } +} diff --git a/src/Attributes/RequiredAttribute.php b/src/Attributes/RequiredAttribute.php new file mode 100644 index 0000000..475a67d --- /dev/null +++ b/src/Attributes/RequiredAttribute.php @@ -0,0 +1,64 @@ + + */ +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; + } + } +} diff --git a/src/Attributes/SizeAttribute.php b/src/Attributes/SizeAttribute.php new file mode 100644 index 0000000..d74c445 --- /dev/null +++ b/src/Attributes/SizeAttribute.php @@ -0,0 +1,70 @@ + + */ +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; + } + } +} diff --git a/src/Attributes/StyleAttribute.php b/src/Attributes/StyleAttribute.php new file mode 100644 index 0000000..dced616 --- /dev/null +++ b/src/Attributes/StyleAttribute.php @@ -0,0 +1,82 @@ + + */ +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); + } +} diff --git a/src/Attributes/TargetAttribute.php b/src/Attributes/TargetAttribute.php new file mode 100644 index 0000000..bf0f33f --- /dev/null +++ b/src/Attributes/TargetAttribute.php @@ -0,0 +1,80 @@ + + */ +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); + } +} diff --git a/src/Attributes/VisibleAttributes.php b/src/Attributes/VisibleAttributes.php new file mode 100644 index 0000000..53f72a2 --- /dev/null +++ b/src/Attributes/VisibleAttributes.php @@ -0,0 +1,21 @@ + + */ +trait VisibleAttributes +{ + use DirectionAttribute, LanguageAttribute, StyleAttribute, CssClassAttribute; + + protected function appendVisibleAttributes(array & $attributes) + { + $this->appendDirectionAttribute($attributes); + $this->appendLanguageAttribute($attributes); + $this->appendCssClassAttribute($attributes); + $this->appendStyleAttribute($attributes); + } +} diff --git a/src/FieldException.php b/src/FieldException.php new file mode 100644 index 0000000..4980447 --- /dev/null +++ b/src/FieldException.php @@ -0,0 +1,16 @@ + + */ +class FieldException extends FormException +{ + public function __construct(string $message = "", int $code = 0, \Throwable $previous = NULL) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/src/Fields/CheckboxField.php b/src/Fields/CheckboxField.php new file mode 100644 index 0000000..a3551cc --- /dev/null +++ b/src/Fields/CheckboxField.php @@ -0,0 +1,53 @@ + + */ +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; + } +} diff --git a/src/Fields/EmailField.php b/src/Fields/EmailField.php new file mode 100644 index 0000000..eab57d7 --- /dev/null +++ b/src/Fields/EmailField.php @@ -0,0 +1,18 @@ + + */ +class EmailField extends AbstractTextField +{ + public function __construct($form, $name, $value = '') + { + parent::__construct($form, 'email', $name, $value); + } +} diff --git a/src/Fields/HiddenField.php b/src/Fields/HiddenField.php new file mode 100644 index 0000000..5c3b66a --- /dev/null +++ b/src/Fields/HiddenField.php @@ -0,0 +1,54 @@ + + */ +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; + } +} diff --git a/src/Fields/PasswordField.php b/src/Fields/PasswordField.php new file mode 100644 index 0000000..6628c5a --- /dev/null +++ b/src/Fields/PasswordField.php @@ -0,0 +1,24 @@ + + */ +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; + } +} diff --git a/src/Fields/RadioField.php b/src/Fields/RadioField.php new file mode 100644 index 0000000..6c07031 --- /dev/null +++ b/src/Fields/RadioField.php @@ -0,0 +1,54 @@ + + */ +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(); + } + } +} diff --git a/src/Fields/RadioOption.php b/src/Fields/RadioOption.php new file mode 100644 index 0000000..fbd9627 --- /dev/null +++ b/src/Fields/RadioOption.php @@ -0,0 +1,35 @@ + + */ +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; + } +} diff --git a/src/Fields/SelectField.php b/src/Fields/SelectField.php new file mode 100644 index 0000000..8aed55e --- /dev/null +++ b/src/Fields/SelectField.php @@ -0,0 +1,52 @@ + + */ +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; + } + } +} diff --git a/src/Fields/SelectOption.php b/src/Fields/SelectOption.php new file mode 100644 index 0000000..073f0c6 --- /dev/null +++ b/src/Fields/SelectOption.php @@ -0,0 +1,35 @@ + + */ +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; + } +} diff --git a/src/Fields/TextField.php b/src/Fields/TextField.php new file mode 100644 index 0000000..239eedc --- /dev/null +++ b/src/Fields/TextField.php @@ -0,0 +1,18 @@ + + */ +class TextField extends AbstractTextField +{ + public function __construct($form, $name, $value = '') + { + parent::__construct($form, 'text', $name, $value); + } +} diff --git a/src/Form.php b/src/Form.php new file mode 100644 index 0000000..a2db03a --- /dev/null +++ b/src/Form.php @@ -0,0 +1,293 @@ + + */ +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; + } +} diff --git a/src/FormException.php b/src/FormException.php new file mode 100644 index 0000000..584c72a --- /dev/null +++ b/src/FormException.php @@ -0,0 +1,16 @@ + + */ +class FormException extends \Exception +{ + public function __construct(string $message = "", int $code = 0, \Throwable $previous = NULL) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/src/FormRenderer.php b/src/FormRenderer.php new file mode 100644 index 0000000..7ed81db --- /dev/null +++ b/src/FormRenderer.php @@ -0,0 +1,92 @@ + + */ +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 'beginForm(' . $expression . '); ?>'; }); + Blade::directive('form_end', function ($expression) { return 'endForm(' . $expression . '); ?>'; }); + + Blade::directive('form_label', function ($expression) { return 'renderLabel(' . $expression . '); ?>'; }); + Blade::directive('form_field', function ($expression) { return 'renderField(' . $expression . '); ?>'; }); + } + + public function beginForm(Form $form) + { + return 'encodeHtmlAttributes($form->getAssociativeAttributes()) . ">\n"; + } + + public function renderLabel(AbstractField $field, $suffix = ':') + { + return 'encodeHtmlAttributes($field->label()->getAssociativeAttributes()) . '>' . e($field->label()->text()) . $suffix . ''; + } + + public function renderField(AbstractField $field) + { + return view('accessories.forms.' . $field->type(), [ + 'renderer' => $this, + 'field' => $field, + ]); + } + + public function endForm() + { + return "\n"; + } + + public function encodeHtmlAttributes(array $attributes) + { + $html = ''; + + foreach ($attributes as $name => $value) + { + $html .= ' ' . e($name); + + if (!is_null($value)) { + $html .= '="' . e($value) . '"'; + } + } + + return $html; + } +} diff --git a/src/Html.php b/src/Html.php new file mode 100644 index 0000000..5eaa09f --- /dev/null +++ b/src/Html.php @@ -0,0 +1,17 @@ + + */ +interface Html +{ + /** + * + * @return array + */ + public function getAssociativeAttributes(); +} diff --git a/src/Label.php b/src/Label.php new file mode 100644 index 0000000..740319b --- /dev/null +++ b/src/Label.php @@ -0,0 +1,61 @@ + + */ +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; + } +} diff --git a/src/Utils.php b/src/Utils.php new file mode 100644 index 0000000..b74bdca --- /dev/null +++ b/src/Utils.php @@ -0,0 +1,26 @@ + + */ +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)); + } +}