From 550266ba1a6de00fabc9217d733642c155805f70 Mon Sep 17 00:00:00 2001 From: Ruslan Guskov Date: Thu, 28 Aug 2025 16:04:05 +0300 Subject: [PATCH 1/4] feat: appendPartToMethod --- src/Commands/InitCommand.php | 22 ++++++ .../Arguments/ClassConstFetchArgument.php | 19 +++++ .../Parser/Expressions/AppendMethodCall.php | 35 +++++++++ src/Support/Parser/PhpParser.php | 16 ++++ .../Parser/Visitors/AddImportsVisitor.php | 64 ++++++++++++++++ .../AppendPartToMethodVisitor.php | 74 +++++++++++++++++++ tests/InitCommandTest.php | 18 +++++ .../InitCommandTest/app_service_provider.php | 25 +++++++ .../modified_app_service_provider.php | 28 +++++++ 9 files changed, 301 insertions(+) create mode 100644 src/Support/Parser/Arguments/ClassConstFetchArgument.php create mode 100644 src/Support/Parser/Expressions/AppendMethodCall.php create mode 100644 src/Support/Parser/Visitors/AddImportsVisitor.php create mode 100644 src/Support/Parser/Visitors/MethodVisitors/AppendPartToMethodVisitor.php create mode 100644 tests/fixtures/InitCommandTest/app_service_provider.php create mode 100644 tests/fixtures/InitCommandTest/modified_app_service_provider.php diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index be362b3..528c3cd 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -15,6 +15,7 @@ use RonasIT\ProjectInitializator\Enums\AppTypeEnum; use Winter\LaravelConfigWriter\ArrayFile; use RonasIT\ProjectInitializator\Support\Parser\PhpParser; +use RonasIT\ProjectInitializator\Support\Parser\Arguments\ClassConstFetchArgument; class InitCommand extends Command implements Isolatable { @@ -553,9 +554,30 @@ protected function enableClerk(): void path: 'app/Support/Clerk', ); + $this->addClerkRepositoryBind(); $this->modifyUserModel(); } + protected function addClerkRepositoryBind(): void + { + $parser = app(PhpParser::class, ['filePath' => 'app/Providers/AppServiceProvider.php']); + + $parser + ->appendPartToMethod( + methodName: 'boot', + variableName: 'this', + callMethodName: 'bind', + propertyName: 'app', + firstArgument: new ClassConstFetchArgument('UserRepositoryContract'), + secondArgument: new ClassConstFetchArgument('ClerkUserRepository'), + ) + ->addImports([ + 'RonasIT\\Clerk\\Contracts\\UserRepositoryContract', + 'App\\Support\Clerk\\ClerkUserRepository', + ]) + ->save(); + } + protected function modifyUserModel(): void { $parser = app(PhpParser::class, ['filePath' => 'app/Models/User.php']); diff --git a/src/Support/Parser/Arguments/ClassConstFetchArgument.php b/src/Support/Parser/Arguments/ClassConstFetchArgument.php new file mode 100644 index 0000000..5145bfb --- /dev/null +++ b/src/Support/Parser/Arguments/ClassConstFetchArgument.php @@ -0,0 +1,19 @@ +value = new ClassConstFetch(new Name($this->argumentName), 'class'); + + return parent::__construct($this->value); + } +} \ No newline at end of file diff --git a/src/Support/Parser/Expressions/AppendMethodCall.php b/src/Support/Parser/Expressions/AppendMethodCall.php new file mode 100644 index 0000000..959f57f --- /dev/null +++ b/src/Support/Parser/Expressions/AppendMethodCall.php @@ -0,0 +1,35 @@ +traverser->addVisitor(new AppendPartToMethodVisitor($methodName, $variableName, $callMethodName, $propertyName, ...$args)); + + return $this; + } + + public function addImports(array $fullClassNames): self + { + $this->traverser->addVisitor(new AddImportsVisitor($fullClassNames)); + + return $this; + } public function save(): void { diff --git a/src/Support/Parser/Visitors/AddImportsVisitor.php b/src/Support/Parser/Visitors/AddImportsVisitor.php new file mode 100644 index 0000000..41db0ec --- /dev/null +++ b/src/Support/Parser/Visitors/AddImportsVisitor.php @@ -0,0 +1,64 @@ +stmts as $stmt) { + if ($stmt instanceof Use_) { + foreach ($stmt->uses as $use) { + $existingUses[] = $use->name->toString(); + } + } + } + + $toAdd = array_diff($this->classFullNames, $existingUses); + if (empty($toAdd)) { + return null; + } + + $newUseStmts = []; + foreach ($toAdd as $classFullName) { + $newUseStmts[] = new Use_([ + new UseUse(new Name($classFullName)) + ]); + } + + $inserted = false; + foreach ($node->stmts as $i => $stmt) { + if (!($stmt instanceof Use_)) { + array_splice($node->stmts, $i, 0, $newUseStmts); + $inserted = true; + break; + } + } + + if (!$inserted) { + foreach ($newUseStmts as $useStmt) { + $node->stmts[] = $useStmt; + } + } + + return null; + } +} \ No newline at end of file diff --git a/src/Support/Parser/Visitors/MethodVisitors/AppendPartToMethodVisitor.php b/src/Support/Parser/Visitors/MethodVisitors/AppendPartToMethodVisitor.php new file mode 100644 index 0000000..edef748 --- /dev/null +++ b/src/Support/Parser/Visitors/MethodVisitors/AppendPartToMethodVisitor.php @@ -0,0 +1,74 @@ +arguments = $arguments; + } + + public function enterNode(Node $node) + { + $stmtToAdd = new Expression( + AppendMethodCall::make( + variableName: $this->variableName, + methodName: $this->callMethodName, + arguments: $this->arguments, + propertyName: $this->propertyName, + ) + ); + + if ($node instanceof ClassMethod + && $node->name->toString() === $this->methodName + && !in_array($this->classMethodKeys($stmtToAdd), $this->findMethodKeysInExistClassMethods($node))) { + $node->stmts[] = $stmtToAdd; + } + } + + protected function classMethodKeys($stmts): array + { + $expressionAttributes = []; + + $expressionAttributes[] = $stmts->expr->name->name; + + foreach ($stmts->expr->var as $var) { + $expressionAttributes[] = $var->name; + } + + foreach ($stmts->expr->args as $arg) { + $expressionAttributes[] = $arg->value->class->name; + } + + return $expressionAttributes; + } + + protected function findMethodKeysInExistClassMethods(Node $node): array + { + $nodeStmtsKey = []; + + if ($node instanceof ClassMethod) { + foreach ($node->stmts as $stmt) { + $nodeStmtsKey[] = $this->classMethodKeys($stmt); + } + } + + return $nodeStmtsKey; + } +} \ No newline at end of file diff --git a/tests/InitCommandTest.php b/tests/InitCommandTest.php index 95b8189..a7f5d4e 100644 --- a/tests/InitCommandTest.php +++ b/tests/InitCommandTest.php @@ -222,11 +222,20 @@ public function testRunWithAdminAndDefaultReadmeCreation() $this->mockNativeFunction('RonasIT\ProjectInitializator\Support\Parser', [ + $this->functionCall( + name: 'file_get_contents', + arguments: ['app/Providers/AppServiceProvider.php'], + result: $this->getFixture('app_service_provider.php'), + ), $this->functionCall( name: 'file_get_contents', arguments: ['app/Models/User.php'], result: $this->getFixture('user_model.php'), ), + $this->functionCall( + name: 'file_put_contents', + arguments: ['app/Providers/AppServiceProvider.php', $this->getFixture('modified_app_service_provider.php')], + ), $this->functionCall( name: 'file_put_contents', arguments: ['app/Models/User.php', $this->getFixture('modified_user_model.php')], @@ -807,11 +816,20 @@ public function testRunWithClerkMobileApp(): void $this->mockNativeFunction('RonasIT\ProjectInitializator\Support\Parser', [ + $this->functionCall( + name: 'file_get_contents', + arguments: ['app/Providers/AppServiceProvider.php'], + result: $this->getFixture('app_service_provider.php'), + ), $this->functionCall( name: 'file_get_contents', arguments: ['app/Models/User.php'], result: $this->getFixture('user_model.php'), ), + $this->functionCall( + name: 'file_put_contents', + arguments: ['app/Providers/AppServiceProvider.php', $this->getFixture('modified_app_service_provider.php')], + ), $this->functionCall( name: 'file_put_contents', arguments: ['app/Models/User.php', $this->getFixture('modified_user_model.php')], diff --git a/tests/fixtures/InitCommandTest/app_service_provider.php b/tests/fixtures/InitCommandTest/app_service_provider.php new file mode 100644 index 0000000..073056c --- /dev/null +++ b/tests/fixtures/InitCommandTest/app_service_provider.php @@ -0,0 +1,25 @@ +app->bind(UserRepositoryContract::class, ClerkUserRepository::class); + } + /** + * Register any application services. + * + * @return void + */ + public function register(): void + { + } +} From a7a9a9d452ab0a4052c9d4d11e0d2c53af5ae240 Mon Sep 17 00:00:00 2001 From: Ruslan Guskov Date: Thu, 28 Aug 2025 16:33:21 +0300 Subject: [PATCH 2/4] fix: remove useless check --- .../Visitors/MethodVisitors/AppendPartToMethodVisitor.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Support/Parser/Visitors/MethodVisitors/AppendPartToMethodVisitor.php b/src/Support/Parser/Visitors/MethodVisitors/AppendPartToMethodVisitor.php index edef748..358915c 100644 --- a/src/Support/Parser/Visitors/MethodVisitors/AppendPartToMethodVisitor.php +++ b/src/Support/Parser/Visitors/MethodVisitors/AppendPartToMethodVisitor.php @@ -63,10 +63,8 @@ protected function findMethodKeysInExistClassMethods(Node $node): array { $nodeStmtsKey = []; - if ($node instanceof ClassMethod) { - foreach ($node->stmts as $stmt) { - $nodeStmtsKey[] = $this->classMethodKeys($stmt); - } + foreach ($node->stmts as $stmt) { + $nodeStmtsKey[] = $this->classMethodKeys($stmt); } return $nodeStmtsKey; From 530356cf98e5cf13a92743add1e0b8c4eb4d22ed Mon Sep 17 00:00:00 2001 From: Ruslan Guskov Date: Fri, 29 Aug 2025 17:24:19 +0300 Subject: [PATCH 3/4] refactor: remove extend --- src/Support/Parser/Expressions/AppendMethodCall.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Support/Parser/Expressions/AppendMethodCall.php b/src/Support/Parser/Expressions/AppendMethodCall.php index 959f57f..903fc7e 100644 --- a/src/Support/Parser/Expressions/AppendMethodCall.php +++ b/src/Support/Parser/Expressions/AppendMethodCall.php @@ -6,7 +6,7 @@ use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; -class AppendMethodCall extends MethodCall +class AppendMethodCall { public static function make( string $variableName, @@ -14,11 +14,11 @@ public static function make( array $arguments, ?string $propertyName = null, array $attributes = [], - ): self + ): MethodCall { $variable = self::setVariable($variableName, $propertyName); - return new self( + return new MethodCall( var: $variable, name: $methodName, args: $arguments, From 0f70a2c2026f8b3b52a40b322a5ef92f35b74a7d Mon Sep 17 00:00:00 2001 From: Ruslan Guskov Date: Tue, 2 Sep 2025 17:07:01 +0300 Subject: [PATCH 4/4] refactor: addImportsVisitor --- .../Parser/Visitors/AddImportsVisitor.php | 55 +++++++++++++------ 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/src/Support/Parser/Visitors/AddImportsVisitor.php b/src/Support/Parser/Visitors/AddImportsVisitor.php index 41db0ec..1567179 100644 --- a/src/Support/Parser/Visitors/AddImportsVisitor.php +++ b/src/Support/Parser/Visitors/AddImportsVisitor.php @@ -17,13 +17,37 @@ public function __construct( { } - public function enterNode(Node $node) + public function enterNode(Node $node): void { if (!$node instanceof Namespace_) { - return null; + return; } + $existingImports = $this->getExistingImports($node); + + $importsToAdd = array_diff($this->classFullNames, $existingImports); + + if (empty($importsToAdd)) { + return; + } + + $newUseStmts = $this->getNewImports($importsToAdd); + + $inserted = false; + + $this->insertImportsToUseBlock($node, $newUseStmts, $inserted); + + if (!$inserted) { + foreach ($newUseStmts as $useStmt) { + $node->stmts[] = $useStmt; + } + } + } + + protected function getExistingImports(Node $node): array + { $existingUses = []; + foreach ($node->stmts as $stmt) { if ($stmt instanceof Use_) { foreach ($stmt->uses as $use) { @@ -32,33 +56,32 @@ public function enterNode(Node $node) } } - $toAdd = array_diff($this->classFullNames, $existingUses); - if (empty($toAdd)) { - return null; - } + return $existingUses; + } + protected function getNewImports(array $importsToAdd): array + { $newUseStmts = []; - foreach ($toAdd as $classFullName) { + + foreach ($importsToAdd as $classFullName) { $newUseStmts[] = new Use_([ new UseUse(new Name($classFullName)) ]); } - $inserted = false; + return $newUseStmts; + } + + protected function insertImportsToUseBlock(Node $node, array $newUseStmts, bool &$inserted): void + { foreach ($node->stmts as $i => $stmt) { if (!($stmt instanceof Use_)) { array_splice($node->stmts, $i, 0, $newUseStmts); + $inserted = true; - break; - } - } - if (!$inserted) { - foreach ($newUseStmts as $useStmt) { - $node->stmts[] = $useStmt; + break; } } - - return null; } } \ No newline at end of file