diff --git a/src/Bridges/FormsLatte/FormMacros.php b/src/Bridges/FormsLatte/FormMacros.php index 9c8b4b85a..bf32e6fc7 100644 --- a/src/Bridges/FormsLatte/FormMacros.php +++ b/src/Bridges/FormsLatte/FormMacros.php @@ -39,6 +39,7 @@ public static function install(Latte\Compiler $compiler): void $me->addMacro('name', [$me, 'macroName'], [$me, 'macroNameEnd'], [$me, 'macroNameAttr']); $me->addMacro('inputError', [$me, 'macroInputError']); $me->addMacro('formPrint', [$me, 'macroFormPrint']); + $me->addMacro('formClassPrint', [$me, 'macroFormPrint']); } @@ -281,7 +282,8 @@ public function macroInputError(MacroNode $node, PhpWriter $writer) /** - * {formPrint [ClassName]} + * {formPrint ClassName} + * {formClassPrint ClassName} */ public function macroFormPrint(MacroNode $node, PhpWriter $writer) { @@ -291,7 +293,7 @@ public function macroFormPrint(MacroNode $node, PhpWriter $writer) } $node->tokenizer->reset(); return $writer->write( - 'Nette\Bridges\FormsLatte\Runtime::renderBlueprint(' + 'Nette\Bridges\FormsLatte\Runtime::render' . $node->name . '(' . ($name[0] === '$' ? 'is_object(%node.word) ? %node.word : ' : '') . '$this->global->uiControl[%node.word]); exit;' ); diff --git a/src/Bridges/FormsLatte/Runtime.php b/src/Bridges/FormsLatte/Runtime.php index e532d0dbb..f6540cc39 100644 --- a/src/Bridges/FormsLatte/Runtime.php +++ b/src/Bridges/FormsLatte/Runtime.php @@ -85,4 +85,22 @@ public static function renderBlueprint(Form $form): void $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 = new Latte\Runtime\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; + } } diff --git a/src/Forms/Rendering/DataClassGenerator.php b/src/Forms/Rendering/DataClassGenerator.php new file mode 100644 index 000000000..0c995fd86 --- /dev/null +++ b/src/Forms/Rendering/DataClassGenerator.php @@ -0,0 +1,95 @@ +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; + } +} diff --git a/tests/Forms.Latte/Runtime.renderFormClassPrint.phpt b/tests/Forms.Latte/Runtime.renderFormClassPrint.phpt new file mode 100644 index 000000000..c0c1d2ebf --- /dev/null +++ b/tests/Forms.Latte/Runtime.renderFormClassPrint.phpt @@ -0,0 +1,44 @@ +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 +); + +if (PHP_VERSION_ID >= 80000) { + Assert::match( + '%A%class SignFormData +{ + use \Nette\SmartObject; + + public function __construct( + public string $name, + ) { + } +} +%A%', + $res + ); +} diff --git a/tests/Forms/DataClassGenerator.phpt b/tests/Forms/DataClassGenerator.phpt new file mode 100644 index 000000000..04d1e9acb --- /dev/null +++ b/tests/Forms/DataClassGenerator.phpt @@ -0,0 +1,72 @@ +addText('name')->setRequired(); +$form->addInteger('age'); +$form->addContainer('cont') + ->addText('name'); +$form->addHidden('id'); +$form->addCheckbox('agree'); +$form->addSubmit('submit', 'Send'); + +$generator = new Nette\Forms\Rendering\DataClassGenerator; +$res = $generator->generateCode($form); + +Assert::match( + 'class SignFormData +{ + use \Nette\SmartObject; + + public string $name; + public ?int $age; + public SignContFormData $cont; + public ?string $id; + public bool $agree; +} + +class SignContFormData +{ + use \Nette\SmartObject; + + public ?string $name; +} +', + $res +); + +$generator->propertyPromotion = true; +$generator->useSmartObject = false; +$res = $generator->generateCode($form); + +Assert::match( + 'class SignFormData +{ + public function __construct( + public string $name, + public ?int $age, + public SignContFormData $cont, + public ?string $id, + public bool $agree, + ) { + } +} + +class SignContFormData +{ + public function __construct( + public ?string $name, + ) { + } +} +', + $res +);