Skip to content

Commit 4042fdd

Browse files
authored
Add rule to convert MyJob::dispatch() to dispatch(new MyJob()) (#193)
* Add `DispatchToHelperFunctionsRector` * Extract classes to `Source` directory/namespace
1 parent d6da062 commit 4042fdd

File tree

11 files changed

+306
-1
lines changed

11 files changed

+306
-1
lines changed

docs/rector_rules_overview.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 57 Rules Overview
1+
# 58 Rules Overview
22

33
## AddArgumentDefaultValueRector
44

@@ -450,6 +450,26 @@ Dispatch non ShouldQueue jobs to dispatchSync
450450

451451
<br>
452452

453+
## DispatchToHelperFunctionsRector
454+
455+
Use the event or dispatch helpers instead of the static dispatch method.
456+
457+
- class: [`RectorLaravel\Rector\StaticCall\DispatchToHelperFunctionsRector`](../src/Rector/StaticCall/DispatchToHelperFunctionsRector.php)
458+
459+
```diff
460+
-ExampleEvent::dispatch($email);
461+
+event(new ExampleEvent($email));
462+
```
463+
464+
<br>
465+
466+
```diff
467+
-ExampleJob::dispatch($email);
468+
+dispatch(new ExampleJob($email));
469+
```
470+
471+
<br>
472+
453473
## EloquentMagicMethodToQueryBuilderRector
454474

455475
The EloquentMagicMethodToQueryBuilderRule is designed to automatically transform certain magic method calls on Eloquent Models into corresponding Query Builder method calls.
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<?php
2+
3+
namespace RectorLaravel\Rector\StaticCall;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Arg;
7+
use PhpParser\Node\Expr\FuncCall;
8+
use PhpParser\Node\Expr\New_;
9+
use PhpParser\Node\Expr\StaticCall;
10+
use PhpParser\Node\Name;
11+
use PHPStan\Broker\ClassNotFoundException;
12+
use PHPStan\Reflection\ClassReflection;
13+
use PHPStan\Reflection\ReflectionProvider;
14+
use PHPStan\Type\ObjectType;
15+
use Rector\Rector\AbstractRector;
16+
use Symplify\RuleDocGenerator\Exception\PoorDocumentationException;
17+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
18+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
19+
20+
/**
21+
* @see \RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\DispatchToHelperFunctionsRectorTest
22+
*/
23+
final class DispatchToHelperFunctionsRector extends AbstractRector
24+
{
25+
public function __construct(
26+
private readonly ReflectionProvider $reflectionProvider,
27+
) {
28+
}
29+
30+
/**
31+
* @throws PoorDocumentationException
32+
*/
33+
public function getRuleDefinition(): RuleDefinition
34+
{
35+
return new RuleDefinition(
36+
'Use the event or dispatch helpers instead of the static dispatch method.',
37+
[
38+
new CodeSample(
39+
'ExampleEvent::dispatch($email);',
40+
'event(new ExampleEvent($email));'
41+
),
42+
new CodeSample(
43+
'ExampleJob::dispatch($email);',
44+
'dispatch(new ExampleJob($email));'
45+
),
46+
],
47+
);
48+
}
49+
50+
/**
51+
* @return array<class-string<Node>>
52+
*/
53+
public function getNodeTypes(): array
54+
{
55+
return [StaticCall::class];
56+
}
57+
58+
public function refactor(Node $node): ?Node
59+
{
60+
if (! $node instanceof StaticCall) {
61+
return null;
62+
}
63+
64+
if (! $this->isName($node->name, 'dispatch')) {
65+
return null;
66+
}
67+
68+
$class = $node->class;
69+
70+
if (! $class instanceof Name) {
71+
return null;
72+
}
73+
74+
$classReflection = $this->getClassReflection($node);
75+
if (! $classReflection instanceof ClassReflection) {
76+
return null;
77+
}
78+
79+
if ($this->usesBusDispatchable($classReflection)) {
80+
return $this->createDispatchableCall($node, 'dispatch');
81+
}
82+
83+
if ($this->usesEventDispatchable($classReflection)) {
84+
return $this->createDispatchableCall($node, 'event');
85+
}
86+
87+
return null;
88+
}
89+
90+
private function getClassReflection(StaticCall $staticCall): ?ClassReflection
91+
{
92+
$type = $this->getType($staticCall->class);
93+
if (! $type instanceof ObjectType) {
94+
return null;
95+
}
96+
97+
try {
98+
return $this->reflectionProvider->getClass(
99+
$type->getClassName()
100+
);
101+
} catch (ClassNotFoundException) {
102+
}
103+
104+
return null;
105+
}
106+
107+
private function usesBusDispatchable(ClassReflection $classReflection): bool
108+
{
109+
$traits = array_keys($classReflection->getTraits(true));
110+
111+
return in_array('Illuminate\Foundation\Bus\Dispatchable', $traits, true);
112+
}
113+
114+
private function usesEventDispatchable(ClassReflection $classReflection): bool
115+
{
116+
$traits = array_keys($classReflection->getTraits(true));
117+
118+
return in_array('Illuminate\Foundation\Events\Dispatchable', $traits, true);
119+
}
120+
121+
private function createDispatchableCall(StaticCall $staticCall, string $method): ?FuncCall
122+
{
123+
$class = $staticCall->class;
124+
if (! $class instanceof Name) {
125+
return null;
126+
}
127+
128+
return new FuncCall(
129+
new Name($method),
130+
[
131+
new Arg(new New_(new Name($class), $staticCall->args)),
132+
],
133+
);
134+
}
135+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Illuminate\Foundation\Events;
4+
5+
if (class_exists('Illuminate\Foundation\Events\Dispatchable')) {
6+
return;
7+
}
8+
trait Dispatchable
9+
{
10+
public static function dispatch(...$arguments)
11+
{
12+
}
13+
14+
public static function dispatchIf($boolean, ...$arguments)
15+
{
16+
}
17+
18+
public static function dispatchUnless($boolean, ...$arguments)
19+
{
20+
}
21+
22+
public static function broadcast()
23+
{
24+
}
25+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector;
4+
5+
use Iterator;
6+
use PHPUnit\Framework\Attributes\DataProvider;
7+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
8+
9+
class DispatchToHelperFunctionsRectorTest extends AbstractRectorTestCase
10+
{
11+
public static function provideData(): Iterator
12+
{
13+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
14+
}
15+
16+
/**
17+
* @test
18+
*/
19+
#[DataProvider('provideData')]
20+
public function test(string $filePath): void
21+
{
22+
$this->doTestFile($filePath);
23+
}
24+
25+
public function provideConfigFilePath(): string
26+
{
27+
return __DIR__ . '/config/configured_rule.php';
28+
}
29+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Fixture;
4+
5+
use RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Source\TestJob;
6+
7+
TestJob::dispatch('param1', 'param2');
8+
-----
9+
<?php
10+
11+
namespace RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Fixture;
12+
13+
use RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Source\TestJob;
14+
15+
dispatch(new RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Source\TestJob('param1', 'param2'));
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Fixture;
4+
5+
use RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Source\TestEvent;
6+
7+
TestEvent::dispatch('param1', 'param2');
8+
-----
9+
<?php
10+
11+
namespace RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Fixture;
12+
13+
use RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Source\TestEvent;
14+
15+
event(new RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Source\TestEvent('param1', 'param2'));
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Fixture;
4+
5+
use RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Source\OtherDispatchable;
6+
7+
OtherDispatchable::dispatch('param1', 'param2');
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Source;
4+
5+
use Some\Other\Dispatchable;
6+
7+
class OtherDispatchable
8+
{
9+
use Dispatchable;
10+
11+
public function __construct(
12+
private string $param1,
13+
private string $param2,
14+
) {
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Source;
4+
5+
use Illuminate\Foundation\Events\Dispatchable;
6+
7+
class TestEvent
8+
{
9+
use Dispatchable;
10+
11+
public function __construct(
12+
private string $param1,
13+
private string $param2,
14+
) {
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\StaticCall\DispatchToHelperFunctionsRector\Source;
4+
5+
use Illuminate\Foundation\Bus\Dispatchable;
6+
7+
class TestJob
8+
{
9+
use Dispatchable;
10+
11+
public function __construct(
12+
private string $param1,
13+
private string $param2,
14+
) {
15+
}
16+
}

0 commit comments

Comments
 (0)