Deep view into CompilerExtension

??.??.2019

@xf3l1x
@nettefw

Hall of Fame

Hall of Fl🔥me

Features

  1. DI config validations❗️
  2. DI CompilerExtension(s)
  3. Tracy panel
  4. Tracy BlueScreen
  5. 🌀Annotations
  6. Fixtures

Nettrine/DBAL

composer require nettrine/dbal

CompilerExtension

Tracy

Classes

CompilerExtension

Parameters

Register

services

Decorate

services

Modify

PHP code

loadConfiguration()

beforeCompile()

afterCompile()

Services

Parameters validation

private $defaults = [
    'debug' => false,
    'configuration' => [
        'sqlLogger' => null,
        'resultCacheImpl' => null,
        'filterSchemaAssetsExpression' => null,
        'autoCommit' => true,
    ],
    'connection' => [...]
];
public function loadConfiguration(): void
{
    $config = $this->validateConfig($this->defaults);
}

Schema 🚀

Expect::structure([
    'debug' => Expect::bool(false),
    'configuration' => Expect::structure([
        'sqlLogger' => Expect::string(),
        'resultCacheImpl' => Expect::string(),
        'filterSchemaExpression' => Expect::string()->nullable(),
        'autoCommit' => Expect::bool(true),
    ]),
    'connection' => Expect::array()->default([
        'driver' => 'pdo_sqlite',
        'types' => [],
        'typesMapping' => [],
    ]),
]);

Nettrine/ORM

composer require nettrine/dbal
composer require nettrine/orm

ORM

Symfony Console

DBAL

Extensions

extensions:
    # Doctrine DBAL
    dbal: Nettrine\DBAL\DI\DbalExtension
    dbal_console: Nettrine\DBAL\DI\DbalConsoleExtension

    # Doctrine ORM
    orm: Nettrine\ORM\DI\OrmExtension
    orm_cache: Nettrine\ORM\DI\OrmCacheExtension
    orm_console: Nettrine\ORM\DI\OrmConsoleExtension
    orm_annotations: Nettrine\ORM\DI\OrmAnnotationsExtension

    # Symfony Console
    console: Contributte\Console\DI\ConsoleExtension(%consoleMode%)

Symfony Console

public function loadConfiguration(): void
{
    $builder = $this->getContainerBuilder();
    $config = $this->validateConfig($this->defaults);

    $builder
        ->addDefinition($this->prefix('application'))
        ->setFactory(Application::class);
}

Nettrine/ORM

public function loadConfiguration(): void
{
    $builder = $this->getContainerBuilder();
    $config = $this->validateConfig($this->defaults);

    $builder->addDefinition($this->prefix('schemaToolCreateCommand'))
        ->setType(CreateCommand::class);
}

Extension ⬅️➡️ Extension

public function beforeCompile(): void
{
    $builder = $this->getContainerBuilder();
    
    $commands = $builder->findByType(Command::class);
    $app = $builder->getDefinition($this->prefix('application'));

    foreach ($commands as $serviceName => $service) {
        $app->addSetup('add', [$service]);
    }
}

Container <?php

public function createServiceConsole__application()
{
    $service = new Symfony\Component\Console\Application;
    $service->add($this->getService('orm.console.schemaToolDropCommand'));
    $service->add($this->getService('orm.console.schemaToolUpdateCommand'));
    $service->add($this->getService('orm.console.validateSchemaCommand'));
    return $service;
}

Nettrine/DBAL

Tracy panel

Tracy panel

tracy:
    bar:
        - My\Awesome\SuperPanel

Tracy panel

public function afterCompile(ClassType $class): void
{
        $initialize = $class->getMethod('initialize');
        $initialize->addBody(
            '$this->getService(?)->addPanel($this->getService(?));',
            ['tracy.bar', $this->prefix('queryPanel')]
        );
    }
}

DbalExtension.php

Tracy panel

# Container_54599b6ea8.php
public function initialize()
{
    $this->getService('tracy.bar')
        ->addPanel($this->getService('dbal.queryPanel'));
}

Tracy panel

public function loadConfiguration(): void
{
    $builder->addDefinition($this->prefix('connection'))
        ->setFactory(Connection::class)	
        ->addSetup('@Tracy\Bar::addPanel', [
            new Statement(QueryPanel::class, ['@self']),
        ]);
}

Tracy panel

public function createServiceDbal__connection()
{
    $service = new Doctrine\DBAL\Connection;

    $this->getService('tracy.bar')
        ->addPanel(new Nettrine\DBAL\QueryPanel($service));

    return $service;
}

Nettrine/DBAL

BlueScreen

tracy:
    blueScreen:
        - My\AwesomeBlueScreen::renderException

BlueScreen

public static function renderException(?Throwable $e): ?array
{
    if ($e === NULL) return NULL;

    if ($e instanceof QueryException) {
        return [
            'tab' => 'SQL',
            'panel' => Helpers::highlight($e->getSql()),
        ];
    }

    return NULL;
}

🌀Annotations

composer require nettrine/annotations
/**
 * @Component\Component(name="user_list")
 * @Component\Security(role="admin")
 * @Component\Cache(expire="+20 minutes")
 */

🌀Annotations

  1. Create @Component annotation
  2. Create control factory
  3. Magic in CompilerExtension
  4. Setup components
  5. 💰💰💰🤑

💰

💰

💰

💰

🌀Annotation

use Doctrine\Common\Annotations\Annotation\Target;

/**
 * @Annotation
 * @Target("CLASS")
 */
final class Component
{

    /** @var string */
    public $name;

}

Control Factory

/**
 * @Component\Component(name="user_list")
 * @Component\Security(role="admin")
 */
final class UserListFactory implements IControlFactory
{
	public function create(?array $args = null)
	{
		return new UserList(...);
	}
}

🧙‍♀️🧙‍♂️🎩

  1. Register needed services
  2. Lookup for all factories
  3. Parse annotations
  4. Apply parsed metadata

🧙‍♀️🧙‍♂️🎩

public function loadConfiguration(): void
{
    $builder = $this->getContainerBuilder();

    $builder->addDefinition($this->prefix('creator'))
        ->setFactory(ComponentCreator::class);
}

🧙‍♀️🧙‍♂️🎩

public function beforeCompile(): void
{
    $builder = $this->getContainerBuilder();
    $components = $builder->findByType(IControlFactory::class);

    $metadata = [];
    foreach ($components as $service => $def) {
        $meta = $this->parseAnnotations($def);
        $meta['def'] = $def;
        $metadata[] = $meta;
    }
}
services:
  - App\Components\UserListFactory
protected function parseAnnotations(string $class): array
{   
    $reader = new AnnotationReader();
    $rc = new ReflectionClass($class);
    $annotations = $reader->getClassAnnotations($rc);

    $metadata = [];
    foreach ($annotations as $annotation) {
        // Parse @Component
        if (get_class($annotation) === Component::class) {
            $metadata['name'] = $annotation->name;
            continue;
        }

        // Other annotations..
    }
    return $metadata;
}

🧙‍♀️🧙‍♂️🎩

public function beforeCompile(): void
{
    // ...

    $creator = $builder->getDefinition($this->prefix('creator'));
    foreach ($metadata as $item) {
        $creator->addSetup('register', [
            $item['name'], 
            $item['def']
        ]);
    }
}

🧙‍♀️🧙‍♂️🎩

trait TMagicControl
{
    /** @var IMagicControlFactory @inject */
    public $magicControlFactory;

    public function createComponentMagic(): MagicControl
    {
        return $this->magicControlFactory->create();
    }
}

Magic Control

class MagicControl
{
    protected function createComponent($name): BaseComponent
    {
       return $this->creator->create($name, $args);
    }
}

Magic Control

{block #content}

    {control magic-user_list}

{/}

Data Fixtures

composer require nettrine/dbal
composer require nettrine/orm
composer require nettrine/fixtures
composer require nelmio/alice

Data Fixtures

use Doctrine\Common\DataFixtures\AbstractFixture;

class UserFixture extends AbstractFixture
{
    public function load(ObjectManager $manager): void
    {
        $user = new User('Felix', 'Felicis');
        $manager->persist($user);
        $manager->flush();
    }
}

Data Fixtures

class UserFixture extends AbstractFixture
{
    public function load(ObjectManager $manager): void
    {
        $loader = new NativeLoader();
        $users = $loader->loadData([
            User::class => [
                'user{0..100}' => [
                    '__construct' => [
                        '<firstName()>',
                        '<lastName()>',
                        '2 (unique)' => '<email()>',
                    ],
                ],
            ],
        ]);
        foreach($users as $user) {
            $manager->persist($user);
        }
        $manager->flush();
    }
}

Data Fixtures

extensions:
    # Doctrine DBAL
    dbal: Nettrine\DBAL\DI\DbalExtension
    dbal_console: Nettrine\DBAL\DI\DbalConsoleExtension

    # Doctrine ORM
    orm: Nettrine\ORM\DI\OrmExtension
    orm_cache: Nettrine\ORM\DI\OrmCacheExtension
    orm_console: Nettrine\ORM\DI\OrmConsoleExtension
    orm_annotations: Nettrine\ORM\DI\OrmAnnotationsExtension

    # Data Fixtures
    fixtures: Nettrine\Fixtures\DI\FixturesExtension

fixtures:
    paths:
        - %rootDir%/db/Fixtures

Time to selfie 📷

Project Nutella

Project Nutella

github.com/planette/nutella-project

Thank you

Keep Contributting!

@xf3l1x
f3l1x.io