madeyourday/contao-rocksolid-custom-elements

Add a hook for adjusting the config

Defcon0 opened this issue · 10 comments

Hello,

we have the need for changing the RSCE elements based on a given YML configuration (contao 4.9). For this we need a hook in CustomElements::loadConfig() (before the line $file = new \File($filePaths['path'], true);).

Would that be possible?

Thanks in advance.

Bye Defcon0

ausi commented

What settings of the RSCE element do you want to change? Is it possible to make your changes in an onload_callback?

We try to make the available rsce elements configurable through a yml file.

The starting point for us was to have a bundle which we can install to new projects and have some complex content elements in place then. Since we don't need all RSCE Elements in all projects, we built a way to configure which of them are selectable in the tl_content.type field.

The problem with onload_callback is that the bundle also adds itself (loadConfig) to onload_callback and if we "clean" the $GLOBALS['TL_CTE'] array, the rsce bundle (which needs to be run after our bundle) adds the elements again.

We currently built a workaround in which we do our logic in the load_callback of the type field in tl_content, but a hook would be safer ;-)

ausi commented

How about adding one onload_callback before and one after the custom elements extension?
If you are using service tags this should be possible easily by using priority 1 and -1.

Service tags are only available in contao 4.9+, aren't they? Since we need the functionality also in v4.4 this won't be an option.

If you don't mind, I can create a PR for the hook, ok?

ausi commented

I’m not convinced that a hook at that point is a good solution. You would have to modify the $contents and the $GLOBALS array with the same values, otherwise the cached and non-cached behavior would be different. And if the data of your hook changes, how would you invalidate the cache?

Since we need the functionality also in v4.4 this won't be an option.

In Contao 4.4 you have to take care of the order yourself:

$GLOBALS['TL_DCA']['tl_content']['config']['onload_callback'][] = ['YourClass', 'onloadAfter'];
array_unshift($GLOBALS['TL_DCA']['tl_content']['config']['onload_callback'], ['YourClass', 'onloadBefore']);

Hi, I'm just read a little through the code. Best way to do the hook would be to only write $contents and afterwards just read the cache again. Could be done in a new method like reloadConfigCache.

public static function loadConfig($bypassCache = false)
{
    // ...
    $loadCachedConfig = false;
    if (isset($GLOBALS['TL_HOOKS']['rsceLoadConfigBeforeSave']) && \is_array($GLOBALS['TL_HOOKS']['rsceLoadConfigBeforeSave'])) {
        foreach ($GLOBALS['TL_HOOKS']['rsceLoadConfigBeforeSave'] as $callback) {
            $hook     = System::importStatic($callback[0]);
            $contents = $hook->{$callback[1]}($contents, $elements, $templates);
        }
        $loadCachedConfig = true;
    }

    $file = new \File($filePaths['path'], true);
    $file->write(implode("\n", $contents));
    $file->close();
    static::refreshOpcodeCache($filePaths['fullPath']);

    if ($loadCachedConfig) {
        static::reloadConfigCache();
    }
}

Cache invalidation should be problem of the hook implementor. Could be done by a cache clearer/warmer calling CustomElements::reloadConfig()

ausi commented

refreshOpcodeCache() does not work on all platforms for an already included file in the same PHP process. The only option would be to use eval(). I don’t want to create a hook that evaluates strings as PHP code, this could go wrong in many ways.

Anyway, I still don’t understand the use case for the hook. If you want to modify the DCA config use the onload_callback. You can use that callback before and after the custom elements extension to get control over what gets executed when.

Or is there anything you need to do that doesn’t work nicely in an onload_callback?

For this use-case there is a another solution, so the hook is not necessary in this case. But hooks are never bad, to be honest, it is hard to extend/build on rsce. In combination with an hook in getConfigByType we could get rid of html5 templates for example (we use twig).

refreshOpcodeCache() does not work on all platforms for an already included file in the same PHP process. The only option would be to use eval(). I don’t want to create a hook that evaluates strings as PHP code, this could go wrong in many ways.

I don't understand. This code part was taken from loadConfig hook to just show where to put the hook code.

ausi commented

But hooks are never bad

They are hard to maintain backwards compatibility for.

In combination with an hook in getConfigByType we could get rid of html5 templates for example (we use twig).

That’s a totally different story to me. A hook (or rather an event) for getConfigByType is more reasonable I think. But probably requires more work to ensure that this works for elements that have no Contao template and/or no _config.php file.

I don't understand. This code part was taken from loadConfig hook to just show where to put the hook code.

That was regarding your comment “Best way to do the hook would be to only write $contents and afterwards just read the cache again.”. This will not work in some environments.