Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save canvural/fa40bae878b9104381f5f2fb063fa2b2 to your computer and use it in GitHub Desktop.
Save canvural/fa40bae878b9104381f5f2fb063fa2b2 to your computer and use it in GitHub Desktop.
<?php
namespace Tools\Rector\Rules;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Query\Builder as QueryBuilder;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use Rector\Rector\AbstractScopeAwareRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
class DynamicWhereToQueryBuilderWhereRector extends AbstractScopeAwareRector
{
public function __construct(private readonly ReflectionProvider $reflectionProvider) {}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change dynamic where to query builder where() calls', [
new CodeSample('$model->whereUserId(1)', '$model->where("user_id", 1)'),
]);
}
/**
* @inheritDoc
*/
public function getNodeTypes(): array
{
return [
Node\Expr\MethodCall::class,
];
}
/**
* @inheritDoc
*/
public function refactorWithScope(Node $node, Scope $scope)
{
if (! $node instanceof Node\Expr\MethodCall) {
return null;
}
$methodName = $this->getName($node->name);
if (! str_starts_with($methodName, 'where')) {
return null;
}
if ($node->args === []) {
return null;
}
$calledOnType = $this->getCalledOnType($node, $scope);
if (! $calledOnType instanceof ObjectType) {
return null;
}
if (
$calledOnType->isInstanceOf(Model::class)->no() &&
$calledOnType->isInstanceOf(EloquentBuilder::class)->no() &&
$calledOnType->isInstanceOf(QueryBuilder::class)->no() &&
$calledOnType->isInstanceOf(Relation::class)->no()
) {
return null;
}
/** @var ClassReflection|null $calledOnReflection */
$calledOnReflection = $calledOnType->getClassReflection();
if ($calledOnReflection === null) {
return null;
}
if ($calledOnReflection->hasNativeMethod($methodName)) {
return null;
}
$model = $this->findModel($calledOnReflection);
if ($model === Model::class) {
return null;
}
$eloquentBuilder = EloquentBuilder::class;
if (
$this->reflectionProvider->getClass(Model::class)->hasNativeMethod($methodName) ||
$model && $this->reflectionProvider->getClass($model)->hasNativeMethod('scope' . ucfirst($methodName)) ||
$this->reflectionProvider->getClass($eloquentBuilder)->hasNativeMethod($methodName) ||
$this->reflectionProvider->getClass(QueryBuilder::class)->hasNativeMethod($methodName) ||
$this->reflectionProvider->getClass(BelongsToMany::class)->hasNativeMethod($methodName)
) {
return null;
}
$node->name = new Node\Identifier('where');
$node->args = [
new Node\Arg(new Node\Scalar\String_(
strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', substr($methodName, 5))))
),
new Node\Arg($node->args[0]->value),
];
return $node;
}
private function getCalledOnType(MethodCall $node, Scope $scope): Type
{
$calledOnType = $scope->getType($node->var);
if ($calledOnType instanceof ThisType) {
$calledOnType = $calledOnType->getStaticObjectType();
}
return TypeCombinator::removeNull($calledOnType);
}
private function findModel(ClassReflection $calledOnReflection): string|null
{
if ($calledOnReflection->isSubclassOf(Model::class)) {
return $calledOnReflection->getName();
}
if (
$calledOnReflection->getName() === EloquentBuilder::class ||
$calledOnReflection->isSubclassOf(EloquentBuilder::class)
) {
$modelType = $calledOnReflection->getActiveTemplateTypeMap()->getType('TModelClass');
if (! $modelType instanceof ObjectType) {
return null;
}
return $modelType->getClassName();
}
if (
$calledOnReflection->getName() === Relation::class ||
$calledOnReflection->isSubclassOf(Relation::class)
) {
$modelType = $calledOnReflection->getActiveTemplateTypeMap()->getType('TRelatedModel');
if (! $modelType instanceof ObjectType) {
return null;
}
return $modelType->getClassName();
}
return null;
}
}
@wilsenhc
Copy link

@canvural Might be awesome if you make a PR of this rule to the driftingly/rector-laravel repo πŸš€

@canvural
Copy link
Author

@canvural Might be awesome if you make a PR of this rule to the driftingly/rector-laravel repo πŸš€

Yeah it's in my mind. But few things needs to be cleaned up first. This was a very hacky way to do it quickly.Just need to find time πŸ˜…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment