-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add EloquentMagicMethodToQueryBuilderRector rule (#132)
* Add EloquentMagicMethodToQueryBuilderRector rule * Fix PHPStan errors
- Loading branch information
1 parent
2af4076
commit c664be4
Showing
9 changed files
with
302 additions
and
1 deletion.
There are no files selected for viewing
12 changes: 12 additions & 0 deletions
12
config/sets/laravel-eloquent-magic-method-to-query-builder.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,12 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Rector\Config\RectorConfig; | ||
|
||
use RectorLaravel\Rector\StaticCall\EloquentMagicMethodToQueryBuilderRector; | ||
|
||
return static function (RectorConfig $rectorConfig): void { | ||
$rectorConfig->import(__DIR__ . '/../config.php'); | ||
$rectorConfig->rule(EloquentMagicMethodToQueryBuilderRector::class); | ||
}; |
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
134 changes: 134 additions & 0 deletions
134
src/Rector/StaticCall/EloquentMagicMethodToQueryBuilderRector.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,134 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RectorLaravel\Rector\StaticCall; | ||
|
||
use Illuminate\Database\Eloquent\Builder as EloquentQueryBuilder; | ||
use Illuminate\Database\Eloquent\Model; | ||
use Illuminate\Database\Query\Builder as QueryBuilder; | ||
use PhpParser\Node; | ||
use PhpParser\Node\Expr\StaticCall; | ||
use PhpParser\Node\Identifier; | ||
use Rector\Core\Rector\AbstractRector; | ||
use ReflectionException; | ||
use ReflectionMethod; | ||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; | ||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; | ||
|
||
/** | ||
* @see \RectorLaravel\Tests\Rector\StaticCall\EloquentMagicMethodToQueryBuilderRector\EloquentMagicMethodToQueryBuilderRectorTest | ||
*/ | ||
final class EloquentMagicMethodToQueryBuilderRector extends AbstractRector | ||
{ | ||
public function getRuleDefinition(): RuleDefinition | ||
{ | ||
return new RuleDefinition('The EloquentMagicMethodToQueryBuilderRule is designed to automatically transform certain magic method calls on Eloquent Models into corresponding Query Builder method calls.', [ | ||
new CodeSample( | ||
<<<'CODE_SAMPLE' | ||
use App\Models\User; | ||
$user = User::find(1); | ||
CODE_SAMPLE, | ||
<<<'CODE_SAMPLE' | ||
use App\Models\User; | ||
$user = User::query()->find(1); | ||
CODE_SAMPLE | ||
), | ||
]); | ||
} | ||
|
||
/** | ||
* @return array<class-string<Node>> | ||
*/ | ||
public function getNodeTypes(): array | ||
{ | ||
return [StaticCall::class]; | ||
} | ||
|
||
/** | ||
* @param StaticCall $node | ||
*/ | ||
public function refactor(Node $node): ?Node | ||
{ | ||
$resolvedType = $this->nodeTypeResolver->getType($node->class); | ||
|
||
// like for variables, example "$namespace" | ||
// @phpstan-ignore-next-line | ||
if (! method_exists($resolvedType, 'getClassName')) { | ||
return null; | ||
} | ||
|
||
$className = (string) $resolvedType->getClassName(); | ||
$originalClassName = $this->getName($node->class); // like "self" or "App\Models\User" | ||
|
||
if (is_null($originalClassName)) { | ||
return null; | ||
} | ||
|
||
// does not extend Eloquent Model | ||
if (! is_subclass_of($className, Model::class)) { | ||
return null; | ||
} | ||
|
||
if (! $node->name instanceof Identifier) { | ||
return null; | ||
} | ||
|
||
$methodName = $node->name->toString(); | ||
|
||
// if not a magic method | ||
if (! $this->isMagicMethod($className, $methodName)) { | ||
return null; | ||
} | ||
|
||
// if method belongs to Eloquent Query Builder or Query Builder | ||
if (! ($this->isPublicMethod(EloquentQueryBuilder::class, $methodName) || | ||
$this->isPublicMethod(QueryBuilder::class, $methodName) | ||
)) { | ||
return null; | ||
} | ||
|
||
$queryMethodCall = $this->nodeFactory->createStaticCall($originalClassName, 'query'); | ||
|
||
$newNode = $this->nodeFactory->createMethodCall($queryMethodCall, $methodName); | ||
foreach ($node->args as $arg) { | ||
$newNode->args[] = $arg; | ||
} | ||
|
||
return $newNode; | ||
} | ||
|
||
public function isMagicMethod(string $className, string $methodName): bool | ||
{ | ||
try { | ||
$reflectionMethod = new ReflectionMethod($className, $methodName); | ||
} catch (ReflectionException $e) { | ||
return true; // method does not exist => is magic method | ||
} | ||
|
||
return false; // not a magic method | ||
} | ||
|
||
public function isPublicMethod(string $className, string $methodName): bool | ||
{ | ||
try { | ||
$reflectionMethod = new ReflectionMethod($className, $methodName); | ||
|
||
// if not public | ||
if (! $reflectionMethod->isPublic()) { | ||
return false; | ||
} | ||
|
||
// if static | ||
if ($reflectionMethod->isStatic()) { | ||
return false; | ||
} | ||
} catch (ReflectionException $e) { | ||
return false; // method does not exist => is magic method | ||
} | ||
|
||
return true; // method exist | ||
} | ||
} |
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,16 @@ | ||
<?php | ||
|
||
namespace Illuminate\Database\Eloquent; | ||
|
||
use Illuminate\Database\Query\Builder as QueryBuilder; | ||
|
||
if (class_exists('Illuminate\Database\Eloquent\Builder')) { | ||
return; | ||
} | ||
|
||
class Builder extends QueryBuilder | ||
{ | ||
public function publicMethodBelongsToEloquentQueryBuilder(): void | ||
{ | ||
} | ||
} |
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,22 @@ | ||
<?php | ||
|
||
namespace Illuminate\Database\Query; | ||
|
||
if (class_exists('Illuminate\Database\Query\Builder')) { | ||
return; | ||
} | ||
|
||
class Builder | ||
{ | ||
public function publicMethodBelongsToQueryBuilder(): void | ||
{ | ||
} | ||
|
||
protected function protectedMethodBelongsToQueryBuilder(): void | ||
{ | ||
} | ||
|
||
private function privateMethodBelongsToQueryBuilder(): void | ||
{ | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
...l/EloquentMagicMethodToQueryBuilderRector/EloquentMagicMethodToQueryBuilderRectorTest.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,36 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RectorLaravel\Tests\Rector\StaticCall\EloquentMagicMethodToQueryBuilderRector; | ||
|
||
use Illuminate\Database\Eloquent\Model; | ||
use Iterator; | ||
use PHPUnit\Framework\Attributes\DataProvider; | ||
use Rector\Testing\PHPUnit\AbstractRectorTestCase; | ||
|
||
final class User extends Model | ||
{ | ||
public static function staticMethodBelongsToModel(): void | ||
{ | ||
} | ||
} | ||
|
||
final class EloquentMagicMethodToQueryBuilderRectorTest extends AbstractRectorTestCase | ||
{ | ||
#[DataProvider('provideData')] | ||
public function test(string $filePath): void | ||
{ | ||
$this->doTestFile($filePath); | ||
} | ||
|
||
public static function provideData(): Iterator | ||
{ | ||
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); | ||
} | ||
|
||
public function provideConfigFilePath(): string | ||
{ | ||
return __DIR__ . '/config/configured_rule.php'; | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
tests/Rector/StaticCall/EloquentMagicMethodToQueryBuilderRector/Fixture/fixture.php.inc
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,47 @@ | ||
<?php | ||
|
||
namespace RectorLaravel\Tests\Rector\StaticCall\EloquentMagicMethodToQueryBuilderRector\Fixture; | ||
|
||
use RectorLaravel\Tests\Rector\StaticCall\EloquentMagicMethodToQueryBuilderRector\User; | ||
|
||
class SomeController | ||
{ | ||
public function getUser() | ||
{ | ||
# eligible | ||
$user = User::publicMethodBelongsToEloquentQueryBuilder(1)->where('xxx', 'xxx')->first(); | ||
$user = User::publicMethodBelongsToQueryBuilder(1); | ||
|
||
# not eligible | ||
$user = User::privateMethodBelongsToQueryBuilder(1); | ||
$user = User::protectedMethodBelongsToQueryBuilder(1); | ||
$user = User::publicMethodNotBelongsToQueryBuilder(1); | ||
$user = User::query()->publicMethodBelongsToEloquentQueryBuilder(1); | ||
$user = User::query()->publicMethodBelongsToQueryBuilder(1); | ||
$user = User::staticMethodBelongsToModel(1); | ||
} | ||
} | ||
----- | ||
<?php | ||
|
||
namespace RectorLaravel\Tests\Rector\StaticCall\EloquentMagicMethodToQueryBuilderRector\Fixture; | ||
|
||
use RectorLaravel\Tests\Rector\StaticCall\EloquentMagicMethodToQueryBuilderRector\User; | ||
|
||
class SomeController | ||
{ | ||
public function getUser() | ||
{ | ||
# eligible | ||
$user = User::query()->publicMethodBelongsToEloquentQueryBuilder(1)->where('xxx', 'xxx')->first(); | ||
$user = User::query()->publicMethodBelongsToQueryBuilder(1); | ||
|
||
# not eligible | ||
$user = User::privateMethodBelongsToQueryBuilder(1); | ||
$user = User::protectedMethodBelongsToQueryBuilder(1); | ||
$user = User::publicMethodNotBelongsToQueryBuilder(1); | ||
$user = User::query()->publicMethodBelongsToEloquentQueryBuilder(1); | ||
$user = User::query()->publicMethodBelongsToQueryBuilder(1); | ||
$user = User::staticMethodBelongsToModel(1); | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
tests/Rector/StaticCall/EloquentMagicMethodToQueryBuilderRector/config/configured_rule.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,14 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Rector\Config\RectorConfig; | ||
|
||
use RectorLaravel\Rector\StaticCall\EloquentMagicMethodToQueryBuilderRector; | ||
|
||
return static function (RectorConfig $rectorConfig): void { | ||
$rectorConfig->import(__DIR__ . '/../../../../../config/config.php'); | ||
$rectorConfig->importNames(importDocBlockNames: false); | ||
$rectorConfig->importShortClasses(false); | ||
$rectorConfig->rule(EloquentMagicMethodToQueryBuilderRector::class); | ||
}; |