News:

Support the VirtueMart project and become a member

Main Menu

VM creates multiple plugin instances

Started by Ghost, October 25, 2024, 10:32:14 AM

Previous topic - Next topic

Ghost

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.

Milbo

Do you mean line 236?

the part line 209 - 226 uses the joomla plugin container and should not create multiple instances.
Should I fix your bug, please support the VirtueMart project and become a member
______________________________________
Extensions approved by the core team: http://extensions.virtuemart.net/

Ghost

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?

hazael

#3
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;
        }
    }
}