Symfony2 conditional service declaration - symfony

I'm currently trying to find a solid solution to change the dependencies of a Symfony2 service dynamically. In detail: I have a Services which uses a HTTP-Driver to communicate with an external API.
class myAwesomeService
{
private $httpDriver;
public function __construct(
HTTDriverInterface $httpDriver
) {
$this->httpDriver = $httpDriver;
}
public function transmitData($data)
{
$this->httpDriver->dispatch($data);
}
}
While running the Behat tests on the CI, I'd like to use a httpMockDriver instead of the real driver because the external API might be down, slow or even broken and I don't want to break the build.
At the moment I'm doing something like this:
<?php
namespace MyAwesome\TestBundle\DependencyInjection;
class MyAwesomeTestExtension extends Extension
{
/**
* {#inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new
FileLocator(__DIR__.'/../Resources/config'));
$environment = //get environment
if ($environment == 'test') {
$loader->load('services_mock.yml');
} else {
$loader->load('services.yml');
}
}
}
This works for now, but will break for sure. So, is there a more elegant/solid way to change the HTTPDriver dynamically?

I finally found a solution that looks solid to me. As of Symfony 2.4 you can use the expression syntax: Using the Expression Language
So I configured my service this way.
service.yml
parameters:
httpDriver.class: HTTP\Driver\Driver
httpMockDriver.class: HTTP\Driver\MockDriver
myAwesomeService.class: My\Awesome\Service
service:
myAwesomeService:
class: "%myAwesomeService.class%"
arguments:
- "#=service('service_container').get('kernel.environment') == 'test'? service('httpMockDriver) : service('httpDriver)"
This works for me.

Related

DI extension : There is no extension able to load the configuration for "twig"

I just created a symfony DI extension , where I'm trying to append some form theme configuration to my app/config.yml.
1) My Extension class:
class ELFinderFieldTypeExtension extends Extension implements PrependExtensionInterface
{
/**
* {#inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('form_themes.yml');
}
...
2) My form_themes.yml
twig:
form_themes:
- 'ELFinderFieldTypeBundle:elfinder:elfinder_widget.html.twig'
Unfortunately, It's not the right way to load a twig config, that's way I'm getting kind of:
There is no extension able to load the configuration for "twig" (in
/var/www/html/..../DependencyInjection/../Resources/config/form_themes.yml).
Looked for namespace "twig", found none
Anyone have any idea would be voted and appreciated.
I found an easy solution, ally I need is to append my config after implementing PrependExtensionInterface Interface:
public function prepend(ContainerBuilder $container)
{
$configFile = \sprintf('%s%s', __DIR__, '/../Resources/config/form_themes.yml');
$this->prependYamlConfigFile($container, 'twig', $configFile);
}

Symfony prependExtensionConfig with data from a service

I try to prepend a config array to a different bundle using the prependExtensionConfig from my bundle. All works fine until $config is hardcoded.
My goal is to load the $config values using a service (from the db for example) and than prepend it to the other bundle.
The problem is that the services are not loaded at that moment.
I guess this is a limitation is symfony2.
Any ideas? thx
class MyExtension extends Extension implements PrependExtensionInterface
{
/**
* {#inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yml');
}
public function prepend(ContainerBuilder $container)
{
$config = // load config from a service ...
$container->prependExtensionConfig('other_bundle', $config);
}
}
First of all you need to set up your config to take values from parameters section of Service container:
Setup config.yml:
# app/config/config.yml
namespace:
subnamespace:
param1: %param1%
param2: %param2%
Then you need to fill %param1% and %param2% parameters in container with values from database. To do that you need to declare your CompilerPass and add it to the container. After whole container will be loaded (in the compile time) you will have access to all services in it.
Just get the entity manager service and query for needed parameters and register them in container.
Define Compiler pass:
# src/Acme/YourBundle/DependencyInjection/Compiler/ParametersCompilerPass.php
class ParametersCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$param1 = $em->getRepository('Acme:Params')->find(1);
$param2 = $em->getRepository('Acme:Params')->find(2);
$container->setParameter('param1', $param1);
$container->setParameter('param2', $param2);
}
}
In the bundle definition class you need to add compiler pass to your container
# src/Acme/YourBundle/AcmeYourBundle.php
class AcmeYourBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ParametersCompilerPass(), PassConfig::TYPE_AFTER_REMOVING);
}
}
PassConfig::TYPE_AFTER_REMOVING means that this CompilerPass will be processed almost after all other compiler passes and in this moment you will already have every service injected.

How can I listen to console events in symfony?

I'm trying to hook into symfonys console events with the symfony standard edition (2.3), but it just won't work.
I created a listener according to their example and follow the guides on event registration:
namespace Acme\DemoBundle\EventListener;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\ConsoleEvents;
class AcmeCommandListener
{
public function onConsoleCommand(ConsoleCommandEvent $event) {
// get the output instance
$output = $event->getOutput();
// get the command to be executed
$command = $event->getCommand();
// write something about the command
$output->writeln(sprintf('Before running command <info>%s</info>', $command->getName()));
}
}
and someone on the mailing list told me to register it as event in the service container. So I did this:
services:
kernel.listener.command_dispatch:
class: Acme\DemoBundle\EventListener\AcmeCommandListener
tags:
- { name: kernel.event_listener, event: console.command }
But obviously the tagging is not correct and I can't find the correct names for that. How would I do that?
Platform\EventListener\Console\InitListener:
tags:
- { name: kernel.event_listener, event: console.command, priority: 1024 }
<?php
class CustomListener
{
public function onConsoleCommand(ConsoleCommandEvent $event): void
{
//do somehting
}
}
?>
So, I finally got it. The above code in the original post ist fully working, but I defined my services.yml within my bundle not in the application config app/config.yml. This means, the configuration was never loaded. I had to import the configuration via container extensions:
# Acme/DemoBundle/DependencyInjection/AcmeDemoExtension.php
namespace Acme\DemoBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
class AcmeDemoExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}
and
# Acme/DemoBundle/DependencyInjection/Configuration.php
namespace Acme\DemoBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('headwork_legacy');
return $treeBuilder;
}
}
Though I guess you can even leave out the $configuration = new Configuration(); part and the Configuration class.
Your tags event listener name must be a console.event_listener. It's has helped me to resolve this issue.
services:
kernel.listener.command_dispatch:
class: Acme\DemoBundle\EventListener\AcmeCommandListener
tags:
- { name: console.event_listener, event: console.command }

Symfony2 custom password encoder and password hash update

I'm learning Symfony2 by moving some wordpress blog to Symfony. I'm stuck with login procedure. Wordpress uses non standard password hashing like $P$.... and I want to check users against old password hash when they login and when password is correct, rehash it to bcrypt. So far I created custome encoder class to use with symfony security mechanism.
<?php
namespace Pkr\BlogUserBundle\Service\Encoder;
use PHPassLib\Application\Context;
use Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder;
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
use Symfony\Component\Security\Core\Util\SecureRandom;
class WpTransitionalEncoder implements PasswordEncoderInterface
{
public function __construct($cost = 13)
{
$secure = new SecureRandom();
$this->_bcryptEncoder = new BCryptPasswordEncoder($secure, $cost);
}
public function isPasswordValid($encoded, $raw, $salt)
{
if (preg_match('^\$P\$', $encoded)) {
$context = new Context();
$context->addConfig('portable');
return $context->verify($raw, $encoded);
}
return $this->_bcryptEncoder->isPasswordValid($encoded, $raw, $salt);
}
public function encodePassword($raw, $salt)
{
return $this->_bcryptEncoder->encodePassword($raw, $salt);
}
}
I'm using it as a service:
#/src/Pkr/BlogUserBundle/Resources/config/services.yml
services:
pkr_blog_user.wp_transitional_encoder:
class: Pkr\BlogUserBundle\Service\Encoder\WpTransitionalEncoder
And in security.yml:
#/app/config/security.yml
security:
encoders:
Pkr\BlogUserBoundle\Entity\User:
id: pkr_blog_user.wp_transitional_encoder
cost: 15
My questions are:
How do I pass parameters to my encoder service form within security.yml?
I'm asking because cost: 15 does not work.
Where should I put password hash update logic? I was thinking that maby just after password validation something like this:
public function isPasswordValid($encoded, $raw, $salt)
{
if (preg_match('^\$P\$', $encoded)) {
$context = new Context();
$context->addConfig('portable');
$isValid = $context->verify($raw, $encoded);
if ($isValid) {
// put logic here...
}
return $isValid;
}
return $this->_bcryptEncoder->isPasswordValid($encoded, $raw, $salt);
}
but it seem somehow like wrong place for it. So what is the right way?
I'll answer my own question.
I placed parameters for my encoder service inside config.yml
pkr_blog_user:
password_encoder:
cost: 17
They will be passed to my bundle extension class:
# /src/Pkr/BlogUserBundle/DependencyInjection/PkrBlogUserExtension.php
namespace Pkr\BlogUserBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class PkrBlogUserExtension extends Extension
{
/**
* {#inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
if ($config['password_encoder']['cost'] < 10) {
$config['password_encoder']['cost'] = sprintf('%02d', $config['password_encoder']['cost']);
}
$container->setParameter('pkr_blog_user.wp_transitional_encoder.cost', $config['password_encoder']['cost']);
}
}
I found out that I could use my own authentication success handler so there is a good place to put password rehash logic. Unfortunately when using custom handler symfony2 won't pass config to class constructor but I found a way to make it work. I described it here:
https://stackoverflow.com/a/15988399/1089412

Symfony2 access Doctrine Entity Manager in custom class

UPDATE:
In case you need to work with Entity Manager in a custom class, you could go this way:
put this code in your bundle:
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpFoundation\Request;
require_once DIR . '/../../../app/bootstrap.php.cache';
require_once DIR . '/../../../app/AppKernel.php';
class ApplicationBoot {
private static $kernel;
public static function getContainer() {
if(self::$kernel instanceof \AppKernel) {
if(!self::$kernel->getContainer() instanceof Container){
self::$kernel->boot();
}
return self::$kernel->getContainer();
}
$environment = 'prod';
if (!array_key_exists('REMOTE_ADDR', $_SERVER) || in_array(#$_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1', 'localhost'))) {
$environment = 'dev';
}
self::$kernel = new \AppKernel($environment, false);
self::$kernel->boot();
return self::$kernel->getContainer();
}
public static function shutDown() {
self::$kernel->shutdown();
}}
So now you can access EntityManager:
$container = ApplicationBoot::getContainer();
$entityManager = $container->get('doctrine')->getEntityManager();
I have not seen a service file like this:
arguments:
entityManager: "#doctrine.orm.entity_manager"
Probably should be:
arguments: [#doctrine.orm.entity_manager]
UPDATE:
Based on some comments it appears that you are trying to do:
$job = new PostJob();
And expecting that entity manager will somehow be passed. And that is just not the way things work. You need to do:
$job = $this->get('postjob.service.id');
In order to have the Symfony 2 dependency injection work. Review the chapter in the manual on services. It might seem a bit over whelming at first but once you get a few services working then it becomes second nature.
To load the services.yml from your bundle, you need to provide an extension class:
// src/Vendor/YourBundle/DedendencyInjection/VendorYourBundleExtension.php
namespace Vendor\YourBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension,
Symfony\Component\DependencyInjection\ContainerBuilder,
Symfony\Component\DependencyInjection\Loader\YamlFileLoader,
Symfony\Component\Config\FileLocator;
class VendorYourBundleExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}

Resources