Symfony 5.4 environmental variables in services.yaml::parameters - php

Recently I decided to use Symfony 5.4 container in one of the projects to provide some comprehensive DI. It works well as usual, until I tried to use some env vars in services.yaml::parameters section.
Docs state that to bind to an env var I should
# services.yaml
parameters:
my_var: '%env(SOME_ENV_VAR)%'
and it will be resolved from an env var on first call. Okay. I did it this way and here what I get:
echo $container->getParameter('my_var');
// env_b057c2b619f37f36_SOME_ENV_VAR_222ed306d0932595cbdeada438ccbb2a
I do see SOME_ENV_VAR in both $_SERVER and $_ENV. I also tried Dotenv component to be sure I'm not missing something, but vainly. Any env var turns into this sort of env_{hash}_{VAR_NAME}_{hash} pattern.
I'm not using complete Symfony installation, just some spare components. What I'm missing? Should I manually populate each env var on container build stage?
Container is instantiated as follows:
// $_ENV and $_SERVER already contain `SOME_ENV_VAR` here
require_once __DIR__ . '/vendor/autoload.php';
// `use` statements go here
$containerBuilder = new ContainerBuilder();
$loader = new YamlFileLoader(
$containerBuilder,
new FileLocator(implode(DIRECTORY_SEPARATOR, [__DIR__, 'config']))
);
$loader->load('services.yaml');
$containerBuilder->compile();
$container = $containerBuilder;
$my_var = $container->getParameter('SOME_ENV_VAR');
echo $my_var;

Based on your updated snippet, you need to use:
$container->compile(true); // false is the default
The argument is called resolveEnvPlaceholders. I remember it catching me some time ago.
Still find it a bit puzzling that you have $_ENV set but I'll take your word for it.
This is an old test case I dug up that still works:
use Symfony\Component\DependencyInjection\ContainerBuilder;
class Service
{
public function __construct(string $dsn)
{
echo $dsn . "\n";
}
}
$container = new ContainerBuilder();
$container->autowire(Service::class,Service::class)->setPublic(true)
->setArguments([
'%env(DSN)%'
]);
$container->setParameter('my_var','%env(DSN)%');
putenv('DSN=dsn_value');
$container->compile(true);
$service = $container->get(Service::class);
echo $container->getParameter('my_var') . "\n";

The signature for ContainerBuilder::compile() is:
public function compile(bool $resolveEnvPlaceholders = false)
If you do not pass it true, it won't resolve the environment variables' placeholders.
Additionally, there is an issue with your example.
You are calling:
$my_var = $container->getParameter('SOME_ENV_VAR');
But SOME_ENV_VAR is not a container parameter, but an environment variable. The correct call would be:
$my_var = $container->getParameter('my_var');
The whole thing would be:
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
require_once __DIR__ . '/vendor/autoload.php';
$containerBuilder = new ContainerBuilder();
$loader = new YamlFileLoader(
$containerBuilder,
new FileLocator(implode(DIRECTORY_SEPARATOR, [__DIR__, 'config']))
);
$loader->load(__DIR__ . '/services.yaml');
$containerBuilder->compile(true);
$container = $containerBuilder;
$my_var = $container->getParameter('my_var');
echo $my_var;

Related

How to use a global constant instead of a class constant in PHP version 5.6

I'm using Monolog to create my app's logging system. In the core app file, after I create a new Monolog object, I need to select the log level that I want to print in the log file. I want to use a global constant LOG_LEVEL which could be 'DEBUG', 'INFO', etc. I need the Monolog class to treat its value as a class constant.
// content of config.php
// Here I declare the constants in a separate file called 'config.php'
define("LOG_FILE", "patch/to/my/log.log");
define("LOG_LEVEL", "ERROR");
// content of app.php
require 'config.php';
require 'vendor/autoload.php';
$container['logger'] = function($c) {
$logger = new \Monolog\Logger('logger');
error_log('log level ' . LOG_LEVEL); // prints 'log level ERROR'
$fileHandler = new \Monolog\Handler\StreamHandler(LOG_FILE, $logger::LOG_LEVEL); // here I get the error 'Undefined class constant LOG_LEVEL'
//the normal syntax would be '$logger::ERROR' in this case and that works fine
$logger->pushHandler($fileHandler);
return $logger;
};
I need the 'LOG_LEVEL' constant to be used as 'ERROR' by the monolog class, not as 'LOG_LEVEL'. What am I doing wrong here, been searching an answer for hours now without any luck.
You are now doing $logger::LOG_LEVEL, which is taking the 'LOG_LEVEL' out of the class whichever $logger is (in this case a \Monolog\Logger). That doesn't have a static variable named LOG_LEVEL, thus you get the undefined.
You have just have 'LOG_LEVEL' defined, out of any class, so:
$fileHandler = new \Monolog\Handler\StreamHandler(LOG_FILE, LOG_LEVEL);
Fancy solution:
You could do a static class and include that in your main page:
Class CONFIG {
public static $LOG_LEVEL = 'default Value';
}
// Then you can use this anywhere:
CONFIG::$LOG_LEVEL
$fileHandler = new \Monolog\Handler\StreamHandler(LOG_FILE, CONFIG::$LOG_LEVEL);
The advantage of this is having only one file for configs, not scattered across all kinds of files, which'll become very annoying very fast.
Make a static class and include that...
class GLOBALCONF{
public static $VALUE= 'Something in here';
}
// Use it where you want
GLOBALCONF::$VALUE
You're making this more complicated than it needs to be. Monolog has a function to convert an error level as as string to its own internal value. Just change your code to this:
$fileHandler = new \Monolog\Handler\StreamHandler(LOG_FILE, $logger::toMonologLevel(LOG_LEVEL));
You can also use Logger::getLevels() like the following:
$log_level = $logger->getLevels()[LOG_LEVEL];
$fileHandler = new ...StreamHandler(LOG_FILE, $log_level);

Replace/decorate `translation.reader`

I filled a bug but it seams I'm off :p
I just want to replace the service Symfony\Component\Translation\Reader\TranslationReader (translation.reader) with my own class. In fact I want to know how to replace any service of SF4 if I want
translation.reader::addLoader() is normally called by the framework but if I decorate with my own class addLoader is not called.
Can you tell me how I can just drop replace my own service ?
https://github.com/symfony/symfony/issues/28843
Symfony version(s) affected: 4.1.6
Description
Cannot decorate translation.reader (I want to change the default i18n file loading process)
How to reproduce
copy/adapt Symfony\Component\Translation\Reader\TranslationReader to App\Translation\Reader\TranslationReader
Follow https://symfony.com/doc/current/service_container/service_decoration.html
Modify services.yaml
Symfony\Component\Translation\Reader\TranslationReader: ~
App\Translation\Reader\TranslationReader:
decorates: Symfony\Component\Translation\Reader\TranslationReader
#translation.reader: '#App\Translation\Reader\TranslationReader'
Without the alias : the new service is ignored
With the alias : read() is trigger but not addLoader()
Here are the generated injection file getTranslationReaderService.php :
<?php
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
// Returns the private 'App\Translation\Reader\TranslationReader' shared autowired service.
include_once $this->targetDirs[3].'/vendor/symfony/translation/Reader/TranslationReaderInterface.php';
include_once $this->targetDirs[3].'/src/Translation/Reader/TranslationReader.php';
return $this->privates['App\Translation\Reader\TranslationReader'] = new \App\Translation\Reader\TranslationReader();
By default it looks like :
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
// Returns the private 'translation.reader' shared service.
include_once $this->targetDirs[3].'/vendor/symfony/translation/Reader/TranslationReaderInterface.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Reader/TranslationReader.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/LoaderInterface.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/ArrayLoader.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/FileLoader.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/PhpFileLoader.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/YamlFileLoader.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/XliffFileLoader.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/PoFileLoader.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/MoFileLoader.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/QtFileLoader.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/CsvFileLoader.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/IcuResFileLoader.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/IcuDatFileLoader.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/IniFileLoader.php';
include_once $this->targetDirs[3].'/vendor/symfony/translation/Loader/JsonFileLoader.php';
$this->privates['translation.reader'] = $instance = new \Symfony\Component\Translation\Reader\TranslationReader();
$a = ($this->privates['translation.loader.yml'] ?? $this->privates['translation.loader.yml'] = new \Symfony\Component\Translation\Loader\YamlFileLoader());
$b = ($this->privates['translation.loader.xliff'] ?? $this->privates['translation.loader.xliff'] = new \Symfony\Component\Translation\Loader\XliffFileLoader());
$instance->addLoader('php', ($this->privates['translation.loader.php'] ?? $this->privates['translation.loader.php'] = new \Symfony\Component\Translation\Loader\PhpFileLoader()));
$instance->addLoader('yaml', $a);
$instance->addLoader('yml', $a);
$instance->addLoader('xlf', $b);
$instance->addLoader('xliff', $b);
$instance->addLoader('po', ($this->privates['translation.loader.po'] ?? $this->privates['translation.loader.po'] = new \Symfony\Component\Translation\Loader\PoFileLoader()));
$instance->addLoader('mo', ($this->privates['translation.loader.mo'] ?? $this->privates['translation.loader.mo'] = new \Symfony\Component\Translation\Loader\MoFileLoader()));
$instance->addLoader('ts', ($this->privates['translation.loader.qt'] ?? $this->privates['translation.loader.qt'] = new \Symfony\Component\Translation\Loader\QtFileLoader()));
$instance->addLoader('csv', ($this->privates['translation.loader.csv'] ?? $this->privates['translation.loader.csv'] = new \Symfony\Component\Translation\Loader\CsvFileLoader()));
$instance->addLoader('res', ($this->privates['translation.loader.res'] ?? $this->privates['translation.loader.res'] = new \Symfony\Component\Translation\Loader\IcuResFileLoader()));
$instance->addLoader('dat', ($this->privates['translation.loader.dat'] ?? $this->privates['translation.loader.dat'] = new \Symfony\Component\Translation\Loader\IcuDatFileLoader()));
$instance->addLoader('ini', ($this->privates['translation.loader.ini'] ?? $this->privates['translation.loader.ini'] = new \Symfony\Component\Translation\Loader\IniFileLoader()));
$instance->addLoader('json', ($this->privates['translation.loader.json'] ?? $this->privates['translation.loader.json'] = new \Symfony\Component\Translation\Loader\JsonFileLoader()));
return $instance;
You can see that loaders are not injected when I do the decorating...
I'm not sure exactly if this is the root of your problem, but here are some remarks. Hopefully this will help you find a solution, even though I'm not actually given a full answer to your question.
1) Some translation services in Symfony are called only during the cache warmup phase. Whenever you change your config, or do a bin/console cache:clear, you'll see these classes are run, and they generate translations in your var/cache/<env>/translations/ folder.
2) You can try to make sure that in your cache, the classe loaded by var/cache/<env>/Container<...>/getTranslation_ReaderService.php is yours and not the default one like this:
$this->privates['translation.reader'] =
new \Symfony\Component\Translation\Reader\TranslationReader();
3) I also encountered a similar issue in the dev environment, where I was trying to replace Symfony\Component\Translation\Translator with my own service, and didn't manage to get my methods to be called at first. Part of the explanation was that when the Symfony Profiler is enabled, Symfony does something like this (in src<env>DebugProjectContainer.php>):
$this->services['translator'] = new \Symfony\Component\Translation\DataCollectorTranslator(
($this->privates['translator.default'] ?? $this->getTranslator_DefaultService())
);
and the DataCollectorTranslator itself is a wrapper for whichever translator it gets as its constructor argument.
I know this is not a perfect answer but hopefully this will help you find your way to a solution.
I've managed to make it work... but please feel free to comment
I had to create a TranslatorPass to add loaders to the decorating service injection file.
<?php
namespace App\Translation\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use App\Translation\Reader\TranslationReader;
class TranslatorPass implements CompilerPassInterface
{
private $readerServiceId;
private $loaderTag;
public function __construct(string $readerServiceId = TranslationReader::class, string $loaderTag = 'translation.loader')
{
$this->readerServiceId = $readerServiceId;
$this->loaderTag = $loaderTag;
}
public function process(ContainerBuilder $container)
{
$loaders = array();
$loaderRefs = array();
foreach ($container->findTaggedServiceIds($this->loaderTag, true) as $id => $attributes) {
$loaderRefs[$id] = new Reference($id);
$loaders[$id][] = $attributes[0]['alias'];
if (isset($attributes[0]['legacy-alias'])) {
$loaders[$id][] = $attributes[0]['legacy-alias'];
}
}
if ($container->hasDefinition($this->readerServiceId)) {
$definition = $container->getDefinition($this->readerServiceId);
foreach ($loaders as $id => $formats) {
foreach ($formats as $format) {
$definition->addMethodCall('addLoader', array($format, $loaderRefs[$id]));
}
}
}
}
}
I've put it in the Kernel.php
protected function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new TranslatorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 1000);
}
then
bin/console cache:clear
et voilĂ  !

Integrating Whatsapp API in symfony

I am trying to integrate the WHAnonymous API in my symfony project.
I have included it in my project using composer install and it is now in my vendor folder.
But I am not understanding how to import it into my project!
This is my manager class.
<?php
namespace AppBundle\Managers;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class WhatsAppManager
{
private $test;
/**
* Constructor
*/
public function __construct()
{
$this->test =1;
}
public function sendMessage()
{
$username = ""; // Your number with country code, ie: 34123456789
$nickname = ""; // Your nickname, it will appear in push notifications
$debug = true; // Shows debug log
// Create a instance of WhastPort.
$w = new WhatsProt($username, $nickname, $debug);
var_dump("In send message method");
}
}
?>
I have used
require_once 'whatsprot.class.php';
and
require_once 'Whatsapp\Bundle\Chat-api\src\whatsprot.class.php';
and
use Whatsapp\Bundle\Chat-api\Whatsprot
But it is just not working.
Please tell me the right way to do it!
And is there something i should do when i am using 3rd party vendors in symfony.
I did look into the documentation of the WHanonymous but i found only snippets of code to use it and not the way to import it.
Git repo for WHAnonymous : https://github.com/WHAnonymous
The class doesn't have a namespace, but is correctly loaded by the autoload system created my composer. So you can reference to the class without any include or require directive but simply with a \ as example:
// Create a instance of WhastPort.
$w = new \WhatsProt($username, $nickname, $debug);
Hope this help

How to access dynamic references from other container items?

How can I pass a dynamic dependency from one registered container definition to another? In this case, a generic Database object wants to inherit from a generic Config object. The twist is config is not static, but loaded depending on a given environment variable.
Config pertinent methods
public function __construct()
{
$configFile = 'example.config.yml';
$yamlParser = new Parser();
$reader = new Config\Reader($yamlParser);
$configYaml = $reader->parse(file_get_contents($configFile));
$config = new Config\Environment(getenv('SITE'), $configYaml);
$this->config = $config;
}
public function getEnvironmentConfig()
{
return $this->config;
}
Registering config is as simple as
$container->register('config', 'Config');
Database is currently added to the container as follows:
$container
->register('database', 'Database')
->addArgument($config->getEnvironmentConfig('Database', 'db.username'))
->addArgument($config->getEnvironmentConfig('Database', 'db.password'))
;
But I want to do something like
$container
->register('database', 'Database')
->addArgument(new Reference('config')->getEnvironmentConfig('Database', 'db.username'))
->addArgument(new Reference('config')->getEnvironmentConfig('Database', 'db.password'))
;
The $config in-PHP variable makes migrating from a PHP-built config impossible. I want to define the services in yaml force the container to:
Instantiate Config
Parse the config yaml file and create an environment-specific version
Return this on a call to getEnvironmentConfig
Is this possible?
This was solved by using the Expression Language Component
So you can easily chain method calls, for example:
use Symfony\Component\ExpressionLanguage\Expression;
$container->register('database', 'Database')
->addArgument(new Expression('service("config").getEnvironmentConfig("Database", "db.username")'));

Symfony 1.4 not loading sfTestFunctional failing with class not found

I've done my functional tests and now I want to run them. However, every time I run them I get sfTestFunctional class not found.
As far as I can tell the functional.php bootstrap is not autoloading the classes from the framework. Any reason why this could be?
This is my functional bootstrap
// guess current application
if (!isset($app))
{
$traces = debug_backtrace();
$caller = $traces[0];
$dirPieces = explode(DIRECTORY_SEPARATOR, dirname($caller['file']));
$app = array_pop($dirPieces);
}
require_once dirname(__FILE__).'/../../config/ProjectConfiguration.class.php';
$configuration = ProjectConfiguration::getApplicationConfiguration($app, 'test', isset($debug) ? $debug : true);
sfContext::createInstance($configuration);
// remove all cache
sfToolkit::clearDirectory(sfConfig::get('sf_app_cache_dir'));
$doctrine = new sfDoctrineDropDbTask($configuration->getEventDispatcher(), new sfAnsiColorFormatter());
$doctrine->run(array(), array("--no-confirmation","--env=test"));
$doctrine = new sfDoctrineBuildDbTask($configuration->getEventDispatcher(), new sfAnsiColorFormatter());
$doctrine->run(array(), array("--env=test"));
$doctrine = new sfDoctrineInsertSqlTask($configuration->getEventDispatcher(), new sfAnsiColorFormatter());
$doctrine->run(array(), array("--env=test"));
This is what is in my the functional tests
include(dirname(__FILE__).'/../../bootstrap/functional.php');
$browser = sfTestFunctional(new sfBrowser());
Doctrine_Core::loadData(sfConfig::get('sf_test_dir').'/fixtures/fixtures_initial.yml');
Ok. So after banging my head against the wall, I found a solution.
For some reason within the test environment custom filters are not autoloaded. The solution is to add require_once for all the custom filters to the ProjectConfiguration file. Here is the example of what I did:
if(sfConfig::get('sf_environment') == 'test' && sfConfig::get('sf_app') == 'frontend')
{
require_once sfConfig::get('sf_app_lib_dir').'/myFilter.class.php';
require_once sfConfig::get('sf_app_lib_dir').'/myotherFilter.class.php';
require_once sfConfig::get('sf_app_lib_dir').'/lovefiltersFilter.php';
require_once sfConfig::get('sf_app_lib_dir').'/eventsManagement.class.php';
require_once sfConfig::get('sf_test_dir').'/ProdPadTestFunctional.class.php';
}
I also had to add my custom testFuntional class as well. This might be more elegantly done using the autoload.yml file.
I spot the problem:
$browser = sfTestFunctional(new sfBrowser());
You should write:
$browser = new sfTestFunctional(new sfBrowser());

Categories