[Laravel] Add RequestStaticValidateToInjectRector

This commit is contained in:
Tomas Votruba 2019-03-04 10:14:45 +01:00
parent 1d8deff30a
commit 708029e7a3
9 changed files with 244 additions and 61 deletions

View File

@ -1,2 +1,3 @@
services:
Rector\Laravel\Rector\StaticCall\FacadeStaticCallToConstructorInjectionRector: ~
Rector\Laravel\Rector\StaticCall\RequestStaticValidateToInjectRector: ~

View File

@ -0,0 +1,96 @@
<?php declare(strict_types=1);
namespace Rector\Laravel\Rector\StaticCall;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use Rector\PhpParser\Node\Manipulator\ClassMethodManipulator;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see https://github.com/laravel/framework/pull/27276
*/
final class RequestStaticValidateToInjectRector extends AbstractRector
{
/**
* @var ClassMethodManipulator
*/
private $classMethodManipulator;
/**
* @var string
*/
private $requestClass = 'Illuminate\Http\Request';
public function __construct(ClassMethodManipulator $classMethodManipulator)
{
$this->classMethodManipulator = $classMethodManipulator;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change static validate() method to $request->validate()', [
new CodeSample(
<<<'CODE_SAMPLE'
use Illuminate\Http\Request;
class SomeClass
{
public function store()
{
$validatedData = Request::validate(['some_attribute' => 'required']);
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use Illuminate\Http\Request;
class SomeClass
{
public function store(\Illuminate\Http\Request $request)
{
$validatedData = $request->validate(['some_attribute' => 'required']);
}
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [StaticCall::class];
}
/**
* @param StaticCall $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isType($node, $this->requestClass)) {
return null;
}
if (! $this->isName($node, 'validate')) {
return null;
}
$requestName = $this->classMethodManipulator->addMethodParameterIfMissing(
$node,
$this->requestClass,
['request', 'httpRequest']
);
$variable = new Variable($requestName);
return new MethodCall($variable, $node->name, $node->args);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\Laravel\Tests\Rector\StaticCall\RequestStaticValidateToInjectRector\Fixture;
use Illuminate\Http\Request;
class SomeClass
{
public function store()
{
$validatedData = Request::validate(['some_attribute' => 'required']);
}
}
?>
-----
<?php
namespace Rector\Laravel\Tests\Rector\StaticCall\RequestStaticValidateToInjectRector\Fixture;
use Illuminate\Http\Request;
class SomeClass
{
public function store(\Illuminate\Http\Request $request)
{
$validatedData = $request->validate(['some_attribute' => 'required']);
}
}
?>

View File

@ -0,0 +1,19 @@
<?php declare(strict_types=1);
namespace Rector\Laravel\Tests\Rector\StaticCall\RequestStaticValidateToInjectRector;
use Rector\Laravel\Rector\StaticCall\RequestStaticValidateToInjectRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class RequestStaticValidateToInjectRectorTest extends AbstractRectorTestCase
{
public function test(): void
{
$this->doTestFiles([__DIR__ . '/Fixture/fixture.php.inc']);
}
protected function getRectorClass(): string
{
return RequestStaticValidateToInjectRector::class;
}
}

View File

@ -7,11 +7,7 @@ use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\PhpParser\Node\Manipulator\ClassMethodManipulator;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
@ -27,13 +23,21 @@ final class FromHttpRequestGetHeaderToHeadersGetRector extends AbstractRector
*/
private $netteHttpRequestClass;
/**
* @var ClassMethodManipulator
*/
private $classMethodManipulator;
/**
* @var string
*/
private $symfonyRequestClass = 'Symfony\Component\HttpFoundation\Request';
public function __construct(string $netteHttpRequestClass = 'Nette\Http\Request')
{
public function __construct(
ClassMethodManipulator $classMethodManipulator,
string $netteHttpRequestClass = 'Nette\Http\Request'
) {
$this->classMethodManipulator = $classMethodManipulator;
$this->netteHttpRequestClass = $netteHttpRequestClass;
}
@ -89,7 +93,11 @@ CODE_SAMPLE
return null;
}
$requestName = $this->completeRequestParameterIfMissingAndGetRequestName($node);
$requestName = $this->classMethodManipulator->addMethodParameterIfMissing(
$node,
$this->symfonyRequestClass,
['request', 'symfonyRequest']
);
$requestVariableNode = new Variable($requestName);
$headersPropertyFetch = new PropertyFetch($requestVariableNode, 'headers');
@ -99,31 +107,4 @@ CODE_SAMPLE
return $node;
}
private function completeRequestParameterIfMissingAndGetRequestName(Node $node): string
{
$classMethodNode = $node->getAttribute(Attribute::METHOD_NODE);
if (! $classMethodNode instanceof ClassMethod) {
throw new ShouldNotHappenException();
}
foreach ($classMethodNode->params as $paramNode) {
if ($this->isType($paramNode, $this->symfonyRequestClass)) {
return $this->getName($paramNode);
}
}
$requestName = 'request';
foreach ($classMethodNode->params as $paramNode) {
if ($this->isName($paramNode, 'request')) {
$requestName = 'symfonyRequest';
}
}
$classMethodNode->params[] = new Param(new Variable($requestName), null, new FullyQualified(
$this->symfonyRequestClass
));
return $requestName;
}
}

View File

@ -2,11 +2,14 @@
namespace Rector\NodeTypeResolver;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
@ -68,6 +71,37 @@ final class NodeTypeResolver
$this->nameResolver = $nameResolver;
}
public function isType(Node $node, string $type): bool
{
$nodeTypes = $this->getTypes($node);
// fnmatch support
if (Strings::contains($type, '*')) {
foreach ($nodeTypes as $nodeType) {
if (fnmatch($type, $nodeType, FNM_NOESCAPE)) {
return true;
}
}
return false;
}
return in_array($type, $nodeTypes, true);
}
/**
* @return string[]
*/
public function getTypes(Node $node): array
{
// @todo should be resolved by NodeTypeResolver internally
if ($node instanceof MethodCall || $node instanceof PropertyFetch || $node instanceof ArrayDimFetch) {
return $this->resolve($node->var);
}
return $this->resolve($node);
}
/**
* @return string[]
*/

View File

@ -4,7 +4,7 @@ namespace Rector\Php\Tests\Rector\FuncCall\RegexDashEscapeRector\Fixture;
use Nette\Utils\Strings;
class MethodCall
class UniqueMethodNameCall
{
public function run()
{
@ -23,7 +23,7 @@ namespace Rector\Php\Tests\Rector\FuncCall\RegexDashEscapeRector\Fixture;
use Nette\Utils\Strings;
class MethodCall
class UniqueMethodNameCall
{
public function run()
{

View File

@ -7,9 +7,11 @@ use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Return_;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\PhpParser\Node\BetterNodeFinder;
@ -187,6 +189,29 @@ final class ClassMethodManipulator
});
}
/**
* @param string[] $possibleNames
*/
public function addMethodParameterIfMissing(Node $node, string $type, array $possibleNames): string
{
$classMethodNode = $node->getAttribute(Attribute::METHOD_NODE);
if (! $classMethodNode instanceof ClassMethod) {
// or null?
throw new ShouldNotHappenException();
}
foreach ($classMethodNode->params as $paramNode) {
if ($this->nodeTypeResolver->isType($paramNode, $type)) {
return $this->nameResolver->resolve($paramNode);
}
}
$paramName = $this->resolveName($classMethodNode, $possibleNames);
$classMethodNode->params[] = new Param(new Variable($paramName), null, new FullyQualified($type));
return $paramName;
}
private function isMethodInParent(string $class, string $method): bool
{
$parentClass = $class;
@ -214,4 +239,22 @@ final class ClassMethodManipulator
return true;
}
/**
* @param string[] $possibleNames
*/
private function resolveName(ClassMethod $classMethod, array $possibleNames): string
{
foreach ($possibleNames as $possibleName) {
foreach ($classMethod->params as $paramNode) {
if ($this->nameResolver->isName($paramNode, $possibleName)) {
continue 2;
}
}
return $possibleName;
}
throw new ShouldNotHappenException();
}
}

View File

@ -2,11 +2,7 @@
namespace Rector\Rector;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\NodeTypeAnalyzer;
use Rector\NodeTypeResolver\NodeTypeResolver;
@ -40,20 +36,7 @@ trait TypeAnalyzerTrait
protected function isType(Node $node, string $type): bool
{
$nodeTypes = $this->getTypes($node);
// fnmatch support
if (Strings::contains($type, '*')) {
foreach ($nodeTypes as $nodeType) {
if (fnmatch($type, $nodeType, FNM_NOESCAPE)) {
return true;
}
}
return false;
}
return in_array($type, $nodeTypes, true);
return $this->nodeTypeResolver->isType($node, $type);
}
/**
@ -134,11 +117,6 @@ trait TypeAnalyzerTrait
*/
protected function getTypes(Node $node): array
{
// @todo should be resolved by NodeTypeResolver internally
if ($node instanceof MethodCall || $node instanceof PropertyFetch || $node instanceof ArrayDimFetch) {
return $this->nodeTypeResolver->resolve($node->var);
}
return $this->nodeTypeResolver->resolve($node);
return $this->nodeTypeResolver->getTypes($node);
}
}