diff --git a/config/sets/laravel130-attributes.php b/config/sets/laravel130-attributes.php index 10edbad7..6607d938 100644 --- a/config/sets/laravel130-attributes.php +++ b/config/sets/laravel130-attributes.php @@ -22,6 +22,7 @@ use RectorLaravel\Rector\Class_\HiddenPropertyToHiddenAttributeRector; use RectorLaravel\Rector\Class_\JobConnectionPropertyToJobConnectionAttributeRector; use RectorLaravel\Rector\Class_\MaxExceptionsPropertyToMaxExceptionsAttributeRector; +use RectorLaravel\Rector\Class_\ObserveCallsToObservedByAttributeRector; use RectorLaravel\Rector\Class_\PreserveKeysPropertyToPreserveKeysAttributeRector; use RectorLaravel\Rector\Class_\QueuePropertyToQueueAttributeRector; use RectorLaravel\Rector\Class_\SignaturePropertyToSignatureAttributeRector; @@ -34,6 +35,7 @@ use RectorLaravel\Rector\Class_\VisiblePropertyToVisibleAttributeRector; use RectorLaravel\Rector\Class_\WithoutIncrementingPropertyToWithoutIncrementingAttributeRector; use RectorLaravel\Rector\Class_\WithoutTimestampsPropertyToWithoutTimestampsAttributeRector; +use RectorLaravel\Rector\ClassMethod\RemoveModelObserveCallsFromBootRector; return static function (RectorConfig $rectorConfig): void { $rectorConfig->import(__DIR__ . '/../config.php'); @@ -53,6 +55,8 @@ $rectorConfig->rule(FillablePropertyToFillableAttributeRector::class); $rectorConfig->rule(GuardedPropertyToGuardedAttributeRector::class); $rectorConfig->rule(HiddenPropertyToHiddenAttributeRector::class); + $rectorConfig->rule(ObserveCallsToObservedByAttributeRector::class); + $rectorConfig->rule(RemoveModelObserveCallsFromBootRector::class); $rectorConfig->rule(TablePropertyToTableAttributeRector::class); $rectorConfig->rule(TouchesPropertyToTouchesAttributeRector::class); $rectorConfig->rule(VisiblePropertyToVisibleAttributeRector::class); diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md index cb0f5d96..dd71f73a 100644 --- a/docs/rector_rules_overview.md +++ b/docs/rector_rules_overview.md @@ -1,4 +1,4 @@ -# 106 Rules Overview +# 108 Rules Overview ## AbortIfRector @@ -1284,6 +1284,25 @@ Swap the use of NotBooleans used with `filled()` and `blank()` to the correct he
+## ObserveCallsToObservedByAttributeRector + +Changes manual model `observe()` registrations in boot methods to use the `ObservedBy` attribute + +- class: [`RectorLaravel\Rector\Class_\ObserveCallsToObservedByAttributeRector`](../src/Rector/Class_/ObserveCallsToObservedByAttributeRector.php) + +```diff + use App\Observers\UserObserver; ++use Illuminate\Database\Eloquent\Attributes\ObservedBy; + use Illuminate\Foundation\Auth\User as Authenticatable; + ++#[ObservedBy([UserObserver::class])] + class User extends Authenticatable + { + } +``` + +
+ ## NowFuncWithStartOfDayMethodCallToTodayFuncRector Use `today()` instead of `now()->startOfDay()` @@ -1357,6 +1376,28 @@ Changes the queue property to use the Queue attribute
+## RemoveModelObserveCallsFromBootRector + +Removes direct model `observe()` registrations from boot methods when they can be represented by `ObservedBy` + +- class: [`RectorLaravel\Rector\ClassMethod\RemoveModelObserveCallsFromBootRector`](../src/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector.php) + +```diff + use App\Models\User; + use App\Observers\UserObserver; + + class AppServiceProvider + { + public function boot(): void + { +- User::observe(UserObserver::class); + $this->bootSomethingElse(); + } + } +``` + +
+ ## Redirect301ToPermanentRedirectRector Change "redirect" call with 301 to "permanentRedirect" diff --git a/src/NodeAnalyzer/ObservedByAnalyzer.php b/src/NodeAnalyzer/ObservedByAnalyzer.php new file mode 100644 index 00000000..d2417dc6 --- /dev/null +++ b/src/NodeAnalyzer/ObservedByAnalyzer.php @@ -0,0 +1,529 @@ +> + */ + private array $observerClassesByModel = []; + + /** + * @var array + */ + private array $canUpdateModelCache = []; + + /** + * @var array + */ + private array $classFilePaths = []; + + /** + * @var array + */ + private array $indexedFilePaths = []; + + private ?string $initializedProjectRoot = null; + + public function __construct( + private readonly RectorParser $rectorParser, + private readonly NodeNameResolver $nodeNameResolver, + private readonly FilesFinder $filesFinder, + ) {} + + public function reset(): void + { + $this->observerClassesByModel = []; + $this->canUpdateModelCache = []; + $this->classFilePaths = []; + $this->indexedFilePaths = []; + $this->initializedProjectRoot = null; + } + + public function matchObserveStaticCall(StaticCall $staticCall, ?string $currentClassName = null): ?ObservedByRegistration + { + if (! $this->nodeNameResolver->isName($staticCall->name, 'observe')) { + return null; + } + + $modelClass = $this->resolveObservedModelClass($staticCall, $currentClassName); + if (! is_string($modelClass)) { + return null; + } + + $observerClasses = $this->resolveObserverClassesFromArgs($staticCall->args); + if ($observerClasses === null) { + return null; + } + + return new ObservedByRegistration($modelClass, $observerClasses); + } + + public function resolveCurrentClassName(Node $node): ?string + { + $parentNode = $node->getAttribute('parent'); + while ($parentNode instanceof Node) { + if ($parentNode instanceof Class_) { + return $this->resolveClassName($parentNode); + } + + $parentNode = $parentNode->getAttribute('parent'); + } + + $scope = $node->getAttribute('scope'); + if (! is_object($scope) || ! method_exists($scope, 'getClassReflection')) { + return null; + } + + $classReflection = call_user_func([$scope, 'getClassReflection']); + if (! is_object($classReflection) || ! method_exists($classReflection, 'getName')) { + return null; + } + + $className = call_user_func([$classReflection, 'getName']); + + return is_string($className) ? $className : null; + } + + /** + * @return list + */ + public function resolveObserverClassesForModel(string $modelClass, string $currentFilePath): array + { + $this->initializeFromProjectRoot($this->resolveProjectRoot($currentFilePath), $currentFilePath); + + return $this->observerClassesByModel[$modelClass] ?? []; + } + + /** + * @return list|null + */ + public function resolveExistingObservedByClasses(Class_ $class): ?array + { + $observerClasses = []; + $hasObservedByAttribute = false; + + foreach ($class->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + if (! $this->nodeNameResolver->isName($attr->name, self::OBSERVED_BY_ATTRIBUTE)) { + continue; + } + + $hasObservedByAttribute = true; + $resolvedObserverClasses = $this->resolveObserverClassesFromAttribute($attr); + if ($resolvedObserverClasses === null) { + return null; + } + + $observerClasses = array_merge($observerClasses, $resolvedObserverClasses); + } + } + + if (! $hasObservedByAttribute) { + return []; + } + + return $this->uniqueObserverClasses($observerClasses); + } + + public function isLikelyEloquentModelClass(Class_ $class): bool + { + if (! $class->extends instanceof Name) { + return false; + } + + $extendedClass = $this->nodeNameResolver->getName($class->extends); + if (! is_string($extendedClass)) { + return false; + } + + return in_array($extendedClass, [ + 'Illuminate\\Database\\Eloquent\\Model', + 'Illuminate\\Foundation\\Auth\\User', + 'Model', + 'Authenticatable', + ], true) + || str_ends_with($extendedClass, '\\Model') + || str_ends_with($extendedClass, '\\Authenticatable'); + } + + /** + * @param list $observerClasses + */ + public function canUpdateModel(string $modelClass, array $observerClasses, string $currentFilePath): bool + { + $this->initializeFromProjectRoot($this->resolveProjectRoot($currentFilePath), $currentFilePath); + + $cacheKey = $modelClass . '|' . implode('|', $observerClasses); + if (array_key_exists($cacheKey, $this->canUpdateModelCache)) { + return $this->canUpdateModelCache[$cacheKey]; + } + + $fileName = $this->classFilePaths[$modelClass] ?? null; + if (! is_string($fileName)) { + return $this->canUpdateModelCache[$cacheKey] = false; + } + + $class = $this->findClassInFile($fileName, $modelClass); + if (! $class instanceof Class_) { + return $this->canUpdateModelCache[$cacheKey] = false; + } + + if (! $this->isLikelyEloquentModelClass($class)) { + return $this->canUpdateModelCache[$cacheKey] = false; + } + + $existingObservedByClasses = $this->resolveExistingObservedByClasses($class); + if ($existingObservedByClasses === null) { + return $this->canUpdateModelCache[$cacheKey] = false; + } + + return $this->canUpdateModelCache[$cacheKey] = true; + } + + /** + * @param array $args + * @return list|null + */ + private function resolveObserverClassesFromArgs(array $args): ?array + { + if (count($args) !== 1) { + return null; + } + + if (! $args[0] instanceof Arg) { + return null; + } + + if ($args[0]->name instanceof Identifier) { + return null; + } + + return $this->resolveObserverClassesFromExpr($args[0]->value); + } + + /** + * @return list|null + */ + private function resolveObserverClassesFromAttribute(Attribute $attribute): ?array + { + if (count($attribute->args) !== 1) { + return null; + } + + if ($attribute->args[0]->name instanceof Identifier) { + return null; + } + + return $this->resolveObserverClassesFromExpr($attribute->args[0]->value); + } + + /** + * @return list|null + */ + private function resolveObserverClassesFromExpr(Expr $expr): ?array + { + if ($expr instanceof ClassConstFetch) { + if (! $this->nodeNameResolver->isName($expr->name, 'class')) { + return null; + } + + $observerClass = $this->nodeNameResolver->getName($expr->class); + if (! is_string($observerClass)) { + return null; + } + + return [$observerClass]; + } + + if (! $expr instanceof Array_) { + return null; + } + + $observerClasses = []; + + foreach ($expr->items as $item) { + if ($item === null) { + return null; + } + + if ($item->key !== null) { + return null; + } + + $itemValue = $item->value; + if (! $itemValue instanceof ClassConstFetch) { + return null; + } + + if (! $this->nodeNameResolver->isName($itemValue->name, 'class')) { + return null; + } + + $observerClass = $this->nodeNameResolver->getName($itemValue->class); + if (! is_string($observerClass)) { + return null; + } + + $observerClasses[] = $observerClass; + } + + if ($observerClasses === []) { + return null; + } + + return $this->uniqueObserverClasses($observerClasses); + } + + private function initializeFromProjectRoot(string $projectRoot, string $currentFilePath): void + { + if ($this->initializedProjectRoot === $projectRoot) { + if (! isset($this->indexedFilePaths[$currentFilePath]) && is_file($currentFilePath)) { + $this->collectObserveRegistrationsFromFile($currentFilePath); + } + + return; + } + + $this->observerClassesByModel = []; + $this->canUpdateModelCache = []; + $this->classFilePaths = []; + $this->indexedFilePaths = []; + $this->initializedProjectRoot = $projectRoot; + + foreach ($this->resolveProjectFiles($projectRoot, $currentFilePath) as $filePath) { + $this->collectObserveRegistrationsFromFile($filePath); + } + } + + private function collectObserveRegistrationsFromFile(string $filePath): void + { + $this->indexedFilePaths[$filePath] = true; + + try { + $stmts = $this->rectorParser->parseFile($filePath); + } catch (Throwable) { + return; + } + + if ($stmts === []) { + return; + } + + $resolvedStmts = $this->resolveNames($stmts); + + SimpleCallableNodeTraverser::traverseNodesWithCallable($resolvedStmts, function (Node $node) use ($filePath): null { + if (! $node instanceof Class_) { + return null; + } + + $className = $this->resolveClassName($node); + if (! is_string($className)) { + return null; + } + + if (! array_key_exists($className, $this->classFilePaths)) { + $this->classFilePaths[$className] = $filePath; + } + + foreach ($node->getMethods() as $classMethod) { + if (! $this->isObserverRegistrationMethod($classMethod)) { + continue; + } + + foreach ((array) $classMethod->stmts as $stmt) { + if (! $stmt instanceof Expression) { + continue; + } + + $expr = $stmt->expr; + if (! $expr instanceof StaticCall) { + continue; + } + + $observedByRegistration = $this->matchObserveStaticCall($expr, $className); + if (! $observedByRegistration instanceof ObservedByRegistration) { + continue; + } + + $existingObserverClasses = $this->observerClassesByModel[$observedByRegistration->modelClass] ?? []; + $this->observerClassesByModel[$observedByRegistration->modelClass] = $this->uniqueObserverClasses([ + ...$existingObserverClasses, + ...$observedByRegistration->observerClasses, + ]); + } + } + + return null; + }); + } + + /** + * @param array $stmts + * @return array + */ + private function resolveNames(array $stmts): array + { + $nameResolver = new NameResolver(null, [ + 'replaceNodes' => false, + 'preserveOriginalNames' => true, + ]); + + $nodeTraverser = new NodeTraverser; + $nodeTraverser->addVisitor($nameResolver); + + /** @var array $resolvedStmts */ + $resolvedStmts = $nodeTraverser->traverse($stmts); + + return $resolvedStmts; + } + + private function resolveProjectRoot(string $currentFilePath): string + { + $directory = is_dir($currentFilePath) ? $currentFilePath : dirname($currentFilePath); + + while ($directory !== dirname($directory)) { + if (is_file($directory . '/composer.json')) { + return $directory; + } + + $directory = dirname($directory); + } + + return dirname($currentFilePath); + } + + private function findClassInFile(string $filePath, string $className): ?Class_ + { + try { + $stmts = $this->rectorParser->parseFile($filePath); + } catch (Throwable) { + return null; + } + + if ($stmts === []) { + return null; + } + + $resolvedStmts = $this->resolveNames($stmts); + $foundClass = null; + + SimpleCallableNodeTraverser::traverseNodesWithCallable($resolvedStmts, function (Node $node) use ($className, &$foundClass): null { + if (! $node instanceof Class_) { + return null; + } + + if ($this->resolveClassName($node) !== $className) { + return null; + } + + $foundClass = $node; + + return null; + }); + + return $foundClass; + } + + /** + * @return list + */ + private function resolveProjectFiles(string $projectRoot, string $currentFilePath): array + { + $configuredPaths = array_filter( + SimpleParameterProvider::provideArrayParameter(Option::PATHS), + is_string(...) + ); + + $projectPaths = []; + + foreach ($configuredPaths as $configuredPath) { + if ($configuredPath === $projectRoot || str_starts_with($configuredPath, $projectRoot . '/')) { + $projectPaths[] = $configuredPath; + } + } + + if ($projectPaths === []) { + $projectPaths = [$projectRoot]; + } + + $filePaths = $this->filesFinder->findInDirectoriesAndFiles($projectPaths, ['php']); + + if (is_file($currentFilePath)) { + $filePaths[] = $currentFilePath; + } + + return array_values(array_unique($filePaths)); + } + + /** + * @param list $observerClasses + * @return list + */ + private function uniqueObserverClasses(array $observerClasses): array + { + return array_values(array_unique($observerClasses)); + } + + private function isObserverRegistrationMethod(ClassMethod $classMethod): bool + { + return $this->nodeNameResolver->isNames($classMethod->name, ['boot', 'booted']); + } + + private function resolveObservedModelClass(StaticCall $staticCall, ?string $currentClassName): ?string + { + if ($currentClassName !== null && $staticCall->class instanceof Name && $this->nodeNameResolver->isNames($staticCall->class, ['self', 'static'])) { + return $currentClassName; + } + + $modelClass = $this->nodeNameResolver->getName($staticCall->class); + if (! is_string($modelClass)) { + return null; + } + + return $modelClass; + } + + private function resolveClassName(Class_ $class): ?string + { + if ($class->namespacedName instanceof Name) { + return $class->namespacedName->toString(); + } + + if (! $class->name instanceof Identifier) { + return null; + } + + return $class->name->toString(); + } +} diff --git a/src/NodeFactory/ObservedByAttributeFactory.php b/src/NodeFactory/ObservedByAttributeFactory.php new file mode 100644 index 00000000..2267785d --- /dev/null +++ b/src/NodeFactory/ObservedByAttributeFactory.php @@ -0,0 +1,35 @@ + $observerClasses + */ + public function create(array $observerClasses): AttributeGroup + { + $items = []; + + foreach ($observerClasses as $observerClass) { + $items[] = new ArrayItem(new ClassConstFetch(new FullyQualified($observerClass), 'class')); + } + + return new AttributeGroup([ + new Attribute( + new FullyQualified('Illuminate\\Database\\Eloquent\\Attributes\\ObservedBy'), + [new Arg(new Array_($items))] + ), + ]); + } +} diff --git a/src/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector.php b/src/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector.php new file mode 100644 index 00000000..0241ad16 --- /dev/null +++ b/src/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector.php @@ -0,0 +1,116 @@ +bootSomethingElse(); + } +} +CODE_SAMPLE, + <<<'CODE_SAMPLE' +use App\Models\User; +use App\Observers\UserObserver; + +class AppServiceProvider +{ + public function boot(): void + { + $this->bootSomethingElse(); + } +} +CODE_SAMPLE + )] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): Node|int|null + { + if (! $this->isNames($node->name, ['boot', 'booted'])) { + return null; + } + + $currentClassName = $this->observedByAnalyzer->resolveCurrentClassName($node); + + $hasChanged = false; + + foreach ((array) $node->stmts as $key => $stmt) { + if (! $stmt instanceof Expression) { + continue; + } + + $expr = $stmt->expr; + if (! $expr instanceof StaticCall) { + continue; + } + + $observedByRegistration = $this->observedByAnalyzer->matchObserveStaticCall($expr, $currentClassName); + if (! $observedByRegistration instanceof ObservedByRegistration) { + continue; + } + + if (! $this->observedByAnalyzer->canUpdateModel($observedByRegistration->modelClass, $observedByRegistration->observerClasses, $this->getFile()->getFilePath())) { + continue; + } + + unset($node->stmts[$key]); + $hasChanged = true; + } + + if (! $hasChanged) { + return null; + } + + $node->stmts = array_values((array) $node->stmts); + + if ($node->stmts === []) { + return NodeVisitor::REMOVE_NODE; + } + + return $node; + } +} diff --git a/src/Rector/Class_/ObserveCallsToObservedByAttributeRector.php b/src/Rector/Class_/ObserveCallsToObservedByAttributeRector.php new file mode 100644 index 00000000..ec2d85ff --- /dev/null +++ b/src/Rector/Class_/ObserveCallsToObservedByAttributeRector.php @@ -0,0 +1,158 @@ +> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + $className = $this->resolveClassName($node); + if (! is_string($className)) { + return null; + } + + $observerClassesFromObserveCalls = $this->observedByAnalyzer->resolveObserverClassesForModel($className, $this->getFile()->getFilePath()); + if ($observerClassesFromObserveCalls === []) { + return null; + } + + if (! $this->observedByAnalyzer->isLikelyEloquentModelClass($node)) { + return null; + } + + $existingObservedByClasses = $this->observedByAnalyzer->resolveExistingObservedByClasses($node); + if ($existingObservedByClasses === null) { + return null; + } + + $mergedObserverClasses = array_values(array_unique([ + ...$existingObservedByClasses, + ...$observerClassesFromObserveCalls, + ])); + + if ($mergedObserverClasses === $existingObservedByClasses) { + return null; + } + + $observedByAttributeGroup = $this->observedByAttributeFactory->create($mergedObserverClasses); + $matchingAttributeGroupKeys = []; + + foreach ($node->attrGroups as $key => $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + if ($this->isName($attr->name, 'Illuminate\\Database\\Eloquent\\Attributes\\ObservedBy')) { + $matchingAttributeGroupKeys[] = $key; + break; + } + } + } + + if ($matchingAttributeGroupKeys === []) { + $node->attrGroups[] = $observedByAttributeGroup; + + return $node; + } + + $firstMatchingAttributeGroupKey = array_shift($matchingAttributeGroupKeys); + if (! is_int($firstMatchingAttributeGroupKey)) { + return null; + } + + $node->attrGroups[$firstMatchingAttributeGroupKey] = $observedByAttributeGroup; + + foreach ($matchingAttributeGroupKeys as $matchingAttributeGroupKey) { + unset($node->attrGroups[$matchingAttributeGroupKey]); + } + + $node->attrGroups = array_values($node->attrGroups); + + return $node; + } + + private function resolveClassName(Class_ $class): ?string + { + if ($class->namespacedName instanceof Name) { + return $class->namespacedName->toString(); + } + + if (! $class->name instanceof Identifier) { + return null; + } + + return $class->name->toString(); + } +} diff --git a/src/ValueObject/ObservedByRegistration.php b/src/ValueObject/ObservedByRegistration.php new file mode 100644 index 00000000..094dd8ed --- /dev/null +++ b/src/ValueObject/ObservedByRegistration.php @@ -0,0 +1,16 @@ + $observerClasses + */ + public function __construct( + public string $modelClass, + public array $observerClasses, + ) {} +} diff --git a/stubs/Illuminate/Database/Eloquent/Attributes/ObservedBy.php b/stubs/Illuminate/Database/Eloquent/Attributes/ObservedBy.php new file mode 100644 index 00000000..bc9f5d69 --- /dev/null +++ b/stubs/Illuminate/Database/Eloquent/Attributes/ObservedBy.php @@ -0,0 +1,20 @@ + $observer + */ + public function __construct( + public string|array $observer, + ) {} +} diff --git a/stubs/Illuminate/Database/Eloquent/Model.php b/stubs/Illuminate/Database/Eloquent/Model.php index eed1bc1e..614ab7db 100644 --- a/stubs/Illuminate/Database/Eloquent/Model.php +++ b/stubs/Illuminate/Database/Eloquent/Model.php @@ -37,6 +37,8 @@ public static function query(): Builder return new Builder; } + public static function observe(string $observer): void {} + /** * Exists in the Illuminate/Database/Eloquent/Concerns/HasTimestamps trait * Put here for simplicity diff --git a/stubs/Illuminate/Foundation/Auth/User.php b/stubs/Illuminate/Foundation/Auth/User.php new file mode 100644 index 00000000..897420f1 --- /dev/null +++ b/stubs/Illuminate/Foundation/Auth/User.php @@ -0,0 +1,11 @@ +bootSomethingElse(); + } + + private function bootSomethingElse(): void + { + } +} + +final class UserObserver +{ +} + +?> +----- +bootSomethingElse(); + } + + private function bootSomethingElse(): void + { + } +} + +final class UserObserver +{ +} + +?> diff --git a/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/remove_direct_observe_when_model_already_has_matching_attribute.php.inc b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/remove_direct_observe_when_model_already_has_matching_attribute.php.inc new file mode 100644 index 00000000..8ea08b29 --- /dev/null +++ b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/remove_direct_observe_when_model_already_has_matching_attribute.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/remove_empty_boot_with_override_attribute.php.inc b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/remove_empty_boot_with_override_attribute.php.inc new file mode 100644 index 00000000..8d0e02ab --- /dev/null +++ b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/remove_empty_boot_with_override_attribute.php.inc @@ -0,0 +1,36 @@ + +----- + diff --git a/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/remove_model_booted_static_observe.php.inc b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/remove_model_booted_static_observe.php.inc new file mode 100644 index 00000000..d02ee5c6 --- /dev/null +++ b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/remove_model_booted_static_observe.php.inc @@ -0,0 +1,40 @@ + +----- + diff --git a/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/remove_multiple_observer_registration_styles.php.inc b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/remove_multiple_observer_registration_styles.php.inc new file mode 100644 index 00000000..1418e156 --- /dev/null +++ b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/remove_multiple_observer_registration_styles.php.inc @@ -0,0 +1,52 @@ + +----- + diff --git a/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/skip_unmergeable_model_attribute.php.inc b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/skip_unmergeable_model_attribute.php.inc new file mode 100644 index 00000000..7f469c4e --- /dev/null +++ b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/Fixture/skip_unmergeable_model_attribute.php.inc @@ -0,0 +1,19 @@ + diff --git a/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/RemoveModelObserveCallsFromBootRectorTest.php b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/RemoveModelObserveCallsFromBootRectorTest.php new file mode 100644 index 00000000..31231781 --- /dev/null +++ b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/RemoveModelObserveCallsFromBootRectorTest.php @@ -0,0 +1,31 @@ +doTestFile($filePath, true); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/config/configured_rule.php b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/config/configured_rule.php new file mode 100644 index 00000000..4d9e1a71 --- /dev/null +++ b/tests/Rector/ClassMethod/RemoveModelObserveCallsFromBootRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(RemoveModelObserveCallsFromBootRector::class); +}; diff --git a/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/MergeExistingObservedByAttribute/AppServiceProvider.php b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/MergeExistingObservedByAttribute/AppServiceProvider.php new file mode 100644 index 00000000..e32e2ff6 --- /dev/null +++ b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/MergeExistingObservedByAttribute/AppServiceProvider.php @@ -0,0 +1,13 @@ + +----- + diff --git a/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/model_booted_static_observe.php.inc b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/model_booted_static_observe.php.inc new file mode 100644 index 00000000..b51fc98a --- /dev/null +++ b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/model_booted_static_observe.php.inc @@ -0,0 +1,44 @@ + +----- + diff --git a/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/simple_observe_registration.php.inc b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/simple_observe_registration.php.inc new file mode 100644 index 00000000..96efd4cb --- /dev/null +++ b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/simple_observe_registration.php.inc @@ -0,0 +1,28 @@ + +----- + diff --git a/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/skip_existing_same_observed_by_attribute.php.inc b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/skip_existing_same_observed_by_attribute.php.inc new file mode 100644 index 00000000..9ad162b7 --- /dev/null +++ b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/skip_existing_same_observed_by_attribute.php.inc @@ -0,0 +1,14 @@ + diff --git a/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/skip_nested_observe_call.php.inc b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/skip_nested_observe_call.php.inc new file mode 100644 index 00000000..dcec21bd --- /dev/null +++ b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/skip_nested_observe_call.php.inc @@ -0,0 +1,13 @@ + diff --git a/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/skip_not_a_model.php.inc b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/skip_not_a_model.php.inc new file mode 100644 index 00000000..cf966c2f --- /dev/null +++ b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/Fixture/skip_not_a_model.php.inc @@ -0,0 +1,11 @@ + diff --git a/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/ObserveCallsToObservedByAttributeRectorTest.php b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/ObserveCallsToObservedByAttributeRectorTest.php new file mode 100644 index 00000000..4dd3f2d1 --- /dev/null +++ b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/ObserveCallsToObservedByAttributeRectorTest.php @@ -0,0 +1,31 @@ +doTestFile($filePath, true); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/config/configured_rule.php b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/config/configured_rule.php new file mode 100644 index 00000000..a81cadd3 --- /dev/null +++ b/tests/Rector/Class_/ObserveCallsToObservedByAttributeRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(ObserveCallsToObservedByAttributeRector::class); +}; diff --git a/tests/Sets/Laravel130/Fixture/ObservedByAttribute/AppServiceProvider.php b/tests/Sets/Laravel130/Fixture/ObservedByAttribute/AppServiceProvider.php new file mode 100644 index 00000000..06fc65a3 --- /dev/null +++ b/tests/Sets/Laravel130/Fixture/ObservedByAttribute/AppServiceProvider.php @@ -0,0 +1,13 @@ + +----- +