Symfony Bundleless Service Definition Validation

You have decided to go bundleless in your project.
You also want to still use Matthias Noback’s nice library that helps you validating the service definitions in the container, whether your project is symfony or based just on the symfony dependency injection component, you can still plug this in. Here is how I did it in our open source project Matthew-7-12 project.

<?php
 
namespace Grace;
 
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Matthias\SymfonyServiceDefinitionValidator\Compiler\ValidateServiceDefinitionsPass;
use Matthias\SymfonyServiceDefinitionValidator\Configuration;
// ...
use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;
 
class AppKernel extends Kernel
{
    // ...
 
    protected function prepareContainer(ContainerBuilder $container)
    {
        $extensions = array();
        foreach ($this->bundles as $bundle) {
            if ($extension = $bundle->getContainerExtension()) {
                $container->registerExtension($extension);
                $extensions[] = $extension->getAlias();
            }
 
            if ($this->debug) {
                $container->addObjectResource($bundle);
            }
        }
        foreach ($this->bundles as $bundle) {
            $bundle->build($container);
        }
 
        $this->buildBundleless($container);
 
        // ensure these extensions are implicitly loaded
        $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions));
    }
 
    private function buildBundleless(ContainerBuilder $container)
    {
        if ($container->getParameter('kernel.debug')) {
            $container->addCompilerPass(new FixValidatorDefinitionPass());
 
            $configuration = new Configuration();
            $configuration->setEvaluateExpressions(true);
            $container->addCompilerPass(
                new ValidateServiceDefinitionsPass($configuration),
                PassConfig::TYPE_AFTER_REMOVING
            );
        }
    }
}

Notice here we hack the kernel, and is ok. We have done it already and methods there are set to protected and not to private because the demands of the app need to remain open especially open for the kernel. In this case we have overridden the prepareContainer method because we want to also add a compiler pass

$this->buildBundleless($container);

but without the need to create an entire bundle just for this. After all we don’t want to create a bundle in a project that is bundle-less. creating 3+ files just because we want to do something simple does not make sense, makes it complex, teaches you less, you learn less about the basics, and well it is just not our cup of tea.

For the contents of buildBundleless it basically will check if in development environment or such and will fix a common validation fix in Symfony service definitions and then will add the check for service definitions from the library. These both are implemented as compiler passes.

Here is the FixValidatorDefinitionPass class:

<?php
 
namespace Grace;
 
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
 
class FixValidatorDefinitionPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        $container->getDefinition('validator')->setClass('Symfony\Component\Validator\Validator');
    }
}

Just sits right next to the AppKernel in Matthew-7-12. We of course could beautify this further, but for now this is enough to spot some service definition corrections.

Next time you try to bring up the Kernel you will see errors that will be easier to correct on your service definitions.

~ php console                                                                            Luiss-MacBook-Pro-3 [16:09:36]
 
 
 
  [Matthias\SymfonyServiceDefinitionValidator\Exception\InvalidServiceDefinitionsException]
  Service definition validation errors (1):
  - grace.pull_flow: Argument for type-hint "Grace\Collabs\Mailer" points to a service of class "Grace\Collabs\Collab
  "

even runtime errors:

~ php console                                                                            Luiss-MacBook-Pro-3 [16:07:32]
 
 
 
  [RuntimeException]
  The autoloader expected class "Grace\Endpoints\Push" to be defined in file "/Users/cordoval/Sites/libs/matthew-7-12
  /src/Endpoints/Push.php". The file was found but the class was not in it, the class name or namespace probably has
  a typo.

Enjoy, thanks for your reading!

Custom Alice Fixture Processors in Symfony Bundle-less Project

Let’s talk about Alice Fixtures. Today I wanted to plug some users into my project and basically just plug a custom Processor to load users with the user manipulator like this:

<?php
 
namespace My\Resources\Fixtures;
 
use Nelmio\Alice\ProcessorInterface;
use My\Domain\Agent;
use Symfony\Component\DependencyInjection\ContainerInterface;
 
class UserProcessor implements ProcessorInterface
{
    protected $container;
 
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }
 
    public function preProcess($object)
    {
 
    }
 
    public function postProcess($object)
    {
        if (!$object instanceof Agent) {
            return;
        }
 
        $userManager = $this->container->get('fos_user.user_manager');
        $userManager->updateUser($object, true);
    }
}

Notice the injection of the container was just because I was in a hurry, but we could as well inject only the service to write a better test. However the loader is badly injected with the container so that is why we just plugged it fast like that.

Notice one more thing, the namespace has no bundle name on it. You are right, that is because there is no need for a bundle when you are building your domain logic. There should be packages, you want packages, not bundles. Bundles here are unreal and artificial making things unnecessarily nested and turning the names into what they ought not to be.

<?php
 
namespace My\Resources\Fixtures;
 
use Hautelook\AliceBundle\Alice\DataFixtureLoader;
use Nelmio\Alice\Fixtures;
use Symfony\Component\Finder\Finder;
 
class FixturesLoader extends DataFixtureLoader
{
    /**
     * {@inheritDoc}
     */
    protected function getFixtures()
    {
        $files = Finder::create()->in(__DIR__)->name('*.yml');
 
        $paths = [];
        /** @var \SplFileInfo $file */
        foreach ($files as $file) {
            $paths[] = $file->getRealPath();
        }
 
        return $paths;
    }
 
    public function getProcessors()
    {
        return [
            new UserProcessor($this->container),
            new OtherDomainProcessor($this->container),
            // ...
        ];
    }
}

And this is how you plug it from your alice loader.
Very simple huh? Then you can just have your agent be plugged when you run the doctrine:fixtures:load command pointing to that folder.

My\Domain\Agent:
    agent{1}:
        username: test
        email: test@test.com
        plainPassword: test
        enabled: true

I just wanted to share this, even if some already know about alice processors. This example was done with the hautelook/alice-bundle. And I found this https://github.com/nelmio/alice#processors after doing it the same way almost ^_^. But it also has mistakes like it shouldn’t return any objet no the post and prePersist methods.

Hope someone finds it when they google, i couldn’t find it on google even though it was on the readme.md

Encouragements!