Я объясню на примере: Есть 2 пакета: Foo\SecurityBundle
и Foo\MenuBundle
. Foo\MenuBundle
имеет класс меню, который выглядит следующим образом:
namespace Foo\MenuBundle;
use Foo\SecurityBundle\MenuSecurer; //note this
class Menu{
protected $securer;
public function __construct(MenuSecurer $securer = null){
$this->securer = $securer;
}
public function buildMenu(){
//build the $menu ...
//...
if($this->securer != null)
$securer->secure($menu);
}
}
пакет безопасности автоматически добавит $ menuSecurer, если он установлен, однако проблема заключается в том, что когда пакет безопасности не установлен, тогда его классы также не определены, поэтому я не могу use Foo\SecurityBundle...
в MenuBundle даже хотя я действительно не использую его. Какая правильная дорога вокруг этого?
Существует немало способов сделать это, но я думаю, что хорошим подходом было бы добавить параметр конфигурации для класса/службы, который должен быть введен в первый аргумент конструктора.
Например, в классе Foo\MenuBundle\Menu
(при условии, что это уже определено как служба) вы можете добавить дополнительный элемент в конфигурацию связки для определения услуги по умолчанию для $securer
, а затем опционально переопределить ее в config if желательно.
В классе конфигурации (DependecyInjection\Configuration.php
):
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('foo_menu');
$rootNode
->children()
->arrayNode('service')
->addDefaultsIfNotSet()
->children()
->scalarNode('menu_securer')->defaultValue('foo_security.menu_securer')->end()
>end()
->end()
->end();
return $treeBuilder;
}
В классе расширения (DependecyInjection\FooMenuExtension.php
):
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
// ...
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
// This is what sets 'foo_menu.menu_securer' to the service you want
foreach ($config['service'] as $key => $service) {
$container->setAlias($this->getAlias() . '.' . $key, $service);
}
$loader->load('services.xml');
$container->getDefinition('foo_menu.menu.menu')
->replaceArgument(0, new Reference('foo_menu.menu_securer'));
}
И ваше определение сервиса будет выглядеть примерно так...
<service id="foo_menu.menu.menu" class="%foo_menu.menu.menu.class%">
<argument /> <!-- foo_menu.menu_securer -->
</service>
Теперь в вашем config.yml
вы можете просто отключить услугу, которую хотите использовать, указав ее ниже...
foo_menu:
service:
menu_securer: 'some_other.service'
Изменение: Что касается намека типа, как упоминалось Маркусом, вероятно, было бы неплохо реализовать интерфейс, $securer
должен реализовать $securer
.
В документах symfony есть раздел, посвященный этой ситуации: http://symfony.com/doc/current/book/service_container.html#optional-dependencies-setter-injection
Если у вас есть необязательные зависимости для класса, то более эффективным вариантом может быть "установка инъекции".
В соответствии с этим ваш класс может выглядеть так:
namespace Foo\MenuBundle;
class Menu{
protected $securer;
public function setSecurer($securer) {
$this->securer = $securer;
}
public function buildMenu(){
//build the $menu ...
//...
if($this->securer != null)
$securer->secure($menu);
}
}
# config.yml
menu_service:
class: Foo\MenuBundle\Menu
calls:
- [setMailer, ["@securer"]]
Что-то вроде этого... К сожалению, вы все еще не можете использовать инструкцию использования без интерфейса, который, как вы знаете, существует.