Skip to content

Commit

Permalink
blueprint WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Nov 10, 2023
1 parent 2bb8187 commit 4a3bd30
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 290 deletions.
2 changes: 1 addition & 1 deletion src/Bridges/FormsLatte/FormMacros.php
Original file line number Diff line number Diff line change
Expand Up @@ -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\Rendering\Blueprint::' . ($node->name === 'formPrint' ? 'latte' : 'dataClass') . '('
. ($name[0] === '$'
? 'is_object($ʟ_tmp = %node.word) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]'
: '$this->global->uiControl[%node.word]')
Expand Down
4 changes: 2 additions & 2 deletions src/Bridges/FormsLatte/Nodes/FormPrintNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -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\Rendering\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,
);
Expand Down
37 changes: 0 additions & 37 deletions src/Bridges/FormsLatte/Runtime.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

namespace Nette\Bridges\FormsLatte;

use Latte;
use Nette;
use Nette\Forms\Form;
use Nette\Utils\Html;
Expand Down Expand Up @@ -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)) {
Expand Down
238 changes: 238 additions & 0 deletions src/Forms/Rendering/Blueprint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\Forms\Rendering;

use Nette;
use Nette\Forms\Container;
use Nette\Forms\Controls;
use Nette\Forms\Form;


/**
* Generates blueprints for forms.
*/
final class Blueprint
{
private const ClassNameSuffix = 'FormData';


/**
* Generates blueprint of form.
*/
public static function latte(Form $form, bool $exit = true): void
{
$bp = new self;
$bp->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 '<script src="https://nette.github.io/resources/prism/prism.js"></script>';
echo '<link rel="stylesheet" href="https://nette.github.io/resources/prism/prism.css">';
echo "<div style='all:initial;position:fixed;overflow:auto;z-index:1000;left:0;right:0;top:0;bottom:0;color:black;background:white;padding:1em'>\n";
}


private function printEnd(): void
{
echo "</div>\n";
}


private function printHeader(string $string): void
{
echo "<h1 style='all:initial;display:block;font-size:2em;margin:1em 0'>",
htmlspecialchars($string),
"</h1>\n";
}


private function printCode(string $code, string $lang): void
{
echo '<pre><code class="language-', htmlspecialchars($lang), '">',
htmlspecialchars($code),
"</code></pre>\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 Nette\Forms\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 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(), '<form n:name="' . $form->getName() . '">', $body);
$body = str_replace($end ?? '', '</form>', $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;
}
}
66 changes: 3 additions & 63 deletions src/Forms/Rendering/DataClassGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@

namespace Nette\Forms\Rendering;

use Nette\Forms\Container;
use Nette\Forms\Controls;
use Nette\Forms\Form;


/**
* Generates blueprint of form data class.
* @deprecated
*/
final class DataClassGenerator
{
Expand All @@ -31,66 +30,7 @@ final class DataClassGenerator

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;
trigger_error(__METHOD__ . '() is deprecated, use ' . Blueprint::class . '::dataClass()', E_USER_DEPRECATED);
return (new Blueprint)->generateDataClass($form, $this->propertyPromotion, $baseName);
}
}
Loading

0 comments on commit 4a3bd30

Please sign in to comment.