bkdotcom/PHPDebugConsole

View on GitHub
src/Debug/Route/Firephp.php

Summary

Maintainability
A
25 mins
Test Coverage
A
100%
<?php

/**
 * This file is part of PHPDebugConsole
 *
 * @package   PHPDebugConsole
 * @author    Brad Kent <bkfake-github@yahoo.com>
 * @license   http://opensource.org/licenses/MIT MIT
 * @copyright 2014-2024 Brad Kent
 * @since     1.3b
 */

namespace bdk\Debug\Route;

use bdk\Debug;
use bdk\Debug\LogEntry;
use bdk\PubSub\Event;

/**
 * Output log via FirePHP
 */
class Firephp extends AbstractRoute
{
    const FIREPHP_PROTO_VER = '0.3';

    /** @var bool */
    protected $appendsHeaders = true;

    /** @var array<string,mixed> */
    protected $cfg = array(
        'channels' => ['*'],
        'channelsExclude' => [
            'events',
            'files',
        ],
        'messageLimit' => 99999,
    );

    /** @var array<string,string> */
    protected $firephpMethods = array(
        'error' => 'ERROR',
        'group' => 'GROUP_START',
        'groupCollapsed' => 'GROUP_START',
        'groupEnd' => 'GROUP_END',
        'info' => 'INFO',
        'log' => 'LOG',
        'table' => 'TABLE',
        'warn' => 'WARN',
    );

    /** @var int */
    protected $messageIndex = 0;

    /** @var Event|null */
    protected $outputEvent;

    /**
     * Constructor
     *
     * @param Debug $debug debug instance
     */
    public function __construct(Debug $debug)
    {
        parent::__construct($debug);
        $this->dumper = $debug->getDump('base');
    }

    /**
     * Output the log via FirePHP headers
     *
     * @param Event|null $event Debug::EVENT_OUTPUT Event object
     *
     * @return void
     */
    public function processLogEntries($event = null)
    {
        $this->debug->utility->assertType($event, 'bdk\PubSub\Event');

        $this->dumper->crateRaw = false;
        $this->outputEvent = $event;
        $this->data = $this->debug->data->get();
        $event['headers'][] = ['X-Wf-Protocol-1', 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'];
        $event['headers'][] = ['X-Wf-1-Plugin-1', 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/' . self::FIREPHP_PROTO_VER];
        $event['headers'][] = ['X-Wf-1-Structure-1', 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'];
        $this->processLogEntryViaEvent(new LogEntry(
            $this->debug,
            'groupCollapsed',
            ['PHP: ' . $this->getRequestMethodUri()]
        ));
        $this->processAlerts();
        $this->processSummary();
        $this->processLog();
        $this->processLogEntryViaEvent(new LogEntry(
            $this->debug,
            'groupEnd'
        ));
        $event['headers'][] = ['X-Wf-1-Index', $this->messageIndex];
        $this->data = array();
        $this->dumper->crateRaw = true;
    }

    /**
     * LogEntry to firePhp header(s)
     *
     * @param LogEntry $logEntry LogEntry instance
     *
     * @return void
     */
    public function processLogEntry(LogEntry $logEntry)
    {
        $method = $logEntry['method'];
        $args = $logEntry['args'];
        $this->setFirephpMeta($logEntry);
        $value = null;
        if ($method === 'alert') {
            $value = $this->methodAlert($logEntry);
        } elseif (\in_array($method, ['group', 'groupCollapsed'], true)) {
            $logEntry['firephpMeta']['Label'] = $args[0];
            $logEntry['firephpMeta']['Collapsed'] = $method === 'groupCollapsed'
                // yes, strings
                ? 'true'
                : 'false';
        } elseif (\in_array($method, ['profileEnd', 'table', 'trace'], true)) {
            $value = $this->methodTabular($logEntry);
        } elseif (\count($args)) {
            $this->dumper->processLogEntry($logEntry);
            $value = $this->getValue($logEntry);
        }
        if ($this->messageIndex < $this->cfg['messageLimit']) {
            $this->setFirephpHeader($logEntry['firephpMeta'], $value);
        } elseif ($this->messageIndex === $this->cfg['messageLimit']) {
            $this->setFirephpHeader(
                array('Type' => $this->firephpMethods['warn']),
                'FirePhp\'s limit of ' . \number_format($this->cfg['messageLimit']) . ' messages reached!'
            );
        }
    }

    /**
     * Get firephp value
     *
     * @param LogEntry $logEntry LogEntry instance
     *
     * @return mixed firephp value
     */
    private function getValue(LogEntry $logEntry)
    {
        $args = $logEntry['args'];
        if (\count($args) === 1) {
            return $args[0];
            // no label;
        }
        $logEntry['firephpMeta']['Label'] = \array_shift($args);
        return \count($args) > 1
            ? $args // firephp only supports label/value...  we'll pass multiple values as an array
            : $args[0];
    }

    /**
     * handle alert
     *
     * @param LogEntry $logEntry LogEntry instance
     *
     * @return string
     */
    private function methodAlert(LogEntry $logEntry)
    {
        $level = $logEntry->getMeta('level');
        $levelToMethod = array(
            'error' => 'error',
            'info' => 'info',
            'success' => 'info',
            'warn' => 'warn',
        );
        $method = $levelToMethod[$level];
        $logEntry['firephpMeta']['Type'] = $this->firephpMethods[$method];
        if ($logEntry->containsSubstitutions()) {
            $this->dumper->processLogEntry($logEntry);
        }
        return $this->getValue($logEntry);
    }

    /**
     * handle tabular type log entry
     *
     * @param LogEntry $logEntry logEntry instance
     *
     * @return mixed
     */
    private function methodTabular(LogEntry $logEntry)
    {
        $logEntry->setMeta('undefinedAs', 'null');
        $this->dumper->processLogEntry($logEntry);
        $logEntry['firephpMeta']['Type'] = $this->firephpMethods['table'];
        $caption = $logEntry->getMeta('caption');
        if ($caption) {
            $logEntry['firephpMeta']['Label'] = $caption;
        }
        $firephpTable = true;
        $args = $logEntry['args'];
        $value = $args[0];
        if ($firephpTable) {
            $value = array();
            $value[] = \array_merge([''], \array_keys(\current($args[0])));
            foreach ($args[0] as $k => $row) {
                $value[] = \array_merge([$k], \array_values($row));
            }
        }
        return $this->dumper->valDumper->dump($value);
    }

    /**
     * set FirePHP log entry header(s)
     *
     * @param array $meta  meta information
     * @param mixed $value value
     *
     * @return void
     */
    private function setFirephpHeader($meta, $value = null)
    {
        \ksort($meta);  // for consistency / testing
        $msg = \json_encode([
            $meta,
            $value,
        ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        $structureIndex = 1;    // refers to X-Wf-1-Structure-1
        $parts = \explode("\n", \rtrim(\chunk_split($msg, 5000, "\n")));
        $numParts = \count($parts);
        for ($i = 0; $i < $numParts; $i++) {
            $part = $parts[$i];
            $this->messageIndex++;
            $headerName = 'X-Wf-1-' . $structureIndex . '-1-' . $this->messageIndex;
            $headerValue = ($i === 0 ? \strlen($msg) : '')
                . '|' . $part . '|'
                . ($i < $numParts - 1 ? '\\' : '');
            $this->outputEvent['headers'][] = [$headerName, $headerValue];
        }
    }

    /**
     * Initialize firephp's meta array
     *
     * @param LogEntry $logEntry LogEntry instance
     *
     * @return void
     */
    private function setFirephpMeta(LogEntry $logEntry)
    {
        $method = $logEntry['method'];
        $meta = $logEntry['meta'];
        $firephpMeta = array(
            'Type' => isset($this->firephpMethods[$method])
                ? $this->firephpMethods[$method]
                : $this->firephpMethods['log'],
            // Label
            // File
            // Line
            // Collapsed  (for group)
        );
        if (isset($meta['file'])) {
            $firephpMeta['File'] = $meta['file'];
            $firephpMeta['Line'] = $meta['line'];
        }
        $logEntry['firephpMeta'] = $firephpMeta;
    }
}