symplify/package-builder
Dependency Injection, Console and Kernel toolkit for Symplify packages.
Requires
- php: ^7.1
- nette/utils: ^2.5
- nette/finder: ^2.4
- symfony/config: ^3.4|^4.1
- symfony/console: ^3.4|^4.1
- symfony/debug: ^3.4|^4.1
- symfony/dependency-injection: ^3.4|^4.1
- symfony/finder: ^3.4|^4.1
- symfony/http-kernel: ^3.4|^4.1
- symfony/yaml: ^3.4|^4.1
Requires (Dev)
- phpunit/phpunit: ^7.5|^8.0
- dev-master / 5.5.x-dev
- v5.4.5
- v5.4.4
- v5.4.3
- v5.4.2
- v5.4.1
- v5.4.0
- v5.3.12
- v5.3.11
- v5.3.10
- v5.3.9
- v5.3.8
- v5.3.7
- v5.3.6
- v5.3.5
- v5.3.4
- v5.3.2
- v5.3.1
- v5.3.0
- v5.2.22
- v5.2.20
- v5.2.19
- v5.2.18
- v5.2.17
- v5.2.16
- v5.2.15
- v5.2.14
- v5.2.13
- v5.2.12
- v5.2.11
- v5.2.10
- v5.2.9
- v5.2.8
- v5.2.7
- v5.2.6
- v5.2.5
- v5.2.4
- v5.2.3
- v5.2.2
- v5.2.1
- v5.2.0
- v5.1.4
- v5.1.3
- v5.1.2
- v5.1.1
- v5.1.0
- v5.0.2
- v5.0.1
- v5.0.0
- v4.8.0
- v4.7.0
- v4.6.1
- v4.6.0
- v4.5.1
- v4.5.0
- v4.4.2
- v4.4.1
- v4.4.0
- v4.3.0
- v4.2.3
- v4.2.2
- v4.2.1
- v4.2.0
- v4.1.2
- v4.1.1
- v4.1.0
- v4.0.4
- v4.0.3
- v4.0.2
- v4.0.1
- v4.0.0
- v4.0.0beta1
- v4.0.0alpha6
- v4.0.0alpha5
- v4.0.0alpha4
- v4.0.0alpha3
- v4.0.0alpha2
- v4.0.0alpha1
- v3.2.30
- v3.2.29
- v3.2.28
- v3.2.27
- v3.2.26
- v3.2.25
- v3.2.24
- v3.2.23
- v3.2.22
- v3.2.21
- v3.2.20
- v3.2.18
- v3.2.17
- v3.2.16
- v3.2.15
- v3.2.14
- v3.2.13
- v3.2.12
- v3.2.11
- v3.2.10
- v3.2.9
- v3.2.7
- v3.2.6
- v3.2.5
- v3.2.4
- v3.2.3
- v3.2.2
- v3.2.1
- v3.2.0
- v3.1.2
- v3.1.1
- v3.1.0
- v3.0.8
- v3.0.7
- v3.0.6
- v3.0.5
- v3.0.4
- v3.0.3
- v3.0.2
- v3.0.1
- v3.0.0
- v3.0.0-RC5
- v3.0.0-RC4
- v3.0.0-RC3
- v3.0.0-RC2
- v3.0.0-RC1
- 2.5.x-dev
- v2.5.12
- v2.5.10
- v2.5.9
- v2.5.8
- v2.5.7
- v2.5.6
- v2.5.5
- v2.5.4
- v2.5.3
- v2.5.2
- v2.5.1
- v2.5.0
- v2.4.3
- v2.4.2
- v2.4.1
- v2.4.0
- v2.3.4
- v2.3.3
- v2.3.2
- v2.3.1
- v2.3.0
- v2.2.15
- v2.2.14
- v2.2.13
- v2.2.12
- v2.2.11
- v2.2.10
- v2.2.9
- v2.2.8
- v2.2.7
- v2.2.6
- v2.2.5
- v2.2.4
- v2.2.3
- v2.2.2
- v2.2.1
- v2.2.0
- v2.1.8
- v2.1.7
- v2.1.6
- v2.1.5
- v2.1.4
- v2.1.3
- v2.1.2
- v2.1.1
- v2.1.0
- v2.0.1
- v2.0.0
- v2.0.0-RC3
- v2.0.0-RC2
- v2.0.0-RC1
- v1.4.10
- v1.4.9
- v1.4.8
- v1.4.7
- v1.4.6
- v1.4.5
- v1.4.4
- v1.4.3
- v1.4.2
- v1.4.1
- v1.4.0
README
This tools helps you with Collectors in DependecyInjection, Console shortcuts, ParameterProvider as service and many more.
Install
composer require symplify/package-builder
Use
Prevent Parameter Typos
Was 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.
Collect Services of Certain Type Together
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 ); } }
Add Service by Interface if Found
<?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())] ); } }
Get All Parameters via Service
# 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" } }
Get Vendor Directory from Anywhere
<?php declare(strict_types=1); Symplify\PackageBuilder\Composer\VendorDirProvider::provide(); // returns path to vendor directory
Load a Config for CLI Application?
Use 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 found
Where "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.yml
Then 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 before
You 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
.
Load Config via --level
Option in CLI App
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 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 $appKernel = new AppKernel('prod', true); if ($configFile) { $appKernel->setConfig($configFile); } else { } $appKernel->boot(); $container = $appKernel->getContainer();
And use like this:
vendor/bin/your-app --level the-config
Merge Parameters in .yaml
Files Instead of Override?
In 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_too
The result will change with Symplify\PackageBuilder\Yaml\FileLoader\ParameterMergingYamlFileLoader
:
parameters:
another_key:
+ - skip_this
- skip_that_too
How 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.
Do you Need to Merge YAML files Outside Kernel?
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"
Use %vendor%
and %cwd%
in Imports Paths
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.
Smart Compiler Passes for Lazy Programmers ↓
Collect Services in Short Configs
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.
Autowire Singly-Implemented Interfaces
Symplify\PackageBuilder\DependencyInjection\CompilerPass\AutowireSinglyImplementedCompilerPass
services: OnlyImplementationOfFooInterface: ~ - - FooInterface: - alias: OnlyImplementationOfFooInterface
Do not Repeat Simple Factories
Symplify\PackageBuilder\DependencyInjection\CompilerPass\AutoReturnFactoryCompilerPass
This prevent repeating factory definitions for obvious 1-instance factories:
services: Symplify\PackageBuilder\Console\Style\SymfonyStyleFactory: ~ Symfony\Component\Console\Style\SymfonyStyle: factory: ['@Symplify\PackageBuilder\Console\Style\SymfonyStyleFactory', 'create']
How this works?
The factory class needs to have return type + create()
method:
<?php namespace Symplify\PackageBuilder\Console\Style; use Symfony\Component\Console\Style\SymfonyStyle; final class SymfonyStyleFactory { public function create(): SymfonyStyle { // ... } }
That's all! The "factory" definition is generated from this obvious usage.
Put this compiler pass first, as it creates new definitions that other compiler passes might work with.
Autowire Array Parameters
Symplify\PackageBuilder\DependencyInjection\CompilerPass\AutowireArrayParameterCompilerPass
This feature surpasses YAML-defined, tag-based or CompilerPass-based collectors in minimalistic way:
<?php class Application { /** * @var Command[] */ private $commands = []; /** * @param Command[] $commands */ public function __construct(array $commands) { $this->commands = $commands; var_dump($commands); // instnace of Command collected from all services } }
If there are failing cases, just exclude them in constructor:
$this->addCompilerPass(new AutowireArrayParameterCompilerPass([ 'Sonata\CoreBundle\Model\Adapter\AdapterInterface' ]);
Autobind Parameters
Symplify\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: ..
Always Autowire this Type
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.
Use Public Services only in Tests
Symplify\PackageBuilder\DependencyInjection\CompilerPass\PublicForTestsCompilerPass
- Read How to Test Private Services in Symfony
# some config for tests services: - _defaults: - public: true
That's all :)