I want to have one class that works with configuration settings. Configuration settings are stored inside config/ directory where I want them to be separated to files. When I call my
Config::gi()->getConfig('config_name')
I want the Config class to be able to access the file config/config_name.cfg.php and return the array (from that file) named exactly the same.
This is because I don't want to read all the configuration data if it's not needed. Also, I'm a bit afraid that setting up configuration inside $GLOBALS variable wouldn't be the best solution. I thought about requiring or including those files and then returning their content, but it also seems a bit unprofessional.
What is the best practice to read the configuration like this? Thank you in advance.
For example:
config/routes.cfg.php
$routes => [
'index' => new Route([
// route config here ...
])
];
and to get the routes array I would execute Config::gi()->getConfig('routes'); from helpers/Config.php class.
I'm not sure I would go this route, I would probably load all configs (most likely from a single file) into the class the first time and go from there. Also you can look at parse_ini_file() if you don't want to write out arrays when they change. But for your case, simply:
public function getConfig($name) {
if(file_exists("config/$name.cfg.php")) {
include("config/$name.cfg.php");
return ${$name}; //this translates to $routes in your example
}
return false;
}
Also, the next logical question might be how to save the config when it changes:
public function setConfig($name, $data) {
file_put_contents("config/$name.cfg.php", "$name = " . var_export($data, true) . ";");
}
$routes = array(/* with some stuff in it */);
Config::gi()->setConfig('routes', $routes);
Related
I have a scenario where Laravel config files are created and accessed during the same incoming request. However, when accessing the new stored config file's values, config() is unable to access the file's keys/values.
Here is an example scenario:
public function create(Request $request)
{
$settings = ['foo' => 'bar'];
// Store the config.
Storage::disk('config')->put("settings.php", '<?php return ' . var_export($settings, true) . ';');
// Results in an error, value not found.
$config_value = config('settings.foo');
}
Is there a way to re-register Laravel config files during a runtime?
Saving contents to the php file config/settings.php is (obviously) not the best way to do it.
Either save the config to a database, or simply use the config() helper to assign the values like this:
class MyConfigProvider extends ServiceProvider
{
public function boot() {
$this->app->booted(function () {
// This will set the config setting `config/settings.php['app_name']` at runtime:
config([
'settings.app_name' => 'My App Name'
]);
});
}
}
Then, make sure to load this service provider as early in the stack as possible in your config/app.php['providers'] array. Basically it overwrites any predefined config that you have set in config/settings.php. You can even retrieve values here from the request object or your database.
Yes there is a method for that:
config()->set('settings.foo', $somethingNew);
and then you can read it normally with:
$config_value = config('settings.foo');
Using CodeIgniter 3, I autoload my database config, now how do I change the database connected dynamically ? I was thinking like using session to pass the database value, but session cannot be used in the database config file.
I know I can manually load database and change it, but then I have to call and load the database in every controller and I have tons of the controller, therefore I would like to avoid setting the database manually.
There is probably more than one way to do what you want. The solution shown here uses CodeIgniter’s "Hooks" feature. Specifically, it uses the "post_controller_constructor" hook to match the name of a controller with a specific database configuration defined in database.php.
After the hook does its work the application can make calls to the database in the typical CI way using $this->db->. For example...
$query = $this->db->get('mytable');
This solution is based on the assumption that only one database connection is need for any given controller. This means that all methods in that controller (or any models loaded by the controller) use the same connection.
Here's how it is done.
In application/config/config.php
$config['enable_hooks'] = TRUE;
In application/config/hooks.php
$hook['post_controller_constructor'][] = array(
'class' => '',
'function' => 'set_db_connection',
'filename' => 'post_controller_hook.php',
'filepath' => 'hooks'
);
The file post_controller_hook.php is where the work gets done. It uses lists of controller names to determine which database config is to be loaded.
The list ($controller_lists) contains sub-arrays which group controller names by the db configuration needed. A search is done through each sub-array to find the matching controller name. When a controller name is found the key of that sub-array is the db config to be loaded. If no match is found the 'default' config is used.
The $controller_lists array is hard-coded here but it could easily be loaded from a config file instead. A config file might make maintaining the lists easier.
file application/config/post_controller_hook.php
function set_db_connection()
{
$CI = get_instance();
$controller = $CI->router->class;
$loadConfig = 'default'; //if nothing found in lists we're still good
$controller_lists = array(
'config2' => ['profile'],
'config3' => ['discusion', 'home'],
'config4' => ['suppliers', 'customers', 'inventory', 'orders']
);
foreach($controller_lists as $config_name => $list)
{
if(in_array($controller, $list))
{
$loadConfig = $config_name;
break;
}
}
$CI->load->database($loadConfig);
}
The ability to not load a database for controllers that don't need one could be added if that was desirable. But I'm not going there.
As stated earlier, this solution uses the assumption that only one database configuration (connection) is used for any given controller. If certain methods of a controller need to use a different db configuration this solution becomes more complicated.
Adding the method to the search is easy. The first few lines of set_db_connection() would look like this.
function set_db_connection()
{
$CI = get_instance();
$controller = $CI->router->class;
$method = $CI->router->method;
if($method !== 'index')
{
$controller .= '/'.$method; //append method name
}
$loadConfig = 'default'; //if nothing found in lists we're still good
So now $controller will hold either 'controller/method', or just 'controller' if index() is to being called.
Consider a controller called Viewstate with three methods
class Viewstate extends CI_Controller
{
public function index(){
//uses db 'config4'
}
public function report(){
//uses db 'Config2'
}
public function process(){
//uses db 'Config3'
}
}
We have to include each 'viewstate/method' in the sub-arrays like this.
$controller_lists = array(
'config2' => ['profile', 'viewstate/report'],
'config3' => ['disscusion', 'home', 'viewstate/process'],
'config4' => ['viewstate', 'customers', 'inventory', 'orders']
);
//the rest of the function is as shown earlier
Any 'viewstate/method' not in the search lists it will be assigned the 'default' db config. So it's easy to sort the various needs of viewstate.
The problem is that every 'controller/method' in the site must now be included in the search lists. If the Profile controller has ten methods every combination must now be in the config2 sub-array. So if there are lots of controllers and controller/methods this solution is a poor choice. There might be an elegant way around this problem but that's probably a topic for a new question.
Given the following file structure (the same for en) in Laravel 5:
resources/
de/
list.php
countries.php
mail.php
pages/
pageA.json
pageB.json
I would like to know:
Is __(..) or #lang(..) disturbed from the presence of a folder pages with .json files?
How are the language files loaded?
This is what I managed to find out by myself:
The function __('list.button_send') triggers getFromJson($key, array $replace = [], $locale = null) with $key = 'list.button_send'
This is the main part of the function:
$locale = $locale ?: $this->locale;
// For JSON translations, there is only one file per locale, so we will simply load
// that file and then we will be ready to check the array for the key. These are
// only one level deep so we do not need to do any fancy searching through it.
$this->load('*', '*', $locale);
$line = $this->loaded['*']['*'][$locale][$key] ?? null;
I am already confused by the comment - my translations are in the three different *.php files. Why is Taylor talking about a single JSON file? But if we ignore that and continue, one finds that the load function looks like this:
// The loader is responsible for returning the array of language lines for the
// given namespace, group, and locale. We'll set the lines in this array of
// lines that have already been loaded so that we can easily access them.
$lines = $this->loader->load($locale, $group, $namespace);
$this->loaded[$namespace][$group][$locale] = $lines;
I have no idea why there is a need to call this function with $namespace and $group because it looks to me that they will always be * inside the Translator class. But ignoring that also, I come to the interesting part: What is $lines ? I would guess its an array of the form
$lines = ['list' => ... , 'countries' => ..., 'mail' => ...];
which would answer my questions to:
no they don't interfere
If one access a singe key in a lang, then all language files in that lang (meaning all *.php in that directory) are loaded and stored in the Translator object.
However, I was not able to confirm my guess. In order to do that, I need to find out what $this->loader->load($locale, $group, $namespace); actually does. But $this->loader get assigned by dependency injection in the constructor and I coulnd't find where it is defined.
Your first question Is __(..) or #lang(..) disturbed from the presence of a folder pages with .json files?:
Short answer, no.
$locale = $locale ?: $this->locale;
// For JSON translations, there is only one file per locale, so we will simply load
// that file and then we will be ready to check the array for the key. These are
// only one level deep so we do not need to do any fancy searching through it.
$this->load('*', '*', $locale);
$line = $this->loaded['*']['*'][$locale][$key] ?? null;
In the introduction section of https://laravel.com/docs/8.x/localization#introduction, it states that there are two approaches, one using an array in a php file:
Laravel provides two ways to manage translation strings. First, language > strings may be stored in files within the resources/lang directory. [...]
/resources
/lang
/en
messages.php
/es
messages.php
and another approach using a json file:
Or, translation strings may be defined within JSON files that are placed within the resources/lang directory [...]
/resources
/lang
en.json
es.json
The second approach is convenient, because it does not need keys. However it comes with the downside, that it will contain all language files from all sites in one file.
If you keep digging, you can see from the TranslationServiceProvider that the loader is definied by the FileLoader class. This is the method that loads the json file:
protected function loadJsonPaths($locale)
{
return collect(array_merge($this->jsonPaths, [$this->path]))
->reduce(function ($output, $path) use ($locale) {
if ($this->files->exists($full = "{$path}/{$locale}.json")) {
$decoded = json_decode($this->files->get($full), true);
if (is_null($decoded) || json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException("Translation file [{$full}] contains an invalid JSON structure.");
}
$output = array_merge($output, $decoded);
}
return $output;
}, []);
}
The array $this->jsonPaths is empty per default. So unless you specify the jsonPaths in your app (through the Translator service) your json files inside your pages directory will be ignored.
Your second question How are the language files loaded?
You can load any translatable string through the helper function __($key,..) (you can also provide replacement array and locale and fallback, but we can ignore this for now). This will call app('translator')->get($key,...) The structure of the key is like this:
Namespace::group.key
The "Namespace::" is only relevant for packages https://laravel.com/docs/8.x/packages#translations so within your application, your keys look probably more like this:
group.key
In the first step, it will be checked if the key can be found in current language json (for example resources/lang/en.json) or in default langauge json.
If this cannot be found, then it will be checked if the language file can be found in resources/lang/{lang}/{group}.php. This will be checked for current locale and fallback locale.The group.php will be loaded only once and stored in the FileLoader singlton in memory.
Thus, for the current language and the default language, first the {lang}.json is loaded, if it exists, and then for those languages the resources/lang/{lang}/{group}.php is loaded (if not found in json). Each group will be loaded once per request. If a langauge group is not requested, it will also not be loaded.
I'm using a package within my project and it stores a setting inside config/packagename
I would like to dynamically change this value inside the config file, this is how the file structure looks currently;
<?php
return [
'view_id' => '118754561',
'cache_lifetime_in_minutes' => 60 * 24,
];
I would like to change it to something like this -
'view_id' => Auth::user()->id,
Can you do this within the config file, or do you have to store some sort of variable to be updated later within a controller. Is there a way to place these variables in an env file and access these new variables from a controller?
This also is a generic solution to dynamically update your .env file (respective the individual key/value pairs)
Change the setting in your config/packagename like so:
return [
'view_id' => env('VIEW_ID', '118754561'),
etc...
]
Add an initial value into .env:
VIEW_ID=118754561
In an appropriate controller (e.g. AuthController), use the code below and call the function like this:
updateDotEnv('VIEW_ID', Auth::User()->id)
protected function updateDotEnv($key, $newValue, $delim='')
{
$path = base_path('.env');
// get old value from current env
$oldValue = env($key);
// was there any change?
if ($oldValue === $newValue) {
return;
}
// rewrite file content with changed data
if (file_exists($path)) {
// replace current value with new value
file_put_contents(
$path, str_replace(
$key.'='.$delim.$oldValue.$delim,
$key.'='.$delim.$newValue.$delim,
file_get_contents($path)
)
);
}
}
(The $delim parameter is needed if you want to make this function more generic in order to work with key=value pairs in .env where the value has to be enclosed in double quotes because they contain spaces).
Admittedly, this might not be a good solution if you have multiple users at the same time using this package in your project. So it depends on what you are using this package for.
NB: You need to make the function public of course if you plan to use it from other classes.
All configuration files of Laravel framework stored in the app/config directory.
so if we need to create custom configuration values it would be better to keep separate our custom configuration in custom file.
so we need to create custom file in app/config directory.
Laravel auto read this file as a config file and will auto manage it
In this topic we are working with custom configuration in laravel and get configuration value in controller or view.
Now i am going to explain how to create a custom config file in Laravel so that we can implement dynamic feature over to this.
create a file in app/config/custom.php which have config keys and value like:-
return array(
'my_val' => 'mysinglelue',
'my_arr_val' => array('1', '2', '3'),
);
Now need to get these config values in view/controller so we will use Config class get() method for this
Syntax:
echo Config::get('filename.arraykey');
where filename is the config file’s name, custom in our case, and key is the array key of the value you’re wanting to access.
In Our case it will be as:
echo Config::get('custom.my_val');
Create run time configuration in laravel :-
Configuration values which are set at run-time are will set for the current request, not be carried over to subsequent requests.
You can pass the dynamic values over here so that you can modify the config file dynamically over here using the isset() functions.
Like how the #Kundan roy as suggested using of the isset() the same condition applies here to. But this one is the alternative method that will work for the dynamic setting of the values in the config.
Config::set('custom.my_val', 'mysinglelue');
Hence by using this method you can create the dynamic config files based on the values that you need.
Since Laravel v5.2 you can dynamically set config values this way:
<?php
config(['app.timezone' => 'America/Chicago']);
$value = config('app.timezone');
echo $value;
// output: 'America/Chicago'
If you want to actually edit the config files (either config/packagename.php or .env) then you may follow matthiku's answer.
However, if I were you, I guess I'd rather want to configure this 3rd party package based on some value defined at runtime, instead of modifying any file (which by the way won't take effect until the next request, when the env values are read again).
So, in my opinion the cleanest way to do this is:
store your desired value in the config data:
config(['packagename.view_id' => Auth::user()->id]);
However, you may notice this probably won't work: the service provider which provides the service you need is often registered before the request is processed, that is, before your config change takes place. So you are still getting the service with old values config.
So, how could you have the service provider be called only when needed and not before (that is, after setting the new config value)? You could make it a deferred provider. Following your example of "spatie laravel-analytics", replace in config/app.php this line:
Spatie\Analytics\AnalyticsServiceProvider::class
with this one:
App\Providers\AnalyticsDeferredServiceProvider::class
and finally create the App\Providers\AnalyticsDeferredServiceProvider class, with:
:
<?php
namespace App\Providers;
use Spatie\Analytics\Analytics;
use Spatie\Analytics\AnalyticsServiceProvider;
class AnalyticsDeferredServiceProvider extends AnalyticsServiceProvider
{
protected $defer = true;
public function provides()
{
return [Analytics::class];
}
}
This way you can have the provider read the config values when you are about to instantiate the service, after you set any runtime config values.
Use this in controller when you need to change.
<?php
use Illuminate\Support\Facades\Config;
//[...]
$user_id = Auth::user()->id;
Config::set('view_id', $user_id );
You can do like this.
In your custom config file. Add the following code You can send your id dynamically from the query string.
'view_id' => isset($_REQUEST['view_id'])?$_REQUEST['view_id']:null,
To get view id
echo config('app.view_id'); // app is config file name
config(['packagename.view_id' => Auth::user()->id]);
Actually if you are that point of code which forces you to make the config values dynamic, then there should be something wrong with your code flow, as the use of config file is just for initializing required values - which should be defined before the app is loaded.
Making config values dynamic is a "BAD PRACTICE" in the world of coding.
So there is the following alternative for your problem.
Define value in .env file (optional)
VIEW_ID=your_view_id_here
Use value inside Controller
$auth_id = auth()->user()->id;
$view_id = env('VIEW_ID', $auth_id);
// If .env file doesn't contains 'VIEW_ID' it will take $auth_user as the view_id
Hope this helps you!
config::set() doesn't works for me in laravel 8. but I got best answer for Create or edit Config file
config(['YOUR-CONFIG.YOUR_KEY' => 'NEW_VALUE']);
$text = '<?php return ' . var_export(config('YOUR-CONFIG'), true) . ';';
file_put_contents(config_path('YOUR-CONFIG.php'), $text);
just try this way this works for me.
original answer you can see here
How can I define a superglobal array in PHP?
I know that there is no way to do this. However I thought there might be some "brains" out there knowing about a workaround for it. Specifically I want to have an array on every .php file I have that has the same data. I also want to access it like this:
echo $_HANS["foo"];
or
if( isset($_HANS["foo"]) && !empty( $_HANS["foo"] ) ) // DO
Important for me is that I don't want to have a static class, where I have to access my members like this:
myClass::myVariable;
By now I have something like this, but I guess it's not really efficient:
define( 'HANS', serialize( array( 'FooKey' => 'FooData' ) ));
To access it I have to unserialize it. Storing the data in a variable and THEN I can work with the index-operators.
$_HANS = array();
foreach(unserialize(HANS) as $Key => $Data)
{
$_HANS[$Key] = $Data;
}
echo $_HANS["FooKey"];
There must be a way to skip this whole foreach-stuff in every single file.
EDIT =======================================
Okay, so by now I have this little workaround, which should be a little more efficient, than including a whole new file. However I'm still listening for a better solution :)
I simply put this code above in a global function:
function getData()
{
$_DATA = array();
foreach( unserialize(HANS) as $Key => $Data )
{
$_DATA[$Key] = $Data;
}
return $_DATA;
}
An everything I have to do is to put this line of code in every .php-file:
$_HANS = getData();
The problem about php.ini is the compatibility with companies offering 'webhosting'. They often give you a predefined installation of Apache, PHP, MySQL and Perl (often: LAMP) and don't allow you to edit the php.ini file.
You can declare $myArray at the beginning of the script (you can use a prepend file), and then access it using $GLOBALS['myArray'] from any point after that.
You can use the auto_prepend_file and auto_append_file configuration directive in php.ini, in which you can define marshalling and unmarshalling of whatever global variable. You still will need to do the serialization trick, but you can do it in one place without having to remember to include the handlers.