Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/.idea
.phpunit.result.cache
.phpunit.cache
composer.lock
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,9 @@ new AppBootstrapBuilder(bootstrap_path('app.php'))
MyMiddleware::class,
'throttle:60,1',
])
->addMiddlewarePrependToGroup('web', WebMiddleware::class,, InsertPositionEnum::Start)
->addMiddlewarePrependToGroup('web', WebMiddleware::class, InsertPositionEnum::Start)
->save();
```

**Note:** Provide the full class name (FQCN) for class-based middleware — the method imports it automatically.

Expand Down
8 changes: 8 additions & 0 deletions src/Enums/ExpressionAttributeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace RonasIT\Larabuilder\Enums;

enum ExpressionAttributeEnum: string
{
case IsArrayMultiline = 'is_array_multiline';
}
21 changes: 4 additions & 17 deletions src/Printer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\PropertyItem;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Property;
use PhpParser\PrettyPrinter\Standard;
use RonasIT\Larabuilder\Enums\ExpressionAttributeEnum;
use RonasIT\Larabuilder\Enums\StatementAttributeEnum;
use RonasIT\Larabuilder\Nodes\PreformattedCode;

Expand Down Expand Up @@ -41,28 +41,15 @@ protected function removeDuplicateEmptyLines(string $code): string

protected function pExpr_Array(Array_ $node): string
{
if ($this->hasParentOfType($node, PropertyItem::class)) {
$isMultiline = $node->getAttribute(ExpressionAttributeEnum::IsArrayMultiline->value, false);

if ($isMultiline) {
return '[' . $this->pCommaSeparatedMultiline($node->items, true) . $this->nl . ']';
}

return parent::pExpr_Array($node);
}

protected function hasParentOfType(Node $node, string $type): bool
{
$parent = $node->getAttribute(StatementAttributeEnum::Parent->value);

while ($parent !== null) {
if ($parent instanceof $type) {
return true;
}

$parent = $parent->getAttribute(StatementAttributeEnum::Parent->value);
}

return false;
}

protected function pStmt_Property(Property $node): string
{
$newLine = ($this->shouldAddNewlineBeforeIfTypeDiffers($node, Property::class)) ? $this->nl : '';
Expand Down
24 changes: 12 additions & 12 deletions src/Support/NodeValueFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,37 @@

class NodeValueFactory
{
public static function make(mixed $value): NodeValueDTO
public static function make(mixed $value, array $attributes = []): NodeValueDTO
{
$type = get_debug_type($value);

$node = match ($type) {
'int' => new Int_($value),
'array' => static::makeArrayValue($value),
'string' => new String_($value),
'float' => new Float_($value),
'bool' => static::makeBoolValue($value),
'null' => new ConstFetch(new Name('null')),
'int' => new Int_($value, $attributes),
'array' => static::makeArrayValue($value, $attributes),
'string' => new String_($value, $attributes),
'float' => new Float_($value, $attributes),
'bool' => static::makeBoolValue($value, $attributes),
'null' => new ConstFetch(new Name('null'), $attributes),
};

return new NodeValueDTO($node, new Identifier($type));
}

protected static function makeBoolValue(bool $value): ConstFetch
protected static function makeBoolValue(bool $value, array $attributes): ConstFetch
{
$name = new Name($value ? 'true' : 'false');

return new ConstFetch($name);
return new ConstFetch($name, $attributes);
}

protected static function makeArrayValue(array $values): Array_
protected static function makeArrayValue(array $values, array $attributes): Array_
{
$items = [];

foreach ($values as $key => $val) {
$items[] = new ArrayItem(static::make($val)->node, static::make($key)->node);
$items[] = new ArrayItem(static::make($val, $attributes)->node, static::make($key)->node);
}

return new Array_($items);
return new Array_($items, $attributes);
}
}
84 changes: 56 additions & 28 deletions src/Visitors/AppBootstrapVisitors/AddMiddlewarePrependToGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace RonasIT\Larabuilder\Visitors\AppBootstrapVisitors;

use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\ArrayItem;
use PhpParser\Node\Expr\Array_;
Expand All @@ -11,13 +12,18 @@
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\UseItem;
use RonasIT\Larabuilder\Enums\ExpressionAttributeEnum;
use RonasIT\Larabuilder\Enums\InsertPositionEnum;

class AddMiddlewarePrependToGroup extends AbstractAppBootstrapVisitor
{
protected array $importedNamespaces = [];

public function __construct(
protected string $group,
protected array $middlewares,
Expand All @@ -29,6 +35,18 @@ public function __construct(
);
}

public function leaveNode(Node $node): Node
{
if ($node instanceof UseItem) {
$this->importedNamespaces[] = [
'namespace' => $node->name->toString(),
'alias' => $node->alias?->toString() ?? null,
];
}

return parent::leaveNode($node);
}

protected function insertNode(MethodCall $node): MethodCall
{
/** @var Closure $closure */
Expand Down Expand Up @@ -56,59 +74,67 @@ protected function removeNopPlaceholder(Closure $closure): void

protected function findMiddlewareGroupIndex(array $stmts): ?int
{
return array_find_key($stmts, function (Expression $stmt) {
return !empty($stmt->expr->name)
return array_find_key($stmts, function (Node $stmt) {
return $stmt instanceof Expression
&& !empty($stmt->expr->name)
&& $stmt->expr->name->toString() === $this->targetMethod
&& $stmt->expr->args[0]->value->value === $this->group;
});
}

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;
$middlewares = $closure->stmts[$groupIndex]->expr->args[1];

$originalMiddlewares = is_array($originalMiddlewares)
? $originalMiddlewares
: [new ArrayItem($closure->stmts[$groupIndex]->expr->args[1]->value)];
$originMiddlewares = ($middlewares->value instanceof Array_)
? $middlewares->value->items
: [new ArrayItem($middlewares->value)];

$mergedMiddlewares = $this->mergeMiddlewares($originalMiddlewares);
$mergedMiddlewares = $this->mergeMiddlewares($originMiddlewares);

$closure->stmts[$groupIndex]->expr->args[1] = $this->buildMiddlewareArg($mergedMiddlewares);
}

protected function mergeMiddlewares(array $originalMiddlewareList): array
protected function mergeMiddlewares(array $originMiddlewares): array
{
$originalResolved = array_map(fn ($middleware) => $this->resolveMiddlewareName($middleware), $originMiddlewares);

$filteredNewList = [];

foreach ($this->middlewares as $middleware) {
$sameMiddlewareKey = array_find_key(
$originalMiddlewareList,
fn ($originalMiddleware) => $this->isSameMiddleware($middleware, $originalMiddleware),
);

if (is_null($sameMiddlewareKey)) {
if (!in_array($middleware, $originalResolved, true)) {
$filteredNewList[] = $this->makeArrayItem($middleware);
}
}

return match ($this->position) {
InsertPositionEnum::Start => [...$filteredNewList, ...$originalMiddlewareList],
InsertPositionEnum::End => [...$originalMiddlewareList, ...$filteredNewList],
InsertPositionEnum::Start => [...$filteredNewList, ...$originMiddlewares],
InsertPositionEnum::End => [...$originMiddlewares, ...$filteredNewList],
};
}

protected function isSameMiddleware(string $newMiddleware, ArrayItem $originalMiddleware): bool
protected function resolveMiddlewareName($middleware): ?string
{
if ($originalMiddleware->value instanceof ClassConstFetch) {
$originalName = $originalMiddleware->value->class->name;
if ($middleware->value instanceof String_) {
return $middleware->value->value;
}

$isClass = $middleware->value instanceof ClassConstFetch;

return $originalName === $newMiddleware
|| $originalName === class_basename($newMiddleware);
if ($isClass && $middleware->value->class instanceof FullyQualified) {
return $middleware->value->class->name;
}

return $originalMiddleware->value->value === $newMiddleware;
if ($isClass) {
$found = array_find($this->importedNamespaces, function (array $namespace) use ($middleware) {
return $middleware->value->class->name === class_basename($namespace['namespace'])
|| $middleware->value->class->name === $namespace['alias'];
});

return $found['namespace'] ?? null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid duplicating class middleware in chained calls

When the same FQCN middleware is added by two addMiddlewarePrependToGroup() calls in one builder run and that class was not already imported in the original file, the first visitor leaves a short Foo::class in the group but AddImports only creates the use statement in afterTraverse(). This resolver therefore has no matching entry in $importedNamespaces, returns null, and the later visitor treats the just-added class as missing and appends a duplicate; keep a basename fallback or account for pending imports so chained calls remain idempotent.

Useful? React with 👍 / 👎.

}

return null;
}

protected function buildPrependToGroupCall(): Expression
Expand All @@ -123,14 +149,16 @@ protected function buildPrependToGroupCall(): Expression
return new Expression($methodCall);
}

protected function buildMiddlewareArg(array $middlewares): Arg
protected function getMiddlewareList(): array
{
return new Arg(new Array_($middlewares));
return array_map(fn ($middleware) => $this->makeArrayItem($middleware), $this->middlewares);
}

protected function getMiddlewareList(): array
protected function buildMiddlewareArg(array $middlewares): Arg
{
return array_map(fn ($middleware) => $this->makeArrayItem($middleware), $this->middlewares);
return new Arg(new Array_($middlewares, [
ExpressionAttributeEnum::IsArrayMultiline->value => true,
]));
}

protected function makeArrayItem(string $middleware): ArrayItem
Expand Down
5 changes: 4 additions & 1 deletion src/Visitors/PropertyVisitors/AddArrayPropertyItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PhpParser\Node\Identifier;
use PhpParser\Node\PropertyItem;
use PhpParser\Node\Stmt\Property;
use RonasIT\Larabuilder\Enums\ExpressionAttributeEnum;
use RonasIT\Larabuilder\Exceptions\UnexpectedPropertyTypeException;

class AddArrayPropertyItem extends SetProperty
Expand All @@ -21,7 +22,9 @@ public function __construct(
parent::__construct($name, $value);

$this->arrayItem = new ArrayItem($this->property->node);
$arrayNode = new Array_([$this->arrayItem]);
$arrayNode = new Array_([$this->arrayItem], [
ExpressionAttributeEnum::IsArrayMultiline->value => true,
]);

$this->propertyItem = new PropertyItem($this->name, $arrayNode);
$this->typeIdentifier = new Identifier('array');
Expand Down
7 changes: 6 additions & 1 deletion src/Visitors/PropertyVisitors/SetProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use RonasIT\Larabuilder\Contracts\InsertNodeContract;
use RonasIT\Larabuilder\DTO\NodeValueDTO;
use RonasIT\Larabuilder\Enums\AccessModifierEnum;
use RonasIT\Larabuilder\Enums\ExpressionAttributeEnum;
use RonasIT\Larabuilder\Support\NodeValueFactory;

class SetProperty extends AbstractPropertyVisitor implements InsertNodeContract
Expand All @@ -24,7 +25,11 @@ public function __construct(
) {
parent::__construct($name);

$this->property = NodeValueFactory::make($value);
$attributes = is_array($value)
? [ExpressionAttributeEnum::IsArrayMultiline->value => true]
: [];

$this->property = NodeValueFactory::make($value, $attributes);

$this->propertyItem = new PropertyItem($this->name, $this->property->node);
$this->typeIdentifier = $this->property->typeNode;
Expand Down
1 change: 1 addition & 0 deletions tests/AppBootstrapBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ public function testAddMiddlewarePrependToGroupExistsMiddlewares()
->addMiddlewarePrependToGroup('api', [
'throttle:60,10',
Authenticate::class,
\Illuminate\Routing\Controllers\Middleware::class,
])
->save();
}
Expand Down
11 changes: 10 additions & 1 deletion tests/Support/OriginStructures/bootstrap_with_prepend_group.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Auth\Middleware\Authenticate;
use Illuminate\Routing\Controllers\Middleware as SomeMiddleware;

return Application::configure(basePath: dirname(__DIR__))
->withRouting(
Expand All @@ -12,7 +13,15 @@
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
$middleware->prependToGroup('api', ['throttle:60,10', Authenticate::class]);
if (app()->environment('production')) {
$middleware->append('auth');
}

$middleware->prependToGroup('api', [
'throttle:60,10',
Authenticate::class,
SomeMiddleware::class,
]);
})
->withExceptions(function (Exceptions $exceptions): void {
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
$middleware->prependToGroup('api', ['throttle:60,10', FakeClass::class]);
$middleware->prependToGroup('web', ['throttle:10,10', FakeClass::class]);
$middleware->prependToGroup('api', [
'throttle:60,10',
FakeClass::class,
]);
$middleware->prependToGroup('web', [
'throttle:10,10',
FakeClass::class,
]);
})
->withExceptions(function (Exceptions $exceptions): void {
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
$middleware->prependToGroup('api', ['throttle:60,10', 'some_middleware']);
$middleware->prependToGroup('api', [
'throttle:60,10',
'some_middleware',
]);
})
->withExceptions(function (Exceptions $exceptions): void {
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
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 {
//
Expand Down
Loading