refactor(cache): Reorganize the Page Cache with SubscriberInterface#35986
refactor(cache): Reorganize the Page Cache with SubscriberInterface#35986anibalsanchez wants to merge 11 commits intojoomla:4.1-devfrom
Conversation
|
@anibalsanchez In the testing instructions you write that we shall check that the page is not cached when
Can it be that this is a copy paste from the description of the case when the page shall be cached and that the „not“ in that sentence is wrong here for the not cached case? |
|
@anibalsanchez What I mean In my previous comment is following, see the red mark: |
|
Thanks @richard67 I've updated the testing instruction to clarify the inconsistency. Pls, check it again. |
@anibalsanchez Looks ok now. Thanks. |
|
For me there is too much static stuff in here. I would just return the events in |
nikosdion
left a comment
There was a problem hiding this comment.
Having had the time to fully review the code I am not happy.
When you use SubscriberInterface the method names have no meaning. The association between event names and method names is what getSubscribedEvents returns.
Since there is no need to “keep b/c” in an internal plugin class that should not be extended you don't need to keep the old method signatures. Just upgrade them to have the correct signature for Event handles (a singular argument of the type Event, returns void) and use their original names in the getSubsribedEvents to make it easier for humans to parse what is used when and why. You are not using the correct signature.
Use the check canPageBeCached on each method. The check itself should be a regular private method, not static.
Do not use static methods, there is no need for that. These static methods are probably leftovers from the Mambot days. If we're upgrading the plugin we might just as well do it right.
There is no need for a constructor. Returning the cache object should be done lazily with a private getter. This prevents the overhead of setting up caching when we do not need it.
The goal is to simplify this plugin, not turn it into a puzzle.
plugins/system/cache/cache.php
Outdated
| */ | ||
| public static function getSubscribedEvents(): array | ||
| { | ||
| if (!self::canPageBeCached()) { |
There was a problem hiding this comment.
Best not check anything here. This is called when the plugin is loaded, possibly before input and identity are initialized. Besides, you are checking canPageBeCached on each method anyway.
There was a problem hiding this comment.
If we check it here, we avoid the plugin execution completely.
plugins/system/cache/cache.php
Outdated
| } | ||
|
|
||
| return [ | ||
| 'onAfterRender' => 'verifyThatCurrentPageCanBeCached', |
There was a problem hiding this comment.
This is a fine mess... Since you are implementing on onAfter... methods anyway use them instead of the misleadingly named methods in here.
Remember that when you use SubscriberInterface Joomla will only register event handlers you specify in this static method. Method names DO NOT have special meaning but it's a hell of a lot easier for us humans to see that the onAfterRender event is handled by a method called onAfterRender which now accepts a single Event argument.
There was a problem hiding this comment.
I hesitate to define the event callbacks in this way:
'onAfterRender' => 'onAfterRender',
'onAfterRoute' => 'onAfterRoute',
'onAfterRespond' => 'onAfterRespond',It is a duplication of information and it looks like the Joomla 3 implementation.
If we assume that each callback has only one action/responsibility, then it is better to associate the event with the right action name.
|
Thanks for all your feedback. Until now, people have been happy with the current plugin and the legacy events implementation. I didn't want to change how the plugin itself has been working, and the original idea has been only to use the Before investing more time in the PR, can we confirm that it will be merged if we continue improving the code? Maybe @bembelimen can give us a hint? |
|
Me personally would like to get rid of all old events in 5.0 and replace them with the new subscriber interface version. Since we don't have a fallback from events written with the subscriber interface to the old "onXXX" functions we would need to introduce new event names. Even if we don't remove the old event system in 5.0 we should rewrite all events to the subscriber interface including proper parameter validation. Also I would be more strict with core plugins and mark them as |
|
@HLeithner Let me address everything. Renaming Events: NO!You do NOT have to rename the events themselves. You just have to remove the legacy plugin support from CMSPlugin added by me and Michael. Its one and only job is to trawl through the public methods, find which ones start with When you remove the legacy wrapper old plugins (replying on the legacy wrapper) will stop working; they will load but do nothing at all. New style plugins, implementing the SubscriberInterface, will work just fine. Renaming the events themselves causes a massive migration issue: it is impossible to write an extension which is simultaneously compatible with Joomla 4 and 5. This means that it's impossible for users to upgrade to Joomla 5 unless they EITHER start from scratch OR remove all third party extensions losing all related data, i.e. start from scratch. Furthermore, renaming the events makes two decades of knowledge and documentation invalid overnight. That's a good way to dissuade new developers to come to Joomla. There's really no good reason to rename the events and there are many good reasons to keep the names the same. Creating Event classes: YESThat was the intention all along. I had created a few event classes for the Table events as a sampler and Proof of Concept — even more so since the scope of my original PR 6 years ago was to refactor Tags and other behaviors into “orthogonal” plugins, handling specific Events. What you did for the Media Manager is on track with the original intention. IMHO all core events such as onAfterInitialise, onContentPrepare (especially this event; each instance of it is ridiculously inconsistent with every other instance!) and so on should have their own Event classes. This will make them more discoverable, self–documenting and also remove any ambiguities about their call arguments. FWIW even plugins which currently expect a generic Event object will work as the more specific event classes still extend from the base Event class. In other words, introducing specific event classes should not affect b/c. It will just make core events discoverable and self–documenting. Once the core starts doing that 3PDs can also follow suit if they feel so inclined (FWIW it is on my to–do list for my extensions).
|
|
I opened the PR to help in the migration of the legacy stuff, but now it seems that we are again talking about the next major version. Joomla 5 is on the horizon ... (Nov 2021). |
|
Migrating the legacy stuff is intrinsically linked to the next major version. Joomla 4 deprecated stuff but Joomla 4 itself is using the deprecated stuff. What you do is a necessary step to get rid of deprecated legacy stuff. How much do we change this plugin? It depends on what we want to do with the next major version 😜 I would personally go with a full Joomla 4 native plugin (with a service provider) so that we don't need to refactor the refactored code in the future and get rid of deprecated legacy in the next major version. If we are OK kicking the can on removing legacy — essentially removing support for how we were writing plugins in 2007 when J5 is released circa, dunno, 2027? — we can just add SubscriberInterface and call it a day. You see why discussing Joomla 5 long before any code is written for it makes sense so early in the 4.x release cycle? It's all about legacy handling and making sure we don't screw up Joomla in the future. |
|
Okok. I'll give it a second pass to clean the hybrid approach and the plugin code. |
|
@nikosdion Thanks for your detailed description, it's very helpful. I have still some questions and comments.
Oh it seems I missed this while I looked the last time, so of course it doesn't make sense to change the names when we have a working b/c layer.
Sadly I think the documentation would be outdated anyway and have to be reworked to match the subscriber interface, but of course it's easier to find the right event. Thanks for pointing this out.
What you mean exactly with "(especially this event; each instance of it is ridiculously inconsistent with every other instance!)"? When we clean up/convert the event api it would make sense to do it right this time?
I had a long talk with @bembelimen couple of weeks ago about this topic to convert all events to subscriber events until 4.2 hopefully we get some traction here to achieve this earlier.
That's what I mean with my note. Mark things final that should be final, and I think plugins are nothing that what you want to extend.
Maybe I misunderstood this but the event listener functions need to be public.
Actually we have no semver promise on plugins, and I don't see a reason to wait for 5.0 to change them to use the service provider interface and use namespaces.
Yeap, that means also that all Events have to be changed to their own
Is this hard? It doesn't sound to be or do I miss something?
Only had a quick look but shouldn't the
Personally I would like to see joomla being able to run multiple times in the same process, but I think that will never happen.
Will do or at least take bigger parts of it. Will see how it fits and who will do it. One other thing. How should the validation work in the events if you look at the new media event's I added much validation into the setArgument functions because we don't have an interface for the "file-object". Is this a way to go if we don't have an interface and object that can validate the data itself. In this case something like MediafileInterface with getter and setter defined which do the validation? |
We use that event everywhere, from populating fields to processing plugin codes (e.g.
Sounds very promising!
You are correct. My brain farted; I was replying past 1 am my time 😅
Is this mentioned anywhere? Because if it's not there's the implicit promise that plugins follow SemVer like the rest of the CMS and its core extensions. FWIW core components do follow SemVer so it would be at least internally inconsistent to treat ONE kind of core extensions differently than the rest of the CMS... I would recommend at the very least a marketing campaign before the work even starts so that 3PDs are not caught off–guard in the off–chance they are using a core plugin in a weird way — and yes, I have seen THINGS...
Precisely. That's what I meant.
Well, two hurdles. First, how do you convey the name of the plugin/module if not by an attribute to the filename? An attribute to the root
Not on CLI. Can't remember exact details, it was definitely after 4.0.1 that I was creating a CLI command that kept failing because the identity was null instead of a guest user.
I'd rather limit the probability of a 3PD reenacting Inception on a server...
The setters should do the validation. Also make sure you call the setters from the constructor. This keeps all checks straightforward. Whether you should implement an interface or not... It depends and needs to be examined on a case by case basis. Unless you have a desperate need for an interface (as opposed to just making sure something is an object which has a specific method) go for it. If you want to leave the event more open–ended, e.g. like what you need for onContentPrepare, don't use an interface. I have obviously not used the media manager events myself that's why I am not handing down an opinion on them. I can tell you that most of the basic events for content and core application events shouldn't have their arguments tied to interfaces because it will break a lot of things in ways that cannot be unbroken. Don't go for pedantry over flexibility. Joomla's bread and butter is flexibility. Yeah, I know, I am a pragmatist, not a purist. Come with the territory of publishing mass distributed software. Those who want purism can write their own apps from scratch using Symfony (I won't even say Laravel; purists will self–combust on contact with Laravel's façades). |
OK, now I understand what you mean.
Our b/c Promise is only for the /library folder, point 6.1.1 at https://developer.joomla.org/development-strategy.html#backward_compatibility
The fun part of this document is that javscript has a much harder b/c promise then the php part. (6.1.2 All JavaScript functions and classes that are not flagged as private.) Of course, allowed to break things doesn't mean we should do this.
Supporting 4.0 and 5.0 shouldn't be a problem (even 3.x should be possible if the service provider is not loaded). Based on your https://github.com/akeeba/sociallogin/blob/feature/joomla4/plugins/system/sociallogin/services/provider.php the loader doesn't need to now the plugin name because it the service provider has a defined name. Or we talk about diffrent topics.
Yeap I think I have saw this, the CLI application doesn't load a user, the question is should the cli application has an user, but maybe that's something for it's own discussion.
Yeah it could be hard, I really like reactPHP and with Fibers in php 8.1 I think we can increase performance on this topic but nothing for a short or midterm goal.
Of course, breaking things just for the sake of consistency is a bad idea. Trying to force existing events into a corset also causes more problems than it solves. Thanks for your feedback. |
|
I have re-organized the plugin according to the comments.
One pending pain point is that the Helpers are not being autoloaded... even when the namespace is defined in the manifest. I still have to investigate this. |
| /** | ||
| * Check if the page is excluded from the cache or not. | ||
| * | ||
| * @param string $cacheKey Cache Key | ||
| * | ||
| * @return boolean True if the page is excluded else false | ||
| * | ||
| * @since __DEPLOY_VERSION__ | ||
| */ |
This comment was marked as abuse.
This comment was marked as abuse.
Sorry, something went wrong.
There was a problem hiding this comment.
Yep. I have to active the Joomla code style ;-) Tomorrow. Txs
| if ($exclusion !== '') | ||
| { | ||
| // Test both external and internal URI | ||
| if (preg_match('#' . $exclusion . '#i', $cacheKey . ' ' . $internalUri, $match)) |
This comment was marked as abuse.
This comment was marked as abuse.
Sorry, something went wrong.
There was a problem hiding this comment.
I will clean it. Txs.
Co-authored-by: Phil E. Taylor <phil@phil-taylor.com>
Co-authored-by: Phil E. Taylor <phil@phil-taylor.com>
Co-authored-by: Phil E. Taylor <phil@phil-taylor.com>
This comment was marked as abuse.
This comment was marked as abuse.
Co-authored-by: Phil E. Taylor <phil@phil-taylor.com>
| $pageCacheKeyGenerator = new PageCacheKeyGenerator(Uri::getInstance()); | ||
| $cacheKey = $pageCacheKeyGenerator->getKey(); | ||
|
|
||
| if (!PageCachingChecker::canPageBeCached($cacheKey)) { |
There was a problem hiding this comment.
I don't know how others see this but from my understanding, the events where the plugin is listen to should not be dependent on the environment. I would always return the same events and then do the check in the respective function. It looks for me too much load during initial phase. I would more go for the lazy load approach and do the check in the function itself. Like that you can then get rid of the static helper class and work on the plugin $app instance.
There was a problem hiding this comment.
I fully agree with that and that's what I had commented on previously.
There was a problem hiding this comment.
If the event subscription is not dynamic and the plugin is always instantiated and subscribed to the same methods, then it is the same as on Joomla 3. We are only moving around the same code. It is simpler to keep onAfterRender, onAfterRoute, and onAfterRespond following the old way of subscribing to the events.
If there is a static function getSubscribedEvents to subscribe the plugin to events, then it is meant to return an empty array or a set of events. In this case, there are conditions where the event set can be empty.
There was a problem hiding this comment.
It's really not the same. Do you want me to convert this simple plugin over the weekend, do a new PR and discuss what was changed and why? I've been doing that for all my plugins, it's not a big deal for me to do that. I just don't have the time during the week.
There was a problem hiding this comment.
In my understanding you you should never need to use Factory::getXYZ(); I know the application is not available in every context (also the CMSPlugin loads it from the Factory but should be possible to change to get it from the provided container).
Also I don't see any benefit to split the functions from the plugin it self. The Plugin class has just 250 lines of code splitting this into 3 helper classes just makes it slower but not more readable.
There was a problem hiding this comment.
Yeah, I agree with you Harald. The concept is that we keep the plugin mostly the same but:
- Use a service provider, to showcase how true J4 native (non-legacy) plugins are meant to be written
- Use SubscriberInterface vs the legacy method which goes through reflection (it's faster with SubscriberInterface and more explicit; also shows that plugin events will be handling an Event object, not arbitrary argument lists)
- Remove all static crap
- Clean up dead code
I have practically rewritten this in my head, maybe if I have a bit of time tomorrow I'll show you what I mean.
The plugin will be bigger (going through a service provider and SubscriberInterface adds some boilerplate) but since we no longer go through Reflection it's actually faster. Granted, a small plugin like that seems to get more complicated (it doesn't really, it's just boilerplate) but for bigger plugins e.g. the WebAuthn plugin it would make much more sense. It's just that the smallest piece of code is always a good way to start a bigger conversion project ;)
|
Since you prefer a different way of implementing the plugin, I close the PR. Thanks for all your feedback. |

Pull Request for Issue #35722 #35625 #35877.
This PR for Joomla 4.1 is the follow-up of the previous issues #35722 #35625 #35877 to solve the same problem using the Joomla 4
SubscriberInterface. It is the same function but uses the new plugin event subscriber interface.Summary of Changes
This PR refactorizes the original System Page Cache to work based on J4
SubscriberInterface. The plugin is an interesting case to use theSubscriberInterface. The plugin is not executed if it doesn't subscribe to any event. The plugin must only be run under these conditions:On Joomla 3, all event methods are always called, leading to duplicated checks and inconsistencies on all methods.
The plugin refactorization avoids duplicated code.
Testing Instructions
Actual result BEFORE applying this Pull Request
The event methods are called following the Joomla 3 model (
onAfterRoute, 'onAfterRender', 'onAfterRespond').The final result of delivering the cached page is the same.
Expected result AFTER applying this Pull Request
The event methods are called following the Joomla 4
SubscriberInterface(verifyIfCurrentPageCanBeCached, 'dumpCachedPage', 'storePage').The final result of delivering the cached page is the same.
Documentation Changes Required
As mentioned @HLeithner before here #35877, there is a need to document how to develop a Joomla 4 plugin using the new
SubscriberInterfaceand avoid the previous way to define the events methods.CC: @alikon @HLeithner @Kubik-Rubik @PhilETaylor @tampe125 @nikosdion @brianteeman @richard67 @ceford @ricardo1709