Skip to content

Commit

Permalink
create rector rule
Browse files Browse the repository at this point in the history
  • Loading branch information
RobinDev committed Nov 12, 2023
1 parent 6296ff0 commit df1c0df
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 25 deletions.
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
19 changes: 12 additions & 7 deletions exampleTemplateClass/Templates/Layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 { ?>
<html>
<head>
<title><?=$tpl->e($this->title)?> | <?=$tpl->e($this->company)?></title>
<title><?=$tpl->e($title)?> | <?=$tpl->e($company)?></title>
</head>
<body>

Expand All @@ -28,4 +26,11 @@ public function display(Template $tpl): void { ?>
</html>
<?php
}
/**
* Autogenerated constructor
* @param string[] $test323
*/
public function __construct(public string|int|null $company = null, public ?string $title = null, public array $test323 = [])
{
}
}
25 changes: 13 additions & 12 deletions exampleTemplateClass/Templates/Profile.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,26 @@
class Profile implements TemplateClassInterface
{

public function __construct(
public string $name,
) {}

public function display(Template $tpl): void { ?>
<?php $tpl->layout(new Layout('User Profile')) ?>
<?php // $this->layout('layout', ['title' => 'User Profile']) // this is working too ! ?>
<?php // $this->layout(new Layout(), ['title' => 'User Profile']) // this is working too ! ?>
public function display(Template $t, string $name): void { ?>
<?php $t->layout(new Layout('User Profile')) ?>
<?php // $this->layout('layout', ['title' => 'User Profile']) // this is working too and will get the example/templates/layout.php ?>

<h1>User Profile</h1>
<p>Hello, <?=$tpl->e($this->name)?>!</p>
<p>Hello, <?=$t->e($name)?>!</p>

<?php $tpl->insert(new Sidebar()) ?>
<?php $t->insert(new Sidebar()) ?>

<?php $tpl->push('scripts') ?>
<?php $t->push('scripts') ?>
<script>
// Some JavaScript
</script>
<?php $tpl->end() ?>
<?php $t->end() ?>
<?php
}
/**
* Autogenerated constructor
*/
public function __construct(public string $name)
{
}
}
2 changes: 1 addition & 1 deletion exampleTemplateClass/Templates/Sidebar.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class Sidebar implements TemplateClassInterface
{

public function display(Template $tpl): void { ?>
public function display(): void { ?>
<ul>
<li><a href="#link">Example sidebar link</a></li>
<li><a href="#link">Example sidebar link</a></li>
Expand Down
15 changes: 15 additions & 0 deletions exampleTemplateClass/rector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

include __DIR__.'/../vendor/autoload.php';

use League\Plates\RectorizeTemplate;
use Rector\Config\RectorConfig;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
__DIR__.'/Templates/Layout.php',
]);
$rectorConfig->rule(RectorizeTemplate::class);
};

// vendor/bin/rector process exampleTemplateClass/Templates --config ./exampleTemplateClass/rector.php
175 changes: 175 additions & 0 deletions src/RectorizeTemplate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<?php

declare (strict_types=1);

namespace League\Plates;

use League\Plates\Template\Template;
use League\Plates\Template\TemplateClass;
use League\Plates\Template\TemplateClassInterface;
use PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\ConstructorNameSniff;
use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Core\NodeAnalyzer\ParamAnalyzer;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\MethodName;
use Rector\NodeTypeResolver\NodeTypeResolver\ParamTypeResolver;
use Rector\Removing\NodeManipulator\ComplexNodeRemover;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use Rector\TypeDeclaration\NodeAnalyzer\CallTypesResolver;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUnusedConstructorParamRector\RemoveUnusedConstructorParamRectorTest
*/
final class RectorizeTemplate extends AbstractRector
{
/**
* @readonly
* @var \Rector\Core\NodeAnalyzer\ParamAnalyzer
*/
private $paramAnalyzer;
/**
* @readonly
* @var \Rector\Removing\NodeManipulator\ComplexNodeRemover
*/
private $complexNodeRemover;


/**
* @readonly
* @var ParamTypeResolver
*/
private $paramTypeResolver;

public function __construct(ParamTypeResolver $paramTypeResolver, ParamAnalyzer $paramAnalyzer, ComplexNodeRemover $complexNodeRemover)
{
$this->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<class-string<Node>>
*/
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;
}
}
50 changes: 48 additions & 2 deletions src/Template/TemplateClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion src/Template/TemplateClassInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@

interface TemplateClassInterface
{
public function display(Template $tpl): void;
// public function display(...$args): void;
}

0 comments on commit df1c0df

Please sign in to comment.