User tests: Successful: Unsuccessful:
@laoneo There is an underlying and conceptual problem in the way that the ServiceProviderInterface anonymous class is loaded and evaluated now in the loadExtension function. Indeed the require_once doesn't provide the same result for a second inclusion of the file/anonymous class.
The very first time the require_once is executed indeed, the real instance of the anonymous class is included and evaluated as a class implementing the ServiceProviderInterface and the new component format is recognized correctly.
However on a second execution of the same require_once the result is a boolean 'true' and there is a fallback on the LegacyComponent, not recognizing correctly the new component format even if it's there.
To reproduce this issue, trigger a second execution of the bootComponent for the same component, for example in the file:
joomla40/administrator/components/com_content/View/Articles/HtmlView.php
inside the 'display' function add the following code:
$siteRouter = \Joomla\CMS\Router\SiteRouter::getInstance ( 'site' );
// Now instantiate and parse the faked url from backend, replacing the uri base it will be = site
$uriObject = \Joomla\CMS\Uri\Uri::getInstance ( 'index.php?option=com_content&view=article&id=1' );
$parseUri = $siteRouter->build ( $uriObject );
Replacing this with a simple 'require' will fix this issue, however i'm afraid of other side effects, so feel free to discuss if needed.
The new component format is always evaluated and instantiated
The second time the $siteRouter->build executes the bootComponent, the same component is evaluated as a LegacyComponent
I noticed that there is a check at the beginning of the loadExtension that could/should prevent this issue but it doesn't seem to work either, indeed the second time the function is invoked it has been reset:
// Check if the extension is already loaded
if (!empty($this->extensions[$type][$extensionName]))
{
return $this->extensions[$type][$extensionName];
}
Status | New | ⇒ | Pending |
Category | ⇒ | Libraries |
Well yes but that's not the case. This is the reason why i pointed you out. You should get always the same instance on a second boot but that's not what's happening. Ball in your hand here.
I narrowed down the problem a bit.
If you are in the administrator application and you execute this multiple time:
$app = \Joomla\CMS\Factory::getApplication();
$componentInstance = $app->bootComponent('com_content');
$componentInstance2 = $app->bootComponent('com_content');
$componentInstance3 = $app->bootComponent('com_content');
everything works as expected because the $app is an instance of Joomla\CMS\Application\AdministratorApplication and the array $this->extensions[$type][$extensionName] in the trait ExtensionManagerTrait is correctly populated and the component is 'cached'
If you are in the administrator application and you execute the following code:
$siteRouter = \Joomla\CMS\Router\SiteRouter::getInstance ( 'site' );
$uriObject = \Joomla\CMS\Uri\Uri::getInstance ( 'index.php?option=com_content&view=article&id=1' );
$parseUri = $siteRouter->build ( $uriObject );
that instantiates the SiteRouter class, as a result the $this->app in the class SiteRouter is an instance of the object Joomla\CMS\Application\SiteApplication. In such case the $this->extensions[$type][$extensionName] in the trait ExtensionManagerTrait is empty and the component is not 'cached' anymore.
The second 'require_once' inside the function loadExtension returns true and as a result the component is dispatched as a LegacyComponent object.
Thus the problem is related to the $app class instance not matching the previous one and it would be solved if the cache was shared between both Site/Adminitrator application objects.
Labels |
Added:
?
|
I modified the PR in order to use the session to load and share loaded components between different instances of the application object. This works.
Basically you can reproduce the issue doing this in administration:
$app = \Joomla\CMS\Factory::getContainer()->get(\Joomla\CMS\Application\AdministratorApplication::class);
$componentInstance = $app->bootComponent('com_content'); // Instance of Joomla\Component\Content\Administrator\Extension\ContentComponent
$app = \Joomla\CMS\Factory::getContainer()->get(\Joomla\CMS\Application\SiteApplication::class);
$componentInstance = $app->bootComponent('com_content'); // Instance of Joomla\CMS\Extension\LegacyComponent
I think that the best solution however would be to decouple the ExtensionManagerTrait from the Application class instance. Technically i don't see any particular reason why it should be part of the application object instance instead of an indipendent class.
The session's not a good idea as that data is going to persist across requests. You really need some kind of static variable somewhere that won't cross requests.
IMHO having an extension loader class at the global container level should solve the problem.
Title |
|
Yes sure, close this one
Status | Pending | ⇒ | Closed |
Closed_Date | 0000-00-00 00:00:00 | ⇒ | 2019-03-19 21:58:37 |
Closed_By | ⇒ | Quy |
Closed_By | Quy | ⇒ | joomla-cms-bot |
Set to "closed" on behalf of @Quy by The JTracker Application at issues.joomla.org/joomla-cms/22825
The components should be cached. So there is no need to process it a second time. Basically you should be able to boot a component as many times as you want. You should get always the same instance.