processwire / processwire-requests

ProcessWire feature requests.
40 stars 0 forks source link

Make Modules::setModuleConfigData hookable or provide option to set runtime config #258

Open wanze opened 5 years ago

wanze commented 5 years ago

Short description of the enhancement

Make Modules::setModuleConfigData hookable so developers can change a module's runtime configuration before the module is getting initialized by ProcessWire.

Steps that explain the enhancement

Module configuration often differs per environment. Let's say I have a stage and prod environment and the ProCache module being installed. ProCache should only be active on prod. Because ProcessWire stores module configuration in the database, it is currently only possible to vary config values by changing data in the database. This means if I want to sync my database from prod to stage, I must reconfigure all modules to have the correct config present. Most frameworks and other CMS's (Symfony, Laravel, Drupal, Statamic..) store configuration data in files. This allows to have a config file per environment and makes it easier to setup automatic deployment processes.

By making Modules::setModuleConfigData hookable, one can store runtime configuration in files. See the example below.

Why would the enhancement be useful to users?

Here is an example on how to use runtime module configuration per environment.

  1. Create a file per environment holding the configuration data
// stage.php
return [
    'site' => [
        'debug' => true,
    ],
    'modules' => [
        'ProCache' => [
            'cacheOn' => false,
        ],
    ],
];

// prod.php
return [
    'site' => [
        'debug' => false,
    ],
    'modules' => [
        'ProCache' => [
            'cacheOn' => true,
        ],
    ],
];
  1. Add a hook in site/init.php
wire()->addHookAfter('Modules::setModuleConfigData', function (HookEvent $event) {
    // Bail if module is not configurable.
    if ($event->return === false) {
        return;
    }

    $env = getenv('APP_ENV');
    $config = include(dirname(dirname(__DIR__)) . "/config/${env}.php");

    $module = $event->arguments('module');
    $moduleName = $module->className(false);

    // Pass runtime configuration to the module, if available.
    if (isset($config['modules'][$moduleName])) {
        $module->setArray($config['modules'][$moduleName]);
    }
});
adrianbj commented 5 years ago

Could another possible option be to define module settings in config.php?

Tracy automatically looks for $config->tracy and merges in any settings defined there, eg:

$config->tracy = array(
    'enabled' => true,
    'guestForceDevelopmentLocal' => true,
    'frontendPanels' => array('mailInterceptor','processwireInfo','requestInfo','processwireLogs','tracyLogs','debugMode','console','panelSelector'),
    'backendPanels' => array('mailInterceptor','processwireInfo','requestInfo','processwireLogs','tracyLogs','debugMode','console','panelSelector'),
    'nonToggleablePanels' => array('mailInterceptor', 'tracyToggler'),
    'panelSelectorTracyTogglerButton' => false
);

What if PW looked for $config->ModuleClassName and automatically did the same?

wanze commented 5 years ago

@adrianbj Nice! That would be the simpler solution :) I think ProcessWire really needs an interface to set runtime configuration of modules. Some things to be aware of:

Both problems could be solved by extending the interface to handle setting/getting config values.

teppokoivula commented 5 years ago

What if PW looked for $config->ModuleClassName and automatically did the same?

This is kind of a nuance, but if such a feature was added, it might make sense to use a different syntax – perhaps something like $config->moduleConfig->ModuleClassName, or really anything that is a) unique to make sure that module names can't clash with other config settings, and b) unlikely to be in use on too many existing projects.

I've used similar syntax before as well, but usually with some case-by-case processing and / or filtering. I can think of a few situations where where ProcessWire suddenly pulling those settings automatically in as-is would be a breaking change 🤔

What @wanze is suggesting here makes sense to me. In this case having a bit more common ground would probably be beneficial. That being said, actually enforcing those new methods in the interface would also be a breaking change for existing modules, right? Not a reason not to do it, of course, but still something to consider.