136 lines
4.6 KiB
PHP
136 lines
4.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* This file is part of the TYPO3 CMS project.
|
|
*
|
|
* It is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, either version 2
|
|
* of the License, or any later version.
|
|
*
|
|
* For the full copyright and license information, please read the
|
|
* LICENSE.txt file that was distributed with this source code.
|
|
*
|
|
* The TYPO3 project - inspiring people to share!
|
|
*/
|
|
|
|
namespace TYPO3\CMS\Install\ExtensionScanner\Php;
|
|
|
|
use PhpParser\BuilderFactory;
|
|
use PhpParser\Node;
|
|
use PhpParser\Node\Expr;
|
|
use PhpParser\Node\Expr\ClassConstFetch;
|
|
use PhpParser\Node\Expr\New_;
|
|
use PhpParser\Node\Expr\StaticCall;
|
|
use PhpParser\Node\Name\FullyQualified;
|
|
use PhpParser\Node\Scalar\String_;
|
|
use PhpParser\NodeVisitorAbstract;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\AbstractCoreMatcher;
|
|
|
|
/**
|
|
* Create a fully qualified class name object from first argument of
|
|
* GeneralUtility::makeInstance('My\\Package\\Class\\Name') if given as string
|
|
* and not as My\Package\Class\Name::class language construct.
|
|
*
|
|
* This resolver is to be called after generic NameResolver::class, but before
|
|
* other search and find visitors that implement CodeScannerInterface::class
|
|
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
|
*/
|
|
class GeneratorClassesResolver extends NodeVisitorAbstract
|
|
{
|
|
/**
|
|
* @var BuilderFactory
|
|
*/
|
|
protected $builderFactory;
|
|
|
|
public function __construct(?BuilderFactory $builderFactory = null)
|
|
{
|
|
$this->builderFactory = $builderFactory ?? new BuilderFactory();
|
|
}
|
|
|
|
/**
|
|
* Called by PhpParser.
|
|
* Create an fqdn object from first makeInstance argument if it is a String
|
|
*
|
|
* @param Node $node Incoming node
|
|
*/
|
|
public function enterNode(Node $node)
|
|
{
|
|
if ($node instanceof StaticCall
|
|
&& $node->class instanceof FullyQualified
|
|
&& $node->class->toString() === GeneralUtility::class
|
|
&& $node->name->name === 'makeInstance'
|
|
&& isset($node->args[0]->value)
|
|
&& $node->args[0]->value instanceof Expr
|
|
) {
|
|
$argValue = $node->args[0]->value;
|
|
$argAlternative = $this->substituteClassString($argValue);
|
|
if ($argAlternative !== null) {
|
|
$node->args[0]->value = $argAlternative;
|
|
$argValue = $argAlternative;
|
|
}
|
|
|
|
$nodeAlternative = $this->substituteMakeInstance($node, $argValue);
|
|
if ($nodeAlternative !== null) {
|
|
$node->setAttribute(AbstractCoreMatcher::NODE_RESOLVED_AS, $nodeAlternative);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Substitutes class-string values with their corresponding class constant
|
|
* representation (`'Vendor\\ClassName'` -> `\Vendor\ClassName::class`).
|
|
*/
|
|
protected function substituteClassString(Expr $argValue): ?ClassConstFetch
|
|
{
|
|
// skip non-strings, and those starting with (invalid) namespace separator
|
|
if (!$argValue instanceof String_ || $argValue->value[0] === '\\') {
|
|
return null;
|
|
}
|
|
|
|
$classString = ltrim($argValue->value, '\\');
|
|
$className = new FullyQualified($classString);
|
|
$classArg = $this->builderFactory->classConstFetch($className, 'class');
|
|
$this->duplicateNodeAttributes($argValue, $className, $classArg);
|
|
return $classArg;
|
|
}
|
|
|
|
/**
|
|
* Substitutes `makeInstance` invocations with proper `new` invocations.
|
|
* `GeneralUtility(\Vendor\ClassName::class, 'a', 'b')` -> `new \Vendor\ClassName('a', 'b')`
|
|
*/
|
|
protected function substituteMakeInstance(StaticCall $node, Expr $argValue): ?New_
|
|
{
|
|
if (!$argValue instanceof ClassConstFetch
|
|
|| !$argValue->class instanceof FullyQualified
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
$newExpr = $this->builderFactory->new(
|
|
$argValue->class,
|
|
array_slice($node->args, 1),
|
|
);
|
|
$this->duplicateNodeAttributes($node, $newExpr);
|
|
return $newExpr;
|
|
}
|
|
|
|
/**
|
|
* Duplicates node positions in source file, based on the assumption
|
|
* that only lines are relevant. In case this shall be used for
|
|
* code-migration, real offset positions would be required.
|
|
*/
|
|
protected function duplicateNodeAttributes(Node $source, Node ...$targets): void
|
|
{
|
|
foreach ($targets as $target) {
|
|
$target->setAttributes([
|
|
'startLine' => $source->getStartLine(),
|
|
'endLine' => $source->getEndLine(),
|
|
]);
|
|
}
|
|
}
|
|
}
|