Skip to content
14 changes: 14 additions & 0 deletions src/Commands/InitCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/ProjectInitializatorServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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']);
});
}
}
21 changes: 21 additions & 0 deletions src/Support/Parser/Factories/PhpParserFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace RonasIT\ProjectInitializator\Support\Parser\Factories;

use PhpParser\NodeTraverser;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
use RonasIT\ProjectInitializator\Support\Parser\PhpParser;

class PhpParserFactory
{
public static function create(string $filePath): PhpParser
{
return new PhpParser(
$filePath,
(new ParserFactory())->createForNewestSupportedVersion(),
new NodeTraverser(),
new Standard(),
);
}
}
118 changes: 118 additions & 0 deletions src/Support/Parser/PhpParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

namespace RonasIT\ProjectInitializator\Support\Parser;

use RonasIT\ProjectInitializator\Support\Parser\Visitors\ArrayVisitors\PropertyArrayVisitors\RemoveValueFromArrayPropertyPropertyArrayVisitor;
use RonasIT\ProjectInitializator\Support\Parser\Visitors\ArrayVisitors\MethodReturnArrayVisitors\RemoveValueFromMethodReturnArrayVisitor;
use RonasIT\ProjectInitializator\Support\Parser\Visitors\ArrayVisitors\PropertyArrayVisitors\AddValueToArrayPropertyPropertyArrayVisitor;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Parser;
use PhpParser\PrettyPrinterAbstract;
use PhpParser\NodeTraverserInterface;

class PhpParser
{
private array $ast;

public function __construct(
protected string $filePath,
protected Parser $parser,
protected NodeTraverserInterface $traverser,
protected PrettyPrinterAbstract $printer,
)
{
$this->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;
}
}
);
}
}
32 changes: 32 additions & 0 deletions src/Support/Parser/Visitors/ArrayVisitors/BaseArrayVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace RonasIT\ProjectInitializator\Support\Parser\Visitors\ArrayVisitors;

use PhpParser\NodeVisitorAbstract;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayItem;

abstract class BaseArrayVisitor extends NodeVisitorAbstract
{
public function __construct(
protected array $stmtNames,
protected string $value,
)
{
}

protected function isTargetProperty(string $propName, $default): bool
{
return in_array($propName, $this->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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace RonasIT\ProjectInitializator\Support\Parser\Visitors\ArrayVisitors\MethodReturnArrayVisitors;

use RonasIT\ProjectInitializator\Support\Parser\Visitors\ArrayVisitors\BaseArrayVisitor;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Expr\Array_;

class BaseMethodReturnArrayVisitor extends BaseArrayVisitor
{
protected function isTargetItem(?ArrayItem $item): bool
{
Comment on lines +14 to +15
Copy link
Preview

Copilot AI Aug 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method assumes the item is not null but doesn't check for null before accessing $item->key. This could cause a null pointer exception if a null ArrayItem is passed.

Suggested change
protected function isTargetItem(?ArrayItem $item): bool
{
{
if ($item === null) {
return false;
}

Copilot uses AI. Check for mistakes.

return !empty($item)
&& $item->key instanceof String_
&& $item->key->value === $this->value;
}

protected function isTargetReturnArray(Node $stmt): bool
{
return $stmt instanceof Return_
&& $stmt->expr instanceof Array_;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace RonasIT\ProjectInitializator\Support\Parser\Visitors\ArrayVisitors\MethodReturnArrayVisitors;

use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;

class RemoveValueFromMethodReturnArrayVisitor extends BaseMethodReturnArrayVisitor
{
public function enterNode(Node $node): void
{
if (!($node instanceof ClassMethod)) {
return;
}

$methodName = $node->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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace RonasIT\ProjectInitializator\Support\Parser\Visitors\ArrayVisitors\PropertyArrayVisitors;

use PhpParser\Node;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Expr\ArrayItem;

class AddValueToArrayPropertyPropertyArrayVisitor extends BasePropertyArrayVisitor
{
public function enterNode(Node $node): void
{
if (!$node instanceof Property) {
return;
}

$property = $node->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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace RonasIT\ProjectInitializator\Support\Parser\Visitors\ArrayVisitors\PropertyArrayVisitors;

use PhpParser\Node\Expr\ArrayItem;
use RonasIT\ProjectInitializator\Support\Parser\Visitors\ArrayVisitors\BaseArrayVisitor;

class BasePropertyArrayVisitor extends BaseArrayVisitor
{
protected function isTargetItem(?ArrayItem $item): bool
{
return !empty($item)
&& $item->value->value === $this->value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace RonasIT\ProjectInitializator\Support\Parser\Visitors\ArrayVisitors\PropertyArrayVisitors;

use PhpParser\Node;
use PhpParser\Node\Stmt\Property;

class RemoveValueFromArrayPropertyPropertyArrayVisitor extends BasePropertyArrayVisitor
{
public function enterNode(Node $node): void
{
if (!$node instanceof Property) {
return;
}

$property = $node->props[0];

$propertyName = $property->name->toString();

if (!$this->isTargetProperty($propertyName, $property->default)) {
return;
}

$array = $property->default;

$array->items = $this->getTargetItems($array->items);
}
}
Loading