PHPit - PHP

"Agora, pois, permanecem a fé, a esperança, o amor, estes três; mas o maior destes é o amor." (I Coríntios 13:13)

7 coisas simples em PHP que alguns ainda complicam

Post original por garotosopa: http://garotosopa.wordpress.com/2009/05/30/7-coisas-simples-em-php-que-alguns-ainda-complicam/

É 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:

Código: Alternar entre o modo de cópia/destaque
  1. $iterator = new DirectoryIterator('/var/www');
  2.  
  3. foreach ( $iterator as $entry ) {
  4. echo $entry->getFilename(), "\n";
  5. }
$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:

Código: Alternar entre o modo de cópia/destaque
  1. $iterator = new RecursiveDirectoryIterator('/var/www');
  2. $recursiveIterator = new RecursiveIteratorIterator($iterator);
  3.  
  4. foreach ( $recursiveIterator as $entry ) {
  5. echo $entry->getFilename(), "\n";
  6. }
$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:

Código: Alternar entre o modo de cópia/destaque
  1. $dados = array(
  2. 'hl' => 'pt-BR',
  3. 'q' => 'Forgetting Sarah Marshall',
  4. 'testa-escape' => 'acentuação',
  5. );
  6.  
  7. echo http_build_query($dados);
  8. // hl=pt-BR&q=Forgetting+Sarah+Marshall&testa-escape=acentua%C3%A7%C3%A3o
$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:

Código: Alternar entre o modo de cópia/destaque
  1. $url = parse_url('http://www.google.com/search?q=anneke+van+giersbergen&num=50');
  2.  
  3. parse_str($url['query'], $query);
  4.  
  5. echo $query['q'];
  6. // anneke van giersbergen
$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:Código: Alternar entre o modo de cópia/destaque
  1. $contents = file_get_contents('http://php.net/file_get_contents');
$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:

Código: Alternar entre o modo de cópia/destaque
  1. $content = http_build_query(array(
  2. 'cidade' => 'Rio de Janeiro',
  3. 'tipo' => 'Apartamento',
  4. ));
  5.  
  6. 'http' => array(
  7. 'method' => 'POST',
  8. 'content' => $content,
  9. )
  10. ));
  11.  
  12. $contents = file_get_contents('http://exemplo/teste.php', null, $context);
$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:
Código: Alternar entre o modo de cópia/destaque
  1. $url = "http://userserve-ak.last.fm/serve/500/4349551/Terri+Clark.jpg";
  2.  
  3. copy($url, '/tmp/' . urldecode(basename($url)));
$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:

Código: Alternar entre o modo de cópia/destaque
  1. $url = 'http://userserve-ak.last.fm/serve/500/4349551/Terri+Clark.jpg';
  2.  
  3. $handle = fopen($url, 'r');
  4.  
  5. $meta_data = stream_get_meta_data($handle);
  6.  
  7. // Repassa todos os headers do servidor remoto para o nosso cliente
  8. foreach ( $meta_data['wrapper_data'] as $header ) {
  9. header($header);
  10. }
  11.  
  12. // Repassa o conteúdo para o nosso cliente
  13. fpassthru($handle);
$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:

Código: Alternar entre o modo de cópia/destaque
  1. echo 'Amanhã: ', strftime('%A', strtotime('tomorrow'));
  2. // Amanhã: domingo
  3.  
  4. echo 'Próxima segunda: ', strftime('%d de %B de %Y', strtotime('next monday'));
  5. // Próxima segunda: 01 de junho de 2009
  6.  
  7. echo 'Vencimento: ', strftime('%d/%m/%Y', strtotime('+3 months'));
  8. // Vencimento: 30/08/2009
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:

Código: Alternar entre o modo de cópia/destaque
  1. $conexao = new PDO('mysql:dbname=banco;host=localhost', 'login', 'senha');
  2.  
  3. $uf = 'RJ';
  4. $idade = 18;
  5.  
  6. $sth = $conexao->prepare('SELECT nome FROM pessoa WHERE uf = ? AND idade > ?');
  7.  
  8. $sth->execute(array($uf, $idade));
  9.  
  10. while ( $row = $sth->fetch() ) {
  11. echo $row['nome'];
  12. }
$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.

Dim dim

 

Comentaí! (13)

cara se precisa colocar um link para twittar deus post! esse ai tem um link gigante!!! anselmo battisti em 15/03/2010 às 10:54 utilizando o Mozilla Firefox Mozilla Firefox 3.5.8 no Ubuntu Linux Ubuntu Linux
Ei... show de bola esse artigo, realmente são coisas "simples" que normalmente muita gente complica :) @xorna em 15/03/2010 às 11:10 utilizando o Google Chrome Google Chrome 5.0.307.11 no Linux Linux
Muito bom! Tem coisas realmente uteis aí. Valeu! Rodrigo Waltenberg em 15/03/2010 às 11:33 utilizando o Safari Safari 530.17 no Mac OS Mac OS X
Ao contrário do que o detector automatico acha, eu to usando o Dolphin Browser e o SO é Android :P Rodrigo Waltenberg em 15/03/2010 às 11:37 utilizando o Safari Safari 530.17 no Mac OS Mac OS X
O DirectoryIterator eu não manjava.
E o file_get_content então ... acionei a CURL exatamente pela necessidade de fazer post.

Vou mexer no código já !!! =)

Tks Roberto Almeida Longhi em 15/03/2010 às 13:33 utilizando o PHP PHP
Várias coisas que eu ainda não sabia! Muito boas as dicas! Julio Bitencourt em 15/03/2010 às 13:58 utilizando o Mozilla Firefox Mozilla Firefox 3.6 no Mac OS Mac OS X
Parabéns pelo post :)

Excelente mesmo.

Abraços ;] Francisco Souza em 16/03/2010 às 09:14 utilizando o Mozilla Firefox Mozilla Firefox 3.6 no Windows Windows XP
Ótimo post

Alguns não conhecia, está de parabéns. Leonel Schaefer em 16/03/2010 às 12:00 utilizando o Google Chrome Google Chrome 4.0.249.89 no Windows Windows NT
Muito bom o artigo! Eu acrecentaria mais coisas aim que muita gente complica, quando pode ser simples, mas já deu para acender a chama da curiosidade e vermos se não caimos em algumas dessas armadilhas! Mingo Max em 17/03/2010 às 12:49 utilizando o Google Chrome Google Chrome 4.0.249.30 no Linux Linux
Outra coisa:

$a = $a + 1; igual $a++; Wanderceslau em 07/04/2010 às 17:22 utilizando o Mozilla Firefox Mozilla Firefox 3.6.3 no Linux Linux
Parabéns aí pelo Post! Que bom que você voltou a escrever. E só uma dúvida a classe DirectoryIterator já vem por padrão? Caso sim, a partir de qual versão do PHP? vlw xD Eduardo Dx em 11/04/2010 às 22:27 utilizando o Mozilla Firefox Mozilla Firefox 3.6.3 no Linux Linux
Ahh... quanto menor o conhecimento, mais se tenta reinventar a roda, normal... bom post, conteúdo relevante de verdade. :D RobsonB em 21/05/2010 às 13:52 utilizando o Mozilla Firefox Mozilla Firefox 3.6.3 no Windows Windows NT
@Eduardo Dx

Olha, rapaz...

A classe é padrão sim, mas não tenho certeza de quando foi implementada...
Eu sei que tem alguns métodos dela como o getBasename() que só está disponível a partir do 5.2.2.

Valeu! :) Rafael Jaques em 31/07/2010 às 11:42 utilizando o Mozilla Firefox Mozilla Firefox 3.6.8 no Mac OS Mac OS X
 

Comenta logo, pô!

* Todos os links inseridos nos comentários possuem rel="nofollow" para impedir com que crawlers considerem os mesmos como relevantes.
* Os e-mails não são divulgados.

Dados pessoais

Você é realmente um humano?

Finalmente, digite seu comentário :)

Caixinha de Sugestões

O que é isso? Aqui você pode simplesmente digitar uma sugestão (artigos, resenhas, melhorias, etc) sem precisar preencher longos formulários. Digite o que quiser na caixinha abaixo e eu lerei com o maior prazer! Se quiser se identificar, fique a vontade!

O PHPit é redigido e mantido por Rafael Jaques - Política de Privacidade.

XHTML/CSS desenvolvido por André Gazola.

PHPit 2007 - 2009 - Alguns direitos reservados.

Feeds ;)

Creative Commons License

PHPit por Rafael Jaques é licensiado sob a Creative Commons Atribuição-Uso Não-Comercial-Compartilhamento pela mesma Licença 2.5 Brasil License.