PHP DO BÁSICO AO AVANÇADO – AULA 19 – Variáveis e Constantes Pré Definidas e Dados Fornecidos Pelo Usuário

Tempo de Leitura: 31 Minutos

A linguagem PHP possui uma maneira peculiar ao disponibilizar para o usuário algumas variáveis de memória com escopo global, que fazem algumas referências a dados fornecidos pelo usuário, atalhos para o programador ao acessar alguns recursos do sistema e até mesmo para a persistência de dados, e justamente sobre essas particularidades que vamos falar nesta aula, para familiarizar o programador com esse tipo de recurso, e como interagir e controlar, bem como para permitir implementar de maneira segura como utilizar esse conteúdo.Na nossa AULA 3, falamos das variáveis de memória e falamos brevemente sobre algumas variáveis que o próprio PHP declara e torna-as disponíveis em todos os scripts, que são chamadas de superglobais, posteriormente na AULA 9, falamos o que representam cada uma dessas variáveis em níveis de tipos de informação disponíveis nela. O que vamos estudar neste momento, é como o PHP estrutura essas informações, e porque elas são considerados atalhos para o programador.

NO PHP como já estudamos, a forma e a ordem de precedência desses valores pode ser sobreposto ou alterado através do PHP.INI e essa definição pode alterar a forma como validamos e recebemos alguns desses valores em nosso script, portanto, cabe ao programador conhecer o ambiente e definir ou garantir que as definições existentes estejam condizentes com o que se está esperando, sobre risco de falhas de execução ou mesmo de segurança. A diretiva variables_order configura a ordem de análise das variáveis EGPCS (Environment, Get, Post, Cookie e Server). Por exemplo, se variables_order estiver configurada como “SP” então o PHP irá criar as variáveis superglobals $_SERVER e $_POST, mas não irá criar $_ENV, $_GET e $_COOKIE. Claro se definida como vazia, nenhuma superglobal será criada.

request_order é a diretiva que descreve a ordem na qual o PHP registra as variáves GET, POST e Cookie no array $_REQUEST. O registro é feito da esquerda para direita, valores mais recentes sobrescrevem os valores mais antigos, ou seja se um mesmo nome é passado por GET e por POST a variável $_REQUEST os valores de POST serão utilizados. Caso ela não esteja declarada, os valores existentes na vaviables_order é usado.

auto_globals_jitQuando ativada, essa diretriz determina se as variáveis SERVER, REQUEST e ENV são criadas somente se utilizadas (“Just In Time”) ao invés de quando o script inicia. Se estas variáveis não são usadas dentro de um script, habilitar esta diretiva resultará em um ganho de desempenho.

Porque as Super Globais são atalhos para o programador?

Como o PHP foi criado justamente com o propósito de tratar requisições via protocolo HTTP alguns usos comuns do protocolo e da comunicação com o servidor web (APACHE, NGINX, IIS ou outros) essas diversas variáveis poupam muito trabalho, pois já realizam algumas tarefas, veja abaixo uma função que é executada automaticamente para criar a variável $_PUT que é muito similar a $_POST porém utilizando o método HTTP PUT

<?php
global $_PUT;
    $HEADERS = apache_request_headers();
    $INPUT = file_get_contents('php://input', false, null, 0, $_SERVER['CONTENT_LENGTH']);
if (isset($HEADERS['Content-Type'])) {
    $CTYPE = $HEADERS['Content-Type'];
} else {
    $CTYPE = '';
}
if (isset($HEADERS['Content-Length'])) {
    $SIZE = $HEADERS['Content-Length'];
} else {
    $SIZE = 0;
}
    $SIZEI = strlen($INPUT);
if ($SIZE != $SIZEI) {
    __out('READ INPUT ERROR', 406);
}
if ($CTYPE == '' && $SIZE == 0) {
    //NONE CONTENT
    $RETURN = array();
}
if ($CTYPE == 'application/x-www-form-urlencoded') {
    parse_str($INPUT, $RETURN);
} else {
    //RAW OU BINARY
    $RETURN = GetRawPut($INPUT);
}
print_r($RETURN);
print_r($_FILES);

function GetRawPut($raw_data)
{
    $boundary = substr($raw_data, 0, strpos($raw_data, "\r\n"));
   // Fetch each part
    $parts = array_slice(explode($boundary, $raw_data), 1);
    $data = array();

    foreach ($parts as $part) {
        // If this is the last part, break
        if ($part == "--\r\n") {
            break;
        }

        // Separate content from headers
        $part = ltrim($part, "\r\n");
        list($raw_headers, $body) = explode("\r\n\r\n", $part, 2);

        // Parse the headers list
        $raw_headers = explode("\r\n", $raw_headers);
        $headers = array();
        foreach ($raw_headers as $header) {
            list($name, $value) = explode(':', $header);
            $headers[strtolower($name)] = ltrim($value, ' ');
        }

        // Parse the Content-Disposition to get the field name, etc.
        if (isset($headers['content-disposition'])) {
            $filename = null;
            $tmp_name = null;
            preg_match(
                '/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/',
                $headers['content-disposition'],
                $matches
            );
            list(, $type, $name) = $matches;
            //Parse File
            if (isset($matches[4])) {
                //if labeled the same as previous, skip
                if (isset($_FILES[ $matches[ 2 ] ])) {
                    continue;
                }

                //get filename
                $filename = $matches[4];

                //get tmp name
                $filename_parts = pathinfo($filename);
                $tmp_name = tempnam(ini_get('upload_tmp_dir'), $filename_parts['filename']);

                //populate $_FILES with information, size may be off in multibyte situation
                $_FILES[ $matches[ 2 ] ] = array(
                    'error' => 0,
                    'name' => $filename,
                    'tmp_name' => $tmp_name,
                    'size' => strlen($body),
                    'type' => $value
                );

                //place in temporary directory
                file_put_contents($tmp_name, $body);
            }
            //Parse Field
            else {
                $data[$name] = substr($body, 0, strlen($body) - 2);
            }
        }
    }
    return $data;
}

Note, que é um código ainda com erros e que não vai tratar de maneira correta TODAS as combinações possíveis, o que pode torna-lo até inseguro, porém é um exemplo de como o PHP facilitou a utilização de valores passados através do protocolo HTTP, abaixo o print da requisição feita no aplicativo POSTMAN simulando o envio de 2 formatos de formulários que são geralmente usados nos browsers.

Note que sem o uso das SUPER GLOBAIS a cada requisição feita o programador da aplicação deveria executar essa função, fazendo os devidos tratamentos de erros, balanceamentos de colchetes e valores, imaginou o que aconteceria se nomes de campos tiverem valores como campo[][] ou campo[[] ou campo[{}], e se existirem aspas e outros caracteres especiais nos nomes ou valores? Já pensou todas as regras que a equipe que desenvolve o core do php já teve que tratar e incluiu e deixou razoavelmente pronto para você? Por este recurso que o PHP é considerada uma linguagem de alto nível (mais próxima a linguagem humana) e que facilita o trabalho e o desenvolvimento de aplicações web.

Por exemplo a variável super global $http_response_header contém todos os valores dos cabeçalhos usados na requisição HTTP e pode ser usada para validar a presença ou a codificação ou tamanho da requisição, isso poderia ser feito analisando as respostas porém, novamente mais trabalho e mais validações, por este grande número de atalhos, e pelo fato do analisador do PHP ter que avaliar o script pois ela é uma linguagem interpretada, que muitas vezes é colocado que o PHP é uma linguagem lenta, se comparado com por exemplo python. Porém, quando analisamos os benefícios e malefícios no uso e o emprego correto de cada uma delas, podemos tirar proveito de ambas, cada qual com seu perfil de uso. Este é um dos motivos que, mesmo uma linguagem considerada mais rápida, como Python pode ser degradada em performance, este tipo de análise se mal implementado pode trazer além dos problemas de segurança, grandes falhas de performance.

Alguns Cuidados no uso das SUPER GLOBAIS

Veja, essas variáveis pré-definidas geralmente são atalhos para entradas provenientes do usuário, e que com certa facilidade podem conter dados errados, tipos de dados inválidos, ou mesmo serem propositalmente manipuladas por um atacante para inserir dados no nosso script que podem afetar a forma como ele vai se comportar, ou mesmo servirem como porta de entrada para hackers terem acesso a recursos do servidor, dados privados e sigilosos ou simplesmente causarem um erro impossibilitando que seu sistema possa realizar as operações como você programador acredita que iriam ser executadas.

ATENÇÃO, em um primeiro momento, o PHP simplesmente criou essas variáveis sem saber seu conteúdo, seu uso, simplesmente as criou como um atalho, para que você possa ter os dados do usuário a disposição, não precisando se importar com um monte de particularidades do protocolo, ou da forma como esses dados foram disponibilizados, porém, eles ainda assim são dados brutos, cabe ao programador realizar a devida validação destes para saber se podem ser ou não considerados corretos para posteriormente serem tratados pelo script e gerarem o resultado esperado. Vale frisar o que já foi dito em outras aulas, jamais confie em qualquer conteúdo enviado pelo usuário, pois em um ambiente aberto como a WEB, você pode facilmente se confundir entre um usuário real e um atacante.

Para facilitar ainda mais a vida de nós programadores, o CORE do php possui algumas funções de filtragem, que servem justamente para evitar ou pelo menos mitigar a entrada de dados inválidos, garantindo por exemplo que o tipo de dado correto (inteiro, string, data, url, etc) está sendo informado, que não está sendo tentado realizar algum tipo de injeção de código ou de sql através dessas entradas, que o tamanho dos dados está dentro do esperado pelo programador, e uma infinidade de recursos para garantir a qualidade dos dados.

Veja as principais funções usadas para a filtragem de dados, e o seu comportamento e retorno, esta é a maneira correta de tratar os dados, pois, essas funções não analisam o conteúdo existente nas super globais, e sim, as partes da requisição, seja:

filter_has_var(int $tipostring $nome)  => Verifica se a variável é proveniente de um tipo, por exemplo analisando a cadeia de consulta para valores GET, considere o seguinte exemplo de requisição GET

http://localhost/test.php?nome=romeu
$_GET['email']="[email protected]";
if ( !filter_has_var(INPUT_GET, 'email') ) {
    echo "INVÁLIDO";
}else{
    echo "VÁLIDO";
}
echo " - ";
if ( !filter_has_var(INPUT_GET, 'nome') ) {
    echo "INVÁLIDO";
}else{
    echo "VÁLIDO";
}

Considerando a primeira linha como sendo digitada no browser do usuário, a saída desse exemplo vai imprimir INVÁLIDO - VÁLIDO pois a variável email, mesmo sendo inserida pelo programador explicitamente na variável GET não vai ser analisada como válida pois não está presente na cadeia de consulta do URL.

Para utilizar com uma maior segurança as entradas dos usuários, seja para inserção ou pesquisa em bancos de dados, para a correta tomada de decisão condicional no script devemos utilizar os filtros, que são funções destinadas ao correto tratamento de valores provenientes do meio externo (web) ou dos usuários, para isso, abaixo vamos descrever de maneira rápida cada uma das funções seus argumentos e seus retornos esperados, ou códigos de erro em casos de falhas que possam ser tratados como exceções e que produzam a saída esperada para o usuário.

filter_list() – Retorna um array dos nomes de todos filtros suportados, um array vazio se não há filtros disponíveis, os índices deste array não são IDs de filtros, eles podem ser obtidos com uso da função filter_id() a partir do nome do filtro, esta função é útil pois algumas extensões podem implementar novos filtros, e também, futuramente o proprio CORE do PHP pode atribuir novos códigos ou valores.

filter_id() – Retorna o ID de um dado filtro pelo nome, usada em conjunto com a filter_list() vai obter uma lista com todos os filtros possíveis e ativos para a instalação atual. veja o exemplo de uso abaixo.

<?php
foreach (filter_list() as $key => $value) {
    echo "<br>".$value.'='.filter_id($value);
}
?>

filter_input( VARIÁVEL, FILTRO, OPÇÕES) –  onde TIPO é um número inteiro, que também pode ser uma das constantes pr=e-definidas que representam cada um dos tipos válidos, atualmente as constantes previstas são INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, INPUT_ENV que representam respectivamente as variáveis super globais de mesmo nome, VARIÁVEL é o nome que o nosso sistema espera receber com o conteúdo da variavel, por exemplo numa requisição GET: http://localhost/index.php?nome=romeu&sobrenome=gomes poderíamos usar a filter da seguinte maneira filter_input(INPUT_GET, ‘nome’, FILTER_SANITIZE_SPECIAL_CHARS); e caso nosso usuário tente passar algum caractere como uma aspas, ou algum outro conteúdo que possa causar alguma resposta inesperada, ele será automaticamente eliminado, restando somente a string limpa. FILTRO é justamente o tipo do filtro desejado identificado pelo ID fornecido pela filter_id, o PHP fornece uma grande quantidade de filtros pré-determinados, que podem ser também acessados pelas constantes pré definidas.

filter_var(TIPO, VARIAVEL, FILTRO, OPÇÕES) –  de maneira análoga a filter_input aplica os filtros de validação a qualquer variável, seja uma variável de sessão, ou uma variável global, uma super global (neste caso, o filtro pega o valor já existente na variável, não tendo exata ciência se esta é realmente proveniente do input correto. É muito útil, para receber parâmetros de outros blocos de código, ou mesmo de outras linguagens através de interface de linha de comando, ou de outros programadores que estejam desenvolvendo outras partes da aplicação, Plugins entre outros.

Outras duas funções que pertencem a classe filters do php são filter_input_array( TIPO, ARR_DEFINIÇÕES ) e que aceita os mesmos valores de TIPO da função anterior, porém, recebe todo o array de conteúdo, note que você pode passar um valor referente a um filter_id que será aplicado a TODOS os valores do array do conteúdo, ou pode especificar um array de chave=>valor onde a chave é o nome da variável e o valor seu FILTER_ID ou um array de 3 posições contendo chave=>valor onde as chaves são ‘filter’, ‘flags’, ‘options’ e determina os valores de chamada da função filter_input, veja 2 exemplos:

$arrGET = array(
    'product_id'   => FILTER_SANITIZE_ENCODED,
    'component'    => array('filter'    => FILTER_VALIDATE_INT,
                            'flags'     => FILTER_REQUIRE_ARRAY,
                            'options'   => array('min_range' => 1, 'max_range' => 10)
                           ),
    'versions'     => FILTER_SANITIZE_ENCODED,
    'doesnotexist' => FILTER_VALIDATE_INT,
    'testscalar'   => array(
                            'filter' => FILTER_VALIDATE_INT,
                            'flags'  => FILTER_REQUIRE_SCALAR,
                           ),
    'testarray'    => array(
                            'filter' => FILTER_VALIDATE_INT,
                            'flags'  => FILTER_REQUIRE_ARRAY,
                           )
);
$myinputs = filter_input_array(INPUT_POST, $args);

$myGetInputs = filter_input_array(INPUT_GET, array( 'nome'=>FILTER_SANITIZE_STRING, 'id'=>FILTER_SANITIZE_NUMBER_INT));

$myGetCookies = filter_input_array(INPUT_COOKIE, FILTER_SANITIZE_NUMBER_INT);

A variável myinputs permite receber todos os parâmetros passados pelo corpo da requisição pelo método POST de acordo com a série de filtros pré estabelecidos, e se algum outro valor for passado será ignorado. Já myGetInputs vai conter os dois valores ID e NOME com as características padrão dos dois filtros escolhidos. E por fim, myGetCookies vai receber TODOS os valores inteiros armazenados nos cookies, outros conteúdos serão convertidos para inteiros (se possível) ou ignorados pelo PHP.

Novamente de maneira análoga a filter_input_array() temos a filter_var_array() que aplica os filtros do mesmo modo a qualquer variável no formato array, ou qualquer objeto iterável como um JSON ou XML devidamente parsed ou encoded para o PHP.

Lista de constantes de filtros do CORE PHP disponíveis e seus significados

INPUT_POST Variáveis POST. INPUT_GET Variáveis GET. INPUT_COOKIE Variáveis COOKIE. INPUT_ENV Variáveis ENV. INPUT_SERVER Variáveis SERVER.

FILTER_FLAG_NONE Sem flags.
FILTER_REQUIRE_SCALAR Flag usada para requerir escalar como entrada
FILTER_REQUIRE_ARRAY Requer um array como entrada.
FILTER_FORCE_ARRAY Sempre retorna um array, mesmo que de um único valor proveniente de uma variável.
FILTER_NULL_ON_FAILURE Usa NULL ao invés de FALSE em caso de falha ou vazio.
FILTER_VALIDATE_INT um número inteiro válido positivo ou negativo.
FILTER_VALIDATE_BOOLEAN filtro “boolean”, pode ser ON|OFF, 0|1, TRUE|FALSE, t|f, T|F ou algum outro valor analisado como válido para o tipo boolean.
FILTER_VALIDATE_FLOAT um número de ponto flutuante, as opções podem ser permitir notação científica, permitir notação decimal (de acordo com o idioma setado permite uso de ponto, virgula ou ambos com separadores de milhar), fração onde só é permitida a notação a parte inteira e da parte fracionaria ignorando a notação de milhar.
FILTER_VALIDATE_REGEXP uma uma REGEX (Expressão Regular) para validação da entrada, permite declarações complexas de tipos como documentos, telefones, placas de automóveis etc).
FILTER_VALIDATE_URL permite um endereço no padrão url, exemplo protocolo://endereço:porta/caminho/do/recurso.extensão?parâmetros=valores&outrosparametros=valor
FILTER_VALIDATE_EMAIL permite um e-mail analisado como válido (indiferente do idioma usado, pode ou não conter caracteres especiais) porém precisa seguir a anatomia de um e-mail como [email protected] qualificado, porém, não garante que o e-mail exista ou seja válido.
FILTER_VALIDATE_IP permite uma mascara ou range ou ip único no padrão V4 ou V6 devidamente qualificado, porém não garante que ele é válido.
FILTER_DEFAULT o filtro padrão configurado no PHP que pode ser UNSAFE_RAW o padrão ou qualquer outro, desde que definido no PHP.INI ou em outra diretriz de inicialização.
FILTER_UNSAFE_RAW filtro bruto, ou seja, o conteúdo recebido é passado de meneira bruta, nao encodado com BASE64.
FILTER_SANITIZE_STRING uma STRING limpa, sem caracteres de escape ou tags e códigos.
FILTER_SANITIZE_STRIPPED uma string somente com letras números e alguns símbolos pré-determinados (útil para senhas e hash ou token).
FILTER_SANITIZE_ENCODED uma string que terá os caracteres como aspas simples, duplas, tab e outros escapados de maneira correta.
FILTER_SANITIZE_SPECIAL_CHARS uma string que possibilita por exemplos símbolos como <> que serão codificados para a entidade correta e escapados, ou seja, terão uma simbologia gráfica de apresentação, mais não serão analisados como código pelo browser ou bancos de dados e etc.
FILTER_SANITIZE_EMAIL análogo ao validate_email, porém, ele vai tentar retirar os dados inválidos e retornar um e-mail validado mesmo que o conteúdo contenha uma entrada pseudo-inválida.
FILTER_SANITIZE_URL análogo também, tentando devolver toda a qualificação de uma URL a partir do conteúdo recebido
FILTER_SANITIZE_NUMBER_INT análogo, os filtros SANITIZE tentam remover o que não faz parte e retornar somente o dado seguindo o esboço para o tipo de dados, definido.
FILTER_SANITIZE_NUMBER_FLOAT permite como opção receber somente a parte inteira, a parte fracionária, ou aceitar notação científica para números e pontos flutuantes.
FILTER_SANITIZE_MAGIC_QUOTES escapa de maneira correta aspas simples, duplas para serem usadas como uma string no PHP
FILTER_CALLBACK um filtro que será chamado como um RETORNO pelo analisador.
FILTER_FLAG_ALLOW_OCTAL Permite notação octal (0[0-7]+) no filtro INTEGER.
FILTER_FLAG_ALLOW_HEX Permite notação hexadecimal (0x[0-9a-fA-F]+) no filtro INTEGER.
FILTER_FLAG_STRIP_LOW Remove caracteres com valor ASCII menor que 32.
FILTER_FLAG_STRIP_HIGH Remove caracteres com valor ASCII maior que 127.
FILTER_FLAG_ENCODE_LOW Codifica caracteres com valor ASCII menor que 32.
FILTER_FLAG_ENCODE_HIGH Codifica caracteres com valor ASCII maior que 127.
FILTER_FLAG_ENCODE_AMP Codifica & como parte da string.
FILTER_FLAG_NO_ENCODE_QUOTES Não codifica ‘ e ” (aspas)
FILTER_FLAG_EMPTY_STRING_NULL sem uso, retorna um valor null
FILTER_FLAG_ALLOW_FRACTION Permite parte fracionado no filtro “number_float”.
FILTER_FLAG_ALLOW_THOUSAND Permite separador de milhar (,) no filtro “number_float”.
FILTER_FLAG_ALLOW_SCIENTIFIC Permite notação científica (e, E) no filtro “number_float”.
FILTER_FLAG_SCHEME_REQUIRED Requer scheme no filtro “validate_url”.
FILTER_FLAG_HOST_REQUIRED Requer host no filtro “validate_url”.
FILTER_FLAG_PATH_REQUIRED Requer path no filtro “validate_url”.
FILTER_FLAG_QUERY_REQUIRED Requer query no filtro “validate_url”.
FILTER_FLAG_IPV4 Permite somente endereço IPv4 no filtro “validate_ip”.
FILTER_FLAG_IPV6 Permite somente endereço IPv6 no filtro “validate_ip”.
FILTER_FLAG_NO_RES_RANGE Não permite endereços reservados no filtro “validate_ip”.
FILTER_FLAG_NO_PRIV_RANGE Não permite endereços privados no filtro “validate_ip”.

FILTER_SANITIZE_FULL_SPECIAL_CHARS – similar a função htmlspecialchars() inclusive com suporte a UTF-8

Excessoes e Erros – Tratando de maneira correta

Todos nós programadores sabemos que não podemos confiar que tudo sempre vai ser executado da maneira exata que esperamos, por isso, os filtros foram criados, porém, além deles, outras coisas podem não sair como o esperado, por este motivo, cabe ao programador, saber tratar de maneira correta as exceções e os erros, por exemplo, imagina que nosso sistema precisa realizar uma série de processamentos para consumir uma API externa para concluir o processo e apresentar a resposta ao nosso usuário e por algum motivo a API está fora do ar, ou devolveu um valor de erro?
Imagina que precisamos inserir um dado enviado pelo usuário para nosso SGDB e o DBA precisou fazer uma manutenção e fechou o servidos para conexões? Ou algum dado obrigatório não foi informado na requisição, ou veio com um valor inválido? E se demos inicio a um processo por exemplo de calculo de comissões de um funcionário que teve alguns milhares de pedidos e bem no meio desse processo a energia do computador do nosso cliente desligou? Estes são só alguns cenários que podem ocorrer e que nós como desenvolvedores precisamos saber como tratar, o PHP possui algumas formas diferentes e algumas funções e configurações especificas para nos ajudar com esse processo, vamos entender como ele pode nos auxiliar.

As principais configurações de como o interpretador deve lidar com mensagens de erro em tempo de execução foram abordadas na AULA 11 e também na AULA 8 neste momento, vamos ver como tratar de maneira mais genérica criando um ponto único de entrada e de saída para nossos scripts, desse modo, se algo der errado, conseguimos fechar corretamente nossas conexões, persistir ou recuperar nossos dados nos bancos de dados, salvar um arquivo no sistema com o LOG DE ERROS, apresentar dados coerentes e informativos para o usuário, sem correr risco de apresentar dados sensíveis como senhas e dados de usuários, que um usuário ou atacante possam utilizar para explorar nosso sistema.

Como já estudado o PHP implementa a classe Exception e a classe ErrorException que é uma extensão dessa e que de maneira genérica permitem identificar o erro e o que o causou, porém, só isso vai acabar gerando mensagens não muito amigáveis para o usuário final, nem vai garantir que tenha-mos realizado todo o processo necessário para garantir que nenhum dado foi comprometido devido a este erro.

Por isso, devemos definir algumas rotinas ou funções específicas que usam a implementação e extensões dessa classe, e de algumas outras ferramentas da linguagem para fazer o correto tratamento em tempo de execução, além, de disparar para os responsáveis ou desenvolvedores alertas sobre possíveis falhas em algum ponto do sistema. Como você viu anteriormente, mais vamos frizar aqui novamente, se você esta rodando sua aplicação em um servidor dedicado, você pode configurar algumas diretrizes diretamente no PHP.INI, se você roda sua aplicação em um servidor com multiplus propósitos, pode definir algumas dessas diretrizes dentro das configurações do próprio PHP em tempo de execução, geralmente ambientes configurados para o desenvolvimento possuem saídas mais verbosas, que consistem em apresentar erros na tela (ou no browser) e até mesmo informações de pilha de processamento ou de debug, já ambientes de teste/homologação já devem ser mais privados podendo apresentar dados mais críticos para facilitarem o feedback para os desenvolvedores porém, garantirem que dados mais sensíveis não sejam exibidos. Por fim ambientes de produção devem possuir um tratamento, e somente mensagens amigáveis aos usuários devem ser apresentadas, de mesmo modo, permitirem que o usuário tente executar as correções e re-fazer a solicitação, ou deixarem claro se é um erro momentâneo ou persistente.

Por exemplo, cenários como o abaixo podem ocorrer tanto em definições diretas nos arquivos de configuração como PHP.INI como em script em tempo de execução

f (AMBIENTE == 'DEVELOPER') {
    error_reporting(E_ALL);
    ini_set("display_startup_errors", true);
    ini_set("display_errors", true);
} elseif (AMBIENTE == 'HOMOLOG') {
    error_reporting(E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR);
    ini_set("display_startup_errors", true);
    ini_set("display_errors", false);
} else {
    error_reporting(0);
    ini_set("display_startup_errors", false);
    ini_set("display_errors", false);
}

A Classe error, implementa algumas funções a saber:
Error::getMessage — Recupera a mensagem de erro (em inglês)
Error::getPrevious — Retorna o último Throwable (ultimo erro que pode ser rastreado)
Error::getCode — Recupera o código do erro (numérico)
Error::getFile — Recupera o arquivo onde o erro ocorreu (qual script estava sendo interpretado)
Error::getLine — Recupera a linha onde o erro ocorreu (numero da linha de código do arquivo sendo executado, ou número da linha que chamou a função externa que disparou o erro)
Error::getTrace — Recupera a pilha de rastreio (STACK TRACE, com todos os arquivos e linhas desde a requisição feita pelo cliente, os includes/requeries se foi usada alguma chamada de função recursiva, qual foi a função e linha dentro da função se for uma função definida pelo usuário.
Error::getTraceAsString — Recupera a pilha de rastreio como uma string – essa é a mensagem impressa para a saída padrão pelo manipulador interno de erro error_reporting.
Error::__toString — Representação em string do erro – mensagem impressa pelo interpretador na saída padrão pelo método interno de tratamento.

O manipulador de erros padrão do PHP é inadequado para aplicações em ambientes de produção, por este motivo, o próprio PHP  permite manipular error com manipuladores próprios, basta instalar o manipulador (ou manipuladores) com a função set_error_handler(). Embora alguns tipos de erros não possam ser manipulados desta maneira, aqueles que puderem, serão manipulados da forma que o programador decidir, por exemplo, utilizado uma página de erro customizada, uma mensagem amigável ao usuário e reportar mais precisamente que por log, como enviando um e-mail. Assim como exceções normais, as exceções Error serão elevadas até alcançarem o primeiro bloco catch correspondente. Se não existir nenhum bloco correspondente, qualquer manipulador de exceção padrão instalado com a função set_exception_handler() será chamado, e se não existir nenhum manipulador padrão de exceção, a exceção será convertida em um erro fatal e tratada como um erro tradicional.

Um bloco de exceção se parece com:

$at = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
);
try {
    $db = new PDO(
        "pgsql:host=" . SERVERDB . ";port=5432;dbname=" . DBNAME . ";user=" . DBUSER . ";password=" . DBPASSW . ";",
        DBUSER,
        DBPASSW,
        $at
    );
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $db->beginTransaction(); // inicia as transações
} catch (PDOException $e) {
    $db = null;
    __out('SERVICE UNAVAIABLE DATABASE CONNECTION ERROR', 503);
}

Neste bloco, tentamos realizar uma conexão a um servidor de banco de dados através da classe PDO e em caso de falha, a função __out que foi definida pelo programador é chamada, ou caso contrário, a execução prossegue normalmente. Esta função, no nosso exemplo se parece com:

function __out($_msg, $code = 200)
{
    global $db;
    http_response_code($code);
    header("content-type: application/json;charset=UTF-8");
    header('Access-Control-Allow-Credentials: true');
    header('Access-Control-Max-Age: 86400');
    if (is_array($_msg)) {
        $_out['errors'] = $_msg;
    } else {
        $_out['erro_no'] = $code;
        $_out['erro'] = $_msg;
    }
    //se existe uma conexão ao banco, 
    if($db != null && $code != 200){
        $db->rollBack();
    } elseif ($db != null && $code == 200) {
        $db->commit();
    } else {
       $db = null;
    }
    echo json_encode($_out);
    $db = null;
    exit($code);
}

Note, como estamos tratando as respectivas saída de uma API, em formato JSON, mesmo que algum erro ocorra, nosso usuário espera receber a saída no formato correto, por isso setamos os cabeçalhos e executamos a saída no formato padrão, indiferente de em que parte da execução esse erro tenha ocorrido, se, nosso script precisasse enviar um e-mail a algum administrador com algum dado como a pilha de erro (STACK) ou alguma informação que permita mitigar a falha, poderíamos acrescentar esses tratamentos aqui. Abaixo, vamos setar um manipulador de erro padrão, que verifica se nosso ambiente é produção ou desenvolvimento e mostra com ou sem verbosidade a mensagem, e caso encontra alguma falha, realiza o tratamento por exemplo de rolback em transações pendentes no banco de dados e outras informações.

Para este tratamento, o PHP implementa duas funções, que fazem o direcionamento para a função de callback, são elas

set_exception_handler() – Tratando excessões não capturadas

set_exception_handler('funcao_tratamento_excessao'); define a função para o tratamento de todas as exceções, se esta não for capturada, e a saída correta não for gerada, por exemplo com um die() a execução do script retorna para o ponto onde a exceção foi gerada. No exemplo, funcao_tratamento_excessao() é chamada caso o bloco TRY-CATCH não capture, no bloco acima, quando tentamos capturar a PDOException a saída é direcionada para o bloco catch. Porém, se alguma outra excessão for lançada, caso exista um set_exception_handler essa função seria chamada.

Coisas que você deve estar ciente, um manipulador de exceções trata as exceções que não foram detectadas antes, ou seja, caso exista um tratamento nos blocos catch esse bloco será executado. É a natureza de uma exceção que ele interrompa a execução de seu programa, uma vez que ela declara uma situação excepcional na qual o programa não pode continuar a menos que você saiba instruí-lo sobre o que fazer, por exemplo, você pode ter funções que caso uma conexão falhe, faça uma pausa e tente novamente. Como não foi detectado, seu código sinaliza que ele não está ciente da situação e não pode continuar.

Implicações no uso: retornar ao script é simplesmente impossível quando o tratador de exceção já foi chamado, uma vez que uma exceção não capturada não é um aviso. Use seu próprio sistema de log de depuração ou notificação para coisas como essa, lembrando, você recebeu o controle e pode fazer o LOG de erro com uma informação detahada ou mesmo envia-lo a um ADMIN e apresentar uma mensagem explicativa diferente para o usuário. Além disso, embora ainda seja possível chamar funções a partir do seu script, uma vez que o manipulador de exceções já foi chamado, as exceções lançadas a partir daquele trecho de código não acionarão o manipulador de exceções novamente, por isso não seria prudente retornar a execução neste ponto pois o php irá morrer sem deixar nenhuma outra informação separada de exceção não capturada, e um quadro de pilha desconhecido. Portanto, se você chama funções a partir de seu script, certifique-se de capturar todas as exceções que possam ocorrer por meio de try..catch dentro do manipulador de exceções, definindo este capturador somente como um recurso final de apresentar algo para o usuário e ainda poder manter uma verbosidade e registro do lado do servidor.

Para quem ainda interpreta mal o significado essencial do manipulador de exceções, ele só pode ser usado para lidar com o aborto de seu script com elegância, por exemplo, em um projeto como um sistema, em produção, sendo utilizado por usuários finais, seu manipulador deve renderizar uma página de erro agradável, eventualmente ocultando informações que não devem vazar para o público, você pode querer escrever em um log do servidor ou ainda enviar um e-mail para o administrador do sistema. Em outras palavras, você não deve redirecionar todos os erros do php para um manipulador de erros usando exceções, incluindo avisos, pois inevitavelmente ele vai abortar seu script e não é uma ideia muito boa, por exemplo abortar porque ouve um aviso de que você não definir uma variável por exemplo.

set_error_handler() -Tratamento de erros recuperáveis e não recuperáveis

set_error_handler('funcao_tratamento_erro', $erro_types = E_ALL) – Define uma função para tratamento de erros, esta função pode ser usada para definir a sua própria maneira de manipular erros em tempo de execução, por exemplo, em aplicações nas quais você precisa fazer fazer uma limpeza de dados/arquivos ou de bancos de dados, quando um erro crítico acontece, ou quando você precisa que haja um erro sob certa circunstancia sendo enviado para o usuário. Note que quando você define uma função para o trataento de erros, o php desabilita o tratamento interno.  As configurações de error_reporting() não terão efeito e o seu manipulador de erro for chamado, entretanto você ainda é capaz de ler o valor atual de error_reporting e agir apropriadamente. É importante notar que este valor será 0 se o comando que causou o erro foi precedido por @ operador de controle de erro. Outro ponto importante, é que é de sua responsabilidade usar um die() se necessário. Se a função manipuladora de erro retornar, a execução do script irá continuar com o próximo comando após o que causou o erro.

Os seguintes tipos de erros não podem ser manipulados com uma função definida pelo usuário: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, e a maioria de E_STRICT que ocorram no arquivo aonde set_error_handler() for chamada. Do mesmo modo, se um erro acontecer antes que o script seja executado (exemplo em uploads de arquivos) a função personalizada de manipulação não pode ser chamada já que não estará registrada para isso neste momento.

A função do usuário chamada precisa aceitar pelo menos dois parâmetros: o código de erro, e uma string descrevendo o erro. Então, outros três parâmetros opcionais podem ser dados o nome do arquivo no qual o erro aconteceu, o número da linha na qual o erro aconteceu, e o contexto no qual o erro aconteceu (uma matriz que aponta para a tabela de símbolos ativos no ponto em que o erro aconteceu). A função pode ser definida como:

funcao_tratamento_erro(
    $errno,
    $errstr,
    $errfile = '',
    $errline = '',
    $errcontext = array()
)

$errno – O primeiro parâmetro, errno, contém o nível de erro que aconteceu, como um inteiro.
$errstr – O segundo parâmetro, errstr, contém a mensagem de erro, como uma string.
$errfile – O terceiro parâmetro é opcional, errfile,  contém o nome do arquivo no qual o erro ocorreu, como uma string.
$errline – O quarto parâmetro é opcional, errline, contém o número da linha na qual o erro ocorreu, como um inteiro.
$errcontext – O quinto parâmetro é opcional, errcontext, é uma matriz (array) que aponta para a tabela de símbolos ativos no ponto aonde o erro ocorreu. Em outras palavras, errcontext irá conter uma matriz de cada váriavel que exista no escopo aonde o erro aconteceu. O manipulador de erro do usuário não deve modificar o contexto de erro de maneira nenhuma.

Se a função retornar false então o manipulador de erro normal continua após o ponto onde o erro ocorreu, se retornar true, o php encerra a execução liberando o buffer de saída para a saída padrão, neste caso, é responsabilidade do programador de ter usado a função die() de maneira correta, se for o caso.

error_types Pode ser usado para mascarar o uso da função error_handler da mesma maneira que a configuação error_reporting controla quais erros são exibidos, ou seja, sem esta mascara definida a função error_handler será chamada para cada erro sem se importar com a definição de error_reporting, já quando configurada dessa maneira, ele só será chamada se o tipo de erro coincidir com o valor passado.

Os seguintes valores ou combinação deles podem ser usados:
1 ou E_ERROR – Erros fatais em tempo de execução. Estes indicam erros que não podem ser recuperados, como problemas de alocação de memória. A execução do script é interrompida.
2 ou E_WARNING – Avisos em tempo de execução ou erros não fatais. A execução do script não é interrompida.
3ou  E_PARSE – Erro em tempo de compilação. Erros gerados pelo interpretador. Não podem ser manipulados.
8 ou E_NOTICE – Notícia em tempo de execução, indica que o script encontrou alguma coisa que pode indicar um erro, mas que também possa acontecer durante a execução normal do script.
16 ou E_CORE_ERROR – Erro fatal que acontece durante a inicialização do PHP. Este é parecido com E_ERROR, exceto que é gerado pelo núcleo do PHP.
32 ou E_CORE_WARNING – Avisos ou erros não fatais que aconteçam durante a inicialização do PHP.
64 ou E_COMPILE_ERROR – Erro fatal em tempo de compilação. Este é parecido com E_ERROR, exceto que é gerado pelo Zend Scripting Engine.
128 ou E_COMPILE_WARNING – Aviso em tempo de compilação. Este é parecido com E_WARNING, exceto que é geredo pelo Zend Scripting Engine.
256 ou E_USER_ERROR – Erro gerado pelo usuário programador. Este é parecido com E_ERROR, exceto que é gerado pelo código PHP usando a função trigger_error()
512 ou E_USER_WARNING – Aviso gerado pelo usuário programador. Este é parecido com E_WARNING, exceto que é gerado pelo código PHP usando a função trigger_error().
1024 ou E_USER_NOTICE – Notícia gerada pelo usuário programador. Este é parecido com E_NOTICE, exceto que é gerado pelo código PHP usando a função trigger_error().
2048 ou E_STRICT – Notícias em tempo de execução. Permite ao PHP sugerir mudanças ao seu código as quais irão assegurar melhor interoperabilidade e compatibilidade futura do seu código. Deve ser ativo em ambiente de desenvolvimento.
4096 ou E_RECOVERABLE_ERROR – Erro fatal capturável. Indica que um erro provavelmente perigoso aconteceu, mas não deixou o Engine em um estado instável. Se o erro não for pego por uma manipulador definido pelo usuário, a aplicação é abortada como se fosse um E_ERROR.
8192 ou E_DEPRECATED – Avisos em tempo de execução. Habilite-o para receber avisos sobre código que não funcionará em futuras versões do PHP. Deve ser ativo em ambiente de desenvolvimento.
16384 E_USER_DEPRECATED – Mensagem de aviso gerado pelo usuário programador. Este é como um E_DEPRECATED, exceto que é gerado em código PHP usando a função trigger_error().
30719 ou E_ALL (integer) Todos erros e avisos, como suportado, exceto de nível E_STRICT

Os valores acima numéricos ou usando as constantes são usados para criar um bitmask que especifica quais erros reportar. Você pode usar os operadores Bit-a-bit para combinar estes valores ou mascarar certos tipos de erro. Note que somente ‘|’, ‘~’, ‘!’, ‘^’ e ‘&’ são interpretados como operadores.

register_shutdown_function() – Função de saída genérica

Uma função definida para ser chamada na finalização do script, note, que essa função pode ser chamada mais de uma vez, e cada uma irá ser chamada na mesma ordem como elas foram registradas. Se você chamar exit() dentro de uma função registrada “shutdown”, o processamento irá parar completamente e nenhuma outra função shutdown irá ser chamada.

register_shutdown_function( ‘funcao_de_saida’)

As funções “shutdown” registradas são chamadas após a requisição ter sido completamente feita (incluindo envio e saída de buffer), então isso não é possível enviar saída para o browser usando echo ou print, ou recuperar o conteúdo de algum buffer de saída usando ob_get_contents(). Outro detalhe importante, se o tempo máximo de execução (timeout) for atingido, mesmo assim essas funções são chamadas. Se você precisar fazer algo com arquivos em sua função_de_saida, use caminhos absolutos, ou baseados no ServerRoot definido para o PHP porque o processamento do script na chamada já está completo, e o diretório de trabalho atual muda para Server Root.

Talvez você esteja pensando em qual a utilidade disso, porém, muitos serviços úteis podem ser delegados a este gatilho, ele é muito eficaz porque é executado no final do script, mas antes da destruição de qualquer objeto, portanto, todas as instanciações ainda estão ativas, você pode definir uma série de gerenciadores de eventos de desligamento simples que permitem gerenciar funções ou métodos estáticos / dinâmicos, com um número indefinido de argumentos sem usar qualquer reflexão, aproveitando um tratamento interno.

É perfeitamente possível, registrar uma função de shutdown em qualquer parte do script, ou mesmo, dentro de uma função ou método, o que torna possível, por exemplo, uma implementação de um fechamento de um socket, ou salvar o estádo de variáveis ou globais para análise de comportamento e heurísticas do sistema, rastreamento de carga (serverload) fechamento de conexões com bancos de dados, onde, é possível, por exemplo abrir diversas conexões com serviços e servidores diferentes, e em cada processo, definir o seu respectivo fechamento ao término da execução.

ATENÇÃO – USUÁRIOS DO PHP 8 EM Módulo PHP-FPM

Para quem usar o módulo FPM a função register_shutdown_function não deve ser usada, pois este módulo segura a saída para o browser até a conclusão da execução, criando um atraso em relação ao término do script, para isso, a função fastcgi_finish_request() deve ser chamada, dentro da função registrada para o shutdown. Desse modo, todos os dados de resposta para o cliente são enviados e a requisição finalizada, isso permite que tarefas demoradas sejam executadas sem que a conexão com o cliente seja mantida aberta.

Existem algumas armadilhas das quais você deve estar ciente ao usar esta função, por exemplo o script ainda ocupará um processo FPM após fastcgi_finish_request () o que para tarefas de longa execução pode ocupar todos os seus threads de FPM ocasionando erros de gateway no servidor da web. Outra coisa importante é o manuseio da sessão de usuário (session) que ficam bloqueadas enquanto estiverem ativas, isso significa que as solicitações subsequentes serão bloqueadas até que a sessão seja encerrada. Para contornar este problema uma boa prática é você chamar session_write_close() o mais rápido possível, até mesmo antes de fastcgi_finish_request() para permitir solicitações subsequentes e uma boa experiência do usuário. Isso também se aplica a todas as outras técnicas de bloqueio de arquivos ou banco de dados, enquanto um bloqueio estiver ativo, as solicitações subsequentes podem ser bloqueadas.

register_tick_function() – Executando uma função a cada instante

register_tick_function(‘funcao_tik’, mixed $arg, mixed $arg2) – Registra uma função para executar a cada instante, ou seja, ela é executada a cada instrução, sua maior utilidade é para por exemplo analisar chamadas que estejam causando tempos longos de resposta, ou uso excessivo de memória, mais não deve ser usada em ambiente de produção, pois pode causar erros e travar alguns processos de servidores dependendo do método que está sendo indicado.

Um ‘tick’ é um evento que ocorre a cada N declarações de baixo nível executadas pelo interpretador dentro do bloco declare. O valor de N é especificado usando ticks=N dentro do bloco declare da seção directives. Nem todas declarações são passiveis de executar um ‘tick’. Normalmente, expressões de condições e argumentos de expressões não são.

Em tempo, caso você tenha criado ou registrado uma função tick a chamada a unregister_tick_function() faz a remoção dessa.

session_register_shutdown() – Salvando dados de sessão

O PHP mantém internamente um poderoso gerenciados de sessões, que é implementado de maneira automática, porém, em algumas situações especiais o programador pode decidir ou necessitar fazer um tratamento mais específico dos dados de sessão do usuário, seja para persistir uma sessao ou salvar configurações do usuário diretamente em um sistema de SGDB ou em arquivos salvos, que possam até mesmo serem portados para outros servidores.

Nestes casos, o programador pode registrar uma função que será chamada quando a SESSÃO for finalizada, por padrão, essa função define que session_write_close() é chamada quando o script terminar com a manipulação da sessão, por exemplo, você pode querer salvar a sessão ou envia-la para um email para depuração ou outro, nestes casos, você deve definir uma função que faça isso, e ainda assim, pode no término de sua função própria chamar a função interna session_write_close().

A função session_write_close() interna, guarda os dados de sessão e fecha a sessão, permitindo que ela seja destravada e utilizada em uma próxima requisição. Os dados de sessão geralmente são guardados depois que o script termina, sem a necessidade de chamar session_write_close(); mas como dados de sessão são travados para evitar escritas concorrentes, apenas um script pode operar em uma sessão por vez. Ao usar um iframe/framesets junto com sessões, os frames serão lidos um a um devido à esta trava, e usar uma chamada a ela pode reduzir o tempo necessário para carregar todos os frames fechando a sessão o mais cedo possível, assim que todas as alterações na variável de sessão tiverem sido feitas.

Você pode usar a função interna sleep() para depurar qualquer coisa e se tiver uma sessão ainda ativa, por exemplo, em uma página que faz uma solicitação ajax, em que a solicitação ajax pesquisa um evento do lado do servidor e pode não retornar imediatamente se a função ajax não fizer uso de session_write_close(), então sua página parecerá estar travada, e abrir outras páginas em novas abas também irá travar gerando lentidão enorme e até mesmo timeouts no lado do servidor.

ATENÇÃO: se você usar prematuramente uma chamada a session_write_close() e posteriormente o script fizer uma chamada a $_SESSION que faça alguma alteração de valor, essa alteração não causará erro, porém, não será persistida na sessão esse tipo de erro pode ser muito difícil de depurar, portanto, faça toda a manipulação de sessão o mais cedo possível e após ter certeza que nenhum outro script ou chamada subsequente (na mesma pilha de execução) precisa alterar os dados, então, libere o fechamento desta para novas requisições.

Conclusão

Nesta aula, estudamos os procedimentos e maneira que o PHP permite para nós programadores interagirem diretamente com todos os dados de usuário, desde os inputs, até funções específicas que permitem mudar o manipulador padrão para erros e exceções e ter o TOTAL CONTROLE sobre tudo o que está sendo feito, além, de entender como funcionam os filtros de dados, e outros processos internos que o PHP pode auxiliar na rotina de trabalho tradicional para nós programadores.

Na próxima aula, vamos falar de extensões da linguagem que definem funções específicas, que possam causar uma alteração no comportamento padrão, e também, implementar ferramentas para por exemplo manipulação aprofundada de erros, debugger, ferramentas que podem auxiliar os desenvolvedores a localizar possíveis pontos de falha em nossos scripts e uma infinidade de técnicas para melhoria de desempenho, adição de funções além do CORE do proprio PHP para amnipulação de imagens, audio, vídeo, outros formatos de arquivos como MP3 ou PDF entre outras.

Deixe um Comentário