Configure services based on env var in Symfony 5.4 - php

I am working on a project that uses many env vars and I used php files (rather than yaml files or xml) to deal with configurations.
I am trying to this code :
// config/may_conf_name.php
return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->extension('my_extension', [
...
]);
// CLIENT_TYPE is an env variable
if ('http' === (string) env('CLIENT_TYPE')->string()) {
$services->alias(AbstractClient::class, HttpClient::class);
} else {
$services->alias(AbstractClient::class, AwsClient::class);
}
...
}
Unfortunately, env('CLIENT_TYPE')->string()) isn’t parsed
dump(
(string) env('CLIENT_TYPE')->string())
)
// output
^ "%env(string:CNT_CLIENT_TYPE)%"
Obviously, when I do this, it works for me :
if (isset($_ENV['CLIENT_TYPE']) && 'http' === $_ENV['CLIENT_TYPE']) {
....
}
So, my question is there a way to get my ‘CLIENT_TYPE’ env var like env('CLIENT_TYPE')->string()? Is it "clean" to do this with $_ENV['CLIENT_TYPE']?
Thank you!

Look at https://symfony.com/doc/current/service_container.html#binding-arguments-by-name-or-type
In your service.yaml or php in your case you can bind variable which will be autoimported inside autowired services.
In Yaml
bind:
$clientType: '%env(CLIENT_TYPE)%'
In php
->bind('$clientType', '%env(CLIENT_TYPE)%')
Then in any service where you want to use your environment var :
public function __construct(string $clientType)
$clientType will be autowired automatically with your binded configuration.
I don't know if this is exactly what you're looking for, dont hesitate to comment i will update my answer, but i hope it will at least give you an idea of what to do.
I asked for your SF version because autowiring is automatically working on 5.4 if you did not disabled it
If CLIENT TYPE is empty, it is not related to your symfony configuration. It mean that your env var is not properly written to your current env file.

Related

Laravel: multiple log providers using `configureMonologUsing()`?

I'm using configureMonologUsing() to add in two custom loggers. Doing the standard SOLID principal, I have two providers: ConsoleLoggerProvider and MailLogProvider.
Both of these have a register similar to:
public function register()
{
app()->configureMonologUsing(function(\Monolog\Logger $monolog) {
$monolog->pushHandler(new HandlerClass());
});
}
However, I have noticed over logger will overwrite another logger... How do I stack these?
I've tried to use boot() as well, and that didn't work. I couldn't find any other way to add to the Monolog stack.
Preferable, I want to stack onto Laravel's built-in logger as well.
I (finally) found the answer my question:
Within my providers, instead of using configureMonologUsing(), I used Log::getMonolog()->pushHandler([..])
That works! All loggers, including built-in Laravel file logger, are firing. Finally!
(I've honestly been looking for days for a way to add onto the Monolog stack; I apparently wasn't searching by the right terms)
According to the Laravel documentation:
You should place a call to the configureMonologUsing method in your bootstrap/app.php file right before the $app variable is returned by the file.
In that case, thus should work for you: create two handler classes and add them to monolog this way (in your bootstrap/app.php):
$app->configureMonologUsing(function ($monolog) {
$monolog->pushHandler(new EmailLogHandler);
$monolog->pushHandler(new ConsoleLogHandler);
});
return $app;
Following Laravel 5.2 docs, in bootstrap/app.php, I added the following code right before return $app;:
$app->configureMonologUsing(function($monolog) {//IMPORTANT: I think the order of pushHandler matters, and the ones defined last here will be the first to be called, which affects anything where bubble=false
if (config('services.slack.send_errors_to_slack')) {
$bubble = false; //I think that if I set the 'bubble' argument to false and handle the most severe logging levels first (which counterintuitively means lower in this function), less severe logging levels don't bother reporting the same message.
$useShortAttachment = false;
$includeContextAndExtra = true; //This is important because otherwise 404 errors wouldn't report the URL, give how 'report' function is coded within App\Exceptions\Handler.php.
$handlerForWarningsToNotifyPhone = new \Monolog\Handler\SlackHandler(config('services.slack.token'), config('services.slack.channel_warnings'), 'Monolog', true, null, \Monolog\Logger::WARNING, $bubble, $useShortAttachment, $includeContextAndExtra);
$monolog->pushHandler($handlerForWarningsToNotifyPhone);
$handlerForErrorsToNotifyPhone = new \Monolog\Handler\SlackHandler(config('services.slack.token'), config('services.slack.channel_errors'), 'Monolog', true, null, \Monolog\Logger::ERROR, $bubble, $useShortAttachment, $includeContextAndExtra);
$monolog->pushHandler($handlerForErrorsToNotifyPhone);
}
if (config('app.send_logs_to_loggy')) {
$logglyHandler = new \Monolog\Handler\LogglyHandler(config('services.loggly.token'), config('app.send_logs_to_loggy')); //See \Monolog\Logger::INFO. Log level 200 is "info".
$logglyHandler->setTag(config('services.loggly.tag'));
$monolog->pushHandler($logglyHandler);
}
if (config('app.log_to_local_disk')) {
$localHandler = new \Monolog\Handler\StreamHandler(storage_path("/logs/laravel.log"));
$monolog->pushHandler($localHandler);
}
});
It's just an example that may help you.
Be sure to edit your config files accordingly (e.g. so that app.log_to_local_disk, services.slack.send_errors_to_slack, etc are available).
http://stackoverflow.com/a/36259944/470749 was helpful.
Here is how I able to configure on Laravel Lumen v5.4
in app.php:
$publisher = new \Gelf\Publisher(new \Gelf\Transport\HttpTransport(env('GRAYLOG_HOST'), env('GRAYLOG_PORT'), env('GRAYLOG_PATH')));
//WhatFailureGroupHandler does not break app execution
//if some exceptions happen happens while logging
$failureHandler = new \Monolog\Handler\WhatFailureGroupHandler([
new \Monolog\Handler\GelfHandler($publisher)
]);
\Log::pushHandler($failureHandler);
\Log::getMonolog() as on accepted answer threw error.
Also tried to configure using $app->configureMonologUsing() which threw A facade root has not been set. error. But at the end, I found out that was because we need to return logger:
$app->configureMonologUsing(function ($monolog) {
$publisher = new \Gelf\Publisher(new \Gelf\Transport\HttpTransport(env('GRAYLOG_HOST'), env('GRAYLOG_PORT'), env('GRAYLOG_PATH')));
$failureHandler = new \Monolog\Handler\WhatFailureGroupHandler([new \Monolog\Handler\GelfHandler($publisher)]);
$monolog->pushHandler($failureHandler);
//fixes error: A facade root has not been set
return $monolog;
});
All the examples of $app->configureMonologUsing() usage I have seen do not have a return statement, even in the other answers, which did not work for me.

Integrating Gulp and bower into Zend Project

The issue is, I would like to use all benefits of tasks runner and bower into my existing Zend-Project. I'm not sure, how should I restructure my whole project folder.
My current structure looks like follows:
config
module
vendor
public
css
js
libs
jquery
bootstrap
controller1
jsController1.
etc
index.php
From this (Gulp and Bower - creating proper files structure) I know, that I have to create a separate folder to save all libraries installed from bower and then I have to copy them with gulp into the public folder. Now depending on enviroment (Production or Development) I want to use minified css and js scripts. So should I create 2 public - folders and depending on enviroment change base path for zend? Or what's the best way to perform that?
Additionally I would like to integrate browser-sync into this project (cause of livereload). Therefore I want to use gulp-connect to start the php-server. But then the enviroment variables from apache are not set. How can I set it? According to documentation I have to add newArgs (cause my Options are array) "APPLICATION_ENV=development". But if add this after comma, i get error: "Could not open input file: APPLICATION_ENV=development"
My currenty gulfile:
var gulp = require('gulp'),
php = require('gulp-connect-php');
gulp.task('php', function() {
php.server({
configCallback: function _configCallback(type, collection) {
// If you wish to leave one of the argument types alone, simply return the passed in collection.
if (type === php.OPTIONS_SPAWN_OBJ) { // As the constant suggests, collection is an Object.
// Lets add a custom env var. Good for injecting AWS_RDS config variables.
collection.env = Object.assign({
APPLICATION_ENV: "development"
}, process.env);
return collection;
} else if (type === php.OPTIONS_PHP_CLI_ARR) { // As the constant suggests, collection is an Array.
let newArgs = [
'-e', // Generate extended information for debugger/profiler.
'-d', 'memory_limit=2G' // Define INI entry, Up memory limit to 2G.
,"APPLICATION_ENV=development"
];
// Ensure our argument switches appear before the rest.
return newArgs.concat(collection);
}
}
,
base: 'public',
port: 8010,
keepalive: true},
function _connected_callback() {
console.log("PHP Development Server Connected.");
});
}
);

Testing Laravel Service Providers

I'm (we're) creating a package that acts as a core component for our future CMS and of course that package needs some unit tests.
When the package registeres, the first thing it does is set the back/frontend context like this:
class FoundationServiceProvider extends ServiceProvider
{
// ... stuff ...
public function register()
{
// Switch the context.
// Url's containing '/admin' will get the backend context
// all other urls will get the frontend context.
$this->app['build.context'] = request()->segment(1) === 'admin'
? Context::BACKEND
: Context::FRONTEND;
}
}
So when I visit the /admin url, the app('build.context') variable will be set to backend otherwise it will be set to `frontend.
To test this I've created the following test:
class ServiceProviderTest extends \TestCase
{
public function test_that_we_get_the_backend_context()
{
$this->visit('admin');
$this->assertEquals(Context::BACKEND, app('build.context'));
}
}
When I'm running the code in the browser (navigating to /admin) the context will get picked up and calling app('build.context') will return backend, but when running this test, I always get 'frontend'.
Is there something I did not notice or some incorrect code while using phpunit?
Thanks in advance
Well, this is a tricky situation. As I understand it, laravel initiates two instances of the framework when running tests - one that is running the tests and another that is being manipulated through instructions. You can see it in tests/TestCase.php file.
So in your case you are manipulating one instance, but checking the context of another (the one that did not visit /admin and is just running the tests). I don't know if there's a way to access the manipulated instance directly - there's nothing helpful in documentation on this issue.
One workaround would be to create a route just for testing purposes, something like /admin/test_context, which would output the current context, and the check it with
$this->visit('admin/test_context')->see(Context::BACKEND);
Not too elegant, but that should work. Otherwise, look around in laravel, maybe you will find some undocumented feature.

Symfony 2 - how to parse %parameter% in my own Yaml file loader?

I have a Yaml loader that loads additional config items for a "profile" (where one application can use different profiles, e.g. for different local editions of the same site).
My loader is very simple:
# YamlProfileLoader.php
use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Yaml\Yaml;
class YamlProfileLoader extends FileLoader
{
public function load($resource, $type = null)
{
$configValues = Yaml::parse($resource);
return $configValues;
}
public function supports($resource, $type = null)
{
return is_string($resource) && 'yml' === pathinfo(
$resource,
PATHINFO_EXTENSION
);
}
}
The loader is used more or less like this (simplified a bit, because there is caching too):
$loaderResolver = new LoaderResolver(array(new YamlProfileLoader($locator)));
$delegatingLoader = new DelegatingLoader($loaderResolver);
foreach ($yamlProfileFiles as $yamlProfileFile) {
$profileName = basename($yamlProfileFile, '.yml');
$profiles[$profileName] = $delegatingLoader->load($yamlProfileFile);
}
So is the Yaml file it's parsing:
# profiles/germany.yml
locale: de_DE
hostname: %profiles.germany.host_name%
At the moment, the resulting array contains literally '%profiles.germany.host_name%' for the 'hostname' array key.
So, how can I parse the % parameters to get the actual parameter values?
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself. I could probably write my own parameter parser - get the parameters from the kernel, search for the %foo% strings and look-up/replace... but if there's a component ready to be used, I prefer to use this.
To give a bit more background, why I can't just include it into the main config.yml: I want to be able to load app/config/profiles/*.yml, where * is the profile name, and I am using my own Loader to accomplish this. If there's a way to wildcard import config files, then that might also work for me.
Note: currently using 2.4 but just about ready to upgrade to 2.5 if that helps.
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself.
Symfony's dependency injection component uses a compiler pass to resolve parameter references during the optimisation phase.
The Compiler gets the registered compiler passes from its PassConfig instance. This class configures a few compiler passes by default, which includes the ResolveParameterPlaceHoldersPass.
During container compilation, the ResolveParameterPlaceHoldersPass uses the Container's ParameterBag to resolve strings containing %parameters%. The compiler pass then sets that resolved value back into the container.
So, how can I parse the % parameters to get the actual parameter values?
You'd need access to the container in your ProfileLoader (or wherever you see fit). Using the container, you can recursively iterate over your parsed yaml config and pass values to the container's parameter bag to be resolved via the resolveValue() method.
Seems to me like perhaps a cleaner approach would be for you to implement this in your bundle configuration. That way your config will be validated against a defined structure, which can catch configuration errors early. See the docs on bundle configuration for more information (that link is for v2.7, but hopefully will apply to your version also).
I realise this is an old question, but I have spent quite a while figuring this out for my own projects, so I'm posting the answer here for future reference.
I tried a lot of options to resolve %parameter% to parameters.yml but no luck at all. All I can think of is parsing %parameter% and fetch it from container, no innovation yet.
On the other hand I don't have enough information about your environment to see the big picture but I just come up with another idea. It can be quite handy if you declare your profiles in your parameters.yml file and load it as an array in your controller or service via container.
app/config/parameters.yml
parameters:
profiles:
germany:
locale: de_DE
host_name: http://de.example.com
uk:
locale: en_EN
host_name: http://uk.example.com
turkey:
locale: tr_TR
host_name: http://tr.example.com
You can have all your profiles as an array in your controller.
<?php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
$profiles = $this->container->getParameter('profiles');
var_dump($profiles);
return $this->render('AcmeDemoBundle:Default:index.html.twig');
}
}
With this approach
you don't have to code a custom YamlLoader
you don't have to worry about importing parameters into other yml files
you can have your profiles as an array anytime you have the $container in your hand
you don't have to load/cache profile files one by one
you don't have to find a wildcard file loading solution
If I got your question correctly, this approach can help you.

Yii console with different configs

In the web application we use same code and modules with different configs like:
in index.php file app will decide, wchin config to turn:
switch($_SERVER['HTTP_HOST']){
default:
$yii=$webRoot.'/framework/yiilite.php';
$config = $webRoot.'/protected/config/main.php';
break;
case 'someurl.com':
...
break;
...
}
But, how can I do it with console application?
The reason in that I use different databases ant etc.
is it possible to do something like this:
$ ./protected/yiic --application=myappname [all defined commands as default]
in the code a
--application
will set with which console config to work
more explanation
my answer to #Joe Miller
But the problem is, how choose theme?
I did in the files foloowings:
in protectes/yiic
$__appId = null;
for( $__i=1,$__max=count($argv); $__i<$__max; ++$__i ) {
if ( strpos($argv[$__i],'--appid',0) === 0 ) {
$__appId = substr($argv[$__i], 8);
unset($argv[$__i]);
}
}
require_once(dirname(__FILE__).'/yiic.php');
and in protected/yiic.php
$__appIdsList = array(
'my_site_1',
'my_site_2',
'my_site_3',
'my_site_4',
);
$yiic=dirname(__FILE__).'/../framework/yiic.php';
$config=dirname(__FILE__).'/config/console_'.$__appId.'.php';
require_once($yiic);
and it works and it catchs that config file what I need
./protected/yiic --appid=my_site_1
bu when I`m trying to do migrate
./protected/yiic --appid=my_site_1 migrate
the app cant recognize comman and gives me migrates help list
And final conslusion (I solved it)
I`d like to add transperent console command without affecting it to other execution of builtin console commands and custom console commands.
Another requirement is, solve this issue on a low-level approach, without inheritance or overloading other classes or methods.
So, my solution is:
in protected/yiic
#!/usr/bin/env php
<?php
$__appId = null;
for( $__i=1,$__max=count($argv); $__i<$__max; ++$__i ) {
if ( strpos($argv[$__i],'--appid',0) === 0 ) {
$__appId = substr($argv[$__i], 8);
unset($argv[$__i]);
unset($_SERVER['argv'][$__i]);
$argv = $_SERVER['argv'] = array_values($argv);
}
}
require_once(dirname(__FILE__).'/yiic.php');
and in /protected/yiic.php
<?php
// change the following paths if necessary
$__appIdsList = array(
'app_1',
'app_2',
);
$yiic=dirname(__FILE__).'/../framework/yiic.php';
$config=dirname(__FILE__).'/config/console_'.$__appId.'.php';
if ( !is_file($config) ) {
die("Error: There is no or wrong parametr appid. Please set parametr or correct. Example -appid={application_name}\n\tThe list of available appid:\n\t\t - ".implode("\n\t\t - ", $__appIdsList));
}
require_once($yiic);
and now it is possible to set param "appid" in any place of command line, like
./protected/yiic migrate --appid=app_1
and it acts only in that app what we need
PS: in any case, thanks #Joe Miller
Copy yiic.php for example to cron.php and modify the config file in the cron.php
then use as if it were yiic, for example:
cd ~/protected;php ~/protected/cron.php app command --param=value >> ~/runtime/crontab.log
If I've understood what you're trying to do correctly, I think you might need something like this. I've referred to this article http://www.yiiframework.com/doc/guide/1.1/en/topics.console#creating-commands. I've not tried this, so I'm just interpreting the article.
Create a base command class, from which you will extend all the other commands. The base class run() method selects the config file to load.
In protected>commands you need a file migrate.php. This must contain the class MigrateCommand, and must extend CConsoleCommand. You can then override the run() method of this class to allow parameters to be passed to the method. e.g.
In protected>commands>baseCommand.php
class MyBaseCommand extends CConsoleCommand{
public function run($args){
//Code here to select the config file to load
//$args are any arguments you have passed in the command line
}
}
In protected>commands>migrate.php
class Migrate extends MyBaseCommand{
public function run($args){
parent::run($args);
//Do your own stuff here
}
}
you should then be able to call the command as;
./protected/yiic migrate --appid=my_site_1
Note that the name of the command appears first, I'm not sure if this is important, but it's what the guide says! I hope I've understood your question this time!
I think, I founded more confortable solution!
It`s more easy, and solved all my requirements.
in the file
protected/yiic.php
I write:
...
$yiic=dirname(__FILE__).'/../lib/framework/yiic.php';
if ( strpos(__FILE__,{first/place}) !== false ) {
$config=dirname(__FILE__).'/config/first_config.php';
} elseif ( strpos(__FILE__,{second/place}) !== false ) {
$config=dirname(__FILE__).'/config/second_plase.php';
} else {
// by default
$config=dirname(__FILE__).'/config/console.php';
}
require_once($yiic);
...
where {first/place},{second/place} - a part of the project`s path. For example:
Your first project is placed in:
/var/www/aproject/first_one
and the second one on the
/var/www/aproject/second_one
than you checks will be:
// for first porject
strpos(__FILE__,'aproject/first_one') !== false
and etc.

Categories