Page MenuHomeIn-Portal Phabricator

in-portal
No OneTemporary

File Metadata

Created
Sat, Feb 22, 12:04 AM

in-portal

Index: branches/5.3.x/core/kernel/utility/factory.php
===================================================================
--- branches/5.3.x/core/kernel/utility/factory.php (revision 16325)
+++ branches/5.3.x/core/kernel/utility/factory.php (revision 16326)
@@ -1,510 +1,513 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
use InPortal\Core\kernel\utility\ClassDiscovery\ClassMapBuilder;
defined('FULL_PATH') or die('restricted access!');
class kFactory extends kBase implements kiCacheable
{
const TYPE_CLASS = 1;
const TYPE_INTERFACE = 2;
const TYPE_TRAIT = 3;
const MODIFIER_ABSTRACT = 1;
const MODIFIER_FINAL = 2;
/**
* Mapping between class name and file name where class definition can be found.
*
* File path absolute!
*
* @var array
*/
protected $classMap = array();
/**
* Class information.
*
* @var array
*/
protected $classInfo = array();
/**
* Information about class usage among other classes.
*
* @var array
*/
protected $classTree = array();
/**
* The PSR-4 compliant namespace-to-folder mapping.
*
* @var array
*/
protected $namespaceMap = array();
/**
* Map class names to their pseudo class names, e.g. key=pseudo_class, value=real_class_name.
*
* @var array
*/
protected $realClasses = array();
/**
* Where all created objects are stored.
*
* @var kBase[]
*/
protected $storage = array();
/**
* Debug factory internal processing.
*
* @var boolean
*/
private $_debugFactory = false;
/**
* Profile memory usage during class file inclusion.
*
* @var boolean
*/
private $_profileMemory = false;
/**
* Creates factory instance.
*/
public function __construct()
{
parent::__construct();
spl_autoload_register(array($this, 'autoload'), true, true);
if ( defined('DEBUG_MODE') && DEBUG_MODE && $this->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'], '/');
}
+ // Don't let class map builder rely on auto-loader (especially during upgrade).
+ require_once KERNEL_PATH . '/utility/ClassDiscovery/ClassMapBuilder.php';
+
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(
'<strong>Creating object:</strong> 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 "<strong>' . $pseudo_class . '</strong>" pseudo_class.';
$error_msg .= ' Please use "<strong>in-portal classmap:rebuild</strong>" 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 <b>' . $real_class . '</b> - 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 "<strong>' . $ancestor_class . '</strong>" 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
{
}

Event Timeline