Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/rector_rules_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,11 @@ Use PHP callable syntax instead of string syntax for controller route declaratio
```diff
-Route::get('/users', 'UserController@index');
+Route::get('/users', [\App\Http\Controllers\UserController::class, 'index']);

Route::group(['namespace' => 'Admin'], function () {
- Route::get('/users', 'UserController@index');
+ Route::get('/users', [\App\Http\Controllers\Admin\UserController::class, 'index']);
})
```

<br>
Expand Down
40 changes: 40 additions & 0 deletions src/NodeFactory/RouterRegisterNodeAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@

namespace RectorLaravel\NodeFactory;

use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar\String_;
use PHPStan\Type\ObjectType;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\NodeTypeResolver;
Expand Down Expand Up @@ -74,4 +78,40 @@ public function isRegisterFallback(Identifier|Expr $name): bool
{
return $this->nodeNameResolver->isName($name, 'fallback');
}

public function isGroup(Identifier|Expr $name): bool
{
return $this->nodeNameResolver->isName($name, 'group');
}

public function getGroupNamespace(MethodCall|StaticCall $call): string|null|false
{
if (! isset($call->args[0]) || ! $call->args[0] instanceof Arg) {
return null;
}

$firstArg = $call->args[0]->value;
if (! $firstArg instanceof Array_) {
return null;
}

foreach ($firstArg->items as $item) {
if (! $item instanceof ArrayItem) {
continue;
}

if ($item->key instanceof String_ && $item->key->value === 'namespace') {

if ($item->value instanceof String_) {
return $item->value->value;
}

// if we can't find the namespace value we specify it exists but is
// unreadable with false
return false;
}
}

return null;
}
}
66 changes: 63 additions & 3 deletions src/Rector/StaticCall/RouteActionCallableRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace RectorLaravel\Rector\StaticCall;

use Illuminate\Support\Facades\Route;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\ArrayItem;
Expand Down Expand Up @@ -42,6 +43,11 @@ final class RouteActionCallableRector extends AbstractRector implements Configur
*/
final public const NAMESPACE = 'namespace';

/**
* @var string
*/
final public const NAMESPACE_ATTRIBUTE = 'laravel_route_group_namespace';

/**
* @var string
*/
Expand All @@ -66,11 +72,19 @@ public function getRuleDefinition(): RuleDefinition
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
Route::get('/users', 'UserController@index');

Route::group(['namespace' => 'Admin'], function () {
Route::get('/users', 'UserController@index');
})
CODE_SAMPLE

,
<<<'CODE_SAMPLE'
Route::get('/users', [\App\Http\Controllers\UserController::class, 'index']);

Route::group(['namespace' => 'Admin'], function () {
Route::get('/users', [\App\Http\Controllers\Admin\UserController::class, 'index']);
})
CODE_SAMPLE
,
[
Expand All @@ -91,12 +105,55 @@ public function getNodeTypes(): array
/**
* @param MethodCall|StaticCall $node
*/
public function refactor(Node $node): ?Node
public function refactor(Node $node): MethodCall|StaticCall|null
{
if ($this->routerRegisterNodeAnalyzer->isGroup($node->name)) {
if (! isset($node->args[1]) || ! $node->args[1] instanceof Arg) {
return null;
}

$namespace = $this->routerRegisterNodeAnalyzer->getGroupNamespace($node);

$groupNamespace = $node->getAttribute(self::NAMESPACE_ATTRIBUTE);

// if the route is in a namespace but can't be resolved to a value, don't continue
if (! is_string($groupNamespace) && ! is_null($groupNamespace)) {
return null;
}

if (is_string($groupNamespace)) {
$namespace = $groupNamespace . '\\' . $namespace;
}

$this->traverseNodesWithCallable($node->args[1]->value, function (Node $node) use ($namespace): Node|int|null {
if (! $node instanceof MethodCall && ! $node instanceof StaticCall) {
return null;
}

if (
$this->routerRegisterNodeAnalyzer->isRegisterMethodStaticCall($node) ||
$this->routerRegisterNodeAnalyzer->isGroup($node->name)
) {
$node->setAttribute(self::NAMESPACE_ATTRIBUTE, $namespace);
}

return null;
});

return null;
}

if (! $this->routerRegisterNodeAnalyzer->isRegisterMethodStaticCall($node)) {
return null;
}

$groupNamespace = $node->getAttribute(self::NAMESPACE_ATTRIBUTE);

// if the route is in a namespace but can't be resolved to a value, don't continue
if (! is_string($groupNamespace) && ! is_null($groupNamespace)) {
return null;
}

$position = $this->getActionPosition($node->name);

if (! isset($node->args[$position])) {
Expand All @@ -110,7 +167,7 @@ public function refactor(Node $node): ?Node
$arg = $node->args[$position];

$argValue = $this->valueResolver->getValue($arg->value);
$segments = $this->resolveControllerFromAction($argValue);
$segments = $this->resolveControllerFromAction($argValue, $groupNamespace);
if ($segments === null) {
return null;
}
Expand Down Expand Up @@ -182,7 +239,7 @@ public function configure(array $configuration): void
/**
* @return array{string, string}|null
*/
private function resolveControllerFromAction(mixed $action): ?array
private function resolveControllerFromAction(mixed $action, ?string $groupNamespace = null): ?array
{
if (! $this->isActionString($action)) {
return null;
Expand All @@ -199,6 +256,9 @@ private function resolveControllerFromAction(mixed $action): ?array

[$controller, $method] = $segments;
$namespace = $this->getNamespace($this->file->getFilePath());
if ($groupNamespace !== null) {
$namespace .= '\\' . $groupNamespace;
}
if (! str_starts_with($controller, '\\')) {
$controller = $namespace . '\\' . $controller;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Route::fallback('SomeController@index');
Route::options('/users', 'SomeController@index');
Route::middleware([])->options('/users', 'SomeController@index');

Route::group(['namespace' => 'SomeNamespace'], function () {
Route::get('/users', 'SomeController@index');
})

?>
-----
<?php
Expand All @@ -36,4 +40,8 @@ Route::fallback([\RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRecto
Route::options('/users', [\RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRector\Source\SomeController::class, 'index']);
Route::middleware([])->options('/users', [\RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRector\Source\SomeController::class, 'index']);

Route::group(['namespace' => 'SomeNamespace'], function () {
Route::get('/users', [\RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRector\Source\SomeNamespace\SomeController::class, 'index']);
})

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRector\Fixture;

use Illuminate\Support\Facades\Route;

Route::group(['namespace' => 'SomeNamespace'], function () {
Route::group(['namespace' => 'SomeOtherNamespace'], function () {
Route::get('/users', 'SomeController@index');
});

Route::get('/users', 'SomeController@index');
});

?>
-----
<?php

namespace RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRector\Fixture;

use Illuminate\Support\Facades\Route;

Route::group(['namespace' => 'SomeNamespace'], function () {
Route::group(['namespace' => 'SomeOtherNamespace'], function () {
Route::get('/users', [\RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRector\Source\SomeNamespace\SomeOtherNamespace\SomeController::class, 'index']);
});

Route::get('/users', [\RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRector\Source\SomeNamespace\SomeController::class, 'index']);
});

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRector\Fixture;

use Illuminate\Support\Facades\Route;

// the concat should make the rule ignore the group and any routes within it
Route::group(['namespace' => 'Some' . 'Namespace'], function () {
Route::get('/users', 'SomeController@index');
});

Route::group(['namespace' => 'SomeNamespace'], function () {
Route::get('/users', 'SomeController@index');

Route::group(['namespace' => 'SomeOther' . 'Namespace'], function () {
Route::get('/users', 'SomeController@index');
});
});

Route::get('/users', 'SomeController@index');

?>
-----
<?php

namespace RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRector\Fixture;

use Illuminate\Support\Facades\Route;

// the concat should make the rule ignore the group and any routes within it
Route::group(['namespace' => 'Some' . 'Namespace'], function () {
Route::get('/users', 'SomeController@index');
});

Route::group(['namespace' => 'SomeNamespace'], function () {
Route::get('/users', [\RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRector\Source\SomeNamespace\SomeController::class, 'index']);

Route::group(['namespace' => 'SomeOther' . 'Namespace'], function () {
Route::get('/users', 'SomeController@index');
});
});

Route::get('/users', [\RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRector\Source\SomeController::class, 'index']);

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRector\Source\SomeNamespace;

class SomeController
{
public function index() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace RectorLaravel\Tests\Rector\StaticCall\RouteActionCallableRector\Source\SomeNamespace\SomeOtherNamespace;

class SomeController
{
public function index() {}
}
Loading