-
-
Notifications
You must be signed in to change notification settings - Fork 142
feat(generator/console): add make:controller, make:model commands and the generator attribute #568
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
Changes from all commits
5f09cad
8994764
ee44096
76063f4
1023fdc
a487fe2
8bb0f99
d9ee3d9
089f694
912f23e
7052427
3953165
c8737ad
1d7a424
a26ae6a
8ad5b76
03ad829
d81a302
b701dee
98c08e9
eddc8be
c8a483a
0849e0d
b88d67c
ecdfecb
6f94a6d
8a4dc32
c755523
4594bb6
872757c
9e2127a
fed3dae
5e5aebe
56e5b7d
e6d78b8
cf9de26
af2af0b
900a57f
925aa11
c396bff
6f45e99
39b0ca9
e3e9ce0
a3e8b84
82eabdb
3c94f12
2634717
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 |
|---|---|---|
|
|
@@ -150,5 +150,10 @@ | |
| "composer phpunit", | ||
| "composer phpstan" | ||
| ] | ||
| }, | ||
| "config": { | ||
| "allow-plugins": { | ||
| "php-http/discovery": true | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,39 +1,42 @@ | ||
| includes: | ||
| - phpstan-baseline.neon | ||
| - vendor/phpat/phpat/extension.neon | ||
| - vendor/spaze/phpstan-disallowed-calls/extension.neon | ||
| - phpstan-baseline.neon | ||
|
Member
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. Why these changes? |
||
| - vendor/phpat/phpat/extension.neon | ||
| - vendor/spaze/phpstan-disallowed-calls/extension.neon | ||
| services: | ||
| - | ||
| class: Tests\Tempest\Architecture\ArchitectureTestCase | ||
| tags: | ||
| - phpat.test | ||
| - | ||
| class: Tests\Tempest\Architecture\ArchitectureTestCase | ||
| tags: | ||
| - phpat.test | ||
| parameters: | ||
| level: 5 | ||
| tmpDir: .cache/phpstan | ||
| excludePaths: | ||
| - tests/Integration/View/blade/cache/**.php | ||
| paths: | ||
| - src | ||
| - tests | ||
| ignoreErrors: | ||
| level: 5 | ||
| tmpDir: .cache/phpstan | ||
| excludePaths: | ||
| - tests/Integration/View/blade/cache/**.php | ||
| paths: | ||
| - src | ||
| - tests | ||
| ignoreErrors: | ||
|
|
||
| - | ||
| message: '#.*#' | ||
| path: src/Tempest/Http/src/Exceptions/exception.php | ||
| - | ||
| message: '#.*exec*#' | ||
| path: src/Tempest/Console/src/Terminal/Terminal.php | ||
| - | ||
| message: '#.*#' | ||
| path: src/Tempest/Http/src/Exceptions/exception.php | ||
| - | ||
| message: '#.*exec*#' | ||
| path: src/Tempest/Console/src/Terminal/Terminal.php | ||
| - | ||
| message: "#^Tempest\\\\Console\\\\ConsoleCommand should be final$#" | ||
| path: src/Tempest/Console/src/ConsoleCommand.php | ||
|
|
||
| disallowedFunctionCalls: | ||
| - | ||
| function: 'exec()' | ||
| - | ||
| function: 'eval()' | ||
| - | ||
| function: 'dd()' | ||
| - | ||
| function: 'dump()' | ||
| - | ||
| function: 'phpinfo()' | ||
| - | ||
| function: 'var_dump()' | ||
| disallowedFunctionCalls: | ||
| - | ||
| function: 'exec()' | ||
| - | ||
| function: 'eval()' | ||
| - | ||
| function: 'dd()' | ||
| - | ||
| function: 'dump()' | ||
| - | ||
| function: 'phpinfo()' | ||
| - | ||
| function: 'var_dump()' | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,12 +5,15 @@ | |
| namespace Tempest\Console\Actions; | ||
|
|
||
| use Closure; | ||
| use RuntimeException; | ||
| use Tempest\Console\ConsoleConfig; | ||
| use Tempest\Console\ConsoleInputBuilder; | ||
| use Tempest\Console\ExitCode; | ||
| use Tempest\Console\GeneratorCommand; | ||
| use Tempest\Console\Initializers\Invocation; | ||
| use Tempest\Console\Input\ConsoleArgumentBag; | ||
| use Tempest\Container\Container; | ||
| use Tempest\Reflection\MethodReflector; | ||
|
|
||
| final readonly class ExecuteConsoleCommand | ||
| { | ||
|
|
@@ -36,17 +39,19 @@ private function getCallable(array $commandMiddleware): Closure | |
| { | ||
| $callable = function (Invocation $invocation) { | ||
| $consoleCommand = $invocation->consoleCommand; | ||
|
|
||
| $handler = $consoleCommand->handler; | ||
|
|
||
| $consoleCommandClass = $this->container->get($handler->getDeclaringClass()->getName()); | ||
|
|
||
| $inputBuilder = new ConsoleInputBuilder($consoleCommand, $invocation->argumentBag); | ||
|
|
||
| $consoleCommand->handler->invokeArgs( | ||
| $consoleCommandClass, | ||
| $inputBuilder->build(), | ||
| ); | ||
| $handler = ($consoleCommand instanceof GeneratorCommand) | ||
|
Member
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. I'm not sure this is the right way to go. I'd be very hesitant making changes within I still need to read through the rest though |
||
| ? $consoleCommand->makeHandler() | ||
| : $consoleCommand->handler; | ||
|
|
||
| match (true) { | ||
| is_callable($handler) => $handler($inputBuilder->build()), | ||
| ($handler instanceof MethodReflector) => $handler->invokeArgs( | ||
| $this->container->get($handler->getDeclaringClass()->getName()), | ||
| $inputBuilder->build() | ||
| ), | ||
| default => throw new RuntimeException('Command handler cannot be resolved.'), // @phpstan-ignore-line | ||
| }; | ||
|
|
||
| return ExitCode::SUCCESS; | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Tempest\Console\Commands\Generators; | ||
|
|
||
| use Tempest\Console\ConsoleArgument; | ||
| use Tempest\Console\GeneratorCommand; | ||
| use Tempest\Console\Stubs\ControllerStub; | ||
| use Tempest\Generation\HasGeneratorCommand; | ||
| use Tempest\Generation\StubFileGenerator; | ||
|
|
||
| final class MakeControllerCommand | ||
| { | ||
| use HasGeneratorCommand; | ||
|
|
||
| #[GeneratorCommand( | ||
| name : 'make:controller', | ||
| description: 'Create a new controller class with a basic route', | ||
| aliases : ['controller:make', 'controller:create', 'create:controller'], | ||
| )] | ||
| public function __invoke( | ||
| #[ConsoleArgument( | ||
| help: 'The name of the controller class to create ( "Controller" will be suffixed )', | ||
| )] | ||
| string $className, | ||
| ?string $controllerPath = null, | ||
| ?string $controllerView = null, | ||
| ): StubFileGenerator { | ||
| $suggestedPath = $this->getSuggestedPath( | ||
| className : $className, | ||
| pathPrefix : 'Controllers', | ||
| classSuffix: 'Controller', | ||
| ); | ||
| $targetPath = $this->promptTargetPath($suggestedPath); | ||
| $shouldOverride = $this->askForOverride($targetPath); | ||
|
|
||
| return new StubFileGenerator( | ||
|
Member
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. We talked about this briefly on Discord: I'm not convinced yet that we need a special kind of console commands that return a special kind of class. I rather just inject the generator into the console command. Take, for example, a look at how the install command works: it doesn't need to return anything special, but rather passes the responsibility of actually installing a package to the underlying |
||
| stubFile : ControllerStub::class, | ||
| targetPath : $targetPath, | ||
| shouldOverride: $shouldOverride, | ||
| replacements : [ | ||
| 'dummy-path' => $controllerPath, | ||
| 'dummy-view' => $controllerView, | ||
| ], | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Tempest\Console\Commands\Generators; | ||
|
|
||
| use Tempest\Console\ConsoleArgument; | ||
| use Tempest\Console\GeneratorCommand; | ||
| use Tempest\Console\Stubs\DatabaseModelStub; | ||
| use Tempest\Console\Stubs\ModelStub; | ||
| use Tempest\Generation\HasGeneratorCommand; | ||
| use Tempest\Generation\StubFileGenerator; | ||
|
|
||
| final class MakeModelCommand | ||
| { | ||
| use HasGeneratorCommand; | ||
|
Member
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 trait should be renamed to |
||
|
|
||
| #[GeneratorCommand( | ||
| name : 'make:model', | ||
| description: 'Create a new model class', | ||
| aliases : ['model:make', 'model:create', 'create:model'], | ||
| )] | ||
| public function __invoke( | ||
| #[ConsoleArgument( | ||
| help: 'The name of the model class to create ( "Model" will be suffixed )', | ||
| )] | ||
| string $className, | ||
| #[ConsoleArgument( | ||
| help: 'Whether the model is a database model', | ||
| )] | ||
| bool $isDatabaseModel = false, | ||
| ): StubFileGenerator { | ||
| $suggestedPath = $this->getSuggestedPath( | ||
| className : $className, | ||
| pathPrefix : 'Models', | ||
| classSuffix: 'Model', | ||
| ); | ||
| $targetPath = $this->promptTargetPath($suggestedPath); | ||
| $shouldOverride = $this->askForOverride($targetPath); | ||
|
|
||
| return new StubFileGenerator( | ||
| stubFile : $isDatabaseModel ? DatabaseModelStub::class : ModelStub::class, | ||
| targetPath : $targetPath, | ||
| shouldOverride: $shouldOverride, | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
|
|
||
| use Tempest\Console\ConsoleCommand; | ||
| use Tempest\Console\ConsoleConfig; | ||
| use Tempest\Console\GeneratorCommand; | ||
| use Tempest\Container\Container; | ||
| use Tempest\Core\Discovery; | ||
| use Tempest\Core\HandlesDiscoveryCache; | ||
|
|
@@ -41,7 +42,7 @@ public function createCachePayload(): string | |
|
|
||
| public function restoreCachePayload(Container $container, string $payload): void | ||
| { | ||
| $commands = unserialize($payload, ['allowed_classes' => [ConsoleCommand::class, MethodReflector::class]]); | ||
| $commands = unserialize($payload, ['allowed_classes' => [GeneratorCommand::class, ConsoleCommand::class, MethodReflector::class]]); | ||
|
Member
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 is one of the reasons I'm hesitant about "a special kind of command class": we need several changes all over the place to make sure they work. That leads to a brittle and complicated design. |
||
|
|
||
| $this->consoleConfig->commands = $commands; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Tempest\Console; | ||
|
|
||
| use Attribute; | ||
| use Closure; | ||
| use Tempest\Generation\StubFileGenerator; | ||
| use function Tempest\get; | ||
| use function Tempest\Support\arr; | ||
|
|
||
| /** | ||
| * Defines a console command that is specifically for generating files. | ||
| */ | ||
| #[Attribute(Attribute::TARGET_METHOD)] | ||
| final class GeneratorCommand extends ConsoleCommand | ||
| { | ||
| /** | ||
| * Make the handler for the given command. | ||
| * This allow the console to run this handler without altering the command structure. | ||
| * | ||
| * @return Closure(array<mixed> $params) The command handler. | ||
| */ | ||
| public function makeHandler(): Closure | ||
| { | ||
| return function (array $params): void { | ||
| // Resolve all generators and run them. | ||
| arr( | ||
| $this->handler->invokeArgs( | ||
| get($this->handler->getDeclaringClass()->getName()), | ||
| $params | ||
| ) | ||
| ) | ||
| ->filter(fn ($generator) => $generator instanceof StubFileGenerator) | ||
| ->each(fn (StubFileGenerator $generator) => $generator->generate()); | ||
| }; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Tempest\Console\Stubs; | ||
|
|
||
| use Tempest\Http\Get; | ||
| use function Tempest\view; | ||
| use Tempest\View\View; | ||
|
|
||
| final class ControllerStub | ||
| { | ||
| #[Get(uri: '/dummy-path')] | ||
| public function __invoke(): View | ||
| { | ||
| return view('dummy-view'); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Tempest\Console\Stubs; | ||
|
|
||
| use Tempest\Database\DatabaseModel; | ||
| use Tempest\Database\IsDatabaseModel; | ||
| use Tempest\Validation\Rules\Length; | ||
|
|
||
| final class DatabaseModelStub implements DatabaseModel | ||
| { | ||
| use IsDatabaseModel; | ||
|
|
||
| public function __construct( | ||
| #[Length(min: 1, max: 120)] | ||
| public string $title | ||
| ) { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Tempest\Console\Stubs; | ||
|
|
||
| use Tempest\Validation\Rules\Length; | ||
|
|
||
| final class ModelStub | ||
| { | ||
| public function __construct( | ||
| #[Length(min: 1, max: 120)] | ||
| public string $title | ||
| ) { | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you remove this? To make sure it doesn't get added again, rebase or merge main into your branch, then run
composer uponce more.