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
10 changes: 10 additions & 0 deletions src/NodeAnalyzer/ApplicationAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ class ApplicationAnalyzer
{
private ?string $version = null;

/**
* @param class-string $applicationClass
*/
public function __construct(
private string $applicationClass = 'Illuminate\Foundation\Application',
) {}
Expand All @@ -20,13 +23,20 @@ public function setVersion(?string $version): static
return $this;
}

/**
* @param class-string $applicationClass
* @return $this
*/
public function setApplicationClass(string $applicationClass): static
{
$this->applicationClass = $applicationClass;

return $this;
}

/**
* @return class-string
*/
public function getApplicationClass(): string
{
return $this->applicationClass;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use RectorLaravel\AbstractRector;
use RectorLaravel\NodeAnalyzer\QueryBuilderAnalyzer;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
Expand Down Expand Up @@ -72,15 +74,19 @@ public function getNodeTypes(): array
return [MethodCall::class, StaticCall::class];
}

public function refactor(Node $node): ?Node
/**
* @param MethodCall|StaticCall $node
*/
public function refactor(Node $node): MethodCall|StaticCall|null
{
if (! $node instanceof MethodCall && ! $node instanceof StaticCall) {
return null;
}
$type = new ObjectType('Illuminate\Contracts\Database\Eloquent\Builder');

if ($this->isWhereRelationMethodWithClosureOrArrowFunction($node)) {
$this->changeClosureParamType($node);
if ($node instanceof MethodCall) {
$type = $this->getType($node->var);
}

/** @phpstan-ignore argument.type */
if ($this->isWhereRelationMethodWithClosureOrArrowFunction($node) && $this->changeClosureParamType($node, $type)) {
return $node;
}

Expand All @@ -103,7 +109,10 @@ private function isWhereRelationMethodWithClosureOrArrowFunction(MethodCall|Stat
! ($node->getArgs()[$position]->value ?? null) instanceof ArrowFunction);
}

private function changeClosureParamType(MethodCall|StaticCall $node): void
/**
* @param ObjectType $type
*/
private function changeClosureParamType(MethodCall|StaticCall $node, Type $type): bool
{
// Morph methods have the closure in the 3rd position, others use the 2nd.
$position = $this->isNames(
Expand All @@ -115,16 +124,22 @@ private function changeClosureParamType(MethodCall|StaticCall $node): void
$closure = $node->getArgs()[$position]->value;

if (! isset($closure->getParams()[0])) {
return;
return false;
}

$param = $closure->getParams()[0];

if ($param->type instanceof Name) {
return;
return false;
}

if ($type->isObject()->no()) {
return false;
}

$param->type = new FullyQualified('Illuminate\Contracts\Database\Query\Builder');
$param->type = new FullyQualified($type->getClassName());

return true;
}

private function expectedObjectTypeAndMethodCall(MethodCall|StaticCall $node): bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use RectorLaravel\AbstractRector;
use RectorLaravel\NodeAnalyzer\QueryBuilderAnalyzer;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
Expand Down Expand Up @@ -70,15 +71,19 @@ public function getNodeTypes(): array
return [MethodCall::class, StaticCall::class];
}

/**
* @param StaticCall|MethodCall $node
*/
public function refactor(Node $node): ?Node
{
if (! $node instanceof MethodCall && ! $node instanceof StaticCall) {
return null;
}
$type = new ObjectType('Illuminate\Contracts\Database\Eloquent\Builder');

if ($this->isWhereMethodWithClosureOrArrowFunction($node)) {
$this->changeClosureParamType($node);
if ($node instanceof MethodCall) {
$type = $this->getType($node->var);
}

/** @phpstan-ignore argument.type */
if ($this->isWhereMethodWithClosureOrArrowFunction($node) && $this->changeClosureParamType($node, $type)) {
return $node;
}

Expand All @@ -95,31 +100,32 @@ private function isWhereMethodWithClosureOrArrowFunction(MethodCall|StaticCall $
! ($node->getArgs()[0]->value ?? null) instanceof ArrowFunction);
}

private function changeClosureParamType(MethodCall|StaticCall $node): void
/**
* @param ObjectType $type
*/
private function changeClosureParamType(MethodCall|StaticCall $node, Type $type): bool
{
/** @var ArrowFunction|Closure $closure */
$closure = $node->getArgs()[0]
->value;

if (! isset($closure->getParams()[0])) {
return;
return false;
}

$param = $closure->getParams()[0];

if ($param->type instanceof Name) {
return;
return false;
}

$classOrVar = $node instanceof MethodCall
? $node->var
: $node->class;
if ($type->isObject()->no()) {
return false;
}

$type = $this->isObjectType($classOrVar, new ObjectType('Illuminate\Database\Eloquent\Model'))
? 'Illuminate\Contracts\Database\Eloquent\Builder'
: 'Illuminate\Contracts\Database\Query\Builder';
$param->type = new FullyQualified($type->getClassName());

$param->type = new FullyQualified($type);
return true;
}

private function expectedObjectTypeAndMethodCall(MethodCall|StaticCall $node): bool
Expand Down
4 changes: 3 additions & 1 deletion stubs/Illuminate/Database/Eloquent/Relations/Relation.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace Illuminate\Database\Eloquent\Relations;

use Illuminate\Contracts\Database\Eloquent\Builder;

if (class_exists('Illuminate\Database\Eloquent\Relations\Relation')) {
return;
}

abstract class Relation {}
abstract class Relation implements Builder {}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereRelationTypeHinting

use RectorLaravel\Tests\Rector\MethodCall\EloquentWhereRelationTypeHintingParameterRector\Source\FooModel;

FooModel::whereHas('posts', function (\Illuminate\Contracts\Database\Query\Builder $query) {
FooModel::whereHas('posts', function (\Illuminate\Contracts\Database\Eloquent\Builder $query) {
$query->where('is_published', true);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereRelationTypeHintingParameterRector\Fixture;

use RectorLaravel\Tests\Rector\MethodCall\EloquentWhereRelationTypeHintingParameterRector\Source\FooModel;

(new FooModel)->relation()->whereHas('posts', function ($query) {
$query->where('is_published', true);
});

?>
-----
<?php

namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereRelationTypeHintingParameterRector\Fixture;

use RectorLaravel\Tests\Rector\MethodCall\EloquentWhereRelationTypeHintingParameterRector\Source\FooModel;

(new FooModel)->relation()->whereHas('posts', function (\Illuminate\Database\Eloquent\Relations\HasMany $query) {
$query->where('is_published', true);
});

?>
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,12 @@
namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereRelationTypeHintingParameterRector\Source;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class FooModel extends Model {}
class FooModel extends Model
{
public function relation(): HasMany
{
return new HasMany;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector\Fixture;

use RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector\Source\FooModel;

(new FooModel())->relation()->where(fn ($query) =>
$query->where('id', 1)
->orWhere('id', 2)
);

?>
-----
<?php

namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector\Fixture;

use RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector\Source\FooModel;

(new FooModel())->relation()->where(fn (\Illuminate\Database\Eloquent\Relations\HasMany $query) =>
$query->where('id', 1)
->orWhere('id', 2)
);

?>
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,12 @@
namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector\Source;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class FooModel extends Model {}
class FooModel extends Model
{
public function relation(): HasMany
{
return new HasMany;
}
}