This tools helps you with Collectors in DependecyInjection, Console shortcuts, ParameterProvider as service and many more.
composer require symplify/package-builderWas it ignoreFiles? Or ignored_files? Or ignore_file? Are you lazy to ready every README.md to find out the corrent name?
Make developer's live happy by helping them.
parameters:
correctKey: 'value'
services:
_defaults:
public: true
autowire: true
Symfony\Component\EventDispatcher\EventDispatcher: ~
# this subscribe will check parameters on every Console and Kernel run
Symplify\PackageBuilder\EventSubscriber\ParameterTypoProofreaderEventSubscriber: ~
Symplify\PackageBuilder\Parameter\ParameterTypoProofreader:
$correctToTypos:
# correct key name
correct_key:
# the most common typos that people make
- 'correctKey'
# regexp also works!
- '#correctKey(s)?#i'This way user is informed on every typo he or she makes via exception:
Parameter "parameters > correctKey" does not exist.
Use "parameters > correct_key" instead.They can focus less on remembering all the keys and more on programming.
How do we load Commands to Console Application without tagging?
<?php declare(strict_types=1);
namespace App\DependencyInjection\CompilerPass;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symplify\PackageBuilder\DependencyInjection\DefinitionFinder;
use Symplify\PackageBuilder\DependencyInjection\DefinitionCollector;
final class CollectorCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $containerBuilder): void
{
$definitionCollector = new DefinitionCollector(new DefinitionFinder());
$definitionCollector->loadCollectorWithType(
$containerBuilder,
Application::class, // 1 main service
Command::class, // many collected services
'add' // the adder method called on 1 main service
);
}
}<?php declare(strict_types=1);
namespace App\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symplify\EasyCodingStandard\Contract\Finder\CustomSourceProviderInterface;
use Symplify\EasyCodingStandard\Finder\SourceFinder;
use Symplify\PackageBuilder\DependencyInjection\DefinitionFinder;
final class CustomSourceProviderDefinitionCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $containerBuilder): void
{
$definitionFinder = new DefinitionFinder();
$customSourceProviderDefinition = $definitionFinder->getByTypeIfExists(
$containerBuilder,
CustomSourceProviderInterface::class
);
if ($customSourceProviderDefinition === null) {
return;
}
$sourceFinderDefinition = $definitionFinder->getByType($containerBuilder, SourceFinder::class);
$sourceFinderDefinition->addMethodCall(
'setCustomSourceProvider',
[new Reference($customSourceProviderDefinition->getClass())]
);
}
}# app/config/services.yml
parameters:
source: "src"
services:
_defaults:
autowire: true
Symplify\PackageBuilder\Parameter\ParameterProvider: ~Then require in __construct() where needed:
<?php declare(strict_types=1);
namespace App\Configuration;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
final class StatieConfiguration
{
/**
* @var ParameterProvider
*/
private $parameterProvider;
public function __construct(ParameterProvider $parameterProvider)
{
$this->parameterProvider = $parameterProvider;
}
public function getSource(): string
{
return $this->parameterProvider->provideParameter('source'); // returns "src"
}
}<?php declare(strict_types=1);
Symplify\PackageBuilder\Composer\VendorDirProvider::provide(); // returns path to vendor directoryUse in CLI entry file bin/<app-name>, e.g. bin/statie or bin/apigen.
<?php declare(strict_types=1);
# bin/statie
use Symfony\Component\Console\Input\ArgvInput;
use Symplify\PackageBuilder\Configuration\ConfigFileFinder;
ConfigFileFinder::detectFromInput('statie', new ArgvInput);
# throws "Symplify\PackageBuilder\Exception\Configuration\FileNotFoundException" exception if no file is foundWhere "statie" is key to save the location under. Later you'll use it get the config.
With --config you can set config via CLI.
bin/statie --config config/statie.ymlThen get the config just run:
<?php declare(strict_types=1);
$config = Symplify\PackageBuilder\Configuration\ConfigFileFinder::provide('statie');
var_dump($config); // returns absolute path to "config/statie.yml"
// or NULL if none was found beforeYou can also provide fallback to file in current working directory:
<?php declare(strict_types=1);
$config = Symplify\PackageBuilder\Configuration\ConfigFileFinder::provide('statie', ['statie.yml']);
$config = Symplify\PackageBuilder\Configuration\ConfigFileFinder::provide('statie', ['statie.yml', 'statie.yaml']);This is common practise in CLI applications, e.g. PHPUnit looks for phpunit.xml.
Do you get exception before getting into Symfony\Console Application, but still want to render it with -v, -vv, -vvv options?
<?php declare(strict_types=1);
use Symfony\Component\Console\Application;
use Symfony\Component\DependencyInjection\Container;
use Symplify\PackageBuilder\Console\ThrowableRenderer;
require_once __DIR__ . '/autoload.php';
try {
/** @var Container $container */
$container = require __DIR__ . '/container.php';
$application = $container->get(Application::class);
exit($application->run());
} catch (Throwable $throwable) {
(new ThrowableRenderer())->render($throwable);
exit($throwable->getCode());
}In you bin/your-app you can use --level option as shortcut to load config from /config directory.
It makes is easier to load config over traditional super long way:
vendor/bin/your-app --config vendor/organization-name/package-name/config/subdirectory/the-config.yml<?php declare(strict_types=1);
use App\DependencyInjection\ContainerFactory;
use Symfony\Component\Console\Input\ArgvInput;
use Symplify\PackageBuilder\Configuration\ConfigFileFinder;
use Symplify\PackageBuilder\Configuration\LevelFileFinder;
// 1. Try --level
$configFile = (new LevelFileFinder)->detectFromInputAndDirectory(new ArgvInput, __DIR__ . '/../config/');
// 2. try --config
if ($configFile === null) {
ConfigFileFinder::detectFromInput('ecs', new ArgvInput);
$configFile = ConfigFileFinder::provide('ecs', ['easy-coding-standard.yml']);
}
// 3. Build DI container
$containerFactory = new ContainerFactory; // your own class
if ($configFile) {
$container = $containerFactory->createWithConfig($configFile);
} else {
$container = $containerFactory->create();
}And use like this:
vendor/bin/your-app --level the-configIn Symfony the last parameter wins by default*, which is bad if you want to decouple your parameters.
# first.yml
parameters:
another_key:
- skip_this# second.yml
imports:
- { resource: 'first.yml' }
parameters:
another_key:
- skip_that_tooThe result will change with Symplify\PackageBuilder\Yaml\FileLoader\ParameterMergingYamlFileLoader:
parameters:
another_key:
+ - skip_this
- skip_that_tooHow to use it?
<?php declare(strict_types=1);
namespace App;
use Symfony\Component\Config\Loader\DelegatingLoader;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Loader\GlobFileLoader;
use Symfony\Component\HttpKernel\Config\FileLocator;
use Symfony\Component\HttpKernel\Kernel;
use Symplify\PackageBuilder\Yaml\FileLoader\ParameterMergingYamlFileLoader;
final class AppKernel extends Kernel
{
/**
* @param ContainerInterface|ContainerBuilder $container
*/
protected function getContainerLoader(ContainerInterface $container): DelegatingLoader
{
$kernelFileLocator = new FileLocator($this);
$loaderResolver = new LoaderResolver([
new GlobFileLoader($container, $kernelFileLocator),
new ParameterMergingYamlFileLoader($container, $kernelFileLocator)
]);
return new DelegatingLoader($loaderResolver);
}
}In case you need to do more work in YamlFileLoader, just extend the abstract parent Symplify\PackageBuilder\Yaml\FileLoader\AbstractParameterMergingYamlFileLoader and add your own logic.
Instead of creating all the classes use this helper class:
<?php declare(strict_types=1);
$parameterMergingYamlLoader = new Symplify\PackageBuilder\Yaml\ParameterMergingYamlLoader;
$parameterBag = $parameterMergingYamlLoader->loadParameterBagFromFile(__DIR__ . '/config.yml');
var_dump($parameterBag);
// instance of "Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface"Instead of 2 paths with ignore_errors use %vendor% and other parameters in imports paths:
imports:
- - { resource: '../../easy-coding-standard/config/psr2.yml', ignore_errors: true }
- - { resource: 'vendor/symplify/easy-coding-standard/config/psr2.yml', ignore_errors: true }
+ - { resource: '%vendor%/symplify/easy-coding-standard/config/psr2.yml' }You can have that with Symplify\PackageBuilder\Yaml\FileLoader\ParameterImportsYamlFileLoader:
<?php declare(strict_types=1);
namespace App;
use Symfony\Component\Config\Loader\DelegatingLoader;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Loader\GlobFileLoader;
use Symfony\Component\HttpKernel\Config\FileLocator;
use Symfony\Component\HttpKernel\Kernel;
use Symplify\PackageBuilder\Yaml\FileLoader\ParameterImportsYamlFileLoader;
final class AppKernel extends Kernel
{
/**
* @param ContainerInterface|ContainerBuilder $container
*/
protected function getContainerLoader(ContainerInterface $container): DelegatingLoader
{
$kernelFileLocator = new FileLocator($this);
$loaderResolver = new LoaderResolver([
new GlobFileLoader($container, $kernelFileLocator),
new ParameterImportsYamlFileLoader($container, $kernelFileLocator)
]);
return new DelegatingLoader($loaderResolver);
}
}In case you need to do more work in YamlFileLoader, just extend the abstract parent Symplify\PackageBuilder\Yaml\FileLoader\AbstractParameterImportsYamlFileLoader and add your own logic.
Symplify\PackageBuilder\DependencyInjection\CompilerPass\ConfigurableCollectorCompilerPass
Must-have for PHP CLI Apps without FrameworkBundle. Collect services by type, e.g. Console Commands to Application, EventSubscribers to EventDispatcher.
parameters:
collectors:
-
main_type: 'Symplify\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory'
collected_type: 'Symplify\BetterPhpDocParser\Contract\PhpDocInfoDecoratorInterface'
add_method: 'addPhpDocInfoDecorator'Read more about collector pattern to know how and when to use it.
Symplify\PackageBuilder\DependencyInjection\CompilerPass\AutowireSinglyImplementedCompilerPass
services:
OnlyImplementationOfFooInterface: ~
-
- FooInterface:
- alias: OnlyImplementationOfFooInterfaceSymplify\PackageBuilder\DependencyInjection\CompilerPass\AutoBindParametersCompilerPass
parameters:
entity_repository_class: 'Doctrine\ORM\EntityRepository'
entity_manager_class: 'Doctrine\ORM\EntityManager'
services:
- _defaults:
- bind:
- $entityRepositoryClass: '%entity_repository_class%'
- $entityManagerClass: '%entity_manager_class%'
-
Rector\:
resource: ..Do you want to allow users to register services without worrying about autowiring? After all, they might forget it and that would break their code. Set types to always autowire:
<?php
// ...
use PhpCsFixer\Fixer\FixerInterface;
use Symplify\PackageBuilder\DependencyInjection\CompilerPass\AutowireInterfacesCompilerPass;
// ...
$containerBuilder->addCompilerPass(new AutowireInterfacesCompilerPass([
FixerInterface::class,
]));This will make sure, that PhpCsFixer\Fixer\FixerInterface is always registered.
Symplify\PackageBuilder\DependencyInjection\CompilerPass\PublicForTestsCompilerPass- Read How to Test Private Services in Symfony
# some config for tests
services:
- _defaults:
- public: trueThat's all :)