Deprecation of Switchable Controller Actions
To understand what the deprecation of SCA's means, one has to understand what they are and why they are used. And to describe this, we need to start at the very beginning, the definition of a plugin which is split into two different calls to static methods.
The naming of those methods is bit irritating as
registerPlugin does indicate that it needs to be called
configurePlugin may be called to configure a registered plugin. The opposite is the case but as all
this is legacy, it's important to explain this in detail.
This method is a helper method which adds your plugin along with all callable controllers and actions onto the
extbase plugin stack, which is represented by a global variable
This method is usually called in
ext_localconf.php which indicates that it is executed very early at request runtime.
It is necessary to be executed early because it defines all available extbase plugins. Also, that method registers the
plugin as a
USER_FUNC in the globally available typoscript.
A plugin which is "configured" via
configurePlugin() is fully registered, configured and executable via typoscript.
That's even the case without
registerPlugin() to be called at all.
This method on the other hand is a helper method which actually just registers the plugin in the TCA of type
tt_content. Calling this method makes the plugin selectable in the plugin content element form. As this method really
just edits TCA, it is nowadays usually called in a
Configuration/TCA/Overrides/tt_content.php file. Per default, this
method creates an entry in the
list_type select field but this method can also be used to create a dedicated
CType for the plugin. This means, that a plugin is a dedicated content element and does not consume a list space in the
list_type select field. This also means, that its accessibility for editors is much better to control. But more about that later.
Let's talk a bit more about
configurePlugin(). That method accepts a set of controllers and controller actions that are accessible by this plugin. The configuration is very dense and hides important functionality, which is bad UX. I'll explain this now in detail with an example:
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( 'Felogin', 'Login', [ \TYPO3\CMS\FrontendLogin\Controller\LoginController::class => 'login, overview', \TYPO3\CMS\FrontendLogin\Controller\PasswordRecoveryController::class => 'recovery,showChangePassword,changePassword' ], [ \TYPO3\CMS\FrontendLogin\Controller\LoginController::class => 'login, overview', \TYPO3\CMS\FrontendLogin\Controller\PasswordRecoveryController::class => 'recovery,showChangePassword,changePassword' ] );
I'll concentrate on the 3rd and 4th parameter for now. Paremeter 3 defines the general set of available controllers and actions while parameter 4 defines which of the controllers in parameter 3 are non-cachable. This style of configuration is bad because it is repetative and might indicate that a controller, defined only in parameter 4 would be available which is not the case.
But there's more. Looking at the syntax of parameter 3 or 4 alone (there are the same), there is a lot of implicit stuff going on.
- The list of defined actions acts as a firewall which isn't clear to newcomers. That's not so much an issue as this rarely leads to misunderstandings but the fact that experienced users actively use this list of actions as a firewall is bad. The configuration of plugins shouldn't define if a user is authorized to access an action. This information should be exposed by a dedicated configuration. This gets worse when looking at backend modules which allow for the same configuration of controller actions but make you take care of authorization yourself (mostly done in fluid templates with view helpers).
- The first action of the list is the default action and when calling a page with a plugin, the plugin calls that default action if no action parameter is set. This has been a sensible behaviour in general back then when users only had realurl for "speakable urls". realurl didn't allow to define default paremeters for a route because realurl guessed/created the url by looking at the paremeters, not the other way around. And that is all that is wrong with realurl. Let's shortly remind ourselves what URI stand's for.
Uniform Resource Identifier, A URL is just a specific type of URI but it shares the same trait that it's a unique identifier for a resource. In the context of TYPO3 a URL defines different resources actually because there is an id, which locates the page and then there are namespaced query parameters that identify plugin resources on that page. Those plugin resources are controllers and actions and with realurl there were always two URL's for a default action of a plugin on a page. The URL without plugin query paremeter and the one with query parameter. Example:
/?id=1&tx_news_pi[action]=defaultAction. Actually, this gets even worse if we respect default controllers here, because they exist as well.
- The first defined controller is the default controller of the plugin which leads to the fact that realurl generated many different URL's for the same resource. Example:
/?id=1&tx_news_pi[controller]=defaultController&tx_news_pi[action]=defaultAction. No matter which URL is called, the content is the same and this is a problem which has been addressed with routing where a route defines a URI with all necessary query paremeters to address a specific resource. This means, that without realurl, there is no need for a default controller and action any more and since it's no problem to get rid of the query params, URL's will eventually at some point in the future always contain all query parameters that are necessary to locate a resource.
Switchable Controller Actions
SCA's have been an invention to mitigate the problem that TYPO3 necessarily needed to generate query parameters for non default controllers and action. The idea is to override the set of controllers and actions of a plugin to change the default controller and action to not have the need for query parameters in the URL. And I hope you see that. This is just plain wrong. It was a just ok solution back then to mitigate the problems we all had with realurl and the demands of clients for clean URL's but as of version 9, that problem does no longer exist and it's only right and technically clean to address all resources with their full URI.
SCA's can't only redefine the default action and controller during runtime, they in fact completely override the plugin configuration of
configurePlugin(). This makes it possible to reduce the set of plugins, controllers and actions, which is a good thing itself. The bad thing is, that this is done during runtime, pretty much hidden from everyone in flexforms in the database which has some severe consequences:
- The configuration cannot be validated against the actual set of available controllers and actions. This is less important in a fresh installation but after some extension updates and refactorings the set of switchable controller actions might not match the actual controllers and actions anymore, which then leads to errors in the frontend. For an experiences developer this issue is quite easy to spot but if you are less advanced you have to have all the knowledge about where the plugin is defined in the first place, where it it's override configuration (flexform) is stored, which content element causes the error and what exact configuration in the flexform of that content element causes the error. While reading this, please admit, you've been there and you got angry.
- The issue mentioned in 1) gets even worse if you use SCA's to override plugins of other extensions. The error risk is higher, the error search lasts longer.
- SCA's may reduce the set of allowed actions to be called. As
configurePlugin()is no longer the single source of truth, you have to look up the flexform definition to check which controller action pairs are allowed for a plugin.
- If SCA's are used, you have no other chance than to edit the flexform configuration if you want to change the plugin configuration. There is not way around it.
- If a plugin content element with SCA's gets reused for another plugin, the flexform data has to be cleared by hand. This is by far the worst UX issue with SCA's. Especially less experienced developers go mad when the frontend errors due to a non callable action which is non even defined in the newly selected plugin.
- SCA's make it impossible to gather and display information about plugins in a information module e.g. As all the flexforms are evaluated during runtime, one cannot reliably tell users how their plugin configuration looks like. On top of that, the configuration cannot be validated properly which means that errors always become visible to the user (frontend).
Issues and their solution
If I need to create a plugin for each SCA, I end up with dozens of plugins in the list
- First of all, there is no need to create a plugin foreach combination of controller/action unless your goal is to have plugins that are callable without query parameters that define controller and action. It's a misconception that the configuration of the plugin defines the default controller and action anyway. It's the case at the moment but as explained further above, a route must define all query parameters that are necessary to identify a resource. The idea that a URL without any indicator for a controller and action magically finds a sensible default is a concept of the past which is to be left behind.
- Secondly, if you still have the need for 20 plugins, there needs to be a way to organize them better in the backend. A simple select box that lists all the user generated labels for plugins is very bad UX already. The selection needs to be grouped at least. But even better would be a completely different UI approach, maybe one that works with groups and icons instead of a list. To be honest, I don't know how the best approach looks like but I can imagine that we find a very good solution here. Especially in 2020 where browser give as more possibilities than ever.
- Ok, you still have too many selections the user can choose from? What about registering the most used plugins as content elements? I bet there are some plugins that are a lot more used than others. List and detail views e.g. They are more used than special plugins like
- "I am fine with my own plugins now but what about the plugins of 3rd-party extensions?" Well, there is no solution yet but with all plugins defined at a very early stage, it is easy enough to override the plugin configuration of other extensions. This especially includes removing non-needed plugins. You only need the list plugin of
news? Deactivate all others and you're done. Sounds good, doesn't it?
- Ok, one last different approach. You get the chance to disable all plugins of a 3rd-party-extension and you create a custom plugin for that extension. This approach is actually doable already, it's just a bit hacky. But it is possible to remove extensions from the global stack and register your own replacements.
SCA's only hide a UX issue that we already have and which we have to solve anyway. That includes some work of the developer/integrator as well of course. A lot of things are doable already, I strive to make them more easy for you before taking away SCA's from you.
[This could be your issue with removed SCA's]
Tell me about your fears regarding the removal of SCA's and I will update this document with a statement.