Application->isDebugMode() ) { $this->_debugFactory = defined('DBG_FACTORY') && DBG_FACTORY; $this->_profileMemory = defined('DBG_PROFILE_MEMORY') && DBG_PROFILE_MEMORY; } } /** * Configures module-based autoloader. * * @return void */ public function configureAutoloader() { if ( !$this->Application->ModuleInfo ) { $error_msg = 'Autoloader configuration can be only performed after module information is available'; throw new LogicException($error_msg); } $this->namespaceMap = array(); foreach ( $this->Application->ModuleInfo as $module_name => $module_info ) { if ( $module_name == 'In-Portal' ) { continue; } $this->namespaceMap[$module_info['ClassNamespace']] = rtrim($module_info['Path'], '/'); } if ( defined('IS_INSTALL') && IS_INSTALL ) { // During installation process all modules, because unit configs from all modules are scanned too. $class_map_builders = ClassMapBuilder::createBuilders(); } else { $class_map_builders = ClassMapBuilder::createBuilders($this->Application->ModuleInfo); } foreach ( $class_map_builders as $class_map_builder ) { list($class_map, $class_info) = $class_map_builder->get(); $class_names = array_keys($class_map); $this->classMap = array_merge($this->classMap, $class_map); $this->classInfo = array_merge($this->classInfo, $class_info); $this->realClasses = array_merge($this->realClasses, array_combine($class_names, $class_names)); foreach ( $class_info as $class => $class_data ) { if ( isset($class_data['extends']) ) { foreach ( $class_data['extends'] as $extends_class ) { if ( !isset($this->classTree[$extends_class]) ) { $this->classTree[$extends_class] = array(); } $this->classTree[$extends_class][] = $class; } } } } } /** * Sets data from cache to object. * * @param array $data Data. * * @return void */ public function setFromCache(&$data) { $this->classMap = $data['Factory.Files']; $this->classInfo = $data['Factory.ClassInfo']; $this->classTree = $data['Factory.ClassTree']; $this->namespaceMap = $data['Factory.Namespaces']; $this->realClasses = $data['Factory.realClasses']; } /** * Performs automatic loading of classes registered with the factory. * * @param string $class Class. * * @return boolean|null */ public function autoload($class) { $file = $this->findFile($class); if ( $file ) { kUtil::includeOnce(FULL_PATH . $file); return true; } return null; } /** * Finds the path to the file where the class is defined. * * @param string $class The name of the class. * * @return string|boolean The path if found, false otherwise. */ protected function findFile($class) { if ( $class[0] == '\\' ) { $class = substr($class, 1); } if ( isset($this->classMap[$class]) ) { return $this->classMap[$class]; } $pos = strrpos($class, '\\'); if ( $pos !== false ) { // Namespaced class name. $class_path = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, 0, $pos)) . DIRECTORY_SEPARATOR; $class_name = substr($class, $pos + 1); } else { // PEAR-like class name. $class_path = null; $class_name = $class; } $class_path .= str_replace('_', DIRECTORY_SEPARATOR, $class_name) . '.php'; foreach ( $this->namespaceMap as $namespace_prefix => $namespace_path ) { if ( strpos($class, $namespace_prefix) === 0 ) { $test_class_path = str_replace( str_replace('\\', DIRECTORY_SEPARATOR, $namespace_prefix), $namespace_path, $class_path ); if ( file_exists(FULL_PATH . DIRECTORY_SEPARATOR . $test_class_path) ) { return DIRECTORY_SEPARATOR . $test_class_path; } } } return $this->classMap[$class] = false; } /** * Gets object data for caching. * * @return array */ public function getToCache() { ksort($this->classMap); ksort($this->classInfo); ksort($this->classTree); ksort($this->namespaceMap); ksort($this->realClasses); return array( 'Factory.Files' => $this->classMap, 'Factory.ClassInfo' => $this->classInfo, 'Factory.ClassTree' => $this->classTree, 'Factory.Namespaces' => $this->namespaceMap, 'Factory.realClasses' => $this->realClasses, ); } /** * Splits any mixing of prefix and special into correct ones. * * @param string $prefix_special Prefix-special. * * @return array */ public function processPrefix($prefix_special) { // Example: "l.pick", "l", "m.test_TagProcessor". $tmp = explode('_', $prefix_special, 2); $tmp[0] = explode('.', $tmp[0]); $prefix = $tmp[0][0]; $prefix_special = $prefix; if ( isset($tmp[1]) ) { $prefix .= '_' . $tmp[1]; } $special = isset($tmp[0][1]) ? $tmp[0][1] : ''; $prefix_special .= '.' . $special; return array('prefix' => $prefix, 'special' => $special, 'prefix_special' => $prefix_special); } /** * Returns object using params specified, creates it if is required. * * @param string $name Object name in factory. * @param string $pseudo_class Pseudo class. * @param array $event_params Event params. * @param array $arguments Constructor arguments. * * @return kBase */ public function getObject($name, $pseudo_class = '', array $event_params = array(), array $arguments = array()) { $name = rtrim($name, '.'); if ( isset($this->storage[$name]) ) { return $this->storage[$name]; } $ret = $this->processPrefix($name); if ( !$pseudo_class ) { $pseudo_class = $ret['prefix']; } if ( $this->_debugFactory ) { $this->Application->Debugger->appendHTML( 'Creating object: Pseudo class: ' . $pseudo_class . ' Prefix: ' . $name ); $this->Application->Debugger->appendTrace(); } $this->storage[$name] = $this->makeClass($pseudo_class, $arguments); $this->storage[$name]->Init($ret['prefix'], $ret['special']); $this->Application->EventManager->runBuildEvent($ret['prefix_special'], $pseudo_class, $event_params); return $this->storage[$name]; } /** * Removes object from storage, so next time it could be created from scratch. * * @param string $name Object's name in the Storage. * * @return void */ public function DestroyObject($name) { unset($this->storage[$name]); } /** * Checks if object with prefix passes was already created in factory. * * @param string $name Object pseudo_class, prefix. * * @return boolean */ public function hasObject($name) { return isset($this->storage[$name]); } /** * Get's real class name for pseudo class, includes class file and creates class instance. * * Pattern: Factory Method * * @param string $pseudo_class Pseudo class. * @param array $arguments Constructor arguments. * * @return kBase * @throws kFactoryException When class not found. */ public function makeClass($pseudo_class, array $arguments = array()) { if ( !isset($this->realClasses[$pseudo_class]) ) { $error_msg = 'RealClass not defined for "' . $pseudo_class . '" pseudo_class.'; $error_msg .= ' Please use "in-portal classmap:rebuild" command to discover new classes.'; if ( $this->Application->isInstalled() ) { throw new kFactoryException($error_msg); } else { if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->appendTrace(); } trigger_error($error_msg, E_USER_WARNING); } return false; } $real_class = $this->realClasses[$pseudo_class]; $mem_before = memory_get_usage(); $time_before = microtime(true); $arguments = (array)$arguments; if ( !$arguments ) { $object = new $real_class(); } else { $reflection = new ReflectionClass($real_class); $object = $reflection->newInstanceArgs($arguments); } if ( $this->_profileMemory ) { $mem_after = memory_get_usage(); $time_after = microtime(true); $mem_used = $mem_after - $mem_before; $mem_used_formatted = round($mem_used / 1024, 3); $time_used = round($time_after - $time_before, 5); $this->Application->Debugger->appendHTML( 'Factory created ' . $real_class . ' - used ' . $mem_used_formatted . 'Kb time: ' . $time_used ); $this->Application->Debugger->profilerAddTotal('objects', null, $mem_used); } return $object; } /** * Returns sub-classes of given ancestor class. * * @param string $ancestor_class Ancestor class. * @param boolean $concrete_only Return only non-abstract classes. * * @return array * @throws kFactoryException When ancestor class not found. */ public function getSubClasses($ancestor_class, $concrete_only = true) { if ( !isset($this->classMap[$ancestor_class]) ) { throw new kFactoryException( 'Class "' . $ancestor_class . '" is not registered in the Factory' ); } if ( !isset($this->classTree[$ancestor_class]) ) { return array(); } $all_sub_classes = array(); foreach ( $this->classTree[$ancestor_class] as $sub_class ) { $real_sub_class = $this->realClasses[$sub_class]; $all_sub_classes[$real_sub_class] = $sub_class; $all_sub_classes = array_merge($all_sub_classes, $this->getSubClasses($sub_class, false)); } if ( $concrete_only ) { $concrete_sub_classes = array(); foreach ( $all_sub_classes as $real_sub_class => $sub_class ) { if ( $this->classInfo[$sub_class]['type'] == self::TYPE_CLASS && !$this->classHasModifier($sub_class, self::MODIFIER_ABSTRACT) ) { $concrete_sub_classes[$real_sub_class] = $sub_class; } } return $concrete_sub_classes; } return $all_sub_classes; } /** * Determines of class has modifier. * * @param string $class Class. * @param integer $modifier Modifier. * * @return boolean */ protected function classHasModifier($class, $modifier) { return ($this->classInfo[$class]['modifiers'] & $modifier) == $modifier; } /** * Registers new class in the factory * * @param string $real_class Real name of class as in class declaration. * @param string $file Filename in what $real_class is declared. * @param string $pseudo_class Name under this class object will be accessed using getObject method. * * @return void */ public function registerClass($real_class, $file, $pseudo_class = null) { if ( !isset($pseudo_class) ) { $pseudo_class = $real_class; } if ( !isset($this->classMap[$real_class]) ) { $this->classMap[$real_class] = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $file, 1); } $this->realClasses[$pseudo_class] = $real_class; } /** * Unregisters existing class from factory * * @param string $real_class Real name of class as in class declaration. * @param string $pseudo_class Name under this class object is accessed using getObject method. * * @return void */ public function unregisterClass($real_class, $pseudo_class = null) { unset($this->classMap[$real_class]); } } class kFactoryException extends Exception { }