diff --git a/src/Bridges/FormsLatte/FormMacros.php b/src/Bridges/FormsLatte/FormMacros.php index 6db574965..eed0bad97 100644 --- a/src/Bridges/FormsLatte/FormMacros.php +++ b/src/Bridges/FormsLatte/FormMacros.php @@ -322,7 +322,7 @@ public function macroFormPrint(MacroNode $node, PhpWriter $writer) $node->tokenizer->reset(); return $writer->write( - 'Nette\Bridges\FormsLatte\Runtime::render' . $node->name . '(' + 'Nette\Forms\Blueprint::' . ($node->name === 'formPrint' ? 'latte' : 'dataClass') . '(' . ($name[0] === '$' ? 'is_object($ʟ_tmp = %node.word) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]' : '$this->global->uiControl[%node.word]') diff --git a/src/Bridges/FormsLatte/Nodes/FormPrintNode.php b/src/Bridges/FormsLatte/Nodes/FormPrintNode.php index 427beedbb..58ff946a7 100644 --- a/src/Bridges/FormsLatte/Nodes/FormPrintNode.php +++ b/src/Bridges/FormsLatte/Nodes/FormPrintNode.php @@ -39,12 +39,12 @@ public static function create(Tag $tag): static public function print(PrintContext $context): string { return $context->format( - 'Nette\Bridges\FormsLatte\Runtime::render%raw(' + 'Nette\Forms\Blueprint::%raw(' . ($this->name ? 'is_object($ʟ_tmp = %node) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]' : 'end($this->global->formsStack)') . ') %2.line; exit;', - $this->mode, + $this->mode === 'formPrint' ? 'latte' : 'dataClass', $this->name, $this->position, ); diff --git a/src/Bridges/FormsLatte/Runtime.php b/src/Bridges/FormsLatte/Runtime.php index 4eee9413f..744acf54f 100644 --- a/src/Bridges/FormsLatte/Runtime.php +++ b/src/Bridges/FormsLatte/Runtime.php @@ -9,7 +9,6 @@ namespace Nette\Bridges\FormsLatte; -use Latte; use Nette; use Nette\Forms\Form; use Nette\Utils\Html; @@ -76,42 +75,6 @@ public static function renderFormEnd(Form $form, bool $withTags = true): string } - /** - * Generates blueprint of form. - */ - public static function renderFormPrint(Form $form): void - { - $blueprint = class_exists(Latte\Runtime\Blueprint::class) - ? new Latte\Runtime\Blueprint - : new Latte\Essential\Blueprint; - $end = $blueprint->printCanvas(); - $blueprint->printHeader('Form ' . $form->getName()); - $blueprint->printCode((new Nette\Forms\Rendering\LatteRenderer)->render($form), 'latte'); - echo $end; - } - - - /** - * Generates blueprint of form data class. - */ - public static function renderFormClassPrint(Form $form): void - { - $blueprint = class_exists(Latte\Runtime\Blueprint::class) - ? new Latte\Runtime\Blueprint - : new Latte\Essential\Blueprint; - $end = $blueprint->printCanvas(); - $blueprint->printHeader('Form Data Class ' . $form->getName()); - $generator = new Nette\Forms\Rendering\DataClassGenerator; - $blueprint->printCode($generator->generateCode($form)); - if (PHP_VERSION_ID >= 80000) { - $generator->propertyPromotion = true; - $blueprint->printCode($generator->generateCode($form)); - } - - echo $end; - } - - public static function item($item, $global): object { if (is_object($item)) { diff --git a/src/Forms/Blueprint.php b/src/Forms/Blueprint.php new file mode 100644 index 000000000..858c7a812 --- /dev/null +++ b/src/Forms/Blueprint.php @@ -0,0 +1,233 @@ +printBegin(); + $bp->printHeader('Form ' . $form->getName()); + $bp->printCode($bp->generateLatte($form), 'latte'); + $bp->printEnd(); + if ($exit) { + exit; + } + } + + + /** + * Generates blueprint of form data class. + */ + public static function dataClass(Form $form, bool $exit = true): void + { + $bp = new self; + $bp->printBegin(); + $bp->printHeader('Form Data Class ' . $form->getName()); + $bp->printCode($bp->generateDataClass($form), 'php'); + if (PHP_VERSION_ID >= 80000) { + $bp->printCode($bp->generateDataClass($form, true), 'php'); + } + $bp->printEnd(); + if ($exit) { + exit; + } + } + + + private function printBegin(): void + { + echo ''; + echo ''; + echo "
\n"; + } + + + private function printEnd(): void + { + echo "
\n"; + } + + + private function printHeader(string $string): void + { + echo "

", + htmlspecialchars($string), + "

\n"; + } + + + private function printCode(string $code, string $lang): void + { + echo '
',
+		htmlspecialchars($code),
+		"
\n"; + } + + + public function generateLatte(Form $form): string + { + $dict = new \SplObjectStorage; + $dummyForm = new class extends Form { + protected function receiveHttpData(): ?array + { + return []; + } + }; + + foreach ($form->getControls() as $input) { + $dict[$input] = $dummyInput = new class extends Controls\BaseControl { + public $inner; + + + public function getLabel($caption = null) + { + return $this->inner->getLabel() + ? '{label ' . $this->inner->lookupPath(Form::class) . '/}' + : null; + } + + + public function getControl() + { + return '{input ' . $this->inner->lookupPath(Form::class) . '}'; + } + + + public function isRequired(): bool + { + return $this->inner->isRequired(); + } + + + public function getOption($key) + { + return $key === 'rendered' + ? parent::getOption($key) + : $this->inner->getOption($key); + } + }; + $dummyInput->inner = $input; + $dummyForm->addComponent($dummyInput, (string) $dict->count()); + $dummyInput->addError('{inputError ' . $input->lookupPath(Form::class) . '}'); + } + + foreach ($form->getGroups() as $group) { + $dummyGroup = $dummyForm->addGroup(); + foreach ($group->getOptions() as $k => $v) { + $dummyGroup->setOption($k, $v); + } + + foreach ($group->getControls() as $control) { + if ($dict[$control]) { + $dummyGroup->add($dict[$control]); + } + } + } + + $renderer = clone $form->getRenderer(); + $dummyForm->setRenderer($renderer); + $dummyForm->onRender = $form->onRender; + $dummyForm->fireRenderEvents(); + + if ($renderer instanceof Rendering\DefaultFormRenderer) { + $renderer->wrappers['error']['container'] = $renderer->getWrapper('error container')->setAttribute('n:ifcontent', true); + $renderer->wrappers['error']['item'] = $renderer->getWrapper('error item')->setAttribute('n:foreach', '$form->getOwnErrors() as $error'); + $renderer->wrappers['control']['errorcontainer'] = $renderer->getWrapper('control errorcontainer')->setAttribute('n:ifcontent', true); + $dummyForm->addError('{$error}'); + + ob_start(); + $dummyForm->render('end'); + $end = ob_get_clean(); + } + + ob_start(); + $dummyForm->render(); + $body = ob_get_clean(); + + $body = str_replace($dummyForm->getElementPrototype()->startTag(), '
', $body); + $body = str_replace($end ?? '', '
', $body); + return $body; + } + + + public function generateDataClass( + Container $container, + ?bool $propertyPromotion = false, + ?string $baseName = null + ): string + { + $baseName = $baseName ?? preg_replace('~Form$~', '', ucwords((string) $container->getName())); + $nextCode = ''; + $props = []; + foreach ($container->getComponents() as $name => $input) { + if ($input instanceof Controls\BaseControl && $input->isOmitted()) { + continue; + } elseif ($input instanceof Controls\Checkbox) { + $type = 'bool'; + } elseif ($input instanceof Controls\MultiChoiceControl) { + $type = 'array'; + } elseif ($input instanceof Controls\ChoiceControl) { + $type = 'string|int'; + if (!$input->isRequired()) { + $type .= '|null'; + } + } elseif ($input instanceof Controls\HiddenField || $input instanceof Controls\TextBase) { + $type = 'string'; + foreach ($input->getRules() as $rule) { + if ($rule->validator === Form::Integer) { + $type = 'int'; + break; + } + } + + if (!$input->isRequired()) { + $type = '?' . $type; + } + } elseif ($input instanceof Controls\UploadControl) { + $type = '\Nette\Http\FileUpload'; + if (!$input->isRequired()) { + $type = '?' . $type; + } + } elseif ($input instanceof Container) { + $type = $baseName . ucwords($name); + $nextCode .= $this->generateDataClass($input, $propertyPromotion, $type); + $type .= self::ClassNameSuffix; + } else { + $type = ''; + } + + $props[] = 'public ' . ($type ? $type . ' ' : '') . '$' . $name; + } + + $class = $baseName . self::ClassNameSuffix; + return "class $class\n" + . "{\n" + . ($propertyPromotion + ? "\tpublic function __construct(\n" + . ($props ? "\t\t" . implode(",\n\t\t", $props) . ",\n" : '') + . "\t) {\n\t}\n" + : ($props ? "\t" . implode(";\n\t", $props) . ";\n" : '') + ) + . "}\n\n" + . $nextCode; + } +} diff --git a/src/Forms/Rendering/DataClassGenerator.php b/src/Forms/Rendering/DataClassGenerator.php index 2c4f5f60f..6fc68ae81 100644 --- a/src/Forms/Rendering/DataClassGenerator.php +++ b/src/Forms/Rendering/DataClassGenerator.php @@ -9,13 +9,13 @@ namespace Nette\Forms\Rendering; -use Nette\Forms\Container; -use Nette\Forms\Controls; +use Nette\Forms\Blueprint; use Nette\Forms\Form; /** * Generates blueprint of form data class. + * @deprecated use Nette\Latte\Blueprint::dataClass() */ final class DataClassGenerator { @@ -29,68 +29,9 @@ final class DataClassGenerator public $useSmartObject = true; + /** @deprecated use Nette\Latte\Blueprint::dataClass() */ public function generateCode(Form $form, ?string $baseName = null): string { - $baseName = $baseName ?? preg_replace('~Form$~', '', ucwords((string) $form->getName())); - return $this->processContainer($form, $baseName); - } - - - private function processContainer(Container $container, string $baseName): string - { - $nextCode = ''; - $props = []; - foreach ($container->getComponents() as $name => $input) { - if ($input instanceof Controls\BaseControl && $input->isOmitted()) { - continue; - } elseif ($input instanceof Controls\Checkbox) { - $type = 'bool'; - } elseif ($input instanceof Controls\MultiChoiceControl) { - $type = 'array'; - } elseif ($input instanceof Controls\ChoiceControl) { - $type = 'string|int'; - if (!$input->isRequired()) { - $type .= '|null'; - } - } elseif ($input instanceof Controls\HiddenField || $input instanceof Controls\TextBase) { - $type = 'string'; - foreach ($input->getRules() as $rule) { - if ($rule->validator === Form::Integer) { - $type = 'int'; - break; - } - } - - if (!$input->isRequired()) { - $type = '?' . $type; - } - } elseif ($input instanceof Controls\UploadControl) { - $type = '\Nette\Http\FileUpload'; - if (!$input->isRequired()) { - $type = '?' . $type; - } - } elseif ($input instanceof Container) { - $type = $baseName . ucwords($name); - $nextCode .= $this->processContainer($input, $type); - $type .= $this->classNameSuffix; - } else { - $type = ''; - } - - $props[] = 'public ' . ($type ? $type . ' ' : '') . '$' . $name; - } - - $class = $baseName . $this->classNameSuffix; - return "class $class\n" - . "{\n" - . ($this->useSmartObject ? "\tuse \\Nette\\SmartObject;\n\n" : '') - . ($this->propertyPromotion - ? "\tpublic function __construct(\n" - . ($props ? "\t\t" . implode(",\n\t\t", $props) . ",\n" : '') - . "\t) {\n\t}\n" - : ($props ? "\t" . implode(";\n\t", $props) . ";\n" : '') - ) - . "}\n\n" - . $nextCode; + return (new Blueprint)->generateDataClass($form, $this->propertyPromotion, $baseName); } } diff --git a/src/Forms/Rendering/LatteRenderer.php b/src/Forms/Rendering/LatteRenderer.php index b800e9063..5f71c8214 100644 --- a/src/Forms/Rendering/LatteRenderer.php +++ b/src/Forms/Rendering/LatteRenderer.php @@ -9,97 +9,19 @@ namespace Nette\Forms\Rendering; -use Nette; +use Nette\Forms\Blueprint; use Nette\Forms\Form; /** * Generates Latte blueprint of form. + * @deprecated use Nette\Latte\Blueprint::latte() */ final class LatteRenderer { + /** @deprecated use Nette\Latte\Blueprint::latte() */ public function render(Form $form): string { - $dict = new \SplObjectStorage; - $dummyForm = new class extends Form { - protected function receiveHttpData(): ?array - { - return []; - } - }; - - foreach ($form->getControls() as $name => $input) { - $dict[$input] = $dummyInput = new class extends Nette\Forms\Controls\BaseControl { - public $inner; - - - public function getLabel($name = null) - { - return $this->inner->getLabel() - ? '{label ' . $this->inner->lookupPath(Form::class) . '/}' - : null; - } - - - public function getControl() - { - return '{input ' . $this->inner->lookupPath(Form::class) . '}'; - } - - - public function isRequired(): bool - { - return $this->inner->isRequired(); - } - - - public function getOption($key) - { - return $key === 'rendered' - ? parent::getOption($key) - : $this->inner->getOption($key); - } - }; - $dummyInput->inner = $input; - $dummyForm->addComponent($dummyInput, (string) $dict->count()); - $dummyInput->addError('{inputError ' . $input->lookupPath(Form::class) . '}'); - } - - foreach ($form->getGroups() as $group) { - $dummyGroup = $dummyForm->addGroup(); - foreach ($group->getOptions() as $k => $v) { - $dummyGroup->setOption($k, $v); - } - - foreach ($group->getControls() as $control) { - if ($dict[$control]) { - $dummyGroup->add($dict[$control]); - } - } - } - - $renderer = clone $form->getRenderer(); - $dummyForm->setRenderer($renderer); - $dummyForm->onRender = $form->onRender; - $dummyForm->fireRenderEvents(); - - if ($renderer instanceof DefaultFormRenderer) { - $renderer->wrappers['error']['container'] = $renderer->getWrapper('error container')->setAttribute('n:ifcontent', true); - $renderer->wrappers['error']['item'] = $renderer->getWrapper('error item')->setAttribute('n:foreach', '$form->getOwnErrors() as $error'); - $renderer->wrappers['control']['errorcontainer'] = $renderer->getWrapper('control errorcontainer')->setAttribute('n:ifcontent', true); - $dummyForm->addError('{$error}'); - - ob_start(); - $dummyForm->render('end'); - $end = ob_get_clean(); - } - - ob_start(); - $dummyForm->render(); - $body = ob_get_clean(); - - $body = str_replace($dummyForm->getElementPrototype()->startTag(), '
', $body); - $body = str_replace($end ?? '', '
', $body); - return $body; + return (new Blueprint)->generateLatte($form); } } diff --git a/tests/Forms.Latte3/Runtime.renderFormClassPrint.phpt b/tests/Forms.Latte3/Runtime.renderFormClassPrint.phpt deleted file mode 100644 index eee743057..000000000 --- a/tests/Forms.Latte3/Runtime.renderFormClassPrint.phpt +++ /dev/null @@ -1,43 +0,0 @@ -addText('name')->setRequired(); - -ob_start(); -Nette\Bridges\FormsLatte\Runtime::renderFormClassPrint($form); -$res = ob_get_clean(); - -Assert::match( - '%A%class SignFormData -{ - use \Nette\SmartObject; - - public string $name; -} -%A%', - $res, -); - -Assert::match( - '%A%class SignFormData -{ - use \Nette\SmartObject; - - public function __construct( - public string $name, - ) { - } -} -%A%', - $res, -); diff --git a/tests/Forms.Latte3/Runtime.renderFormPrint.phpt b/tests/Forms.Latte3/Runtime.renderFormPrint.phpt deleted file mode 100644 index 832275444..000000000 --- a/tests/Forms.Latte3/Runtime.renderFormPrint.phpt +++ /dev/null @@ -1,51 +0,0 @@ -addGroup('Personal data'); -$form->addText('name')->setRequired('Enter your name'); -$form->addSubmit('submit', 'Send'); - -ob_start(); -Nette\Bridges\FormsLatte\Runtime::renderFormPrint($form); -$res = ob_get_clean(); - -Assert::match( - '%A%
- - - - -
-Personal data - - - - - - - - - - - - - -
{label name/}{input name} - {inputError name}
{input submit}
-
- -
%A%', - html_entity_decode($res), -); diff --git a/tests/Forms/DataClassGenerator.phpt b/tests/Forms/Blueprint.dataClass.phpt similarity index 76% rename from tests/Forms/DataClassGenerator.phpt rename to tests/Forms/Blueprint.dataClass.phpt index 04d1e9acb..98c67102f 100644 --- a/tests/Forms/DataClassGenerator.phpt +++ b/tests/Forms/Blueprint.dataClass.phpt @@ -2,6 +2,7 @@ declare(strict_types=1); +use Nette\Forms\Blueprint; use Nette\Forms\Form; use Tester\Assert; @@ -18,14 +19,11 @@ $form->addHidden('id'); $form->addCheckbox('agree'); $form->addSubmit('submit', 'Send'); -$generator = new Nette\Forms\Rendering\DataClassGenerator; -$res = $generator->generateCode($form); +$res = (new Blueprint)->generateDataClass($form); Assert::match( 'class SignFormData { - use \Nette\SmartObject; - public string $name; public ?int $age; public SignContFormData $cont; @@ -35,17 +33,13 @@ Assert::match( class SignContFormData { - use \Nette\SmartObject; - public ?string $name; } ', $res ); -$generator->propertyPromotion = true; -$generator->useSmartObject = false; -$res = $generator->generateCode($form); +$res = (new Blueprint)->generateDataClass($form, true); Assert::match( 'class SignFormData diff --git a/tests/Forms/LatteRenderer.phpt b/tests/Forms/Blueprint.latte.phpt similarity index 94% rename from tests/Forms/LatteRenderer.phpt rename to tests/Forms/Blueprint.latte.phpt index c653be0a0..cf3e368dd 100644 --- a/tests/Forms/LatteRenderer.phpt +++ b/tests/Forms/Blueprint.latte.phpt @@ -2,6 +2,7 @@ declare(strict_types=1); +use Nette\Forms\Blueprint; use Nette\Forms\Form; use Nette\Utils\Html; use Tester\Assert; @@ -33,8 +34,7 @@ $form->onRender[] = function ($form) { $renderer->wrappers['label']['suffix'] = ':'; }; -$renderer = new Nette\Forms\Rendering\LatteRenderer; -$res = $renderer->render($form); +$res = (new Blueprint)->generateLatte($form); Assert::match( '