? Pending

User tests: Successful: Unsuccessful:

avatar joeforjoomla
joeforjoomla
26 Oct 2018

Summary of Changes

@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.

Testing Instructions

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.

Expected result

The new component format is always evaluated and instantiated

Actual result

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];
		}

avatar joeforjoomla joeforjoomla - open - 26 Oct 2018
avatar joeforjoomla joeforjoomla - change - 26 Oct 2018
Status New Pending
avatar joomla-cms-bot joomla-cms-bot - change - 26 Oct 2018
Category Libraries
avatar laoneo
laoneo - comment - 27 Oct 2018

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.

avatar joeforjoomla
joeforjoomla - comment - 27 Oct 2018

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.

avatar joeforjoomla
joeforjoomla - comment - 27 Oct 2018

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.

avatar joeforjoomla joeforjoomla - change - 27 Oct 2018
Labels Added: ?
avatar joeforjoomla
joeforjoomla - comment - 27 Oct 2018

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.

avatar mbabker
mbabker - comment - 27 Oct 2018

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.

avatar joeforjoomla
joeforjoomla - comment - 27 Oct 2018

I agree with you @mbabker but i can't find a way to use a static variable here.

avatar laoneo
laoneo - comment - 29 Oct 2018

Something like an extension loader can work here. @mbabker what you think to introduce such a class where we load it through a service provider in to the global container?

avatar joeforjoomla
joeforjoomla - comment - 29 Oct 2018

IMHO having an extension loader class at the global container level should solve the problem.

avatar brianteeman brianteeman - change - 31 Oct 2018
Title
Unreliable way of loading ServiceProviderInterface class in ExtensionManagerTrait.php
[4.0] Unreliable way of loading ServiceProviderInterface class in ExtensionManagerTrait.php
avatar brianteeman brianteeman - edited - 31 Oct 2018
avatar joeforjoomla joeforjoomla - change - 25 Jan 2019
The description was changed
avatar joeforjoomla joeforjoomla - edited - 25 Jan 2019
avatar SharkyKZ
SharkyKZ - comment - 19 Mar 2019

Since #23667 has been merged, can this be closed?

avatar joeforjoomla
joeforjoomla - comment - 19 Mar 2019

Yes sure, close this one

avatar Quy Quy - change - 19 Mar 2019
Status Pending Closed
Closed_Date 0000-00-00 00:00:00 2019-03-19 21:58:37
Closed_By Quy
avatar joomla-cms-bot joomla-cms-bot - change - 19 Mar 2019
Closed_By Quy joomla-cms-bot
avatar joomla-cms-bot joomla-cms-bot - close - 19 Mar 2019
avatar joomla-cms-bot
joomla-cms-bot - comment - 19 Mar 2019

Set to "closed" on behalf of @Quy by The JTracker Application at issues.joomla.org/joomla-cms/22825

Add a Comment

Login with GitHub to post a comment