Skip to content

Commit

Permalink
TASK: Extract type converters to services.yaml (#3212)
Browse files Browse the repository at this point in the history
* TASK: Extract type converters to services.yaml

* TASK: Remove obsolete methods from TypeConverters
  • Loading branch information
sabbelasichon authored Nov 13, 2022
1 parent 56946ae commit ca72ca1
Show file tree
Hide file tree
Showing 17 changed files with 556 additions and 2 deletions.
1 change: 1 addition & 0 deletions config/v12/typo3-120.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,5 @@
$rectorConfig->rule(\Ssch\TYPO3Rector\Rector\v12\v0\typo3\UseConfigArrayForTSFEPropertiesRector::class);
$rectorConfig->rule(\Ssch\TYPO3Rector\Rector\v12\v0\typo3\ReplacePageRepoOverlayFunctionRector::class);
$rectorConfig->rule(\Ssch\TYPO3Rector\Rector\v12\v0\typo3\ImplementSiteLanguageAwareInterfaceRector::class);
$rectorConfig->rule(\Ssch\TYPO3Rector\Rector\v12\v0\typo3\RegisterExtbaseTypeConvertersAsServicesRector::class);
};
3 changes: 2 additions & 1 deletion ecs.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@
]);

$ecsConfig->skip([
__DIR__ . '/tests/Rector/v12/v0/typo3/RegisterExtbaseTypeConvertersAsServicesRector/Fixture/ExpectedMySpecialTypeConverter.php',
__DIR__ . '/tests/Rector/v12/v0/typo3/RegisterExtbaseTypeConvertersAsServicesRector/Fixture/ExpectedMySecondSpecialTypeConverter.php',
// on php 8.1, it adds space on &$variable
FunctionTypehintSpaceFixer::class => [
__DIR__ . '/src/FileProcessor/Yaml/Form/Rector/EmailFinisherRector.php',
__DIR__ . '/src/FileProcessor/Yaml/Form/Rector/TranslationFileRector.php',
],
__DIR__ . '/config/composer',
__DIR__ . '/utils/generator/templates/src',
AssignmentInConditionSniff::class,
DeclareStrictTypesFixer::class => ['*/Fixture/*'],
Expand Down
52 changes: 52 additions & 0 deletions src/NodeVisitor/RemoveExtbaseTypeConverterNodeVisitor.php
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;
}
}
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));
}
}
11 changes: 11 additions & 0 deletions stubs/TYPO3/CMS/Extbase/Property/TypeConverterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,16 @@

interface TypeConverterInterface
{
public function getSupportedSourceTypes(): array;

public function getSupportedTargetType(): string;

/**
* Return the priority of this TypeConverter. TypeConverters with a high priority are chosen before low priority.
*
* @return int
*/
public function getPriority(): int;

public function canConvertFrom($source, string $targetType): bool;
}
5 changes: 5 additions & 0 deletions stubs/TYPO3/CMS/Extbase/Utility/ExtensionUtility.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,9 @@ public static function registerModule($extensionName, $mainModuleName = '', $sub
public static function configurePlugin($extensionName, $pluginName, array $controllerActions, array $nonCacheableControllerActions = [], $pluginType = self::PLUGIN_TYPE_PLUGIN)
{
}

public static function registerTypeConverter($typeConverterClassName): void
{

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ final class IconsRectorTest extends AbstractRectorTestCase
*/
public function test(string $filePath): void
{
#$this->markTestIncomplete('Could not find a way to make this work again for now');
$this->doTestFile($filePath);
$this->assertSame(1, $this->removedAndAddedFilesCollector->getAddedFileCount());
}
Expand Down
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
{
}
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
{
}
Loading

0 comments on commit ca72ca1

Please sign in to comment.