Melhores práticas no PHP

Prefácio

Salve, galerinha! Tudo tranquilo?

Primeiramente eu gostaria de pedir desculpas pela demora em escrever um novo artigo, porém, com essas paradas de final de ano, acabei tirando tempo para ler… E li bastante! E é por isso que lhes trago uma coletânea bacana de listinhas, que eu traduzi, com dicas rápidas para você melhorar o desempenho de seus códigos e até alguns modos de ferrar tudo congelar o PHP.

Nada melhor para começar o ano do que um artigo gostoso de ler! :P

12 dicas de otimização

Fonte: http://www.moskalyuk.com/blog/php-optimization-tips/1272

1. Se um método pode ser static, declare-o como static! O desempenho aumenta 4 vezes

2. Evite utilizar metódos mágicos como __get, __set e __autoload, se possível

3. require_once() é dispensável e demanda bastante memória

4. Use caminhos completos (full path) nos includes e requires, pois é gasto menos tempo resolvendo os caminhos do sistema operacional

5. Se você quer descobrir o tempo em que o script começou a ser executado, é preferível utilizar $_SERVER[‘REQUEST_TIME’] do que time()

6. Veja se pode utilizar strncasecmp, strpbrk e stripos ao invés de regex

7. str_replace é mais rápido que preg_replace, mas strtr é 4 vezes mais rápido que str_replace

8. Se uma função, como de substituição de strings, aceitar tanto arrays como caracteres simples como argumentos, e se a sua lista de argumentos não é muito longa, considere fazer algumas declarações redundantes de substituição, passando um caractere por vez, ao invés de uma linha de código que aceita arrays como argumentos de busca e substituição

9. Supressão de rro com @ é muito lento

10. $row[‘id’] é 7 vezes mais rápido que $row[id]

11. Mensagens de erro demandam mais processamento

12. Não utilize funções dentro de loops, como por exemplo: for ($x=0; $x < count($array); $x). A função count() é chamada todas as vezes que o loop é executado.

10 coisas que (provavelmente) não sabia sobre o PHP

http://blog.rightbrainnetworks.com/2006/09/18/10-things-you-probably-didnt-know-about-php/

1. Utilize ip2long() e long2ip() para armazenar endereços de IP como integers ao invés de strings em um banco de dados. Isto poderá reduzir o espaço ocupado por armazenamento em até quatro vezes (15 bytes de um char(15) contra 4 bytes de um int), tornará mais fácil de calcular intervalos de endereços e aumentar a velocidade de busca e ordenação

2. É possível validar parcialmente um e-mail checando se o nome do domínio existe utilizando checkdnsrr(). Esta função integrada verifica se um determinado nome de domínio resolve com um endereço IP. Uma simples função criada pelo usuário que utiliza checkdnsrr() para fazer uma verificação parcial de e-mail pode ser encontrada nos comentários da documentação do PHP. Isso é interessante para resolver aqueles problemas de pessoas que acham que seu email é “usuario@wwwphp.net” ao invés de “joeuser@php.net”

3. Se você estiver usando PHP 5 com MySQL 4.1 ou acima, considere deixar de lado as funções mysql_* e começar a usar as melhoradas funções mysqli_*. Uma coisa legal é que você pode usar declarações previamente feitas para aumentar a velocidade das consultas, se você administra um site com grande acesso ao banco de dados. É possível também fazer alguns benchmarks.

4. Aprenda a amar o operador ternário

5. Se você estiver sentindo que está reinventando a roda durante um projeto, dê uma olhada na biblioteca PEAR antes de escrever mais uma linha. PEAR é um recurso que muitos desenvolvedores PHP conhecem, mas também há muitos que não. É um repositório online contendo certa de 400 códigos reutilizáveis que podem ser jogandos diretamente pra dentro de sua aplicação PHP. A menos que seu projeto seja extremamente único, você pode acabar encontrando um pacote PEAR que irá lhe poupar, pelo menos, um tempinho de desenvolvimento. (Veja também PECL)

6. Imprima uma cópia do código-fonte das páginas formatado devidamente com highlight_file(). Esta função pode ser útil quando você precisa pedir ajuda com um script em um fórum, canal IRC, etc. E é claro que você deve tomar cuidado para não acabar revelando acidentalmente o código quando o script contém informações de conexão ao banco de dados, senhas, etc

7. Previna potenciais mensagens de erro que contenham informações sensíveis utilizando a função error_reporting(0). O ideal é que o “error reporting” seja completamente desabilitado em um sistema em produção de dentro do php.ini. De qualquer forma, se você estiver em um host compartilhado e não tem a possibilidade de ter seu próprio php.ini, então sua melhor aposta é adicionar error_reporting(0); como a primeira linha do seu código em cada script (ou utiliza-lo como include). Isso irá previnir que possíveis consultas SQL e caminhos de diretórios apareçam se der pau em alguma coisa.

8. Utilize gzcompress() e gzuncompress() para comprimir/descomprimir grandes strings transparentemente antes de gravá-las em um banco de dados. Estas funções integradas utilizam o algorítimo gzip e podem comprimir texto plano (plaintext) em até 90%. Eu uso estas funções quase sempre que leio/escrevo em um campo BLOB de dentro do PHP. A única exceção é quando eu preciso de indexação por “full text”

9. Retorne múltiplos valores de uma função utilizando parâmetros “por referência”. Como o operador ternário, grande parte dos desenvolvedores PHP que vêm de uma linguagem de programação mais formal já conhecem. De qualquer forma, aqueles que vêm de uma linguagem mais pra HTML do que Pascal, provavelmente já devem ter se perguntado alguma vez: “Como eu retorno múltiplos valores de uma função que escrevi, mesmo podendo retornar apenas um valor pelo return?”. A resposta é que você deve preceder a variável com “&” (“E” comercial) e usá-la “por referência” ao invés de “por valor”.

10. Entenda as “aspas mágicas” (magic quotes) e os perigos da SQL Injection. Eu espero que a maioria dos desenvolvedores que esta lendo isto já seja familiar com SQL injection. De qualquer forma eu listo aqui porque é absolutamente imprescindível saber. Se você nunca ouviu este termo antes, gaste o resto do dia googleando e lendo! ;)

Os 10 modos mais fáceis de travar o PHP

Fonte: http://ilia.ws/archives/5_Top_10_ways_to_crash_PHP.html

Nota do Rafael: Este artigo é de 2004. Algumas das técnicas comentadas aqui podem não mais funcionar.

Sobrecarga de pilha: O PHP não possui nenhuma proteção interna de pilha preferindo então deixar a cargo do sistema, sem nenhuma proteção. Isto significa que se você tiver uma função recursiva ou um método, o PHP irá eventualmente travar!

<?php
    function a() {
        a();
    }     

    a();
?>

Há duas soluções para este problema. A primeira é evitar utilizar funções recursivas que, geralmente, são uma má idéia, e se você REALMENTE PRECISAR utilizá-las, use algum contador utilizando variáveis globais que irão prevenir que a função itere sobre ela mesma mais do que um número X de vezes, tendo em vista que valores para X podem estar entre 500 e 1000. A outra solução envolve utilizar a extensão xdebug que implementa proteção contra sobrecargas de pilha, definindo um limite do quão profundo uma recursão pode ir através de um valor no php.ini. Esta é a melhor solução em hosts onde você não tem nenhum controle sobre os scripts que estão sendo rodados.

Bug dentro da função pack() Esta não é uma função muito comum, mas de qualquer forma faz parte da distribuição padrão do PHP significando que mesmo que seja raro o seu uso, todos a têm e estão vulneráveis a ela. A natureza do “crash” consiste em fazer com que o PHP tente alocar mais memória do que geralmente é disponível no sistema. Quando isto acontece, o PHP irá fechar automaticamente, terminando a requisição ou se você estiver usando algum debugger irá lançar um erro e depois dar pau.

<?php

	pack("d4294967297", 2);

?>

A única solução para este problema é desabilitar a função pack através da diretiva disable_functions no php.ini e esperar que nenhum dos seus scripts ou algum script de seus usuários (servidor de hospedagem) utilizem esta funcionalidade. Como eu mencionei anteriormente, esta função é raramente utilizada, então é uma solução mais ou menos válida. Outra solução é habilitar a diretiva memory_limit que irá configurar o limit do máximode memória que os scripts PHP poderam utilizar e quando tentar utilizar mais do que isso, o PHP irá gentilmente encerrar a requisição e não irá encerrar nenhum processo utilizado na requisição.

Vulnerabilidades na interpretação de vários cabeçalhos.

Existe uma série de extensões PHP que possuem a habilidade de trabalhar com imagens. Estas extensões incluem: exif, GD, imagemagick e a função padrão getimagesize(). Para trabalhar com imagens, algumas vezes é necessário alocar um número muito grande de dados no buffer para poder fazer a armazenagem. O tamanho desse buffer é definido através da interpretação do header da imagem que contém este dado. Modificando este dado utilizando um editor hexadecimal ou geranto um cabeçalho de imagem falso, você pode forçar o PHP, através desta extensão, a alocar mais memória do que é possível ou tentar alocar um valor de memória inválido, como -1, resultando em um crash.

Nenhum exemplo será dado tento em vista que tornará muito fácil explorar estas falhas de segurança, principalmente hoje, quando se encontram diversos servidores que se valem destas funções.

Na maioria dos casos há checagens suficientes para prevenir alocação de memória inválida como -1, então o problema mais comum seria o problema de sobrealocação idêntico ao do segundo ítem. Então a única solução, fora desabilitar estas extensões (boa solução se você não as usa) é novamente recorrer ao memory_limit para previnir que o PHP tente alocar mais memória do que poode ou do que deveria.

Sobrecarga de pilha na biblioteca GD está exposta através da função imagefilltoborder()

Esta função baseia-se em uma chamada de função recursiva que dado um certo tipo de imagem resulta em uma sobrecarga de pilha.
Você pode enxontrar alguns exemplos de crash dentro do teste /etc/gd/tests/bug27582_2.phpt.

A solução para este problema seria reescrever a função imagefilltoborder() da biblioteca GD de um modo que a recursividade não fosse necessária. Por enquanto o único jeito de resolver este problema é utilizar a diretiva disable_functions para previnir o acesso à esta função.

Sobrealocação forçada dentro da função unserialize()

<?php 

	$a = "a";
	unserialize(str_replace('1', 2147483647, serialize($a)));

?>

Assim podemos “forçar” o PHP a tentar alocar mais memória do que ele pode, resultando em um término de requisição. A solução, novamente, é utilizar o memory_limit() para permitir que o PHP trate esta situação seguramente.

Sobrecarga de Refcount

Na ZendEngine 1, o número de referências á uma variável em particular é limitada a 65535 porque elas são armazenadas dentro de um “unsigned short”. Foi algo consertado na ZendEngine 2, onde este valor é armazenado dentro de uma integer com uma capacidade muito maior. Entretanto, como as versões anteriores do PHP são desenvolvidas em cima da ZendEngine 1, significa que qualquer PHP 4.X é vulnerável a este problema e a pior parte é que não existe nenhuma solução fácil.

<?php 

	$t = array(1);
	while (1) {
		$a[] = &$t;
	}

?>

O único modo de evitar este problema no PHP4 é aplicar o seguinte patch e recompilar seu PHP. Se você estiver utilizando qualquer módulo Zend ou PHP externos, terão que ser recompilados também. Você só não poderá fazer isso se você estiver usando os módulos binários (usuários Windows), uma vez que você não possui acesso ao código fonte.

      Index: zend.h
      ====================================
      RCS file: /repository/Zend/Attic/zend.h,v
      retrieving revision 1.164.2.21
      diff -u -3 -p -r1.164.2.21 zend.h
      --- zend.h      16 Mar 2004 17:36:17 -0000      1.164.2.21
      +++ zend.h      14 Apr 2004 18:01:37 -0000
      @@ -263,7 +263,7 @@ struct _zval_struct {
              zvalue_value value;             /* value */
              zend_uchar type;        /* active type */
              zend_uchar is_ref;
      -       zend_ushort refcount;
      +       int refcount;
       };

Uma vez que o último crash foi especificamente para o PHP4, vamos especificar um para o PHP5 para deixar as coisas legais

Como você deve saber, uma série de extensões do PHP5 (SQlite, Tidy, etc) implementa interface nativa orientada a objetos, o que é muito conveniente, mas há um problema. Devido a ordem destrutiva dos módulos carregaveis externamente devido a função dl() (que está depreciada, diga-se de passagem), o módulo será liberado após os objetos criados por aquele módulo serem liberados. O resultado é que quando o PHP tenta liberar um objeto, as funções de “libertação” não estão mais disponíveis (elas estavam dentro do módulo) e aí dá pau no PHP.

<?php

	dl("sqlite.so");
	$db = new SqliteDatabase("foo");

?>

A solução para este problema é ou desabilitar a função dl() (porque pessoas não devem usar desse jeito, é melhor carregar as extensões via php.ini) ou então assegurar-se de destruir qualquer objeto criado por esta extensão manualmente utilizando unset().

Bug na biblioteca PCRE

Embora não seja um bug no PHP propriemante dito, ainda sim pode fazer com que dê pau no PHP.

<?php 

	preg_match('/(.(?!b))*/', str_repeat("a", 10000));

?>

Não há solução para este bug atualmente (Nota do tradutor: lembre que este artigo é de 2004), é minha esperança que os desenvolvedores do PCRE lembrem deste problema e implementem uma correção na próxima release.

De volta ao nosso velho favorito: Alocação de memória

Dessa vez a função str_repeat() permite-nos muito facilmente fazer com que o PHP aloque memória excessiva e trave ou caia fora quando falhar.

<?php 

	str_repeat("a", 10000000000);

?>

A solução, de novo, é usar memory_limit() para que o PHP possa se safar desta!

O último jeito não é exatamente um crash, mas ainda assim dá pra esculhambar com tudo em um servidor Apache

<?php 

	shell_exec("killall -11 httpd");

?>

Como você pode imaginar, este pequeno comando irá resultar no encerramento de todos os processos filhos do apache que não o daemon, que está rodando como root. Uma vez que todos os processos filhos do Apache rodam sob os mesmos privilégios, um processo filho pode matar todo o resto. Há diversas proteções contra isso. O primeiro de tudo é verificar se a variável PATH do seu webserver não contém nenhum caminho perigoso, como algum que contenha os comandos kill e killall. Você pode também desabilitar a execução de comandos através do safe_mode ou utilizando a diretiva disable_functions. A última solução é rodar o PHP como CGI ou FastCGI, onde os processos rodam sob o mesmo usuário que está executando o script. Isto significa que o script PHP não terá permissões que lhe dêem o direito de matar processos filhos do Apache.

E existem tantos outros diversos modos de quebrar tudo no PHP, alguns já foram corrigidos e outros podem estar ainda presentes. De qualquer forma, a maioria deles tende a ser em extensões obscuras que a maioria das pessoas não possui habilitada ou então não utiliza.

Conclusão

Espero que vocês tenham gostado destes artigos legais que eu traduzi (acreditem, demorei pra traduzir porque estou realmente sem tempo) e me perdoem por eu ter demorado tanto a atualizar o site.

Ficarei honrado em receber críticas e sugestões por e-mail!

Um grande abraço à todos e que Deus abençoe todos vocês neste novo ano que se inicia!
Até a próxima! ;)

Rafael Jaques

  • PQP Cara.. lendo isso me senti um baita programador kkkk… levando em conta que me preocupo com no mínimo 60 ou 70% disso tudo até eu ler esse post… prometo ser um programador comportado em 2008 =)..

    Parabéns mesmo..

    abraço

  • poderia me explicar melhor a função strpbrk()?

    vejo que vc é um PHP expert

  • Wilian…

    Obrigado, mas estou longe de ser um PHP expert!

    Quanto à sua dúvida:

    A idéia do strpbrk é retornar uma string a partir de um dos caracteres passados.

    O primeiro parâmetro é a string onde será feita a busca. O segundo é uma string contendo uma cadeia de caracteres que deverão ser buscados na primeira string.

    <?php

    $texto = 'Texto para teStes';

    print strpbrk($texto, 'oe'); // irá mostrar "exto para teStes", porque a letre "e" é encontrada primeiro

    print strpbrk($texto, 'S'); // irá mostrar "Stes", porque a função é case sensitive

    ?>

    Espero ter esclarecido a sua dúvida! :)

    Abraços!

  • blz, saquei a idéia da strpbrk(), pois não tinha entendido mto bem no php.net, valew fera

  • RomanMG

    Confesso que fiquei muito tempo sem visitar seu site, mas cada vez que venho tem algo útil que me ajuda.

    Grande abraço Rafa !!

  • Luiz Henrique

    Cara um site muito legal, conteúdo de 1ª! Parabéns pelo trabalho =)

  • Rafael

    Òtimo site, obrigado pelas dicas.

  • Laérciokelson

    Por quê require_once() demanda bastante memória?
    Nesse caso o que deve ser usado?

    • O require_once() vai fazer verificações para determinar se o arquivo já foi incluído anteriormente.
      Nesse caso é melhor utilizar apenas o require(), pois economiza processamento.

  • paulo robson

    belo post. parabens

  • Estou procurando um exemplo de formulário com linhas com checkbox….aquelas variaveis da linha com checkbox setado seriam atualizadas em um banco de dados e as outras linhas seriam ignoradas. Vc conhece?