From 318b4c0749442a0ec20a94ef4d18a9e26ca72e33 Mon Sep 17 00:00:00 2001 From: Sandro Rodrigues Date: Sat, 9 Dec 2017 04:26:10 +0000 Subject: [PATCH 1/4] Integrated twig template-engine --- .editorconfig | 3 + CHANGELOG.md | 25 +++- composer.json | 7 +- config.php | 2 - src/Config.php | 78 ++++++++++++ src/Errors/ErrorList.php | 29 ++--- src/Fields/BoundField.php | 37 +++--- src/Fields/MultipleChoiceField.php | 2 - src/Forms/Form.php | 7 +- src/Renderers/Renderer.php | 11 ++ src/Renderers/TwigRenderer.php | 31 +++++ src/Singleton.php | 38 ++++++ src/Utils/Attributes.php | 20 ---- src/Widgets/CheckboxInput.php | 3 +- src/Widgets/CheckboxSelectMultiple.php | 21 +--- src/Widgets/ChoiceWidget.php | 113 ++++++++++++++---- src/Widgets/DateInput.php | 3 - src/Widgets/EmailInput.php | 3 +- src/Widgets/FileInput.php | 3 +- src/Widgets/HiddenInput.php | 3 +- src/Widgets/Input.php | 22 +--- src/Widgets/NumberInput.php | 3 +- src/Widgets/PasswordInput.php | 3 +- src/Widgets/RadioSelect.php | 21 +--- src/Widgets/Select.php | 22 +--- src/Widgets/TextInput.php | 3 +- src/Widgets/Textarea.php | 7 +- src/Widgets/URLInput.php | 3 +- src/Widgets/Widget.php | 30 ++--- src/templates.php | 23 ---- src/templates/default/attrs.html | 1 + src/templates/default/checkbox.html | 1 + src/templates/default/checkbox_select.html | 1 + .../default/checkbox_select_option.html | 1 + src/templates/default/email.html | 1 + src/templates/default/error_list.html | 1 + .../templates/default/errors_list.html | 0 src/templates/default/file.html | 1 + src/templates/default/hidden.html | 1 + src/templates/default/input.html | 1 + src/templates/default/label.html | 1 + src/templates/default/multiple_input.html | 5 + .../default/multiple_input_option.html | 1 + src/templates/default/number.html | 1 + src/templates/default/password.html | 1 + src/templates/default/radio_select.html | 1 + .../default/radio_select_option.html | 1 + src/templates/default/select.html | 5 + src/templates/default/select_option.html | 1 + src/templates/default/text.html | 1 + src/templates/default/textarea.html | 1 + src/templates/default/url.html | 1 + tests/unit/Errors/ErrorListTest.php | 13 -- tests/unit/Fields/BoundFieldTest.php | 21 ++-- tests/unit/PHPFormConfigTest.php | 39 ------ tests/unit/Renderers/TwigRendererTest.php | 28 +++++ tests/unit/Renderers/templates/dummy.html | 1 + tests/unit/Utils/Attributes.php | 10 +- tests/unit/Widgets/CheckboxInputTest.php | 4 +- .../Widgets/CheckboxSelectMultipleTest.php | 44 +++---- tests/unit/Widgets/ChoiceWidgetTest.php | 15 ++- tests/unit/Widgets/InputTest.php | 6 +- tests/unit/Widgets/RadioSelectTest.php | 24 ++-- tests/unit/Widgets/URLInputTest.php | 13 +- 64 files changed, 482 insertions(+), 341 deletions(-) create mode 100644 src/Config.php create mode 100644 src/Renderers/Renderer.php create mode 100644 src/Renderers/TwigRenderer.php create mode 100644 src/Singleton.php delete mode 100644 src/templates.php create mode 100644 src/templates/default/attrs.html create mode 100644 src/templates/default/checkbox.html create mode 100644 src/templates/default/checkbox_select.html create mode 100644 src/templates/default/checkbox_select_option.html create mode 100644 src/templates/default/email.html create mode 100644 src/templates/default/error_list.html rename tests/unit/Widgets/WidgetTest.php => src/templates/default/errors_list.html (100%) create mode 100644 src/templates/default/file.html create mode 100644 src/templates/default/hidden.html create mode 100644 src/templates/default/input.html create mode 100644 src/templates/default/label.html create mode 100644 src/templates/default/multiple_input.html create mode 100644 src/templates/default/multiple_input_option.html create mode 100644 src/templates/default/number.html create mode 100644 src/templates/default/password.html create mode 100644 src/templates/default/radio_select.html create mode 100644 src/templates/default/radio_select_option.html create mode 100644 src/templates/default/select.html create mode 100644 src/templates/default/select_option.html create mode 100644 src/templates/default/text.html create mode 100644 src/templates/default/textarea.html create mode 100644 src/templates/default/url.html create mode 100644 tests/unit/Renderers/TwigRendererTest.php create mode 100644 tests/unit/Renderers/templates/dummy.html diff --git a/.editorconfig b/.editorconfig index 8fa3c2a..284dbaf 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,3 +16,6 @@ trim_trailing_whitespace = true [*.json] indent_size = 2 insert_final_newline = ignore + +[*.html] +insert_final_newline = ignore diff --git a/CHANGELOG.md b/CHANGELOG.md index 5715cc9..3e72030 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,26 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [1.0.0] - 2017-12-07 - - First release; +## [1.1.0] - XXXX-XX-XX +### Added + - Renderers to facilitate integrations of template-engines: + - Added integration to `twig/twig` through `TwigRenderer` class renderer (defined as default); + - Template packs to facilitate customization and extensibility of templates: + - Added template pack `default` and `bootstrap4`. + - Support to configure default renderer and default template pack through `Config` singleton class; + +### Changed + - Class name `PHPFormConfig` to `Config` and moved to `src/` directory; + - `BoundField` attribute name `choices` changed to `options`; + - `BoundField` attribute `options` now return an array instead of formated string; + - `Widgets`, `labelTag` and `ErrorList` now render through default renderer instead of formatter `fleshgrinder/format`; + - `CheckboxSelectMultiple` and `RadioSelect` widget wrapped in an unordered list tag instead of previous `div`; + - Method name `getSubWidgets` to `getOptions` in `Widgets` class; + +### Removed: + - Method `asUL` from `ErrorList` class; + - `config.php` and `templates.php` files; + - Static method `flatatt` from `Attributes` class; ## [1.0.1] - 2017-12-07 ### Added @@ -14,3 +32,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fix formatValue method on ChoiceWidget when value is empty or null; - Fix validate on ChoiceField when value if empty and not required. + +## [1.0.0] - 2017-12-07 + - First release; diff --git a/composer.json b/composer.json index 4e186ca..98f954c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ }, "require": { "php": ">=7.0.0", - "fleshgrinder/format": "^1.1" + "fleshgrinder/format": "^1.1", + "twig/twig": "^2.4" }, "require-dev": { "phpunit/phpunit": "~6.0", @@ -38,5 +39,9 @@ "phpcs --standard=phpcs.xml", "phpunit -c phpunit.xml" ] + }, + "config": { + "sort-packages": true, + "optimize-autoloader": true } } diff --git a/config.php b/config.php index a44dd7c..1e7d929 100644 --- a/config.php +++ b/config.php @@ -4,7 +4,6 @@ class PHPFormConfig { const MESSAGES_FILE_PATH = 'src/messages.php'; - const TEMPLATES_FILE_PATH = 'src/templates.php'; private static $config = null; @@ -14,7 +13,6 @@ class PHPFormConfig private function __construct() { $this->messages = include static::MESSAGES_FILE_PATH; - $this->templates = include static::TEMPLATES_FILE_PATH; } public static function getInstance() diff --git a/src/Config.php b/src/Config.php new file mode 100644 index 0000000..19e5bb4 --- /dev/null +++ b/src/Config.php @@ -0,0 +1,78 @@ +templates_dir . $this->template_pack . "/"; + + if (!file_exists($path)) { + trigger_error("Template dir '$path' don't exists.", E_USER_ERROR); + } + + return $path; + } + + /** + * Return renderer class instantiated + * @return PHPForm\Renderers\Renderer + */ + public function getRenderer() + { + if (is_null($this->renderer)) { + $this->renderer = new $this->renderer_class($this->getTemplatesPath()); + } + + return $this->renderer; + } + + /** + * Define template pack + * @param string + */ + public function setTemplatePack($template_pack) + { + if (!in_array($template_pack, self::TEMPLATE_PACKS)) { + $packs = implode(", ", self::TEMPLATE_PACKS); + trigger_error("Template pack '$template_pack' not valid. Available packs are: $packs", E_USER_ERROR); + } + + $this->template_pack = $template_pack; + } +} diff --git a/src/Errors/ErrorList.php b/src/Errors/ErrorList.php index 1d2c2b2..75d0a25 100644 --- a/src/Errors/ErrorList.php +++ b/src/Errors/ErrorList.php @@ -3,42 +3,27 @@ use ArrayObject; -use Fleshgrinder\Core\Formatter; - -use PHPForm\PHPFormConfig; +use PHPForm\Config; class ErrorList extends ArrayObject { + const TEMPLATE = "error_list.html"; + /** * Returns the error list rendered as HTML. * * @return string */ public function __toString() - { - return $this->asUL(); - } - - /** - * Returns an error dictionary as an unordered list in HTML. - * - * @return string - */ - public function asUL() { if (!count($this)) { return ''; } - $items = []; - $list_item_template = PHPFormConfig::getITemplate("ERRORLIST_ITEM"); - - foreach ($this as $error) { - $items[] = Formatter::format($list_item_template, array("content" => $error)); - } - - $list_template = PHPFormConfig::getITemplate("ERRORLIST"); + $renderer = Config::getInstance()->getRenderer(); - return Formatter::format($list_template, array("items" => implode($items))); + return $renderer->render(self::TEMPLATE, array( + "errors" => $this, + )); } } diff --git a/src/Fields/BoundField.php b/src/Fields/BoundField.php index 60544f2..ed23d24 100644 --- a/src/Fields/BoundField.php +++ b/src/Fields/BoundField.php @@ -1,17 +1,16 @@ getValue(); } - if ($name == 'choices') { - if (!isset($subwidgets_cache)) { - $subwidgets_cache = $this->getSubWidgets(); + if ($name == 'options') { + if (!isset($options_cache)) { + $options_cache = $this->getOptions(); } - return $subwidgets_cache; + return $options_cache; } - return parent::__get($name); + return null; } - private function getSubWidgets(array $attrs = array()) + private function getOptions(array $attrs = array()) { - $widget = $this->field->getWidget(); $attrs = $this->buildWidgetAttrs($attrs); - return $widget->getSubWidgets($this->html_name, $this->getValue(), $attrs); + return $this->field->getWidget()->getOptions($this->html_name, $this->getValue(), $attrs); } protected function asWidget($widget = null, array $attrs = array()) @@ -82,7 +80,7 @@ protected function asWidget($widget = null, array $attrs = array()) return $widget->render($this->html_name, $this->getValue(), $attrs); } - public function labelTag($contents = null, array $attrs = null) + public function labelTag($contents = null, array $attrs = array()) { $contents = is_null($contents) ? $this->label : $contents; @@ -90,20 +88,15 @@ public function labelTag($contents = null, array $attrs = null) return ""; } - if (!is_null($attrs)) { - $attrs = Attributes::flatatt($attrs); - } - $widget = $this->field->getWidget(); - $label_tpl = PHPFormConfig::getITemplate("LABEL"); - $label_required_tpl = PHPFormConfig::getITemplate("LABEL_REQUIRED"); + $renderer = Config::getInstance()->getRenderer(); - return Formatter::format($label_tpl, array( + return $renderer->render(self::TEMPLATE_LABEL, array( "for" => $widget->buildAutoId($this->html_name), "attrs" => $attrs, "contents" => $contents, - "required" => $this->field->isRequired() ? $label_required_tpl : null + "required" => $this->field->isRequired() )); } diff --git a/src/Fields/MultipleChoiceField.php b/src/Fields/MultipleChoiceField.php index b3d8f12..746987c 100644 --- a/src/Fields/MultipleChoiceField.php +++ b/src/Fields/MultipleChoiceField.php @@ -4,8 +4,6 @@ */ namespace PHPForm\Fields; -use Fleshgrinder\Core\Formatter; - use PHPForm\Exceptions\ValidationError; use PHPForm\PHPFormConfig; use PHPForm\Widgets\SelectMultiple; diff --git a/src/Forms/Form.php b/src/Forms/Form.php index 94b24ac..4fb5ae5 100644 --- a/src/Forms/Form.php +++ b/src/Forms/Form.php @@ -16,7 +16,7 @@ abstract class Form implements ArrayAccess, Iterator, Countable { - const PREFIX_TEMPLATE = '{prefix}-{field_name}'; + const PREFIX_FORMAT = '%s-%s'; const NON_FIELD_ERRORS = '__all__'; /** @@ -145,10 +145,7 @@ public function __get(string $name) public function addPrefix(string $field_name) { if (!is_null($this->prefix)) { - return Formatter::format(self::PREFIX_TEMPLATE, array( - "prefix" => $this->prefix, - "field_name" => $field_name - )); + return sprintf(static::PREFIX_FORMAT, $this->prefix, $field_name); } return $field_name; diff --git a/src/Renderers/Renderer.php b/src/Renderers/Renderer.php new file mode 100644 index 0000000..e12ee5c --- /dev/null +++ b/src/Renderers/Renderer.php @@ -0,0 +1,11 @@ +loader = new Twig_Loader_Filesystem($templates_dir); + $this->twig = new Twig_Environment($this->loader); + } + + public function getTemplate($template_name) + { + return $this->twig->load($template_name); + } + + public function render($template_name, $context) + { + $template = $this->getTemplate($template_name); + return $template->render($context); + } +} diff --git a/src/Singleton.php b/src/Singleton.php new file mode 100644 index 0000000..a0e51a8 --- /dev/null +++ b/src/Singleton.php @@ -0,0 +1,38 @@ + $value) { - $flat[] = Formatter::format('{name}="{value}"', array( - "name" => $name, - "value" => htmlentities($value) - )); - } - - return implode(" ", $flat); - } - public static function prettyName($name) { return ucfirst(str_replace("_", " ", $name)); diff --git a/src/Widgets/CheckboxInput.php b/src/Widgets/CheckboxInput.php index 0f63c0e..a430811 100644 --- a/src/Widgets/CheckboxInput.php +++ b/src/Widgets/CheckboxInput.php @@ -6,7 +6,8 @@ class CheckboxInput extends Input { - protected $input_type = 'checkbox'; + const TEMPLATE = 'checkbox.html'; + const INPUT_TYPE = 'checkbox'; public function getContext(string $name, $value, array $attrs = null) { diff --git a/src/Widgets/CheckboxSelectMultiple.php b/src/Widgets/CheckboxSelectMultiple.php index d6a220a..ab6ebad 100644 --- a/src/Widgets/CheckboxSelectMultiple.php +++ b/src/Widgets/CheckboxSelectMultiple.php @@ -8,22 +8,11 @@ class CheckboxSelectMultiple extends ChoiceWidget { + const TEMPLATE = 'checkbox_select.html'; + const TEMPLATE_CHOICE = 'checkbox_select_option.html'; + const INPUT_TYPE = 'checkbox'; + protected $allow_multiple_selected = true; protected $option_inherits_attrs = true; - protected $selected_attribute = "checked"; - protected $input_type = "checkbox"; - - /** - * The constructor. - */ - public function __construct(array $choices = array(), array $attrs = null) - { - $this->template = PHPFormConfig::getITemplate("CHECKBOX_SELECT_MULTIPLE"); - $this->template_choice = sprintf( - PHPFormConfig::getITemplate("CHECKBOX_SELECT_MULTIPLE_ITEM"), - PHPFormConfig::getITemplate("INPUT") - ); - - parent::__construct($choices, $attrs); - } + protected $selected_attribute = 'checked'; } diff --git a/src/Widgets/ChoiceWidget.php b/src/Widgets/ChoiceWidget.php index a9fe9fa..3ee0e47 100644 --- a/src/Widgets/ChoiceWidget.php +++ b/src/Widgets/ChoiceWidget.php @@ -4,17 +4,35 @@ */ namespace PHPForm\Widgets; -use Fleshgrinder\Core\Formatter; - use PHPForm\Utils\Attributes; abstract class ChoiceWidget extends Widget { - protected $template_choice = ""; + const TEMPLATE_CHOICE = ''; + const INPUT_TYPE = null; + + /** + * Allow multiple choices being selected. + * @var boolean + */ protected $allow_multiple_selected = false; - protected $input_type = null; + + /** + * Option inherit the attrs of mains widget. + * @var boolean + */ protected $option_inherits_attrs = true; + + /** + * Attr name when option is selected. + * @var string + */ protected $selected_attribute = "selected"; + + /** + * Valid choices for this widget. + * @var array + */ protected $choices; /** @@ -27,31 +45,54 @@ public function __construct(array $choices = array(), array $attrs = null) $this->setChoices($choices); } + /** + * @param array $choices Choices to be setted. + */ public function setChoices(array $choices) { $this->choices = $choices; } - public function buildName($name) + /** + * If allow multiple selected, add [] to the end of name. + * + * @param string $name + * @return string + */ + protected function buildName(string $name) { - if ($this->allow_multiple_selected && substr($name, -2, 2) !== "[]") { - $name = $name . "[]"; - } - - return $name; + return $this->allow_multiple_selected ? $name . "[]" : $name; } - public function getContext(string $name, $value, array $attrs = null) + /** + * Prepare context to be used on render method. + * + * @param string $name Field name. + * @param mixed $value Field value. + * @param array $attrs Extra widget attributes. + * + * @return array + */ + protected function getContext(string $name, $value, array $attrs = null) { $context = parent::getContext($name, $value, $attrs); $context["name"] = $this->buildName($name); - $context["options"] = implode($this->getSubWidgets($name, $value, $attrs)); + $context["options"] = $this->getOptions($name, $value, $attrs); return $context; } - public function getSubWidgets(string $name, $value, array $attrs = null) + /** + * Prepare options. + * + * @param string $name Choice name. + * @param mixed $value Choice value. + * @param array $attrs Extra choice attributes. + * + * @return array + */ + public function getOptions(string $name, $value, array $attrs = null) { $value = $this->formatValue($value); $subwidgets = array(); @@ -63,21 +104,33 @@ public function getSubWidgets(string $name, $value, array $attrs = null) $selected = false; if (!$has_selected || $this->allow_multiple_selected) { - $selected = in_array($choice_value, $value); - $has_selected = $selected; + $has_selected = $selected = in_array($choice_value, $value); } - $context = $this->getSubWidgetContext($name, $choice_value, $choice_label, $selected, $index, $attrs); - - $subwidgets[] = Formatter::format($this->template_choice, $context); - - $index++; + $subwidgets[] = $this->buildOption( + $name, + $choice_value, + $choice_label, + $selected, + $index++, + $attrs + ); } return $subwidgets; } - public function getSubWidgetContext( + /** + * @param string $name + * @param mixed $value + * @param string $label + * @param bool $is_selected + * @param int $index + * @param array|null $attrs + * + * @return array + */ + protected function buildOption( string $name, $value, string $label, @@ -99,14 +152,22 @@ public function getSubWidgetContext( return array( "for" => $this->buildAutoId($name, $index), - "type" => $this->input_type, - "name" => htmlentities($this->buildName($name)), - "value" => htmlentities($value), - "label" => htmlentities($label), - "attrs" => Attributes::flatatt($attrs), + "type" => static::INPUT_TYPE, + "name" => $this->buildName($name), + "value" => $value, + "label" => $label, + "attrs" => $attrs, + "template" => static::TEMPLATE_CHOICE, ); } + /** + * Format value to be rendered in html. + * + * @param mixed $value Value to be formated. + * + * @return array + */ protected function formatValue($value) { if (is_array($value)) { diff --git a/src/Widgets/DateInput.php b/src/Widgets/DateInput.php index 2e7eb39..be81450 100644 --- a/src/Widgets/DateInput.php +++ b/src/Widgets/DateInput.php @@ -6,8 +6,5 @@ class DateInput extends TemporalInput { - /** - * @var string Format of temporal value. - */ const FORMAT = 'd-m-Y'; } diff --git a/src/Widgets/EmailInput.php b/src/Widgets/EmailInput.php index 7170af0..3e1e6b3 100644 --- a/src/Widgets/EmailInput.php +++ b/src/Widgets/EmailInput.php @@ -6,5 +6,6 @@ class EmailInput extends Input { - protected $input_type = 'email'; + const TEMPLATE = 'email.html'; + const INPUT_TYPE = 'email'; } diff --git a/src/Widgets/FileInput.php b/src/Widgets/FileInput.php index a7f6f37..8ed0ec9 100644 --- a/src/Widgets/FileInput.php +++ b/src/Widgets/FileInput.php @@ -6,7 +6,8 @@ class FileInput extends Input { - protected $input_type = 'file'; + const TEMPLATE = 'file.html'; + const INPUT_TYPE = 'file'; /** * FileInput don't render the value. diff --git a/src/Widgets/HiddenInput.php b/src/Widgets/HiddenInput.php index b06c857..c5811fb 100644 --- a/src/Widgets/HiddenInput.php +++ b/src/Widgets/HiddenInput.php @@ -6,5 +6,6 @@ class HiddenInput extends Input { - protected $input_type = 'hidden'; + const TEMPLATE = 'hidden.html'; + const INPUT_TYPE = 'hidden'; } diff --git a/src/Widgets/Input.php b/src/Widgets/Input.php index 4454d72..c4723ef 100644 --- a/src/Widgets/Input.php +++ b/src/Widgets/Input.php @@ -4,24 +4,10 @@ */ namespace PHPForm\Widgets; -use PHPForm\PHPFormConfig; - abstract class Input extends Widget { - /** - * @var string The input type to use for the widget. - */ - protected $input_type = null; - - /** - * The constructor. - */ - public function __construct(array $attrs = null) - { - $this->template = PHPFormConfig::getITemplate("INPUT"); - - parent::__construct($attrs); - } + const TEMPLATE = 'input.html'; + const INPUT_TYPE = null; /** * Prepare context to be used on render method. @@ -35,7 +21,9 @@ public function __construct(array $attrs = null) public function getContext(string $name, $value, array $attrs = null) { $context = parent::getContext($name, $value, $attrs); - $context["type"] = $this->input_type; + + $context["type"] = static::INPUT_TYPE; + return $context; } } diff --git a/src/Widgets/NumberInput.php b/src/Widgets/NumberInput.php index 471f90d..3a57046 100644 --- a/src/Widgets/NumberInput.php +++ b/src/Widgets/NumberInput.php @@ -6,5 +6,6 @@ class NumberInput extends Input { - protected $input_type = 'number'; + const TEMPLATE = 'number.html'; + const INPUT_TYPE = 'number'; } diff --git a/src/Widgets/PasswordInput.php b/src/Widgets/PasswordInput.php index a344cc1..831a34b 100644 --- a/src/Widgets/PasswordInput.php +++ b/src/Widgets/PasswordInput.php @@ -6,7 +6,8 @@ class PasswordInput extends Input { - protected $input_type = 'password'; + const TEMPLATE = 'password.html'; + const INPUT_TYPE = 'password'; /** * PasswordInput don't render the value. diff --git a/src/Widgets/RadioSelect.php b/src/Widgets/RadioSelect.php index 74575db..dd28e60 100644 --- a/src/Widgets/RadioSelect.php +++ b/src/Widgets/RadioSelect.php @@ -8,21 +8,10 @@ class RadioSelect extends ChoiceWidget { - protected $option_inherits_attrs = true; - protected $selected_attribute = "checked"; - protected $input_type = "radio"; - - /** - * The constructor. - */ - public function __construct(array $choices = array(), array $attrs = null) - { - $this->template = PHPFormConfig::getITemplate("RADIOSELECT"); - $this->template_choice = sprintf( - PHPFormConfig::getITemplate("RADIOSELECT_ITEM"), - PHPFormConfig::getITemplate("INPUT") - ); + const TEMPLATE = 'radio_select.html'; + const TEMPLATE_CHOICE = 'radio_select_option.html'; + const INPUT_TYPE = 'radio'; - parent::__construct($choices, $attrs); - } + protected $option_inherits_attrs = true; + protected $selected_attribute = 'checked'; } diff --git a/src/Widgets/Select.php b/src/Widgets/Select.php index 728a8f3..b8dc596 100644 --- a/src/Widgets/Select.php +++ b/src/Widgets/Select.php @@ -8,29 +8,19 @@ class Select extends ChoiceWidget { - protected $option_inherits_attrs = false; - - /** - * The constructor. - */ - public function __construct(array $choices = array(), array $attrs = null) - { - $this->template = PHPFormConfig::getITemplate("SELECT"); - $this->template_choice = PHPFormConfig::getITemplate("SELECT_ITEM"); + const TEMPLATE = 'select.html'; + const TEMPLATE_CHOICE = 'select_option.html'; - parent::__construct($choices, $attrs); - } + protected $option_inherits_attrs = false; public function getContext(string $name, $value, array $attrs = null) { - if (is_null($attrs)) { - $attrs = array(); - } + $context = parent::getContext($name, $value, $attrs); if ($this->allow_multiple_selected) { - $attrs["multiple"] = "multiple"; + $context["attrs"]["multiple"] = "multiple"; } - return parent::getContext($name, $value, $attrs); + return $context; } } diff --git a/src/Widgets/TextInput.php b/src/Widgets/TextInput.php index 71d28d5..5d8f1a6 100644 --- a/src/Widgets/TextInput.php +++ b/src/Widgets/TextInput.php @@ -6,5 +6,6 @@ class TextInput extends Input { - protected $input_type = 'text'; + const TEMPLATE = 'text.html'; + const INPUT_TYPE = 'text'; } diff --git a/src/Widgets/Textarea.php b/src/Widgets/Textarea.php index 41e4dc8..861a23d 100644 --- a/src/Widgets/Textarea.php +++ b/src/Widgets/Textarea.php @@ -4,10 +4,11 @@ */ namespace PHPForm\Widgets; -use PHPForm\PHPFormConfig; - class Textarea extends Widget { + const TEMPLATE = 'textarea.html'; + const INPUT_TYPE = 'textarea'; + /** * The constructor. */ @@ -19,8 +20,6 @@ public function __construct(array $attrs = null) $extra_attrs = array_merge($extra_attrs, $attrs); } - $this->template = PHPFormConfig::getITemplate("TEXTAREA"); - parent::__construct($extra_attrs); } } diff --git a/src/Widgets/URLInput.php b/src/Widgets/URLInput.php index a4c2bcd..2cd6c6a 100644 --- a/src/Widgets/URLInput.php +++ b/src/Widgets/URLInput.php @@ -6,5 +6,6 @@ class URLInput extends Input { - protected $input_type = 'url'; + const TEMPLATE = 'url.html'; + const INPUT_TYPE = 'url'; } diff --git a/src/Widgets/Widget.php b/src/Widgets/Widget.php index 81e78a5..d904699 100644 --- a/src/Widgets/Widget.php +++ b/src/Widgets/Widget.php @@ -4,15 +4,11 @@ */ namespace PHPForm\Widgets; -use Fleshgrinder\Core\Formatter; - -use PHPForm\Utils\Attributes; +use PHPForm\Config; abstract class Widget { - const AUTO_ID_TEMPLATE = "id_{name}[_{index}?]"; - - protected $template = ""; + const TEMPLATE = ""; /** * @var array Attributes to be added to the widget. @@ -41,9 +37,11 @@ public function __construct(array $attrs = null) */ public function render(string $name, $value, array $attrs = null) { + $renderer = Config::getInstance()->getRenderer(); + $context = $this->getContext($name, $value, $attrs); - return Formatter::format($this->template, $context); + return $renderer->render(static::TEMPLATE, $context); } /** @@ -65,9 +63,9 @@ protected function getContext(string $name, $value, array $attrs = null) } return array( - "name" => htmlentities($name), - "attrs" => Attributes::flatatt($attrs), - "value" => is_string($value) ? htmlspecialchars($value) : $value, + "name" => $name, + "attrs" => $attrs, + "value" => $value, ); } @@ -98,13 +96,13 @@ public function valueFromData($data, $files, string $name) } /** - * Return defined subwidget. + * Return defined options. * * @return array */ - public function getSubWidgets(string $name, $value, array $attrs = null) + public function getOptions(string $name, $value, array $attrs = null) { - return $this->widget; + return array(); } /** @@ -144,9 +142,7 @@ private function buildAttrs(array $extra_attrs = null) */ public function buildAutoId(string $name, int $index = null) { - return Formatter::format(self::AUTO_ID_TEMPLATE, array( - "name" => $name, - "index" => $index - )); + $auto_id = is_null($index) ? "id_%s" : "id_%s_%s"; + return sprintf($auto_id, $name, $index); } } diff --git a/src/templates.php b/src/templates.php deleted file mode 100644 index 75fc3b7..0000000 --- a/src/templates.php +++ /dev/null @@ -1,23 +0,0 @@ - '', - "LABEL_REQUIRED" => '*', - - // ErrorList - "ERRORLIST" => '', - "ERRORLIST_ITEM" => '
  • {content}
  • ', - - // Widgets - "TEXTAREA" => '', - "INPUT" => '', - - "SELECT" => '', - "SELECT_ITEM" => '', - - "CHECKBOX_SELECT_MULTIPLE" => '
    {options}
    ', - "CHECKBOX_SELECT_MULTIPLE_ITEM" => '', - - "RADIOSELECT" => '
    {options}
    ', - "RADIOSELECT_ITEM" => '', -); diff --git a/src/templates/default/attrs.html b/src/templates/default/attrs.html new file mode 100644 index 0000000..878ba67 --- /dev/null +++ b/src/templates/default/attrs.html @@ -0,0 +1 @@ +{% for name, value in attrs %} {{ name }}="{{ value|e }}"{% endfor %} \ No newline at end of file diff --git a/src/templates/default/checkbox.html b/src/templates/default/checkbox.html new file mode 100644 index 0000000..3c0c8a9 --- /dev/null +++ b/src/templates/default/checkbox.html @@ -0,0 +1 @@ +{% include 'input.html' %} \ No newline at end of file diff --git a/src/templates/default/checkbox_select.html b/src/templates/default/checkbox_select.html new file mode 100644 index 0000000..86f1a99 --- /dev/null +++ b/src/templates/default/checkbox_select.html @@ -0,0 +1 @@ +{% include 'multiple_input.html' %} \ No newline at end of file diff --git a/src/templates/default/checkbox_select_option.html b/src/templates/default/checkbox_select_option.html new file mode 100644 index 0000000..13a48ac --- /dev/null +++ b/src/templates/default/checkbox_select_option.html @@ -0,0 +1 @@ +{% include 'multiple_input_option.html' %} \ No newline at end of file diff --git a/src/templates/default/email.html b/src/templates/default/email.html new file mode 100644 index 0000000..3c0c8a9 --- /dev/null +++ b/src/templates/default/email.html @@ -0,0 +1 @@ +{% include 'input.html' %} \ No newline at end of file diff --git a/src/templates/default/error_list.html b/src/templates/default/error_list.html new file mode 100644 index 0000000..5622853 --- /dev/null +++ b/src/templates/default/error_list.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/unit/Widgets/WidgetTest.php b/src/templates/default/errors_list.html similarity index 100% rename from tests/unit/Widgets/WidgetTest.php rename to src/templates/default/errors_list.html diff --git a/src/templates/default/file.html b/src/templates/default/file.html new file mode 100644 index 0000000..3c0c8a9 --- /dev/null +++ b/src/templates/default/file.html @@ -0,0 +1 @@ +{% include 'input.html' %} \ No newline at end of file diff --git a/src/templates/default/hidden.html b/src/templates/default/hidden.html new file mode 100644 index 0000000..3c0c8a9 --- /dev/null +++ b/src/templates/default/hidden.html @@ -0,0 +1 @@ +{% include 'input.html' %} \ No newline at end of file diff --git a/src/templates/default/input.html b/src/templates/default/input.html new file mode 100644 index 0000000..32e3036 --- /dev/null +++ b/src/templates/default/input.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/templates/default/label.html b/src/templates/default/label.html new file mode 100644 index 0000000..2ac0f4f --- /dev/null +++ b/src/templates/default/label.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/templates/default/multiple_input.html b/src/templates/default/multiple_input.html new file mode 100644 index 0000000..fa8174e --- /dev/null +++ b/src/templates/default/multiple_input.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/src/templates/default/multiple_input_option.html b/src/templates/default/multiple_input_option.html new file mode 100644 index 0000000..26d96b6 --- /dev/null +++ b/src/templates/default/multiple_input_option.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/templates/default/number.html b/src/templates/default/number.html new file mode 100644 index 0000000..3c0c8a9 --- /dev/null +++ b/src/templates/default/number.html @@ -0,0 +1 @@ +{% include 'input.html' %} \ No newline at end of file diff --git a/src/templates/default/password.html b/src/templates/default/password.html new file mode 100644 index 0000000..3c0c8a9 --- /dev/null +++ b/src/templates/default/password.html @@ -0,0 +1 @@ +{% include 'input.html' %} \ No newline at end of file diff --git a/src/templates/default/radio_select.html b/src/templates/default/radio_select.html new file mode 100644 index 0000000..86f1a99 --- /dev/null +++ b/src/templates/default/radio_select.html @@ -0,0 +1 @@ +{% include 'multiple_input.html' %} \ No newline at end of file diff --git a/src/templates/default/radio_select_option.html b/src/templates/default/radio_select_option.html new file mode 100644 index 0000000..13a48ac --- /dev/null +++ b/src/templates/default/radio_select_option.html @@ -0,0 +1 @@ +{% include 'multiple_input_option.html' %} \ No newline at end of file diff --git a/src/templates/default/select.html b/src/templates/default/select.html new file mode 100644 index 0000000..312f353 --- /dev/null +++ b/src/templates/default/select.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/src/templates/default/select_option.html b/src/templates/default/select_option.html new file mode 100644 index 0000000..4efd53c --- /dev/null +++ b/src/templates/default/select_option.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/templates/default/text.html b/src/templates/default/text.html new file mode 100644 index 0000000..3c0c8a9 --- /dev/null +++ b/src/templates/default/text.html @@ -0,0 +1 @@ +{% include 'input.html' %} \ No newline at end of file diff --git a/src/templates/default/textarea.html b/src/templates/default/textarea.html new file mode 100644 index 0000000..dd9e452 --- /dev/null +++ b/src/templates/default/textarea.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/templates/default/url.html b/src/templates/default/url.html new file mode 100644 index 0000000..3c0c8a9 --- /dev/null +++ b/src/templates/default/url.html @@ -0,0 +1 @@ +{% include 'input.html' %} \ No newline at end of file diff --git a/tests/unit/Errors/ErrorListTest.php b/tests/unit/Errors/ErrorListTest.php index 5235fea..e65ac7d 100644 --- a/tests/unit/Errors/ErrorListTest.php +++ b/tests/unit/Errors/ErrorListTest.php @@ -13,17 +13,4 @@ public function testToString() $expected = ''; $this->assertEquals((string) $error, $expected); } - - public function testAsUL() - { - $error = new ErrorList(array(1, 2, 3)); - $expected = ''; - $this->assertEquals($error->asUL(), $expected); - } - - public function testAsULEmptyArray() - { - $error = new ErrorList(); - $this->assertEquals($error->asUL(), ''); - } } diff --git a/tests/unit/Fields/BoundFieldTest.php b/tests/unit/Fields/BoundFieldTest.php index 7d70429..c0135b6 100644 --- a/tests/unit/Fields/BoundFieldTest.php +++ b/tests/unit/Fields/BoundFieldTest.php @@ -169,16 +169,21 @@ public function testLabelTagWithAttrs() } - public function testChoices() + public function testOptions() { - $field = new ChoiceField(["choices" => array("option1" => "Option1", "option2" => "Option2")]); + $field = new ChoiceField(["choices" => array("option1" => "Option1")]); $bound = new BoundField($this->simple_form, $field, "name"); - $expected = array( - '', - '' - ); - - $this->assertEquals($expected, $bound->choices); + $expected = array(array( + "for" => "id_name_1", + "type" => null, + "name" => "name", + "value" => "option1", + "label" => "Option1", + "attrs" => array(), + "template" => "select_option.html" + )); + + $this->assertEquals($expected, $bound->options); } } diff --git a/tests/unit/PHPFormConfigTest.php b/tests/unit/PHPFormConfigTest.php index d0c9bd4..ac96e16 100644 --- a/tests/unit/PHPFormConfigTest.php +++ b/tests/unit/PHPFormConfigTest.php @@ -26,20 +26,6 @@ public function testGetMessage() $this->assertNull($instance->getMessage("Inexistent")); } - public function testGetITemplate() - { - $this->assertEquals(PHPFormConfig::getITemplate("LABEL_REQUIRED"), '*'); - $this->assertNull(PHPFormConfig::getITemplate("Inexistent")); - } - - public function testGetTemplate() - { - $instance = PHPFormConfig::getInstance(); - - $this->assertEquals($instance->getTemplate("LABEL_REQUIRED"), '*'); - $this->assertNull($instance->getTemplate("Inexistent")); - } - public function testSetIMessages() { PHPFormConfig::setIMessages(array( @@ -64,29 +50,4 @@ public function testSetMessages() $this->assertEquals("Invalid 2", $instance->getMessage("Invalid 2")); $this->assertEquals("Invalid 3", $instance->getMessage("Invalid 3")); } - - public function testSetITemplates() - { - PHPFormConfig::setITemplates(array( - "Invalid 2" => "Invalid 2", - "Invalid 3" => "Invalid 3", - )); - - $this->assertEquals('*', PHPFormConfig::getITemplate("LABEL_REQUIRED")); - $this->assertEquals("Invalid 2", PHPFormConfig::getITemplate("Invalid 2")); - $this->assertEquals("Invalid 3", PHPFormConfig::getITemplate("Invalid 3")); - } - - public function testSetTemplates() - { - $instance = PHPFormConfig::getInstance(); - $instance->setTemplates(array( - "Invalid 2" => "Invalid 2", - "Invalid 3" => "Invalid 3", - )); - - $this->assertEquals('*', $instance->getTemplate("LABEL_REQUIRED")); - $this->assertEquals("Invalid 2", $instance->getTemplate("Invalid 2")); - $this->assertEquals("Invalid 3", $instance->getTemplate("Invalid 3")); - } } diff --git a/tests/unit/Renderers/TwigRendererTest.php b/tests/unit/Renderers/TwigRendererTest.php new file mode 100644 index 0000000..d3cb895 --- /dev/null +++ b/tests/unit/Renderers/TwigRendererTest.php @@ -0,0 +1,28 @@ +renderer = new TwigRenderer(__DIR__ . "/templates"); + } + + public function testGetTempate() + { + $result = $this->renderer->getTemplate("dummy.html"); + $this->assertInstanceOf(Twig_TemplateWrapper::class, $result); + } + + public function testRender() + { + $expected = "Dummy template test\n"; + $result = $this->renderer->render("dummy.html", array("name" => "test")); + $this->assertEquals($expected, $result); + } +} diff --git a/tests/unit/Renderers/templates/dummy.html b/tests/unit/Renderers/templates/dummy.html new file mode 100644 index 0000000..2b895a4 --- /dev/null +++ b/tests/unit/Renderers/templates/dummy.html @@ -0,0 +1 @@ +Dummy template {{name}} diff --git a/tests/unit/Utils/Attributes.php b/tests/unit/Utils/Attributes.php index 8fb7779..048ab15 100644 --- a/tests/unit/Utils/Attributes.php +++ b/tests/unit/Utils/Attributes.php @@ -1,5 +1,5 @@ "name", "type" => "text"); - $this->assertEquals(Attributes::flatatt($attrs), 'name="name" type="text"'); - $this->assertEquals(Attributes::flatatt(array()), ''); - $this->assertEquals(Attributes::flatatt("string"), ''); - } - public function testPrettyName() { $this->assertEquals(Attributes::prettyName("label_name"), 'Label name'); diff --git a/tests/unit/Widgets/CheckboxInputTest.php b/tests/unit/Widgets/CheckboxInputTest.php index dcefcb0..12833db 100644 --- a/tests/unit/Widgets/CheckboxInputTest.php +++ b/tests/unit/Widgets/CheckboxInputTest.php @@ -15,13 +15,13 @@ public function setUp() public function testGetContext() { $context = $this->widget->getContext("name", 10); - $this->assertEquals('checked="checked" id="id_name"', $context['attrs']); + $this->assertArraySubset(array("checked" => "checked"), $context['attrs']); } public function testGetContextWithFalseValue() { $context = $this->widget->getContext("name", false); - $this->assertEquals('id="id_name"', $context['attrs']); + $this->assertArrayNotHasKey("checked", $context['attrs']); } public function testRender() diff --git a/tests/unit/Widgets/CheckboxSelectMultipleTest.php b/tests/unit/Widgets/CheckboxSelectMultipleTest.php index ae495aa..2ee4eaa 100644 --- a/tests/unit/Widgets/CheckboxSelectMultipleTest.php +++ b/tests/unit/Widgets/CheckboxSelectMultipleTest.php @@ -15,14 +15,14 @@ public function setUp() public function testRender() { $expected = - '
    ' . - '
    '; + '' . + ''; $this->assertXmlStringEqualsXmlString($expected, $this->widget->render("name", null)); } @@ -30,14 +30,14 @@ public function testRender() public function testRenderChecked() { $expected = - '
    ' . - '
    '; + '' . + ''; $this->assertXmlStringEqualsXmlString($expected, $this->widget->render("name", "option1")); } @@ -52,20 +52,20 @@ public function testRenderMultipleChecked() ]); $expected = - '
    ' . - '
    '; + '' . + ''; $this->assertXmlStringEqualsXmlString($expected, $widget->render("name", ["option2", "option3"])); } diff --git a/tests/unit/Widgets/ChoiceWidgetTest.php b/tests/unit/Widgets/ChoiceWidgetTest.php index ad514f1..3f1ba14 100644 --- a/tests/unit/Widgets/ChoiceWidgetTest.php +++ b/tests/unit/Widgets/ChoiceWidgetTest.php @@ -19,12 +19,15 @@ public function setUp() $this->widget = $this->getMockForAbstractClass(ChoiceWidget::class, array($attrs)); } - public function testFormatValue() + public function testSetChoices() { - // $this->assertEquals([], $this->widget->formatValue("")); - // $this->assertEquals([], $this->widget->formatValue(null)); - // $this->assertEquals([], $this->widget->formatValue([])); - // $this->assertEquals(["1", "2"], $this->widget->formatValue([1, 2])); - $this->assertEquals(1, 1); + $choices = array( + "option1" => "Option 1", + "option2" => "Option 2", + ); + + $this->widget->setChoices($choices); + + $this->assertAttributeEquals($choices, "choices", $this->widget); } } diff --git a/tests/unit/Widgets/InputTest.php b/tests/unit/Widgets/InputTest.php index 2cfdaaf..400b961 100644 --- a/tests/unit/Widgets/InputTest.php +++ b/tests/unit/Widgets/InputTest.php @@ -18,7 +18,7 @@ public function testRender() { $stub = $this->getMockForAbstractClass(Input::class); $result = $stub->render("name", "value"); - $expected = ''; + $expected = ''; $this->assertXmlStringEqualsXmlString($result, $expected); } @@ -26,7 +26,7 @@ public function testRenderWithoutValue() { $stub = $this->getMockForAbstractClass(Input::class); $result = $stub->render("name", null); - $expected = ''; + $expected = ''; $this->assertXmlStringEqualsXmlString($result, $expected); } @@ -34,7 +34,7 @@ public function testRenderWithExtraAttrs() { $stub = $this->getMockForAbstractClass(Input::class); $result = $stub->render("name", "value", array("class" => "input")); - $expected = ''; + $expected = ''; $this->assertXmlStringEqualsXmlString($result, $expected); } } diff --git a/tests/unit/Widgets/RadioSelectTest.php b/tests/unit/Widgets/RadioSelectTest.php index b4e5bac..b73afe4 100644 --- a/tests/unit/Widgets/RadioSelectTest.php +++ b/tests/unit/Widgets/RadioSelectTest.php @@ -15,14 +15,14 @@ public function setUp() public function testRender() { $expected = - '
    ' . - '
    '; + '' . + ''; $this->assertXmlStringEqualsXmlString($expected, $this->widget->render("name", null)); } @@ -30,14 +30,14 @@ public function testRender() public function testRenderChecked() { $expected = - '
    ' . - '
    '; + '' . + ''; $this->assertXmlStringEqualsXmlString($expected, $this->widget->render("name", "option1")); $this->assertXmlStringEqualsXmlString($expected, $this->widget->render("name", ["option1", "option2"])); diff --git a/tests/unit/Widgets/URLInputTest.php b/tests/unit/Widgets/URLInputTest.php index 912577b..e8c517a 100644 --- a/tests/unit/Widgets/URLInputTest.php +++ b/tests/unit/Widgets/URLInputTest.php @@ -7,17 +7,20 @@ class URLInputTest extends TestCase { + public function setUp() + { + $this->widget = new URLInput(); + } + public function testGetContext() { - $widget = new URLInput(); - $context = $widget->getContext("name", "url"); + $context = $this->widget->getContext("name", "url"); $this->assertEquals($context["type"], "url"); } public function testRender() { - $widget = new URLInput(); - $render = $widget->render("name", "url"); - $this->assertXmlStringEqualsXmlString($render, ''); + $expected = $this->widget->render("name", "url"); + $this->assertXmlStringEqualsXmlString($expected, ''); } } From 474d8599dd201267ab4a6efe53a0c84204bce2e0 Mon Sep 17 00:00:00 2001 From: Sandro Rodrigues Date: Sat, 9 Dec 2017 06:36:10 +0000 Subject: [PATCH 2/4] Added fallbacks to template loading --- CHANGELOG.md | 10 +++-- src/Config.php | 39 +++++++++---------- src/Fields/BoundField.php | 2 +- src/Renderers/Renderer.php | 7 ++-- src/Renderers/TwigRenderer.php | 21 ++++++---- src/Widgets/CheckboxInput.php | 4 +- src/Widgets/ChoiceWidget.php | 4 +- src/Widgets/Input.php | 4 +- src/Widgets/Select.php | 4 +- src/Widgets/Widget.php | 8 ++-- tests/unit/Renderers/TwigRendererTest.php | 27 +++++++++++-- tests/unit/Renderers/templates/dummy.html | 1 - .../Renderers/templates/pack1/template.html | 1 + .../Renderers/templates/pack1/template2.html | 1 + .../Renderers/templates/pack2/template.html | 1 + tests/unit/Widgets/InputTest.php | 2 +- 16 files changed, 84 insertions(+), 52 deletions(-) delete mode 100644 tests/unit/Renderers/templates/dummy.html create mode 100644 tests/unit/Renderers/templates/pack1/template.html create mode 100644 tests/unit/Renderers/templates/pack1/template2.html create mode 100644 tests/unit/Renderers/templates/pack2/template.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e72030..434f997 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [1.1.0] - XXXX-XX-XX ### Added - Renderers to facilitate integrations of template-engines: - - Added integration to `twig/twig` through `TwigRenderer` class renderer (defined as default); + - Added `Renderer` interface; + - Added `TwigRenderer` that integrates `twig/twig`; + - Added fallback template loading support. - Template packs to facilitate customization and extensibility of templates: - - Added template pack `default` and `bootstrap4`. - - Support to configure default renderer and default template pack through `Config` singleton class; + - Added template pack `default` and defined as fallback; + - Added template pack `bootstrap4` that integrates custom elements of Bootstrap v4.0.0-beta.2. + - Added extra arg `label` on method `getContext` of `Widget` class; + - Support to configure renderer and template pack through `Config` singleton class; ### Changed - Class name `PHPFormConfig` to `Config` and moved to `src/` directory; diff --git a/src/Config.php b/src/Config.php index 19e5bb4..ab7c2d2 100644 --- a/src/Config.php +++ b/src/Config.php @@ -6,44 +6,41 @@ class Config extends Singleton { /** - * Template packs available + * @var string Directory with templates */ - const TEMPLATE_PACKS = array("default", "bootstrap4"); + protected $templates_dir = __DIR__ . '/templates/'; /** - * Directory with templates - * @var string + * @var string Default fallback template pack */ - protected $templates_dir = __DIR__ . '/templates/'; + protected $fallback_template_pack = "default"; /** - * Template pack defined - * @var string + * @var string Default template pack defined. If null, fallback is used. */ - protected $template_pack = "default"; + protected $template_pack = null; /** - * Renderer class used to render html content - * @var string + * @var string Renderer class used to render html content */ protected $renderer_class = TwigRenderer::class; /** - * Renderer instance based on $renderer_class. - * @var PHPForm\Renderers\Renderer + * @var PHPForm\Renderers\Renderer Renderer instance based on $renderer_class. */ private $renderer; /** * Templates path accordingly with defined template pack. + * * @return string */ - private function getTemplatesPath() + private function buildPath($template_pack) { - $path = $this->templates_dir . $this->template_pack . "/"; + $path = $this->templates_dir . $template_pack . "/"; if (!file_exists($path)) { - trigger_error("Template dir '$path' don't exists.", E_USER_ERROR); + trigger_error("Template pack dir '$path' don't exists.", E_USER_ERROR); } return $path; @@ -51,12 +48,16 @@ private function getTemplatesPath() /** * Return renderer class instantiated + * * @return PHPForm\Renderers\Renderer */ public function getRenderer() { if (is_null($this->renderer)) { - $this->renderer = new $this->renderer_class($this->getTemplatesPath()); + $fallback_pack_dir = $this->buildPath($this->fallback_template_pack); + $pack_dir = !is_null($this->template_pack) ? $this->buildPath($this->template_pack) : null; + + $this->renderer = new $this->renderer_class($fallback_pack_dir, $pack_dir); } return $this->renderer; @@ -64,15 +65,11 @@ public function getRenderer() /** * Define template pack + * * @param string */ public function setTemplatePack($template_pack) { - if (!in_array($template_pack, self::TEMPLATE_PACKS)) { - $packs = implode(", ", self::TEMPLATE_PACKS); - trigger_error("Template pack '$template_pack' not valid. Available packs are: $packs", E_USER_ERROR); - } - $this->template_pack = $template_pack; } } diff --git a/src/Fields/BoundField.php b/src/Fields/BoundField.php index ed23d24..887c59d 100644 --- a/src/Fields/BoundField.php +++ b/src/Fields/BoundField.php @@ -77,7 +77,7 @@ protected function asWidget($widget = null, array $attrs = array()) $attrs = $this->buildWidgetAttrs($attrs); - return $widget->render($this->html_name, $this->getValue(), $attrs); + return $widget->render($this->html_name, $this->getValue(), $this->label, $attrs); } public function labelTag($contents = null, array $attrs = array()) diff --git a/src/Renderers/Renderer.php b/src/Renderers/Renderer.php index e12ee5c..ed4a1ca 100644 --- a/src/Renderers/Renderer.php +++ b/src/Renderers/Renderer.php @@ -4,8 +4,9 @@ */ namespace PHPForm\Renderers; -abstract class Renderer +interface Renderer { - abstract public function getTemplate($template_name); - abstract public function render($template_name, $context); + public function __construct(string $fallback_templates_dir, string $templates_dir); + public function getTemplate(string $template_name); + public function render(string $template_name, array $context); } diff --git a/src/Renderers/TwigRenderer.php b/src/Renderers/TwigRenderer.php index d487ad0..5defe26 100644 --- a/src/Renderers/TwigRenderer.php +++ b/src/Renderers/TwigRenderer.php @@ -6,24 +6,31 @@ use Twig_Loader_Filesystem; use Twig_Environment; +use Twig_Loader_Chain; -class TwigRenderer extends Renderer +class TwigRenderer implements Renderer { - private $loader; private $twig; - public function __construct($templates_dir) + public function __construct(string $fallback_templates_dir, string $templates_dir = null) { - $this->loader = new Twig_Loader_Filesystem($templates_dir); - $this->twig = new Twig_Environment($this->loader); + $loaders = new Twig_Loader_Chain(); + + if (!is_null($templates_dir)) { + $loaders->addLoader(new Twig_Loader_Filesystem($templates_dir)); + } + + $loaders->addLoader(new Twig_Loader_Filesystem($fallback_templates_dir)); + + $this->twig = new Twig_Environment($loaders); } - public function getTemplate($template_name) + public function getTemplate(string $template_name) { return $this->twig->load($template_name); } - public function render($template_name, $context) + public function render(string $template_name, array $context) { $template = $this->getTemplate($template_name); return $template->render($context); diff --git a/src/Widgets/CheckboxInput.php b/src/Widgets/CheckboxInput.php index a430811..37e2f24 100644 --- a/src/Widgets/CheckboxInput.php +++ b/src/Widgets/CheckboxInput.php @@ -9,14 +9,14 @@ class CheckboxInput extends Input const TEMPLATE = 'checkbox.html'; const INPUT_TYPE = 'checkbox'; - public function getContext(string $name, $value, array $attrs = null) + public function getContext(string $name, $value, string $label = null, array $attrs = null) { if ($value) { $attrs = is_null($attrs) ? array() : $attrs; $attrs["checked"] = "checked"; } - return parent::getContext($name, $value, $attrs); + return parent::getContext($name, $value, $label, $attrs); } public function valueFromData($data, $files, string $name) diff --git a/src/Widgets/ChoiceWidget.php b/src/Widgets/ChoiceWidget.php index 3ee0e47..7e8d8ca 100644 --- a/src/Widgets/ChoiceWidget.php +++ b/src/Widgets/ChoiceWidget.php @@ -73,9 +73,9 @@ protected function buildName(string $name) * * @return array */ - protected function getContext(string $name, $value, array $attrs = null) + protected function getContext(string $name, $value, string $label = null, array $attrs = null) { - $context = parent::getContext($name, $value, $attrs); + $context = parent::getContext($name, $value, $label, $attrs); $context["name"] = $this->buildName($name); $context["options"] = $this->getOptions($name, $value, $attrs); diff --git a/src/Widgets/Input.php b/src/Widgets/Input.php index c4723ef..3f4add3 100644 --- a/src/Widgets/Input.php +++ b/src/Widgets/Input.php @@ -18,9 +18,9 @@ abstract class Input extends Widget * * @return array */ - public function getContext(string $name, $value, array $attrs = null) + public function getContext(string $name, $value, string $label = null, array $attrs = null) { - $context = parent::getContext($name, $value, $attrs); + $context = parent::getContext($name, $value, $label, $attrs); $context["type"] = static::INPUT_TYPE; diff --git a/src/Widgets/Select.php b/src/Widgets/Select.php index b8dc596..93dc67a 100644 --- a/src/Widgets/Select.php +++ b/src/Widgets/Select.php @@ -13,9 +13,9 @@ class Select extends ChoiceWidget protected $option_inherits_attrs = false; - public function getContext(string $name, $value, array $attrs = null) + public function getContext(string $name, $value, string $label = null, array $attrs = null) { - $context = parent::getContext($name, $value, $attrs); + $context = parent::getContext($name, $value, $label, $attrs); if ($this->allow_multiple_selected) { $context["attrs"]["multiple"] = "multiple"; diff --git a/src/Widgets/Widget.php b/src/Widgets/Widget.php index d904699..6c52b4a 100644 --- a/src/Widgets/Widget.php +++ b/src/Widgets/Widget.php @@ -35,11 +35,11 @@ public function __construct(array $attrs = null) * * @return string */ - public function render(string $name, $value, array $attrs = null) + public function render(string $name, $value, string $label = null, array $attrs = null) { $renderer = Config::getInstance()->getRenderer(); - $context = $this->getContext($name, $value, $attrs); + $context = $this->getContext($name, $value, $label, $attrs); return $renderer->render(static::TEMPLATE, $context); } @@ -53,7 +53,7 @@ public function render(string $name, $value, array $attrs = null) * * @return array */ - protected function getContext(string $name, $value, array $attrs = null) + protected function getContext(string $name, $value, string $label = null, array $attrs = null) { $value = $this->formatValue($value); $attrs = $this->buildAttrs($attrs); @@ -63,9 +63,11 @@ protected function getContext(string $name, $value, array $attrs = null) } return array( + "for" => $this->buildAutoId($name), "name" => $name, "attrs" => $attrs, "value" => $value, + "label" => $label, ); } diff --git a/tests/unit/Renderers/TwigRendererTest.php b/tests/unit/Renderers/TwigRendererTest.php index d3cb895..40f0c60 100644 --- a/tests/unit/Renderers/TwigRendererTest.php +++ b/tests/unit/Renderers/TwigRendererTest.php @@ -10,19 +10,38 @@ class TwigRendererTest extends TestCase { public function setUp() { - $this->renderer = new TwigRenderer(__DIR__ . "/templates"); + $this->renderer = new TwigRenderer(__DIR__ . "/templates/pack1", __DIR__ . "/templates/pack2"); } public function testGetTempate() { - $result = $this->renderer->getTemplate("dummy.html"); + $result = $this->renderer->getTemplate("template.html"); $this->assertInstanceOf(Twig_TemplateWrapper::class, $result); } public function testRender() { - $expected = "Dummy template test\n"; - $result = $this->renderer->render("dummy.html", array("name" => "test")); + $result = $this->renderer->render("template.html", array("name" => "test")); + $expected = "Pack2 template test"; + + $this->assertEquals($expected, $result); + } + + public function testRenderFallback() + { + $result = $this->renderer->render("template2.html", array("name" => "test")); + $expected = "Pack1 template2 test"; + + $this->assertEquals($expected, $result); + } + + public function testRenderWithOnlyFallbackDefined() + { + $renderer = new TwigRenderer(__DIR__ . "/templates/pack1"); + + $result = $renderer->render("template.html", array("name" => "test")); + $expected = "Pack1 template test"; + $this->assertEquals($expected, $result); } } diff --git a/tests/unit/Renderers/templates/dummy.html b/tests/unit/Renderers/templates/dummy.html deleted file mode 100644 index 2b895a4..0000000 --- a/tests/unit/Renderers/templates/dummy.html +++ /dev/null @@ -1 +0,0 @@ -Dummy template {{name}} diff --git a/tests/unit/Renderers/templates/pack1/template.html b/tests/unit/Renderers/templates/pack1/template.html new file mode 100644 index 0000000..c6ce9c9 --- /dev/null +++ b/tests/unit/Renderers/templates/pack1/template.html @@ -0,0 +1 @@ +Pack1 template {{name}} \ No newline at end of file diff --git a/tests/unit/Renderers/templates/pack1/template2.html b/tests/unit/Renderers/templates/pack1/template2.html new file mode 100644 index 0000000..1bebaa9 --- /dev/null +++ b/tests/unit/Renderers/templates/pack1/template2.html @@ -0,0 +1 @@ +Pack1 template2 {{name}} \ No newline at end of file diff --git a/tests/unit/Renderers/templates/pack2/template.html b/tests/unit/Renderers/templates/pack2/template.html new file mode 100644 index 0000000..47c930f --- /dev/null +++ b/tests/unit/Renderers/templates/pack2/template.html @@ -0,0 +1 @@ +Pack2 template {{name}} \ No newline at end of file diff --git a/tests/unit/Widgets/InputTest.php b/tests/unit/Widgets/InputTest.php index 400b961..30ab75f 100644 --- a/tests/unit/Widgets/InputTest.php +++ b/tests/unit/Widgets/InputTest.php @@ -33,7 +33,7 @@ public function testRenderWithoutValue() public function testRenderWithExtraAttrs() { $stub = $this->getMockForAbstractClass(Input::class); - $result = $stub->render("name", "value", array("class" => "input")); + $result = $stub->render("name", "value", null, array("class" => "input")); $expected = ''; $this->assertXmlStringEqualsXmlString($result, $expected); } From 388f3f47b84fa24c7946e6873eea409a5ee9a016 Mon Sep 17 00:00:00 2001 From: Sandro Rodrigues Date: Sat, 9 Dec 2017 06:36:32 +0000 Subject: [PATCH 3/4] Removed not used empty file --- src/templates/default/errors_list.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/templates/default/errors_list.html diff --git a/src/templates/default/errors_list.html b/src/templates/default/errors_list.html deleted file mode 100644 index e69de29..0000000 From 0b1cb2e699e796f73757aeca75bd4ba1d9fb5bc2 Mon Sep 17 00:00:00 2001 From: Sandro Rodrigues Date: Sat, 9 Dec 2017 06:37:09 +0000 Subject: [PATCH 4/4] Added bootstrap4 specific templates --- src/templates/bootstrap4/checkbox.html | 1 + src/templates/bootstrap4/file.html | 5 +++++ src/templates/bootstrap4/multiple_input.html | 5 +++++ src/templates/bootstrap4/multiple_input_option.html | 6 ++++++ 4 files changed, 17 insertions(+) create mode 100644 src/templates/bootstrap4/checkbox.html create mode 100644 src/templates/bootstrap4/file.html create mode 100644 src/templates/bootstrap4/multiple_input.html create mode 100644 src/templates/bootstrap4/multiple_input_option.html diff --git a/src/templates/bootstrap4/checkbox.html b/src/templates/bootstrap4/checkbox.html new file mode 100644 index 0000000..13a48ac --- /dev/null +++ b/src/templates/bootstrap4/checkbox.html @@ -0,0 +1 @@ +{% include 'multiple_input_option.html' %} \ No newline at end of file diff --git a/src/templates/bootstrap4/file.html b/src/templates/bootstrap4/file.html new file mode 100644 index 0000000..6969e76 --- /dev/null +++ b/src/templates/bootstrap4/file.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/src/templates/bootstrap4/multiple_input.html b/src/templates/bootstrap4/multiple_input.html new file mode 100644 index 0000000..933b66f --- /dev/null +++ b/src/templates/bootstrap4/multiple_input.html @@ -0,0 +1,5 @@ +
    + {% for option in options %} + {% include option.template with option only %} + {% endfor %} +
    \ No newline at end of file diff --git a/src/templates/bootstrap4/multiple_input_option.html b/src/templates/bootstrap4/multiple_input_option.html new file mode 100644 index 0000000..1fe14fc --- /dev/null +++ b/src/templates/bootstrap4/multiple_input_option.html @@ -0,0 +1,6 @@ + \ No newline at end of file