garotosopa

7 coisas simples em PHP que alguns ainda complicam

Publicado em PHP por garotosopa em Maio 30, 2009

É comum ver scripts com dezenas de linhas de código pra fazer algo extremamente simples. Fica aqui meu apelo desesperado com algumas dicas rápidas.

1 – Listar arquivos de um diretório

Se não houver um motivo muito claro pra usar opendir, readdir e closedir (não consigo pensar em nenhum), a forma mais prática de listar o conteúdo de um diretório é com DirectoryIterator:

$iterator = new DirectoryIterator('/var/www');
 
foreach ( $iterator as $entry ) {
    echo $entry->getFilename(), "\n";
}

Se for necessário listar os arquivos recursivamente, percorrendo todos os subdiretórios, é só usar o RecursiveDirectoryIterator junto com o RecursiveIteratorIterator:

$iterator = new RecursiveDirectoryIterator('/var/www');
$recursiveIterator = new RecursiveIteratorIterator($iterator);
 
foreach ( $recursiveIterator as $entry ) {
    echo $entry->getFilename(), "\n";
}

Com um pouco de criatividade, é possível estender essas classes com qualquer lógica facilmente, como por exemplo, para montar uma árvore com a estrutura dos diretórios.

2 – montar e desmontar query strings

Mesmo que menos comum (e menos útil), colocar uma query string numa URL é um trabalho trivial demais pra ser feito com implode, concatenando tudo ou qualquer outro método engenhoso. Desde o lançamento do PHP 5 é possível contar com a http_build_query:

$dados = array(
    'hl=> 'pt-BR',
    'q'  => 'Forgetting Sarah Marshall',
    'testa-escape=> 'acentuação',
);
 
echo http_build_query($dados);
// hl=pt-BR&q=Forgetting+Sarah+Marshall&testa-escape=acentua%C3%A7%C3%A3o

E o inverso também é possível com as funções parse_url e parse_str:

$url = parse_url('http://www.google.com/search?q=anneke+van+giersbergen&num=50');
 
parse_str($url['query'], $query);
 
echo $query['q'];
// anneke van giersbergen

Só fique atento que, por motivos alheios ao bom senso, parse_str por padrão extrai as variáveis no escopo onde foi chamada. É necessário passar um segundo argumento para ter um array gerado por referência, como no exemplo acima com a variável $query.

3 – ler páginas remotas

Dentre todas as implementações, a mais desnecessária costuma ser fsockopen, fwrite, feof, fgets e fclose para ler arquivos remotos por HTTP.

Uma função já resolve:

$contents = file_get_contents('http://php.net/file_get_contents');

Isso é possível graças aos protocol wrappers que encapsulam a lógica de acesso aos respectivos protocolos, tal como HTTP. Esta forma de acesso, no entanto, depende da configuração allow_url_fopen estar habilitada no php.ini (que é o padrão).

Para ler os response headers da requisição, utilize fopen com stream_get_meta_data.

E se um dia você quiser impressionar a mulherada, veja a função stream_wrapper_register para criar o seu próprio protocol wrapper.

4 – submeter dados por post para uma página remota

A coisa fica mais complicada quando o desenvolvedor pensa em usar cURL pra submeter dados por POST para outro servidor. A extensão até tem seu mérito, mas usá-la apenas pra este propósito é um grande equívoco.

As funções que fazem uso dos protocol wrappers aceitam um objeto de stream context, criado pela função stream_context_create, para configurar alguns aspectos do protocolo. As opções de contexto do protocolo HTTP permitem definir, entre outras coisas, o método de acesso (GET, POST, etc) e o conteúdo a ser postado:

$content = http_build_query(array(
    'cidade=> 'Rio de Janeiro',
    'tipo'   => 'Apartamento',
));
 
$context = stream_context_create(array(
    'http=> array(
        'method'  => 'POST',
        'content=> $content,
    )
));
 
$contents = file_get_contents('http://exemplo/teste.php', null, $context);

Quando não for necessário ler o retorno da requisição, basta chamar a url com fopen passando o contexto como quarto argumento.

5 – fazer download de um arquivo remoto

Vale lembrar que a maioria das funções de stream e filesystem aceitam URLs completas e fazem uso da abstração do protocolo. O que eu vejo muita gente esquecer é que isso inclui a função copy:

$url = "http://userserve-ak.last.fm/serve/500/4349551/Terri+Clark.jpg";
 
copy($url, '/tmp/. urldecode(basename($url)));

O trecho acima vai baixar a imagem remota e salvar no arquivo local /tmp/Terri Clark.jpg. E caso não seja óbvio, “local” se refere a quem está executando o script PHP, que no caso será o seu servidor caso seja um script web, e não o cliente que está acessando pelo browser.

Se o objetivo for realmente repassar o conteúdo remoto para o cliente que estiver acessando pelo browser, o script é igualmente simples:

$url = 'http://userserve-ak.last.fm/serve/500/4349551/Terri+Clark.jpg';
 
$handle = fopen($url, 'r');
 
$meta_data = stream_get_meta_data($handle);
 
// Repassa todos os headers do servidor remoto para o nosso cliente
foreach ( $meta_data['wrapper_data'] as $header ) {
    header($header);
}
 
// Repassa o conteúdo para o nosso cliente
fpassthru($handle);

Considerando que estamos apenas testando a funcionalidade. Ter um script de proxy totalmente funcional é bem mais complexo, e certamente já tem algo pronto por aí.

6 – fazer cálculo com data

Dentre todas as simplificações possíveis, a que mais costuma comover é a função strtotime. Pra quem já está acostumado, parece que não faz mais do que sua obrigação. Mas pra quem ainda faz cálculos com data multiplicando por 86400, chega a parecer mágico:

echo 'Amanhã: ', strftime('%A', strtotime('tomorrow'));
// Amanhã: domingo
 
echo 'Próxima segunda: ', strftime('%d de %B de %Y', strtotime('next monday'));
// Próxima segunda: 01 de junho de 2009
 
echo 'Vencimento: ', strftime('%d/%m/%Y', strtotime('+3 months'));
// Vencimento: 30/08/2009

Mais exemplos você mesmo pode ver no manual do PHP ou na página de Date Input Formats do projeto GNU. Para o nome dos meses e dias da semana ficarem em português, utilize setlocale(LC_TIME, ‘pt_BR’); antes de chamar a função strftime.

7 – escapar sql e html

Felizmente nunca mais vi nenhum script com aberrações anti-sql-injection, mas há algum tempo era possível encontrar pessoas removendo palavras-chave de SQL de todas as strings que íam para o banco de dados. Se o usuário digitasse palavras como select, delete ou drop, elas eram simplesmente removidas da frase. Isso quando o programador não interrompia o script e acusava o usuário de estar tentando explorar alguma falha de segurança. Eu juro, isso existia.

Ao trabalhar com PDO, a melhor opção (pra não dizer a única!) é utilizar prepare e execute pra separar a query em si dos seus parâmetros:

$conexao = new PDO('mysql:dbname=banco;host=localhost', 'login', 'senha');
 
$uf = 'RJ';
$idade = 18;
 
$sth = $conexao->prepare('SELECT nome FROM pessoa WHERE uf = ? AND idade > ?');
 
$sth->execute(array($uf, $idade));
 
while ( $row = $sth->fetch() ) {
    echo $row['nome'];
}

Se estiver utilizando drivers nativos, veja as funções mysql_real_escape_string ou mysqli_prepare e mysqli_stmt_bind_param, dependendo da extensão. Certamente outros bancos de dados têm a mesma funcionalidade.

A única preocupação é garantir que os parâmetros não se misturem com a query; não precisa inventar moda e remover o que o usuário digitou.

Outra confusão comum é ao escapar HTML. O objetivo é evitar que o texto digitado por um usuário seja interpretado pelo browser de todos os usuários do site.

Conceitualmente, esta é uma responsabilidade da camada de exibição. O template é que deve utilizar htmlspecialchars antes de gerar a saída na tela, e não antes de salvar no banco. Isso garante que o conteúdo que está no banco é fiel ao que foi digitado pelo usuário e pode ser reaproveitado em outras mídias além do HTML.

O que pode e deve ser feito é filtrar conteúdo realmente indevido, como caracteres inválidos ou espaços extras, dependendo da aplicação.

22 Respostas

Subscreva aos comentários comRSS.

  1. Fernando said, on Maio 30, 2009 at 10:00 pm

    Dicas interessantes, não conhecia o que apresentou na 2ª e na 4ª dica. São formas relmante simplistas de se montar e ler querystrings e de se submeter requisições remotas.

    Até mais.

  2. Patrick Kelecom said, on Maio 31, 2009 at 12:19 am

    Ótimo post! Parabéns. Realmente existe muita gente que ainda dificulta as coisas.

    Abraços

  3. [...] this article: 7 coisas simples em PHP que alguns ainda complicam « garotosopa Share and [...]

  4. marcos junior said, on Junho 2, 2009 at 1:44 pm

    Muito bom!!!! Parabéns!

  5. Rond said, on Junho 2, 2009 at 3:06 pm

    Nossa, nao conhecia esse 4 =o
    parabens ;D

  6. Bill said, on Junho 2, 2009 at 3:44 pm

    Mto legal estas dicas.
    Outra coisa q costumo usar, é ao invés de $_POST['variavel'] pra pegar cada variável de um POST de formulário, utilizo no início do arquivo um
    extract($_POST). E com isso, extraio todas variáveis vindas do POST e uso direto como variável $variavel.
    Abs

  7. garotosopa said, on Junho 2, 2009 at 4:14 pm

    Bill, fazer extract($_POST) é extremamente perigoso, você pode acabar sobrescrevendo alguma variável por engano.

    Experimente, por exemplo, submeter um formulário com um input hidden name=”_SERVER[REMOTE_ADDR]” value=”123″ e o seu script vai passar a considerar outro IP do usuário.

    Pior ainda, dependendo de como você faz a autenticação, submeter name=”_SESSION[username]” value=”admin” pode ser desastroso.

  8. Dubai said, on Junho 3, 2009 at 11:37 am

    Dicas muito uteis. Obrigado!

  9. Rodrigo said, on Junho 4, 2009 at 12:41 am

    Bem útil =]

  10. Rond said, on Junho 4, 2009 at 12:19 pm

    Quanto ao POST, voce pode usar os flags do extract pra adicionar um prefixo =}
    ou num foreach($_POST) mesmo

    extract($_POST, EXTR_PREFIX_ALL, “prefixo”);
    ou
    foreach($_POST as $key=>$value){
    $_{$key} = $value;
    }

    o foreach e ‘melhor, ja que voce pode filtrar os dados, tipo com o addslashes direto, sem fazer um por um

  11. Lucas Arruda said, on Junho 4, 2009 at 1:00 pm

    Rond, não esqueça que você deve tratar esses dados.

  12. Rond said, on Junho 4, 2009 at 5:01 pm

    foreach($_POST as $key=>$value){
    $_{$key} = addslashes($value);
    }
    ou funcao propria sua, tanto faz.
    por isso citei o foreach pra tratar =]

  13. Fernando Binasco said, on Junho 6, 2009 at 1:28 pm

    Parabéns pelo post!

  14. Felipe Pena said, on Junho 12, 2009 at 9:06 am

    Boaaaa, sopa! ;-)

  15. caike said, on Junho 12, 2009 at 2:15 pm

    Excelentes dicas! Diretas e objetivas.

  16. Cláudio dos Santos Júnior said, on Junho 12, 2009 at 4:40 pm

    Muito bom artigo: criativo e útil.

  17. Murilo Adriano said, on Junho 16, 2009 at 12:11 am

    Ficou muito legal sopa, livrou muitos caracteres digitados no #php-br :)
    Agora é só copy/paste a url no #!

    Valeu!!

  18. olive said, on Julho 3, 2009 at 3:29 am

    o item 7.. ainda existe quem complique e não são poucos.

    e por mais que vc explique nao dão ouvidos..

  19. [...] fazer um comentário » Bom, vou fazer uma série de posts dizendo sobre coisas simples de se fazer em C++ que muitos ainda complicam. A idéia surgiu do post de um colega. [...]

  20. Daniel said, on Agosto 5, 2009 at 5:10 pm

    Muito bom!

  21. Garotomacarrão said, on Setembro 12, 2009 at 8:21 pm

    Esse Garotosopa parece um cara muito estranho pra mim, mas até que algumas dicas foram excelentes, principalmente o POST com file_get_contents(). Queria chegar a ser uma partícula infinitesimal do cocô do micróbio que fica nos pés dos sapatos dele.


Deixe uma resposta