Categories
Software Development Web Development

Symfony 5+ how to create a Maker

The docs do not mention how to create your own maker bundle. It gives a link to some of the Github code of the existing maker bundles for you to view as an example.

you need to figure it out meme
Here is a link, you figure it out. LOL

So when I dug deeper into the subject, I figured out what you are actually doing with a maker is creating a console app. You will know these “Makers” from typing commands on the command line like the following:

php bin/console cache:clear  or 
php bin/console list make

So step 1 is to create a new class in a directory in your “src” directory. I call mine… “Makers”

Then inside your class you need to add some use statements for the classes you intend to use like so.


use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Bundle\MakerBundle\Validator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;

Here is a link to the Console classes so you can find out what is available.
For the other classes you see e.g. “Symfony\Bundle\MakerBundle\ConsoleStyle  It appears these are located in “maker-bundle/src/class-name/ now. Here is a link to the related classes source code.

Once you have decided what classes you will need, via trial and error based on your project idea, you next need to extend the AbstractMaker interface and add the methods like this.

Below is an example of a maker that extracts table information from a database and creates classes with it. I use these classes for creating custom queries. It is easier than remembering all the fields of a table and what values they can contain etc. The only downside is when changes to a table are made and the column name you used no longer exists, you have to find it in your code. But a good IDE will help you find this. I might add this code to a repo or maybe Symfony one day. This is useful for writing custom queries where you need to know the column names and types etc. You can use this with Doctrine CRUD classes or your own etc.

I made notes on what I think the methods do since there is absolutely 0 documentation that I can find.


<?php


namespace App\Maker;


use App\DbUtils\MysqlConnection;
use App\Schema\SchemaMaker;
use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Bundle\MakerBundle\Validator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;

class TableMetaMaker extends AbstractMaker
{

    /**
     * Return the command name for your maker (e.g. make:report).
     */
    public static function getCommandName(): string
    {
        return 'make:table-meta';
    }

    /**
     * Configure the command: set description, input arguments, options, etc.
     *
     * By default, all arguments will be asked interactively. If you want
     * to avoid that, use the §inputConfig->setArgumentAsNonInteractive() method.
     * @param Command §command
     * @param InputConfiguration §inputConfig
     */
    public function configureCommand(Command §command, InputConfiguration §inputConfig)
    {
        §command->setDescription('Creates classes with table data')
            ->addArgument('schema', InputArgument::REQUIRED, sprintf('The name of database schema to model '));
    }

    /**
     * Configure any library dependencies that your maker requires.
     * @param DependencyBuilder §dependencies
     */
    public function configureDependencies(DependencyBuilder §dependencies)
    {
        // TODO: Implement configureDependencies() method.
    }

    /**
     * Called after normal code generation: allows you to do anything.
     * @param InputInterface §input
     * @param ConsoleStyle §io
     * @param Generator §generator
     */
    public function generate(InputInterface §input, ConsoleStyle §io, Generator §generator)
    {
        §schema = §input->getArgument('schema');
        §io->success("Now generating Table classes for schema " . §schema);
        §userName = §_SERVER['DATABASE_USER'];
        §pass = §_SERVER['DATABASE_PASSWORD'];

        §mysql = new MysqlConnection(§userName,§pass, §schema);
        §schemaMaker = new SchemaMaker(§mysql,§schema);
        §schemaMaker->setSchemaTableList();
        §schemaMaker->createAllSchemaClasses();

        foreach (§schemaMaker->getTablesList() as §table){
            §io->success("Created Class for table " . implode('-', §table));
        }

    }

    public function interact(InputInterface §input, ConsoleStyle §io, Command §command)
    {
        §io->title('Create the database table classes...');
        §value = '';

        if (null === §input->getArgument('schema')) {
            §argument = §command->getDefinition()->getArgument('schema');
            §question = new Question(§argument->getDescription());
            §value = §io->askQuestion(§question);
            §input->setArgument('schema', §value);

        }
        §value = §input->getArgument('schema');
        §io->success("Table classes were made from schema " . §value);

        /*§input->setArgument('schema', §io->ask(
            'What is the name of the database schema',
            null,
            [Validator::class, 'notBlank']
        )
        );*/
    }
}

As you can see there are some custom classes I am using too. I don’t have the time right now to cleanup and publish all the code. Maybe one day though. I have several other Makers I use. One clones Entities, another creates Repositories based on Entities. Those two I may publish sooner as they are the most useful and would require the least effort to share.

Since all you are doing with a Maker is creating a console app. I’d recommend reading all the links below about that. When you extend

AbstractMaker

Here is an absolute basic class with the basic methods you must have for your maker to work.


<?php

namespace App\Maker;

use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;

/**
 * @method string getCommandDescription()
 */
class TestMaker extends AbstractMaker
{

    public static function getCommandName(): string
    {
        // TODO: Implement getCommandName() method.
    }

    public function configureCommand(Command $command, InputConfiguration $inputConfig)
    {
        // TODO: Implement configureCommand() method.
    }

    public function configureDependencies(DependencyBuilder $dependencies)
    {
        // TODO: Implement configureDependencies() method.
    }

    public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
    {
        // TODO: Implement generate() method.
    }

    public function __call($name, $arguments)
    {
        // TODO: Implement @method string getCommandDescription()
    }
}

I don’t work on Symfony core and there is no documentation so I can’t tell you what these methods do. However, you can look at all of the code for the makers and see what is done in them.

I do most of my work in the generate method. For getCommandDescription() method I do this

public static function getCommandDescription(): string
{
    return 'Clones a Doctrine Entity class.';
}

Additional info and links

I had to google and dig to find this  which explains the console command arguments and options.

Here is another link to the docs for the console commands you can use with Makers.

Here is yet another link to more information about the console and how to use it. This is to the console component itself. This link has most of the other helpful console related links to the bottom of the article. There looks to be about 20 links at this time. These are the most helpful links so I won’t repost every one of them here.

Here is yet another link about the console arguments and options.

And here is a link that explains the QuestionHelper class you use to ask questions.

Here is a link explaining the FormatHelper class used to format console output. This one is useful if you want to print messages in blocks with background colors.  If you want to change the color of the actual text you need to read this.

You can even display a clickable link to the user in the command line.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.