From 9080535afc562a6589c8495eba5eee3533344ca8 Mon Sep 17 00:00:00 2001 From: RobinDev Date: Sun, 12 Nov 2023 11:24:09 +0100 Subject: [PATCH 01/17] Implement Template Class --- composer.json | 97 +++++++++++----------- exampleTemplateClass/Templates/Layout.php | 31 +++++++ exampleTemplateClass/Templates/Profile.php | 32 +++++++ exampleTemplateClass/Templates/Sidebar.php | 20 +++++ exampleTemplateClass/example.php | 15 ++++ src/Engine.php | 10 ++- src/Template/Template.php | 20 +++-- src/Template/TemplateClass.php | 59 +++++++++++++ src/Template/TemplateClassInterface.php | 8 ++ 9 files changed, 233 insertions(+), 59 deletions(-) create mode 100644 exampleTemplateClass/Templates/Layout.php create mode 100644 exampleTemplateClass/Templates/Profile.php create mode 100644 exampleTemplateClass/Templates/Sidebar.php create mode 100644 exampleTemplateClass/example.php create mode 100644 src/Template/TemplateClass.php create mode 100644 src/Template/TemplateClassInterface.php diff --git a/composer.json b/composer.json index 98451032..882e0a7a 100644 --- a/composer.json +++ b/composer.json @@ -1,52 +1,53 @@ { - "name": "league/plates", - "description": "Plates, the native PHP template system that's fast, easy to use and easy to extend.", - "keywords": [ - "league", - "package", - "templating", - "templates", - "views" - ], - "homepage": "https://platesphp.com", - "license": "MIT", - "authors" : [ - { - "name": "Jonathan Reinink", - "email": "jonathan@reinink.ca", - "role": "Developer" - }, - { - "name": "RJ Garcia", - "email": "ragboyjr@icloud.com", - "role": "Developer" - } - ], - "require" : { - "php": "^7.0|^8.0" + "name": "league/plates", + "description": "Plates, the native PHP template system that's fast, easy to use and easy to extend.", + "keywords": [ + "league", + "package", + "templating", + "templates", + "views" + ], + "homepage": "https://platesphp.com", + "license": "MIT", + "authors": [ + { + "name": "Jonathan Reinink", + "email": "jonathan@reinink.ca", + "role": "Developer" }, - "require-dev": { - "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "^3.5" - }, - "autoload": { - "psr-4": { - "League\\Plates\\": "src" - } - }, - "autoload-dev": { - "psr-4": { - "League\\Plates\\Tests\\": "tests" - } - }, - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "scripts": { - "test": "phpunit --testdox --colors=always", - "docs": "hugo -s doc server" + { + "name": "RJ Garcia", + "email": "ragboyjr@icloud.com", + "role": "Developer" + } + ], + "require": { + "php": "^7.0|^8.0" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "autoload": { + "psr-4": { + "League\\Plates\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "League\\Plates\\Tests\\": "tests", + "Templates\\": "exampleTemplateClass/Templates" + } + }, + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" } + }, + "scripts": { + "test": "phpunit --testdox --colors=always", + "docs": "hugo -s doc server" + } } diff --git a/exampleTemplateClass/Templates/Layout.php b/exampleTemplateClass/Templates/Layout.php new file mode 100644 index 00000000..aa265815 --- /dev/null +++ b/exampleTemplateClass/Templates/Layout.php @@ -0,0 +1,31 @@ + + + + <?=$tpl->e($this->title)?> | <?=$tpl->e($this->company)?> + + + +section('content')?> + +section('scripts')?> + + + + +layout(new Layout('User Profile')) ?> +layout('layout', ['title' => 'User Profile']) // this is working too ! ?> +layout(new Layout(), ['title' => 'User Profile']) // this is working too ! ?> + +

User Profile

+

Hello, e($this->name)?>!

+ +insert(new Sidebar()) ?> + +push('scripts') ?> + +end() ?> + + +addData(['company' => 'The Company Name'], Layout::class); + +// Render a template +echo $templates->render(new Profile('Jonathan')); diff --git a/src/Engine.php b/src/Engine.php index c889c5e6..4aa1a585 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -12,6 +12,8 @@ use League\Plates\Template\Name; use League\Plates\Template\ResolveTemplatePath; use League\Plates\Template\Template; +use League\Plates\Template\TemplateClass; +use League\Plates\Template\TemplateClassInterface; use League\Plates\Template\Theme; /** @@ -282,13 +284,15 @@ public function exists($name) /** * Create a new template. - * @param string $name - * @param array $data + * @param string|TemplateClassInterface $name + * @param array $data * @return Template */ public function make($name, array $data = array()) { - $template = new Template($this, $name); + + $template = $name instanceof TemplateClassInterface ? new TemplateClass($this, $name) + : new Template($this, $name); $template->data($data); return $template; } diff --git a/src/Template/Template.php b/src/Template/Template.php index db11a9f1..f30b65b3 100644 --- a/src/Template/Template.php +++ b/src/Template/Template.php @@ -62,7 +62,7 @@ class Template /** * The name of the template layout. - * @var string + * @var string|TemplateClassInterface */ protected $layoutName; @@ -158,17 +158,11 @@ public function path() public function render(array $data = array()) { $this->data($data); - $path = ($this->engine->getResolveTemplatePath())($this->name); try { $level = ob_get_level(); ob_start(); - - (function() { - extract($this->data); - include func_get_arg(0); - })($path); - + $this->display(); $content = ob_get_clean(); if (isset($this->layoutName)) { @@ -187,6 +181,16 @@ public function render(array $data = array()) } } + + protected function display() { + $path = ($this->engine->getResolveTemplatePath())($this->name); + + (function() { + extract($this->data); + include func_get_arg(0); + })($path); + } + /** * Set the template's layout. * @param string $name diff --git a/src/Template/TemplateClass.php b/src/Template/TemplateClass.php new file mode 100644 index 00000000..ac70ff48 --- /dev/null +++ b/src/Template/TemplateClass.php @@ -0,0 +1,59 @@ +engine = $engine; + $name = $templateClass::class; + + $this->data($this->engine->getData($name)); // needed for addData, too much magic, deprecate it ?! + } + + protected function display() { + + $this->autowireDataToTemplateClass(); + $this->templateClass->display($this); + } + + protected function autowireDataToTemplateClass() + { + $properties = get_object_vars($this->templateClass); + foreach ($properties as $propertyName => $propertyValue) { + if ($propertyValue !== null) { + continue; + } + + $this->templateClass->$propertyName = $this->data[$propertyName] ?? null; + } + } + + /** Disable useless public/protected parent method and property */ + + /** + * @var Name Useless here + */ + protected $name; + + public function exists(): bool + { + return true; + } + + public function path(): string + { + return ''; + } +} diff --git a/src/Template/TemplateClassInterface.php b/src/Template/TemplateClassInterface.php new file mode 100644 index 00000000..f724f3b5 --- /dev/null +++ b/src/Template/TemplateClassInterface.php @@ -0,0 +1,8 @@ + Date: Sun, 12 Nov 2023 14:00:32 +0100 Subject: [PATCH 02/17] fix type --- src/Engine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Engine.php b/src/Engine.php index 4aa1a585..54265e82 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -299,7 +299,7 @@ public function make($name, array $data = array()) /** * Create a new template and render it. - * @param string $name + * @param string|TemplateClassInterface $name * @param array $data * @return string */ From 6296ff014ab0019428185c78dad0a5717326af3b Mon Sep 17 00:00:00 2001 From: RobinDev Date: Sun, 12 Nov 2023 14:05:01 +0100 Subject: [PATCH 03/17] fix type (bis) --- src/Template/Template.php | 6 +++--- src/Template/TemplateClass.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Template/Template.php b/src/Template/Template.php index f30b65b3..1e653ba7 100644 --- a/src/Template/Template.php +++ b/src/Template/Template.php @@ -193,7 +193,7 @@ protected function display() { /** * Set the template's layout. - * @param string $name + * @param string|TemplateClassInterface $name * @param array $data * @return null */ @@ -311,7 +311,7 @@ public function section($name, $default = null) /** * Fetch a rendered template. - * @param string $name + * @param string $name|TemplateClassInterface * @param array $data * @return string */ @@ -322,7 +322,7 @@ public function fetch($name, array $data = array()) /** * Output a rendered template. - * @param string $name + * @param string $name|TemplateClassInterface * @param array $data * @return null */ diff --git a/src/Template/TemplateClass.php b/src/Template/TemplateClass.php index ac70ff48..2c829b1f 100644 --- a/src/Template/TemplateClass.php +++ b/src/Template/TemplateClass.php @@ -54,6 +54,6 @@ public function exists(): bool public function path(): string { - return ''; + return get_class($this->templateClass); } } From df1c0df8c96f85c1662cbbf66083e78ea27ff832 Mon Sep 17 00:00:00 2001 From: RobinDev Date: Sun, 12 Nov 2023 23:45:03 +0100 Subject: [PATCH 04/17] create rector rule --- composer.json | 6 +- exampleTemplateClass/Templates/Layout.php | 19 ++- exampleTemplateClass/Templates/Profile.php | 25 +-- exampleTemplateClass/Templates/Sidebar.php | 2 +- exampleTemplateClass/rector.php | 15 ++ src/RectorizeTemplate.php | 175 +++++++++++++++++++++ src/Template/TemplateClass.php | 50 +++++- src/Template/TemplateClassInterface.php | 2 +- 8 files changed, 269 insertions(+), 25 deletions(-) create mode 100644 exampleTemplateClass/rector.php create mode 100644 src/RectorizeTemplate.php diff --git a/composer.json b/composer.json index 882e0a7a..ab230722 100644 --- a/composer.json +++ b/composer.json @@ -23,12 +23,14 @@ } ], "require": { - "php": "^7.0|^8.0" + "php": "^7.0|^8.0", + "symfony/var-dumper": "^6.3" }, "require-dev": { "mikey179/vfsstream": "^1.6", "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "^3.5" + "squizlabs/php_codesniffer": "^3.5", + "rector/rector": "^0.18.6" }, "autoload": { "psr-4": { diff --git a/exampleTemplateClass/Templates/Layout.php b/exampleTemplateClass/Templates/Layout.php index aa265815..4423c5c1 100644 --- a/exampleTemplateClass/Templates/Layout.php +++ b/exampleTemplateClass/Templates/Layout.php @@ -8,15 +8,13 @@ class Layout implements TemplateClassInterface { - public function __construct( - public ?string $title = null, - public ?string $company = null, - ) {} - - public function display(Template $tpl): void { ?> + /** + * @param string[] $test323 + */ + public function display(Template $tpl, string|int|null $company = null, ?string $title = null, array $test323 = []): void { ?> - <?=$tpl->e($this->title)?> | <?=$tpl->e($this->company)?> + <?=$tpl->e($title)?> | <?=$tpl->e($company)?> @@ -28,4 +26,11 @@ public function display(Template $tpl): void { ?> -layout(new Layout('User Profile')) ?> -layout('layout', ['title' => 'User Profile']) // this is working too ! ?> -layout(new Layout(), ['title' => 'User Profile']) // this is working too ! ?> + public function display(Template $t, string $name): void { ?> +layout(new Layout('User Profile')) ?> +layout('layout', ['title' => 'User Profile']) // this is working too and will get the example/templates/layout.php ?>

User Profile

-

Hello, e($this->name)?>!

+

Hello, e($name)?>!

-insert(new Sidebar()) ?> +insert(new Sidebar()) ?> -push('scripts') ?> +push('scripts') ?> -end() ?> +end() ?> + public function display(): void { ?>
  • Example sidebar link
  • Example sidebar link
  • diff --git a/exampleTemplateClass/rector.php b/exampleTemplateClass/rector.php new file mode 100644 index 00000000..6a1684da --- /dev/null +++ b/exampleTemplateClass/rector.php @@ -0,0 +1,15 @@ +paths([ + __DIR__.'/Templates/Layout.php', + ]); + $rectorConfig->rule(RectorizeTemplate::class); +}; + +// vendor/bin/rector process exampleTemplateClass/Templates --config ./exampleTemplateClass/rector.php diff --git a/src/RectorizeTemplate.php b/src/RectorizeTemplate.php new file mode 100644 index 00000000..5e230320 --- /dev/null +++ b/src/RectorizeTemplate.php @@ -0,0 +1,175 @@ +paramAnalyzer = $paramAnalyzer; + $this->complexNodeRemover = $complexNodeRemover; + $this->paramTypeResolver = $paramTypeResolver; + } + public function getRuleDefinition() : RuleDefinition + { + return new RuleDefinition('Duplicate diplay to constructor except Template', [new CodeSample(<<<'CODE_SAMPLE' +final class SomeTemplate +{ + public function display(string $name, Template $t): void + { + // ... + } +} +CODE_SAMPLE +, <<<'CODE_SAMPLE' +final class SomeClass +{ + public function display(string $name, Template $t): void + { + // ... + } + + public function __construct(public string $name) + { + } +} +CODE_SAMPLE +)]); + } + /** + * @return array> + */ + public function getNodeTypes() : array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node) : ?Node + { + $implementedInterfaces = array_map( fn (Name $interface) => $interface->toString(), $node->implements ); + if (! in_array(TemplateClassInterface::class, $implementedInterfaces, true)) { + return null; + } + + $displayMethod = $node->getMethod('display') ; + if ($displayMethod === null) + return null; + + + if ($displayMethod->params === []) + return $this->removeConstructor($node); + + $paramsForConstructor = []; + $docBlockForConstructor = []; + $methodDocBlock = $displayMethod?->getDocComment()?->getText() ?? ''; + foreach($displayMethod->params as $parameter) { + $paramType = $this->paramTypeResolver->resolve($parameter); + if ($paramType instanceof FullyQualifiedObjectType && in_array($paramType->getClassName(), [Template::class, TemplateClass::class], true)) + continue; + + $paramDocBlock = $this->getParameterDocblock($methodDocBlock, $this->getName($parameter)); + if ($paramDocBlock !== null) + $docBlockForConstructor[] = $paramDocBlock; + + $cloneParameter = clone $parameter; + $cloneParameter->flags = 1; + $paramsForConstructor[] = $cloneParameter; + } + + if ($paramsForConstructor === []) + return $this->removeConstructor($node); + + $constructor = $node->getMethod(MethodName::CONSTRUCT) ; + $docBlockConstructor = $constructor?->getDocComment(); + $newDocBlockConstructor = new Doc("/**\n * ".trim("Autogenerated constructor\n * ".implode("\n * ", $docBlockForConstructor), "\n *")."\n*/"); + if ($paramsForConstructor === $constructor?->params || $docBlockConstructor === $newDocBlockConstructor) // TODO compare docblock + return null; + + $this->removeConstructor($node); + + $constructor = new ClassMethod('__construct', [ + 'flags' => Node\Stmt\Class_::MODIFIER_PUBLIC, + 'params' => $paramsForConstructor, + ]); + $constructor->setDocComment($newDocBlockConstructor); + + + $node->stmts[] = $constructor; + + return $node; + } + + private function removeConstructor(Class_ $class):null + { + foreach ($class->stmts as $key => $stmt) { + if ($stmt instanceof Node\Stmt\ClassMethod && $stmt->name->toString() === MethodName::CONSTRUCT) { + unset($class->stmts[$key]); + break; + } + } + + return null; + } + + private function getParameterDocblock(?string $methodDocblock, string $parameterName): ?string + { + if ($methodDocblock === null) { + return null; + } + + // Regular expression to match a docblock for a specific parameter + $pattern = '/@param\s+[^$]*?\$'.$parameterName.'\b([^@]*)/'; + + $patternIsFound = preg_match($pattern, $methodDocblock, $matches); + + if ($patternIsFound) { + return trim($matches[0], '*/ '."\n"); + } + + return null; + } +} \ No newline at end of file diff --git a/src/Template/TemplateClass.php b/src/Template/TemplateClass.php index 2c829b1f..2db2f9e0 100644 --- a/src/Template/TemplateClass.php +++ b/src/Template/TemplateClass.php @@ -5,7 +5,9 @@ use Exception; use League\Plates\Engine; use League\Plates\Template\Name; -use Throwable; +use ReflectionClass; +use ReflectionMethod; +use ReflectionProperty; /** * Container which holds template data and provides access to template functions. @@ -24,8 +26,52 @@ public function __construct( protected function display() { + $this->mergePropertyToData(); $this->autowireDataToTemplateClass(); - $this->templateClass->display($this); + + $vars = $this->getVarToAutowireDisplayMethod(); + $this->templateClass->display(...$vars); + } + + protected function mergePropertyToData(): void + { + $properties = (new ReflectionClass($this->templateClass))->getProperties(ReflectionProperty::IS_PUBLIC); + + $dataToImport = []; + foreach ($properties as $property) { + $propertyValue = $property->getValue($this->templateClass); + if ($propertyValue === $property->getDefaultValue() || $propertyValue === null) + continue; + + $dataToImport[$property->getName()] = $propertyValue; + } + + if ($dataToImport !== []) { + $this->data($dataToImport); + } + + } + + protected function getVarToAutowireDisplayMethod(): array + { + $displayReflection = new ReflectionMethod($this->templateClass, 'display'); + + $parameters = $displayReflection->getParameters(); + + // Extract the parameter names + $parametersToAutowire = []; + foreach ($parameters as $parameter) { + if (in_array($parameter->getType()->getName(), [TemplateClass::class, Template::class], true)) { + $parametersToAutowire[$parameter->getName()] = $this; + + continue; + } + + $parametersToAutowire[$parameter->getName()] = $this->data()[$parameter->getName()] ?? $parameter->getDefaultValue() ?? null; + } + + return $parametersToAutowire; + } protected function autowireDataToTemplateClass() diff --git a/src/Template/TemplateClassInterface.php b/src/Template/TemplateClassInterface.php index f724f3b5..f516ad0b 100644 --- a/src/Template/TemplateClassInterface.php +++ b/src/Template/TemplateClassInterface.php @@ -4,5 +4,5 @@ interface TemplateClassInterface { - public function display(Template $tpl): void; + // public function display(...$args): void; } From 7b3de249a94b675da0aaffa0ebc4914793dd4648 Mon Sep 17 00:00:00 2001 From: RobinDev Date: Sun, 12 Nov 2023 23:56:25 +0100 Subject: [PATCH 05/17] add do not add in constructor interface --- src/RectorizeTemplate.php | 8 ++++++-- src/Template/DoNotAddItInConstructorInterface.php | 7 +++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 src/Template/DoNotAddItInConstructorInterface.php diff --git a/src/RectorizeTemplate.php b/src/RectorizeTemplate.php index 5e230320..a80d445e 100644 --- a/src/RectorizeTemplate.php +++ b/src/RectorizeTemplate.php @@ -4,6 +4,7 @@ namespace League\Plates; +use League\Plates\Template\DoNotAddItInConstructorInterface; use League\Plates\Template\Template; use League\Plates\Template\TemplateClass; use League\Plates\Template\TemplateClassInterface; @@ -27,6 +28,7 @@ */ final class RectorizeTemplate extends AbstractRector { + const CLASS_TO_NOT_ADD_IN_CONSTRUCTOR = [Template::class, TemplateClass::class, DoNotAddItInConstructorInterface::class]; /** * @readonly * @var \Rector\Core\NodeAnalyzer\ParamAnalyzer @@ -108,8 +110,10 @@ public function refactor(Node $node) : ?Node $methodDocBlock = $displayMethod?->getDocComment()?->getText() ?? ''; foreach($displayMethod->params as $parameter) { $paramType = $this->paramTypeResolver->resolve($parameter); - if ($paramType instanceof FullyQualifiedObjectType && in_array($paramType->getClassName(), [Template::class, TemplateClass::class], true)) - continue; + if ($paramType instanceof FullyQualifiedObjectType + && in_array($paramType->getClassName(), self::CLASS_TO_NOT_ADD_IN_CONSTRUCTOR, true)) { + continue; + } $paramDocBlock = $this->getParameterDocblock($methodDocBlock, $this->getName($parameter)); if ($paramDocBlock !== null) diff --git a/src/Template/DoNotAddItInConstructorInterface.php b/src/Template/DoNotAddItInConstructorInterface.php new file mode 100644 index 00000000..d82d5220 --- /dev/null +++ b/src/Template/DoNotAddItInConstructorInterface.php @@ -0,0 +1,7 @@ + Date: Mon, 13 Nov 2023 00:41:11 +0100 Subject: [PATCH 06/17] fix typo --- src/Template/Template.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Template/Template.php b/src/Template/Template.php index 1e653ba7..979361ad 100644 --- a/src/Template/Template.php +++ b/src/Template/Template.php @@ -311,7 +311,7 @@ public function section($name, $default = null) /** * Fetch a rendered template. - * @param string $name|TemplateClassInterface + * @param string|TemplateClassInterface $name * @param array $data * @return string */ @@ -322,7 +322,7 @@ public function fetch($name, array $data = array()) /** * Output a rendered template. - * @param string $name|TemplateClassInterface + * @param string|TemplateClassInterface $name * @param array $data * @return null */ From d38bf687d12e5ab97b56e57afc615a485021e29a Mon Sep 17 00:00:00 2001 From: RobinDev Date: Mon, 13 Nov 2023 00:54:07 +0100 Subject: [PATCH 07/17] update rector config --- exampleTemplateClass/rector.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/exampleTemplateClass/rector.php b/exampleTemplateClass/rector.php index 6a1684da..97882b6a 100644 --- a/exampleTemplateClass/rector.php +++ b/exampleTemplateClass/rector.php @@ -1,15 +1,12 @@ paths([ - __DIR__.'/Templates/Layout.php', - ]); $rectorConfig->rule(RectorizeTemplate::class); }; -// vendor/bin/rector process exampleTemplateClass/Templates --config ./exampleTemplateClass/rector.php +// vendor/bin/rector process exampleTemplateClass/Templates --config ./exampleTemplateClass/rector.php --debug +// vendor/bin/rector process exampleTemplateClass/Templates --config ./vendor/league/plates/exampleTemplateClass/rector.php From 98a4d419ec43397c767964616ca13b7588c7bb1c Mon Sep 17 00:00:00 2001 From: RobinDev Date: Mon, 13 Nov 2023 02:52:29 +0100 Subject: [PATCH 08/17] bug fix (1/2) on rector --- composer.json | 3 +- exampleTemplateClass/Templates/Layout.php | 9 ++- exampleTemplateClass/Templates/Profile.php | 4 +- src/RectorizeTemplate.php | 82 ++++++++++++++++++---- 4 files changed, 75 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index ab230722..7adee852 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,8 @@ "mikey179/vfsstream": "^1.6", "phpunit/phpunit": "^9.5", "squizlabs/php_codesniffer": "^3.5", - "rector/rector": "^0.18.6" + "rector/rector": "^0.18.6", + "phpstan/phpstan": "^1.10" }, "autoload": { "psr-4": { diff --git a/exampleTemplateClass/Templates/Layout.php b/exampleTemplateClass/Templates/Layout.php index 4423c5c1..3b34bc26 100644 --- a/exampleTemplateClass/Templates/Layout.php +++ b/exampleTemplateClass/Templates/Layout.php @@ -11,7 +11,7 @@ class Layout implements TemplateClassInterface /** * @param string[] $test323 */ - public function display(Template $tpl, string|int|null $company = null, ?string $title = null, array $test323 = []): void { ?> + public function display(Template $tpl, string $company, ?string $title = null ): void { ?> <?=$tpl->e($title)?> | <?=$tpl->e($company)?> @@ -27,10 +27,9 @@ public function display(Template $tpl, string|int|null $company = null, ?string paramAnalyzer = $paramAnalyzer; - $this->complexNodeRemover = $complexNodeRemover; $this->paramTypeResolver = $paramTypeResolver; } public function getRuleDefinition() : RuleDefinition @@ -121,6 +114,14 @@ public function refactor(Node $node) : ?Node $cloneParameter = clone $parameter; $cloneParameter->flags = 1; + + if ($parameter->default instanceof Expr) { + + if (! $this->hasNullType($paramType) && $parameter->default instanceof \PhpParser\Node\Expr\ConstFetch && $this->hasNullValue($parameter)) { + $this->addTypeToParameter($cloneParameter, 'null'); + } + } + $paramsForConstructor[] = $cloneParameter; } @@ -129,7 +130,7 @@ public function refactor(Node $node) : ?Node $constructor = $node->getMethod(MethodName::CONSTRUCT) ; $docBlockConstructor = $constructor?->getDocComment(); - $newDocBlockConstructor = new Doc("/**\n * ".trim("Autogenerated constructor\n * ".implode("\n * ", $docBlockForConstructor), "\n *")."\n*/"); + $newDocBlockConstructor = new Doc("/**\n * ".trim("Autogenerated constructor.\n * ".implode("\n * ", $docBlockForConstructor), "\n *")."\n */"); if ($paramsForConstructor === $constructor?->params || $docBlockConstructor === $newDocBlockConstructor) // TODO compare docblock return null; @@ -141,12 +142,37 @@ public function refactor(Node $node) : ?Node ]); $constructor->setDocComment($newDocBlockConstructor); - $node->stmts[] = $constructor; return $node; } + private function hasNullType(\PHPStan\Type\Type $type):bool + { + if ($type instanceof \PHPStan\Type\NullType) + return true; + + if ($type instanceof \PHPStan\Type\UnionType) { + foreach($type->getTypes() as $type) { + if ($this->hasNullType($type)) { + return true; + } + } + } + + return false; + } + + private function hasNullValue(Param $param): bool + { + $text = (new BetterStandardPrinter())->print($param); + + if (str_ends_with($text, 'null')) + return true; + + return false; + } + private function removeConstructor(Class_ $class):null { foreach ($class->stmts as $key => $stmt) { @@ -176,4 +202,30 @@ private function getParameterDocblock(?string $methodDocblock, string $parameter return null; } + + + private function addTypeToParameter( Param $param, string $type): void + { + + $existingType = $this->nodeTypeResolver->getType($param); + + if ($existingType instanceof \PHPStan\Type\UnionType) { + $existingTypes = $existingType->getTypes(); + $newTypes = [...$existingTypes, new \PHPStan\Type\NullType()]; + $param->type = new UnionType($newTypes); + return; + } + + if ( $existingType === null) { + $param->type = new \PHPStan\Type\NullType(); + return; + } + + $param->type = null; + // The following code is not working + //$param->type = new UnionType([$existingType, new \PHPStan\Type\NullType()]); + // Get it worked using nullable_type_declaration_for_default_null_value for phpcsfixer + + } + } \ No newline at end of file From 4f16682f0d6d4babec8943e4a4f69a4753f21196 Mon Sep 17 00:00:00 2001 From: RobinDev Date: Mon, 13 Nov 2023 09:52:06 +0100 Subject: [PATCH 09/17] better do not add to constructor + cs + rector --- src/RectorizeTemplate.php | 137 ++++++++++++++++++++------------------ 1 file changed, 74 insertions(+), 63 deletions(-) diff --git a/src/RectorizeTemplate.php b/src/RectorizeTemplate.php index 8a5582ea..ac65fcc4 100644 --- a/src/RectorizeTemplate.php +++ b/src/RectorizeTemplate.php @@ -1,6 +1,6 @@ paramTypeResolver = $paramTypeResolver; + public function __construct( + private readonly ParamTypeResolver $paramTypeResolver + ) { } - public function getRuleDefinition() : RuleDefinition + + public function getRuleDefinition(): RuleDefinition { - return new RuleDefinition('Duplicate diplay to constructor except Template', [new CodeSample(<<<'CODE_SAMPLE' + return new RuleDefinition('Duplicate diplay to constructor except Template', [new CodeSample( + <<<'CODE_SAMPLE' final class SomeTemplate { public function display(string $name, Template $t): void @@ -57,7 +51,8 @@ public function display(string $name, Template $t): void } } CODE_SAMPLE -, <<<'CODE_SAMPLE' + , + <<<'CODE_SAMPLE' final class SomeClass { public function display(string $name, Template $t): void @@ -70,12 +65,13 @@ public function __construct(public string $name) } } CODE_SAMPLE -)]); + )]); } + /** * @return array> */ - public function getNodeTypes() : array + public function getNodeTypes(): array { return [Class_::class]; } @@ -83,61 +79,64 @@ public function getNodeTypes() : array /** * @param Class_ $node */ - public function refactor(Node $node) : ?Node + public function refactor(Node $node): ?Node { - $implementedInterfaces = array_map( fn (Name $interface) => $interface->toString(), $node->implements ); - if (! in_array(TemplateClassInterface::class, $implementedInterfaces, true)) { + $implementedInterfaces = array_map(static fn (Name $interface): string => $interface->toString(), $node->implements); + if (! \in_array(TemplateClassInterface::class, $implementedInterfaces, true)) { return null; } - $displayMethod = $node->getMethod('display') ; - if ($displayMethod === null) + $displayMethod = $node->getMethod('display'); + if (null === $displayMethod) { return null; + } - - if ($displayMethod->params === []) + if ([] === $displayMethod->params) { return $this->removeConstructor($node); + } $paramsForConstructor = []; $docBlockForConstructor = []; $methodDocBlock = $displayMethod?->getDocComment()?->getText() ?? ''; - foreach($displayMethod->params as $parameter) { + foreach ($displayMethod->params as $parameter) { $paramType = $this->paramTypeResolver->resolve($parameter); - if ($paramType instanceof FullyQualifiedObjectType - && in_array($paramType->getClassName(), self::CLASS_TO_NOT_ADD_IN_CONSTRUCTOR, true)) { - continue; + if ($paramType instanceof FullyQualifiedObjectType && ! $this->mustAddObjectInConstructor($paramType)) { + continue; } $paramDocBlock = $this->getParameterDocblock($methodDocBlock, $this->getName($parameter)); - if ($paramDocBlock !== null) + if (null !== $paramDocBlock) { $docBlockForConstructor[] = $paramDocBlock; + } $cloneParameter = clone $parameter; $cloneParameter->flags = 1; - if ($parameter->default instanceof Expr) { - - if (! $this->hasNullType($paramType) && $parameter->default instanceof \PhpParser\Node\Expr\ConstFetch && $this->hasNullValue($parameter)) { - $this->addTypeToParameter($cloneParameter, 'null'); - } + if ($parameter->default instanceof Expr + && (! $this->hasNullType($paramType) + && $parameter->default instanceof ConstFetch + && $this->hasNullValue($parameter))) { + $this->addTypeToParameter($cloneParameter, 'null'); } $paramsForConstructor[] = $cloneParameter; } - if ($paramsForConstructor === []) + if ([] === $paramsForConstructor) { return $this->removeConstructor($node); + } - $constructor = $node->getMethod(MethodName::CONSTRUCT) ; + $constructor = $node->getMethod(MethodName::CONSTRUCT); $docBlockConstructor = $constructor?->getDocComment(); $newDocBlockConstructor = new Doc("/**\n * ".trim("Autogenerated constructor.\n * ".implode("\n * ", $docBlockForConstructor), "\n *")."\n */"); - if ($paramsForConstructor === $constructor?->params || $docBlockConstructor === $newDocBlockConstructor) // TODO compare docblock + if ($paramsForConstructor === $constructor?->params || $docBlockConstructor === $newDocBlockConstructor) { // TODO compare docblock return null; + } $this->removeConstructor($node); $constructor = new ClassMethod('__construct', [ - 'flags' => Node\Stmt\Class_::MODIFIER_PUBLIC, + 'flags' => Class_::MODIFIER_PUBLIC, 'params' => $paramsForConstructor, ]); $constructor->setDocComment($newDocBlockConstructor); @@ -147,13 +146,14 @@ public function refactor(Node $node) : ?Node return $node; } - private function hasNullType(\PHPStan\Type\Type $type):bool + private function hasNullType(Type $type): bool { - if ($type instanceof \PHPStan\Type\NullType) + if ($type instanceof NullType) { return true; + } if ($type instanceof \PHPStan\Type\UnionType) { - foreach($type->getTypes() as $type) { + foreach ($type->getTypes() as $type) { if ($this->hasNullType($type)) { return true; } @@ -167,17 +167,15 @@ private function hasNullValue(Param $param): bool { $text = (new BetterStandardPrinter())->print($param); - if (str_ends_with($text, 'null')) - return true; - - return false; + return str_ends_with($text, 'null'); } - private function removeConstructor(Class_ $class):null + private function removeConstructor(Class_ $class): null { foreach ($class->stmts as $key => $stmt) { - if ($stmt instanceof Node\Stmt\ClassMethod && $stmt->name->toString() === MethodName::CONSTRUCT) { + if ($stmt instanceof ClassMethod && MethodName::CONSTRUCT === $stmt->name->toString()) { unset($class->stmts[$key]); + break; } } @@ -187,7 +185,7 @@ private function removeConstructor(Class_ $class):null private function getParameterDocblock(?string $methodDocblock, string $parameterName): ?string { - if ($methodDocblock === null) { + if (null === $methodDocblock) { return null; } @@ -203,29 +201,42 @@ private function getParameterDocblock(?string $methodDocblock, string $parameter return null; } - - private function addTypeToParameter( Param $param, string $type): void + private function mustAddObjectInConstructor(FullyQualifiedObjectType $paramType): bool { + if (\in_array($paramType->getClassName(), self::CLASS_TO_NOT_ADD_IN_CONSTRUCTOR, true)) { + return false; + } + + foreach (self::CLASS_TO_NOT_ADD_IN_CONSTRUCTOR as $classToCheck) { + if (is_subclass_of($paramType->getClassName(), $classToCheck)) { + return false; + } + } + + return true; + } + private function addTypeToParameter(Param $param, string $type): void + { $existingType = $this->nodeTypeResolver->getType($param); if ($existingType instanceof \PHPStan\Type\UnionType) { $existingTypes = $existingType->getTypes(); - $newTypes = [...$existingTypes, new \PHPStan\Type\NullType()]; + $newTypes = [...$existingTypes, new NullType()]; $param->type = new UnionType($newTypes); + return; } - if ( $existingType === null) { - $param->type = new \PHPStan\Type\NullType(); + if (null === $existingType) { + $param->type = new NullType(); + return; } $param->type = null; // The following code is not working - //$param->type = new UnionType([$existingType, new \PHPStan\Type\NullType()]); + // $param->type = new UnionType([$existingType, new \PHPStan\Type\NullType()]); // Get it worked using nullable_type_declaration_for_default_null_value for phpcsfixer - } - -} \ No newline at end of file +} From 9001a4a85e64a9ba91268cef3eef028832b661f6 Mon Sep 17 00:00:00 2001 From: RobinDev Date: Mon, 13 Nov 2023 10:25:34 +0100 Subject: [PATCH 10/17] autowire template to template public property if exists --- src/Template/TemplateClass.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Template/TemplateClass.php b/src/Template/TemplateClass.php index 2db2f9e0..dd8d07c5 100644 --- a/src/Template/TemplateClass.php +++ b/src/Template/TemplateClass.php @@ -81,6 +81,9 @@ protected function autowireDataToTemplateClass() if ($propertyValue !== null) { continue; } + if ($propertyName === 'template') { + $this->templateClass->template = $this; + } $this->templateClass->$propertyName = $this->data[$propertyName] ?? null; } From 56affacca358be2d2ef01b89aa552bc4a608985b Mon Sep 17 00:00:00 2001 From: RobinDev Date: Mon, 13 Nov 2023 10:56:21 +0100 Subject: [PATCH 11/17] autowire template and not override it with a data --- src/Template/TemplateClass.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Template/TemplateClass.php b/src/Template/TemplateClass.php index dd8d07c5..0b987651 100644 --- a/src/Template/TemplateClass.php +++ b/src/Template/TemplateClass.php @@ -83,6 +83,7 @@ protected function autowireDataToTemplateClass() } if ($propertyName === 'template') { $this->templateClass->template = $this; + continue; } $this->templateClass->$propertyName = $this->data[$propertyName] ?? null; From 764432ead1ca4971844730f812fb82e21414a905 Mon Sep 17 00:00:00 2001 From: RobinDev Date: Mon, 13 Nov 2023 11:09:36 +0100 Subject: [PATCH 12/17] shorter alias for fetch from templateClass --- exampleTemplateClass/Templates/Profile.php | 5 +++-- src/RectorizeTemplate.php | 10 ++++++++-- src/Template/TemplateClass.php | 6 ++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/exampleTemplateClass/Templates/Profile.php b/exampleTemplateClass/Templates/Profile.php index fd175ff2..c702c0f9 100644 --- a/exampleTemplateClass/Templates/Profile.php +++ b/exampleTemplateClass/Templates/Profile.php @@ -8,14 +8,15 @@ class Profile implements TemplateClassInterface { - public function display(Template $t, string $name): void { ?> + public function display(Template $t, callable $f, string $name): void { ?> layout(new Layout('User Profile')) ?> layout('layout', ['title' => 'User Profile']) // this is working too and will get the example/templates/layout.php ?>

    User Profile

    Hello, e($name)?>!

    -insert(new Sidebar()) ?> +insert(new Sidebar()) ?> + push('scripts') ?>