Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
1a32fc3
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 22, 2026
1ec1f91
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 22, 2026
8071f7f
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 22, 2026
05bac0d
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 22, 2026
b3a142e
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 26, 2026
08bb80f
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 26, 2026
a47a861
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 26, 2026
d3835da
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 26, 2026
58ade35
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 26, 2026
b307beb
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 26, 2026
bfd5cb5
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 26, 2026
c513952
Merge branch 'master' into generate-withSchedule
AZabolotnikov Jan 26, 2026
57580d9
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 26, 2026
7c35d34
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 26, 2026
e12f780
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 26, 2026
b08bfbe
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 26, 2026
1bb478b
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 26, 2026
b2f486c
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 27, 2026
d6c0d71
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 27, 2026
0c71342
Merge branch 'master' into generate-withSchedule
AZabolotnikov Jan 27, 2026
0cebe13
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 27, 2026
d5b78c8
Merge remote-tracking branch 'origin/generate-withSchedule' into gene…
AZabolotnikov Jan 27, 2026
6da6f6d
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 27, 2026
37a62d3
Merge branch 'master' into generate-withSchedule
AZabolotnikov Jan 28, 2026
58d7380
Merge branch 'master' into generate-withSchedule
AZabolotnikov Jan 30, 2026
50acbe6
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 30, 2026
f0d48df
Merge remote-tracking branch 'origin/generate-withSchedule' into gene…
AZabolotnikov Jan 30, 2026
769d14e
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 30, 2026
7a57ad2
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 30, 2026
13a8f09
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 30, 2026
5e41979
Update tests/AppBootstrapBuilderTest.php
AZabolotnikov Jan 30, 2026
dd730fd
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 30, 2026
fee25ee
Merge remote-tracking branch 'origin/generate-withSchedule' into gene…
AZabolotnikov Jan 30, 2026
3445a5f
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Jan 30, 2026
0f2c548
Update README.md
DenTray Feb 5, 2026
3092f2d
Update README.md
DenTray Feb 5, 2026
ecd449f
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Feb 10, 2026
f3a0605
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Feb 10, 2026
9bba11b
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Feb 10, 2026
7aaa525
feat: ability generate withSchedule for boostrap/app.php file
AZabolotnikov Mar 13, 2026
dd1402c
Merge branch 'master' into generate-withSchedule
DenTray Mar 23, 2026
a57198d
fix: remarks from reviewer
artengin Mar 23, 2026
94a51ea
fix: remarks from reviewer
artengin Mar 23, 2026
78d54df
fix: remarks
artengin Mar 23, 2026
751d0a8
Merge remote-tracking branch 'origin/master' into generate-withSchedule
artengin Mar 24, 2026
1427f86
fix: remarks from reviewer
artengin Mar 27, 2026
d550bee
Merge remote-tracking branch 'origin/master' into generate-withSchedule
artengin Mar 31, 2026
b7a9305
Merge branch 'master' into generate-withSchedule
artengin Apr 2, 2026
b9002a1
fix: update invalid_schedule_option fixture with daysOfMonth method
artengin Apr 2, 2026
235bed2
Merge remote-tracking branch 'origin/master' into generate-withSchedule
artengin Apr 10, 2026
7be3a6b
Merge remote-tracking branch 'origin/master' into generate-withSchedule
artengin Apr 10, 2026
1aef897
Merge remote-tracking branch 'origin/refactor-architecture' into crea…
artengin Apr 10, 2026
14f8da6
Merge remote-tracking branch 'origin/generate-withSchedule' into crea…
artengin Apr 10, 2026
cf81906
feat: Add method to manage withRouting options
artengin Apr 10, 2026
0fd37c4
docs: add addRoutingOption to README
artengin Apr 10, 2026
b0928d8
fix: scope bootstrap chain detection to Application::configure
artengin Apr 10, 2026
5917f78
fix: remarks
artengin Apr 10, 2026
0de859c
Merge branch 'master' into generate-withSchedule
artengin Apr 13, 2026
b6c67d6
Merge remote-tracking branch 'origin/master' into create-addRoutingOp…
artengin Apr 14, 2026
17ad6a7
Merge branch 'master' into generate-withSchedule
artengin Apr 14, 2026
dbb2703
Merge branch 'master' into create-addRoutingOption-method
artengin Apr 14, 2026
48fbb27
Merge branch 'master' into generate-withSchedule
artengin Apr 14, 2026
4c3c2fe
fix: testScheduleOptionDTOInvalidMethod
artengin Apr 14, 2026
7647f88
fix: testScheduleOptionDTOInvalidMethod
artengin Apr 14, 2026
2e17f0f
Merge remote-tracking branch 'origin/generate-withSchedule' into crea…
artengin Apr 17, 2026
5619273
Merge remote-tracking branch 'origin/refactor-architecture' into crea…
artengin Apr 17, 2026
c806788
fix: remarks
artengin Apr 17, 2026
0609ce7
fix: remarks
artengin Apr 19, 2026
f90a799
fix: remarks
artengin Apr 20, 2026
90dfe63
fix: remarks
artengin Apr 20, 2026
96d468a
feat: extend ScheduleOption validation to include Event scheduling me…
artengin Apr 20, 2026
821815b
docs: note automatic Schedule import in addScheduleCommand
artengin Apr 20, 2026
e5d10db
Merge remote-tracking branch 'origin/master' into generate-withSchedule
artengin May 7, 2026
5d9abb8
Merge branch 'master' into create-addRoutingOption-method
artengin May 7, 2026
9ada00c
fix: remarks
artengin May 7, 2026
a8f4ece
Merge remote-tracking branch 'origin/master' into generate-withSchedule
artengin Jun 5, 2026
ab48bc1
fix: remarks from reviewer
artengin Jun 5, 2026
2abe022
Merge branch 'master' into create-addRoutingOption-method
artengin Jun 19, 2026
4415785
Merge branch 'master' into generate-withSchedule
artengin Jun 19, 2026
043fac8
Merge remote-tracking branch 'origin/generate-withSchedule' into crea…
artengin Jun 26, 2026
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
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,49 @@ render for the passed exception class.

**Note** Need to provide the full exception class name (FQCN) to the method, it automatically imports it.

#### addScheduleCommand

Adds a scheduled command into the `withSchedule` method closure.
If `withSchedule` does not exist, it will be automatically created. The new scheduled command will then be inserted into its closure.
Automatically adds `use Illuminate\Support\Facades\Schedule;` to the file imports.

Example usage:

```php
new AppBootstrapBuilder()
->addScheduleCommand(
'command',
new ScheduleOptionDTO('environments', ['production']),
new ScheduleOptionDTO('daily'),
new ScheduleOptionDTO(
method: 'timezone',
attributes: ['America/New_York'],
),
)
->save();
```

#### addRoutingOption

Adds or updates a named argument in the `withRouting` method call. Supports all keys of Laravel's `withRouting` method.
If `withRouting` does not exist, it will be automatically created.

**Note:** Use `PreformattedExpression` for complex values like closures or PHP expressions.

Example usage:

```php
new AppBootstrapBuilder()
->addRoutingOption('api', new PreformattedExpression("__DIR__.'/../routes/api.php'"))
->addRoutingOption('apiPrefix', '')
->save();
```

## Contributing

Thank you for considering contributing to Laravel Builder package! The contribution guide
can be found in the [Contributing guide](CONTRIBUTING.md).

## License

Laravel Builder package is open-sourced software licensed under the [MIT license](LICENSE).
Laravel Builder package is open-sourced software licensed under the [MIT license](LICENSE).
24 changes: 23 additions & 1 deletion src/Builders/AppBootstrapBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

namespace RonasIT\Larabuilder\Builders;

use RonasIT\Larabuilder\Nodes\PreformattedExpression;
use RonasIT\Larabuilder\ValueOptions\ScheduleOption;
use RonasIT\Larabuilder\Visitors\AppBootstrapVisitors\AddExceptionsRender;
use RonasIT\Larabuilder\Visitors\AppBootstrapVisitors\AddRoutingOption;
use RonasIT\Larabuilder\Visitors\AppBootstrapVisitors\AddScheduleCommand;

class AppBootstrapBuilder extends PHPFileBuilder
{
Expand All @@ -15,7 +19,7 @@ public function addExceptionsRender(string $exceptionClass, string $renderBody,
{
$this->traverser->addVisitor(new AddExceptionsRender($exceptionClass, $renderBody, $includeRequestArg));

$imports = [$exceptionClass];
$imports = ['Illuminate\Foundation\Configuration\Exceptions', $exceptionClass];

Comment thread
artengin marked this conversation as resolved.
if ($includeRequestArg) {
$imports[] = 'Illuminate\Http\Request';
Expand All @@ -25,4 +29,22 @@ public function addExceptionsRender(string $exceptionClass, string $renderBody,

return $this;
}

public function addScheduleCommand(string $command, ScheduleOption ...$options): self
{
$this->traverser->addVisitor(new AddScheduleCommand($command, ...$options));

$this->addImports([
'Illuminate\Support\Facades\Schedule',
]);
Comment thread
artengin marked this conversation as resolved.

return $this;
}

public function addRoutingOption(string $key, string|PreformattedExpression $value): self
{
$this->traverser->addVisitor(new AddRoutingOption($key, $value));

return $this;
}
}
1 change: 1 addition & 0 deletions src/Enums/StatementAttributeEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ enum StatementAttributeEnum: string
case Parent = 'parent';
case Previous = 'previous';
case Comments = 'comments';
case WasCreated = 'wasCreated';
}
8 changes: 3 additions & 5 deletions src/Nodes/PreformattedCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,26 @@

namespace RonasIT\Larabuilder\Nodes;

use Illuminate\Support\Str;
use PhpParser\Error;
use PhpParser\Node\Stmt;
use PhpParser\ParserFactory;
use RonasIT\Larabuilder\Exceptions\InvalidPHPCodeException;
use RonasIT\Larabuilder\Traits\PreformattedNodesHelperTrait;

/**
* Used to insert code with saving original formatting
*/
class PreformattedCode extends Stmt
{
public readonly array $code;
use PreformattedNodesHelperTrait;

public function __construct(
public string $value,
public array $attributes = [],
) {
parent::__construct($this->attributes);

$this->value = Str::chopStart($this->value, '<?php');

$this->code = $this->parsePHPCode($this->value);
$this->initPreformattedNode($this->value);
}

public function getSubNodeNames(): array
Expand Down
33 changes: 33 additions & 0 deletions src/Nodes/PreformattedExpression.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace RonasIT\Larabuilder\Nodes;

use PhpParser\Node\Expr;
use RonasIT\Larabuilder\Traits\PreformattedNodesHelperTrait;

/**
* Used to insert expression code with saving original formatting
*/
class PreformattedExpression extends Expr
{
use PreformattedNodesHelperTrait;

public function __construct(
public string $value,
public array $attributes = [],
) {
parent::__construct($this->attributes);

$this->initPreformattedNode($this->value);
}

public function getSubNodeNames(): array
{
return ['value'];
}

public function getType(): string
{
return 'Expr_PreformattedExpression';
}
}
38 changes: 37 additions & 1 deletion src/Printer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\MethodCall;
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\StatementAttributeEnum;
use RonasIT\Larabuilder\Nodes\PreformattedCode;
use RonasIT\Larabuilder\Nodes\PreformattedExpression;

class Printer extends Standard
{
Expand Down Expand Up @@ -98,9 +100,19 @@ protected function shouldAddNewlineBeforeIfTypeSame(Node $node, string $type): b
return $previousNode !== null && $previousNode instanceof $type;
}

protected function pExpr_PreformattedExpression(PreformattedExpression $node): string
{
return $this->formatPreformattedCode($node->value);
}

protected function pStmt_PreformattedCode(PreformattedCode $node): string
{
$value = $this->preparePreformattedCode($node->value);
return $this->formatPreformattedCode($node->value);
}

private function formatPreformattedCode(string $value): string
{
$value = $this->preparePreformattedCode($value);

$indentLength = strspn($value, " \t");
$indent = substr($value, 0, $indentLength);
Expand All @@ -122,4 +134,28 @@ protected function preparePreformattedCode(string $value): string

return rtrim($value);
}

protected function pExpr_MethodCall(MethodCall $node): string
{
if ($node->getAttribute(StatementAttributeEnum::WasCreated->value)) {
$this->indent();

$args = $this->isMultilineMethodCall($node)
? $this->pCommaSeparatedMultiline($node->args, true) . $this->nl
: $this->pMaybeMultiline($node->args);

$newCall = $this->nl . '->' . $this->pObjectProperty($node->name) . '(' . $args . ')';

$this->outdent();

return $this->pDereferenceLhs($node->var) . $newCall;
}

return parent::pExpr_MethodCall($node);
}

protected function isMultilineMethodCall(MethodCall $node): bool
{
return $node->name->toString() === 'withRouting';
}
}
30 changes: 30 additions & 0 deletions src/Traits/PreformattedNodesHelperTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace RonasIT\Larabuilder\Traits;

use Illuminate\Support\Str;
use PhpParser\Node\Stmt\Nop;
use PhpParser\ParserFactory;

trait PreformattedNodesHelperTrait
{
public readonly array $code;

protected function initPreformattedNode(string &$value): void
{
$value = Str::chopStart($value, '<?php');

$this->code = $this->parsePHPCode($value);
}

protected function parsePHPCode(string $code): array
{
$nodes = new ParserFactory()->createForHostVersion()->parse("<?php\n{$code};");

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 Preserve InvalidPHPCodeException for bad snippets

When a caller passes invalid preformatted code, for example an invalid renderBody to addExceptionsRender(), this shared parser now lets PhpParser\Error escape instead of translating it to the package's InvalidPHPCodeException as PreformattedCode did before. That breaks the public error contract and the existing invalid-body test/callers that catch InvalidPHPCodeException; wrap this parse in the same catch before returning the nodes.

Useful? React with 👍 / 👎.


if (end($nodes) instanceof Nop) {
array_pop($nodes);
}
Comment thread
artengin marked this conversation as resolved.

return $nodes;
}
}
28 changes: 28 additions & 0 deletions src/ValueOptions/RoutingOption.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace RonasIT\Larabuilder\ValueOptions;

use Illuminate\Foundation\Configuration\ApplicationBuilder;
use InvalidArgumentException;
use ReflectionMethod;

readonly class RoutingOption
{
public function __construct(string $key)
{
$this->validateKey($key);
}

private function validateKey(string $key): void
{
$params = new ReflectionMethod(ApplicationBuilder::class, 'withRouting')->getParameters();

$allowed = array_map(fn ($param) => $param->getName(), $params);

if (!in_array($key, $allowed)) {
$list = implode("\n", $allowed);

throw new InvalidArgumentException("Unknown routing key `{$key}`.\nAllowed keys:\n{$list}");
}
}
}
42 changes: 42 additions & 0 deletions src/ValueOptions/ScheduleOption.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace RonasIT\Larabuilder\ValueOptions;

use Illuminate\Console\Scheduling\Event;
use Illuminate\Console\Scheduling\ManagesAttributes;
use Illuminate\Console\Scheduling\ManagesFrequencies;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionMethod;

readonly class ScheduleOption
{
public function __construct(
public string $method,
public array $arguments = [],
) {
$this->validateMethod($this->method);

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 Validate ScheduleOption argument arity during construction

ScheduleOption currently validates only method names, so options with required parameters can be constructed with empty arguments (e.g., new ScheduleOption('dailyAt') or new ScheduleOption('between')). The generator then emits calls like ->dailyAt() that are syntactically valid but fail with ArgumentCountError when Laravel evaluates the schedule callback. Adding required-parameter validation here would prevent generating broken bootstrap code.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The library is responsible for generating syntactically valid code, not for enforcing Laravel's runtime contracts. Ensuring correct arguments is the caller's responsibility, just as with any code generation tool.

}

private function validateMethod(string $method): void
{
$methods = array_merge(
$this->getMethods(ManagesAttributes::class),
$this->getMethods(ManagesFrequencies::class),
$this->getMethods(Event::class),
);
Comment thread
artengin marked this conversation as resolved.

if (!in_array($method, $methods, true)) {
$methods = implode("\n", $methods);

throw new InvalidArgumentException("Unknown schedule method `{$method}`.\nAllowed methods:\n{$methods}");
}
}

private function getMethods(string $class): array
{
$schedulePublicMethods = new ReflectionClass($class)->getMethods(ReflectionMethod::IS_PUBLIC);

return array_map(fn (ReflectionMethod $method) => $method->getName(), $schedulePublicMethods);
}
}
Loading