Skip to content
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

Idea: console-extra-bundle, to provide a make:invokable-command #31

Open
tacman opened this issue Aug 21, 2022 · 15 comments
Open

Idea: console-extra-bundle, to provide a make:invokable-command #31

tacman opened this issue Aug 21, 2022 · 15 comments

Comments

@tacman
Copy link
Contributor

tacman commented Aug 21, 2022

What a cool library!

Sometimes instead of loading fixtures, I want a simple command that let's me create/update an object. For example:

bin/console make:book --title "Rust" --isbn "29294-42" --author "..."

Obviously, I can create that now using Symfony's make:command, but I like how tight the code is using console-extra.

Perhaps Symfony 7 will incorporate this structure into make:command, but in the meantime, it'd be nice to have something that facilitated create it.

@kbond
Copy link
Member

kbond commented Aug 21, 2022

Can you describe more what you're looking for from this library?

@tacman
Copy link
Contributor Author

tacman commented Aug 21, 2022

From this library (console-extra), I don't have any specific things I'm looking for, though the V2 list #29 looks good.

From the bundle, I'm just thinking of something that acts like make:entity, but generates an invokable controller.

So bin/console make:invokable-controller create:book --description "Creates or updates a book in the database" --arg book --arg isbn --arg author would generate

#[AsCommand('create:book', 'Creates or updates a book in the database')]
final class CreateBookCommand extends InvokableServiceCommand
{
    use ConfigureWithAttributes;

    public function __invoke(
        IO $io,


        #[Argument]
        string $author,

        #[Argument]
        string $isbn,

        #[Argument]
        string $author,

    ): void {

        $io->success('Finished');
    }
}

Maybe instead of --arg, it could be --string, --array, etc.

Just brainstorming.

@tacman
Copy link
Contributor Author

tacman commented Aug 21, 2022

Of course, I'd want to use the model factories from zenstruck/foundry.

@kbond
Copy link
Member

kbond commented Aug 22, 2022

Oh ok, you refer to a maker for an invokable controller (make:invokable-controller) but you actually mean an invokable command maker (make:invokable-command).

Indeed, I think this is a good idea. I have an open issue related to this: #3.

To start, maybe make:invokable-command --arg book --arg isbn --arg author --option foo would generate string properties for the --arg's and bool properties for the --options as that's probably the most common types. We could then possibly add --arg-array, --option-array, etc...

Trying to think how best to wire such a maker. This library doesn't include a bundle so it seems a bit much to create a bundle just for wiring the maker in dev. What about a contrib-recipe that wires the maker service?

@tacman tacman changed the title Idea: console-extra-bundle, to provide a make:invokable-controller command Idea: console-extra-bundle, to provide a make:invokable-command Aug 22, 2022
@tacman
Copy link
Contributor Author

tacman commented Aug 22, 2022

What about a contrib-recipe that wires the maker service?

I'm not sure how that would work. contrib-recipe is one of the few parts of the Symfony ecosystem that I dislike. But I'm sure there's a solution there.

Alternatively, there could be a make-invokable-bundle, that would provide makers for both controllers and commands.

One maker that's been missing (IMHO) is a make:dto. Given your experience with make:factory, it seems like there's some overlap. IF you were to create something like that, then perhaps you could have a zenstruck-maker-bundle for all of these.

OR, if Foundry is not specific to Model Factories, you could move bundle to there, since it's already got the maker and such.

Again, just brainstorming.

@tacman
Copy link
Contributor Author

tacman commented Sep 30, 2022

I took a stab at this in my own maker-bundle. Feedback welcome.

symfony new --webapp make-invokable-command-demo && cd make-invokable-command-demo 
composer req survos/maker-bundle --dev
bin/console survos:make:command app:test "Just a silly test"  --arg name  --arg code --oint-arg size --obool-arg force

Generates

<?php

namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Zenstruck\Console\Attribute\Argument;
use Zenstruck\Console\Attribute\Option;
use Zenstruck\Console\ConfigureWithAttributes;
use Zenstruck\Console\IO;
use Zenstruck\Console\InvokableServiceCommand;
use Zenstruck\Console\RunsCommands;
use Zenstruck\Console\RunsProcesses;

#[AsCommand('app:test', 'Just a silly test')]
final class AppTestCommand extends InvokableServiceCommand
{
use ConfigureWithAttributes, RunsCommands, RunsProcesses;

public function __invoke(
IO $io,

// custom injections
// UserRepository $repo,

// expand the arguments and options
#[Argument(description: 'string')]
string $name,
#[Argument(description: 'string')]
string $code,
#[Argument(description: '?int')]
?int $size,
#[Argument(description: '?bool')]
?bool $force,

#[Option(name: 'role', shortcut: 'r')]
array $roles,

): void {

   $io->note(sprintf("string %s %s", 'name', $name) ?? 'null');
   $io->note(sprintf("string %s %s", 'code', $code) ?? 'null');
   $io->note(sprintf("?int %s %s", 'size', $size) ?? 'null');
   $io->note(sprintf("?bool %s %s", 'force', $force) ?? 'null');

// $this->runCommand('another:command');
// $this->runProcess('/some/script');

$io->success('app:test success.');
}

}

And works as expected:

 bin/console app:test --help
Description:
  Just a silly test

Usage:
  app:test [options] [--] <name> <code> [<size> [<force>]]

Arguments:
  name                  string
  code                  string
  size                  ?int
  force                 ?bool

Options:
  -r, --role=ROLE        (multiple values allowed)
  -h, --help            Display help for the given command. When no command is given display help for the list command
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi|--no-ansi  Force (or disable --no-ansi) ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -e, --env=ENV         The Environment name. [default: "dev"]
      --no-debug        Switch off debug mode.
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
tac@pop-os:~/survos/play/make-invokable-command-demo$ 
tac@pop-os:~/survos/play/make-invokable-command-demo$ bin/console app:test "Bob Smith" smith_robert 15 

 ! [NOTE] string name Bob Smith                                                                                         

 ! [NOTE] string code smith_robert                                                                                      

 ! [NOTE] ?int size 15                                                                                                  

 ! [NOTE] ?bool force                                                                                                   

                                                                                                                        
 [OK] app:test success.                                                                                                 
                                                                

Still a work in progress. Creating the options from the command line isn't implemented yet, and still some polish to do.

@kbond
Copy link
Member

kbond commented Sep 30, 2022

Cool! I like this.

What do you think about the following syntax for arguments (special type prefix - defaults to string)?

bin/console make:invokable-command app:test name code ?int:size ?bool:force --description="Just a silly test"

For args:

  • name: string $name
  • ?name: ?string $name
  • ?int:name: ?int $name
  • name[]: array $name

We could do something similar for options.

Additional, making it interactive like make:entity would be very useful I think!

@tacman
Copy link
Contributor Author

tacman commented Sep 30, 2022 via email

@kbond
Copy link
Member

kbond commented Sep 30, 2022

I wonder if ?bool should be handled differently, since it's really a flag

Good point!

@kbond
Copy link
Member

kbond commented Sep 30, 2022

I do want to deprecate/remove docblock/@command syntax, but this might be helpful to determine the syntax for a maker.

@tacman
Copy link
Contributor Author

tacman commented Oct 2, 2022

I've played around some more, this is now working, though the internals are kinda ugly.

bin/console survos:make:command app:greet \
  string:message:"What do you want to say?" \
  array:names:"Who do you want to greet (separate multiple names with a space)?" \
  int:iterations-i?:"How many times should the message be printed?" \
  array:colors?:"Which colors do you like?" \
  bool:yell?

bin/console app:greet --help
Usage:
  app:greet [options] [--] <message> <names>...

Arguments:
  message                      What do you want to say?
  names                        Who do you want to greet (separate multiple names with a space)?

Options:
  -i, --iterations=ITERATIONS  How many times should the message be printed?
      --colors=COLORS          Which colors do you like? (multiple values allowed)
      --yell                   (bool)

The syntax rules

type // If a ? preceeds the type, then it's an optional argument
:
name // if the name ends in ?, then it's an option.  If the name has a hyphenated suffix, use that is the shortcut.
:
description

I played around with a few ways to do default, I think the best way is to parse an = in the name, e.g.

  int:iterations-i?=7:"How many times should the message be printed?" \

@tacman
Copy link
Contributor Author

tacman commented Oct 3, 2022

Additional, making it interactive like make:entity would be very useful I think!

This is how I incorporate make:entity into a script with multiple fields:

cat <<'EOF' | sed "s/:/\n/g"  |  bin/console make:entity Country
name:string:55:no
alpha2:string:2:no
alpha3:string:3:yes
EOF

I don't love it, I prefer the approach we've been working on here, but perhaps the better solution is to implement the interactive approach like make:entity and use this pipe approach.

@kbond
Copy link
Member

kbond commented Oct 3, 2022

I think, at the end of the day, an ugly syntax/internals is ok because the expectation would be to use the maker interactively.

@tacman
Copy link
Contributor Author

tacman commented Oct 3, 2022

Yeah, my use case is a bit funky -- I want to have a single bash script that can create a complete application (and related tutorial), without ever opening an IDE.

So the workflow I'm playing with now, for creating a demo application of a bundle I'm working on, is

  • symfony new ...
  • set the database url
  • composer req [various bundles]
  • composer req survos:maker:bundle etc. --dev
  • make the entity classes
  • make an import service (that loads the entities)
  • make an invokable command line
  • populate the __invoke method of the command, injecting the services and use statement
  • make a controller
  • populate the controller method, route, twig path, etc. and inject services and use statement
  • create the twig template
  • repeat the steps above
  • start the symfony server

The primary reason I want to do this from symfony new to symfony run entirely from the command line is because often I can't figure out how to use a bundle from within my application. Often demos don't work with the latest version of Symfony or PHP, so before I even get started I'm running rector and composer recipes:update and the like, tweaking security.yaml and doctrine_migrations.yaml, etc.

Anyway, not particularly relevant, but I quite like the elegance of your invokable controllers (and your twig components, but that's a topic for another place).

@kbond
Copy link
Member

kbond commented Oct 3, 2022

Ok, yeah, we've been playing around with the idea of scaffolds that would be provided by either the maker-bundle or flex itself (as a special type of recipe). Sounds similar to what you're describing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants