public function testConstructorNormalInstanceCreation() { $paramOne = 'one'; $paramTwo = 'two'; /* @var $service DummyService */ $service = ServiceManager::getServiceObject(self::SERVICE_CLASS, self::CONTEXT, self::LANGUAGE, [$paramOne, $paramTwo], APFService::SERVICE_TYPE_NORMAL); $this->assertEquals($paramOne, $service->getParamOne()); $this->assertEquals($paramTwo, $service->getParamTwo()); }
/** * Creates the database connection. * * @param string $context The current context. * @param string $language The current language. * * @return AbstractDatabaseHandler The database connection to read the configuration from and store it. * * @author Christian Achatz * @version * Version 0.1, 29.10.2010<br /> */ private function &getConnection($context, $language) { // create service "manually", since we have no convenience method /* @var $connMgr ConnectionManager */ $connMgr =& ServiceManager::getServiceObject(ConnectionManager::class, $context, $language); return $connMgr->getConnection($this->connectionName); }
/** * Returns a service object according to the current application context. * * @param string $class Fully qualified class name of the service object. * @param array $arguments A list of constructor arguments to create the service instance with. * @param string $type The initializing type (see service manager for details). * @param string $instanceId The id of the instance to return. * * @return APFObject The desired service object. * * @author Christian Schäfer * @version * Version 0.1, 07.03.2007<br /> * Version 0.2, 08.03.2007 (Context is now taken from the current object)<br /> * Version 0.3, 10.03.2007 (Method now is considered protected)<br /> * Version 0.4, 22.04.2007 (Added language initialization of the service manager)<br /> * Version 0.5, 24.02.2008 (Added the service type param)<br /> * Version 0.6 21.11.2012 Jens Prangenberg <*****@*****.**> (Added the instance id param)<br /> */ protected function &getServiceObject($class, array $arguments = [], $type = APFService::SERVICE_TYPE_SINGLETON, $instanceId = null) { return ServiceManager::getServiceObject($class, $this->getContext(), $this->getLanguage(), $arguments, $type, $instanceId); }
/** * Returns the initialized service object. * * @param string $configNamespace The namespace of the service object. * @param string $sectionName The name of the desired service object. * @param string $context The context of the current application. * @param string $language The language of the current application. * * @return APFDIService The pre-configured service object. * @throws ConfigurationException In case the requested service is not existent. * @throws InvalidArgumentException In case of injection issues. * * @author Christian Achatz * @version * Version 0.1, 19.04.2009<br /> * Version 0.2, 24.08.2011 (Added "setupmethod" functionality)<br/> * Version 0.3, 07.07.2012 Jan Wiese (Corrected service retrieval to respect context and language each time.<br /> * Introduced CACHED service type to retrieve a NORMAL instance from the cache and thus gain performance for none-singletons.)<br /> * Version 0.4, 10.07.2012 Jan Wiese (Introduced configuration cache to gain performance)<br /> * Version 0.5, 10.07.2012 Jan Wiese (Improvements in code quality and removed bugs from v0.3/v0.4)<br /> */ public static function &getServiceObject($configNamespace, $sectionName, $context, $language) { // build cache key. because configuration-file path includes context, include context (and language) in cache key // In 2.0 language has been removed from the instance id since within multi-language applications // you want to re-use the instance throughout different languages! $cacheKey = $configNamespace . '|' . $sectionName . '|' . $context; // Check, whether service object was created before. If yes, deliver it from cache for all services types except NORMAL. // Do not cache ServiceType 'NORMAL' because we want to have different instances! if (isset(self::$SERVICE_OBJECT_CACHE[$cacheKey]) && self::$SERVICE_TYPE_CACHE[$cacheKey] != APFService::SERVICE_TYPE_NORMAL) { return self::$SERVICE_OBJECT_CACHE[$cacheKey]; } // Invoke benchmarker. Suppress warning for already started timers with circular calls! // Suppressing is here done by a dirty '@', because we will run into an error anyway. $t = Singleton::getInstance(BenchmarkTimer::class); /* @var $t BenchmarkTimer */ $benchId = 'DIServiceManager::getServiceObject(' . $configNamespace . ',' . $sectionName . ',' . $context . ',' . $language . ')'; @$t->start($benchId); // Get config to determine, which object to create. $config = self::getServiceConfiguration($configNamespace, $context, $language, $cacheKey); if (!$config->hasSection($sectionName)) { throw new ConfigurationException('[DIServiceManager::getServiceObject()] Service object configuration with ' . 'name "' . $sectionName . '" cannot be found within namespace "' . $configNamespace . '"! Please double-check your setup.', E_USER_ERROR); } $section = $config->getSection($sectionName); // check, whether the section contains the basic directives and read/write service type cache if (isset(self::$SERVICE_TYPE_CACHE[$cacheKey])) { $serviceType = self::$SERVICE_TYPE_CACHE[$cacheKey]; } else { $serviceType = $section->getValue('servicetype'); self::$SERVICE_TYPE_CACHE[$cacheKey] = $serviceType; } $class = $section->getValue('class'); // The behaviour of service types CACHED and NORMAL is equal in the following, thus remapping it. if ($serviceType == APFService::SERVICE_TYPE_CACHED) { $serviceType = APFService::SERVICE_TYPE_NORMAL; } // Check if configuration section was complete. If not throw an exception to fail fast. if ($serviceType === null || $class === null) { throw new InvalidArgumentException('[DIServiceManager::getServiceObject()] Initialization of the service object "' . $sectionName . '" from namespace "' . $configNamespace . '" cannot be accomplished, due to missing or incorrect configuration! Please revise the configuration file and consult the manual!', E_USER_ERROR); } // Create the service object with use of the "normal" service manager. Perhaps, this // may run into problems, because we have to ensure, that the singleton objects are // only treated once by the injection mechanism! // But: if we constitute, that the injected service objects are often also singletons // and the DIServiceManager caches the created service objects within a singleton cache, // this is no problem. Hence, the injected instance is then only one time constructed. // ID#249: as of 3.0 you are also able to inject dependent configuration and services via constructor. // This replaces the former getAndInitServiceObject() method of the ServiceManager with a more elegant // way. Configuration allows any number of constructor arguments as string, array, or other services. $arguments = []; if ($section->hasSection('construct')) { $constructorArguments = $section->getSection('construct'); foreach ($constructorArguments->getSectionNames() as $argumentKey) { $directive = $constructorArguments->getSection($argumentKey); // be aware of the params needed for injection $value = $directive->getValue('value'); $namespace = $directive->getValue('namespace'); $name = $directive->getValue('name'); if ($value === null && ($namespace === null || $name === null)) { throw new InvalidArgumentException('[DIServiceManager::getServiceObject()] Construction of the' . ' service object "' . $sectionName . '" cannot be accomplished, due to' . ' incorrect constructor injection configuration! Please revise the "' . $argumentKey . '" sub section and consult the manual!', E_USER_ERROR); } // Simple value argument (string) if ($directive->hasValue('value')) { $arguments[] = $value; } else { // complex injection with another service $arguments[] =& self::getServiceObject($namespace, $name, $context, $language); } } } /* @var $serviceObject APFDIService */ $serviceObject =& ServiceManager::getServiceObject($class, $context, $language, $arguments, $serviceType, $cacheKey); // do param injection (static configuration) if ($section->hasSection('conf')) { $cfTasks = $section->getSection('conf'); foreach ($cfTasks->getSectionNames() as $initKey) { $directive = $cfTasks->getSection($initKey); // be aware of the params needed for injection $method = $directive->getValue('method'); if ($method === null) { throw new InvalidArgumentException('[DIServiceManager::getServiceObject()] Initialization of the' . ' service object "' . $sectionName . '" cannot be accomplished, due to' . ' incorrect configuration! Please revise the "' . $initKey . '" sub section and' . ' consult the manual!', E_USER_ERROR); } // check, if method exists to avoid fatal errors if (!method_exists($serviceObject, $method)) { throw new InvalidArgumentException('[DIServiceManager::getServiceObject()] Injection of' . ' configuration value "' . $directive->getValue('value') . '" cannot be accomplished' . ' to service object "' . $class . '"! Method ' . $method . '() is not implemented!', E_USER_ERROR); } if (($value = $directive->getValue('value')) !== null) { $serviceObject->{$method}($value); } else { if (!$directive->hasSection('value')) { throw new InvalidArgumentException('[DIServiceManager::getServiceObject()] Initialization of the' . ' service object "' . $sectionName . '" cannot be accomplished, due to' . ' missing value(s) for method ' . $method . '()! Please revise the "' . $initKey . '" sub section and' . ' consult the manual!', E_USER_ERROR); } $cfSubSection = $directive->getSection('value'); $values = []; foreach ($cfSubSection->getValueNames() as $valueName) { $values[] = $cfSubSection->getValue($valueName); } call_user_func_array([$serviceObject, $method], $values); } } } // do service object injection if ($section->hasSection('init')) { $miTasks = $section->getSection('init'); foreach ($miTasks->getSectionNames() as $initKey) { $directive = $miTasks->getSection($initKey); // be aware of the params needed for injection $method = $directive->getValue('method'); $namespace = $directive->getValue('namespace'); $name = $directive->getValue('name'); if ($method === null || $namespace === null || $name === null) { throw new InvalidArgumentException('[DIServiceManager::getServiceObject()] Initialization of the service object "' . $sectionName . '" cannot be accomplished, due to incorrect configuration! Please revise the "' . $initKey . '" sub section and consult the manual!', E_USER_ERROR); } // check for circular injection $injectionKey = $namespace . '::' . $class . '[' . $serviceType . ']' . ' injected with ' . $method . '(' . $namespace . '::' . $name . ')'; // TODO why do we accept loops for normal services? if (isset(self::$INJECTION_CALL_CACHE[$injectionKey]) && $serviceType != APFService::SERVICE_TYPE_NORMAL) { // append error to log to provide debugging information $log = Singleton::getInstance(Logger::class); /* @var $log Logger */ $instructions = ''; foreach (self::$INJECTION_CALL_CACHE as $injectionInstruction => $DUMMY) { $instructions .= PHP_EOL . $injectionInstruction; } $log->addEntry(new SimpleLogEntry(Registry::retrieve('APF\\core', 'InternalLogTarget'), '[DIServiceManager::getServiceObject()] Injection stack trace: ' . $instructions, LogEntry::SEVERITY_TRACE)); // print note with shorted information throw new InvalidArgumentException('[DIServiceManager::getServiceObject()] Detected circular injection! ' . 'Class "' . $class . '" from namespace "' . $namespace . '" with service type "' . $serviceType . '" was already configured with service object "' . $name . '" from namespace "' . $namespace . '"! Full stack trace can be taken from the logfile!', E_USER_ERROR); } else { // add the current run to the recursion detection array self::$INJECTION_CALL_CACHE[$injectionKey] = true; // get the dependent service object $miObject =& self::getServiceObject($namespace, $name, $context, $language); // inject the current service object with the created one if (method_exists($serviceObject, $method)) { $serviceObject->{$method}($miObject); } else { throw new InvalidArgumentException('[DIServiceManager::getServiceObject()] Injection of service object "' . $name . '" from namespace "' . $namespace . '" cannot be accomplished to service object "' . $class . '" from namespace "' . $namespace . '"! Method ' . $method . '() is not implemented!', E_USER_ERROR); } } } } // Often, there you have a services that depends on several other services (e.g. database connection). Thus, // you are forced to initialize your service using these components. To ease such cases, you may specify a // generic method within the "setupmethod" attribute. The DIServiceManager calls this method at the end of // the initialization process and you can initialize your service without being dependent on the user's // order of configuration parameters. // in order to not execute the setup method on every request, check the initialization status of the service // object before. this mechanism can be used for re-initialization on __wakeup() in case the property is // set to false (=reinitialization after session wake-up). $setupMethod = $section->getValue('setupmethod'); if (!empty($setupMethod)) { if (!$serviceObject->isInitialized()) { if (method_exists($serviceObject, $setupMethod)) { $serviceObject->{$setupMethod}(); } else { throw new InvalidArgumentException('[DIServiceManager::getServiceObject()] Custom service object setup ' . 'method "' . $setupMethod . '()" is not implemented for given type "' . get_class($serviceObject) . '"! Please double-check your configuration ' . 'for service "' . $sectionName . '" from namespace "' . $configNamespace . '."', E_USER_ERROR); } } } $t->stop($benchId); // add service object to cache and return it self::$SERVICE_OBJECT_CACHE[$cacheKey] = $serviceObject; return self::$SERVICE_OBJECT_CACHE[$cacheKey]; }