*/ /** * Debug: a diagnostic output control class. * * This class attempts to solve the problem of selectively controlling what * diagnostic information is displayed at runtime. The class is a singleton that * maintains an array of debug settings. The format of a setting is * "type@namespace::class::method". Any value can be associated with a setting. * All parts of a setting are optional; an empty setting string refers to the * global default setting. * * The setting type is managed by the application. An example of using a type * might be to use "backtrace" to control the generation of call stacks, while * leaving default diagnostic output on. This would be achieved with: * * * $dbg = AP5L_Debug::getInstance(); * $dbg -> setState('', true); * $dbg -> setState ('backtrace@', false); * * * The remainder of the setting string is evaluated in a hierarchical manner * (namespaces are part of the syntax in anticipation of the namespace feature * in PHP 5.3). * * The basic diagnostic output routine, write() takes the output string as the * first parameter, and an optional setting (or setting handle) as the second * parameter. If the setting is not provided, or if only a setting type is * provided, the debug class will assume that the current namespace, class, and * method have been passed. For example in this code: * * * class MyClass_Driver { * * function foo() { * AP5L::getDebug() -> write('some message', 'details@'); * } * } * * * The setting 'details@' will be interpreted as 'details@::MyClass_Driver:: * foo'. The hierarchical evaluation will cause the manager to look for settings * in the following order: 'details@::MyClass_Driver::foo', 'details@:: * MyClass_Driver', 'details@::MyClass_', 'details@::MyClass', 'details@', and * finally '' (the global default). If the first value found in this sequence * evaluates true, then output will be generated. * * This allows the application to exert fine control over the generation of * diagnostics at any level. If a explicit setting is not set, then the next * step in the hierarchy is evaluated until a value is found. * * Note that there is no requirement to use actual namespace, class, or method * names. An application can use any values in the settings, but when this is * done, the setting string needs to be passed in as a handle explicitly. */ abstract class AP5L_Debug implements AP5L_DebugProvider { /** * Mapping from setting key to handle. * * @var array. */ protected $_handleMap = array(); /** * Cached references to settings. * * @var array */ protected $_handles = array(); /** * Handle refresh required flag. * * @var boolean */ protected $_handleRefresh = false; /** * Reference to a global instance of the default debugger. */ static protected $_instance; /** * New line string. Variable so output can be routed to other streams. * * @var string */ public $newLine = AP5L::NL; /** * Output control settings. Array indexed by hierarchical key * (information_type@namespace::class::method) (class keys with a trailing * underscore refer to the hierarchy, those without refer to a specific * class). Empty string represents the default. * * @var string */ protected $_settings = array('@::::' => false); /** * Verify that handles reference the correct setting. * * The handle refresh flag is set whenever */ protected function _checkHandles() { if (! $this -> _handleRefresh) { return; } foreach ($this -> _handleMap as $key => $handle) { if (isset($this -> _settings[$key])) { $this -> _handles[$handle] = &$this -> _getComponent($key); } else { unset($this -> _handles[$handle]); } } $this -> _handleRefresh = false; } /** * Decompose a debug key into subcomponents. */ static protected function _decompose($key) { $elements = array(); /* * If there is a specified infotype, add "infotype@" to the * decomposition. Otherwise just add "@". */ if (($posn = strpos($key, '@')) !== false) { $elements[] = substr($key, 0, $posn + 1); $key = substr($key, $posn + 1); } else { $elements[] = '@'; } $terms = explode('::', $key); /* * Add package::class::method elements */ $elements = array_merge($elements, self::_decomposePackage(array_shift($terms))); $elements[] = '::'; if (count($terms)) { $elements = array_merge($elements, self::_decomposePackage(array_shift($terms))); } $elements[] = '::'; if (count($terms)) { $elements[] = array_shift($terms); } return $elements; } /** * Break a class name down into subcomponents. * * Given a full class name like My_Class_Name, this function will return an * array of ('My_', 'Class_', 'Name'). * * @param string A class name, possibly with components delimited by * underscores. * @return array An array of each component, preserving underscores. */ static protected function _decomposePackage($name) { $elements = explode('_', $name); $last = array_pop($elements); $result = array(); foreach ($elements as $element) { $result[] = $element . '_'; } if ($last != '') { $result[] = $last; } return $result; } /** * Get the settings value for a setting. * * Walks up the hierarchy of settings until a match is found. * * @param string A scope identifier of the form [type@][namespace]::class[:: * method]. Examples ::MyClass, ::MyClass::method, network@::MyClass, * dumpconfig@space::Package_. * @return mixed The matching settings value for the key. */ protected function &_getComponent($key) { $elements = self::_decompose($key); $suffix = ''; while (count($elements)) { if (end($elements) == '') { // skip the element } elseif (end($elements) == '::') { $suffix = '::' . $suffix; } else { $mkey = implode('', $elements) . $suffix; if (isset($this -> _settings[$mkey])) { return $this -> _settings[$mkey]; } } array_pop($elements); } return $this -> _settings['@::::']; } /** * Get information about where an application invoked the debug output * class. * * @return array A debug_backtrace() array entry detailing where AP5L_Debug * was called. */ protected function _getCaller() { $trace = debug_backtrace(); for ($stack = 0; $stack < count($trace); ++$stack) { if ($trace[$stack]['class'] != __CLASS__) { /* * Return the line that called us. */ $caller = $trace[$stack]; $caller['line'] = $stack ? $trace[$stack - 1]['line'] : ''; return $caller; } } } /** * Get handle of caller. * * @return string A string of the form class::method, where the class and * the method are those of the module that invoked the AP5L_Debug class. */ protected function _getDefaultHandle() { $caller = $this -> _getCaller(); return '::' . $caller['class']. '::' . $caller['function']; } /** * Normalize a key by parsing it into elements and reassembling it. * * This function ensures that all elements are represented. * * @param string A scope identifier of the form [type@][namespace]::class[:: * method]. Examples ::MyClass, ::MyClass::method, network@::MyClass, * dumpconfig@space::Package_. * @return string A normalized key. */ function _keyNormalize($key) { $elements = self::_decompose($key); return implode('', $elements); } abstract function _write($data); function backtrace($handle = null, $options = array()) { /* $trace = debug_backtrace(); foreach ($trace as &$call) { if (isset($call['args'])) { foreach ($call['args'] as &$arg) { if (is_object($arg)) { $arg = get_class($arg) . ' Object'; } elseif (is_array($arg)) { $arg = 'Array[' . count($arg) . ']'; } } } } print_r($trace[0]); */ } function flush() { $this -> _write($this -> _buffer); $this -> _buffer = ''; } /** * Free a handle. * * Freeing the handle saves an unused handle from being re-evaluated each * time the settings change. * * @param int The handle to free. */ function freeHandle($handle) { $map = array_search($this -> handleMap, $handle); if ($map !== false) { unset($this -> handleMap[$map]); } unset($this -> handles[$handle]); } /** * Get or assign a handle. * * @param string A scope identifier of the form [type@][namespace]::class[:: * method]. Examples ::MyClass, ::MyClass::method, network@::MyClass, * dumpconfig@space::Package_. * @return int A handle identifier corresponding to the key. */ function getHandle($key = null) { if ($key === null) { $key = $this -> _getDefaultHandle(); } if (isset($this -> handleMap[$key])) { return $this -> handleMap[$key]; } $handle = count($this -> _handles); $this -> handleMap[$key] = $handle; $this -> _handles[] = &$this -> _getComponent($key); return $handle; } /** * Get the class singleton. * * @return AP5L_Debug */ static function getInstance() { return self::$_instance; } /** * Get the current state setting for a key. * * @param string The key. Optional. If not provided, the caller's handle is * used. * @return mixed The current setting for the key. */ function getState($key = null) { if (is_null($key)) { $key = $this -> _getDefaultHandle(); } $state = $this -> _getComponent($key); return $state; } /** * See if debug output is enabled. * * This method is useful when there's significant computation required to * create a diagnostic string, by allowing a branch around it if it's not * required. * * @param int|string An optional debugger handle or scope identifier. See * {@see write()}. */ function isEnabled($handle = null) { if (is_null($handle)) { $handle = $this -> _getDefaultHandle(); } if (is_int($handle)) { $this -> _checkHandles(); $enable = isset($this -> _handles[$handle]) ? $this -> _handles[$handle] : false; } else { /* * If only a type is passed, add the package/class/method */ if (substr($handle, -1) == '@') { $handle .= $this -> _getDefaultHandle(); } $enable = $this -> _getComponent($handle); } return $enable; } /** * Get the class singleton. */ static function setInstance(&$instance) { self::$_instance = $instance; } /** * Configure the debugger. * * @param string A scope identifier of the form [type@][namespace]::class[:: * method]. Examples ::MyClass, ::MyClass::method, network@::MyClass, * dumpconfig@space::Package_. * @param mixed The corresponding value. Typically boolean, but it can be * anything. */ function setState($key, $value) { $key = $this -> _keyNormalize($key); if (! isset($this -> _settings[$key])) { /* * This is a new setting. New settings may change the setting * hierarchy and cause a key to map to a new component, so we need * to do a handle refresh. The refresh will trigger the next time a * handle is used, which saves us from evaluating all handles every * time a new setting is made. */ $this -> _handleRefresh = true; } $this -> _settings[$key] = $value; } /** * Clear a setting. * * @param string A scope identifier of the form [type@][namespace]::class[:: * method]. Examples ::MyClass, ::MyClass::method, network@::MyClass, * dumpconfig@space::Package_. */ function unsetState($key) { $key = $this -> _keyNormalize($key); if ($key == '@::::') { // Can't clear the default, set it false instead $this -> _settings[$key] = false; $this -> _handleRefresh = true; } elseif (isset($this -> _settings[$key])) { unset($this -> _settings[$key]); $this -> _handleRefresh = true; } } /** * Write diagnostic information. * * @param string The information to be written * @param int|string An optional debugger handle or scope identifier of the * form [type@] [namespace]:: class [:: method]. Examples ::MyClass, :: * MyClass:: method, network@:: MyClass, dumpconfig@space::Package_. If * missing, the caller's class and method are used. * @param array Options. */ function write($data, $handle = null, $options = array()) { if (is_null($handle)) { $handle = $this -> _getDefaultHandle(); } /* * Trap some cases where an echo x, y, z... has been poorly converted to * a function call. */ if ( func_num_args() > 3 || (func_num_args() >= 3 && ! is_array($options)) ) { $caller = $this -> _getCaller(); $this -> _write( 'Warning: Debug parameter error in ' . $caller['class']. $caller['type'] . $caller['function'] . ' at line ' . $caller['line'] . PHP_EOL ); } if (is_int($handle)) { $this -> _checkHandles(); $enable = isset($this -> _handles[$handle]) ? $this -> _handles[$handle] : false; } else { /* * If only a type is passed, add the package/class/method */ if (substr($handle, -1) == '@') { $handle .= $this -> _getDefaultHandle(); } $enable = $this -> _getComponent($handle); } if ($enable) { $this -> _write($data); } } /** * Write diagnostic information, followed by a newline. * * @param string The information to be written * @param int|string An optional debugger handle or scope identifier of the * form [type@] [namespace]:: class [:: method]. Examples ::MyClass, :: * MyClass:: method, network@:: MyClass, dumpconfig@space::Package_. If * missing, the caller's class and method are used. * @param array Options. */ function writeln($data, $handle = null, $options = array()) { $this -> write($data . $this -> newLine, $handle, $options); } } ?>