diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 0c8cdbd98..d58840701 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -126,6 +126,13 @@ abstract class MetaModel /** @var string */ protected static $m_sEnvironment = 'production'; + /** + * MetaModel constructor. + */ + public function __construct() + { + } + /** * @return array */ @@ -2757,7 +2764,7 @@ abstract class MetaModel $aInterfaces = array('iApplicationUIExtension', 'iPreferencesExtension', 'iApplicationObjectExtension', 'iLoginFSMExtension', 'iLoginUIExtension', 'iLogoutExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPortalUIExtension', 'ModuleHandlerApiInterface', 'iNewsroomProvider', 'iModuleExtension'); foreach($aInterfaces as $sInterface) { - self::$m_aExtensionClasses[$sInterface] = array(); + self::$m_aExtensionClassNames[$sInterface] = array(); } foreach(get_declared_classes() as $sPHPClass) @@ -2768,11 +2775,7 @@ abstract class MetaModel { if ($oRefClass->implementsInterface($sInterface) && $oRefClass->isInstantiable()) { - if (is_null($oExtensionInstance)) - { - $oExtensionInstance = new $sPHPClass; - } - self::$m_aExtensionClasses[$sInterface][$sPHPClass] = $oExtensionInstance; + self::$m_aExtensionClassNames[$sInterface][$sPHPClass] = $sPHPClass; } } } @@ -6357,7 +6360,7 @@ abstract class MetaModel if (is_array($result)) { // todo - verifier que toutes les classes mentionnees ici sont chargees dans InitClasses() - self::$m_aExtensionClasses = $result['m_aExtensionClasses']; + self::$m_aExtensionClassNames = $result['m_aExtensionClassNames']; self::$m_Category2Class = $result['m_Category2Class']; self::$m_aRootClasses = $result['m_aRootClasses']; self::$m_aParentClasses = $result['m_aParentClasses']; @@ -6394,7 +6397,7 @@ abstract class MetaModel $oKPI = new ExecutionKPI(); $aCache = array(); - $aCache['m_aExtensionClasses'] = self::$m_aExtensionClasses; + $aCache['m_aExtensionClassNames'] = self::$m_aExtensionClassNames; $aCache['m_Category2Class'] = self::$m_Category2Class; $aCache['m_aRootClasses'] = self::$m_aRootClasses; // array of "classname" => "rootclass" $aCache['m_aParentClasses'] = self::$m_aParentClasses; // array of ("classname" => array of "parentclass") @@ -6480,6 +6483,8 @@ abstract class MetaModel /** @var array */ protected static $m_aExtensionClasses = array(); + /** @var array */ + protected static $m_aExtensionClassNames = array(); /** * @param string $sToInclude @@ -7274,25 +7279,9 @@ abstract class MetaModel */ public static function EnumPlugins($sInterface, $sFilterInstanceOf = null) { - if (!array_key_exists($sInterface, self::$m_aExtensionClasses)) - { - return array(); - } + $pluginManager = new PluginManager(self::$m_aExtensionClassNames, self::$m_aExtensionClasses); - if (is_null($sFilterInstanceOf)) - { - return self::$m_aExtensionClasses[$sInterface]; - } - - $fFilterCallback = function ($instance) use ($sFilterInstanceOf) - { - return $instance instanceof $sFilterInstanceOf; - }; - - return array_filter( - self::$m_aExtensionClasses[$sInterface], - $fFilterCallback - ); + return $pluginManager->EnumPlugins($sInterface, $sFilterInstanceOf); } /** @@ -7303,16 +7292,9 @@ abstract class MetaModel */ public static function GetPlugins($sInterface, $sClassName) { - $oInstance = null; - if (array_key_exists($sInterface, self::$m_aExtensionClasses)) - { - if (array_key_exists($sClassName, self::$m_aExtensionClasses[$sInterface])) - { - return self::$m_aExtensionClasses[$sInterface][$sClassName]; - } - } + $pluginManager = new PluginManager(self::$m_aExtensionClassNames, self::$m_aExtensionClasses); - return $oInstance; + return $pluginManager->GetPlugins($sInterface, $sClassName); } /** @@ -7462,6 +7444,119 @@ abstract class MetaModel } } +class PluginManager +{ + + private $m_aExtensionClassNames; + private $m_aExtensionClasses; + private $m_pluginInstantiationManager ; + + public function __construct($m_aExtensionClassNames, $m_aExtensionClasses, $m_pluginInstanciationManager=null) + { + $this->m_aExtensionClasses = $m_aExtensionClasses; + $this->m_aExtensionClassNames = $m_aExtensionClassNames; + + if ($m_pluginInstanciationManager==null) + { + $this->m_pluginInstantiationManager = new PluginInstanciationManager(); + } + else + { + $this->m_pluginInstantiationManager = $m_pluginInstanciationManager; + } + } + + /** + * @param string $sInterface + * @param bool $bCanInstantiatePlugins internal use, let this value to true + * @param string|null $sFilterInstanceOf [optional] if given, only instance of this string will be returned + * @return array classes=>instance implementing the given interface + */ + public function EnumPlugins($sInterface, $sFilterInstanceOf = null, $bCanInstantiatePlugins = true) + { + $aPlugins = array(); + if (array_key_exists($sInterface, $this->m_aExtensionClasses)) + { + $aAllPlugins = array_values($this->m_aExtensionClasses[$sInterface]); + + if (is_null($sFilterInstanceOf)) + { + return $aAllPlugins; + }; + + $aPlugins = array(); + foreach ($aAllPlugins as $instance) + { + if ($instance instanceof $sFilterInstanceOf) + { + $aPlugins[] = $instance; + } + } + } + else if ($bCanInstantiatePlugins && array_key_exists($sInterface, $this->m_aExtensionClassNames)) + { + $this->InstantiatePlugins($sInterface); + + return $this->EnumPlugins($sInterface, $sFilterInstanceOf, false); + } + return $aPlugins; + } + + public function InstantiatePlugins($sInterface) + { + $this->m_aExtensionClasses[$sInterface] = $this->m_pluginInstantiationManager->InstantiatePlugins($this->m_aExtensionClassNames, $sInterface); + } + + /** + * @param string $sInterface + * @param string $sClassName + * @param bool $bCanInstantiatePlugins internal use, let this value to true + * + * @return mixed the instance of the specified plug-ins for the given interface + */ + public function GetPlugins($sInterface, $sClassName, $bCanInstantiatePlugins = true) + { + $oInstance = null; + if (array_key_exists($sInterface, $this->m_aExtensionClasses)) + { + if (array_key_exists($sClassName, $this->m_aExtensionClasses[$sInterface])) + { + return $this->m_aExtensionClasses[$sInterface][$sClassName]; + } + } + else if ($bCanInstantiatePlugins && array_key_exists($sInterface, $this->m_aExtensionClassNames)) + { + $this->InstantiatePlugins($sInterface); + return $this->GetPlugins($sInterface, $sClassName, false); + } + + return $oInstance; + } +} //PluginManager class + +class PluginInstanciationManager +{ + public function InstantiatePlugins($m_aExtensionClassNames, $sInterface) + { + $newPerInstanceClasses = array(); + if (array_key_exists($sInterface, $m_aExtensionClassNames)) + { + foreach ($m_aExtensionClassNames[$sInterface] as $sClassName) + { + if (class_exists($sClassName)) + { + $class = new ReflectionClass($sClassName); + + if ($class->isInstantiable()) + { + $newPerInstanceClasses[$sClassName] = new $sClassName(); + } + } + } + } + return $newPerInstanceClasses; + } +} // Standard attribute lists MetaModel::RegisterZList("noneditable", array("description" => "non editable fields", "type" => "attributes")); diff --git a/test/core/MetaModelTest.php b/test/core/MetaModelTest.php index 3ec4e47f9..369bcdf3e 100644 --- a/test/core/MetaModelTest.php +++ b/test/core/MetaModelTest.php @@ -179,4 +179,134 @@ class MetaModelTest extends ItopDataTestCase } } + + /** + * @dataProvider enumPluginsProvider + * + * @param $expectedResults + * @param $m_aExtensionClassNames + * @param $m_aExtensionClasses + * @param $interface + * @param null $sFilterInstanceOf + */ + public function testEnumPlugins($expectedInstanciationCalls, $expectedResults, $m_aExtensionClassNames, $m_aExtensionClasses, $interface, $sFilterInstanceOf=null) + { + $pluginInstanciationManager = new \PluginInstanciationManager(); + $res = $pluginInstanciationManager->InstantiatePlugins($m_aExtensionClassNames, $interface); + + $mPluginInstanciationManager = $this->createMock(\PluginInstanciationManager::class); + $mPluginInstanciationManager->expects($this->exactly($expectedInstanciationCalls)) + ->method('InstantiatePlugins') + ->willReturn($res); + $m_PluginManager = new \PluginManager($m_aExtensionClassNames, $m_aExtensionClasses, $mPluginInstanciationManager); + + //warning: called twice on purpose + $m_PluginManager->EnumPlugins($interface, $sFilterInstanceOf); + $pluginInstances = $m_PluginManager->EnumPlugins($interface, $sFilterInstanceOf); + + $this->assertCount(sizeof($expectedResults), $pluginInstances); + foreach($pluginInstances as $pluginInstance) + { + if ($sFilterInstanceOf!==null) + { + $this->assertTrue($pluginInstance instanceof $sFilterInstanceOf); + } + } + $index=0; + foreach($expectedResults as $expectedInterface) + { + $this->assertTrue(is_a($pluginInstances[$index], $expectedInterface)); + $index++; + } + } + + public function enumPluginsProvider(){ + $aInterfaces = [ + "empty conf" => [ 0, [], [], [], 'Wizzard'], + "simple instance retrieval" => [ 1, [Gryffindor::class], [ 'Wizzard' => [ Gryffindor::class]], [], 'Wizzard'], + "check instanceof parameter" => [ 1, [Gryffindor::class, Slytherin::class], [ 'Wizzard' => [ Gryffindor::class, Slytherin::class]], [], 'Wizzard'], + "try to retrieve a non instanciable object" => [ 1, [Gryffindor::class], [ 'Wizzard' => [ Gryffindor::class, Muggle::class]], [], 'Wizzard', Gryffindor::class ], + ]; + return $aInterfaces; + } + + /** + * @dataProvider getPluginsProvider + * + * @param $expectedInstanciationCalls + * @param $expectedResults + * @param $m_aExtensionClassNames + * @param $m_aExtensionClasses + * @param $interface + * @param $className + */ + public function testGetPlugins($expectedInstanciationCalls, $expectedResults, $m_aExtensionClassNames, $m_aExtensionClasses, $interface, $className) + { + $pluginInstanciationManager = new \PluginInstanciationManager(); + $res = $pluginInstanciationManager->InstantiatePlugins($m_aExtensionClassNames, $interface); + + $mPluginInstanciationManager = $this->createMock(\PluginInstanciationManager::class); + $mPluginInstanciationManager->expects($this->exactly($expectedInstanciationCalls)) + ->method('InstantiatePlugins') + ->willReturn($res); + $m_PluginManager = new \PluginManager($m_aExtensionClassNames, $m_aExtensionClasses, $mPluginInstanciationManager); + + //warning: called twice on purpose + $m_PluginManager->GetPlugins($interface, $className); + $pluginInstance = $m_PluginManager->GetPlugins($interface, $className); + + if (sizeof($expectedResults)==0) + { + $this->assertNull($pluginInstance); + return; + } + + $this->assertTrue($pluginInstance instanceof $className); + $this->assertTrue(is_a($pluginInstance, $expectedResults[0])); + } + + public function getPluginsProvider(){ + $aInterfaces = [ + "empty conf" => [ 0, [], [], [], 'Wizzard', Gryffindor::class], + "simple instance retrieval" => [ 1, [Gryffindor::class], [ 'Wizzard' => [ Gryffindor::class]], [], 'Wizzard', Gryffindor::class], + "check instanceof parameter" => [ 1, [Gryffindor::class], [ 'Wizzard' => [ Gryffindor::class, Slytherin::class]], [], 'Wizzard', Gryffindor::class], + "try to retrieve a non instanciable object" => [ 1, [Gryffindor::class], [ 'Wizzard' => [ Gryffindor::class, Muggle::class]], [], 'Wizzard', Gryffindor::class ], + ]; + return $aInterfaces; + } + +} + +abstract class Wizzard +{ + + /** + * Wizzard constructor. + */ + public function __construct() + { + } +} + +class Gryffindor extends Wizzard +{ + +} +class Hufflepuff extends Wizzard +{ + +} +class Ravenclaw extends Wizzard +{ + +} + +class Slytherin extends Wizzard +{ + +} + +class Muggle +{ + }