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..903fc7e --- /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..1567179 --- /dev/null +++ b/src/Support/Parser/Visitors/AddImportsVisitor.php @@ -0,0 +1,87 @@ +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) { + $existingUses[] = $use->name->toString(); + } + } + } + + return $existingUses; + } + + protected function getNewImports(array $importsToAdd): array + { + $newUseStmts = []; + + foreach ($importsToAdd as $classFullName) { + $newUseStmts[] = new Use_([ + new UseUse(new Name($classFullName)) + ]); + } + + 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; + } + } + } +} \ 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..358915c --- /dev/null +++ b/src/Support/Parser/Visitors/MethodVisitors/AppendPartToMethodVisitor.php @@ -0,0 +1,72 @@ +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 = []; + + 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 + { + } +}