Setting/retrieving mode in Slim Framework V3 - php

I am new to Slim Framework (PHP). I was going through tutorials and testing some code and tried the following:
$app=new App([
'mode'=>file_get_contents(INC_ROOT . '/mode.php')
]);
echo $app->config('mode');
The output was blank.
It seems the function config() works with V2, however I am using V3. What am I doing wrong?

Slim 3 no longer has a config method. Instead, you must add configuration settings through the dependency injection container:
$app = new \Slim\App([
'settings' => [
'mode' => true
]
]);
$container = $app->getContainer();
echo $container->get('settings')['mode'];
A few things worth noting:
Slim 3 no longer handles managing different versions of configuration settings through a mode setting. So, you can set a value for a setting variable called mode as I've demonstrated here, but it won't actually do anything (i.e., Slim won't use it to determine your environment).
As an alternative, you can check out userfrosting/Config, a library we've been working on that can search multiple directories and different environment configuration files, merging together their contents:
/path/to/config/default.php
return [
'contacts' => [
'housekeeper' => [
'name' => 'Alex',
'email' => 'alex#cleansthetoilet.com'
]
]
];
/path/to/config/production.php
return [
'contacts' => [
'housekeeper' => [
'email' => 'alex#istheboss.com'
]
],
'database' => [
'password' => 'sup3rC-cr3t'
]
];
index.php
$app = new \Slim\App();
$container = $app->getContainer();
// Site config object (separate from Slim settings)
$container['config'] = function ($c) {
// Create and inject new config item
$config = new \UserFrosting\Config\Config();
$config->setPaths([
'/path/to/config'
]);
$config->loadConfigurationFiles('production');
return $config;
};
This will recursively merge in the settings from development.php with those in default.php, updating settings with the same name and scope as necessary:
Running print_r($container['config']); returns:
[
'contacts' => [
'housekeeper' => [
'name' => 'Alex',
'email' => 'alex#istheboss.com'
]
],
'database' => [
'password' => 'sup3rC-cr3t'
]
]
Notice that the value of contacts.housekeeper.email has been updated to 'alex#istheboss.com', and that the database config info has been merged in. Incidentally, you can also access config settings using the more convenient "dot syntax":
$config = $container->get('config');
echo $config['contacts.housekeeper.email'];
// Easier to type instead of $config['contacts']['housekeeper']['email'];
We recommend injecting this as a separate config service in Slim, rather than using their settings array.
You can combine this with phpdotenv to load settings from your system environment, or any .env files you create:
/path/to/config/production.php
return [
'database' => [
'password' => getenv('DB_PASSWORD')
]
];

Related

Laravel log file specific to a package

I'm writing a couple of laravel packages and I'm wondering if it is possible to have the package write to a specific log file but only for messages related to the package?
I tried making a logging.php file in the packages/myorg/mypackage/config (below) but it doesn't seem to do anything.
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
'default' => env('LOG_CHANNEL', 'stack'),
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/mypackage.log'),
'level' => env('LOG_LEVEL', 'debug'),
]
]
];
I am using "jeroen-g/laravel-packager" to set up the packages. It appears to manually load the mypackage.config in the ServiceProvider bootForConsole
protected function bootForConsole(): void
{
// Publishing the configuration file.
$this->publishes([
mypackage.'/../config/mypackage.php' => config_path('mypackage.php'),
], 'mypackage.config');
}
I'm not sure how to add custom logging to that though. I'm still learning Laravel and I'm not quite sure what or how the main applications config/logging.php is read so I'm not quite sure how to inject a custom version for an add-on package.
EDIT:
I found a post that suggested using the following in the ServiceManager boot() method:
$this->app->make('config')->set('logging.channels.mychannel', [
/* settings */
]);
I used the package config to set a 'logging' => [ 'channels' => [ 'mychannel' => [ /* settings */ ] ] ] and could then do the same thing as above with:
$this->app->make('config')->set('logging.channels.mychannel', config('mypackage.logging.channels.mychannel');
But that still required something in the code. The next best thing I have found thus far is to change my config/logging.php to config/logging.channels.php and include something like:
return [
'mychannel' => [
'driver' => 'single',
'path' => storage_path('logs/mypackage.log'),
'level' => env('LOG_LEVEL', 'debug'),
]
];
Then in the service provider register() method add:
$this->mergeConfigFrom(__DIR__ . '/../config/logging.channels.php', 'logging.channels');
I tried doing it from the original 'logging.php' with channels array nested in a 'logging' key, but array_merge doesn't appear to merge the nested elements so my channel never showed up in logging.channels.
I'm not sure if this is ideal, however. I'd still like to know if there is a 'better' or best practices way of adding custom package logging parameters and whether there is a need to publish it in any way (and how).

Monolog MongoDBHandler having BindingResolutionException error in Laravel 8

I tried to create a mongodb logging channel
'mongo' => [
'driver' => 'monolog',
'handler' => \Monolog\Handler\MongoDBHandler::class,
'formatter' => \Monolog\Formatter\MongoDBFormatter::class,
'handler_with' => [
'mongo' => new \MongoDB\Client("mongodb://localhost:27017"),
'database' => 'testdb',
'collection' => 'testcoll'
]
],
However, im getting error:
Illuminate\Contracts\Container\BindingResolutionException(code: 0): Unresolvable dependency resolving [Parameter #0 [ <required> $mongodb ]] in class Monolog\Handler\MongoDBHandler
The error is only resolved when I tried to add type hint to the class constructor but obviously I can't do that since it's a package:
public function __construct(Client<<if I add this it works>> $mongodb, string $database, string $collection, $level = Logger::DEBUG, bool $bubble = true)
Any solution for this?
So, I wanted to add here a complete answer because this post is the first one that shows up when looking for adding a mongo logger, and since the answer is buried in the comments, I wanted to add a proper answer.
The solution was to change the key mongo to mongodb in the handler_with array.
Leaving the working code like this:
'mongo' => [
'driver' => 'monolog',
'handler' => \Monolog\Handler\MongoDBHandler::class,
'formatter' => \Monolog\Formatter\MongoDBFormatter::class,
'handler_with' => [
'mongodb' => new \MongoDB\Client("mongodb://localhost:27017"),
'database' => 'testdb',
'collection' => 'testcoll'
]
],
Also, you could add the following element at the same level as formatter to set a custom max level of nesting. This is because, by default, the document stored cannot have a depth greater than 3, and it's automatically converted to "[...]" in the log.
'formatter_with' => [
'maxNestingLevel' => 10
],
Warning, in the probable event of a recursion, or an incredibly deep array, it can cause problems in mongo, because it doesn't support more than 100 levels of nesting, source.
According to the Laravel 8.x doc:
'mongo' => [
'driver' => 'monolog',
'handler' => \Monolog\Handler\MongoDBHandler::class,
'formatter' => \Monolog\Formatter\MongoDBFormatter::class,
'with' => [ // <-- This is `with` instead of `handler_with`
// 'mongodb' => new \MongoDB\Client("mongodb://localhost:27017"), <-- This line will cause an error in `php artisan config:cache`
'database' => 'testdb',
'collection' => 'testcoll'
],
],
So you need to configure the service for MongoDBHandler. as you mentioned with a look at the handler source code the first argument is $mongodb.
public function __construct($mongodb, string $database, string $collection, $level = Logger::DEBUG, bool $bubble = true)
and to be able to resolve this dependency we can configure service container:
// AppServiceProvider.php
// ...
public function register() {
$this->app->when(\Monolog\Handler\MongoDBHandler::class)
->needs('$mongodb')
->give(app(
\MongoDB\Client::class,
[
'uri' => 'mongodb://localhost:27017'
]
));
}
// ...

The "access_key" option must be provided to use fixer.io

For the currency conversion i am using "florianv/laravel-swap": "^1.1" library. Florianv/Laravel-swap.
As Fixer.io has changed its implementation, it is necessary to pass the access_key with the request, and because of that i am getting this error: "InvalidArgumentException: The "access_key" option must be provided to use fixer.io in /var/www/project/project-files/vendor/florianv/exchanger/src/Service/Fixer.php:51".
I registered and got the access_key.
I updated the library using composer and now i can see three constants in the vendor/florianv/exchanger/src/Service/Fixer.php.
const ACCESS_KEY_OPTION = 'access_key';
const LATEST_URL = 'http://data.fixer.io/api/latest?base=%s&access_key=%s';
const HISTORICAL_URL = 'http://data.fixer.io/api/%s?base=%s&access_key=%s';
To pass the access key i tried this:
I have a swap.php in config folder which looks something like this:
return [
'options' => [
'cache_ttl' => 86400, // 24 hours.
'cache_key_prefix' => 'currency_rate'
],
'services' => [
'fixer' => true,
],
'currency_layer' => [
'access_key' => 'asdfas7832mw3nsdfa776as8dfa', // Your app id
'enterprise' => true, // True if your AppId is an enterprise one
],
'cache' => env('CACHE_DRIVER', 'file'),
'http_client' => null,
'request_factory' => null,
'cache_item_pool' => null,
];
This had one more option which was commented, i enabled and passed the access_key in it but it doesn't work.
I also added it in services block below 'fixer => true'.
'currency_layer' => [
'access_key' => 'asdfas7832mw3nsdfa776as8dfa'
]
Also in options block:
'options' => [
'cache_ttl' => 86400, // 24 hours.
'cache_key_prefix' => 'currency_rate',
'access_key'=>'7ca208e9136c5e140d6a14427bf9ed21'
],
I tried with adding access_key in config/services.php file but it also didn't work.
'fixer' => [
'access_key' => 'asdfas7832mw3nsdfa776as8dfa'
],
Even i tried, adding to env file and calling from there, but no success. How do i pass the access_key, can anyone help me on this, what should be the approach.
vendor/florianv/exchanger/src/Service/Fixer.php -> don't touch the constant (that was my own error).
Pass the options-array by creating the Builder:
$options = ['access_key' => 'YourGeneratedAPIKeyAtCurrencyLayer'];
$this->exchangeSwap = (new Builder($options))
->add('fixer', $options )
->build();
I hope I could help ;-)

Yii2 Set component value dynamically

I am using this library https://github.com/yiioverflow/yii2-imap
'imap' => [
'class' => 'roopz\imap\Imap',
'connection' => [
'imapPath' => '{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX',
'imapLogin' => 'abc#gmail.com',//set this value dynamically
'imapPassword' => '123',//set this value dynamically
'serverEncoding' => 'encoding', // utf-8 default.
'attachmentsDir' => 'uploads/attachments'
],
],
//Create imap class object
$mailbox = yii::$app->imap->connection;
// Read all messaged into an array:
$mailsIds = $mailbox->searchMailbox('ALL');
in controller. want to set this value with help of session in yii2.
I found alternative php-imap library here [PHP IMAP][1]
[1]: https://github.com/barbushin/php-imap. which can be easily install with composer in yii2. and can pass a dynamic value
$mailbox = new PhpImap\Mailbox('{imap.gmail.com:993/imap/ssl}INBOX', 'some#gmail.com', '*********', __DIR__);
// Read all messaged into an array:
$mailsIds = $mailbox->searchMailbox('ALL');
Passing dynamic values that depends on Yii::$app right in config will not work because you are referring to application, and it's constructed using that config (components is a part of application too) and doesn't exist at this moment. It needs to be set later, when application is initialized and Yii::$app object exists. For example, in controller or some custom component.
Using library yiioverflow/yii2-imap it can be done like this:
use Yii;
...
Yii:$app->imap->connection = [
'imapPath' => '{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX',
'imapLogin' => $imapLogin, // Set this value dynamically
'imapPassword' => $imapPassword, // Set this value dynamically
'serverEncoding' => 'encoding', // utf-8 default
'attachmentsDir' => 'uploads/attachments',
],
Then you need to call:
Yii:$app->imap->createConnection();
to properly update the config.
There is no way to set separately imapLogin or imapPassword because of the way this component is written (these properties are protected and filled from connection array). If you want to do that, you have to subclass this component and write these setters by yourself and replace used component with your custom one.
More info about application components can be found in official docs.
You can use own "service layer" (that works similar to global Yii::$app). Just create \yii\di\ServiceLocator instance:
// Init service layer.
$services = new ServiceLocator();
$services->setComponents([
'imap' => [
'class' => 'roopz\imap\Imap',
'connection' => [
'imapPath' => '{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX',
'imapLogin' => 'abc#gmail.com',//set this value dynamically
'imapPassword' => '123',//set this value dynamically
'serverEncoding' => 'encoding', // utf-8 default.
'attachmentsDir' => 'uploads/attachments'
],
],
// ...
]);
// Retrieving the defined components:
$imap = $services->get('imap');
$imap = $services->imap;
If imap component will use only your controller, you can store $services as protected/private property of this controller.
Described approach works completely similarly to usual components in Yii::$app, because application class is also ServiceLocator.
Alternatively, you can define or redefine your component using imap-instance:
// Preparing components
$defaultImapConfig = [
'connection' => [
'imapPath' => '{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX',
'imapLogin' => null,
'imapPassword' => null,
'serverEncoding' => 'encoding', // utf-8 default.
'attachmentsDir' => 'uploads/attachments'
],
];
// Init service layer.
$services = new ServiceLocator();
// Define component
$imap = new \roopz\imap\Imap(ArrayHelper::merge($defaultImapConfig, ['connection' => [
'imapLogin' => 'abc#gmail.com',
'imapPassword' => '123',
]]));
$services->set('imap', $imap);
// Redefine component with new config
$imap = new \roopz\imap\Imap(ArrayHelper::merge($defaultImapConfig, ['connection' => [
'imapLogin' => 'dfg#gmail.com',
'imapPassword' => '456',
]]));
$services->set('imap', $imap); // If component definition with the same name already exist, it will be ovewritten.
Of course, you can use similar way to redefine global components in Yii::$app, but it is bad practice. I recommend to create separate (local) service layer, that can be accessed from your controllers, models etc.
More details about work with service locators you can found here.

Override Yii2 assetManager config in controller

I use yii-jui to add some UI elements in the views such as datePicker. In the frontend\config\main-local.php I set the following to change the theme used by the JqueryUI:
$config = [
'components' => [
'request' => [
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
'cookieValidationKey' => 'gjhgjhghjg87hjh8878878',
],
'assetManager' => [
'bundles' => [
'yii\jui\JuiAsset' => [
'css' =>
['themes/flick/jquery-ui.css'],
],
],
],
],
];
I tried the following to override this configuration item in the controller actions method:
public function actions() {
Yii::$app->components['assetManager'] = [
'bundles' => [
'yii\jui\JuiAsset' => [
'css' =>
['themes/dot-luv/jquery-ui.css'],
],
],
];
return parent::actions();
}
Also I tried to set the value of Yii::$app->components['assetManager'] shown above to the view itself (it is partial view of form _form.php) and to the action that calls this view (updateAction). However, all this trying doesn't be succeeded to change the theme. Is there in Yii2 a method like that found in CakePHP such as Configure::write($key, $value);?
You should modify Yii::$app->assetManager->bundles (Yii::$app->assetManager is an object, not an array), e.g.
Yii::$app->assetManager->bundles = [
'yii\jui\JuiAsset' => [
'css' => ['themes/dot-luv/jquery-ui.css'],
],
];
Or if you want to keep other bundles config :
Yii::$app->assetManager->bundles['yii\jui\JuiAsset'] = [
'css' => ['themes/dot-luv/jquery-ui.css'],
];
You are going about this all wrong, you want to change the JUI theme for 1 controller alone because of a few controls. You are applying 2 css files to different parts of the website that have the potential to change styles in the layouts too. The solution you found works but it is incredibly bad practice.
If you want to change just some controls do it the proper way by using JUI scopes.
Here are some links that will help you:
http://www.filamentgroup.com/lab/using-multiple-jquery-ui-themes-on-a-single-page.html
http://jqueryui.com/download/
In this way you are making the website easier to maintain and you do not create a bigger problem for the future than you what solve.

Categories