I must be missing something. I have a "ConfigBundle" inside of which I have this services.yml (in Acme/ConfigBundle/Resources/config):
services:
config.manager:
class: Acme\ConfigBundle\Manager\ConfigManager
arguments: ["#doctrine.orm.entity_manager", "#config.storage"]
This service works as expected.
Next I have a Configuration.php:
class Configuration implements ConfigurationInterface
{
/**
* {#inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('config');
$rootNode
->children()
->scalarNode('storage')->isRequired()->cannotBeEmpty()->end()
->variableNode('defaults')->end()
->end();
;
return $treeBuilder;
}
}
Which also works as expected. And at the end I have ConfigExtensions.php:
class ConfigExtension extends ConfigurableExtension
{
/**
* {#inheritdoc}
*/
protected function loadInternal(array $mergedConfig, ContainerBuilder $container)
{
// Set storage service.
$container->setAlias('config.storage', $mergedConfig['storage']);
if (!empty($mergedConfig['defaults'])) {
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
//$container->getDefinition('config.manager')->addArgument('test');
$container->getDefinition('config.manager')->addMethodCall('setDefaults', [$mergedConfig['defaults']]);
var_dump('It is being invoked!');
}
}
}
Which doesn't work as expected because method addMethodCall doesn't work. addMethodCall executes, I checked this, but the call is never invoked and is not visible in appDevDebugProjectContainer.xml in cache. When I add:
calls:
- [setDefaults, ['test']]
To my service definition in services.yml it gets invoked, but I cannot achieve it by using addMethodCall in Extension. I'm not getting any errors, I cleared cache directory, more than once, when trying to figure this out. Any ideas why this doesn't work?
Related
I have a service who intercepts the events of Sentry. I'm using a function called beforeSend.
I would to load a json file who contains the data to scrub or to keep. It's a service and I build my constructor with a similar way than others, but the "$this" context doesn't exist when I'm in the debugger in this function.
The kernel is in the Global variables, but I think it's not a good idea... I only would to get the root dir and it's all, but I don't find how to do this in this class... The constructor seems useless.
Someone could help me with a similar experience ?
EDIT :
Service :
namespace App\Services;
use Sentry\Event;
use Symfony\Component\HttpKernel\KernelInterface;
class SentryBeforeSendService
{
private static $rootDir;
public function __construct(KernelInterface $kernel)
{
self::$rootDir = $kernel->getRootDir();
}
/**
* Scrubs the value of all TARGET_PARAMETERS
* in the event's request.
*
* #param Event $event
*
* #return Event
*/
public function beforeSend(Event $event)
{
$rootDir = self::$rootDir;
$event->setRequest(self::scrubRequest($event->getRequest(), $rootDir));
try {
$composerData = json_decode(file_get_contents($rootDir.'/../composer.json'), true);
$version = $composerData['version'];
$event->setRelease($version);
} catch (\Exception $e) {
//do nothing
}
return $event;
}
/**
* Scrubs GET and POST parameters
*
* #param array $request
*
* #return array
*/
private static function scrubRequest(array $request, $rootDir)
{
// DO SOMETHING WITH $rootDir to scrub data with external file
}}
services.yml :
app.service.sentry_before_send:
class: 'App\Services\SentryBeforeSendService'
arguments: ['#kernel']
config_prod.yml :
sentry:
dsn: "%sentry_dsn%"
options:
environment: "%sentry_environment%"
# release: '%env(VERSION)%' #overridden from composer.json version in SentryBeforeSendService::beforeSend
before_send: 'App\Services\SentryBeforeSendService::beforeSend'
But it seems the construct never happened.
Thank you very much.
I was unable to inject a parameter, but I found a way to get the project_root from my method. Half victory ...
config_prod.yml:
sentry:
dsn: "%sentry_dsn%"
options:
environment: "%sentry_environment%"
# release: '%env(VERSION)%' #overridden from composer.json version in SentryBeforeSendService::beforeSend
before_send: 'App\Services\SentryBeforeSendService::beforeSend'
project_root: '%kernel.project_dir%'
Service :
<?php
namespace App\Services;
use Sentry\Event;
use Sentry\State\Hub;
class SentryBeforeSendService
{
private static $projectRoot;
/**
* Scrubs the value of all TARGET_PARAMETERS
* in the event's request.
*
* #param Event $event
*
* #return Event
*/
public function beforeSend(Event $event)
{
$sentryClient = Hub::getCurrent()->getClient();
self::$projectRoot = $sentryClient->getOptions()->getProjectRoot();
$event->setRequest(self::scrubRequest($event->getRequest()));
try {
$composerData = json_decode(file_get_contents(self::$projectRoot.'/composer.json'), true);
$version = $composerData['version'];
$event->setRelease($version);
} catch (\Exception $e) {
//do nothing
}
return $event;
}}
Hope it'll help someone else.
Thank you for answers.
You can inject the kernel.project_dir parameter in your service constructor with a named parameter:
In your services.yml file:
services:
_defaults:
bind:
string $kernelProjectDir: '%kernel.project_dir%'
Then in your service:
public function __construct(string $kernelProjectDir)
{
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.
There's something that I'm missing.
Let's say I have a database from wich I start a new project. This database will be created after restoring a .dump file so it will also contain some "static" (static inserts from scratch db to populate it with "unchangeable" data) information inside it.
As this information will never change and as I need some of them (i.e.: an id for retrieve exactly a specific entity) I've thought to place them into a configuration file that is processed with the DIC & co.
First question: is this approach right or is better to create a specific .php file with some configuration values?
Second question: Why parameters I've defined through DIC aren't available?
I've create a folder called DependencyInjection inside my bundle, I've created MyCompanyMyBundleNameExtension file and create a Configuration file. I'm sure that files are placed in the right place as If I "remove" them all the things will mess up (just for people who will comment with "are you sure that ..." yes, I'm sure) I've also included inside my bundle "main" file the following
public function build(ContainerBuilder $container)
{
parent::build($container);
}
but if I dump (I use this approach as inside controller the exception "The parameter "xxxxx" must be defined. is raised") the config file inside Extension I can notice that is empty. Have I missing something?
UPDATE
This is Configuration.php file
namespace Foo\FooBundle\DependencyInjection;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
class Configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree.
*
* #return TreeBuilder
*/
public function getConfigTreeBuilder()
{
$tree_builder = new TreeBuilder();
$root_node = $tree_builder->root('foo_bundle');
$root_node
->children()
->arrayNode('text_type')
->children()
->integerNode('title')->defaultValue(1)->end()
->integerNode('description')->defaultValue(2)->end()
->end()
->end()
->end()
;
return $tree_builder;
}
}
and this is the Extension
<?php
namespace Foo\BookingEngineBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\Definition\Processor;
class FooFooBundleExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$processor = new Processor();
$configuration = new Configuration();
$config = $processor->processConfiguration($configuration, $configs);
error_log(print_r($config, true)); //empty
$this->remapParametersNamespaces($config, $container, array(
'' => array(
'text_type' => 'foo_bundle.text_type.%s',
),
));
}
protected function remapParametersNamespaces(array $config, ContainerBuilder $container, array $namespaces)
{
foreach ($namespaces as $ns => $map) {
if ($ns) {
if (!array_key_exists($ns, $config)) {
continue;
}
$namespaceConfig = $config[$ns];
} else {
$namespaceConfig = $config;
}
if (is_array($map)) {
$this->remapParameters($namespaceConfig, $container, $map);
} else {
foreach ($namespaceConfig as $name => $value) {
$container->setParameter(sprintf($map, $name), $value);
}
}
}
}
protected function remapParameters(array $config, ContainerBuilder $container, array $map)
{
foreach ($map as $name => $paramName) {
if (array_key_exists($name, $config)) {
$container->setParameter($paramName, $config[$name]);
}
}
}
UPDATE 2
If I run from command line the following php app/console config:dump-reference foo_bundle I can see configuration printed out. I'm pretty confused.
UPDATE 3
I got it finally:
it seems that you need to specify at least one parameter into your config.yml main file, otherwise the "merge" option performed into YourBundleNameExtension will fail and return an empty array.
Subquestion: there isn't a method that I can follow to free me from write parateter into parameters.yml?
I got it finally:
it seems that you need to specify at least one parameter (related to your Config D.I. file) into your config.yml main file, otherwise the "merge" option performed into YourBundleNameExtension will fail and return an empty array.
Looking at my Configuration.php file, in order to make the code works without specify parameter directly into parameters.yml file, modify Configuration.php that way:
<?php
namespace Koobi\BookingEngineBundle\DependencyInjection;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
class Configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree.
*
* #return TreeBuilder
*/
public function getConfigTreeBuilder()
{
$tree_builder = new TreeBuilder();
$root_node = $tree_builder->root('koobi_booking_engine');
$root_node
->children()
->arrayNode('text_type')
->cannotBeOverwritten()
->addDefaultsIfNotSet()
->children()
->integerNode('title')->defaultValue(1)->end()
->integerNode('description')->defaultValue(2)->end()
->end()
->end()
->end()
;
return $tree_builder;
}
}
the key is ->addDefaultsIfNotSet() builder's method
How do I went to solution?
Look at Extension file: you should notice the
$config = $processor->processConfiguration($configuration, $configs);
snippet of code.
If you open use Symfony\Component\Config\Definition\Processor; and analyze processConfiguration() method, you'll see that $configs - that represent config.yml content - will be merged to $configuration (that represent Configuration.php, so the DIC file that extenion will manage).
So the idea that a common key should be present somewhere came in my mind: after that successful check I've started to search for some methodology that helps me avoid the explicit process of writing parameters into config.yml (that wasn't what i want) and now all works like a charm
SIDE NOTE
I still don't know if this is a valid approach so I'm not gonna check this as correct answer.
I'm working in a project using Symfony 2,
I'm using Assetic with rewrite and less filter, and it work fine,
Now I'm planing to let administrator (connected user) to controle some features in css like font and main color.
The problem that I'm facing is :
- how can I proceed to integrate these css changes from entity to the css management
I can't let assetic use routing rule to include custom css
Eaven if I success to get this work, every time I have a changes to the custom css I have to install assets to web folder and make the assetic:dump and clearing cache from a controller.
If you (or someone else) still need this:
I solved this by putting all generic CSS in a asset handled by Assetic like usual and putting the dynamic CSS generation in a Controller action and rendering the CSS with Twig.
As suggested by Steffen you should put the dynamic CSS in a Twig template.
But now you might suffer from that part of the css being a full request to a symfony application instead of a css (HTTP 302 and such) which increases server load.
Thats why I would advise you to do 3 things (you can skip step 2 if your css doesn't change without interaction, e.g. date based):
Implement a service which caches the current output to e.g. web/additional.css.
Write and register a RequestListener to update the css regularly
Extend all controller actions that could introduce changes to the css with the service call
Example (assumes you use Doctrine and have an entity with some color information):
Service
<?php
//Acme\DemoBundle\Service\CSSDeployer.php
namespace Acme\DemoBundle\Service;
use Doctrine\ORM\EntityManager;
class CSSDeployer
{
/**
* #var EntityManager
*/
protected $em;
/**
* Twig Templating Service
*/
protected $templating;
public function __construct(EntityManager $em, $templating)
{
$this->em = $em;
$this->templating = $templating;
}
public function deployStyle($filepath)
{
$entity = $this->em->getRepository('AcmeDemoBundle:Color')->findBy(/* your own logic here */);
if(!$entity) {
// your error handling
}
if(!file_exists($filepath)) {
// your error handling, be aware of the case where this service is run the first time though
}
$content = $this->templating->render('AcmeDemoBundle:CSS:additional.css.twig', array(
'data' => $entity
));
//Maybe you need to wrap below in a try-catch block
file_put_contents($filepath, $content);
}
}
Service Registration
#Acme\DemoBundle\Resources\config\services.yml
services:
#...
css_deployer:
class: Acme\DemoBundle\Service\CSSDeployer
arguments: [ #doctrine.orm.entity_manager, #templating ]
RequestListener
<?php
//Acme\DemoBundle\EventListener\RequestListener.php
namespace Acme\DemoBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Debug\Exception\ContextErrorException;
use \DateTime;
use Doctrine\ORM\EntityManager;
class RequestListener
{
/**
* #var ContainerInterface
*/
protected $container;
/**
* #var EntityManager
*/
protected $em;
public function __construct(ContainerInterface $container, $em)
{
$this->container = $container;
$this->em = $em;
}
/**
* Checks filemtime (File modification time) of web/additional.css
* If it is not from today it will be redeployed.
*/
public function onKernelRequest(GetResponseEvent $event)
{
$kernel = $event->getKernel();
$container = $this->container;
$path = $container->get('kernel')->getRootDir().'/../web'.'/additional.css';
$time = 1300000000;
try {
$time = #filemtime($path);
} catch(ContextErrorException $ex) {
//Ignore
} catch(\Exception $ex) {
//will never get here
if(in_array($container->getParameter("kernel.environment"), array("dev","test"))) {
throw $ex;
}
}
if($time === FALSE || $time == 1300000000) {
file_put_contents($path, "/*Leer*/");
$time = 1300000000;
}
$modified = new \DateTime();
$modified->setTimestamp($time);
$today = new \DateTime();
if($modified->format("Y-m-d")!= $today->format("Y-m-d")) {
//UPDATE CSS
try {
$container->get('css_deployer')->deployStyle($path);
} catch(\Exception $ex) {
if(in_array($container->getParameter("kernel.environment"), array("dev","test"))){
throw $ex;
}
}
} else {
//DO NOTHING
}
}
}
RequestListener registration
#Acme\DemoBundle\Resources\config\services.yml
acme_style_update_listener.request:
class: Acme\DemoBundle\EventListener\RequestListener
arguments: [ #service_container, #doctrine.orm.entity_manager ]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Controller actions
public function updateAction()
{
// Stuff
$path = '....';
$this->get('css_deployer')->deployStyle($path);
}
Hope this helps someone in the future.
I've a serious problem with a Symfony 2.0.16 installation. Cache file generated for a simple service is wrong.
One service replacer.factory, sort of factory one. I'll show only a simplified version:
/**
* #DI\Service("replacer.factory")
*/
class ReplacerFactory
{
/**
* #DI\InjectParams({"container" = #DI\Inject("service_container")})
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getReplacer($object)
{
$replacer = new NewsletterReplacer($this->container);
// Return the instance of NewsletterReplacer class
return $replacer->setInstance($object);
}
}
And this is the instance returned, again a bit simplified:
class NewsletterReplacer
{
private $container;
private $instance;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function setInstance(Newsletter $newsletter)
{
$this->instance = $newsletter;
}
}
For some reason, cache file generated is completely wrong.
In fact, this is part of appDevProjectContainer.php file, after the command php app/console cache:clear --env=dev --no-debug:
protected function getReplacer_FactoryService()
{
return $this->services['replacer.factory']
= new \Acme\HelloBundle\Service\Replacer\NewsletterReplacer();
}
It should be instead:
protected function getReplacer_FactoryService()
{
return $this->services['replacer.factory']
= new \Acme\HelloBundle\Service\Replacer\ReplacerFactory($this);
}
So, what's wrong and how can i solve it?
It was a PHP bug with annotation along with JMSDiExtraBundle, see this issue. Solved updating PHP 5.3.3-7.