*/ class ChromePhp { /** * @var string */ const VERSION = '4.1.0'; /** * @var string */ const HEADER_NAME = 'X-ChromeLogger-Data'; /** * @var string */ const BACKTRACE_LEVEL = 'backtrace_level'; /** * @var string */ const LOG = 'log'; /** * @var string */ const WARN = 'warn'; /** * @var string */ const ERROR = 'error'; /** * @var string */ const GROUP = 'group'; /** * @var string */ const INFO = 'info'; /** * @var string */ const GROUP_END = 'groupEnd'; /** * @var string */ const GROUP_COLLAPSED = 'groupCollapsed'; /** * @var string */ const TABLE = 'table'; /** * @var string */ protected $_php_version; /** * @var int */ protected $_timestamp; /** * @var array */ protected $_json = array( 'version' => self::VERSION, 'columns' => array('log', 'backtrace', 'type'), 'rows' => array() ); /** * @var array */ protected $_backtraces = array(); /** * @var bool */ protected $_error_triggered = false; /** * @var array */ protected $_settings = array( self::BACKTRACE_LEVEL => 1 ); /** * @var ChromePhp */ protected static $_instance; /** * Prevent recursion when working with objects referring to each other * * @var array */ protected $_processed = array(); /** * constructor */ private function __construct() { $this->_php_version = phpversion(); $this->_timestamp = $this->_php_version >= 5.1 ? $_SERVER['REQUEST_TIME'] : time(); $this->_json['request_uri'] = $_SERVER['REQUEST_URI']; } /** * gets instance of this class * * @return ChromePhp */ public static function getInstance() { if (self::$_instance === null) { self::$_instance = new self(); } return self::$_instance; } /** * logs a variable to the console * * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] * @return void */ public static function log() { $args = func_get_args(); return self::_log('', $args); } /** * logs a warning to the console * * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] * @return void */ public static function warn() { $args = func_get_args(); return self::_log(self::WARN, $args); } /** * logs an error to the console * * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] * @return void */ public static function error() { $args = func_get_args(); return self::_log(self::ERROR, $args); } /** * sends a group log * * @param string value */ public static function group() { $args = func_get_args(); return self::_log(self::GROUP, $args); } /** * sends an info log * * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] * @return void */ public static function info() { $args = func_get_args(); return self::_log(self::INFO, $args); } /** * sends a collapsed group log * * @param string value */ public static function groupCollapsed() { $args = func_get_args(); return self::_log(self::GROUP_COLLAPSED, $args); } /** * ends a group log * * @param string value */ public static function groupEnd() { $args = func_get_args(); return self::_log(self::GROUP_END, $args); } /** * sends a table log * * @param string value */ public static function table() { $args = func_get_args(); return self::_log(self::TABLE, $args); } /** * internal logging call * * @param string $type * @return void */ protected static function _log($type, array $args) { // nothing passed in, don't do anything if (count($args) == 0 && $type != self::GROUP_END) { return; } $logger = self::getInstance(); $logger->_processed = array(); $logs = array(); foreach ($args as $arg) { $logs[] = $logger->_convert($arg); } $backtrace = debug_backtrace(false); $level = $logger->getSetting(self::BACKTRACE_LEVEL); $backtrace_message = 'unknown'; if (isset($backtrace[$level]['file']) && isset($backtrace[$level]['line'])) { $backtrace_message = $backtrace[$level]['file'] . ' : ' . $backtrace[$level]['line']; } $logger->_addRow($logs, $backtrace_message, $type); } /** * converts an object to a better format for logging * * @param Object * @return array */ protected function _convert($object) { // if this isn't an object then just return it if (!is_object($object)) { return $object; } //Mark this object as processed so we don't convert it twice and it //Also avoid recursion when objects refer to each other $this->_processed[] = $object; $object_as_array = array(); // first add the class name $object_as_array['___class_name'] = get_class($object); // loop through object vars $object_vars = get_object_vars($object); foreach ($object_vars as $key => $value) { // same instance as parent object if ($value === $object || in_array($value, $this->_processed, true)) { $value = 'recursion - parent object [' . get_class($value) . ']'; } $object_as_array[$key] = $this->_convert($value); } $reflection = new ReflectionClass($object); // loop through the properties and add those foreach ($reflection->getProperties() as $property) { // if one of these properties was already added above then ignore it if (array_key_exists($property->getName(), $object_vars)) { continue; } $type = $this->_getPropertyKey($property); if ($this->_php_version >= 5.3) { $property->setAccessible(true); } try { $value = $property->getValue($object); } catch (ReflectionException $e) { $value = 'only PHP 5.3 can access private/protected properties'; } // same instance as parent object if ($value === $object || in_array($value, $this->_processed, true)) { $value = 'recursion - parent object [' . get_class($value) . ']'; } $object_as_array[$type] = $this->_convert($value); } return $object_as_array; } /** * takes a reflection property and returns a nicely formatted key of the property name * * @param ReflectionProperty * @return string */ protected function _getPropertyKey(ReflectionProperty $property) { $static = $property->isStatic() ? ' static' : ''; if ($property->isPublic()) { return 'public' . $static . ' ' . $property->getName(); } if ($property->isProtected()) { return 'protected' . $static . ' ' . $property->getName(); } if ($property->isPrivate()) { return 'private' . $static . ' ' . $property->getName(); } } /** * adds a value to the data array * * @var mixed * @return void */ protected function _addRow(array $logs, $backtrace, $type) { // if this is logged on the same line for example in a loop, set it to null to save space if (in_array($backtrace, $this->_backtraces)) { $backtrace = null; } // for group, groupEnd, and groupCollapsed // take out the backtrace since it is not useful if ($type == self::GROUP || $type == self::GROUP_END || $type == self::GROUP_COLLAPSED) { $backtrace = null; } if ($backtrace !== null) { $this->_backtraces[] = $backtrace; } $row = array($logs, $backtrace, $type); $this->_json['rows'][] = $row; $this->_writeHeader($this->_json); } protected function _writeHeader($data) { header(self::HEADER_NAME . ': ' . $this->_encode($data)); } /** * encodes the data to be sent along with the request * * @param array $data * @return string */ protected function _encode($data) { return base64_encode(utf8_encode(json_encode($data))); } /** * adds a setting * * @param string key * @param mixed value * @return void */ public function addSetting($key, $value) { $this->_settings[$key] = $value; } /** * add ability to set multiple settings in one call * * @param array $settings * @return void */ public function addSettings(array $settings) { foreach ($settings as $key => $value) { $this->addSetting($key, $value); } } /** * gets a setting * * @param string key * @return mixed */ public function getSetting($key) { if (!isset($this->_settings[$key])) { return null; } return $this->_settings[$key]; } }