root/components/backtrace/BackTraceExplain.php

User picture

Author: blaine

Revision: 262 («Previous)


File Size: 13.7 KB

(April 30, 2009 21:14 UTC) About 3 years ago

phpdoc change. separating the package notation of the class notation to make easy to the doxygen generate the phpdoc html description

 
Show/hide line numbers
<?php
/**
 * BackTraceExplain Element
 * @package backtrace
 */

/**
 * Manipula um objeto BackTrace e monta um HTML com seus detalhes.
 *
 * Manipulates an BackTrace object and create a HTML with its details.
 *
 * Constrói dados de um rastreamento de uma forma mais agradável que, por
 * exemplo, a chamada da função {@link var_dump()} tendo como parâmetro
 * {@link debug_backtrace()}. A classe foi inspirada em um nota lançada no link
 * {@link http://www.php.net/manual/en/function.debug-backtrace.php#47644}.
 * Caso os manipuladores de erros ou exceções do seu sistema utilize essa classe
 * é altamente recomendado que use também
 * {@link http://php.net/manual/en/ref.outcontrol.php controle de saída} para
 * que o rastreamento seja melhor visualizado e não seja misturado com outros
 * fontes HTML. A função {@link highlight_string()} é utilizada, qualquer
 * modificação no formato retornado implica na necessidade de revisão dessa
 * classe.
 *
 * @author Renan de Lima Barbosa <renandelima@gmail.com>
 */
class BackTraceExplain
{
    /**
     * Quantidade de linhas de código visíveis.
     *
     * Amount of visible lines code.
     *
     * Quantidade de linhas a serem exibidas no detalhamento antes de depois de
     * cada mudança de escopo. O valor zero é altamente recomendado para
     * sistemas em produção. O valor -1 ou qualquer outro negativo faz com que
     * todas as linhas do arquivo onde ocorreu a mudança de escopo sejam
     * exibidas.
     *
     * @var integer
     */
    const CONTEXT_LINES = 0;

    /**
     * Visibilidade dos parâmetros das chamadas da pilha.
     *
     * Trace argument visibility.
     *
     * Indica se os parâmetros de cada chamada na pilha deve ser exibido. Caso
     * diretiva possua valor falso apenas o tipo do parâmetro é exibido.
     *
     * @var integer
     */
    const SHOW_ARGUMENT_VALUE = false;

    /**
     * Cache de código fontes.
     *
     * Source code cache.
     *
     * @see BackTraceExplain::fetchSource()
     * @var Array
     */
    protected static $aFileContent = array();

    /**
     * Gerador de identificadores para detalhamento.
     *
     * Identifiers generator for detailing.
     *
     * @var integer
     */
    protected static $iExplainId = 0;

    /**
     * Nível inicial do rastreamento.
     *
     * Trace start level.
     *
     * @var integer
     */
    protected $iStartLevel = 0;

    /**
     * Rastramento a ser detalhado.
     *
     * Trace to detailing.
     *
     * @var BackTrace
     */
    protected $oBackTrace = null;

    /**
     * Nome da função JavaScript para ocultar e exibir elementos HTML.
     *
     * JavaScript function name for to hide and show HTML elements.
     *
     * @var string
     */
    protected $sScriptFunctionName = "";

    /**
     * @ignore
     */
    protected function __construct(){}

    /**
     * Captura resumo dos argumentos do nível atual do rastreamento.
     *
     * Captures arguments summary of current trace level.
     *
     * Caso a constante {@link BackTraceExplain::SHOW_ARGUMENT_VALUE} possua
     * valor verdadeiro e o tipo do parâmetro seja string ou integer o valor do
     * parâmetro é exibido, caso contrário apenas um resumo é apresentado.
     *
     * @return string[]
     * @see BackTraceExplain::SHOW_ARGUMENT_VALUE
     */
    protected function fetchArguments()
    {
        $aArgument = $this->oBackTrace->getArguments();
        if ( count( $aArgument ) == 0 )
        {
            return array();
        }
        $aReturn = array();
        foreach ( $aArgument as $mArgument )
        {
            switch( gettype( $mArgument ) )
            {
                case "array":
                    $sSummary = "array( " . count( $mArgument ) . " )";
                    break;
                case "boolean":
                    $sSummary = $mArgument == true ? "true" : "false";
                    break;
                case "double":
                case "integer":
                    $sSummary = self::SHOW_ARGUMENT_VALUE == true ? (string) $mArgument : "integer";
                    break;
                case "NULL":
                    $sSummary = "null";
                    break;
                case "object":
                    $sSummary = "object( " . htmlentities( get_class( $mArgument ) ) . " )";
                    break;
                case "resource":
                    $sSummary = "resource( " . htmlentities( strstr( $mArgument, "#" ) . " " . get_resource_type( $mArgument ) ) . " )";
                    break;
                case "string":
                    if ( self::SHOW_ARGUMENT_VALUE == true )
                    {
                        if ( strlen( $mArgument ) > 15 )
                        {
                            $mArgument = substr( $mArgument, 0, 12 ) . "...";
                        }
                        $sSummary = htmlentities( "\"" . $mArgument . "\"" );
                    }
                    else
                    {
                        $sSummary = "string( " . strlen( $mArgument ) . " )";
                    }
                    break;
                default:
                    $sSummary = "unknown type " . htmlentities( gettype( $mArgument ) );
                    break;
            }
            array_push( $aReturn, $sSummary );
        }
        return $aReturn;
    }

    /**
     * Constrói HTML da chamada do nível atual do rastreamento.
     *
     * Creates HTML of current trace level call.
     *
     * @return string
     */
    protected function fetchCall()
    {
        if ( $this->oBackTrace->getScope() == BackTrace::SCOPE_FILE )
        {
            return "";
        }
        $iLevel = self::$iExplainId + $this->oBackTrace->getLevel();
        $sAnchor = "";
        $sArgument = "";
        foreach ( $this->fetchArguments() as $sSummary )
        {
            $sArgument .= ", " . $sSummary;
        }
        if ( $sArgument != "" )
        {
            $sArgument = substr( $sArgument, 1 ) . " ";
        }
        $sClass = $this->oBackTrace->getClass();
        $sType = $this->oBackTrace->getType();
        $sFunction = $this->oBackTrace->getFunction();
        return htmlentities( $sClass . $sType . $sFunction ) . "(" . $sArgument . ")";
    }

    /**
     * Constrói detalhes do nível atual do rastreamento.
     *
     * Generates details of current trace level.
     *
     * @return string
     */
    protected function fetchLevel()
    {
        $sLocation = htmlentities( $this->oBackTrace->getFile() ) . " on line " . $this->oBackTrace->getLine();
        $sSource = $this->fetchSource();
        $sSourceId = "BackTraceExplainSource" . ( self::$iExplainId + $this->oBackTrace->getLevel() );
        if ( $sSource != "" )
        {
            $sLocation =
                "<a href=\"\" onclick=\"this.blur(); " . $this->sScriptFunctionName . "( '" . $sSourceId . "' ); return false;\">" .
                    $sLocation .
                "</a>";
        }
        return
            "<div class=\"traceCall\">" .
                $this->fetchCall() .
            "</div>" .
            "<div class=\"traceLocation\">" .
                $sLocation .
            "</div>" .
            "<div class=\"traceSource\" id=\"" . $sSourceId . "\">" .
                $sSource .
            "</div>";
    }

    /**
     * Constrói HTML com código do arquivo do nível atual do rastreamento.
     *
     * Creates HTML with file source code of current trace level.
     *
     * Caso a constante {@link BackTraceExplain::CONTEXT_LINES} possua valor
     * zero ou arquivo do nível atual do rastreamento não exista ou não seja
     * legível um texto vazio é retornado. A função {@link highlight_string()} é
     * utilizada, qualquer modificação no formato retornado implica na
     * necessidade de revisão desse método.
     *
     * @return string
     * @see BackTraceExplain::CONTEXT_LINES
     */
    protected function fetchSource()
    {
        $sFile = $this->oBackTrace->getFile();
        if ( self::CONTEXT_LINES == 0 || file_exists( $sFile ) == false || is_readable( $sFile ) == false )
        {
            return "";
        }
        # verifica se existe cache
        if ( array_key_exists( $sFile, self::$aFileContent ) == false )
        {
            self::$aFileContent[$sFile] = substr( highlight_string( file_get_contents( $sFile ), true ), 6, -7 );
        }
        $sSource = "";
        $aLine = explode( "<br />", self::$aFileContent[$sFile] );
        $iLine = $this->oBackTrace->getLine();
        # define linha inicial e final a ser exibida
        if ( self::CONTEXT_LINES < 0 )
        {
            $iLineStart = 1;
            $iLineEnd = count( $aLine );
        }
        else
        {
            $fEdge = ( self::CONTEXT_LINES - 1 ) / 3;
            $iLineStart = $iLine - ceil( $fEdge * 2 );
            $iLineEnd = $iLine + floor( $fEdge );
            if ( $iLineStart < 1 )
            {
                $iLineEnd += abs( $iLineStart ) + 1;
                $iLineStart = 1;
            }
            $iSourceLength = count( $aLine );
            if ( $iLineEnd > $iSourceLength )
            {
                $iLineStart -= $iLineEnd - $iSourceLength;
                if ( $iLineStart < 1 )
                {
                    $iLineStart = 1;
                }
                $iLineEnd = $iSourceLength;
            }
        }
        # monta texto de deteção da primeira linha, que precisa de tratamento especial
        $sFirstLineDetect = "<span style=\"color: #000000\">\n<span style=\"color: #0000BB\">";
        $iFirstLineDetectLength = strlen( $sFirstLineDetect );
        # percorre linhas de código
        foreach ( $aLine as $iCurrentLine => $sLineContent )
        {
            # trata linha corrente para contagem iniciar no número 1
            $iCurrentLine++;
            # verifica se linha deve estar visível
            if ( $iCurrentLine < $iLineStart || $iCurrentLine > $iLineEnd )
            {
                # remove todo conteúdo que existir entre duas marcações HTML
                $aMatch = array();
                preg_match_all( "(<[^>]+>)", $sLineContent, $aMatch );
                if ( count( $aMatch ) > 0 && is_array( $aMatch[0] ) === true )
                {
                    $sLineContent = str_replace( array( "<br/>", "&nbsp;", "\n" ), "", implode( "", $aMatch[0] ) );
                }
            }
            else
            {
                # verifica se é a primeira linha
                if ( substr( $sLineContent, 0, $iFirstLineDetectLength ) === $sFirstLineDetect )
                {
                    # retira o prefixo para evitar erros de tratamento do código
                    # o prefixo é adiciona de volta a linha ao final do tratamento da linha atual
                    $sLineContent = substr( $sLineContent, $iFirstLineDetectLength );
                    $sLinePrefix = $sFirstLineDetect;
                }
                else
                {
                    $sLinePrefix = "";
                }
                # prepara linha para que ela receba a numeração com uma classe de estilo
                # adiciona uma quebra de linha visível ao final da linha
                $sLineContent = "</span>" . $sLineContent . "<br/>";
                # verifica se linha deve ser destacada
                if ( $iCurrentLine !== $iLine )
                {
                    $sClass = "traceNumberLineOff";
                }
                else
                {
                    # destaca número da linha
                    # adiciona classe de estilo para destaque de todo conteúdo da linha
                    $sClass = "traceNumberLineOn";
                    $sLineContent = preg_replace( "/>([^<]*)</", "><span class=\"traceLineActive\">$1</span><", $sLineContent );
                }
                $sLineContent =
                    $sLinePrefix .
                    "<span class=\"" . $sClass . "\">" .
                        sprintf( "%0" . strlen( $iLineEnd ) . "d", $iCurrentLine ) .
                        $sLineContent;
            }
            $sSource .= $sLineContent;
        }
        return $sSource;
    }

    /**
     * Constrói HTML com detalhes de todos os níveis de um rastreamento.
     *
     * Creates HTML with details of all trace levels.
     *
     * Os níveis são detalhados a partir do atual. O nível do rastreamento
     * permanece o mesmo após a execução desse método.
     *
     * @param BackTrace $oBackTrace rastreamento a ser detalhado
     * @return string
     */
    public static function perform( BackTrace $oBackTrace = null )
    {
        if ( $oBackTrace == null )
        {
            $oBackTrace = new BackTrace();
            $oBackTrace->levelDown();
        }
        $iOriginalLevel = $oBackTrace->getLevel();
        $oExplain = new self();
        $oExplain->oBackTrace = $oBackTrace;
        $oExplain->iStartLevel = $oBackTrace->getLevel();
        $oExplain->sScriptFunctionName = "BackTraceExplainFunction" . self::$iExplainId;
        $sTrace = "";
        do
        {
            $sTrace .=
                "<li class=\"traceLevel\">" .
                    $oExplain->fetchLevel() .
                "</li>";
        }
        while ( $oBackTrace->levelDown() == true );
        self::$iExplainId += $oBackTrace->getLevel() - $iOriginalLevel + 1;
        $oBackTrace->setLevel( $iOriginalLevel );
        $sDir = dirname( __FILE__ ) . "/resource/";
        return
            "<style type=\"text/css\">" .
                file_get_contents( $sDir . "style.css" ) .
            "</style>" .
            "<script type=\"text/javascript\">" .
                str_replace( "_TRACE_FUNCTION_", $oExplain->sScriptFunctionName, file_get_contents( $sDir . "script.js" ) ) .
            "</script>" .
            "<ol class=\"trace\">" .
                $sTrace .
            "</ol>";
    }
}