Skip to content
Open
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
17 changes: 16 additions & 1 deletion docs/rector_rules_overview.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 86 Rules Overview
# 87 Rules Overview

## AbortIfRector

Expand All @@ -19,6 +19,21 @@ Change if abort to abort_if

<br>

## AbortIfToGateDenyIfRector

Change `abort_if()` to `Gate::denyIf()` when condition is user-related

- class: [`RectorLaravel\Rector\FuncCall\AbortIfToGateDenyIfRector`](../src/Rector/FuncCall/AbortIfToGateDenyIfRector.php)

```diff
-abort_if($user->id === $post->user_id);
-abort_if($post->user()->is($user));
+\Illuminate\Support\Facades\Gate::denyIf($user->id === $post->user_id);
+\Illuminate\Support\Facades\Gate::denyIf($post->user()->is($user));
```

<br>

## AddArgumentDefaultValueRector

Adds default value for arguments in defined methods.
Expand Down
119 changes: 119 additions & 0 deletions src/Rector/FuncCall/AbortIfToGateDenyIfRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

declare(strict_types=1);

namespace RectorLaravel\Rector\FuncCall;

use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name\FullyQualified;
use RectorLaravel\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \RectorLaravel\Tests\Rector\FuncCall\AbortIfToGateDenyIfRector\AbortIfToGateDenyIfRectorTest
*/
final class AbortIfToGateDenyIfRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change abort_if() to Gate::denyIf() when condition is user-related', [
new CodeSample(
<<<'CODE_SAMPLE'
abort_if($user->id === $post->user_id);
abort_if($post->user()->is($user));
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
\Illuminate\Support\Facades\Gate::denyIf($user->id === $post->user_id);
\Illuminate\Support\Facades\Gate::denyIf($post->user()->is($user));
CODE_SAMPLE
),
]);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [FuncCall::class];
}

/**
* @param FuncCall $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isName($node->name, 'abort_if')) {
return null;
}

if (! isset($node->args[0])) {
return null;
}

$firstArg = $node->args[0];
if (! $firstArg instanceof Arg) {
return null;
}

$condition = $firstArg->value;

if (! $this->containsUserRelatedCheck($condition)) {
return null;
}

return new StaticCall(
new FullyQualified('Illuminate\Support\Facades\Gate'),
'denyIf',
$node->args
);
}

/**
* Check if the expression contains user-related references
*/
private function containsUserRelatedCheck(Node $node): bool
{
$hasUserReference = false;

$this->traverseNodesWithCallable($node, function (Node $subNode) use (&$hasUserReference): ?int {
if ($subNode instanceof Variable && is_string($subNode->name) && str_contains(strtolower($subNode->name), 'user')) {
$hasUserReference = true;

return null;
}

if ($subNode instanceof PropertyFetch) {
$propertyName = $this->getName($subNode->name);
if ($propertyName !== null && str_contains(strtolower($propertyName), 'user')) {
$hasUserReference = true;

return null;
}
}

if ($subNode instanceof FuncCall && $this->isName($subNode->name, 'auth')) {
$hasUserReference = true;

return null;
}

if ($subNode instanceof StaticCall && $this->isName($subNode->class, 'Auth')) {
$hasUserReference = true;

return null;
}

return null;
});

return $hasUserReference;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace RectorLaravel\Tests\Rector\FuncCall\AbortIfToGateDenyIfRector;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class AbortIfToGateDenyIfRectorTest extends AbstractRectorTestCase
{
public static function provideData(): Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

/**
* @test
*/
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace RectorLaravel\Tests\Rector\FuncCall\AbortIfToGateDenyIfRector\Fixture;

class Fixture
{
public function run($user, $post)
{
abort_if($user->id === $post->user_id);
}
}

?>
-----
<?php

namespace RectorLaravel\Tests\Rector\FuncCall\AbortIfToGateDenyIfRector\Fixture;

class Fixture
{
public function run($user, $post)
{
\Illuminate\Support\Facades\Gate::denyIf($user->id === $post->user_id);
}
}

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

namespace RectorLaravel\Tests\Rector\FuncCall\AbortIfToGateDenyIfRector\Fixture;

class SkipNonUserCondition
{
public function run($condition)
{
abort_if($condition);
}
}

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

namespace RectorLaravel\Tests\Rector\FuncCall\AbortIfToGateDenyIfRector\Fixture;

class WithAuthHelper
{
public function run($post)
{
abort_if(auth()->id() !== $post->author_id, 403);
}
}

?>
-----
<?php

namespace RectorLaravel\Tests\Rector\FuncCall\AbortIfToGateDenyIfRector\Fixture;

class WithAuthHelper
{
public function run($post)
{
\Illuminate\Support\Facades\Gate::denyIf(auth()->id() !== $post->author_id, 403);
}
}

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

namespace RectorLaravel\Tests\Rector\FuncCall\AbortIfToGateDenyIfRector\Fixture;

class WithMultipleArgs
{
public function run($post)
{
abort_if($post->user()->is(auth()->user()), 403, 'Unauthorized');
}
}

?>
-----
<?php

namespace RectorLaravel\Tests\Rector\FuncCall\AbortIfToGateDenyIfRector\Fixture;

class WithMultipleArgs
{
public function run($post)
{
\Illuminate\Support\Facades\Gate::denyIf($post->user()->is(auth()->user()), 403, 'Unauthorized');
}
}

?>
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\FuncCall\AbortIfToGateDenyIfRector;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->import(__DIR__ . '/../../../../../config/config.php');

$rectorConfig->rule(AbortIfToGateDenyIfRector::class);
};