VirtueMart Forum

VirtueMart 2 + 3 + 4 => Virtuemart Development and bug reports => Topic started by: Ghost on October 25, 2024, 10:32:14 AM

Title: VM creates multiple plugin instances
Post by: Ghost on October 25, 2024, 10:32:14 AM
For modern plugins that use service provider VM creates multiple instances of the plugin. It's causing issues like duplicated HTML in vmcustom plugins, among other things. The problem is coming from this code block https://dev.virtuemart.net/projects/virtuemart/repository/virtuemart/revisions/11044/entry/trunk/virtuemart/administrator/components/com_virtuemart/helpers/vdispatcher.php#L209
Not sure what was the idea here but it will eventually cause even more issues like dependency conflicts. If it needs to return the plugin object, maybe $app->bootPlugin() should be used instead.

VM 4.2.18.11050.
Title: Re: VM creates multiple plugin instances
Post by: Milbo on October 30, 2024, 10:40:56 AM
Do you mean line 236?

the part line 209 - 226 uses the joomla plugin container and should not create multiple instances.
Title: Re: VM creates multiple plugin instances
Post by: Ghost on October 31, 2024, 07:44:43 AM
Lines 209-226. I can see the plugin being instantiated at least twice and listeners called even more times. First time it is constructed by Joomla application when plugins are imported using PluginHelper::importPlugin(). This is correct. Then this code registers the plugin and its dependencies again but now with global DIC and returns a new instance. What issue was this code trying solve?
Title: Re: VM creates multiple plugin instances
Post by: hazael on November 04, 2024, 09:45:26 AM
Maybe this will change something?

<?php

use Joomla\Event\Dispatcher as EventDispatcher;

class 
vDispatcher {

    static 
$dispatcher null;

    static function 
trigger ($name$params) {

        if (
self::$dispatcher === null) {
            if (
JVM_VERSION 4) {
                
self::$dispatcher JEventDispatcher::getInstance();
            } else {
                
self::$dispatcher JFactory::getApplication();
            }
        }

        if (
JVM_VERSION 4) {
            return 
self::$dispatcher->trigger($name$params);
        } else {
            return 
self::$dispatcher->triggerEvent($name$params);
        }
    }

    static function 
setDispatcher() {
        
// Implementation for setting a custom dispatcher, if needed.
    
}

    
/**
    * Triggers Joomla 5 plugins directly, backward compatible for J3, J4.
    * @param $plg
    * @param $trigger
    * @param $args
    * @param $ret
    * @return array|mixed|void|null
    * @throws ReflectionException
    */
    
static function triggerJ5($plg$trigger$args, &$ret) {

        if (
is_object($plg)) {
            
$className get_class($plg);
            if (
method_exists($className'getSubscribedEvents')) {
                
$evts $className::getSubscribedEvents();
                if (!empty(
$evts[$trigger])) {
                    
$fn $evts[$trigger];
                    
$reflector = new ReflectionClass($plg);
                    
$parameters $reflector->getMethod($fn)->getParameters();
                    
$EventClass '\Joomla\Event\Event';
                    foreach (
$parameters as $param) {
                        
$cTest $param->getType();
                        if (
$cTest instanceof ReflectionNamedType && strpos($cTest->getName(), 'Event') !== false) {
                            
$EventClass $cTest->getName();
                            break;
                        }
                    }

                    
$event = new $EventClass($trigger$args);
                    if (
is_array($args)) {
                        foreach (
$args as $named => $value) {
                            
$event->setArgument($named$value);
                        }
                    }
                    if (
is_array($ret)) {
                        
$ret[] = call_user_func_array([$plg$fn], [$event]);
                    } else {
                        return 
call_user_func_array([$plg$fn], [$event]);
                    }
                }
            } elseif (
method_exists($plg$trigger)) {
                if (
is_array($ret)) {
                    
$ret[] = call_user_func_array([$plg$trigger], $args);
                } else {
                    return 
call_user_func_array([$plg$trigger], $args);
                }
            }
            return 
$ret;
        }
        return 
null;
    }

    static public function 
directTrigger($type$element$trigger$args$enabled true) {
        
JPluginHelper::importPlugin($type);
        
$app JFactory::getApplication();
        
$plg $app->bootPlugin($element$type);
        
        if (
$plg) {
            
$ret false;
            return 
self::triggerJ5($plg$trigger$args$ret);
        }
        return 
null;
    }

    static function 
importVMPlugins($ptype) {
        static 
$types = ['vmextended' => true'vmuserfield' => true'vmcalculation' => true'vmcustom' => true'vmcoupon' => true'vmshipment' => true'vmpayment' => true];
        if (!isset(
$types[$ptype])) return;

        foreach (
$types as $type => $v) {
            
JPluginHelper::importPlugin($type);
            unset(
$types[$type]);
            if (
$type == $ptype) {
                break;
            }
        }
    }

    static public function 
createPlugin($type$element$enabled true) {
        if (empty(
$type) || empty($element)) {
            
vmdebug('Developer error, class vmpluglin function createPlugin: empty type or element');
        }

        
$plugin self::getPlugin($type$element$enabled);
        if (!isset(
$plugin->type) || !isset($plugin->name)) {
            
vmdebug('VmPlugin function createPlugin, plugin not found or unpublished'$type$element);
            return 
false;
        }

        
$app JFactory::getApplication();
        
$pluginObject $app->bootPlugin($element$type);

        if (
$pluginObject) {
            return 
$pluginObject;
        }

        
$className 'Plg' str_replace('-'''$type) . $element;
        if (
class_exists($className)) {
            if (
JVM_VERSION 4) {
                if (
self::$dispatcher === null) {
                    
self::$dispatcher JEventDispatcher::getInstance();
                }
                return new 
$className(self::$dispatcher, (array) $plugin);
            } else {
                
$dummy = new EventDispatcher();
                return new 
$className($dummy, (array) $plugin);
            }
        } else {
            
vmdebug('VmPlugin function createPlugin, class does not exist'$className$type$element);
            return 
false;
        }
    }

    static function 
getPlugin($type$element$enabled true) {
        
$db JFactory::getDbo();
        
$query $db->getQuery(true)
            ->
select($db->quoteName(['extension_id''folder''element''params''state']))
            ->
from($db->quoteName('#__extensions'))
            ->
where($db->quoteName('type') . ' = ' $db->quote('plugin'))
            ->
where($db->quoteName('folder') . ' = ' $db->quote($type))
            ->
where($db->quoteName('element') . ' = ' $db->quote($element));

        if (
$enabled) {
            
$query->where($db->quoteName('enabled') . ' = 1')
                  ->
where($db->quoteName('state') . ' = 0');
        }

        
$db->setQuery($query);

        try {
            return 
$db->loadObject();
        } catch (
Exception $e) {
            
vmError('Could not load Plugin ' $type ' ' $element ': ' $e->getMessage(), 'Plugin Load Error');
            return 
null;
        }
    }
}