In this example we are going to create a vendor bundle. It will have a service definition with default arguments. We will then override the default arguments with the parameters coming from the parent application.


Bundle


Structure


.
├── composer.json
└── src
├── BuddyBundle.php
├── DependencyInjection
│   ├── BuddyExtension.php
│   └── Configuration.php
├── Resources
│   └── config
│   └── services.yaml
├── Service
│   ├── PrinterInterface.php
│   ├── ScreenService.php
│   └── TerminalService.php
└── Utility
└── HelloUtility.php

composer.json


{
"name": "Inanzzz/buddy",
"type": "symfony-bundle",
"description": "Buddy up",
"license": "MIT",
"require": {
"php": "^7.2",
"symfony/config": "^4.2",
"symfony/dependency-injection": "^4.2"
},
"autoload": {
"psr-4": {
"Inanzzz\\Buddy\\": "src/"
}
},
"config": {
"sort-packages": true
},
"prefer-stable": true,
"minimum-stability": "stable"
}

BuddyBundle.php


declare(strict_types=1);

namespace Inanzzz\Buddy;

use Inanzzz\Buddy\DependencyInjection\BuddyExtension;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class BuddyBundle extends Bundle
{
public function getContainerExtension()
{
if (null === $this->extension) {
$this->extension = new BuddyExtension();
}

return $this->extension;
}
}

BuddyExtension.php


declare(strict_types=1);

namespace Inanzzz\Buddy\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

class BuddyExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);

$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yaml');

$this->setServices($container, $config);
}

public function getAlias()
{
return Configuration::CONFIG_ROOT;
}

private function setServices(ContainerBuilder $container, array $config): void
{
// Fetch existing service definition and override its class with either:
// - the default value coming from 'Configuration.php' file
// or
// - the specific value coming from the parent application.
$classService = $container->getDefinition(sprintf('%s.service.%s',
Configuration::CONFIG_CHILD,
Configuration::CONFIG_PRINTER
));
$classService->setClass($config[Configuration::CONFIG_CHILD][Configuration::CONFIG_PRINTER]);

// Fetch existing service definition and override its arguments one by one with either:
// - the default values coming from 'Configuration.php' file
// or
// - the specific values coming from the parent application.
$argumentService = $container->getDefinition('Inanzzz\Buddy\Utility\HelloUtility');
$argumentService->setArgument(
sprintf('$%s', Configuration::CONFIG_MESSAGE),
$config[Configuration::CONFIG_CHILD][Configuration::CONFIG_MESSAGE]
);
$argumentService->setArgument(
sprintf('$%s', Configuration::CONFIG_PRINTER),
$classService
);
}
}

Configuration.php


declare(strict_types=1);

namespace Inanzzz\Buddy\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{
public const CONFIG_ROOT = 'inanzzz';
public const CONFIG_CHILD = 'buddy';

public const CONFIG_MESSAGE = 'message';
public const CONFIG_PRINTER = 'printer';

public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder(self::CONFIG_ROOT);

$treeBuilder->getRootNode()
->children()
->arrayNode(self::CONFIG_CHILD)
->addDefaultsIfNotSet()
->children()
->scalarNode(self::CONFIG_MESSAGE)
->cannotBeEmpty()
->defaultValue('inanzzz')
->end()
->scalarNode(self::CONFIG_PRINTER)
->cannotBeEmpty()
->defaultValue('Inanzzz\Buddy\Service\TerminalService')
->end()
->end()
->end()
->end();

return $treeBuilder;
}
}

services.yaml


services:

# SERVICE
buddy.service.printer:
class: ~

# UTILITY
Inanzzz\Buddy\Utility\HelloUtility:
arguments:
$printer: ~
$message: ~

PrinterInterface.php


declare(strict_types=1);

namespace Inanzzz\Buddy\Service;

interface PrinterInterface
{
public function print(string $message): void;
}

ScreenService.php


declare(strict_types=1);

namespace Inanzzz\Buddy\Service;

class ScreenService implements PrinterInterface
{
public function print(string $message): void
{
echo sprintf('Printing %s to screen!', $message);
}
}

TerminalService.php


declare(strict_types=1);

namespace Inanzzz\Buddy\Service;

class TerminalService implements PrinterInterface
{
public function print(string $message): void
{
echo sprintf('Printing %s to terminal!', $message);
}
}

HelloUtility.php


declare(strict_types=1);

namespace Inanzzz\Buddy\Utility;

use Inanzzz\Buddy\Service\PrinterInterface;

class HelloUtility
{
private $printer;
private $message;

public function __construct(
PrinterInterface $printer,
string $message
) {
$this->printer = $printer;
$this->message = $message;
}

public function handle(): void
{
$this->printer->print($this->message);
}
}

Parent application


Assume that you injected HelloUtility class into one of the services in parent application. Depending on the usage, you will get given results.


config/packages/inanzzz.yaml


inanzzz:
buddy:
printer: 'Inanzzz\Buddy\Service\ScreenService'

// Printing inanzzz to screen!

inanzzz:
buddy:
message: 'moon'

// Printing moon to terminal!

inanzzz:
buddy:
printer: 'Inanzzz\Buddy\Service\ScreenService'
message: 'mama'

// Printing mama to screen!

inanzzz:
buddy:

inanzzz:

// Printing inanzzz to terminal!

inanzzz:
buddy:
printer: 'Some\Parent\Servie\That\Implements\PrinterInterface'
message: 'something'

// Printing something to parent printer!