diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index 2fc9d80..2fcfdcb 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -14,6 +14,7 @@ use RonasIT\ProjectInitializator\Enums\RoleEnum; use RonasIT\ProjectInitializator\Enums\AppTypeEnum; use Winter\LaravelConfigWriter\ArrayFile; +use RonasIT\ProjectInitializator\Support\Parser\PhpParser; class InitCommand extends Command implements Isolatable { @@ -562,6 +563,19 @@ protected function enableClerk(): void fileName: 'ClerkUserRepository', filePath: 'app/Support/Clerk', ); + + $this->modifyUserModel(); + } + + protected function modifyUserModel(): void + { + $parser = app(PhpParser::class, ['filePath' => 'app/Models/User.php']); + + $parser + ->addValueToArrayProperty(['fillable'], 'clerk_id') + ->removeValueFromArrayProperty(['fillable', 'hidden'], 'password') + ->removeValueFromMethodReturnArray(['casts'], 'password') + ->save(); } protected function publishWebLogin(): void diff --git a/src/ProjectInitializatorServiceProvider.php b/src/ProjectInitializatorServiceProvider.php index b0d1fca..441798a 100644 --- a/src/ProjectInitializatorServiceProvider.php +++ b/src/ProjectInitializatorServiceProvider.php @@ -4,6 +4,8 @@ use Illuminate\Support\ServiceProvider; use RonasIT\ProjectInitializator\Commands\InitCommand; +use RonasIT\ProjectInitializator\Support\Parser\PhpParser; +use RonasIT\ProjectInitializator\Support\Parser\Factories\PhpParserFactory; class ProjectInitializatorServiceProvider extends ServiceProvider { @@ -22,5 +24,9 @@ public function boot(): void ], 'initializator-web-login'); $this->loadViewsFrom(__DIR__ . '/../resources/views', 'initializator'); + + $this->app->bind(PhpParser::class, function ($app, $params) { + return PhpParserFactory::create($params['filePath']); + }); } } diff --git a/src/Support/Parser/Factories/PhpParserFactory.php b/src/Support/Parser/Factories/PhpParserFactory.php new file mode 100644 index 0000000..e8e1be1 --- /dev/null +++ b/src/Support/Parser/Factories/PhpParserFactory.php @@ -0,0 +1,21 @@ +createForNewestSupportedVersion(), + new NodeTraverser(), + new Standard(), + ); + } +} \ No newline at end of file diff --git a/src/Support/Parser/PhpParser.php b/src/Support/Parser/PhpParser.php new file mode 100644 index 0000000..f1cbd96 --- /dev/null +++ b/src/Support/Parser/PhpParser.php @@ -0,0 +1,118 @@ +ast = $parser->parse(file_get_contents($filePath)); + + $this->addEmptySpacesVisitor(); + } + + public function removeValueFromArrayProperty(array $propertyNames, string $value): self + { + $this->traverser->addVisitor(new RemoveValueFromArrayPropertyPropertyArrayVisitor($propertyNames, $value)); + + return $this; + } + + public function removeValueFromMethodReturnArray(array $methodNames, string $value): self + { + $this->traverser->addVisitor(new RemoveValueFromMethodReturnArrayVisitor($methodNames, $value)); + + return $this; + } + + public function addValueToArrayProperty(array $propertyNames, string $value): self + { + $this->traverser->addVisitor(new AddValueToArrayPropertyPropertyArrayVisitor($propertyNames, $value)); + + return $this; + } + + public function save(): void + { + $modifiedAst = $this->traverser->traverse($this->ast); + + file_put_contents($this->filePath, $this->printer->prettyPrintFile($modifiedAst)); + } + + protected function addEmptySpacesVisitor(): void + { + $this->traverser->addVisitor( + new class extends NodeVisitorAbstract { + public function enterNode(Node $node): void + { + if (!$node instanceof Namespace_) { + return; + } + + $this->addLineAfterLastUse($node); + $this->addEndLine($node); + } + + private function addLineAfterLastUse(Namespace_ $namespace): void + { + $lastUseIndex = $this->findLastUseIndex($namespace); + + if ($lastUseIndex === null) { + return; + } + + $afterLastUse = $namespace->stmts[$lastUseIndex + 1] ?? null; + + if (!$afterLastUse instanceof Nop) { + array_splice($namespace->stmts, $lastUseIndex + 1, 0, [new Nop()]); + } + } + + private function addEndLine(Namespace_ $namespace): void + { + $lastStmtIndex = count($namespace->stmts) - 1; + + if ($lastStmtIndex < 0) { + return; + } + + if (!$namespace->stmts[$lastStmtIndex] instanceof Nop) { + $namespace->stmts[] = new Nop(); + } + } + + private function findLastUseIndex(Namespace_ $namespace): ?int + { + $lastUseIndex = null; + + foreach ($namespace->stmts as $i => $stmt) { + if ($stmt instanceof Use_) { + $lastUseIndex = $i; + } + } + + return $lastUseIndex; + } + } + ); + } +} \ No newline at end of file diff --git a/src/Support/Parser/Visitors/ArrayVisitors/BaseArrayVisitor.php b/src/Support/Parser/Visitors/ArrayVisitors/BaseArrayVisitor.php new file mode 100644 index 0000000..19ebaa5 --- /dev/null +++ b/src/Support/Parser/Visitors/ArrayVisitors/BaseArrayVisitor.php @@ -0,0 +1,32 @@ +stmtNames, true) + && $default instanceof Array_; + } + + protected function getTargetItems(array $items): array + { + $filteredArray = array_filter($items, fn($item) => !$this->isTargetItem($item)); + + return array_values($filteredArray); + } + + abstract protected function isTargetItem(?ArrayItem $item): bool; +} \ No newline at end of file diff --git a/src/Support/Parser/Visitors/ArrayVisitors/MethodReturnArrayVisitors/BaseMethodReturnArrayVisitor.php b/src/Support/Parser/Visitors/ArrayVisitors/MethodReturnArrayVisitors/BaseMethodReturnArrayVisitor.php new file mode 100644 index 0000000..1b3fc41 --- /dev/null +++ b/src/Support/Parser/Visitors/ArrayVisitors/MethodReturnArrayVisitors/BaseMethodReturnArrayVisitor.php @@ -0,0 +1,26 @@ +key instanceof String_ + && $item->key->value === $this->value; + } + + protected function isTargetReturnArray(Node $stmt): bool + { + return $stmt instanceof Return_ + && $stmt->expr instanceof Array_; + } +} \ No newline at end of file diff --git a/src/Support/Parser/Visitors/ArrayVisitors/MethodReturnArrayVisitors/RemoveValueFromMethodReturnArrayVisitor.php b/src/Support/Parser/Visitors/ArrayVisitors/MethodReturnArrayVisitors/RemoveValueFromMethodReturnArrayVisitor.php new file mode 100644 index 0000000..68587f5 --- /dev/null +++ b/src/Support/Parser/Visitors/ArrayVisitors/MethodReturnArrayVisitors/RemoveValueFromMethodReturnArrayVisitor.php @@ -0,0 +1,28 @@ +name->toString(); + + if (!in_array($methodName, $this->stmtNames, true)) { + return; + } + + foreach ($node->stmts as $stmt) { + if ($this->isTargetReturnArray($stmt)) { + $stmt->expr->items = $this->getTargetItems($stmt->expr->items); + } + } + } +} \ No newline at end of file diff --git a/src/Support/Parser/Visitors/ArrayVisitors/PropertyArrayVisitors/AddValueToArrayPropertyPropertyArrayVisitor.php b/src/Support/Parser/Visitors/ArrayVisitors/PropertyArrayVisitors/AddValueToArrayPropertyPropertyArrayVisitor.php new file mode 100644 index 0000000..24b6bba --- /dev/null +++ b/src/Support/Parser/Visitors/ArrayVisitors/PropertyArrayVisitors/AddValueToArrayPropertyPropertyArrayVisitor.php @@ -0,0 +1,34 @@ +props[0]; + + $propertyName = $property->name->toString(); + + if (!$this->isTargetProperty($propertyName, $property->default)) { + return; + } + + $array = $property->default; + + if ($this->getTargetItems($array->items) !== $array->items) { + return; + } + + $array->items[] = new ArrayItem(new String_($this->value)); + } +} \ No newline at end of file diff --git a/src/Support/Parser/Visitors/ArrayVisitors/PropertyArrayVisitors/BasePropertyArrayVisitor.php b/src/Support/Parser/Visitors/ArrayVisitors/PropertyArrayVisitors/BasePropertyArrayVisitor.php new file mode 100644 index 0000000..dbe9839 --- /dev/null +++ b/src/Support/Parser/Visitors/ArrayVisitors/PropertyArrayVisitors/BasePropertyArrayVisitor.php @@ -0,0 +1,15 @@ +value->value === $this->value; + } +} \ No newline at end of file diff --git a/src/Support/Parser/Visitors/ArrayVisitors/PropertyArrayVisitors/RemoveValueFromArrayPropertyPropertyArrayVisitor.php b/src/Support/Parser/Visitors/ArrayVisitors/PropertyArrayVisitors/RemoveValueFromArrayPropertyPropertyArrayVisitor.php new file mode 100644 index 0000000..63884ce --- /dev/null +++ b/src/Support/Parser/Visitors/ArrayVisitors/PropertyArrayVisitors/RemoveValueFromArrayPropertyPropertyArrayVisitor.php @@ -0,0 +1,28 @@ +props[0]; + + $propertyName = $property->name->toString(); + + if (!$this->isTargetProperty($propertyName, $property->default)) { + return; + } + + $array = $property->default; + + $array->items = $this->getTargetItems($array->items); + } +} \ No newline at end of file diff --git a/tests/InitCommandTest.php b/tests/InitCommandTest.php index 5591514..e2f15d3 100644 --- a/tests/InitCommandTest.php +++ b/tests/InitCommandTest.php @@ -242,6 +242,20 @@ public function testRunWithAdminAndDefaultReadmeCreation() ], ); + $this->mockNativeFunction('RonasIT\ProjectInitializator\Support\Parser', + [ + $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/Models/User.php', $this->getFixture('modified_user_model.php')], + ), + ] + ); + $this->mockFilePutContent( 'env.example.yml', 'env.development.yml', @@ -841,6 +855,20 @@ public function testRunWithClerkMobileApp(): void ], ); + $this->mockNativeFunction('RonasIT\ProjectInitializator\Support\Parser', + [ + $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/Models/User.php', $this->getFixture('modified_user_model.php')], + ), + ] + ); + $this->mockFilePutContent( 'env.example.yml', 'env.development.yml', diff --git a/tests/fixtures/InitCommandTest/modified_user_model.php b/tests/fixtures/InitCommandTest/modified_user_model.php new file mode 100644 index 0000000..ee5e8b4 --- /dev/null +++ b/tests/fixtures/InitCommandTest/modified_user_model.php @@ -0,0 +1,35 @@ + */ + use HasFactory, Notifiable; + /** + * The attributes that are mass assignable. + * + * @var list + */ + protected $fillable = ['name', 'email', 'clerk_id']; + /** + * The attributes that should be hidden for serialization. + * + * @var list + */ + protected $hidden = ['remember_token']; + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return ['email_verified_at' => 'datetime']; + } +} diff --git a/tests/fixtures/InitCommandTest/user_model.php b/tests/fixtures/InitCommandTest/user_model.php new file mode 100644 index 0000000..6e4b7e2 --- /dev/null +++ b/tests/fixtures/InitCommandTest/user_model.php @@ -0,0 +1,35 @@ + */ + use HasFactory, Notifiable; + /** + * The attributes that are mass assignable. + * + * @var list + */ + protected $fillable = ['name', 'email', 'password']; + /** + * The attributes that should be hidden for serialization. + * + * @var list + */ + protected $hidden = ['password', 'remember_token']; + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return ['email_verified_at' => 'datetime', 'password' => 'hashed']; + } +} \ No newline at end of file