-
Notifications
You must be signed in to change notification settings - Fork 0
Add middlewares into prependToGroup #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
6bff827
2b6ac06
682a3dd
0d4eefe
e49fac4
6f0e82b
8b80c3c
230e361
77d419f
c084939
953dc62
54f1c99
b4e147b
cbc208f
c1f9c72
fe4b39d
12b0c83
cd14d92
3ca229d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| <?php | ||
|
|
||
| namespace RonasIT\Larabuilder\Visitors\AppBootstrapVisitors; | ||
|
|
||
| use PhpParser\Node\Arg; | ||
| use PhpParser\Node\ArrayItem; | ||
| use PhpParser\Node\Expr\Array_; | ||
| use PhpParser\Node\Expr\ClassConstFetch; | ||
| use PhpParser\Node\Expr\Closure; | ||
| use PhpParser\Node\Expr\MethodCall; | ||
| use PhpParser\Node\Expr\Variable; | ||
| use PhpParser\Node\Identifier; | ||
| use PhpParser\Node\Name; | ||
| 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', | ||
| targetMethod: 'prependToGroup', | ||
| ); | ||
| } | ||
|
|
||
| protected function insertNode(MethodCall $node): MethodCall | ||
| { | ||
| /** @var Closure $closure */ | ||
| $closure = $node->args[0]->value; | ||
|
|
||
| $this->removeNopPlaceholder($closure); | ||
|
|
||
| $statementIndex = $this->findMiddlewareGroupIndex($closure->stmts); | ||
|
|
||
| if (is_null($statementIndex)) { | ||
| $closure->stmts[] = $this->buildPrependToGroupCall(); | ||
| } else { | ||
| $this->updateMiddlewareGroup($closure, $statementIndex); | ||
| } | ||
|
|
||
| return $node; | ||
| } | ||
|
|
||
| protected function removeNopPlaceholder(Closure $closure): void | ||
| { | ||
| if (!empty($closure->stmts) && ($closure->stmts[0] ?? null) instanceof Nop) { | ||
| array_shift($closure->stmts); | ||
| } | ||
| } | ||
|
|
||
| protected function findMiddlewareGroupIndex(array $stmts): ?int | ||
| { | ||
| return array_find_key($stmts, function (Expression $stmt) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a bootstrap already has more than one Useful? React with 👍 / 👎. |
||
| return !empty($stmt->expr->name) | ||
| && $stmt->expr->name->toString() === $this->targetMethod | ||
| && $stmt->expr->args[0]->value->value === $this->group; | ||
|
AZabolotnikov marked this conversation as resolved.
AZabolotnikov marked this conversation as resolved.
|
||
| }); | ||
| } | ||
|
AZabolotnikov marked this conversation as resolved.
|
||
|
|
||
| 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; | ||
|
AZabolotnikov marked this conversation as resolved.
|
||
|
|
||
| $originalMiddlewares = is_array($originalMiddlewares) | ||
| ? $originalMiddlewares | ||
| : [new ArrayItem($closure->stmts[$groupIndex]->expr->args[1]->value)]; | ||
|
|
||
| $mergedMiddlewares = $this->mergeMiddlewares($originalMiddlewares); | ||
|
|
||
| $closure->stmts[$groupIndex]->expr->args[1] = $this->buildMiddlewareArg($mergedMiddlewares); | ||
|
AZabolotnikov marked this conversation as resolved.
|
||
| } | ||
|
|
||
| protected function mergeMiddlewares(array $originalMiddlewareList): array | ||
| { | ||
| $filteredNewList = []; | ||
|
|
||
| foreach ($this->middlewares as $middleware) { | ||
| $sameMiddlewareKey = array_find_key( | ||
| $originalMiddlewareList, | ||
| fn ($originalMiddleware) => $this->isSameMiddleware($middleware, $originalMiddleware), | ||
| ); | ||
|
|
||
| if (is_null($sameMiddlewareKey)) { | ||
| $filteredNewList[] = $this->makeArrayItem($middleware); | ||
| } | ||
| } | ||
|
|
||
| return match ($this->position) { | ||
| InsertPositionEnum::Start => [...$filteredNewList, ...$originalMiddlewareList], | ||
| InsertPositionEnum::End => [...$originalMiddlewareList, ...$filteredNewList], | ||
| }; | ||
| } | ||
|
|
||
| protected function isSameMiddleware(string $newMiddleware, ArrayItem $originalMiddleware): bool | ||
| { | ||
| if ($originalMiddleware->value instanceof ClassConstFetch) { | ||
| $originalName = $originalMiddleware->value->class->name; | ||
|
|
||
| return $originalName === $newMiddleware | ||
| || $originalName === class_basename($newMiddleware); | ||
|
Comment on lines
+107
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the target group already contains an imported class with the same short name as the middleware being added, this basename comparison treats different classes as duplicates. For example, if the bootstrap imports Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| return $originalMiddleware->value->value === $newMiddleware; | ||
| } | ||
|
|
||
| protected function buildPrependToGroupCall(): Expression | ||
| { | ||
| $middlewareList = $this->getMiddlewareList(); | ||
|
|
||
| $methodCall = new MethodCall(new Variable('middleware'), new Identifier($this->targetMethod), [ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If the bootstrap file names the Useful? React with 👍 / 👎. |
||
| new Arg(new String_($this->group)), | ||
| $this->buildMiddlewareArg($middlewareList), | ||
| ]); | ||
|
|
||
| return new Expression($methodCall); | ||
| } | ||
|
|
||
| protected function buildMiddlewareArg(array $middlewares): Arg | ||
| { | ||
| return new Arg(new Array_($middlewares)); | ||
| } | ||
|
|
||
| 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'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the bootstrap file already imports a different class with the same short name as the middleware being added, this drops the namespace and later adds another unaliased Useful? React with 👍 / 👎. |
||
| } else { | ||
| $value = new String_($middleware); | ||
| } | ||
|
|
||
| return new ArrayItem($value); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,11 +2,14 @@ | |
|
|
||
| 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; | ||
| use RonasIT\Larabuilder\Tests\Support\Traits\PHPFileBuilderTestMockTrait; | ||
|
AZabolotnikov marked this conversation as resolved.
|
||
| use Symfony\Component\HttpKernel\Exception\HttpException; | ||
|
|
||
|
|
@@ -137,4 +140,78 @@ 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', | ||
| position: InsertPositionEnum::Start, | ||
| ) | ||
| ->addMiddlewarePrependToGroup( | ||
| group: 'web', | ||
| middleware: [ | ||
| 'throttle:10,10', | ||
| FakeClass::class, | ||
| ], | ||
| ) | ||
| ->save(); | ||
|
AZabolotnikov marked this conversation as resolved.
|
||
| } | ||
|
|
||
| public function testAddMiddlewarePrependToGroupExistsMiddlewares() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test isn't quite correct: it doesn't actually change the contents of I suggest we either:
|
||
| { | ||
| $file = $this->generateOriginalStructurePath('bootstrap_with_prepend_group.php'); | ||
|
|
||
| $this->mockNativeFunction( | ||
| 'RonasIT\Larabuilder\Builders', | ||
| $this->callFilePutContent($file, 'bootstrap_without_changed_prepend_group.php'), | ||
| ); | ||
|
|
||
| new AppBootstrapBuilder($file) | ||
| ->addMiddlewarePrependToGroup('api', [ | ||
| 'throttle:60,10', | ||
| Authenticate::class, | ||
| ]) | ||
| ->save(); | ||
| } | ||
|
|
||
| public static function provideMiddlewareAsString(): array | ||
| { | ||
| 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', | ||
| ], | ||
| ]; | ||
| } | ||
|
|
||
| #[DataProvider('provideMiddlewareAsString')] | ||
| public function testAddMiddlewarePrependToGroupMiddlewareAsString(string $original, string $result): void | ||
|
AZabolotnikov marked this conversation as resolved.
|
||
| { | ||
| $file = $this->generateOriginalStructurePath($original); | ||
|
|
||
| $this->mockNativeFunction('RonasIT\Larabuilder\Builders', $this->callFilePutContent($file, $result)); | ||
|
|
||
| new AppBootstrapBuilder($file) | ||
| ->addMiddlewarePrependToGroup('api', [ | ||
| 'some_middleware', | ||
| ]) | ||
| ->save(); | ||
| } | ||
|
AZabolotnikov marked this conversation as resolved.
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| <?php | ||
|
|
||
| namespace RonasIT\Larabuilder\Tests\Support\Classes; | ||
|
|
||
| class FakeClass | ||
| { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <?php | ||
|
|
||
| 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( | ||
| web: __DIR__.'/../routes/web.php', | ||
| commands: __DIR__.'/../routes/console.php', | ||
| health: '/up', | ||
| ) | ||
| ->withMiddleware(function (Middleware $middleware): void { | ||
| $middleware->prependToGroup('api', ['throttle:60,10', Authenticate::class]); | ||
| }) | ||
| ->withExceptions(function (Exceptions $exceptions): void { | ||
| // | ||
| })->create(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <?php | ||
|
|
||
| use Illuminate\Foundation\Application; | ||
| use Illuminate\Foundation\Configuration\Exceptions; | ||
| use Illuminate\Foundation\Configuration\Middleware; | ||
|
|
||
| return Application::configure(basePath: dirname(__DIR__)) | ||
| ->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(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <?php | ||
|
|
||
| use Illuminate\Foundation\Application; | ||
| use Illuminate\Foundation\Configuration\Exceptions; | ||
| use Illuminate\Foundation\Configuration\Middleware; | ||
|
|
||
| return Application::configure(basePath: dirname(__DIR__)) | ||
| ->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(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <?php | ||
|
|
||
| use Illuminate\Foundation\Application; | ||
| use Illuminate\Foundation\Configuration\Exceptions; | ||
| use Illuminate\Foundation\Configuration\Middleware; | ||
| use RonasIT\Larabuilder\Tests\Support\Classes\FakeClass; | ||
|
|
||
| return Application::configure(basePath: dirname(__DIR__)) | ||
| ->withRouting( | ||
| web: __DIR__.'/../routes/web.php', | ||
| commands: __DIR__.'/../routes/console.php', | ||
| health: '/up', | ||
| ) | ||
| ->withMiddleware(function (Middleware $middleware): void { | ||
| $middleware->prependToGroup('api', ['throttle:60,10', FakeClass::class]); | ||
| $middleware->prependToGroup('web', ['throttle:10,10', FakeClass::class]); | ||
| }) | ||
| ->withExceptions(function (Exceptions $exceptions): void { | ||
| // | ||
| })->create(); |
Uh oh!
There was an error while loading. Please reload this page.