I look for the way to inject more parameters into symfony configuration cache. Currently, I use kernel.cache_warmer hook to my class in services.yml to generate another yml file in a directory. Then, it will be include in the symfony configuration cache, are there any possible way to inject a variable into generated config cache without need to create the Yml file?
Basically, I would like to make cache key changed everytime when run app/console cache:clear. Here is my service,
services.yml
imports:
- { resource: version.yml }
services:
cacheManager:
class: "%cacheManager.class%"
calls:
- [ setCachePrefix, ["%memcache.deploymentPrefix%"]]
memcacheDataVersioning:
class: WarmUpListener
tags:
- { name: kernel.cache_warmer, priority: 0}
WarmUpListener.php
class WarmUpListener implements CacheWarmerInterface{
public function warmUp($dir)
{
$array = ['parameters' => ['memcache.deploymentPrefix' => date('Ymd')]];
$dumper = new Dumper();
$yaml = $dumper->dump($array);
file_put_contents(__DIR__ . '/../Resources/config/version.yml', $yaml);
}
public function isOptional()
{
return false;
}
}
I have added to the DependencyInjection/*Extension class as below
DependencyInjection/somethingExtension.php
$container->setParameter('memcache.deploymentPrefix', date('Ymd') );
This will help to inject the variable in the configuration cached without need to make the Yml file and can removed all warmUp hookup on the question.
Related
Hey Im trying API Platform with Symfony 6.0 (and PHP 8)
Everything was going alright until I needed to make a DataPersister so I can encrypt the user password before saving it
I literally copied the example in the docs (here https://api-platform.com/docs/core/data-persisters/#decorating-the-built-in-data-persisters) since my entity is actually called User:
<?php
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use App\Entity\User;
final class UserDataPersister implements ContextAwareDataPersisterInterface
{
private $decorated;
public function __construct(ContextAwareDataPersisterInterface $decorated)
{
$this->decorated = $decorated;
}
public function supports($data, array $context = []): bool
{
return $this->decorated->supports($data, $context);
}
public function persist($data, array $context = [])
{
$result = $this->decorated->persist($data, $context);
return $result;
}
public function remove($data, array $context = [])
{
return $this->decorated->remove($data, $context);
}
}
I just removed the mailer parts cause what Im trying to do has nothing to do with that. Other than that, it is exactly equal to the example
But it wont work. I get this error when I try to persist:
Cannot autowire service "App\DataPersister\UserDataPersister": argument "$decorated" of method "__construct()" references interface "ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface" but no such service exists. Try changing the type-hint to "ApiPlatform\Core\DataPersister\DataPersisterInterface" instead.
I tried doing what the error suggests but it seems to throw the framework in some endless loop or something cause I get a memory error. And in any case, I need a ContextAwareDataPersisterInterface
Am I doing something wrong or missing something here? Or this a bug? The docs says:
"If service autowiring and autoconfiguration are enabled (they are by default), you are done!"
They are both enabled in services.yaml:
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
I works if I explicity define the service in services.yaml:
App\DataPersister\UserDataPersister:
bind:
$decorated: '#api_platform.doctrine.orm.data_persister'
edit: sorry, the documentation actually says we have to do that, I missed it. My bad.
Problem solved
I have a bundle that has a listener which I have configured:
class Configuration implements ConfigurationInterface
{
/**
* {#inheritdoc}
*/
public function getConfigTreeBuilder ()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('mybundle_name');
$rootNode
->children()
->scalarNode('name')->defaultValue('value')
->end()
;
return $treeBuilder;
}
}
I also have a listener which has a few services injected into, primarily doctrine and the container parameters:
services:
app.router_subscriber:
class: MyBundle\EventSubscriber\RequestSubscriber
calls:
- [setEntityManager, ['#doctrine.orm.entity_manager']]
- [setContainer, ['#service_container']]
tags:
- { name: kernel.event_subscriber }
When I dump the $this->container I can see the parameters except my own defined above.
When I run
bin/console config:dump-reference MyBundle
I do see what I am expecting
What am I missing to have my bundle parameters get merged into the application parameters? I am seeing third party bundles listed but not my own bundle. I followed the docs as verbatim as I could so the conventions have been followed as far as I am aware...
EDIT | I haven't created a bundle config.yml file - I assumed the Configuraiton object did that for me - setting the schema and default values - which could be overridden by application configs (if desired). Do I need to specifcy a bundle config.yml and import into application something like this (Merge config files in symfony2)?
Ideas?
I wrote a couple blog posts showing how you can set bundle configuration defaults using YAML files, and a follow-up on how to automatically set bundle configuration values as container parameters. This was for Symfony2 and written in 2014, and the particular section of the Symfony documentation I link to disappeared from Symfony 2.3 onward, but the same concept still applies.
The main takeaway from those posts is that you can set your configuration values as container parameters in your bundle's Extension class via the load() method manually like so:
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter($this->getAlias().'.name', $config['name']);
}
Notice that you can call $this->getAlias() to get the bundle's root name (mybundle_name). Using the above call you would then have a parameter defined as mybundle_name.name which you could then override in your application's config.yml if need be.
My config is
jms_serializer:
metadata:
auto_detection: true
directories:
NameOfBundle:
namespace_prefix: ""
path: "#VendorNameOfBundle/Resources/config/serializer"
My YML file named Entity.Project.yml contains
Vendor\NameOfBundle\Entity\Project:
exclusion_policy: ALL
properties:
id:
expose: true
I am loading the serializer like so from within a Controller
$serializer = SerializerBuilder::create()
->configureListeners(function(EventDispatcher $dispatcher) {
$dispatcher->addSubscriber(new ProjectSubscriber($this->container));
})
->addDefaultListeners()
->build();
This completely ignored my YML file and exposes all fields from the Project. I have cleared the cache.
But if I use this instead without the custom subscriber, then the exclusions work
$serializer = $this->get("jms_serializer");
Even explicitly adding a dir does not work either
$serializer = SerializerBuilder::create()
->configureListeners(function(EventDispatcher $dispatcher) {
$dispatcher->addSubscriber(new ProjectSubscriber($this->container));
})
->addDefaultListeners()
->addMetadataDir(realpath($this->get('kernel')->getRootDir()."/../") . '/src/Vendor/NameOfBundle/Resources/config/serializer')
->build();
The docs are not clear on how this path should befined. The above method does not error, but does not pull in the YML files. The below method errors and says the directory does not exist;
$serializer = SerializerBuilder::create()
->configureListeners(function(EventDispatcher $dispatcher) {
$dispatcher->addSubscriber(new ProjectSubscriber($this->container));
})
->addDefaultListeners()
->addMetadataDir('#VendorNameOfBundle/Resources/config/serializer')
->build();
How do I make the JMS Serializer look at my YML file in order to exclude the fields and also use the Subscriber?
As i see from documentation you need to setup your Yaml files:
it is necessary to configure a metadata directory where those files are located:
$serializer =
JMS\Serializer\SerializerBuilder::create()
->addMetadataDir($someDir)
->build();
For more information read manual.
This was helpful Using JMSSerialize to serialize Doctrine2 Entities that follow SimplifiedYamlDriver convention
It would appear that the file names needs to be completely different if you do not specify a namespace. I never thought to specify a namespace as this is not mentioned in the main docs.
If there is no namespace then the addMetaDir usage is fine but you also need to make sure your file names look like this
Vendor.NameOfBundle.Entity.Project.yml
In Symfony2, everytime I clear my cache via the console:
php app/console cache:clear
The console prints out the contents of my services.yml file! If I manually delete the cache via rm -rf app/cache/* (which I have to do since my console user doesn't have access to the apache user www-data, for some reason, despite being in the same group because the files are created as 640 instead of 660), then the public website also prints it out the FIRST time the page is loaded and the cache is generated.
NOTE: this prints out even if services.yml is NOT loaded in the app/config/config.yml (just by existing, somehow it's being referenced)
We import the services.yml file:
# /app/config/config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: "#AcmeBundle/Resources/config/services.yml" }
Then set global services in the services.yml file:
# /src/Acme/Bundle/Resources/config/services.yml
# TODO: ALERT! this prints out whenever I clear the cache...
services:
#This is a service so that we can access the view object anywhere
acme.view:
class: Acme\Bundle\Controller\ViewController
arguments: [ #doctrine.orm.entity_manager ]
Question: Any ideas why this file is printing out every time I clear the cache?
This was caused by changing the /Acme/Bundle/Resources/config/services.yml services parameters from PHP to YAML format (I created as PHP originally in my testing).
The reference to the service parameters file is hard coded in the /Acme/Bundle/DependencyInjection/AcmeBundleExtension.php in two places.
Here is the broken code:
class AcmeBundleExtension extends Extension
{
/**
* {#inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}
Since the services.yml was being loaded as a PHP file, it was just printing out text whenever the cache was recreated. Amazingly, all the services still actually loaded somehow...!
So take note, if you change the config file from PHP to YAML (or vice versa) you have to update:
$loader->load('services.yml');
(which I did)
But also you must update the loader function from Loader\PhpFileLoader to Loader\YamlFileLoader:
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
Of course that seems obvious, but if you are new to Symfony, take note that converting formats of your service config file requires more than just changing your file name.
I have added a setting to my config.yml file as such:
app.config:
contact_email: somebody#gmail.com
...
For the life of me, I can't figure out how to read it into a variable. I tried something like this in one of my controllers:
$recipient =
$this->container->getParameter('contact_email');
But I get an error saying:
The parameter "contact_email" must be
defined.
I've cleared my cache, I also looked everywhere on the Symfony2 reloaded site documentation, but I can't find out how to do this.
Probably just too tired to figure this out now. Can anyone help with this?
Rather than defining contact_email within app.config, define it in a parameters entry:
parameters:
contact_email: somebody#gmail.com
You should find the call you are making within your controller now works.
While the solution of moving the contact_email to parameters.yml is easy, as proposed in other answers, that can easily clutter your parameters file if you deal with many bundles or if you deal with nested blocks of configuration.
First, I'll answer strictly the question.
Later, I'll give an approach for getting those configs from services without ever passing via a common space as parameters.
FIRST APPROACH: Separated config block, getting it as a parameter
With an extension (more on extensions here) you can keep this easily "separated" into different blocks in the config.yml and then inject that as a parameter gettable from the controller.
Inside your Extension class inside the DependencyInjection directory write this:
class MyNiceProjectExtension extends Extension
{
public function load( array $configs, ContainerBuilder $container )
{
// The next 2 lines are pretty common to all Extension templates.
$configuration = new Configuration();
$processedConfig = $this->processConfiguration( $configuration, $configs );
// This is the KEY TO YOUR ANSWER
$container->setParameter( 'my_nice_project.contact_email', $processedConfig[ 'contact_email' ] );
// Other stuff like loading services.yml
}
Then in your config.yml, config_dev.yml and so you can set
my_nice_project:
contact_email: someone#example.com
To be able to process that config.yml inside your MyNiceBundleExtension you'll also need a Configuration class in the same namespace:
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root( 'my_nice_project' );
$rootNode->children()->scalarNode( 'contact_email' )->end();
return $treeBuilder;
}
}
Then you can get the config from your controller, as you desired in your original question, but keeping the parameters.yml clean, and setting it in the config.yml in separated sections:
$recipient = $this->container->getParameter( 'my_nice_project.contact_email' );
SECOND APPROACH: Separated config block, injecting the config into a service
For readers looking for something similar but for getting the config from a service, there is even a nicer way that never clutters the "paramaters" common space and does even not need the container to be passed to the service (passing the whole container is practice to avoid).
This trick above still "injects" into the parameters space your config.
Nevertheless, after loading your definition of the service, you could add a method-call like for example setConfig() that injects that block only to the service.
For example, in the Extension class:
class MyNiceProjectExtension extends Extension
{
public function load( array $configs, ContainerBuilder $container )
{
$configuration = new Configuration();
$processedConfig = $this->processConfiguration( $configuration, $configs );
// Do not add a paramater now, just continue reading the services.
$loader = new YamlFileLoader( $container, new FileLocator( __DIR__ . '/../Resources/config' ) );
$loader->load( 'services.yml' );
// Once the services definition are read, get your service and add a method call to setConfig()
$sillyServiceDefintion = $container->getDefinition( 'my.niceproject.sillymanager' );
$sillyServiceDefintion->addMethodCall( 'setConfig', array( $processedConfig[ 'contact_email' ] ) );
}
}
Then in your services.yml you define your service as usual, without any absolute change:
services:
my.niceproject.sillymanager:
class: My\NiceProjectBundle\Model\SillyManager
arguments: []
And then in your SillyManager class, just add the method:
class SillyManager
{
private $contact_email;
public function setConfig( $newConfigContactEmail )
{
$this->contact_email = $newConfigContactEmail;
}
}
Note that this also works for arrays instead of scalar values! Imagine that you configure a rabbit queue and need host, user and password:
my_nice_project:
amqp:
host: 192.168.33.55
user: guest
password: guest
Of course you need to change your Tree, but then you can do:
$sillyServiceDefintion->addMethodCall( 'setConfig', array( $processedConfig[ 'amqp' ] ) );
and then in the service do:
class SillyManager
{
private $host;
private $user;
private $password;
public function setConfig( $config )
{
$this->host = $config[ 'host' ];
$this->user = $config[ 'user' ];
$this->password = $config[ 'password' ];
}
}
I have to add to the answer of douglas, you can access the global config, but symfony translates some parameters, for example:
# config.yml
...
framework:
session:
domain: 'localhost'
...
are
$this->container->parameters['session.storage.options']['domain'];
You can use var_dump to search an specified key or value.
In order to be able to expose some configuration parameters for your bundle you should consult the documentation for doing so. It's fairly easy to do :)
Here's the link: How to expose a Semantic Configuration for a Bundle
Like it was saying previously - you can access any parameters by using injection container and use its parameter property.
"Symfony - Working with Container Service Definitions" is a good article about it.
I learnt a easy way from code example of http://tutorial.symblog.co.uk/
1) notice the ZendeskBlueFormBundle and file location
# myproject/app/config/config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: #ZendeskBlueFormBundle/Resources/config/config.yml }
framework:
2) notice Zendesk_BlueForm.emails.contact_email and file location
# myproject/src/Zendesk/BlueFormBundle/Resources/config/config.yml
parameters:
# Zendesk contact email address
Zendesk_BlueForm.emails.contact_email: dunnleaddress#gmail.com
3) notice how i get it in $client and file location of controller
# myproject/src/Zendesk/BlueFormBundle/Controller/PageController.php
public function blueFormAction($name, $arg1, $arg2, $arg3, Request $request)
{
$client = new ZendeskAPI($this->container->getParameter("Zendesk_BlueForm.emails.contact_email"));
...
}
Inside a controller:
$this->container->getParameter('configname')
to get the config from config/config.yaml:
parameters:
configname: configvalue