Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

MakerBundle

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

For our last trick, I want to introduce a bundle that's going to make our life awesome. And, for the first time, we are going to hook into Symfony.

Installing Maker Bundle

First, find your terminal, and install that bundle:

composer require "maker:^1.35" --dev

Yep! That's a Flex alias for symfony/maker-bundle. And, in this case, "make" means - "make your life easier by generating code".

We know that the main purpose of a bundle is to give us services. And, that's true in this case too... but the purpose of these services isn't for us to use them directly, like in our controller. Nope, the purpose of these services is that they give us new bin/console commands:

php bin/console

Nice! About 10 new commands, capable of generating all kinds of things. And, more make commands are still being added.

Generating a new Command

So... let's try one! Let's use the MakerBundle to create our very own, custom bin/console command. Use:

php bin/console make:command

This will ask us for a command name - how about article:stats - we'll create a command that will return some stats about an article. And... it's done! We now have a shiny new src/Command/ArticleStatsCommand.php file. Open it!

... lines 1 - 2
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class ArticleStatsCommand extends Command
{
protected static $defaultName = 'article:stats';
protected function configure()
{
$this
->setDescription('Add a short description for your command')
->addArgument('arg1', InputArgument::OPTIONAL, 'Argument description')
->addOption('option1', null, InputOption::VALUE_NONE, 'Option description')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$argument = $input->getArgument('arg1');
if ($input->getOption('option1')) {
// ...
}
$io->success('You have a new command! Now make it your own! Pass --help to see your options.');
}
}

Hey! It even added some example code to get us started! Run:

php bin/console

And on top... yes! Symfony already sees our new article:stats command. Sweet! Um... so... let's try it!

php bin/console article:stats

It doesn't do much... yet - but it's already working.

Service autoconfigure

But... how does Symfony already know about this new command? I mean, is it scanning all of our files looking for command classes? Actually, no! And that's a good thing - that would be super slow!

Here's the answer. Remember: all of our classes in src/ are loaded as services:

... lines 1 - 5
services:
... lines 7 - 22
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests}'
... lines 28 - 37

Notice that our new class extends Symfony's base Command class:

... lines 1 - 4
use Symfony\Component\Console\Command\Command;
... lines 6 - 11
class ArticleStatsCommand extends Command
... lines 13 - 35
}

When the service was registered, Symfony noticed this and made sure that it included it as a command. This nice feature has a name - autoconfigure. It's not too important, but just like autowiring, this is activated thanks to a little bit of config in our services.yaml file:

... lines 1 - 5
services:
# default configuration for services in *this* file
_defaults:
... line 9
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
... lines 11 - 37

It's just another way that you can avoid configuration, and keep working!

Next, let's have fun and make our command much more awesome!

Leave a comment!

20
Login or Register to join the conversation

Getting this error:

"Your requirements could not be resolved to an installable set of packages.

Problem 1
- Root composer.json requires symfony/maker-bundle ^1.43 -> satisfiable by symfony/maker-bundle[v1.43.0].
- symfony/maker-bundle v1.43.0 requires symfony/config ^5.4.7|^6.0 -> found symfony/config[v5.4.7, v5.4.8, v5.4.9, v6.0.0, ..., v6.1.0] but the package is fixed to v4.4.41 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.

Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.
You can also try re-running composer require with an explicit version constraint, e.g. "composer require symfony/maker-bundle:*" to figure out if any version is installable, or "composer require symfony/maker-bundle:^2.1" if you know which you need.

Installation failed, reverting ./composer.json and ./composer.lock to their original content."

Reply

Hey Guilherme,

Yeah, looks like the latest MakerBundle version requires a newer Symfony version that installed in the course code - we use Symfony 4.0 there. Try to limit MakerBundle to ^1.35 at least, i.e. run this command instead:

$ composer require "maker:^1.35"

This should install the Maker, though not the latest version of it. For the latest version you would need to upgrade installed Symfony packages first. But 1.35 version of maker is totally fine for this tutorial - on the time we record this tutorial - we used v1.0.2 :)

Cheers!

Reply

I installed with $ compose require maker:* (it installs the latest "possible" version right?) it installed v1.39, it's working so I guess it's fine? Is it a good practice to use the wildcard package:* in this situations where you have locked versions of dependencies? (feel free to correct anything I said, I'm learning). Thank you for your answer my friend!

Reply

Hey Guilherme,

If that * worked for you - great! You probably have a bit fresher Symfony version than we do in the course code. About using it every time - I'd not recommend this. You should just execute "composer require maker" and if it failed because of some incompatibility - try that * wildcard then. But better, look at the error message and try to figure out why exactly it's failing.

I hope this helps!

Cheers!

Reply
Gaetano S. Avatar
Gaetano S. Avatar Gaetano S. | posted 2 years ago

Hi,

before starting the Forms course I would like to understand how to get hearts with the best practise 'Getting Services from the Service Container'.
I suppose that I have to create a service in directory service and then using EntityManagerInterface to get hearts number. Is this the right way?
Thanks for your help.

Reply

Hey Gaetano S.

You can create an EntityRepository that will encapsulate the query logic for fetching the total hearts. Then in a controller you can just inject that repository and use it. Or, if you want to have a layer between your repositories and controllers, then you can create a service class that will call the repository for you

Cheers!

Reply
Gaetano S. Avatar

Hello again :),
what do yo think about this way?
And then I inject this service in ArticleStatsCommand.

Thanks a lot for your advice.


namespace App\Service;

use App\Entity\Article;
use Doctrine\ORM\EntityManagerInterface;

class HeartHelper
{
/**
* @var EntityManagerInterface
*/
private $entityManager;

/**
* HeartHelper constructor.
* @param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}

public function getHeartNumber(string $slug) {
$article = $this->entityManager->getRepository(Article::class)->findOneBy(['slug' => $slug]);
return $article->getHeartCount();
}
}

Reply

That's not bad but I have a couple of tips :)

1) Since Symfony4 you can inject Repositories, you don't have to get them through the EntityManager (Although you still can)
2) You need to check that the $article was found, in other words, it's not null because you are fetching by slug field, it may not exist

Cheers!

Reply
Gaetano S. Avatar

ok, now I understand better. Inside the function execute of my articlestatscommand I inject ArticleRepository and I call my Articlerepository method .
This is my function inside ArticleRepository:

public function getHeartNumberBySlug($slug) {
$article = $this->findOneBy(['slug' => $slug]);
if(!$article)
{
return 'No article found';
}
return $article->getHeartCount();
}

Thanks for your help.

1 Reply
Michal S. Avatar
Michal S. Avatar Michal S. | posted 3 years ago

As I am watching it, the maker can now create the entire CRUD for Doctrine Entity. Time to say goodbye to CakePHP.

Reply

Hey Michal S. ,

Yay, our friend Vladimir contributed that! https://symfony.com/blog/ne...

But did you wait only for CRUD generator so far before saying goodbye to CakePHP? It looks like the most wanted feature for you :p

Cheers!

Reply
Michal S. Avatar

CRUD generator IS one of the CakePHP's amazing superpowers Symfony lacked. To be completely honest, it is still difficult to say goodbye to CakePHP, as it is soooooo intuitive thanks to the convention over configuration approach, like not having to call neither the entity manager nor the template but still having them was amazing.

Having said that, Symfony feels much less like a great toy but more like a mature tool which makes it a go-to choice as the projects become more advanced. In short, I absolutely love Symfony, but leaving CakePHP does feel a lot like an actual break-up. And getting options such as CRUD generator just makes the transition easier. So while it is not the deal-breaker, it helps to get in Symfony what I had in Cake (aside from the Symfony-specific features like autowiring which is CRAZY in a good way).

Also, a shout out to Vladimir - you are the man!

Reply

Hey Michal S.

Thanks for the good words! I'm appreciate that you liked CRUD generator.

BTW I always wondered if someone use it :p

Cheers!

Reply

Hey Michal S. ,

I think it's just more matter of habit, if you get used to something, other workflows may seem weird at the first sight, though I have never used CakePHP so it's difficult to compare for me :) Anyway, Symfony is getting much more thin with the new Symfony Flex, that's a crazy cool. ;)

Cheers!

Reply
Mike P. Avatar
Mike P. Avatar Mike P. | posted 3 years ago

Wouldn't it make sense to install maker bundle via --dev flag? Because we only need to create files with make: in dev env?

Reply

Hey Mika,

Yes, Ryan just missed it. We totally should install Maker for dev only... and we've already fixed it in scripts and added a note in the video.

Cheers!

Reply
Ray D. Avatar

A quick note for those who may be using Windows10 PowerShell ISE.

For whatver reason, it appears, at least on my system, that while in the ISE,the make:command doesn't execute properly and leaves you hanging at the "Choose a command name (e.g. app:some-thing):" prompt without allowing you to enter the command name.

The good news, if you use the standalone PowerShell, this is not an issue.

Reply

Hey Raymond,

Thanks for this notice. I personally haven't heard about this problem before. It makes sense to upgrade to the latest version of MakerBundle. If you still have this issue, feel free to open a bug in its repo with detailed steps to reproduce.

Cheers!

Reply
Ray D. Avatar

I'm honestly not sure if this is a problem wiht MakerBundle, or with PowerShell ISE. I'm more inclined to blame ISE as it is working fine in a standalone PowerShell window

Reply

Yeah, probably so. Not sure what can you do then, probably that PowerShell ISE has some kind of configuration where this can be fixed, do not know :/

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.1.4
        "symfony/asset": "^4.0", // v4.0.4
        "symfony/console": "^4.0", // v4.0.14
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/framework-bundle": "^4.0", // v4.0.14
        "symfony/lts": "^4@dev", // dev-master
        "symfony/twig-bundle": "^4.0", // v4.0.4
        "symfony/web-server-bundle": "^4.0", // v4.0.4
        "symfony/yaml": "^4.0" // v4.0.14
    },
    "require-dev": {
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.4
        "symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
        "symfony/dotenv": "^4.0", // v4.0.14
        "symfony/maker-bundle": "^1.0", // v1.0.2
        "symfony/monolog-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.4
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.0.4
    }
}