From dbfe02b4631c1ebe596a63aead35ac5ae46b39bc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 18:37:48 +0100 Subject: [PATCH 01/23] cs nullable typehints --- src/Forms/Container.php | 34 ++++++++++++--------- src/Forms/Controls/BaseControl.php | 2 +- src/Forms/Controls/CheckboxList.php | 2 +- src/Forms/Controls/ChoiceControl.php | 2 +- src/Forms/Controls/CsrfProtection.php | 2 +- src/Forms/Controls/ImageButton.php | 2 +- src/Forms/Controls/MultiChoiceControl.php | 2 +- src/Forms/Controls/MultiSelectBox.php | 2 +- src/Forms/Controls/RadioList.php | 2 +- src/Forms/Controls/SelectBox.php | 2 +- src/Forms/Controls/TextInput.php | 2 +- src/Forms/Form.php | 8 ++--- src/Forms/Helpers.php | 6 ++-- src/Forms/Rendering/DataClassGenerator.php | 2 +- src/Forms/Rendering/DefaultFormRenderer.php | 4 +-- src/Forms/Rules.php | 4 +-- tests/bootstrap.php | 2 +- 17 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/Forms/Container.php b/src/Forms/Container.php index 858ddf5df..46d5bfe50 100644 --- a/src/Forms/Container.php +++ b/src/Forms/Container.php @@ -110,7 +110,7 @@ public function setValues($data, bool $erase = false) * @param Control[]|null $controls * @return object|array */ - public function getValues($returnType = null, array $controls = null) + public function getValues($returnType = null, ?array $controls = null) { $form = $this->getForm(false); if ($form && ($submitter = $form->isSubmitted())) { @@ -137,7 +137,7 @@ public function getValues($returnType = null, array $controls = null) * @param Control[]|null $controls * @return object|array */ - public function getUnsafeValues($returnType, array $controls = null) + public function getUnsafeValues($returnType, ?array $controls = null) { if (is_object($returnType)) { $obj = $returnType; @@ -217,7 +217,7 @@ public function isValid(): bool * Performs the server side validation. * @param Control[]|null $controls */ - public function validate(array $controls = null): void + public function validate(?array $controls = null): void { $this->validated = null; foreach ($controls ?? $this->getComponents() as $control) { @@ -257,7 +257,7 @@ public function getErrors(): array /** @return static */ - public function setCurrentGroup(ControlGroup $group = null) + public function setCurrentGroup(?ControlGroup $group = null) { $this->currentGroup = $group; return $this; @@ -278,7 +278,11 @@ public function getCurrentGroup(): ?ControlGroup * @return static * @throws Nette\InvalidStateException */ - public function addComponent(Nette\ComponentModel\IComponent $component, ?string $name, string $insertBefore = null) + public function addComponent( + Nette\ComponentModel\IComponent $component, + ?string $name, + ?string $insertBefore = null + ) { parent::addComponent($component, $name, $insertBefore); if ($this->currentGroup !== null) { @@ -314,7 +318,7 @@ public function getForm(bool $throw = true): ?Form * Adds single-line text input control to the form. * @param string|object $label */ - public function addText(string $name, $label = null, int $cols = null, int $maxLength = null): Controls\TextInput + public function addText(string $name, $label = null, ?int $cols = null, ?int $maxLength = null): Controls\TextInput { return $this[$name] = (new Controls\TextInput($label, $maxLength)) ->setHtmlAttribute('size', $cols); @@ -328,8 +332,8 @@ public function addText(string $name, $label = null, int $cols = null, int $maxL public function addPassword( string $name, $label = null, - int $cols = null, - int $maxLength = null + ?int $cols = null, + ?int $maxLength = null ): Controls\TextInput { return $this[$name] = (new Controls\TextInput($label, $maxLength)) ->setHtmlAttribute('size', $cols) @@ -341,7 +345,7 @@ public function addPassword( * Adds multi-line text input control to the form. * @param string|object $label */ - public function addTextArea(string $name, $label = null, int $cols = null, int $rows = null): Controls\TextArea + public function addTextArea(string $name, $label = null, ?int $cols = null, ?int $rows = null): Controls\TextArea { return $this[$name] = (new Controls\TextArea($label)) ->setHtmlAttribute('cols', $cols)->setHtmlAttribute('rows', $rows); @@ -415,7 +419,7 @@ public function addCheckbox(string $name, $caption = null): Controls\Checkbox * Adds set of radio button controls to the form. * @param string|object $label */ - public function addRadioList(string $name, $label = null, array $items = null): Controls\RadioList + public function addRadioList(string $name, $label = null, ?array $items = null): Controls\RadioList { return $this[$name] = new Controls\RadioList($label, $items); } @@ -425,7 +429,7 @@ public function addRadioList(string $name, $label = null, array $items = null): * Adds set of checkbox controls to the form. * @param string|object $label */ - public function addCheckboxList(string $name, $label = null, array $items = null): Controls\CheckboxList + public function addCheckboxList(string $name, $label = null, ?array $items = null): Controls\CheckboxList { return $this[$name] = new Controls\CheckboxList($label, $items); } @@ -435,7 +439,7 @@ public function addCheckboxList(string $name, $label = null, array $items = null * Adds select box control that allows single item selection. * @param string|object $label */ - public function addSelect(string $name, $label = null, array $items = null, int $size = null): Controls\SelectBox + public function addSelect(string $name, $label = null, ?array $items = null, ?int $size = null): Controls\SelectBox { return $this[$name] = (new Controls\SelectBox($label, $items)) ->setHtmlAttribute('size', $size > 1 ? $size : null); @@ -449,8 +453,8 @@ public function addSelect(string $name, $label = null, array $items = null, int public function addMultiSelect( string $name, $label = null, - array $items = null, - int $size = null + ?array $items = null, + ?int $size = null ): Controls\MultiSelectBox { return $this[$name] = (new Controls\MultiSelectBox($label, $items)) ->setHtmlAttribute('size', $size > 1 ? $size : null); @@ -482,7 +486,7 @@ public function addButton(string $name, $caption = null): Controls\Button * @param string $src URI of the image * @param string $alt alternate text for the image */ - public function addImageButton(string $name, string $src = null, string $alt = null): Controls\ImageButton + public function addImageButton(string $name, ?string $src = null, ?string $alt = null): Controls\ImageButton { return $this[$name] = new Controls\ImageButton($src, $alt); } diff --git a/src/Forms/Controls/BaseControl.php b/src/Forms/Controls/BaseControl.php index 7a7f9a3d4..41defb7fa 100644 --- a/src/Forms/Controls/BaseControl.php +++ b/src/Forms/Controls/BaseControl.php @@ -134,7 +134,7 @@ public function loadHttpData(): void * Loads HTTP data. * @return mixed */ - protected function getHttpData($type, string $htmlTail = null) + protected function getHttpData($type, ?string $htmlTail = null) { return $this->getForm()->getHttpData($type, $this->getHtmlName() . $htmlTail); } diff --git a/src/Forms/Controls/CheckboxList.php b/src/Forms/Controls/CheckboxList.php index c933ca26c..dd058db80 100644 --- a/src/Forms/Controls/CheckboxList.php +++ b/src/Forms/Controls/CheckboxList.php @@ -35,7 +35,7 @@ class CheckboxList extends MultiChoiceControl /** * @param string|object $label */ - public function __construct($label = null, array $items = null) + public function __construct($label = null, ?array $items = null) { parent::__construct($label, $items); $this->control->type = 'checkbox'; diff --git a/src/Forms/Controls/ChoiceControl.php b/src/Forms/Controls/ChoiceControl.php index 6e890f0e3..c9b652f39 100644 --- a/src/Forms/Controls/ChoiceControl.php +++ b/src/Forms/Controls/ChoiceControl.php @@ -27,7 +27,7 @@ abstract class ChoiceControl extends BaseControl private $items = []; - public function __construct($label = null, array $items = null) + public function __construct($label = null, ?array $items = null) { parent::__construct($label); if ($items !== null) { diff --git a/src/Forms/Controls/CsrfProtection.php b/src/Forms/Controls/CsrfProtection.php index a2db72c9a..1c4bf2ac5 100644 --- a/src/Forms/Controls/CsrfProtection.php +++ b/src/Forms/Controls/CsrfProtection.php @@ -81,7 +81,7 @@ public function getToken(): string } - private function generateToken(string $random = null): string + private function generateToken(?string $random = null): string { if ($random === null) { $random = Nette\Utils\Random::generate(10); diff --git a/src/Forms/Controls/ImageButton.php b/src/Forms/Controls/ImageButton.php index d326e9c18..035fcbe0b 100644 --- a/src/Forms/Controls/ImageButton.php +++ b/src/Forms/Controls/ImageButton.php @@ -19,7 +19,7 @@ class ImageButton extends SubmitButton * @param string $src URI of the image * @param string $alt alternate text for the image */ - public function __construct(string $src = null, string $alt = null) + public function __construct(?string $src = null, ?string $alt = null) { parent::__construct(); $this->control->type = 'image'; diff --git a/src/Forms/Controls/MultiChoiceControl.php b/src/Forms/Controls/MultiChoiceControl.php index 5a223d026..2d0d71878 100644 --- a/src/Forms/Controls/MultiChoiceControl.php +++ b/src/Forms/Controls/MultiChoiceControl.php @@ -27,7 +27,7 @@ abstract class MultiChoiceControl extends BaseControl private $items = []; - public function __construct($label = null, array $items = null) + public function __construct($label = null, ?array $items = null) { parent::__construct($label); if ($items !== null) { diff --git a/src/Forms/Controls/MultiSelectBox.php b/src/Forms/Controls/MultiSelectBox.php index 5bcfc64bc..c154de1f4 100644 --- a/src/Forms/Controls/MultiSelectBox.php +++ b/src/Forms/Controls/MultiSelectBox.php @@ -24,7 +24,7 @@ class MultiSelectBox extends MultiChoiceControl private $optionAttributes = []; - public function __construct($label = null, array $items = null) + public function __construct($label = null, ?array $items = null) { parent::__construct($label, $items); $this->setOption('type', 'select'); diff --git a/src/Forms/Controls/RadioList.php b/src/Forms/Controls/RadioList.php index 233f40ae1..5a9282b0b 100644 --- a/src/Forms/Controls/RadioList.php +++ b/src/Forms/Controls/RadioList.php @@ -38,7 +38,7 @@ class RadioList extends ChoiceControl /** * @param string|object $label */ - public function __construct($label = null, array $items = null) + public function __construct($label = null, ?array $items = null) { parent::__construct($label, $items); $this->control->type = 'radio'; diff --git a/src/Forms/Controls/SelectBox.php b/src/Forms/Controls/SelectBox.php index a33c9ac28..8d69f6e38 100644 --- a/src/Forms/Controls/SelectBox.php +++ b/src/Forms/Controls/SelectBox.php @@ -30,7 +30,7 @@ class SelectBox extends ChoiceControl private $optionAttributes = []; - public function __construct($label = null, array $items = null) + public function __construct($label = null, ?array $items = null) { parent::__construct($label, $items); $this->setOption('type', 'select'); diff --git a/src/Forms/Controls/TextInput.php b/src/Forms/Controls/TextInput.php index 7b16dfeb9..604d52541 100644 --- a/src/Forms/Controls/TextInput.php +++ b/src/Forms/Controls/TextInput.php @@ -21,7 +21,7 @@ class TextInput extends TextBase /** * @param string|object $label */ - public function __construct($label = null, int $maxLength = null) + public function __construct($label = null, ?int $maxLength = null) { parent::__construct($label); $this->control->maxlength = $maxLength; diff --git a/src/Forms/Form.php b/src/Forms/Form.php index 45587a9cf..070e486b3 100644 --- a/src/Forms/Form.php +++ b/src/Forms/Form.php @@ -131,7 +131,7 @@ class Form extends Container implements Nette\HtmlStringable private $beforeRenderCalled; - public function __construct(string $name = null) + public function __construct(?string $name = null) { if ($name !== null) { $this->getElementPrototype()->id = 'frm-' . $name; @@ -235,7 +235,7 @@ public function allowCrossOrigin(): void /** * Cross-Site Request Forgery (CSRF) form protection. */ - public function addProtection(string $errorMessage = null): Controls\CsrfProtection + public function addProtection(?string $errorMessage = null): Controls\CsrfProtection { $control = new Controls\CsrfProtection($errorMessage); $this->addComponent($control, self::PROTECTOR_ID, key((array) $this->getComponents())); @@ -382,7 +382,7 @@ public function setSubmittedBy(?SubmitterControl $by) * Returns submitted HTTP data. * @return mixed */ - public function getHttpData(int $type = null, string $htmlName = null) + public function getHttpData(?int $type = null, ?string $htmlName = null) { if ($this->httpData === null) { if (!$this->isAnchored()) { @@ -514,7 +514,7 @@ protected function receiveHttpData(): ?array /********************* validation ****************d*g**/ - public function validate(array $controls = null): void + public function validate(?array $controls = null): void { $this->cleanErrors(); if ($controls === null && $this->submittedBy instanceof SubmitterControl) { diff --git a/src/Forms/Helpers.php b/src/Forms/Helpers.php index a1c899b18..59c42e9cf 100644 --- a/src/Forms/Helpers.php +++ b/src/Forms/Helpers.php @@ -160,8 +160,8 @@ public static function exportRules(Rules $rules): array public static function createInputList( array $items, - array $inputAttrs = null, - array $labelAttrs = null, + ?array $inputAttrs = null, + ?array $labelAttrs = null, $wrapper = null ): string { [$inputAttrs, $inputTag] = self::prepareAttrs($inputAttrs, 'input'); @@ -193,7 +193,7 @@ public static function createInputList( } - public static function createSelectBox(array $items, array $optionAttrs = null, $selected = null): Html + public static function createSelectBox(array $items, ?array $optionAttrs = null, $selected = null): Html { if ($selected !== null) { $optionAttrs['selected?'] = $selected; diff --git a/src/Forms/Rendering/DataClassGenerator.php b/src/Forms/Rendering/DataClassGenerator.php index fdb6358d4..0c56a63eb 100644 --- a/src/Forms/Rendering/DataClassGenerator.php +++ b/src/Forms/Rendering/DataClassGenerator.php @@ -29,7 +29,7 @@ final class DataClassGenerator public $useSmartObject = true; - public function generateCode(Form $form, string $baseName = null): string + public function generateCode(Form $form, ?string $baseName = null): string { $baseName = $baseName ?? preg_replace('~Form$~', '', ucwords((string) $form->getName())); return $this->processContainer($form, $baseName); diff --git a/src/Forms/Rendering/DefaultFormRenderer.php b/src/Forms/Rendering/DefaultFormRenderer.php index 6be8ea4cd..8ee515106 100644 --- a/src/Forms/Rendering/DefaultFormRenderer.php +++ b/src/Forms/Rendering/DefaultFormRenderer.php @@ -129,7 +129,7 @@ class DefaultFormRenderer implements Nette\Forms\FormRenderer * Provides complete form rendering. * @param string $mode 'begin', 'errors', 'ownerrors', 'body', 'end' or empty to render all */ - public function render(Nette\Forms\Form $form, string $mode = null): string + public function render(Nette\Forms\Form $form, ?string $mode = null): string { if ($this->form !== $form) { $this->form = $form; @@ -220,7 +220,7 @@ public function renderEnd(): string /** * Renders validation errors (per form or per control). */ - public function renderErrors(Nette\Forms\Control $control = null, bool $own = true): string + public function renderErrors(?Nette\Forms\Control $control = null, bool $own = true): string { $errors = $control ? $control->getErrors() diff --git a/src/Forms/Rules.php b/src/Forms/Rules.php index bee4ae63b..f41bbbb66 100644 --- a/src/Forms/Rules.php +++ b/src/Forms/Rules.php @@ -221,7 +221,7 @@ public function getToggles(bool $actual = false): array /** @internal */ - public function getToggleStates(array $toggles = [], bool $success = true, bool $emptyOptional = null): array + public function getToggleStates(array $toggles = [], bool $success = true, ?bool $emptyOptional = null): array { foreach ($this->toggles as $id => $hide) { $toggles[$id] = ($success xor !$hide) || !empty($toggles[$id]); @@ -247,7 +247,7 @@ public function getToggleStates(array $toggles = [], bool $success = true, bool /** * Validates against ruleset. */ - public function validate(bool $emptyOptional = null): bool + public function validate(?bool $emptyOptional = null): bool { $emptyOptional = $emptyOptional ?? (!$this->isRequired() && !$this->control->isFilled()); foreach ($this as $rule) { diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 039552af1..abb911275 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -15,7 +15,7 @@ date_default_timezone_set('Europe/Prague'); -function before(Closure $function = null) +function before(?Closure $function = null) { static $val; if (!func_num_args()) { From 00b4e7bd9e6cf6bf0bff682938f152a8a1434334 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 7 Mar 2022 14:47:01 +0100 Subject: [PATCH 02/23] private constants are PascalCase --- src/Forms/Container.php | 12 ++++++------ src/Forms/Helpers.php | 4 ++-- src/Forms/Rules.php | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Forms/Container.php b/src/Forms/Container.php index 46d5bfe50..63792d678 100644 --- a/src/Forms/Container.php +++ b/src/Forms/Container.php @@ -24,7 +24,7 @@ class Container extends Nette\ComponentModel\Container implements \ArrayAccess { use Nette\ComponentModel\ArrayAccess; - private const ARRAY = 'array'; + private const Array = 'array'; /** * Occurs when the form was validated @@ -126,7 +126,7 @@ public function getValues($returnType = null, ?array $controls = null) } } - $returnType = $returnType === true ? self::ARRAY : $returnType; + $returnType = $returnType === true ? self::Array : $returnType; return $this->getUnsafeValues($returnType, $controls); } @@ -145,7 +145,7 @@ public function getUnsafeValues($returnType, ?array $controls = null) } else { $returnType = ($returnType ?? $this->mappedType ?? ArrayHash::class); - $rc = new \ReflectionClass($returnType === self::ARRAY ? \stdClass::class : $returnType); + $rc = new \ReflectionClass($returnType === self::Array ? \stdClass::class : $returnType); if ($rc->hasMethod('__construct') && $rc->getMethod('__construct')->getNumberOfRequiredParameters()) { $obj = new \stdClass; $useConstructor = true; @@ -165,8 +165,8 @@ public function getUnsafeValues($returnType, ?array $controls = null) $obj->$name = $control->getValue(); } elseif ($control instanceof self) { - $type = $returnType === self::ARRAY && !$control->mappedType - ? self::ARRAY + $type = $returnType === self::Array && !$control->mappedType + ? self::Array : ($rc->hasProperty($name) ? Nette\Utils\Reflection::getPropertyType($rc->getProperty($name)) : null); $obj->$name = $control->getUnsafeValues($type, $allowed ? null : $controls); } @@ -176,7 +176,7 @@ public function getUnsafeValues($returnType, ?array $controls = null) return new $returnType(...(array) $obj); } - return $returnType === self::ARRAY + return $returnType === self::Array ? (array) $obj : $obj; } diff --git a/src/Forms/Helpers.php b/src/Forms/Helpers.php index 59c42e9cf..0ad98f920 100644 --- a/src/Forms/Helpers.php +++ b/src/Forms/Helpers.php @@ -21,7 +21,7 @@ class Helpers { use Nette\StaticClass; - private const UNSAFE_NAMES = [ + private const UnsafeNames = [ 'attributes', 'children', 'elements', 'focus', 'length', 'reset', 'style', 'submit', 'onsubmit', 'form', 'presenter', 'action', ]; @@ -93,7 +93,7 @@ public static function generateHtmlName(string $id): string $name = substr_replace($name, '', strpos($name, ']'), 1) . ']'; } - if (is_numeric($name) || in_array($name, self::UNSAFE_NAMES, true)) { + if (is_numeric($name) || in_array($name, self::UnsafeNames, true)) { $name = '_' . $name; } diff --git a/src/Forms/Rules.php b/src/Forms/Rules.php index f41bbbb66..0f65656e3 100644 --- a/src/Forms/Rules.php +++ b/src/Forms/Rules.php @@ -19,7 +19,7 @@ class Rules implements \IteratorAggregate { use Nette\SmartObject; - private const NEG_RULES = [ + private const NegRules = [ Form::FILLED => Form::BLANK, Form::BLANK => Form::FILLED, ]; @@ -164,8 +164,8 @@ public function addConditionOn(Control $control, $validator, $arg = null) public function elseCondition() { $rule = clone end($this->parent->rules); - if (isset(self::NEG_RULES[$rule->validator])) { - $rule->validator = self::NEG_RULES[$rule->validator]; + if (isset(self::NegRules[$rule->validator])) { + $rule->validator = self::NegRules[$rule->validator]; } else { $rule->isNegative = !$rule->isNegative; } @@ -331,8 +331,8 @@ private function adjustOperation(Rule $rule): void trigger_error("Negative validation rules such as ~$name are deprecated.", E_USER_DEPRECATED); } - if (isset(self::NEG_RULES[$rule->validator])) { - $rule->validator = self::NEG_RULES[$rule->validator]; + if (isset(self::NegRules[$rule->validator])) { + $rule->validator = self::NegRules[$rule->validator]; $rule->isNegative = false; trigger_error('Replace negative validation rule ~Form::FILLED with Form::BLANK and vice versa.', E_USER_DEPRECATED); } From a55e327ca021356320060a84847cba618b5c483d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 29 Mar 2022 06:41:28 +0200 Subject: [PATCH 03/23] FormMacros: optimized code --- src/Bridges/FormsLatte/FormMacros.php | 45 ++++++++++++------- .../expected/FormMacros.formContainer.phtml | 4 +- .../expected/FormMacros.forms.phtml | 22 ++++----- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/Bridges/FormsLatte/FormMacros.php b/src/Bridges/FormsLatte/FormMacros.php index 3b125a8ff..b411d5bc8 100644 --- a/src/Bridges/FormsLatte/FormMacros.php +++ b/src/Bridges/FormsLatte/FormMacros.php @@ -68,8 +68,10 @@ public function macroForm(MacroNode $node, PhpWriter $writer) $node->tokenizer->reset(); return $writer->write( 'echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin($form = $this->global->formsStack[] = ' - . ($name[0] === '$' ? 'is_object(%node.word) ? %node.word : ' : '') - . '$this->global->uiControl[%node.word], %node.array)' + . ($name[0] === '$' + ? 'is_object($ʟ_tmp = %node.word) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]' + : '$this->global->uiControl[%node.word]') + . ', %node.array)' . " /* line $node->startLine */;" ); } @@ -96,8 +98,9 @@ public function macroFormContext(MacroNode $node, PhpWriter $writer) $node->tokenizer->reset(); return $writer->write( '$form = $this->global->formsStack[] = ' - . ($name[0] === '$' ? 'is_object(%node.word) ? %node.word : ' : '') - . '$this->global->uiControl[%node.word]' + . ($name[0] === '$' + ? 'is_object($ʟ_tmp = %node.word) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]' + : '$this->global->uiControl[%node.word]') . " /* line $node->startLine */;" ); } @@ -120,8 +123,9 @@ public function macroFormContainer(MacroNode $node, PhpWriter $writer) $node->tokenizer->reset(); return $writer->write( '$this->global->formsStack[] = $formContainer = ' - . ($name[0] === '$' ? 'is_object(%node.word) ? %node.word : ' : '') - . 'end($this->global->formsStack)[%node.word]' + . ($name[0] === '$' + ? 'is_object($ʟ_tmp = %node.word) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]' + : 'end($this->global->formsStack)[%node.word]') . " /* line $node->startLine */;" ); } @@ -145,7 +149,7 @@ public function macroLabel(MacroNode $node, PhpWriter $writer) $name = array_shift($words); return $writer->write( ($name[0] === '$' - ? '$ʟ_input = is_object(%0.word) ? %0.word : end($this->global->formsStack)[%0.word]; if ($ʟ_label = $ʟ_input' + ? '$ʟ_input = is_object($ʟ_tmp = %0.word) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; if ($ʟ_label = $ʟ_input' : 'if ($ʟ_label = end($this->global->formsStack)[%0.word]' ) . '->%1.raw) echo $ʟ_label' @@ -185,8 +189,10 @@ public function macroInput(MacroNode $node, PhpWriter $writer) $node->replaced = true; $name = array_shift($words); return $writer->write( - ($name[0] === '$' ? '$ʟ_input = $_input = is_object(%0.word) ? %0.word : end($this->global->formsStack)[%0.word]; echo $ʟ_input' : 'echo end($this->global->formsStack)[%0.word]') - . '->%1.raw' + ($name[0] === '$' + ? '$ʟ_input = $_input = is_object($ʟ_tmp = %word) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; echo $ʟ_input' + : 'echo end($this->global->formsStack)[%word]') + . '->%raw' . ($node->tokenizer->isNext() ? '->addAttributes(%node.array)' : '') . " /* line $node->startLine */;", $name, @@ -217,8 +223,10 @@ public function macroNameAttr(MacroNode $node, PhpWriter $writer) if ($tagName === 'form') { $node->openingCode = $writer->write( 'global->formsStack[] = ' - . ($name[0] === '$' ? 'is_object(%0.word) ? %0.word : ' : '') - . "\$this->global->uiControl[%0.word] /* line $node->startLine */; ?>", + . ($name[0] === '$' + ? 'is_object($ʟ_tmp = %0.word) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]' + : '$this->global->uiControl[%0.word]') + . " /* line $node->startLine */; ?>", $name ); return $writer->write( @@ -228,8 +236,11 @@ public function macroNameAttr(MacroNode $node, PhpWriter $writer) } else { $method = $tagName === 'label' ? 'getLabel' : 'getControl'; return $writer->write( - '$ʟ_input = $_input = ' . ($name[0] === '$' ? 'is_object(%0.word) ? %0.word : ' : '') - . 'end($this->global->formsStack)[%0.word]; echo $ʟ_input->%1.raw' + '$ʟ_input = $_input = ' + . ($name[0] === '$' + ? 'is_object($ʟ_tmp = %0.word) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]' + : 'end($this->global->formsStack)[%0.word]') + . '; echo $ʟ_input->%1.raw' . ($definedHtmlAttributes ? '->addAttributes(%2.var)' : '') . '->attributes()' . " /* line $node->startLine */;", $name, @@ -285,7 +296,7 @@ public function macroInputError(MacroNode $node, PhpWriter $writer) return $writer->write("echo %escape(\$ʟ_input->getError()) /* line $node->startLine */;"); } elseif ($name[0] === '$') { return $writer->write( - '$ʟ_input = is_object(%0.word) ? %0.word : end($this->global->formsStack)[%0.word];' + '$ʟ_input = is_object($ʟ_tmp = %0.word) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp];' . "echo %escape(\$ʟ_input->getError()) /* line $node->startLine */;", $name ); @@ -309,8 +320,10 @@ public function macroFormPrint(MacroNode $node, PhpWriter $writer) $node->tokenizer->reset(); return $writer->write( 'Nette\Bridges\FormsLatte\Runtime::render' . $node->name . '(' - . ($name[0] === '$' ? 'is_object(%node.word) ? %node.word : ' : '') - . '$this->global->uiControl[%node.word]); exit;' + . ($name[0] === '$' + ? 'is_object($ʟ_tmp = %node.word) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]' + : '$this->global->uiControl[%node.word]') + . '); exit;' ); } } diff --git a/tests/Forms.Latte/expected/FormMacros.formContainer.phtml b/tests/Forms.Latte/expected/FormMacros.formContainer.phtml index a296c5b42..02cb5b99f 100644 --- a/tests/Forms.Latte/expected/FormMacros.formContainer.phtml +++ b/tests/Forms.Latte/expected/FormMacros.formContainer.phtml @@ -39,7 +39,7 @@ $iterations = 0; foreach ($formContainer->controls AS $name => $field) /* line 19 */ { echo '
  • '; - $ʟ_input = $_input = is_object($field) ? $field : end($this->global->formsStack)[$field]; + $ʟ_input = $_input = is_object($ʟ_tmp = $field) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; echo $ʟ_input->getControl() /* line 20 */; echo '
  • '; @@ -71,7 +71,7 @@ $iterations = 0; foreach ($items as $item) /* line 34 */ { if (!isset($formContainer[$item])) /* line 35 */ continue; - $this->global->formsStack[] = $formContainer = is_object($item) ? $item : end($this->global->formsStack)[$item] /* line 36 */; + $this->global->formsStack[] = $formContainer = is_object($ʟ_tmp = $item) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp] /* line 36 */; echo ' '; echo end($this->global->formsStack)["input"]->getControl() /* line 37 */; echo "\n"; diff --git a/tests/Forms.Latte/expected/FormMacros.forms.phtml b/tests/Forms.Latte/expected/FormMacros.forms.phtml index 1cf2f425f..ffb93af75 100644 --- a/tests/Forms.Latte/expected/FormMacros.forms.phtml +++ b/tests/Forms.Latte/expected/FormMacros.forms.phtml @@ -5,41 +5,41 @@ $iterations = 0; foreach (['id', 'username', 'select', 'area', 'send'] as $name) /* line 2 */ { echo ' '; - $ʟ_input = is_object($name) ? $name : end($this->global->formsStack)[$name]; + $ʟ_input = is_object($ʟ_tmp = $name) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; if ($ʟ_label = $ʟ_input->getLabel()) echo $ʟ_label; echo ' '; - $ʟ_input = $_input = is_object($name) ? $name : end($this->global->formsStack)[$name]; + $ʟ_input = $_input = is_object($ʟ_tmp = $name) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; echo $ʟ_input->getControl()->addAttributes(['title' => 'Hello', 'size' => 10]) /* line 4 */; echo ' '; echo LR\Filters::escapeHtmlText($ʟ_input->getError()) /* line 5 */; echo ' '; - $ʟ_input = is_object($name) ? $name : end($this->global->formsStack)[$name]; + $ʟ_input = is_object($ʟ_tmp = $name) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; echo LR\Filters::escapeHtmlText($ʟ_input->getError()) /* line 6 */; echo '
    '; - $ʟ_input = is_object($form[$name]) ? $form[$name] : end($this->global->formsStack)[$form[$name]]; + $ʟ_input = is_object($ʟ_tmp = $form[$name]) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; if ($ʟ_label = $ʟ_input->getLabel()) echo $ʟ_label->addAttributes(['title' => 'hello'])->startTag(); echo ' '; - $ʟ_input = $_input = is_object($form[$name]) ? $form[$name] : end($this->global->formsStack)[$form[$name]]; + $ʟ_input = $_input = is_object($ʟ_tmp = $form[$name]) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; echo $ʟ_input->getControl()->addAttributes(['title' => 'Hello', 'size' => 10]) /* line 10 */; echo ' '; if ($ʟ_label) echo $ʟ_label->endTag(); echo ' '; - $ʟ_input = is_object($form[$name]) ? $form[$name] : end($this->global->formsStack)[$form[$name]]; + $ʟ_input = is_object($ʟ_tmp = $form[$name]) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; echo LR\Filters::escapeHtmlText($ʟ_input->getError()) /* line 11 */; echo "\n"; $iterations++; } echo ' '; - $ʟ_input = is_object($form['username']) ? $form['username'] : end($this->global->formsStack)[$form['username']]; + $ʟ_input = is_object($ʟ_tmp = $form['username']) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; if ($ʟ_label = $ʟ_input->getLabel()) echo $ʟ_label; echo ' @@ -53,17 +53,17 @@ echo '> global->formsStack)[$form['username']]; + $ʟ_input = $_input = is_object($ʟ_tmp = $form['username']) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; echo $ʟ_input->getLabelPart()->attributes() /* line 19 */; echo '> global->formsStack)[$form['username']]; + $ʟ_input = $_input = is_object($ʟ_tmp = $form['username']) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; echo $ʟ_input->getLabelPart()->attributes() /* line 20 */; echo '>'; echo $ʟ_input->getLabelPart()->getHtml() /* line 20 */; echo ' global->formsStack)[$form['username']]; + $ʟ_input = $_input = is_object($ʟ_tmp = $form['username']) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; echo $ʟ_input->getControlPart()->attributes() /* line 21 */; echo '> @@ -229,7 +229,7 @@ '; - $form = $this->global->formsStack[] = is_object($this->global->uiControl['myForm']) ? $this->global->uiControl['myForm'] : $this->global->uiControl[$this->global->uiControl['myForm']] /* line 68 */; + $form = $this->global->formsStack[] = is_object($ʟ_tmp = $this->global->uiControl['myForm']) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp] /* line 68 */; echo 'global->formsStack), [], false); echo '> From c2f1b4a74f57fd379178203633ff7c047b41f6d1 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 25 Mar 2022 15:41:44 +0100 Subject: [PATCH 04/23] added bridge for Latte 3 (beta 2) --- composer.json | 4 +- examples/latte.php | 4 +- src/Bridges/FormsLatte/FormMacros.php | 2 +- src/Bridges/FormsLatte/FormsExtension.php | 34 ++ .../FormsLatte/Nodes/FormContainerNode.php | 65 ++++ src/Bridges/FormsLatte/Nodes/FormNode.php | 92 ++++++ .../FormsLatte/Nodes/FormPrintNode.php | 63 ++++ .../FormsLatte/Nodes/InputErrorNode.php | 67 ++++ src/Bridges/FormsLatte/Nodes/InputNode.php | 73 +++++ src/Bridges/FormsLatte/Nodes/LabelNode.php | 92 ++++++ src/Bridges/FormsLatte/Nodes/NNameNode.php | 160 ++++++++++ src/Bridges/FormsLatte/Runtime.php | 10 +- tests/Forms.Latte/Runtime.get.phpt | 6 +- .../Runtime.renderFormClassPrint.phpt | 1 - .../Forms.Latte/Runtime.renderFormPrint.phpt | 1 - ...rmMacros.button.html => forms.button.html} | 0 tests/Forms.Latte/expected/forms.button.phtml | 29 ++ ...ontainer.html => forms.formContainer.html} | 0 .../expected/forms.formContainer.phtml | 105 ++++++ .../{FormMacros.get.html => forms.get.html} | 0 tests/Forms.Latte/expected/forms.get.phtml | 17 + tests/Forms.Latte/expected/forms.html | 126 ++++++++ tests/Forms.Latte/expected/forms.phtml | 300 ++++++++++++++++++ tests/Forms.Latte/forms.button.phpt | 31 ++ tests/Forms.Latte/forms.error.phpt | 51 +++ tests/Forms.Latte/forms.formContainer.phpt | 51 +++ tests/Forms.Latte/forms.get.phpt | 32 ++ tests/Forms.Latte/forms.phpt | 57 ++++ .../templates/forms.formContainer.latte | 2 +- tests/Forms.Latte/templates/forms.latte | 3 +- .../FormMacros.button.phpt | 5 +- .../FormMacros.error.phpt | 5 +- .../FormMacros.formContainer.phpt | 5 +- .../FormMacros.forms.phpt | 5 +- .../FormMacros.get.phpt | 5 +- .../expected/FormMacros.button.html | 10 + .../expected/FormMacros.button.phtml | 0 .../expected/FormMacros.formContainer.html | 41 +++ .../expected/FormMacros.formContainer.phtml | 0 .../expected/FormMacros.forms.html | 0 .../expected/FormMacros.forms.phtml | 0 .../Forms.Latte2/expected/FormMacros.get.html | 7 + .../expected/FormMacros.get.phtml | 0 .../Forms.Latte2/templates/forms.button.latte | 9 + .../templates/forms.formContainer.latte | 48 +++ tests/Forms.Latte2/templates/forms.get.latte | 4 + tests/Forms.Latte2/templates/forms.latte | 86 +++++ 47 files changed, 1685 insertions(+), 23 deletions(-) create mode 100644 src/Bridges/FormsLatte/FormsExtension.php create mode 100644 src/Bridges/FormsLatte/Nodes/FormContainerNode.php create mode 100644 src/Bridges/FormsLatte/Nodes/FormNode.php create mode 100644 src/Bridges/FormsLatte/Nodes/FormPrintNode.php create mode 100644 src/Bridges/FormsLatte/Nodes/InputErrorNode.php create mode 100644 src/Bridges/FormsLatte/Nodes/InputNode.php create mode 100644 src/Bridges/FormsLatte/Nodes/LabelNode.php create mode 100644 src/Bridges/FormsLatte/Nodes/NNameNode.php rename tests/Forms.Latte/expected/{FormMacros.button.html => forms.button.html} (100%) create mode 100644 tests/Forms.Latte/expected/forms.button.phtml rename tests/Forms.Latte/expected/{FormMacros.formContainer.html => forms.formContainer.html} (100%) create mode 100644 tests/Forms.Latte/expected/forms.formContainer.phtml rename tests/Forms.Latte/expected/{FormMacros.get.html => forms.get.html} (100%) create mode 100644 tests/Forms.Latte/expected/forms.get.phtml create mode 100644 tests/Forms.Latte/expected/forms.html create mode 100644 tests/Forms.Latte/expected/forms.phtml create mode 100644 tests/Forms.Latte/forms.button.phpt create mode 100644 tests/Forms.Latte/forms.error.phpt create mode 100644 tests/Forms.Latte/forms.formContainer.phpt create mode 100644 tests/Forms.Latte/forms.get.phpt create mode 100644 tests/Forms.Latte/forms.phpt rename tests/{Forms.Latte => Forms.Latte2}/FormMacros.button.phpt (85%) rename tests/{Forms.Latte => Forms.Latte2}/FormMacros.error.phpt (93%) rename tests/{Forms.Latte => Forms.Latte2}/FormMacros.formContainer.phpt (92%) rename tests/{Forms.Latte => Forms.Latte2}/FormMacros.forms.phpt (93%) rename tests/{Forms.Latte => Forms.Latte2}/FormMacros.get.phpt (86%) create mode 100644 tests/Forms.Latte2/expected/FormMacros.button.html rename tests/{Forms.Latte => Forms.Latte2}/expected/FormMacros.button.phtml (100%) create mode 100644 tests/Forms.Latte2/expected/FormMacros.formContainer.html rename tests/{Forms.Latte => Forms.Latte2}/expected/FormMacros.formContainer.phtml (100%) rename tests/{Forms.Latte => Forms.Latte2}/expected/FormMacros.forms.html (100%) rename tests/{Forms.Latte => Forms.Latte2}/expected/FormMacros.forms.phtml (100%) create mode 100644 tests/Forms.Latte2/expected/FormMacros.get.html rename tests/{Forms.Latte => Forms.Latte2}/expected/FormMacros.get.phtml (100%) create mode 100644 tests/Forms.Latte2/templates/forms.button.latte create mode 100644 tests/Forms.Latte2/templates/forms.formContainer.latte create mode 100644 tests/Forms.Latte2/templates/forms.get.latte create mode 100644 tests/Forms.Latte2/templates/forms.latte diff --git a/composer.json b/composer.json index 9ae31e55f..a0130b8f9 100644 --- a/composer.json +++ b/composer.json @@ -24,13 +24,13 @@ "nette/application": "^3.0", "nette/di": "^3.0", "nette/tester": "^2.0", - "latte/latte": "^2.10.2", + "latte/latte": "^2.10.2 || ^3.0", "tracy/tracy": "^2.4", "phpstan/phpstan-nette": "^0.12" }, "conflict": { "nette/di": "<3.0-stable", - "latte/latte": ">=3.0" + "latte/latte": ">=3.1" }, "autoload": { "classmap": ["src/"] diff --git a/examples/latte.php b/examples/latte.php index 32bc421d4..3995b8441 100644 --- a/examples/latte.php +++ b/examples/latte.php @@ -39,8 +39,6 @@ } $latte = new Latte\Engine; -$latte->onCompile[] = function ($latte) { - Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler()); -}; +$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension); $latte->render(__DIR__ . '/latte/page.latte', ['form' => $form]); diff --git a/src/Bridges/FormsLatte/FormMacros.php b/src/Bridges/FormsLatte/FormMacros.php index b411d5bc8..f2d7fbdb7 100644 --- a/src/Bridges/FormsLatte/FormMacros.php +++ b/src/Bridges/FormsLatte/FormMacros.php @@ -17,7 +17,7 @@ /** - * Latte macros for Nette\Forms. + * Latte v2 macros for Nette\Forms. * * - {form name} ... {/form} * - {input name} diff --git a/src/Bridges/FormsLatte/FormsExtension.php b/src/Bridges/FormsLatte/FormsExtension.php new file mode 100644 index 000000000..71fd693d1 --- /dev/null +++ b/src/Bridges/FormsLatte/FormsExtension.php @@ -0,0 +1,34 @@ + [Nodes\FormNode::class, 'create'], + 'formContext' => [Nodes\FormNode::class, 'create'], + 'formContainer' => [Nodes\FormContainerNode::class, 'create'], + 'label' => [Nodes\LabelNode::class, 'create'], + 'input' => [Nodes\InputNode::class, 'create'], + 'inputError' => [Nodes\InputErrorNode::class, 'create'], + 'formPrint' => [Nodes\FormPrintNode::class, 'create'], + 'formClassPrint' => [Nodes\FormPrintNode::class, 'create'], + 'n:name' => [Nodes\NNameNode::class, 'create'], + ]; + } +} diff --git a/src/Bridges/FormsLatte/Nodes/FormContainerNode.php b/src/Bridges/FormsLatte/Nodes/FormContainerNode.php new file mode 100644 index 000000000..287c32419 --- /dev/null +++ b/src/Bridges/FormsLatte/Nodes/FormContainerNode.php @@ -0,0 +1,65 @@ + */ + public static function create(Tag $tag): \Generator + { + $tag->outputMode = $tag::OutputRemoveIndentation; + $tag->expectArguments(); + + $node = new static; + $node->name = $tag->parser->parseUnquotedStringOrExpression(); + [$node->content] = yield; + return $node; + } + + + public function print(PrintContext $context): string + { + return $context->format( + '$this->global->formsStack[] = $formContainer = ' + . ($this->name instanceof StringNode + ? 'end($this->global->formsStack)[%raw]' + : 'is_object($ʟ_tmp = %raw) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]') + . '%line;' + . '%raw' + . 'array_pop($this->global->formsStack); $formContainer = end($this->global->formsStack);' + . "\n\n", + $this->name, + $this->position, + $this->content, + ); + } + + + public function &getIterator(): \Generator + { + yield $this->name; + yield $this->content; + } +} diff --git a/src/Bridges/FormsLatte/Nodes/FormNode.php b/src/Bridges/FormsLatte/Nodes/FormNode.php new file mode 100644 index 000000000..b739278a0 --- /dev/null +++ b/src/Bridges/FormsLatte/Nodes/FormNode.php @@ -0,0 +1,92 @@ + */ + public static function create(Tag $tag): \Generator + { + if ($tag->isNAttribute()) { + throw new CompileException('Did you mean
    ?', $tag->position); + } + + $tag->outputMode = $tag::OutputKeepIndentation; + $tag->expectArguments(); + $node = new static; + $node->name = $tag->parser->parseUnquotedStringOrExpression(); + $tag->parser->stream->tryConsume(','); + $node->attributes = $tag->parser->parseArguments(); + $node->print = $tag->name === 'form'; + + [$node->content, $endTag] = yield; + $node->endLine = $endTag?->position; + if ($endTag && $node->name instanceof StringNode) { + $endTag->parser->stream->tryConsume($node->name->value); + } + + return $node; + } + + + public function print(PrintContext $context): string + { + return $context->format( + '$form = $this->global->formsStack[] = ' + . ($this->name instanceof StringNode + ? '$this->global->uiControl[%raw]' + : 'is_object($ʟ_tmp = %raw) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]') + . ' %2.line;' + . ($this->print + ? 'echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin($form, %raw) %2.line;' + : '') + . ' %3.raw' + . ($this->print + ? 'echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack))' + : 'array_pop($this->global->formsStack)') + . " %4.line;\n\n", + $this->name, + $this->attributes, + $this->position, + $this->content, + $this->endLine, + ); + } + + + public function &getIterator(): \Generator + { + yield $this->name; + yield $this->attributes; + yield $this->content; + } +} diff --git a/src/Bridges/FormsLatte/Nodes/FormPrintNode.php b/src/Bridges/FormsLatte/Nodes/FormPrintNode.php new file mode 100644 index 000000000..0e3683487 --- /dev/null +++ b/src/Bridges/FormsLatte/Nodes/FormPrintNode.php @@ -0,0 +1,63 @@ +name = $tag->parser->isEnd() + ? null + : $tag->parser->parseUnquotedStringOrExpression(); + $node->mode = $tag->name; + return $node; + } + + + public function print(PrintContext $context): string + { + return $context->format( + 'Nette\Bridges\FormsLatte\Runtime::render%raw(' + . match (true) { + !$this->name => 'end($this->global->formsStack)', + $this->name instanceof StringNode => '$this->global->uiControl[%raw]', + default => 'is_object($ʟ_tmp = %raw) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]', + } + . ') %line; exit;', + $this->mode, + $this->name, + $this->position, + ); + } + + + public function &getIterator(): \Generator + { + if ($this->name) { + yield $this->name; + } + } +} diff --git a/src/Bridges/FormsLatte/Nodes/InputErrorNode.php b/src/Bridges/FormsLatte/Nodes/InputErrorNode.php new file mode 100644 index 000000000..f5e00eeec --- /dev/null +++ b/src/Bridges/FormsLatte/Nodes/InputErrorNode.php @@ -0,0 +1,67 @@ +outputMode = $tag::OutputKeepIndentation; + $node = new static; + $node->name = $tag->parser->isEnd() + ? null + : $tag->parser->parseUnquotedStringOrExpression(); + return $node; + } + + + public function print(PrintContext $context): string + { + if (!$this->name) { + return $context->format('echo %escape($ʟ_input->getError()) %line;', $this->position); + + } elseif ($this->name instanceof StringNode) { + return $context->format( + 'echo %escape(end($this->global->formsStack)[%raw]->getError()) %line;', + $this->name, + $this->position, + ); + + } else { + return $context->format( + '$ʟ_input = is_object($ʟ_tmp = %raw) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp];' + . 'echo %escape($ʟ_input->getError()) %line;', + $this->name, + $this->position, + ); + } + } + + + public function &getIterator(): \Generator + { + if ($this->name) { + yield $this->name; + } + } +} diff --git a/src/Bridges/FormsLatte/Nodes/InputNode.php b/src/Bridges/FormsLatte/Nodes/InputNode.php new file mode 100644 index 000000000..7efce8e45 --- /dev/null +++ b/src/Bridges/FormsLatte/Nodes/InputNode.php @@ -0,0 +1,73 @@ +outputMode = $tag::OutputKeepIndentation; + $tag->expectArguments(); + + $node = new static; + $node->name = $tag->parser->parseUnquotedStringOrExpression(colon: false); + if ($tag->parser->stream->tryConsume(':') && !$tag->parser->stream->is(',')) { + $node->part = $tag->parser->isEnd() + ? new StringNode('') + : $tag->parser->parseUnquotedStringOrExpression(); + } + $tag->parser->stream->tryConsume(','); + $node->attributes = $tag->parser->parseArguments(); + return $node; + } + + + public function print(PrintContext $context): string + { + return $context->format( + ($this->name instanceof StringNode + ? 'echo end($this->global->formsStack)[%raw]->' + : '$ʟ_input = is_object($ʟ_tmp = %raw) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; echo $ʟ_input->') + . ($this->part ? ('getControlPart(%raw)') : 'getControl()') + . ($this->attributes->items ? '->addAttributes(%2.raw)' : '') + . ' %3.line;', + $this->name, + $this->part, + $this->attributes, + $this->position, + ); + } + + + public function &getIterator(): \Generator + { + yield $this->name; + if ($this->part) { + yield $this->part; + } + yield $this->attributes; + } +} diff --git a/src/Bridges/FormsLatte/Nodes/LabelNode.php b/src/Bridges/FormsLatte/Nodes/LabelNode.php new file mode 100644 index 000000000..81d5bb287 --- /dev/null +++ b/src/Bridges/FormsLatte/Nodes/LabelNode.php @@ -0,0 +1,92 @@ + */ + public static function create(Tag $tag): \Generator + { + if ($tag->isNAttribute()) { + throw new CompileException('Did you mean
    + global->formsStack)['username']; + echo $ʟ_input->getControlPart()->addAttributes(['value' => null, 'type' => null, 'class' => null])->attributes() /* line %d% */; + echo '> + + global->formsStack)[$ʟ_tmp]; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '> + global->formsStack)[$ʟ_tmp]; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' + global->formsStack)[$ʟ_tmp]; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '> + + '; + if ($ʟ_label = end($this->global->formsStack)['my']->getLabel()) echo $ʟ_label /* line %d% */; + echo end($this->global->formsStack)['my']->getControl() /* line %d% */; + echo "\n"; + echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack)) /* line %d% */; + + echo ' + + +'; + $form = $this->global->formsStack[] = $this->global->uiControl['myForm'] /* line %d% */; + echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin($form, []) /* line %d% */; + echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack)) /* line %d% */; + + echo ' + +'; + $form = $this->global->formsStack[] = $this->global->uiControl['myForm'] /* line %d% */; + echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin($form, []) /* line %d% */; + echo "\n"; + $iterations = 0; + foreach ($form['sex']->items as $key => $label) /* line %d% */ { + echo ' '; + if ($ʟ_label = end($this->global->formsStack)['sex']->getLabelPart($key)) echo $ʟ_label->startTag() /* line %d% */; + echo ' '; + echo end($this->global->formsStack)['sex']->getControlPart($key) /* line %d% */; + echo ' '; + echo LR\Filters::escapeHtmlText($label) /* line %d% */; + if ($ʟ_label) echo $ʟ_label->endTag() /* line %d% */; + echo ' + global->formsStack)['sex']; + echo $ʟ_input->getLabelPart($key)->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title=hello> global->formsStack)['sex']; + echo $ʟ_input->getControlPart($key)->attributes() /* line %d% */; + echo '> +'; + $iterations++; + } + + echo 'global->formsStack)['sex']; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '> +global->formsStack)['sex']; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' +global->formsStack)['sex']; + echo $ʟ_input->getLabelPart()->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title="hello">'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' +global->formsStack)[$ʟ_tmp]; + echo $ʟ_input->getControlPart("{$key}")->attributes() /* line %d% */; + echo '> + + +'; + if ($ʟ_label = end($this->global->formsStack)['checkbox']->getLabelPart('')) echo $ʟ_label->startTag() /* line %d% */; + echo ' '; + echo end($this->global->formsStack)['checkbox']->getControlPart('') /* line %d% */; + echo ' Label'; + if ($ʟ_label) echo $ʟ_label->endTag() /* line %d% */; + echo ' +global->formsStack)['checkbox']; + echo $ʟ_input->getLabelPart('')->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title=hello> global->formsStack)['checkbox']; + echo $ʟ_input->getControlPart('')->attributes() /* line %d% */; + echo '> +global->formsStack)['checkbox']; + echo $ʟ_input->getLabelPart()->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title=hello> global->formsStack)['checkbox']; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '> +global->formsStack)['checkbox']; + echo $ʟ_input->getLabelPart('')->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' +global->formsStack)['checkbox']; + echo $ʟ_input->getLabelPart()->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title=hello>'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' + + +'; + $iterations = 0; + foreach ($form['checklist']->items as $key => $label) /* line %d% */ { + echo ' '; + if ($ʟ_label = end($this->global->formsStack)['checklist']->getLabelPart($key)) echo $ʟ_label->startTag() /* line %d% */; + echo ' '; + echo end($this->global->formsStack)['checklist']->getControlPart($key) /* line %d% */; + echo ' '; + echo LR\Filters::escapeHtmlText($label) /* line %d% */; + if ($ʟ_label) echo $ʟ_label->endTag() /* line %d% */; + echo ' + global->formsStack)['checklist']; + echo $ʟ_input->getLabelPart($key)->attributes() /* line %d% */; + echo '> global->formsStack)['checklist']; + echo $ʟ_input->getControlPart($key)->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title=hello> +'; + $iterations++; + } + + echo 'global->formsStack)['checklist']; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '> +global->formsStack)['checklist']; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' +global->formsStack)['checklist']; + echo $ʟ_input->getLabelPart()->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title="hello">'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' + + +'; + if (1) /* line %d% */ { + echo 'global->formsStack[] = $this->global->uiControl['myForm'] /* line %d% */; + echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin(end($this->global->formsStack), ['id' => null, 'class' => null], false) /* line %d% */; + echo ' id="myForm" class="ajax"> + global->formsStack)['username']; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '> +'; + echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack), false) /* line %d% */; + echo ' +'; + } + echo ' + +global->formsStack[] = $this->global->uiControl['myForm'] /* line %d% */; + echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin(end($this->global->formsStack), ['class' => null], false) /* line %d% */; + echo ($ʟ_tmp = array_filter(['nclass'])) ? ' class="' . LR\Filters::escapeHtmlAttr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* line %d% */; + echo '> + global->formsStack)['username']; + echo $ʟ_input->getControlPart()->addAttributes(['class' => null])->attributes() /* line %d% */; + echo ($ʟ_tmp = array_filter(['nclass'])) ? ' class="' . LR\Filters::escapeHtmlAttr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* line %d% */; + echo '> +'; + echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack), false) /* line %d% */; + echo ' + + +global->formsStack[] = is_object($ʟ_tmp = $this->global->uiControl['myForm']) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp] /* line %d% */; + echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin(end($this->global->formsStack), [], false) /* line %d% */; + echo '> + global->formsStack)['username']; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '> +'; + echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack), false) /* line %d% */; + echo ' + + +global->formsStack)['select']; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getControl()->getHtml() /* line %d% */; + echo ' + + +global->formsStack)['area']; + echo $ʟ_input->getControlPart()->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title="'; + echo LR\Filters::escapeHtmlAttr(10) /* line %d% */; + echo '">'; + echo $ʟ_input->getControl()->getHtml() /* line %d% */; + echo ' + + +global->formsStack)['select']; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getControl()->getHtml() /* line %d% */; + echo ' +'; + echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack)) /* line %d% */; + + echo ' + + +'; + $form = $this->global->formsStack[] = $this->global->uiControl['myForm'] /* line %d% */; + echo ' +global->formsStack)['sex']; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' +global->formsStack)['username']; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '> +'; + array_pop($this->global->formsStack) /* line %d% */; +%A% diff --git a/tests/Forms.Latte/forms.button.phpt b/tests/Forms.Latte/forms.button.phpt new file mode 100644 index 000000000..a1cb3ac94 --- /dev/null +++ b/tests/Forms.Latte/forms.button.phpt @@ -0,0 +1,31 @@ +addSubmit('send', 'Sign in'); + +$latte = new Latte\Engine; +$latte->addExtension(new FormsExtension); +$latte->addProvider('uiControl', ['myForm' => $form]); + +Assert::matchFile( + __DIR__ . '/expected/forms.button.phtml', + $latte->compile(__DIR__ . '/templates/forms.button.latte') +); + +Assert::matchFile( + __DIR__ . '/expected/forms.button.html', + $latte->renderToString(__DIR__ . '/templates/forms.button.latte') +); diff --git a/tests/Forms.Latte/forms.error.phpt b/tests/Forms.Latte/forms.error.phpt new file mode 100644 index 000000000..01f7e7d6a --- /dev/null +++ b/tests/Forms.Latte/forms.error.phpt @@ -0,0 +1,51 @@ +setLoader(new Latte\Loaders\StringLoader); +$latte->addExtension(new FormsExtension); + +Assert::exception(function () use ($latte) { + $latte->compile('
    '); +}, Latte\CompileException::class, 'Did you mean
    ? (at column 7)'); + +Assert::exception(function () use ($latte) { + $latte->compile('
    '); +}, Latte\CompileException::class, 'Missing arguments in n:name (at column 7)'); + +Assert::exception(function () use ($latte) { + $latte->compile('
    '); +}, Latte\CompileException::class, 'Unexpected attribute n:inner-name, did you mean n:inner-label? (at column 7)'); + + +Assert::exception(function () use ($latte) { + $latte->compile('{form /}'); +}, Latte\CompileException::class, 'Missing arguments in {form} (at column 7)'); + +Assert::exception(function () use ($latte) { + $latte->compile('{formContainer /}'); +}, Latte\CompileException::class, 'Missing arguments in {formContainer} (at column 7)'); + + +Assert::exception(function () use ($latte) { + $latte->compile('{label /}'); +}, Latte\CompileException::class, 'Missing arguments in {label} (at column 7)'); + +Assert::exception(function () use ($latte) { + $latte->compile('{input /}'); +}, Latte\CompileException::class, 'Missing arguments in {input} (at column 7)'); + +Assert::exception(function () use ($latte) { + $latte->compile('{name /}'); +}, Latte\CompileException::class, 'Unexpected tag {name} (at column 7)'); diff --git a/tests/Forms.Latte/forms.formContainer.phpt b/tests/Forms.Latte/forms.formContainer.phpt new file mode 100644 index 000000000..fc5b3a901 --- /dev/null +++ b/tests/Forms.Latte/forms.formContainer.phpt @@ -0,0 +1,51 @@ +addText('input1', 'Input 1'); + +$cont1 = $form->addContainer('cont1'); +$cont1->addText('input2', 'Input 2'); +$cont1->addText('input3', 'Input 3'); + +$cont2 = $cont1->addContainer('cont2'); +$cont2->addCheckbox('input4', 'Input 4'); +$cont2->addCheckbox('input5', 'Input 5'); +$cont2->addCheckbox('input6', 'Input 6'); + +$cont1->addText('input7', 'Input 7'); + +$contItems = $form->addContainer('items'); +$items = [1, 3]; +foreach ($items as $item) { + $contItem = $contItems->addContainer($item); + $contItem->addText('input', 'Input'); +} + +$form->addSubmit('input8', 'Input 8'); + + +$latte = new Latte\Engine; +$latte->addExtension(new FormsExtension); +$latte->addProvider('uiControl', ['myForm' => $form]); + +Assert::matchFile( + __DIR__ . '/expected/forms.formContainer.phtml', + $latte->compile(__DIR__ . '/templates/forms.formContainer.latte') +); +Assert::matchFile( + __DIR__ . '/expected/forms.formContainer.html', + $latte->renderToString(__DIR__ . '/templates/forms.formContainer.latte') +); diff --git a/tests/Forms.Latte/forms.get.phpt b/tests/Forms.Latte/forms.get.phpt new file mode 100644 index 000000000..76f253873 --- /dev/null +++ b/tests/Forms.Latte/forms.get.phpt @@ -0,0 +1,32 @@ +setMethod('get'); +$form->setAction('?arg=val'); +$form->addSubmit('send', 'Sign in'); + +$latte = new Latte\Engine; +$latte->addExtension(new FormsExtension); +$latte->addProvider('uiControl', ['myForm' => $form]); + +Assert::matchFile( + __DIR__ . '/expected/forms.get.phtml', + $latte->compile(__DIR__ . '/templates/forms.get.latte') +); +Assert::matchFile( + __DIR__ . '/expected/forms.get.html', + $latte->renderToString(__DIR__ . '/templates/forms.get.latte') +); diff --git a/tests/Forms.Latte/forms.phpt b/tests/Forms.Latte/forms.phpt new file mode 100644 index 000000000..86cab7ff1 --- /dev/null +++ b/tests/Forms.Latte/forms.phpt @@ -0,0 +1,57 @@ +My'; + } + + + public function getControl() + { + return ''; + } +} + + +$form = new Form; +$form->getElementPrototype()->addClass('form-class'); +$form->addHidden('id'); +$form->addText('username', 'Username:'); // must have just one textfield to generate IE fix +$form['username']->getControlPrototype()->addClass('control-class'); +$form->addRadioList('sex', 'Sex:', ['m' => 'male', 'f' => 'female']); +$form->addSelect('select', null, ['m' => 'male', 'f' => 'female']); +$form->addTextArea('area', null)->setValue('oneaddCheckbox('checkbox', 'Checkbox'); +$form->addCheckboxList('checklist', 'CheckboxList:', ['m' => 'male', 'f' => 'female']); +$form->addSubmit('send', 'Sign in'); +$form['my'] = new MyControl; + +$latte = new Latte\Engine; +$latte->addExtension(new FormsExtension); +$latte->addProvider('uiControl', ['myForm' => $form]); + +$form['username']->addError('error'); + +Assert::matchFile( + __DIR__ . '/expected/forms.phtml', + $latte->compile(__DIR__ . '/templates/forms.latte') +); +Assert::matchFile( + __DIR__ . '/expected/forms.html', + $latte->renderToString(__DIR__ . '/templates/forms.latte') +); diff --git a/tests/Forms.Latte/templates/forms.formContainer.latte b/tests/Forms.Latte/templates/forms.formContainer.latte index 5abd962bc..cbc69cbb9 100644 --- a/tests/Forms.Latte/templates/forms.formContainer.latte +++ b/tests/Forms.Latte/templates/forms.formContainer.latte @@ -16,7 +16,7 @@ Checkboxes -
      +
      1. {input $field}
      diff --git a/tests/Forms.Latte/templates/forms.latte b/tests/Forms.Latte/templates/forms.latte index 0c4f5bc38..f4f3604f8 100644 --- a/tests/Forms.Latte/templates/forms.latte +++ b/tests/Forms.Latte/templates/forms.latte @@ -11,7 +11,7 @@ {inputError $form[$name]} {/foreach} - {label $form[username]} + {label $form[username] /} @@ -35,6 +35,7 @@