Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexanderschnitzler/c685218feea1a8956cc3f915f7a08d0b to your computer and use it in GitHub Desktop.
Save alexanderschnitzler/c685218feea1a8956cc3f915f7a08d0b to your computer and use it in GitHub Desktop.

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.

  1. \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin()
  2. \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin()

The naming of those methods is bit irritating as registerPlugin does indicate that it needs to be called whereas 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.

configurePlugin()

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 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'].

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.

registerPlugin()

This method on the other hand is a helper method which actually just registers the plugin in the TCA of type list of 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.

  1. 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).
  2. 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 and /?id=1&tx_news_pi[action]=defaultAction. Actually, this gets even worse if we respect default controllers here, because they exist as well.
  3. 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 and /?id=1&tx_news_pi[action]=defaultAction and /?id=1&tx_news_pi[controller]=defaultController and /?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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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

  1. 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.
  2. 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.
  3. 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 news archive e.g..
  4. "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?
  5. 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.

Conclusion

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.

@robert-heinig
Copy link

Thank you for your thoughts and all this information!
I'm in favor of deprecating SCAs in their current form, but I think their most common usecase requires a replacement.

First of all I want to point out that a lot of bad UX with SCAs is a general problem with the use of Flexforms, and not necessarily limited by the use of SCAs. But that is out of scope of this topic.

In my experience SCAs are a lot of times not used directly as separate resources, and are therefore never addressed directly with query params.
Two examples:
1: Different views of the same data type like "list" or "grid".
2: Different data types like "list of CDs" or "list of DVDs".
The plugins with SCAs like this are often single use plugins on different pages, so the resource is only the page itself and not related to the plugin.

Why do developers use SCAs this way?
Pure convenience. You could implement this with custom Flexform settings like "view" in example 1 and "type" in example 2, but with SCAs the template is mapped automatically and you don't have to parse the settings yourself.
If you take SCAs away, developers will complain that they have to do this tedious setting parsing and template mapping stuff manually. It's not something you're relishing as a developer.
If usecases like this stay within flexforms (which they imo will), there is not much gained related to further development and refactoring. Even if separate plugins without any configuration are used, if you can't deploy all the changes as code and non structural database changes are necessary, it imo does not matter if the query is a little bit more complicated because you have to replace a string within the flexform data. Or if you migrate the plugins manually in the backend. The threshold is the necessity of changes you can't deploy as code.

An option to enable the selection of a single action of a controller within a plugin in the backend is a very nice feature which enables developers to avoid tedious tasks and therefore should be provided by TYPO3.
The permission feature of the current SCAs could be dropped imo, and this configuration could be done outside of flexforms. With annotations maybe?
You still will have to store the information which action the plugins uses in the database, but if you use separate plugins the type of the plugin is stored in the database and may need manual changes because of refactoring. I'm not able to see an improvement in quality when using separate plugins.

Summary: deprecating SCAs is good, but please provide a replacement for multi action plugins controllable in the backend.

@alexanderschnitzler
Copy link
Author

Hi Robert,
thanks for your comment. There are some things I don't really understand in your statement.

Pure convenience. You could implement this with custom Flexform settings like "view" in example 1 and "type" in example 2, but with SCAs the template is mapped automatically and you don't have to parse the settings yourself. If you take SCAs away, developers will complain that they have to do this tedious setting parsing and template mapping stuff manually. It's not something you're relishing as a developer.

Well, you also have the "convenience" of an automatic template mapping without flexform at all if you treat every action as a unique resource with just one purpose. I clearly discourage the use of any kind of configuration them enables you to give multiple purposes to one action. Controllers are not intended to hold any logic themselves. Therefore it's not necessary to use the same action for different purposes.


If usecases like this stay within flexforms (which they imo will), there is not much gained related to further development and refactoring.

Well, I can't force people not to use flexforms, reimplement SCA's or the like and have a dirty architecture. But I will make the lives of those very hard in the future. Not because I want to make things hard but because I want to teach people to have a clean architecture and benefit from it. Even better though if they understand my intentions.


Even if separate plugins without any configuration are used, if you can't deploy all the changes as code and non structural database changes are necessary, it imo does not matter if the query is a little bit more complicated because you have to replace a string within the flexform data. Or if you migrate the plugins manually in the backend. The threshold is the necessity of changes you can't deploy as code.

I totally don't get that. What changes exactly should be deployable? And what string do I have to replace in the flexform? I am puzzled.


An option to enable the selection of a single action of a controller within a plugin in the backend is a very nice feature which enables developers to avoid tedious tasks and therefore should be provided by TYPO3.

I disagree because that contradicts what you said before. I don't want to store the information about which action is to be called in the database. I want users to define that in their routes by defining the default query params that lead to the selection of the "default" controller and action. That configuration can be put into version control and is deployable.


The permission feature of the current SCAs could be dropped imo, and this configuration could be done outside of flexforms. With annotations maybe?

I agree on that one. And I'd like to extend that statement. My goal is to drop the definition of callable actions at all. It means, all actions that are public methods, are callable by default. This of course, is only possible if there is another firewall solution in place.


You still will have to store the information which action the plugins uses in the database

No, that is a misconception I guess. The list of callable actions is defined by the extension author and it's available during runtime as it's stored in a global variable (a registry class in the future) and the database does not need to know about controllers and actions.


but if you use separate plugins the type of the plugin is stored in the database and may need manual changes because of refactoring.

I don't understand that statement at all. Sorry.


I'm not able to see an improvement in quality when using separate plugins.

Using different plugins is not the concequence of dropping SCA's. In fact, you can very well just define one plugin with all the callable actions and give it a generic name. The main reason, people use different plugins, is to take advantage of the implicit configuration that comes with it: The firewall and the default controllers and actions which they don't need to use query parameters for. That in fact is a bad habit and I am about to change this. As soon as the order of defined controllers and actions doesn't determine the defaults any more, there is no need to register different plugins for that reason. In fact, I believe people with use less plugins.

The idea to create different plugins to replace all the SCA's now (version 10 LTS) does not make sense because this indeed only means a massive overhead in reconfiguration and refactoring of everything. Dropping SCA's is just one part of this whole change I am heading for. The more important and interesting thing is the replacement of the current way of configuring plugins. There is no code yet but without that change, dropping SCA's doesn't make any sense at all. Then, things would just be messed up for no reason.

@benwick
Copy link

benwick commented Mar 7, 2020

So it will still be possible to call a plugin/controller/action via typoscript? I am using this quite often. Defining a USER content object and rendering it inside fluid with the CObject viewhelper makes my life as a typo3 developer simpler. For this to work the plugin should only contain one controller and action combination as far as I have understood your article.

@alexanderschnitzler
Copy link
Author

So it will still be possible to call a plugin/controller/action via typoscript?

Well, for now, yes. I am aware of that mechanic and that many people use is but I am not sure how to properly deal with plugins being dispatched from a typoscript context. But as long as the core dispatches plugins via typoscript, you are able to do so yourself. So, for now, nothing to worry about.

@TokeHerkild
Copy link

How many of the developers who thinks this is really great, have tried to work on enterprise sized TYPO3 installations, and seen the differences with and without SCA's ?

@ursbraem
Copy link

ursbraem commented Aug 1, 2022

I think it would be helpful to see an example of an extension with SCA, how it can be transfered into

a) full split up and
b) what you mention in forge "No, you don't need to create a single plugin for each controller/action combination, only for those entrypoints where controller and action params should be hidden in the url."

Actually, I'm worried about extending tx_news with an additional action without recreating the entire flexform, which would have been done with SCA previously. Any thoughts on that use case?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment