-
-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TASK: Extract type converters to services.yaml (#3212)
* TASK: Extract type converters to services.yaml * TASK: Remove obsolete methods from TypeConverters
- Loading branch information
1 parent
56946ae
commit ca72ca1
Showing
17 changed files
with
556 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Ssch\TYPO3Rector\NodeVisitor; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Stmt\ClassMethod; | ||
use PhpParser\NodeTraverser; | ||
use PhpParser\NodeVisitor; | ||
use Rector\NodeNameResolver\NodeNameResolver; | ||
|
||
final class RemoveExtbaseTypeConverterNodeVisitor implements NodeVisitor | ||
{ | ||
private NodeNameResolver $nodeNameResolver; | ||
|
||
public function __construct(NodeNameResolver $nodeNameResolver) | ||
{ | ||
$this->nodeNameResolver = $nodeNameResolver; | ||
} | ||
|
||
public function beforeTraverse(array $nodes): ?array | ||
{ | ||
return $nodes; | ||
} | ||
|
||
public function enterNode(Node $node): Node | ||
{ | ||
return $node; | ||
} | ||
|
||
public function leaveNode(Node $node) | ||
{ | ||
if (! $node instanceof ClassMethod) { | ||
return $node; | ||
} | ||
|
||
if (! $this->nodeNameResolver->isNames( | ||
$node->name, | ||
['getSupportedSourceTypes', 'getSupportedTargetType', 'getPriority', 'canConvertFrom'] | ||
)) { | ||
return $node; | ||
} | ||
|
||
return NodeTraverser::REMOVE_NODE; | ||
} | ||
|
||
public function afterTraverse(array $nodes): ?array | ||
{ | ||
return $nodes; | ||
} | ||
} |
268 changes: 268 additions & 0 deletions
268
src/Rector/v12/v0/typo3/RegisterExtbaseTypeConvertersAsServicesRector.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Ssch\TYPO3Rector\Rector\v12\v0\typo3; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Expr\StaticCall; | ||
use PhpParser\Node\Stmt\ClassMethod; | ||
use PhpParser\NodeTraverser; | ||
use PHPStan\Reflection\ReflectionProvider; | ||
use PHPStan\Type\ObjectType; | ||
use Rector\Core\Application\FileSystem\RemovedAndAddedFilesCollector; | ||
use Rector\Core\PhpParser\Parser\SimplePhpParser; | ||
use Rector\Core\PhpParser\Printer\NodesWithFileDestinationPrinter; | ||
use Rector\Core\Rector\AbstractRector; | ||
use Rector\FileSystemRector\ValueObject\AddedFileWithContent; | ||
use Rector\FileSystemRector\ValueObject\AddedFileWithNodes; | ||
use Ssch\TYPO3Rector\Helper\FilesFinder; | ||
use Ssch\TYPO3Rector\NodeVisitor\RemoveExtbaseTypeConverterNodeVisitor; | ||
use Symfony\Component\Yaml\Yaml; | ||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; | ||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; | ||
use Symplify\SmartFileSystem\SmartFileInfo; | ||
|
||
/** | ||
* @changelog https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/12.0/Breaking-94117-RegisterExtbaseTypeConvertersAsServices.html | ||
* @see \Ssch\TYPO3Rector\Tests\Rector\v12\v0\typo3\RegisterExtbaseTypeConvertersAsServicesRector\RegisterExtbaseTypeConvertersAsServicesRectorTest | ||
*/ | ||
final class RegisterExtbaseTypeConvertersAsServicesRector extends AbstractRector | ||
{ | ||
/** | ||
* @readonly | ||
*/ | ||
private ReflectionProvider $reflectionProvider; | ||
|
||
/** | ||
* @readonly | ||
*/ | ||
private SimplePhpParser $simplePhpParser; | ||
|
||
/** | ||
* @readonly | ||
*/ | ||
private FilesFinder $filesFinder; | ||
|
||
private RemovedAndAddedFilesCollector $removedAndAddedFilesCollector; | ||
|
||
private NodesWithFileDestinationPrinter $nodesWithFileDestinationPrinter; | ||
|
||
public function __construct( | ||
ReflectionProvider $reflectionProvider, | ||
SimplePhpParser $simplePhpParser, | ||
FilesFinder $filesFinder, | ||
RemovedAndAddedFilesCollector $removedAndAddedFilesCollector, | ||
NodesWithFileDestinationPrinter $nodesWithFileDestinationPrinter | ||
) { | ||
$this->reflectionProvider = $reflectionProvider; | ||
$this->simplePhpParser = $simplePhpParser; | ||
$this->filesFinder = $filesFinder; | ||
$this->removedAndAddedFilesCollector = $removedAndAddedFilesCollector; | ||
$this->nodesWithFileDestinationPrinter = $nodesWithFileDestinationPrinter; | ||
} | ||
|
||
/** | ||
* @return array<class-string<Node>> | ||
*/ | ||
public function getNodeTypes(): array | ||
{ | ||
return [StaticCall::class]; | ||
} | ||
|
||
/** | ||
* @param StaticCall $node | ||
*/ | ||
public function refactor(Node $node): ?Node | ||
{ | ||
if ($this->shouldSkip($node)) { | ||
return null; | ||
} | ||
|
||
$className = $this->valueResolver->getValue($node->args[0]->value); | ||
|
||
if (! $this->reflectionProvider->hasClass($className)) { | ||
return null; | ||
} | ||
|
||
$classReflection = $this->reflectionProvider->getClass($className); | ||
|
||
$fileName = $classReflection->getFileName(); | ||
|
||
if (! is_string($fileName) || ! file_exists($fileName)) { | ||
return null; | ||
} | ||
|
||
$extEmConf = $this->filesFinder->findExtEmConfRelativeFromGivenFileInfo( | ||
new SmartFileInfo($this->file->getFilePath()) | ||
); | ||
|
||
if (null === $extEmConf) { | ||
return null; | ||
} | ||
|
||
$classStatements = $this->simplePhpParser->parseFile($fileName); | ||
$originalClassStatements = $classStatements; | ||
|
||
$collectServiceTags = $this->collectServiceTags($classStatements); | ||
|
||
$existingServicesYamlFilePath = $extEmConf->getRealPathDirectory() . '/Configuration/Services.yaml'; | ||
|
||
$yamlConfiguration = $this->getYamlConfiguration($existingServicesYamlFilePath); | ||
|
||
if (! isset($yamlConfiguration['services'])) { | ||
$yamlConfiguration['services'] = []; | ||
} | ||
|
||
if (! isset($yamlConfiguration['services'][$className])) { | ||
$yamlConfiguration['services'][$className]['tags'] = $collectServiceTags; | ||
} | ||
|
||
$yamlConfigurationAsYaml = Yaml::dump($yamlConfiguration, 99); | ||
|
||
$servicesYaml = new AddedFileWithContent($existingServicesYamlFilePath, $yamlConfigurationAsYaml); | ||
$this->removedAndAddedFilesCollector->addAddedFile($servicesYaml); | ||
|
||
$this->nodeRemover->removeNode($node); | ||
$this->removeClassMethodsFromTypeConverter($fileName, $originalClassStatements); | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* @codeCoverageIgnore | ||
*/ | ||
public function getRuleDefinition(): RuleDefinition | ||
{ | ||
return new RuleDefinition('Register extbase type converters as services', [ | ||
new CodeSample( | ||
<<<'CODE_SAMPLE' | ||
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerTypeConverter( | ||
MySpecialTypeConverter::class | ||
); | ||
CODE_SAMPLE | ||
, | ||
<<<'CODE_SAMPLE' | ||
// Remove node and add or modify existing Services.yaml in Configuration/Services.yaml | ||
CODE_SAMPLE | ||
), | ||
]); | ||
} | ||
|
||
/** | ||
* @param Node[] $classStatements | ||
* | ||
* @return array<string, string|int> | ||
*/ | ||
protected function collectServiceTags(array $classStatements): array | ||
{ | ||
$collectServiceTags = [ | ||
'name' => 'extbase.type_converter', | ||
]; | ||
|
||
$this->traverseNodesWithCallable($classStatements, function (Node $node) use (&$collectServiceTags) { | ||
if ($this->shouldSkipNodeOfTypeConverter($node)) { | ||
return null; | ||
} | ||
|
||
if (! $node instanceof ClassMethod || null === $node->stmts) { | ||
return null; | ||
} | ||
|
||
/** @var Node\Stmt\Return_[] $returns */ | ||
$returns = $this->betterNodeFinder->findInstanceOf($node->stmts, Node\Stmt\Return_::class); | ||
|
||
$value = null; | ||
foreach ($returns as $return) { | ||
if (null === $return->expr) { | ||
continue; | ||
} | ||
|
||
$value = $this->valueResolver->getValue($return->expr); | ||
} | ||
|
||
if ($this->isName($node->name, 'getPriority')) { | ||
$collectServiceTags['priority'] = $value; | ||
} | ||
|
||
if ($this->isName($node->name, 'getSupportedSourceTypes')) { | ||
$collectServiceTags['sources'] = implode(',', $value); | ||
} | ||
|
||
if ($this->isName($node->name, 'getSupportedTargetType')) { | ||
$collectServiceTags['target'] = $value; | ||
} | ||
}); | ||
|
||
return $collectServiceTags; | ||
} | ||
|
||
private function shouldSkip(StaticCall $node): bool | ||
{ | ||
if (! $this->nodeTypeResolver->isMethodStaticCallOrClassMethodObjectType( | ||
$node, | ||
new ObjectType('TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility') | ||
)) { | ||
return true; | ||
} | ||
|
||
if (! $this->nodeNameResolver->isName($node->name, 'registerTypeConverter')) { | ||
return true; | ||
} | ||
|
||
return ! isset($node->args[0]); | ||
} | ||
|
||
/** | ||
* @return array<mixed> | ||
*/ | ||
private function getYamlConfiguration(string $existingServicesYamlFilePath): array | ||
{ | ||
$yamlConfiguration = []; | ||
|
||
if (file_exists($existingServicesYamlFilePath)) { | ||
return Yaml::parse((string) file_get_contents($existingServicesYamlFilePath)); | ||
} | ||
|
||
$addedFilesWithContent = $this->removedAndAddedFilesCollector->getAddedFilesWithContent(); | ||
foreach ($addedFilesWithContent as $addedFileWithContent) { | ||
if ($addedFileWithContent->getFilePath() === $existingServicesYamlFilePath) { | ||
return Yaml::parse($addedFileWithContent->getFileContent()); | ||
} | ||
} | ||
|
||
return $yamlConfiguration; | ||
} | ||
|
||
private function shouldSkipNodeOfTypeConverter(Node $node): bool | ||
{ | ||
if (! $node instanceof ClassMethod) { | ||
return true; | ||
} | ||
|
||
if (! $this->nodeNameResolver->isNames( | ||
$node->name, | ||
['getSupportedSourceTypes', 'getSupportedTargetType', 'getPriority'] | ||
)) { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* @param Node\Stmt[] $classStatements | ||
*/ | ||
private function removeClassMethodsFromTypeConverter(string $fileName, array $classStatements): void | ||
{ | ||
$nodeTraverser = new NodeTraverser(); | ||
$visitor = new RemoveExtbaseTypeConverterNodeVisitor($this->nodeNameResolver); | ||
|
||
$nodeTraverser->addVisitor($visitor); | ||
$nodeTraverser->traverse($classStatements); | ||
|
||
$addedFileWithNodes = new AddedFileWithNodes($fileName, $classStatements); | ||
$content = $this->nodesWithFileDestinationPrinter->printNodesWithFileDestination($addedFileWithNodes); | ||
$this->removedAndAddedFilesCollector->addAddedFile(new AddedFileWithContent($fileName, $content)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
...terExtbaseTypeConvertersAsServicesRector/Fixture/ExpectedMySecondSpecialTypeConverter.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
namespace Ssch\TYPO3Rector\Tests\Rector\v12\v0\typo3\RegisterExtbaseTypeConvertersAsServicesRector\Source; | ||
|
||
use TYPO3\CMS\Extbase\Property\TypeConverterInterface; | ||
final class MySecondSpecialTypeConverter implements TypeConverterInterface | ||
{ | ||
} |
9 changes: 9 additions & 0 deletions
9
.../RegisterExtbaseTypeConvertersAsServicesRector/Fixture/ExpectedMySpecialTypeConverter.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
namespace Ssch\TYPO3Rector\Tests\Rector\v12\v0\typo3\RegisterExtbaseTypeConvertersAsServicesRector\Source; | ||
|
||
use TYPO3\CMS\Extbase\Property\TypeConverterInterface; | ||
final class MySpecialTypeConverter implements TypeConverterInterface | ||
{ | ||
} |
Oops, something went wrong.