From 6bff82739a11167cfd6156241b8bfb79f9ec6dd3 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Mon, 8 Jun 2026 10:41:10 +0500 Subject: [PATCH 01/17] feat: ability remove method refs: https://github.com/RonasIT/larabuilder/issues/70 --- docker-compose.yml | 2 + src/Builders/AppBootstrapBuilder.php | 16 +++ .../AbstractAppBootstrapVisitor.php | 2 +- .../AddMiddlewarePrependToGroup.php | 130 ++++++++++++++++++ tests/AppBootstrapBuilderTest.php | 47 +++++++ tests/Support/Classes/FakeClass.php | 8 ++ .../bootstrap_with_prepend_group.php | 18 +++ .../bootstrap_with_changed_prepend_group.php | 19 +++ .../bootstrap_with_prepend_group.php | 20 +++ 9 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php create mode 100644 tests/Support/Classes/FakeClass.php create mode 100644 tests/Support/OriginStructures/bootstrap_with_prepend_group.php create mode 100644 tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php create mode 100644 tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group.php diff --git a/docker-compose.yml b/docker-compose.yml index c49b546..3e69e26 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,3 +4,5 @@ services: working_dir: /app volumes: - ./:/app + extra_hosts: + - "host.docker.internal:host-gateway" \ No newline at end of file diff --git a/src/Builders/AppBootstrapBuilder.php b/src/Builders/AppBootstrapBuilder.php index f3a0316..059c7c5 100644 --- a/src/Builders/AppBootstrapBuilder.php +++ b/src/Builders/AppBootstrapBuilder.php @@ -3,6 +3,7 @@ namespace RonasIT\Larabuilder\Builders; use RonasIT\Larabuilder\Visitors\AppBootstrapVisitors\AddExceptionsRender; +use RonasIT\Larabuilder\Visitors\AppBootstrapVisitors\AddMiddlewarePrependToGroup; class AppBootstrapBuilder extends PHPFileBuilder { @@ -25,4 +26,19 @@ public function addExceptionsRender(string $exceptionClass, string $renderBody, return $this; } + + public function addMiddlewarePrependToGroup(string $group, string|array $middleware): self + { + $middlewares = is_string($middleware) ? [$middleware] : $middleware; + + $this->traverser->addVisitor(new AddMiddlewarePrependToGroup($group, $middlewares)); + + $imports = array_filter($middlewares, fn ($middleware) => class_exists($middleware)); + + if (!empty($imports)) { + $this->addImports($imports); + } + + return $this; + } } diff --git a/src/Visitors/AppBootstrapVisitors/AbstractAppBootstrapVisitor.php b/src/Visitors/AppBootstrapVisitors/AbstractAppBootstrapVisitor.php index 3c92660..7243ab7 100644 --- a/src/Visitors/AppBootstrapVisitors/AbstractAppBootstrapVisitor.php +++ b/src/Visitors/AppBootstrapVisitors/AbstractAppBootstrapVisitor.php @@ -14,7 +14,7 @@ abstract class AbstractAppBootstrapVisitor extends NodeVisitorAbstract { - protected const FORBIDDEN_NODES = [ + protected const array FORBIDDEN_NODES = [ Class_::class, Trait_::class, Interface_::class, diff --git a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php new file mode 100644 index 0000000..24a6312 --- /dev/null +++ b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php @@ -0,0 +1,130 @@ +args[0]->value; + + $this->prepareClosure($closure); + + $statementIndex = $this->findExistsMiddlewareGroupIndex($closure->stmts); + + if (is_null($statementIndex)) { + $closure->stmts[] = $this->buildRenderCall(); + } else { + $this->updateMiddlewareGroupStatement($closure, $statementIndex); + } + + return $node; + } + + protected function prepareClosure(Closure $closure): void + { + if (!empty($closure->stmts) && get_class(array_first($closure->stmts)) === Nop::class) { + array_shift($closure->stmts); + } + } + + protected function findExistsMiddlewareGroupIndex(array $stmts): ?int + { + return array_find_key($stmts, function ($stmt) { + return !empty($stmt->expr->name) + && $stmt->expr->name->toString() === self::methodName + && $stmt->expr->args[0]->value->value === $this->group; + }); + } + + protected function updateMiddlewareGroupStatement(Closure $closure, int $indexForReplace): void + { + $originalMiddlewareList = $closure->stmts[$indexForReplace]->expr->args[1]->value->items; + + $changedMiddlewareList = $this->mergeMiddlewares($originalMiddlewareList, $this->getMiddlewareList()); + + $closure->stmts[$indexForReplace]->expr->args[1]->value->items = $changedMiddlewareList; + } + + protected function mergeMiddlewares(array $originalMiddlewareList, array $newMiddlewareList): array + { + $filteredNewList = array_filter($newMiddlewareList, function ($newMiddleware) use ($originalMiddlewareList) { + foreach ($originalMiddlewareList as $originalMiddleware) { + if ($this->isSameMiddleware($newMiddleware, $originalMiddleware)) { + return false; + } + } + + return true; + }); + + return [...$originalMiddlewareList, ...$filteredNewList]; + } + + private function isSameMiddleware(ArrayItem $newMiddleware, ArrayItem $originalMiddleware): bool + { + $original = $originalMiddleware->value->class->name + ?? $originalMiddleware->value->value; + + $new = $newMiddleware->value->class->name + ?? $newMiddleware->value->value; + + return $original === $new; + } + + protected function buildRenderCall(): Expression + { + $middlewareList = $this->getMiddlewareList(); + + $methodCall = new MethodCall(new Variable('middleware'), new Identifier(self::methodName), [ + new Arg(new String_($this->group)), + new Arg(new Array_($middlewareList)), + ]); + + return new Expression($methodCall); + } + + protected function getMiddlewareList(): array + { + return array_map(fn($middleware) => $this->makeArrayItem($middleware), $this->middlewares); + } + + protected function makeArrayItem(string $middleware): ArrayItem + { + if (class_exists($middleware)) { + $basename = class_basename($middleware); + + $value = new ClassConstFetch(new Name($basename), 'class'); + } else { + $value = new String_($middleware); + } + + return new ArrayItem($value); + } +} diff --git a/tests/AppBootstrapBuilderTest.php b/tests/AppBootstrapBuilderTest.php index 6945e47..f94736c 100644 --- a/tests/AppBootstrapBuilderTest.php +++ b/tests/AppBootstrapBuilderTest.php @@ -7,6 +7,8 @@ use RonasIT\Larabuilder\Builders\AppBootstrapBuilder; use RonasIT\Larabuilder\Exceptions\InvalidBootstrapAppFileException; use RonasIT\Larabuilder\Exceptions\InvalidPHPCodeException; +use RonasIT\Larabuilder\Tests\Support\Classes\FakeClass; +use RonasIT\Larabuilder\Tests\Support\SomeClass; use RonasIT\Larabuilder\Tests\Support\Traits\PHPFileBuilderTestMockTrait; use Symfony\Component\HttpKernel\Exception\HttpException; @@ -137,4 +139,49 @@ public function testInvalidBootstrapAppFileException(string $fixture, string $ty ) ->save(); } + + public function testAddMiddlewarePrependToGroup() + { + $file = $this->generateOriginalStructurePath('bootstrap_empty.php'); + + $this->mockNativeFunction( + 'RonasIT\Larabuilder\Builders', + $this->callFilePutContent($file, 'bootstrap_with_prepend_group.php'), + ); + + new AppBootstrapBuilder($file) + ->AddMiddlewarePrependToGroup( + group: 'api', + middleware: FakeClass::class, + ) + ->AddMiddlewarePrependToGroup( + group: 'api', + middleware: 'throttle:60,10', + ) + ->AddMiddlewarePrependToGroup( + group: 'web', + middleware: [ + 'throttle:10,10', + FakeClass::class, + ], + ) + ->save(); + } + + public function testAddMiddlewarePrependToGroupExistsMiddlewares() + { + $file = $this->generateOriginalStructurePath('bootstrap_with_prepend_group.php'); + + $this->mockNativeFunction( + 'RonasIT\Larabuilder\Builders', + $this->callFilePutContent($file, 'bootstrap_with_changed_prepend_group.php'), + ); + + new AppBootstrapBuilder($file) + ->AddMiddlewarePrependToGroup('api', [ + FakeClass::class, + 'throttle:60,10', + ]) + ->save(); + } } diff --git a/tests/Support/Classes/FakeClass.php b/tests/Support/Classes/FakeClass.php new file mode 100644 index 0000000..bab84c7 --- /dev/null +++ b/tests/Support/Classes/FakeClass.php @@ -0,0 +1,8 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware): void { + $middleware->prependToGroup('api', ['throttle:60,10', Illuminate\Auth\Middleware\Authenticate::class]); + }) + ->withExceptions(function (Exceptions $exceptions): void { + // + })->create(); diff --git a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php new file mode 100644 index 0000000..ce9dcdc --- /dev/null +++ b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php @@ -0,0 +1,19 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware): void { + $middleware->prependToGroup('api', ['throttle:60,10', Illuminate\Auth\Middleware\Authenticate::class, FakeClass::class]); + }) + ->withExceptions(function (Exceptions $exceptions): void { + // + })->create(); diff --git a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group.php b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group.php new file mode 100644 index 0000000..e547e5b --- /dev/null +++ b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group.php @@ -0,0 +1,20 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware): void { + $middleware->prependToGroup('api', [FakeClass::class, 'throttle:60,10']); + $middleware->prependToGroup('web', ['throttle:10,10', FakeClass::class]); + }) + ->withExceptions(function (Exceptions $exceptions): void { + // + })->create(); From 2b6ac0667043879c16464fc2916699b17d844939 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Mon, 8 Jun 2026 10:59:36 +0500 Subject: [PATCH 02/17] feat: ability remove method refs: https://github.com/RonasIT/larabuilder/issues/70 --- .../AddMiddlewarePrependToGroup.php | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php index 24a6312..9832a03 100644 --- a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php +++ b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php @@ -17,15 +17,13 @@ class AddMiddlewarePrependToGroup extends AbstractAppBootstrapVisitor { - private const string methodName = 'prependToGroup'; - public function __construct( protected string $group, protected array $middlewares, ) { parent::__construct( parentMethod: 'withMiddleware', - targetMethod: 'render', + targetMethod: 'prependToGroup', ); } @@ -34,12 +32,12 @@ protected function insertNode(MethodCall $node): MethodCall /** @var Closure $closure */ $closure = $node->args[0]->value; - $this->prepareClosure($closure); + $this->removeNopPlaceholder($closure); - $statementIndex = $this->findExistsMiddlewareGroupIndex($closure->stmts); + $statementIndex = $this->findMiddlewareGroupIndex($closure->stmts); if (is_null($statementIndex)) { - $closure->stmts[] = $this->buildRenderCall(); + $closure->stmts[] = $this->buildPrependToGroupCall(); } else { $this->updateMiddlewareGroupStatement($closure, $statementIndex); } @@ -47,29 +45,29 @@ protected function insertNode(MethodCall $node): MethodCall return $node; } - protected function prepareClosure(Closure $closure): void + protected function removeNopPlaceholder(Closure $closure): void { - if (!empty($closure->stmts) && get_class(array_first($closure->stmts)) === Nop::class) { + if (!empty($closure->stmts) && array_first($closure->stmts) instanceof Nop) { array_shift($closure->stmts); } } - protected function findExistsMiddlewareGroupIndex(array $stmts): ?int + protected function findMiddlewareGroupIndex(array $stmts): ?int { - return array_find_key($stmts, function ($stmt) { + return array_find_key($stmts, function (Expression $stmt) { return !empty($stmt->expr->name) - && $stmt->expr->name->toString() === self::methodName + && $stmt->expr->name->toString() === $this->targetMethod && $stmt->expr->args[0]->value->value === $this->group; }); } - protected function updateMiddlewareGroupStatement(Closure $closure, int $indexForReplace): void + protected function updateMiddlewareGroupStatement(Closure $closure, int $groupIndex): void { - $originalMiddlewareList = $closure->stmts[$indexForReplace]->expr->args[1]->value->items; + $originalMiddlewareList = $closure->stmts[$groupIndex]->expr->args[1]->value->items; - $changedMiddlewareList = $this->mergeMiddlewares($originalMiddlewareList, $this->getMiddlewareList()); + $merged = $this->mergeMiddlewares($originalMiddlewareList, $this->getMiddlewareList()); - $closure->stmts[$indexForReplace]->expr->args[1]->value->items = $changedMiddlewareList; + $closure->stmts[$groupIndex]->expr->args[1]->value->items = $merged; } protected function mergeMiddlewares(array $originalMiddlewareList, array $newMiddlewareList): array @@ -89,20 +87,22 @@ protected function mergeMiddlewares(array $originalMiddlewareList, array $newMid private function isSameMiddleware(ArrayItem $newMiddleware, ArrayItem $originalMiddleware): bool { - $original = $originalMiddleware->value->class->name - ?? $originalMiddleware->value->value; + $original = ($originalMiddleware->value instanceof ClassConstFetch) + ? $originalMiddleware->value->class->name + : $originalMiddleware->value->value; - $new = $newMiddleware->value->class->name - ?? $newMiddleware->value->value; + $new = ($newMiddleware->value instanceof ClassConstFetch) + ? $newMiddleware->value->class->name + : $newMiddleware->value->value; return $original === $new; } - protected function buildRenderCall(): Expression + protected function buildPrependToGroupCall(): Expression { $middlewareList = $this->getMiddlewareList(); - $methodCall = new MethodCall(new Variable('middleware'), new Identifier(self::methodName), [ + $methodCall = new MethodCall(new Variable('middleware'), new Identifier($this->targetMethod), [ new Arg(new String_($this->group)), new Arg(new Array_($middlewareList)), ]); From 682a3ddf1b07b3f43bbca6dcd12902506c20fa40 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Mon, 8 Jun 2026 11:17:40 +0500 Subject: [PATCH 03/17] feat: ability remove method refs: https://github.com/RonasIT/larabuilder/issues/70 --- .../AppBootstrapVisitors/AddMiddlewarePrependToGroup.php | 2 +- tests/AppBootstrapBuilderTest.php | 1 - tests/Support/Classes/FakeClass.php | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php index 9832a03..cc8fa55 100644 --- a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php +++ b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php @@ -112,7 +112,7 @@ protected function buildPrependToGroupCall(): Expression protected function getMiddlewareList(): array { - return array_map(fn($middleware) => $this->makeArrayItem($middleware), $this->middlewares); + return array_map(fn ($middleware) => $this->makeArrayItem($middleware), $this->middlewares); } protected function makeArrayItem(string $middleware): ArrayItem diff --git a/tests/AppBootstrapBuilderTest.php b/tests/AppBootstrapBuilderTest.php index f94736c..bc12a4d 100644 --- a/tests/AppBootstrapBuilderTest.php +++ b/tests/AppBootstrapBuilderTest.php @@ -8,7 +8,6 @@ use RonasIT\Larabuilder\Exceptions\InvalidBootstrapAppFileException; use RonasIT\Larabuilder\Exceptions\InvalidPHPCodeException; use RonasIT\Larabuilder\Tests\Support\Classes\FakeClass; -use RonasIT\Larabuilder\Tests\Support\SomeClass; use RonasIT\Larabuilder\Tests\Support\Traits\PHPFileBuilderTestMockTrait; use Symfony\Component\HttpKernel\Exception\HttpException; diff --git a/tests/Support/Classes/FakeClass.php b/tests/Support/Classes/FakeClass.php index bab84c7..e11cb28 100644 --- a/tests/Support/Classes/FakeClass.php +++ b/tests/Support/Classes/FakeClass.php @@ -4,5 +4,4 @@ class FakeClass { - -} \ No newline at end of file +} From 0d4eefe2587a9098a469b085d9e1f9fe8648076f Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Mon, 8 Jun 2026 11:25:59 +0500 Subject: [PATCH 04/17] feat: ability remove method refs: https://github.com/RonasIT/larabuilder/issues/70 --- tests/AppBootstrapBuilderTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/AppBootstrapBuilderTest.php b/tests/AppBootstrapBuilderTest.php index bc12a4d..5ce38c3 100644 --- a/tests/AppBootstrapBuilderTest.php +++ b/tests/AppBootstrapBuilderTest.php @@ -149,15 +149,15 @@ public function testAddMiddlewarePrependToGroup() ); new AppBootstrapBuilder($file) - ->AddMiddlewarePrependToGroup( + ->addMiddlewarePrependToGroup( group: 'api', middleware: FakeClass::class, ) - ->AddMiddlewarePrependToGroup( + ->addMiddlewarePrependToGroup( group: 'api', middleware: 'throttle:60,10', ) - ->AddMiddlewarePrependToGroup( + ->addMiddlewarePrependToGroup( group: 'web', middleware: [ 'throttle:10,10', @@ -177,7 +177,7 @@ public function testAddMiddlewarePrependToGroupExistsMiddlewares() ); new AppBootstrapBuilder($file) - ->AddMiddlewarePrependToGroup('api', [ + ->addMiddlewarePrependToGroup('api', [ FakeClass::class, 'throttle:60,10', ]) From e49fac41fb8219c79225c35fc4ee1d3950549186 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Mon, 8 Jun 2026 11:27:48 +0500 Subject: [PATCH 05/17] feat: ability remove method refs: https://github.com/RonasIT/larabuilder/issues/70 --- tests/AppBootstrapBuilderTest.php | 1 + .../bootstrap_with_changed_prepend_group.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/AppBootstrapBuilderTest.php b/tests/AppBootstrapBuilderTest.php index 5ce38c3..585252e 100644 --- a/tests/AppBootstrapBuilderTest.php +++ b/tests/AppBootstrapBuilderTest.php @@ -180,6 +180,7 @@ public function testAddMiddlewarePrependToGroupExistsMiddlewares() ->addMiddlewarePrependToGroup('api', [ FakeClass::class, 'throttle:60,10', + 'some_middleware', ]) ->save(); } diff --git a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php index ce9dcdc..c858898 100644 --- a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php +++ b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php @@ -12,7 +12,7 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - $middleware->prependToGroup('api', ['throttle:60,10', Illuminate\Auth\Middleware\Authenticate::class, FakeClass::class]); + $middleware->prependToGroup('api', ['throttle:60,10', Illuminate\Auth\Middleware\Authenticate::class, FakeClass::class, 'some_middleware']); }) ->withExceptions(function (Exceptions $exceptions): void { // From 6f0e82bb7b928905eb1124ee31689a1a0aa0e35e Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Mon, 8 Jun 2026 12:14:28 +0500 Subject: [PATCH 06/17] feat: ability remove method refs: https://github.com/RonasIT/larabuilder/issues/70 --- src/Builders/AppBootstrapBuilder.php | 3 ++- .../AddMiddlewarePrependToGroup.php | 23 ++++++++++++++----- tests/AppBootstrapBuilderTest.php | 16 +++++++++++++ ...bootstrap_with_prepend_group_as_string.php | 18 +++++++++++++++ ...bootstrap_with_prepend_group_as_string.php | 18 +++++++++++++++ 5 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 tests/Support/OriginStructures/bootstrap_with_prepend_group_as_string.php create mode 100644 tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group_as_string.php diff --git a/src/Builders/AppBootstrapBuilder.php b/src/Builders/AppBootstrapBuilder.php index 059c7c5..3a08327 100644 --- a/src/Builders/AppBootstrapBuilder.php +++ b/src/Builders/AppBootstrapBuilder.php @@ -2,6 +2,7 @@ namespace RonasIT\Larabuilder\Builders; +use Illuminate\Support\Arr; use RonasIT\Larabuilder\Visitors\AppBootstrapVisitors\AddExceptionsRender; use RonasIT\Larabuilder\Visitors\AppBootstrapVisitors\AddMiddlewarePrependToGroup; @@ -29,7 +30,7 @@ public function addExceptionsRender(string $exceptionClass, string $renderBody, public function addMiddlewarePrependToGroup(string $group, string|array $middleware): self { - $middlewares = is_string($middleware) ? [$middleware] : $middleware; + $middlewares = Arr::wrap($middleware); $this->traverser->addVisitor(new AddMiddlewarePrependToGroup($group, $middlewares)); diff --git a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php index cc8fa55..b7c0392 100644 --- a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php +++ b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php @@ -2,6 +2,7 @@ namespace RonasIT\Larabuilder\Visitors\AppBootstrapVisitors; +use Illuminate\Support\Arr; use PhpParser\Node\Arg; use PhpParser\Node\ArrayItem; use PhpParser\Node\Expr\Array_; @@ -39,7 +40,7 @@ protected function insertNode(MethodCall $node): MethodCall if (is_null($statementIndex)) { $closure->stmts[] = $this->buildPrependToGroupCall(); } else { - $this->updateMiddlewareGroupStatement($closure, $statementIndex); + $this->updateMiddlewareGroup($closure, $statementIndex); } return $node; @@ -61,13 +62,18 @@ protected function findMiddlewareGroupIndex(array $stmts): ?int }); } - protected function updateMiddlewareGroupStatement(Closure $closure, int $groupIndex): void + protected function updateMiddlewareGroup(Closure $closure, int $groupIndex): void { - $originalMiddlewareList = $closure->stmts[$groupIndex]->expr->args[1]->value->items; + $originalMiddlewares = $closure->stmts[$groupIndex]->expr->args[1]->value->value + ?? $closure->stmts[$groupIndex]->expr->args[1]->value->items; - $merged = $this->mergeMiddlewares($originalMiddlewareList, $this->getMiddlewareList()); + $originalMiddlewares = is_string($originalMiddlewares) + ? [$this->makeArrayItem($originalMiddlewares)] + : $originalMiddlewares; - $closure->stmts[$groupIndex]->expr->args[1]->value->items = $merged; + $mergedMiddlewares = $this->mergeMiddlewares($originalMiddlewares, $this->getMiddlewareList()); + + $closure->stmts[$groupIndex]->expr->args[1] = $this->buildMiddlewareArg($mergedMiddlewares); } protected function mergeMiddlewares(array $originalMiddlewareList, array $newMiddlewareList): array @@ -104,12 +110,17 @@ protected function buildPrependToGroupCall(): Expression $methodCall = new MethodCall(new Variable('middleware'), new Identifier($this->targetMethod), [ new Arg(new String_($this->group)), - new Arg(new Array_($middlewareList)), + $this->buildMiddlewareArg($middlewareList), ]); return new Expression($methodCall); } + protected function buildMiddlewareArg(array $middlewares) + { + return new Arg(new Array_($middlewares)); + } + protected function getMiddlewareList(): array { return array_map(fn ($middleware) => $this->makeArrayItem($middleware), $this->middlewares); diff --git a/tests/AppBootstrapBuilderTest.php b/tests/AppBootstrapBuilderTest.php index 585252e..11853a8 100644 --- a/tests/AppBootstrapBuilderTest.php +++ b/tests/AppBootstrapBuilderTest.php @@ -184,4 +184,20 @@ public function testAddMiddlewarePrependToGroupExistsMiddlewares() ]) ->save(); } + + public function testAddMiddlewarePrependToGroupMiddlewareExistsAsString() + { + $file = $this->generateOriginalStructurePath('bootstrap_with_prepend_group_as_string.php'); + + $this->mockNativeFunction( + 'RonasIT\Larabuilder\Builders', + $this->callFilePutContent($file, 'bootstrap_with_prepend_group_as_string.php'), + ); + + new AppBootstrapBuilder($file) + ->addMiddlewarePrependToGroup('api', [ + 'some_middleware', + ]) + ->save(); + } } diff --git a/tests/Support/OriginStructures/bootstrap_with_prepend_group_as_string.php b/tests/Support/OriginStructures/bootstrap_with_prepend_group_as_string.php new file mode 100644 index 0000000..b09e30a --- /dev/null +++ b/tests/Support/OriginStructures/bootstrap_with_prepend_group_as_string.php @@ -0,0 +1,18 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware): void { + $middleware->prependToGroup('api', 'throttle:60,10'); + }) + ->withExceptions(function (Exceptions $exceptions): void { + // + })->create(); diff --git a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group_as_string.php b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group_as_string.php new file mode 100644 index 0000000..bd7306b --- /dev/null +++ b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group_as_string.php @@ -0,0 +1,18 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware): void { + $middleware->prependToGroup('api', ['throttle:60,10', 'some_middleware']); + }) + ->withExceptions(function (Exceptions $exceptions): void { + // + })->create(); From 8b80c3c90eb719b3f90a4e504ea3367abf2bcf03 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Mon, 8 Jun 2026 12:18:21 +0500 Subject: [PATCH 07/17] feat: ability remove method refs: https://github.com/RonasIT/larabuilder/issues/70 --- .../AppBootstrapVisitors/AddMiddlewarePrependToGroup.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php index b7c0392..50175d5 100644 --- a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php +++ b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php @@ -2,7 +2,6 @@ namespace RonasIT\Larabuilder\Visitors\AppBootstrapVisitors; -use Illuminate\Support\Arr; use PhpParser\Node\Arg; use PhpParser\Node\ArrayItem; use PhpParser\Node\Expr\Array_; From 230e3613983373fdac32fbf256e0aef5c8ab4935 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Mon, 15 Jun 2026 15:17:17 +0500 Subject: [PATCH 08/17] feat: ability remove method refs: https://github.com/RonasIT/larabuilder/issues/70 --- src/Builders/AppBootstrapBuilder.php | 5 +- .../AddMiddlewarePrependToGroup.php | 50 ++++++++++++++----- tests/AppBootstrapBuilderTest.php | 28 ++++++++--- ...with_prepend_group_as_string_set_class.php | 18 +++++++ .../bootstrap_with_changed_prepend_group.php | 3 +- .../bootstrap_with_prepend_group.php | 2 +- ...with_prepend_group_as_string_set_class.php | 18 +++++++ 7 files changed, 102 insertions(+), 22 deletions(-) create mode 100644 tests/Support/OriginStructures/bootstrap_with_prepend_group_as_string_set_class.php create mode 100644 tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group_as_string_set_class.php diff --git a/src/Builders/AppBootstrapBuilder.php b/src/Builders/AppBootstrapBuilder.php index 3a08327..72b57ab 100644 --- a/src/Builders/AppBootstrapBuilder.php +++ b/src/Builders/AppBootstrapBuilder.php @@ -3,6 +3,7 @@ namespace RonasIT\Larabuilder\Builders; use Illuminate\Support\Arr; +use RonasIT\Larabuilder\Enums\InsertPositionEnum; use RonasIT\Larabuilder\Visitors\AppBootstrapVisitors\AddExceptionsRender; use RonasIT\Larabuilder\Visitors\AppBootstrapVisitors\AddMiddlewarePrependToGroup; @@ -28,11 +29,11 @@ public function addExceptionsRender(string $exceptionClass, string $renderBody, return $this; } - public function addMiddlewarePrependToGroup(string $group, string|array $middleware): self + public function addMiddlewarePrependToGroup(string $group, string|array $middleware, InsertPositionEnum $position = InsertPositionEnum::End): self { $middlewares = Arr::wrap($middleware); - $this->traverser->addVisitor(new AddMiddlewarePrependToGroup($group, $middlewares)); + $this->traverser->addVisitor(new AddMiddlewarePrependToGroup($group, $middlewares, $position)); $imports = array_filter($middlewares, fn ($middleware) => class_exists($middleware)); diff --git a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php index 50175d5..2d279ad 100644 --- a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php +++ b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php @@ -14,12 +14,14 @@ use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Nop; +use RonasIT\Larabuilder\Enums\InsertPositionEnum; class AddMiddlewarePrependToGroup extends AbstractAppBootstrapVisitor { public function __construct( protected string $group, protected array $middlewares, + protected InsertPositionEnum $position, ) { parent::__construct( parentMethod: 'withMiddleware', @@ -47,7 +49,7 @@ protected function insertNode(MethodCall $node): MethodCall protected function removeNopPlaceholder(Closure $closure): void { - if (!empty($closure->stmts) && array_first($closure->stmts) instanceof Nop) { + if (!empty($closure->stmts) && ($closure->stmts[0] ?? null) instanceof Nop) { array_shift($closure->stmts); } } @@ -64,40 +66,64 @@ protected function findMiddlewareGroupIndex(array $stmts): ?int protected function updateMiddlewareGroup(Closure $closure, int $groupIndex): void { $originalMiddlewares = $closure->stmts[$groupIndex]->expr->args[1]->value->value + ?? $closure->stmts[$groupIndex]->expr->args[1]->value->class->name ?? $closure->stmts[$groupIndex]->expr->args[1]->value->items; $originalMiddlewares = is_string($originalMiddlewares) - ? [$this->makeArrayItem($originalMiddlewares)] + ? [new ArrayItem($closure->stmts[$groupIndex]->expr->args[1]->value)] : $originalMiddlewares; $mergedMiddlewares = $this->mergeMiddlewares($originalMiddlewares, $this->getMiddlewareList()); + $this->collection = $mergedMiddlewares; + $closure->stmts[$groupIndex]->expr->args[1] = $this->buildMiddlewareArg($mergedMiddlewares); } protected function mergeMiddlewares(array $originalMiddlewareList, array $newMiddlewareList): array { - $filteredNewList = array_filter($newMiddlewareList, function ($newMiddleware) use ($originalMiddlewareList) { - foreach ($originalMiddlewareList as $originalMiddleware) { - if ($this->isSameMiddleware($newMiddleware, $originalMiddleware)) { - return false; + $filteredNewList = []; + + foreach ($newMiddlewareList as $newMiddleware) { + $sameMiddlewareKey = array_find_key( + $originalMiddlewareList, + fn ($originalMiddleware) => $this->isSameMiddleware($newMiddleware, $originalMiddleware), + ); + + if (!is_null($sameMiddlewareKey)) { + if ($originalMiddlewareList[$sameMiddlewareKey]->value instanceof ClassConstFetch) { + $this->setClassBaseName($originalMiddlewareList[$sameMiddlewareKey]->value); } + + continue; } - return true; - }); + if ($newMiddleware->value instanceof ClassConstFetch) { + $this->setClassBaseName($newMiddleware->value); + } - return [...$originalMiddlewareList, ...$filteredNewList]; + $filteredNewList[] = $newMiddleware; + } + + return match ($this->position) { + InsertPositionEnum::Start => [...$filteredNewList, ...$originalMiddlewareList], + InsertPositionEnum::End => [...$originalMiddlewareList, ...$filteredNewList], + }; + } + + protected function setClassBaseName(ClassConstFetch $class): void + { + $class->class->name = class_basename($class->class->name); } private function isSameMiddleware(ArrayItem $newMiddleware, ArrayItem $originalMiddleware): bool { $original = ($originalMiddleware->value instanceof ClassConstFetch) - ? $originalMiddleware->value->class->name + ? class_basename($originalMiddleware->value->class->name) : $originalMiddleware->value->value; $new = ($newMiddleware->value instanceof ClassConstFetch) - ? $newMiddleware->value->class->name + ? class_basename($newMiddleware->value->class->name) : $newMiddleware->value->value; return $original === $new; @@ -115,7 +141,7 @@ protected function buildPrependToGroupCall(): Expression return new Expression($methodCall); } - protected function buildMiddlewareArg(array $middlewares) + protected function buildMiddlewareArg(array $middlewares): Arg { return new Arg(new Array_($middlewares)); } diff --git a/tests/AppBootstrapBuilderTest.php b/tests/AppBootstrapBuilderTest.php index 11853a8..a7c5c07 100644 --- a/tests/AppBootstrapBuilderTest.php +++ b/tests/AppBootstrapBuilderTest.php @@ -2,9 +2,11 @@ namespace RonasIT\Larabuilder\Tests; +use Illuminate\Auth\Middleware\Authenticate; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\ExpectationFailedException; use RonasIT\Larabuilder\Builders\AppBootstrapBuilder; +use RonasIT\Larabuilder\Enums\InsertPositionEnum; use RonasIT\Larabuilder\Exceptions\InvalidBootstrapAppFileException; use RonasIT\Larabuilder\Exceptions\InvalidPHPCodeException; use RonasIT\Larabuilder\Tests\Support\Classes\FakeClass; @@ -156,6 +158,7 @@ public function testAddMiddlewarePrependToGroup() ->addMiddlewarePrependToGroup( group: 'api', middleware: 'throttle:60,10', + position: InsertPositionEnum::Start, ) ->addMiddlewarePrependToGroup( group: 'web', @@ -181,18 +184,31 @@ public function testAddMiddlewarePrependToGroupExistsMiddlewares() FakeClass::class, 'throttle:60,10', 'some_middleware', + Authenticate::class, ]) ->save(); } - public function testAddMiddlewarePrependToGroupMiddlewareExistsAsString() + public static function provideMiddlewareAsString(): array { - $file = $this->generateOriginalStructurePath('bootstrap_with_prepend_group_as_string.php'); + return [ + [ + 'original' => 'bootstrap_with_prepend_group_as_string.php', + 'result' => 'bootstrap_with_prepend_group_as_string.php', + ], + [ + 'original' => 'bootstrap_with_prepend_group_as_string_set_class.php', + 'result' => 'bootstrap_with_prepend_group_as_string_set_class.php', + ], + ]; + } - $this->mockNativeFunction( - 'RonasIT\Larabuilder\Builders', - $this->callFilePutContent($file, 'bootstrap_with_prepend_group_as_string.php'), - ); + #[DataProvider('provideMiddlewareAsString')] + public function testAddMiddlewarePrependToGroupMiddlewareAsString(string $original, string $result): void + { + $file = $this->generateOriginalStructurePath($original); + + $this->mockNativeFunction('RonasIT\Larabuilder\Builders', $this->callFilePutContent($file, $result)); new AppBootstrapBuilder($file) ->addMiddlewarePrependToGroup('api', [ diff --git a/tests/Support/OriginStructures/bootstrap_with_prepend_group_as_string_set_class.php b/tests/Support/OriginStructures/bootstrap_with_prepend_group_as_string_set_class.php new file mode 100644 index 0000000..15bb292 --- /dev/null +++ b/tests/Support/OriginStructures/bootstrap_with_prepend_group_as_string_set_class.php @@ -0,0 +1,18 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware): void { + $middleware->prependToGroup('api', Illuminate\Auth\Middleware\Authenticate::class); + }) + ->withExceptions(function (Exceptions $exceptions): void { + // + })->create(); diff --git a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php index c858898..adf77df 100644 --- a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php +++ b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php @@ -4,6 +4,7 @@ use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; use RonasIT\Larabuilder\Tests\Support\Classes\FakeClass; +use Illuminate\Auth\Middleware\Authenticate; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( @@ -12,7 +13,7 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - $middleware->prependToGroup('api', ['throttle:60,10', Illuminate\Auth\Middleware\Authenticate::class, FakeClass::class, 'some_middleware']); + $middleware->prependToGroup('api', ['throttle:60,10', Authenticate::class, FakeClass::class, 'some_middleware']); }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group.php b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group.php index e547e5b..9dd5260 100644 --- a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group.php +++ b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group.php @@ -12,7 +12,7 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - $middleware->prependToGroup('api', [FakeClass::class, 'throttle:60,10']); + $middleware->prependToGroup('api', ['throttle:60,10', FakeClass::class]); $middleware->prependToGroup('web', ['throttle:10,10', FakeClass::class]); }) ->withExceptions(function (Exceptions $exceptions): void { diff --git a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group_as_string_set_class.php b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group_as_string_set_class.php new file mode 100644 index 0000000..49de65c --- /dev/null +++ b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group_as_string_set_class.php @@ -0,0 +1,18 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware): void { + $middleware->prependToGroup('api', [Illuminate\Auth\Middleware\Authenticate::class, 'some_middleware']); + }) + ->withExceptions(function (Exceptions $exceptions): void { + // + })->create(); From 77d419f49f451b3f2d1d5d0dbc24fd19d91ad7e8 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Mon, 15 Jun 2026 15:17:51 +0500 Subject: [PATCH 09/17] feat: ability remove method refs: https://github.com/RonasIT/larabuilder/issues/70 --- .../AppBootstrapVisitors/AddMiddlewarePrependToGroup.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php index 2d279ad..62447ca 100644 --- a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php +++ b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php @@ -75,8 +75,6 @@ protected function updateMiddlewareGroup(Closure $closure, int $groupIndex): voi $mergedMiddlewares = $this->mergeMiddlewares($originalMiddlewares, $this->getMiddlewareList()); - $this->collection = $mergedMiddlewares; - $closure->stmts[$groupIndex]->expr->args[1] = $this->buildMiddlewareArg($mergedMiddlewares); } From c084939a1d0b13dd579b043a48c16ab3125e5501 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Mon, 15 Jun 2026 15:21:00 +0500 Subject: [PATCH 10/17] feat: ability remove method refs: https://github.com/RonasIT/larabuilder/issues/70 --- .../AddMiddlewarePrependToGroup.php | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php index 62447ca..fa4903a 100644 --- a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php +++ b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php @@ -89,18 +89,12 @@ protected function mergeMiddlewares(array $originalMiddlewareList, array $newMid ); if (!is_null($sameMiddlewareKey)) { - if ($originalMiddlewareList[$sameMiddlewareKey]->value instanceof ClassConstFetch) { - $this->setClassBaseName($originalMiddlewareList[$sameMiddlewareKey]->value); - } + $this->normalizeMiddleware($originalMiddlewareList[$sameMiddlewareKey]); + } else { + $this->normalizeMiddleware($newMiddleware); - continue; + $filteredNewList[] = $newMiddleware; } - - if ($newMiddleware->value instanceof ClassConstFetch) { - $this->setClassBaseName($newMiddleware->value); - } - - $filteredNewList[] = $newMiddleware; } return match ($this->position) { @@ -109,11 +103,6 @@ protected function mergeMiddlewares(array $originalMiddlewareList, array $newMid }; } - protected function setClassBaseName(ClassConstFetch $class): void - { - $class->class->name = class_basename($class->class->name); - } - private function isSameMiddleware(ArrayItem $newMiddleware, ArrayItem $originalMiddleware): bool { $original = ($originalMiddleware->value instanceof ClassConstFetch) @@ -127,6 +116,18 @@ private function isSameMiddleware(ArrayItem $newMiddleware, ArrayItem $originalM return $original === $new; } + protected function normalizeMiddleware(ArrayItem $middleware): void + { + if ($middleware->value instanceof ClassConstFetch) { + $this->setClassBaseName($middleware->value); + } + } + + protected function setClassBaseName(ClassConstFetch $class): void + { + $class->class->name = class_basename($class->class->name); + } + protected function buildPrependToGroupCall(): Expression { $middlewareList = $this->getMiddlewareList(); From 953dc6297ed64321ad9718fe97b4fe57afddfc17 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Mon, 15 Jun 2026 16:22:10 +0500 Subject: [PATCH 11/17] feat: add middlewares into prependToGroup refs: https://github.com/RonasIT/larabuilder/issues/73 --- README.md | 25 +++++++++++++++++ .../AddMiddlewarePrependToGroup.php | 27 +++++++++---------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 952a281..1556f12 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,31 @@ render for the passed exception class. **Note** Need to provide the full exception class name (FQCN) to the method, it automatically imports it. +#### addMiddlewarePrependToGroup + +Adds middleware to a named group via `prependToGroup` inside the `withMiddleware` closure. +Accepts a single class name or string, or an array of them. Skips values that already exist in the target group. +Does not affect other groups. + +```php +new AppBootstrapBuilder(bootstrap_path('app.php')) + ->addMiddlewarePrependToGroup('api', [ + MyMiddleware::class, + 'throttle:60,1', + ]) + ->save(); +``` + +To insert at the beginning of the group instead of the end, pass `InsertPositionEnum::Start`: + +```php +new AppBootstrapBuilder(bootstrap_path('app.php')) + ->addMiddlewarePrependToGroup('api', MyMiddleware::class, InsertPositionEnum::Start) + ->save(); +``` + +**Note:** Provide the full class name (FQCN) for class-based middleware — the method imports it automatically. + ## Contributing Thank you for considering contributing to Laravel Builder package! The contribution guide diff --git a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php index fa4903a..8bb9442 100644 --- a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php +++ b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php @@ -73,27 +73,25 @@ protected function updateMiddlewareGroup(Closure $closure, int $groupIndex): voi ? [new ArrayItem($closure->stmts[$groupIndex]->expr->args[1]->value)] : $originalMiddlewares; - $mergedMiddlewares = $this->mergeMiddlewares($originalMiddlewares, $this->getMiddlewareList()); + $mergedMiddlewares = $this->mergeMiddlewares($originalMiddlewares); $closure->stmts[$groupIndex]->expr->args[1] = $this->buildMiddlewareArg($mergedMiddlewares); } - protected function mergeMiddlewares(array $originalMiddlewareList, array $newMiddlewareList): array + protected function mergeMiddlewares(array $originalMiddlewareList): array { $filteredNewList = []; - foreach ($newMiddlewareList as $newMiddleware) { + foreach ($this->middlewares as $middleware) { $sameMiddlewareKey = array_find_key( $originalMiddlewareList, - fn ($originalMiddleware) => $this->isSameMiddleware($newMiddleware, $originalMiddleware), + fn ($originalMiddleware) => $this->isSameMiddleware($middleware, $originalMiddleware), ); if (!is_null($sameMiddlewareKey)) { $this->normalizeMiddleware($originalMiddlewareList[$sameMiddlewareKey]); } else { - $this->normalizeMiddleware($newMiddleware); - - $filteredNewList[] = $newMiddleware; + $filteredNewList[] = $this->makeArrayItem($middleware); } } @@ -103,17 +101,16 @@ protected function mergeMiddlewares(array $originalMiddlewareList, array $newMid }; } - private function isSameMiddleware(ArrayItem $newMiddleware, ArrayItem $originalMiddleware): bool + private function isSameMiddleware(string $newMiddleware, ArrayItem $originalMiddleware): bool { - $original = ($originalMiddleware->value instanceof ClassConstFetch) - ? class_basename($originalMiddleware->value->class->name) - : $originalMiddleware->value->value; + if ($originalMiddleware->value instanceof ClassConstFetch) { + $originalName = $originalMiddleware->value->class->toString(); - $new = ($newMiddleware->value instanceof ClassConstFetch) - ? class_basename($newMiddleware->value->class->name) - : $newMiddleware->value->value; + return $originalName === $newMiddleware + || $originalName === class_basename($newMiddleware); + } - return $original === $new; + return $originalMiddleware->value->value === $newMiddleware; } protected function normalizeMiddleware(ArrayItem $middleware): void From 54f1c99cdf02ee83c5411ad78c105a61d18af4da Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Fri, 19 Jun 2026 16:12:59 +0500 Subject: [PATCH 12/17] feat: add middlewares into prependToGroup refs: https://github.com/RonasIT/larabuilder/issues/74 --- .../AddMiddlewarePrependToGroup.php | 32 ++++++++----------- tests/AppBootstrapBuilderTest.php | 2 -- ...with_prepend_group_as_string_set_class.php | 2 +- .../bootstrap_with_changed_prepend_group.php | 3 +- ...with_prepend_group_as_string_set_class.php | 2 +- 5 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php index 8bb9442..bc5436d 100644 --- a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php +++ b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php @@ -29,6 +29,8 @@ public function __construct( ); } + // TODO: create withMiddleware() closure if absent + // https://github.com/RonasIT/larabuilder/issues/73#issuecomment-4718330712 protected function insertNode(MethodCall $node): MethodCall { /** @var Closure $closure */ @@ -63,15 +65,17 @@ protected function findMiddlewareGroupIndex(array $stmts): ?int }); } + // TODO: handle inline FQCN namespace in array instead of use import + // https://github.com/RonasIT/larabuilder/issues/73#issuecomment-4718330712 protected function updateMiddlewareGroup(Closure $closure, int $groupIndex): void { $originalMiddlewares = $closure->stmts[$groupIndex]->expr->args[1]->value->value ?? $closure->stmts[$groupIndex]->expr->args[1]->value->class->name ?? $closure->stmts[$groupIndex]->expr->args[1]->value->items; - $originalMiddlewares = is_string($originalMiddlewares) - ? [new ArrayItem($closure->stmts[$groupIndex]->expr->args[1]->value)] - : $originalMiddlewares; + $originalMiddlewares = is_array($originalMiddlewares) + ? $originalMiddlewares + : [new ArrayItem($closure->stmts[$groupIndex]->expr->args[1]->value)]; $mergedMiddlewares = $this->mergeMiddlewares($originalMiddlewares); @@ -88,9 +92,7 @@ protected function mergeMiddlewares(array $originalMiddlewareList): array fn ($originalMiddleware) => $this->isSameMiddleware($middleware, $originalMiddleware), ); - if (!is_null($sameMiddlewareKey)) { - $this->normalizeMiddleware($originalMiddlewareList[$sameMiddlewareKey]); - } else { + if (is_null($sameMiddlewareKey)) { $filteredNewList[] = $this->makeArrayItem($middleware); } } @@ -101,10 +103,12 @@ protected function mergeMiddlewares(array $originalMiddlewareList): array }; } + // TODO: fix deduplication when same class has FQCN in one place and short imported name in another + // https://github.com/RonasIT/larabuilder/issues/73#issuecomment-4718330712 private function isSameMiddleware(string $newMiddleware, ArrayItem $originalMiddleware): bool { if ($originalMiddleware->value instanceof ClassConstFetch) { - $originalName = $originalMiddleware->value->class->toString(); + $originalName = $originalMiddleware->value->class->name; return $originalName === $newMiddleware || $originalName === class_basename($newMiddleware); @@ -113,18 +117,6 @@ private function isSameMiddleware(string $newMiddleware, ArrayItem $originalMidd return $originalMiddleware->value->value === $newMiddleware; } - protected function normalizeMiddleware(ArrayItem $middleware): void - { - if ($middleware->value instanceof ClassConstFetch) { - $this->setClassBaseName($middleware->value); - } - } - - protected function setClassBaseName(ClassConstFetch $class): void - { - $class->class->name = class_basename($class->class->name); - } - protected function buildPrependToGroupCall(): Expression { $middlewareList = $this->getMiddlewareList(); @@ -137,6 +129,8 @@ protected function buildPrependToGroupCall(): Expression return new Expression($methodCall); } + // TODO: preserve original multiline formatting of middleware array + // https://github.com/RonasIT/larabuilder/issues/73#issuecomment-4718330712 protected function buildMiddlewareArg(array $middlewares): Arg { return new Arg(new Array_($middlewares)); diff --git a/tests/AppBootstrapBuilderTest.php b/tests/AppBootstrapBuilderTest.php index a7c5c07..1963bd9 100644 --- a/tests/AppBootstrapBuilderTest.php +++ b/tests/AppBootstrapBuilderTest.php @@ -181,9 +181,7 @@ public function testAddMiddlewarePrependToGroupExistsMiddlewares() new AppBootstrapBuilder($file) ->addMiddlewarePrependToGroup('api', [ - FakeClass::class, 'throttle:60,10', - 'some_middleware', Authenticate::class, ]) ->save(); diff --git a/tests/Support/OriginStructures/bootstrap_with_prepend_group_as_string_set_class.php b/tests/Support/OriginStructures/bootstrap_with_prepend_group_as_string_set_class.php index 15bb292..e334479 100644 --- a/tests/Support/OriginStructures/bootstrap_with_prepend_group_as_string_set_class.php +++ b/tests/Support/OriginStructures/bootstrap_with_prepend_group_as_string_set_class.php @@ -11,7 +11,7 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - $middleware->prependToGroup('api', Illuminate\Auth\Middleware\Authenticate::class); + $middleware->prependToGroup('api', \Illuminate\Auth\Middleware\Authenticate::class); }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php index adf77df..5dc2fa5 100644 --- a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php +++ b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php @@ -3,7 +3,6 @@ use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; -use RonasIT\Larabuilder\Tests\Support\Classes\FakeClass; use Illuminate\Auth\Middleware\Authenticate; return Application::configure(basePath: dirname(__DIR__)) @@ -13,7 +12,7 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - $middleware->prependToGroup('api', ['throttle:60,10', Authenticate::class, FakeClass::class, 'some_middleware']); + $middleware->prependToGroup('api', ['throttle:60,10', Illuminate\Auth\Middleware\Authenticate::class]); }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group_as_string_set_class.php b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group_as_string_set_class.php index 49de65c..bed591c 100644 --- a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group_as_string_set_class.php +++ b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_prepend_group_as_string_set_class.php @@ -11,7 +11,7 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - $middleware->prependToGroup('api', [Illuminate\Auth\Middleware\Authenticate::class, 'some_middleware']); + $middleware->prependToGroup('api', [\Illuminate\Auth\Middleware\Authenticate::class, 'some_middleware']); }) ->withExceptions(function (Exceptions $exceptions): void { // From b4e147b61b74aba296a7f504121c10b28e8bf081 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Fri, 19 Jun 2026 16:15:10 +0500 Subject: [PATCH 13/17] feat: add middlewares into prependToGroup refs: https://github.com/RonasIT/larabuilder/issues/74 --- .../AppBootstrapVisitors/AddMiddlewarePrependToGroup.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php index bc5436d..386f09e 100644 --- a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php +++ b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php @@ -29,8 +29,6 @@ public function __construct( ); } - // TODO: create withMiddleware() closure if absent - // https://github.com/RonasIT/larabuilder/issues/73#issuecomment-4718330712 protected function insertNode(MethodCall $node): MethodCall { /** @var Closure $closure */ @@ -65,8 +63,6 @@ protected function findMiddlewareGroupIndex(array $stmts): ?int }); } - // TODO: handle inline FQCN namespace in array instead of use import - // https://github.com/RonasIT/larabuilder/issues/73#issuecomment-4718330712 protected function updateMiddlewareGroup(Closure $closure, int $groupIndex): void { $originalMiddlewares = $closure->stmts[$groupIndex]->expr->args[1]->value->value @@ -103,8 +99,6 @@ protected function mergeMiddlewares(array $originalMiddlewareList): array }; } - // TODO: fix deduplication when same class has FQCN in one place and short imported name in another - // https://github.com/RonasIT/larabuilder/issues/73#issuecomment-4718330712 private function isSameMiddleware(string $newMiddleware, ArrayItem $originalMiddleware): bool { if ($originalMiddleware->value instanceof ClassConstFetch) { @@ -129,8 +123,6 @@ protected function buildPrependToGroupCall(): Expression return new Expression($methodCall); } - // TODO: preserve original multiline formatting of middleware array - // https://github.com/RonasIT/larabuilder/issues/73#issuecomment-4718330712 protected function buildMiddlewareArg(array $middlewares): Arg { return new Arg(new Array_($middlewares)); From c1f9c72b3fb5fb9281260a1fc161088c5482e7e2 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov <110885041+AZabolotnikov@users.noreply.github.com> Date: Mon, 22 Jun 2026 12:04:20 +0500 Subject: [PATCH 14/17] Update README.md Co-authored-by: Artyom Osepyan <152782500+artengin@users.noreply.github.com> --- README.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 64495aa..b9fa114 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ render for the passed exception class. Adds middleware to a named group via `prependToGroup` inside the `withMiddleware` closure. Accepts a single class name or string, or an array of them. Skips values that already exist in the target group. Does not affect other groups. +Supports `InsertPositionEnum::Start` and `InsertPositionEnum::End` to control the insertion position. `End` is used by default. ```php new AppBootstrapBuilder(bootstrap_path('app.php')) @@ -114,16 +115,8 @@ new AppBootstrapBuilder(bootstrap_path('app.php')) MyMiddleware::class, 'throttle:60,1', ]) + ->addMiddlewarePrependToGroup('web', WebMiddleware::class,, InsertPositionEnum::Start) ->save(); -``` - -To insert at the beginning of the group instead of the end, pass `InsertPositionEnum::Start`: - -```php -new AppBootstrapBuilder(bootstrap_path('app.php')) - ->addMiddlewarePrependToGroup('api', MyMiddleware::class, InsertPositionEnum::Start) - ->save(); -``` **Note:** Provide the full class name (FQCN) for class-based middleware — the method imports it automatically. From fe4b39d03184baa71d7c975fb91cd9d1349a25e1 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov <110885041+AZabolotnikov@users.noreply.github.com> Date: Mon, 22 Jun 2026 12:04:42 +0500 Subject: [PATCH 15/17] Update src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php Co-authored-by: Artyom Osepyan <152782500+artengin@users.noreply.github.com> --- .../AppBootstrapVisitors/AddMiddlewarePrependToGroup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php index 386f09e..ea3ca9f 100644 --- a/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php +++ b/src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php @@ -99,7 +99,7 @@ protected function mergeMiddlewares(array $originalMiddlewareList): array }; } - private function isSameMiddleware(string $newMiddleware, ArrayItem $originalMiddleware): bool + protected function isSameMiddleware(string $newMiddleware, ArrayItem $originalMiddleware): bool { if ($originalMiddleware->value instanceof ClassConstFetch) { $originalName = $originalMiddleware->value->class->name; From 12b0c8329d50d94703a2f1f4ce8f2cd94303d19d Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Mon, 22 Jun 2026 12:17:56 +0500 Subject: [PATCH 16/17] feat: add middlewares into prependToGroup refs: https://github.com/RonasIT/larabuilder/issues/74 --- .../Support/OriginStructures/bootstrap_with_prepend_group.php | 3 ++- .../bootstrap_with_changed_prepend_group.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Support/OriginStructures/bootstrap_with_prepend_group.php b/tests/Support/OriginStructures/bootstrap_with_prepend_group.php index 68de544..0052a0e 100644 --- a/tests/Support/OriginStructures/bootstrap_with_prepend_group.php +++ b/tests/Support/OriginStructures/bootstrap_with_prepend_group.php @@ -3,6 +3,7 @@ use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; +use Illuminate\Auth\Middleware\Authenticate; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( @@ -11,7 +12,7 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - $middleware->prependToGroup('api', ['throttle:60,10', Illuminate\Auth\Middleware\Authenticate::class]); + $middleware->prependToGroup('api', ['throttle:60,10', Authenticate::class]); }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php index 5dc2fa5..0052a0e 100644 --- a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php +++ b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php @@ -12,7 +12,7 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - $middleware->prependToGroup('api', ['throttle:60,10', Illuminate\Auth\Middleware\Authenticate::class]); + $middleware->prependToGroup('api', ['throttle:60,10', Authenticate::class]); }) ->withExceptions(function (Exceptions $exceptions): void { // From 3ca229dc0472f8ca4443ca47989ad25cdb2014b4 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikov Date: Mon, 22 Jun 2026 15:02:44 +0500 Subject: [PATCH 17/17] feat: add middlewares into prependToGroup refs: https://github.com/RonasIT/larabuilder/issues/74 --- tests/AppBootstrapBuilderTest.php | 2 +- ...nd_group.php => bootstrap_without_changed_prepend_group.php} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/fixtures/AppBootstrapBuilderTest/{bootstrap_with_changed_prepend_group.php => bootstrap_without_changed_prepend_group.php} (100%) diff --git a/tests/AppBootstrapBuilderTest.php b/tests/AppBootstrapBuilderTest.php index 1963bd9..8c02c15 100644 --- a/tests/AppBootstrapBuilderTest.php +++ b/tests/AppBootstrapBuilderTest.php @@ -176,7 +176,7 @@ public function testAddMiddlewarePrependToGroupExistsMiddlewares() $this->mockNativeFunction( 'RonasIT\Larabuilder\Builders', - $this->callFilePutContent($file, 'bootstrap_with_changed_prepend_group.php'), + $this->callFilePutContent($file, 'bootstrap_without_changed_prepend_group.php'), ); new AppBootstrapBuilder($file) diff --git a/tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php b/tests/fixtures/AppBootstrapBuilderTest/bootstrap_without_changed_prepend_group.php similarity index 100% rename from tests/fixtures/AppBootstrapBuilderTest/bootstrap_with_changed_prepend_group.php rename to tests/fixtures/AppBootstrapBuilderTest/bootstrap_without_changed_prepend_group.php