Laravel package config does not merge - php

I am developing several packages and would like to have a single config file for all of them if they are to be published.
Inside my service provider I did this:
public function boot()
{
$this->publishes([
__DIR__ . '/config/custom.php' => config_path('custom.php'),
]);
}
public function register()
{
$this->mergeConfigFrom(
__DIR__ . '/config/custom.php', 'custom'
);
}
Config:
return [
'containers' => [
...
]
];
And surely enough, if I publish it, it creates the file with values inside. But if a file already exists, having different keys:
return [
'xxxyyy' => [
...
],
];
publishing doesn't do anything. I would expect it to look like:
return [
'xxxyyy' => [
...
],
'containers' => [
...
]
];
What am I doing wrong?

In case anyone else is baffled by this, I have tested several cases and here is my explanation (official docs).
$this->publishes part enables the package config to be published by vendor:publish command. Nothing to explain here.
$this->mergeConfigFrom part enables the package config to be merged with currently published copy. Merge in this case means merged from Laravel config() scope.
This means that going to artisan tinker and running config('key') (where key is name of your config file) will return nothing if there is no published file and you don't have mergeConfigFrom. If a file is published and has other key value pairs which are not present in your package config and you have mergeConfigFrom present, config('key') will return merged array of values from a published file together with values from your config.
If a file with a same config name exists in your root config folder, and your package internally uses the same config name but you don't have mergeConfigFrom, config('key') will return only contents of a file inside root config folder, and will ignore everything from your package as you didn't provide a way for Laravel to see this as a "global" configuration. Your package will keep internally using the package config, but from app scope you will not be able to fetch it.

Related

Custom configuration files in CakePHP 3

I have a CakePHP 3.3.14 application where I've created 2 subdirectories, webroot/data/downloads/ and webroot/data/master
I want to put these paths in a custom configuration file and reference them in a Controller. But I can't see how to do this.
I've followed the documentation on Configuration but it's not very clear.
So what I've done:
Created config/my_config.php
The above file defines an array:
return [ 'downloadsPath' => 'webroot/data/downloads/', 'masterPath' => 'webroot/data/master/' ];
In config/bootstrap.php I've put: Configure::load('my_config', 'default');
How do I then use this in a Controller? If I put Configure::read('my_config.masterPath'); it gives an error saying: Class 'App\Controller\Configure' not found
If I add use Cake\Core\Configure; to the top of my Controller, that clears the error but the return value is null:
debug(Configure::read('my_config.masterPath')); // null
Loading another config file just extends the default App.config. So just use \Cake\Core\Configure::read('masterPath') and you are good.
EDIT
If it is your goal to have different config paths you could do it like this:
// my_config.php
return [
'MyConfig' => [
'masterPath' => '...',
...
]
];
Then use the config like this:
<?= \Cake\Core\Configure::read('MyConfig.masterPath') ?>

overriding zf2 modules configuration in phpunit test

I'm trying to write a test for a module that requires a specific configuration change during a test run, overwriting the default configuration provided by the module's own module.config.php.
In a normal application run, configuration is merged in order and I can use config/autoload/*.local.php to supersede module's config. But during a phpunit run those files aren't loaded, and I can't find a way to modify it during testing runtime.
I tried modifying the config in my Bootstrap file, to no avail; and even in the test controller setup directly:
public function setUp()
{
$app_config = include(Bootstrap::getRootPath() . '/config/application.config.php');
$test_config = Bootstrap::getTestConfig();
$new_config = ArrayUtils::merge($app_config, $test_config);
$this->setApplicationConfig($new_config);
}
But by the time one of the factories is running I check the values for the config during a debug session, and my injected values are nowhere to be found.
Also tried hooking to EVENT_MERGE_CONFIG and modify it there:
public static function onMergeConfig(ModuleEvent $e)
{
$configListener = $e->getConfigListener();
$config = $configListener->getMergedConfig(false);
$new_config = ArrayUtils::merge($config, $this->getTestConfig(static::$zf2ModulePaths));
// Pass the changed configuration back to the listener:
$configListener->setMergedConfig($new_config);
}
What's the proper way to accomplish this?
Personally I go with a similar approach as zfcampus/zf-development-mode does. Depending on the environment, I add another config_glob_paths to the application configuration. While I use it for managing development related configuration, this can easily be adapted for testing.
The basic idea is to have two application configuration files, one general and one environment specific (expect for production).
application.config.php:
return [
'modules' => [],
'module_listener_options' => [
'config_glob_paths' => [
__DIR__ . '/autoload/{{,*.}global,{,*.}local}.php',
],
'config_cache_enabled' => false,
'module_map_cache_enabled' => false,
],
];
development.config.php:
return [
'modules' => [],
'module_listener_options' => [
'config_glob_paths' => [
'config/autoload/{,*.}{global,local}-development.php',
],
'config_cache_enabled' => false,
'module_map_cache_enabled' => false,
],
];
Merging those two, will autoload the files matching in this order:
'config_glob_paths' => [
__DIR__ . '/autoload/{{,*.}global,{,*.}local}.php',
'config/autoload/{,*.}{global,local}-development.php',
],
While the later ones (postfixed with -development) will overwrite settings of the default ones.
So in my autoload directory I have the files like:
database.global.php
database.global-development.php
Local files are still possible for each environment as well of course:
database.local.php
database.local-development.php
The load order on development for the files then would be (if existing):
database.global.php
database.local.php
database.global-development.php
database.local-development.php
On a production environment, the development.config.php file is not merged, hence the *-development.php files are not loaded.
This pattern can easily be changed to testing environments as well of course. It is also noteworth that by this approach, you can change other application settings as well (e.g. application config caching / modules to load).

Where should I put a library in a Yii project that is not available via Composer?

I can't put the library under vendor/ because that directory is ignored in .gitignore. I put it under bing-ads/ and I have
<?php
namespace app\models;
include 'bing-ads\v10\bingads\ClientProxy.php';
use \Yii;
use BingAds\Proxy\ClientProxy;
to access it. It works for console commands/actions, but I have a feeling it will not work during a web call because the root directory will be web/. Where should I put this library and how can I access it via both console actions and web actions?
The Microsoft PHP library is located here.
I found a way by adding the file paths to the autoload section of composer.json. I remembered that I had to do that for some of the other libraries as well, even the ones available via Composer.
"autoload": {
"classmap": [
"vendor/googleads/googleads-php-lib/src/Google/Api/Ads/Common/Util",
"vendor/googleads/googleads-php-lib/src/Google/Api/Ads/AdWords/Util/v201605",
"bing-ads/v9/bingads/CustomerManagementClasses.php",
"bing-ads/v10/bingads/v10/CampaignManagementClasses.php",
"bing-ads/v10/bingads/v10/BulkClasses.php",
"bing-ads/v10/bingads/ClientProxy.php"
]
}
Then I ran
$ composer install
...
Generating autoload files
I'm not sure this is the best way though.
You could store them wherever you want e.g. in a folder named "BingAds".
Just add the alias as example in a common base config file /common/config/base.php which is included in your console as well as in your web application e.g. for your /web/index.php
$config = \yii\helpers\ArrayHelper::merge(
require(__DIR__ . '/../common/config/base.php'),
require(__DIR__ . '/../common/config/web.php'),
require(__DIR__ . '/../config/base.php'),
require(__DIR__ . '/../config/web.php')
);
Inside this common/config/base.php you could add your settings, extensions etc. which are valid for both the console and your web application
<?php
$config = [
...
'aliases' => [
'#BingAds' => '#app/BingAds/v10',
'#BingAds/Proxy' => '#app/BingAds/v10/bingads',
],
];
Your Clientproxy.php is stored as example in the directory /BingAds/Proxy/.
Then you don't have to include your files every time you want to use them and just write.
use BingAds\Proxy\ClientProxy;
I have unpacked your linked zip file and stored the files of the directory Bing Ads API in PHP\PHP\Bing Ads API in PHP to my application root directory BingAds whith the aliases mentioned above.
I have tested it by creating a clientProxy object in both a console and web application.
$test = new ClientProxy('test');
var_dump($test);
Both Printed out
object(BingAds\Proxy\ClientProxy)[140]
private 'authenticationToken' => null
private 'username' => null
private 'password' => null
private 'developerToken' => null
private 'wsdlUrl' => string 'test' (length=4)
private 'accountId' => null
private 'customerId' => null
private 'service' => null
private 'namespace' => null
I haven`t tested out other classes but I guess you get how it works.
Add this to your composer.json:
{
"repositories": [
{
"type": "package",
"package": {
"name": "microsoft/bing-ads",
"version": "9.0.0",
"dist": {
"url": "https://code.msdn.microsoft.com/Bing-Ads-API-Version-9-in-fb27761f/file/159208/2/Bing%20Ads%20API%20in%20PHP.zip",
"type": "zip"
},
"autoload": {
"classmap": [
"PHP/Bing Ads API in PHP/v10/bingads/"
]
}
}
}
],
"require": {
"microsoft/bing-ads": "9.0.0"
}
}
Then add composer generated autload.php file if you haven't already.
Then you can call the BingAds\Proxy\ClientProxy() without includes.
There's no better way:)
It's best to place external library to extensions directory.
And in config do
return [
'import' => [
'application.extensions.bing-ads.v10.bingads.ClientProxy',
],
...
];
in main and in console configs.
I usually just put the code in a components folder. If you're using the basic template, this folder can be under your application root; if you're using the advanced template, this folder can be under the necessary app: frontend, backend, console, or common. I recommend putting it under common for reasons I shall explain later
Under every app config, Yii 2 uses the ::setAlias method to assign different aliases to the key folders. In the basic app template, #app refers to the application root. While in the advanced template #app may refer to any of backend, frontend, or console.
If your library code is under backend, you can access it like so
namespace backend\controllers;
use yii\web\Controller;
use backend\components\MyLibCode;
class SiteController extends Controller
{
public function actionIndex()
{
$mlb = new MyLibCode();
// ...
}
}
However, it is good Yii 2 practice to have common library code in the common folder. So if this library code is to be used across apps, I suggest you put it into the common\components folder and replace backend with common in the use statement in the code above.
p.s: Justinas method also works; it's borrowed from Yii 1. But this may become cumbersome because this asks Yii to load the class when the application starts. If there is some heavy-lifting in that file, it may be detrimental to the app's performance.

Setting aliases in Yii2 within the app config file

I'm trying to set an alias in Yii2 but I'm getting a Invalid Parameter / Invalid path alias for the below code that is placed in the app config file:
'aliases' => [
// Set the editor language dir
'#editor_lang_dir' => '#webroot/scripts/sceditor/languages/',
],
If I remove the # it works.
I noticed you can do this:
Yii::setAlias('#foobar', '#foo/bar');
...but I would prefer to set it within the app config file. Is this not possible? If so, how?
Yii2 basic application
To set inside config file, write this inside $config array
'aliases' => [
'#name1' => 'path/to/path1',
'#name2' => 'path/to/path2',
],
Ref: http://www.yiiframework.com/doc-2.0/guide-structure-applications.html
But as mentioned here,
The #yii alias is defined when you include the Yii.php file in your entry script. The rest of the aliases are defined in the application constructor when applying the application configuration.
If you need to use predefined alias, write one component and link it in config bootstrap array
namespace app\components;
use Yii;
use yii\base\Component;
class Aliases extends Component
{
public function init()
{
Yii::setAlias('#editor_lang_dir', Yii::getAlias('#webroot').'/scripts/sceditor/languages/');
}
}
and inside config file, add 'app\components\Aliases' to bootstrap array
'bootstrap' => [
'log',
'app\components\Aliases',
],
In config folder create file aliases.php. And put this:
Yii::setAlias('webroot', dirname(dirname(__DIR__)) . '/web');
Yii::setAlias('editor_lang_dir', '#webroot/scripts/sceditor/languages/');
In web folder in index.php file put:
require(__DIR__ . '/../config/aliases.php');
Before:
(new yii\web\Application($config))->run();
If run echo in view file:
echo Yii::getAlias('#editor_lang_dir');
Show like this:
C:\OpenServer\domains\yii2_basic/web/scripts/sceditor/languages/
#webroot alias is not available at this point, it is defined during application bootstrap :
https://github.com/yiisoft/yii2/blob/2.0.3/framework/web/Application.php#L60
No need to define this alias yourself, you should simply use another one :
'aliases' => [
// Set the editor language dir
'#editor_lang_dir' => '#app/web/scripts/sceditor/languages/',
],
To improve on #vitalik_74's answer
you can place it in config/web.php instead(if you are using the basic yii app, I'm not sure about the main config file in the advance version, but the same applies, just put the require on the main config file) so that it gets shorten to:
require(__DIR__ . '/aliases.php');

Yii2 assets clear cache

Everytime I update my css or js files in infoweb\menu\module\assets, I have to empty the backend\web\assets folder
is there a way to automatically clear the assets cache?
Add this in your view:
use vendor\myVendorName\myPackageName\assets\AppAsset;
AppAsset::register($this);
Add this in your config:
'components' => [
'assetManager' => [
'linkAssets' => true,
],
]
Empty assets folder, and refresh, done
there is additional property as
if (YII_ENV_DEV) {
...;
...;
...;
$config['components']['assetManager']['forceCopy'] = true;
...;
...;
}
to publish files even there are published before
If you enviroment is production I recommend to use Cache Busting :
return [
// ...
'components' => [
'assetManager' => [
'appendTimestamp' => true,
],
],
];
for more information about assets, read the Assets Yii2 documentation.
If you are developing your own plugin, you can force publish assets per bundle
(note: $sourcePath should be set)
<?php
namespace app\components\forms\redactorAssets;
use yii\web\AssetBundle;
class RedactorCutAsset extends AssetBundle {
public $sourcePath = '#app/components/forms/redactorAssets/assets';
public $js = [
'cut.js',
];
public $publishOptions = [
'forceCopy'=>true,
];
}
sudo rm -rf frontend/web/assets/*
sudo chmod 777 frontend/web/assets
./yii cache/flush-all
If this does not work:
sudo rm -rf vendor/*
composer install
I use CClientScript::registerScriptFile method in my view files:
Yii::app()->clientScript->registerScriptFile(
$this->getAssetsBase() . '/js/script.js'
);
If I modified script.js, after next page reload I will see all changes
For css files - CClientScript::registerCssFile
Yii::app()->clientScript->registerCssFile(
$this->getAssetsBase() . '/css/style.css'
);
UPDATE: if you use yii 2.0 beta, you can read some information about changes in mechanic of client helpers here: link
The AssetManager will create a hash based on the file modification time. The modification time of a directory does not change when any file is changed.
If you have an AssetBundle that has a directory as $sourcePath, the modification time of the directory is used, the hash will not change, and nothing gets copied to the web/assets directory.
I suggest overriding AssetManager::hash() in a subclass or write a function for AssetManager::$hashCallback:
'components' => [
'assetManager' => [
'hashCallback' => function($path) {
// if: $path is directory: figure out when files were changed in directory
// else: use original hash function in \yii\web\AssetManager
}
],
]
For a sample implementation for finding the max modified date over all asset files in a bundle you could look at this comment by wookie # http://php.net/manual/en/function.filemtime.php#35779
Note that modification to any asset file will create a new directory in web/assets, and regular cleanup will remain necessary. However, browser cache aside, refreshing the page will follow the latest changes.
I configure the assetManager::forceCopy=true in main-local.php for the dev environment like this
return [
'components' => [
...
'assetManager' => [
'forceCopy' => true,
]
...
],
];

Categories