Tem muita informação velha na web que desvia quem está começando em PHP, espalhando má prática e código inseguro. PHP: The Right Way é uma referência rápida: padrões de código em uso, links para tutoriais autoritativos e o que os colaboradores consideram boa prática hoje.
Não existe um jeito canônico de usar PHP. Este site apresenta a quem está começando temas que muita gente só encontra tarde demais. Também serve para quem já trabalha com PHP há anos e nunca parou para rever o hábito. Não dizemos qual ferramenta você deve usar. Quando dá, sugerimos mais de uma opção e explicamos a diferença de abordagem e de caso de uso.
Este é um documento vivo. Vamos atualizar com mais informação e exemplos conforme aparecerem.
Esta edição em português brasileiro não é uma tradução oficial. Foi traduzida por IA. A oficial está em br.phptherightway.com.
PHP: The Right Way existe em vários idiomas:
A versão mais recente de PHP: The Right Way também sai em PDF, EPUB e MOBI. Ir para o Leanpub
Ajude este site a ser o melhor ponto de partida para quem está começando em PHP. Contribua no GitHub
Se você está nos primeiros passos com PHP, comece pela versão estável atual: PHP 8.5. O PHP 8.x traz muitas novidades em relação às 7.x e 5.x. O motor foi praticamente reescrito e o PHP ficou mais rápido. O PHP 8 é uma atualização grande da linguagem, com recursos novos e otimizações.
Atualize logo para a última versão estável. O PHP 7.4 já está em fim de vida. A migração é simples: há poucas quebras de compatibilidade. Veja PHP 8.0, PHP 8.1, PHP 8.2, PHP 8.3, PHP 8.4, PHP 8.5. Se você não sabe em qual versão está uma função ou recurso, consulte a documentação no php.net.
A partir do PHP 5.4, você já pode aprender PHP sem instalar e configurar um servidor web completo. Para subir o servidor, rode este comando no terminal, na raiz web do projeto:
> php -S localhost:8000O macOS 12 (Monterey) em diante não traz PHP. Versões mais antigas incluem PHP, mas ficam atrás da última estável. Dá para instalar o PHP atual no macOS de várias formas.
Homebrew é um gerenciador de pacotes para macOS. Com ele você instala PHP e extensões sem complicação. O Homebrew core tem “formulae” para PHP 8.1, 8.2, 8.3, 8.4 e 8.5. Instale a versão mais recente com este comando:
brew install php
Para trocar de versão do PHP do Homebrew, altere a variável PATH. Ou use o brew-php-switcher para trocar automaticamente.
Você também pode trocar de versão na mão, com unlink e link da versão desejada:
brew unlink php
brew link --overwrite php@8.2
brew unlink php
brew link --overwrite php@8.3
O projeto MacPorts é uma iniciativa de código aberto da comunidade para compilar, instalar e atualizar software de código aberto no macOS — de linha de comando, X11 ou Aqua.
O MacPorts oferece binários pré-compilados. Você não precisa recompilar cada dependência a partir do tarball. Isso ajuda muito se o sistema ainda não tem pacote nenhum.
Daqui você instala php54, php55, php56, php70, php71, php72, php73, php74, php80, php81, php82, php83 ou php84 com port install. Por exemplo:
sudo port install php74
sudo port install php83
Use o comando select para trocar o PHP ativo:
sudo port select --set php php83
phpbrew instala e gerencia várias versões de PHP. Útil quando dois apps ou projetos pedem versões diferentes e você não usa máquina virtual.
Outra opção comum é o php-osx.liip.ch, com instalação em uma linha para as versões 5.3 até 7.3.
Ele não sobrescreve o PHP da Apple: instala tudo em outro lugar (/usr/local/php5).
Se quiser controlar a versão, compile o PHP você mesmo. Nesse caso, instale o Xcode ou o substituto da Apple, “Command Line Tools for XCode”, no Apple Developer Center.
As opções acima cuidam sobretudo do PHP. Não trazem Apache, Nginx nem um servidor SQL. Pacotes “all-in-one” como MAMP e XAMPP instalam o resto e amarram tudo. A instalação fica fácil, mas você perde flexibilidade.
Baixe os binários na página de downloads do php.net. Depois de extrair o PHP, coloque a pasta raiz (onde está o php.exe) no PATH para rodar PHP de qualquer lugar.
Para aprender e desenvolver localmente, use o servidor web embutido no PHP 5.4+ e pule a configuração de um servidor completo. Se quiser um pacote completo com servidor web e MySQL, EasyPHP, OpenServer ou WampServer montam um ambiente de desenvolvimento no Windows rápido. Esses pacotes diferem da produção: cuidado com as diferenças se você desenvolve no Windows e faz deploy no Linux.
Rodar o app em ambientes diferentes no desenvolvimento e na produção costuma gerar bugs estranhos na hora de ir ao ar. Se você desenvolve no Windows e faz deploy no Linux (ou em qualquer sistema que não seja Windows), use uma máquina virtual ou o Windows Subsystem for Linux (WSL).
O Chris Tankersley tem um post útil sobre as ferramentas que ele usa para desenvolver PHP no Windows.
A maioria das distribuições GNU/Linux já traz PHP nos repositórios oficiais, mas esses pacotes costumam ficar um pouco atrás da versão estável atual. Há várias formas de instalar um PHP mais novo.
No Ubuntu e em distribuições GNU/Linux baseadas em Debian, as melhores alternativas aos pacotes nativos vêm do Ondřej Surý: PPA (Personal Package Archive) no Ubuntu e DPA/bikeshed no Debian. As instruções de cada um estão abaixo.
No Ubuntu, o PPA do Ondřej Surý oferece as versões suportadas do PHP e várias extensões PECL. Para adicionar esse PPA, rode no terminal:
Adicione o PPA às fontes de software:
sudo add-apt-repository ppa:ondrej/php
Atualize a lista de pacotes:
sudo apt update
Assim o sistema passa a enxergar e instalar os pacotes PHP mais recentes do PPA.
Nas distribuições baseadas em Debian, o Ondřej Surý também mantém um bikeshed (o equivalente ao PPA). Para adicionar e atualizar:
Você precisa de acesso root. Sem isso, use sudo nos comandos abaixo.
Atualize a lista de pacotes:
sudo apt-get update
Instale lsb-release, ca-certificates e curl:
sudo apt-get -y install lsb-release ca-certificates curl
Baixe a chave de assinatura do repositório:
sudo curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg
Adicione o repositório às fontes de software:
sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list'
Atualize de novo a lista de pacotes:
sudo apt-get update
Pronto: o sistema consegue instalar os pacotes PHP mais recentes do bikeshed.
Em distribuições baseadas em RPM (CentOS, Fedora, RHEL etc.), use o repositório RPM do Remi para instalar a versão mais recente do PHP ou manter várias versões ao mesmo tempo.
Há um assistente de configuração para ajustar a distribuição.
Fora isso, você sempre pode usar containers ou compilar o PHP a partir do código-fonte.
Quem começa a programar para a web pergunta: “onde eu coloco os arquivos?”. Há anos a resposta é “no DocumentRoot”. Não cobre tudo, mas é um bom começo.
Por segurança, o visitante não pode acessar arquivos de configuração. Scripts públicos ficam num diretório público; configurações e dados privados ficam fora dele.
Cada time, CMS ou framework já tem uma estrutura de diretórios. Se você começa um projeto sozinho, escolher essa estrutura é difícil.
Paul M. Jones pesquisou as práticas de dezenas de milhares de projetos PHP no GitHub e, com isso, montou uma estrutura padrão de arquivos e diretórios: o Standard PHP Package Skeleton. Nela, o DocumentRoot aponta para public/, os testes unitários ficam em tests/ e as bibliotecas de terceiros, instaladas pelo Composer, ficam em vendor/. Para o resto, seguir o Standard PHP Package Skeleton é o que mais faz sentido para quem contribui no projeto.
A comunidade PHP é grande e diversa: bibliotecas, frameworks e componentes sem conta. É comum juntar vários deles num único projeto. Por isso o código PHP precisa seguir, o mais perto possível, um estilo de código comum. Assim fica fácil misturar bibliotecas no mesmo projeto.
O Framework Interop Group propôs e aprovou uma série de recomendações de estilo. Nem todas falam de estilo de código. As que falam são PSR-1, PSR-12, PSR-4 e PER Coding Style. São só um conjunto de regras. Projetos como Drupal, Zend, Symfony, Laravel, CakePHP, phpBB, AWS SDK, FuelPHP, Lithium e outros já adotam. Você pode usar no seu projeto ou manter o seu próprio estilo.
O ideal é escrever PHP num padrão conhecido. Pode ser uma combinação de PSRs, ou o padrão do PEAR ou do Zend. Assim outros desenvolvedores leem e trabalham no seu código sem atrito. E o app que usa esses componentes fica consistente, mesmo com muito código de terceiros.
Você pode usar o PHP_CodeSniffer para checar o código contra qualquer uma dessas recomendações. Plugins para editores como o Sublime Text dão feedback em tempo real.
Você também pode corrigir o layout do código automaticamente com uma destas ferramentas:
E você pode rodar o phpcs na mão, no terminal:
phpcs -sw --standard=PSR1 file.php
Ele mostra os erros e descreve como corrigir.
Também vale incluir o comando phpcs num hook pre-commit do git, com o argumento
--filter=GitStaged. Assim, código que viola o padrão escolhido não entra no repositório até
você corrigir.
Se você já tem o PHP_CodeSniffer, dá para corrigir automaticamente os problemas de layout que ele aponta com o PHP Code Beautifier and Fixer.
phpcbf -w --standard=PSR1 file.php
Outra opção é o PHP Coding Standards Fixer. Ele mostra que tipo de erro a estrutura do código tinha antes de corrigir.
php-cs-fixer fix -v --rules=@PSR1 file.php
Prefira inglês nos nomes de símbolos e na infraestrutura do código. Comentários podem ficar em qualquer idioma que quem trabalha no código hoje — e quem for trabalhar depois — consiga ler sem esforço.
Por fim, um bom recurso extra para escrever PHP limpo é o Clean Code PHP.
PHP é uma linguagem flexível e dinâmica. Cobre várias técnicas de programação. Evoluiu bastante: modelo sólido de orientação a objetos (OOP) no PHP 5.0 (2004), funções anônimas e namespaces no PHP 5.3 (2009) e traits no PHP 5.4 (2012).
O PHP tem um conjunto bem completo de OOP: classes, classes abstratas, interfaces, herança, construtores, clonagem, exceções e mais.
O PHP tem funções de primeira classe: você atribui uma função a uma variável. Funções definidas por você e as nativas podem ser referenciadas por variável e invocadas dinamicamente. Você passa funções como argumento de outras (funções de ordem superior) e uma função pode devolver outra.
Recursão existe — a função chama a si mesma — mas a maior parte do código PHP usa iteração.
Funções anônimas (com suporte a closures) existem desde o PHP 5.3 (2009).
O PHP 5.4 passou a permitir vincular closures ao escopo de um objeto e melhorou o suporte a callables: quase sempre você usa um callable no lugar de uma função anônima.
call_user_func_array()O PHP cobre várias formas de metaprogramação, com a Reflection API e os métodos mágicos. Há vários — __get(), __set(), __clone(), __toString(), __invoke() e outros — para você interceptar o comportamento da classe. Quem vem de Ruby costuma dizer que falta method_missing no PHP. Está em __call() e __callStatic().
A comunidade PHP tem muita gente produzindo muito código. Uma biblioteca pode usar o mesmo nome de classe que outra. Se as duas entram no mesmo namespace, colidem e dão problema.
Namespaces resolvem isso. O manual do PHP compara namespace a diretórios do sistema operacional: dois arquivos com o mesmo nome convivem em pastas diferentes. Do mesmo jeito, duas classes PHP com o mesmo nome convivem em namespaces PHP diferentes. É só isso.
Coloque o seu código em namespace. Assim outros desenvolvedores usam sem medo de colidir com outras bibliotecas.
Uma forma recomendada está no PSR-4: convenção padrão de arquivo, classe e namespace para o código encaixar sem adaptação.
Em outubro de 2014, o PHP-FIG marcou como obsoleto o padrão anterior de autoloading (autoload): PSR-0. PSR-0 e PSR-4 ainda funcionam. O PSR-4 exige PHP 5.3, então muitos projetos só em PHP 5.2 implementam PSR-0.
Se você vai usar um padrão de autoload em um app ou pacote novo, escolha PSR-4.
A SPL (biblioteca padrão do PHP) já vem no PHP: um conjunto de classes e interfaces. Em geral, estruturas de dados comuns (stack, queue, heap etc.) e iterators que percorrem essas estruturas — ou classes suas que implementam as interfaces da SPL.
O PHP nasceu para escrever apps web, mas também serve para scripts de interface de linha de comando (CLI). Programas PHP de CLI ajudam a automatizar tarefas comuns como testes, deploy e administração do app.
Scripts PHP de CLI são poderosos: você usa o código do app direto, sem criar e proteger uma interface web. Só não coloque esses scripts na pasta pública do site!
Experimente rodar o PHP no terminal:
> php -iA opção -i imprime a configuração do PHP, como a função phpinfo().
A opção -a abre um shell interativo, no estilo do IRB do Ruby ou do shell interativo do Python. Tem outras
opções de linha de comando úteis também.
Vamos escrever um programa CLI simples de “Olá, $name”. Para testar, crie um arquivo hello.php assim.
<?php
if ($argc !== 2) {
echo "Uso: php hello.php <nome>" . PHP_EOL;
exit(1);
}
$name = $argv[1];
echo "Olá, $name" . PHP_EOL;O PHP monta duas variáveis especiais a partir dos argumentos passados ao script. $argc é um inteiro
com a contagem de argumentos e $argv é um array com o valor de cada um.
O primeiro argumento é sempre o nome do arquivo do script, neste caso hello.php.
Use exit() com um número diferente de zero para o shell saber que o comando falhou. Códigos de saída comuns
estão aqui.
Para rodar o script acima no terminal:
> php hello.php
Uso: php hello.php <nome>
> php hello.php mundo
Olá, mundoUm dos recursos mais úteis no desenvolvimento é um debugger de verdade. Com ele você acompanha a execução do código e vê o que está na stack. O Xdebug, debugger do PHP, funciona com várias IDEs para breakpoints e inspeção da stack. Também deixa o PHPUnit e o KCacheGrind fazerem análise de cobertura e profiling.
Se você está travado, já recorreu a var_dump()/print_r() e ainda não achou a solução — talvez seja hora de usar o debugger.
Instalar o Xdebug pode ser chato, mas um dos recursos mais importantes é o Remote Debugging. Se você desenvolve localmente e testa numa VM ou em outro servidor, ative o Remote Debugging logo de cara.
O jeito clássico é colocar estes valores no VHost do Apache ou no .htaccess:
php_value xdebug.remote_host 192.168.?.?
php_value xdebug.remote_port 9000O remote host e a remote port são o seu computador e a porta em que a IDE escuta. Depois é só colocar a IDE em modo de escuta e abrir a URL:
http://seu-site.example.com/index.php?XDEBUG_SESSION_START=1
A IDE intercepta o estado atual enquanto o script roda. Você define breakpoints e inspeciona os valores na memória.
Debuggers gráficos facilitam percorrer o código, inspecionar variáveis e avaliar expressões no runtime ao vivo. Muitas IDEs já vêm com suporte nativo ou via plugin para debug gráfico com Xdebug. O MacGDBp é uma GUI gratuita e open-source do Xdebug para macOS, independente de IDE.
Tem um monte de bibliotecas, frameworks e componentes PHP. Seu projeto provavelmente usa vários — essas são as dependências. O PHP não tinha um jeito bom de gerenciá-las. Mesmo cuidando delas na mão, você ainda precisava lidar com autoloading (autoload). Isso deixou de ser problema.
Hoje há dois gerenciadores de pacotes principais no PHP: Composer e PEAR. O Composer é o mais popular. Por muito tempo o PEAR foi o gerenciador padrão. Vale conhecer a história do PEAR: você ainda encontra referências a ele, mesmo sem nunca usá-lo.
Composer é o gerenciador de dependências recomendado para PHP. Liste as dependências do projeto num arquivo composer.json e, com poucos comandos, o Composer baixa tudo e configura o autoloading (autoload). É o equivalente ao NPM no node.js, ou ao Bundler no Ruby.
Tem muita biblioteca PHP compatível com Composer, pronta para usar no projeto. Esses pacotes estão no Packagist, o repositório oficial de bibliotecas PHP compatíveis com Composer.
O jeito mais seguro de baixar o Composer é seguindo as instruções oficiais.
Isso verifica se o instalador não está corrompido ou adulterado.
O instalador coloca o binário composer.phar no diretório de trabalho atual.
Recomendamos instalar o Composer globalmente (uma cópia só em /usr/local/bin). Para isso, rode este comando em seguida:
mv composer.phar /usr/local/bin/composerNota: Se o comando acima falhar por permissão, use sudo na frente.
Para rodar um Composer local, use php composer.phar. O global é só composer.
No Windows, o caminho mais fácil é o instalador ComposerSetup, que faz a instalação global e configura o $PATH para você chamar composer de qualquer diretório no terminal.
O Composer registra as dependências do projeto num arquivo composer.json. Você pode editar na mão ou deixar o próprio Composer cuidar. O comando composer require adiciona uma dependência e, se não existir composer.json, cria um. Este exemplo adiciona o Twig como dependência do projeto.
composer require twig/twig:^2.0Outra opção: composer init te guia na criação de um composer.json completo. De um jeito ou de outro, com o arquivo criado, peça ao Composer para baixar e instalar as dependências em vendor/. Vale também para projetos que você baixou e que já vêm com composer.json:
composer installDepois, acrescente esta linha no arquivo PHP principal do app. Assim o PHP usa o autoload do Composer para as dependências.
<?php
require 'vendor/autoload.php';Pronto. As dependências entram sob demanda via autoload.
O Composer cria um arquivo composer.lock com a versão exata de cada pacote baixado na primeira vez que você rodou composer install. Se você compartilha o projeto, inclua o composer.lock. Assim, quando outra pessoa rodar composer install, recebe as mesmas versões. Para atualizar as dependências, rode composer update. No deploy, não use composer update — só composer install. Senão, produção pode ficar com versões diferentes.
Isso ajuda mais quando você define as versões de forma flexível. Por exemplo, ~1.8 significa “qualquer coisa mais nova que 1.8.0, mas abaixo de 2.0.x-dev”. Também dá para usar o curinga *, como em 1.8.*. Aí o composer update sobe todas as dependências para a versão mais nova que cabe nas restrições.
Para receber aviso de versões novas, cadastre-se no libraries.io, um serviço que monitora dependências e manda alerta de atualização.
O Local PHP Security Checker é uma ferramenta de linha de comando que examina o composer.lock e diz se alguma dependência precisa de atualização.
O Composer também gerencia dependências globais e os binários delas. O uso é direto: prefixe o comando com global. Para instalar o PHPUnit e tê-lo disponível em qualquer lugar, rode:
composer global require phpunit/phpunitIsso cria a pasta ~/.composer, onde ficam as dependências globais. Para os binários dos pacotes ficarem disponíveis em qualquer lugar, acrescente ~/.composer/vendor/bin à variável $PATH.
O PEAR é um gerenciador de pacotes veterano. Parte dos desenvolvedores PHP ainda usa. Funciona parecido com o Composer, mas tem diferenças importantes.
O PEAR exige uma estrutura específica em cada pacote. Quem publica precisa preparar o pacote para o PEAR. Sem isso, não dá para usar.
O PEAR instala pacotes de forma global. Depois de instalar uma vez, eles ficam disponíveis para todos os projetos daquele servidor. Ajuda se vários projetos usam o mesmo pacote na mesma versão. Vira problema se as versões entram em conflito.
Baixe o instalador .phar e execute. A documentação do PEAR tem instruções de instalação detalhadas para cada sistema.
No Linux, veja também o gerenciador de pacotes da distribuição. Debian e Ubuntu, por exemplo, têm o pacote apt php-pear.
Se o pacote está na lista de pacotes PEAR, instale pelo nome oficial:
pear install fooSe o pacote está em outro channel, faça discover no channel primeiro e informe o channel na instalação. Veja a documentação de channels.
Se você já usa Composer e quer instalar código PEAR também, o Composer cuida dessas dependências. O Composer 2 não aceita mais repositórios PEAR direto. Você precisa adicionar o repositório na mão para instalar pacotes PEAR:
{
"repositories": [
{
"type": "package",
"package": {
"name": "pear2/pear2-http-request",
"version": "2.5.1",
"dist": {
"url": "https://github.com/pear2/HTTP_Request/archive/refs/heads/master.zip",
"type": "zip"
}
}
}
],
"require": {
"pear2/pear2-http-request": "*"
},
"autoload": {
"psr-4": {"PEAR2\\HTTP\\": "vendor/pear2/pear2-http-request/src/HTTP/"}
}
}A seção "repositories" avisa o Composer para “inicializar” (ou “discover”, no jargão do PEAR) o repositório pear. A seção require prefixa o nome do pacote assim:
pear-channel/package
O prefixo “pear” é fixo para evitar conflito: um channel PEAR pode coincidir com o vendor de outro pacote. Depois vem o nome curto do channel (ou a URL completa), para indicar de qual channel o pacote sai.
Depois de instalado, o código fica no diretório vendor e entra no autoloader do Composer:
vendor/pear2/pear2-http-request/pear2/HTTP/Request.php
Para usar o pacote PEAR, referencie assim:
<?php
require __DIR__ . '/vendor/autoload.php';
use PEAR2\HTTP\Request;
$request = new Request();PHP é uma linguagem vasta. Em qualquer nível, você consegue escrever código rápido e eficiente. Conforme você avança, o básico que aprendeu (ou pulou) some. Sobram atalho e mau hábito. Esta seção lembra as práticas básicas de código em PHP.
O PHP tem a classe DateTime para ler, escrever, comparar e calcular data e hora. Há várias outras funções de data e hora além de DateTime, mas ela oferece uma interface orientada a objetos para o que você mais usa. DateTime lida com fusos horários; isso fica fora desta introdução.
Para começar, transforme a string bruta de data e hora em objeto com createFromFormat(), ou use new DateTime para
pegar agora. Use format() para devolver o DateTime como string.
<?php
$raw = '22. 11. 1968';
$start = DateTime::createFromFormat('d. m. Y', $raw);
echo 'Data inicial: ' . $start->format('Y-m-d') . PHP_EOL;Cálculos com DateTime usam a classe DateInterval. DateTime tem métodos como add() e sub() que recebem um
DateInterval. Não assuma que todo dia tem o mesmo número de segundos: horário de verão e mudança de fuso quebram isso.
Use intervalos. Para a diferença entre datas, diff() devolve um DateInterval novo, fácil de exibir.
<?php
// copia $start e soma um mês e 6 dias
$end = clone $start;
$end->add(new DateInterval('P1M6D'));
$diff = $end->diff($start);
echo 'Diferença: ' . $diff->format('%m mês, %d dias (total: %a dias)') . PHP_EOL;
// Diferença: 1 mês, 6 dias (total: 37 dias)Você pode comparar objetos DateTime com os operadores padrão:
<?php
if ($start < $end) {
echo "O início vem antes do fim!" . PHP_EOL;}Último exemplo: a classe DatePeriod itera eventos recorrentes. Você passa dois DateTime (início e fim) e o intervalo; ela devolve os eventos no meio.
<?php
// imprime todas as quintas entre $start e $end
$periodInterval = DateInterval::createFromDateString('first thursday');
$periodIterator = new DatePeriod($start, $periodInterval, $end, DatePeriod::EXCLUDE_START_DATE);
foreach ($periodIterator as $date) {
// imprime cada data do período
echo $date->format('Y-m-d') . ' ';
}Uma biblioteca popular é o Carbon. Herda tudo de DateTime, então a mudança de código é mínima. Em cima disso: localização, mais jeitos de somar, subtrair e formatar um DateTime, e um modo de testar o código simulando a data e hora que você quiser.
Ao montar o app, use padrões comuns no código e na estrutura do projeto. Isso facilita manter o código e deixa outros desenvolvedores entenderem rápido como as peças se encaixam.
Se você usa um framework, boa parte do código de alto nível e da estrutura do projeto já vem dele — muitas decisões de padrão já estão feitas. Ainda assim, cabe a você escolher os melhores padrões no código que você escreve em cima do framework. Sem framework, você precisa achar os padrões que combinam com o tipo e o tamanho do app.
Para aprender mais sobre padrões de projeto em PHP e ver exemplos funcionando:
Esta seção foi escrita originalmente por Alex Cabal em PHP Best Practices e serviu de base para o nosso guia de UTF-8.
O PHP ainda não tem suporte a Unicode em baixo nível. Dá para processar strings UTF-8 direito, mas não é fácil: você precisa cuidar de quase todas as camadas do app web, do HTML ao SQL e ao PHP. Aqui vai um resumo prático.
Operações básicas de string — concatenar e atribuir a variáveis — não pedem nada especial para UTF-8.
Já a maioria das funções de string, como strpos() e strlen(), exige cuidado. Elas costumam ter
um par mb_*: por exemplo, mb_strpos() e mb_strlen(). Essas funções mb_* vêm da
extensão Multibyte String e foram feitas para operar em strings Unicode.
Use as funções mb_* sempre que operar numa string Unicode. Se você usar substr() numa string
UTF-8, o resultado pode sair com meio caractere quebrado. A função certa é a versão multibyte,
mb_substr().
O difícil é lembrar de usar mb_* o tempo todo. Esqueceu uma vez? A string Unicode pode se corromper
no processamento seguinte.
Nem toda função de string tem um par mb_*. Se não existir uma para o que você precisa, aí complica.
Chame mb_internal_encoding() no topo de todo script PHP (ou no include global). Se o script
responde para o navegador, chame mb_http_output() logo em seguida. Definir a codificação das strings
em todo script poupa muita dor de cabeça depois.
Várias funções PHP que mexem com strings têm um parâmetro opcional de codificação. Sempre
passe UTF-8 quando essa opção existir. Por exemplo, htmlentities() aceita esse parâmetro: informe UTF-8
se estiver lidando com essas strings. A partir do PHP 5.4.0, UTF-8 já é o padrão de htmlentities()
e htmlspecialchars().
Por fim, se você monta um app distribuído e não pode garantir que a extensão mbstring está
habilitada, considere o pacote Composer symfony/polyfill-mbstring. Ele usa mbstring quando
existe e cai nas funções sem UTF-8 se não existir.
Se o script PHP acessa MySQL, as strings ainda podem ir para o banco fora de UTF-8, mesmo com todos os cuidados acima.
Para as strings irem do PHP ao MySQL em UTF-8, banco e tabelas precisam estar no charset e na
collation utf8mb4, e a string de conexão PDO precisa usar o charset utf8mb4. Veja o
código abaixo. Isso é crítico.
Use o charset utf8mb4 para suporte completo a UTF-8, não o utf8! O porquê está em
Para saber mais.
Use mb_http_output() para o script PHP enviar strings UTF-8 ao navegador.
O navegador ainda precisa saber, pela resposta HTTP, que a página é UTF-8. Hoje o comum é definir o charset no header HTTP assim:
<?php
header('Content-Type: text/html; charset=UTF-8')O jeito antigo era incluir a tag <meta> de charset no <head> da página.
<?php
// Diz ao PHP que as strings são UTF-8 até o fim do script
mb_internal_encoding('UTF-8');
$utf_set = ini_set('default_charset', 'utf-8');
if (!$utf_set) {
throw new Exception('não foi possível definir default_charset como utf-8; confira se já está definido no sistema!');
}
// Diz ao PHP que a saída para o navegador será UTF-8
mb_http_output('UTF-8');
// String de teste UTF-8
$string = 'Êl síla erin lû e-govaned vîn.';
// Transforma a string com uma função multibyte
// Cortamos num caractere não-ASCII de propósito, para demonstrar
$string = mb_substr($string, 0, 15);
// Conecta ao banco para guardar a string transformada
// Veja o exemplo de PDO neste documento para mais detalhes
// Repare no `charset=utf8mb4` no Data Source Name (DSN)
$link = new PDO(
'mysql:host=seu-hostname;dbname=seu-db;charset=utf8mb4',
'seu-usuario',
'sua-senha',
array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => false
)
);
// Guarda a string transformada como UTF-8 no banco
// Banco e tabelas estão em utf8mb4 no charset e na collation, certo?
$handle = $link->prepare('insert into ElvishSentences (Id, Body, Priority) values (default, :body, :priority)');
$handle->bindParam(':body', $string, PDO::PARAM_STR);
$priority = 45;
$handle->bindParam(':priority', $priority, PDO::PARAM_INT); // avisa o PDO que espera um int
$handle->execute();
// Lê a string de volta para provar que gravou certo
$handle = $link->prepare('select * from ElvishSentences where Id = :id');
$id = 7;
$handle->bindParam(':id', $id, PDO::PARAM_INT);
$handle->execute();
// Guarda o resultado num objeto que vamos imprimir depois no HTML
// Esse objeto não estoura a memória: busca os dados sob demanda
$result = $handle->fetchAll(\PDO::FETCH_OBJ);
// Função auxiliar de exemplo para escapar dados para HTML
function escape_to_html($dirty){
echo htmlspecialchars($dirty, ENT_QUOTES, 'UTF-8');
}
header('Content-Type: text/html; charset=UTF-8'); // Desnecessário se default_charset já for utf-8
?><!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Página de teste UTF-8</title>
</head>
<body>
<?php
foreach($result as $row){
escape_to_html($row->Body); // Deve imprimir a string UTF-8 transformada corretamente no navegador
}
?>
</body>
</html>Se você está começando: i18n e l10n são numerônimos — abreviações que usam números para encurtar palavras. Internacionalização vira i18n; localização, l10n.
Primeiro, vamos separar esses dois conceitos parecidos e o que vem junto:
O jeito mais fácil de internacionalizar software PHP é um arquivo de arrays e usar essas strings nos templates, tipo <h1><?=$TRANS['title_about_page']?></h1>. Em projeto sério isso quase nunca vale: a manutenção aperta rápido — pluralização já aparece no começo. Não use isso se o projeto tiver mais que umas poucas páginas.
O caminho clássico, e ainda a referência de i18n e l10n, é uma ferramenta Unix chamada gettext. Existe desde 1995 e continua sendo uma implementação completa para traduzir software. É fácil de colocar para rodar e tem ferramentas de apoio fortes. É do Gettext que vamos falar aqui. Para você não se perder na linha de comando, também mostramos um app gráfico bom para atualizar a fonte de l10n.
Há bibliotecas comuns que suportam Gettext e outras implementações de i18n. Algumas parecem mais fáceis de instalar ou trazem recursos extras ou outros formatos de arquivo. Aqui o foco são as ferramentas do núcleo do PHP; as outras entram só para completar:
intl (incluindo plurais).gettext não cobre) e exportação para outros formatos além de .mo/.po. Útil se você precisa integrar as traduções em outras partes do sistema, como uma interface JavaScript.strtr() por dentro.Outros frameworks também têm módulos de i18n, mas só dentro do próprio código:
@lang para templates.Intl, disponível desde o PHP 5.3 e baseada no projeto ICU; isso permite substituições fortes, como escrever números por extenso, formatar datas, horas, intervalos, moeda e ordinais.Se você escolher uma biblioteca sem extrator, prefira o formato gettext. Assim você usa as ferramentas originais do gettext (incluindo o Poedit), como no resto do capítulo.
Talvez você precise instalar o Gettext e a biblioteca PHP pelo gerenciador de pacotes, como apt-get ou yum. Depois de instalado, habilite com extension=gettext.so (Linux/Unix) ou extension=php_gettext.dll (Windows) no php.ini.
Aqui também usamos o Poedit para criar os arquivos de tradução. Em geral ele está no gerenciador de pacotes do sistema; roda em Unix, macOS e Windows, e dá para baixar de graça no site.
Você lida com três tipos de arquivo no gettext. Os principais são PO (Portable Object) e MO (Machine Object): o primeiro é a lista legível dos “objetos traduzidos”; o segundo é o binário que o gettext lê na localização. Existe ainda o POT (Template), que só lista as chaves do código-fonte e serve de guia para gerar e atualizar os PO. O template não é obrigatório: dependendo da ferramenta de l10n, PO/MO bastam. Sempre há um par PO/MO por idioma e região, e só um POT por domínio.
Em projetos grandes, às vezes a mesma palavra muda de sentido conforme o contexto. Aí você separa em domínios diferentes. São grupos nomeados de arquivos POT/PO/MO; o nome do arquivo é o domínio de tradução. Projetos pequenos e médios, por simplicidade, usam um domínio só; o nome é livre, mas nos exemplos vamos usar “main”. Em projetos Symfony, por exemplo, os domínios separam a tradução das mensagens de validação.
Um locale é só um código que identifica uma versão de um idioma. Segue as especificações ISO 639-1 e ISO 3166-1 alpha-2: duas letras minúsculas para o idioma, opcionalmente um underline e duas maiúsculas para o país ou a região. Em idiomas raros, são três letras.
Para muita gente a parte do país parece redundante. Na prática, alguns idiomas têm dialetos em países diferentes, como o alemão da Áustria (de_AT) ou o português do Brasil (pt_BR). A segunda parte distingue esses dialetos — sem ela, vale uma versão “genérica” ou “híbrida” do idioma.
Para usar Gettext, a pasta precisa seguir um formato fixo. Primeiro escolha uma raiz qualquer para os arquivos de l10n no repositório. Dentro dela, uma pasta por locale e uma pasta fixa LC_MESSAGES com os pares PO/MO. Exemplo:
<project root>
├─ src/
├─ templates/
└─ locales/
├─ forum.pot
├─ site.pot
├─ de/
│ └─ LC_MESSAGES/
│ ├─ forum.mo
│ ├─ forum.po
│ ├─ site.mo
│ └─ site.po
├─ es_ES/
│ └─ LC_MESSAGES/
│ └─ ...
├─ fr/
│ └─ ...
├─ pt_BR/
│ └─ ...
└─ pt_PT/
└─ ...Como na introdução, cada idioma pode ter regras de plural diferentes. O gettext tira isso da sua frente. Ao criar um .po novo, você declara as regras de plural daquele idioma; trechos sensíveis a plural ganham uma forma para cada regra. Na hora de chamar o Gettext no código, você passa o número da frase e ele escolhe a forma certa — inclusive com substituição de string, se precisar.
As regras de plural incluem quantos plurais existem e um teste booleano com n que diz em qual regra o número cai (a contagem começa em 0). Exemplos:
nplurals=1; plural=0 — uma regra sónplurals=2; plural=(n != 1); — duas regras; a primeira se N é um, a segunda no restonplurals=2; plural=(n > 1); — duas regras; a segunda se N é maior que um, a primeira no restoSe a base ficou clara — e se não ficou, veja a explicação mais fundo no tutorial do LingoHub —, copie as regras de uma lista em vez de escrever na mão.
Ao pedir ao Gettext para localizar frases com contador, passe o número também. Ele descobre a regra e usa a versão localizada certa. No .po você precisa de uma frase diferente para cada regra de plural.
Chega de teoria, vamos ao prático. Abaixo um trecho de .po — não se prenda ao formato, olhe o conteúdo; você vai editar isso com facilidade depois:
msgid ""
msgstr ""
"Language: pt_BR\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgid "We are now translating some strings"
msgstr "Nós estamos traduzindo algumas strings agora"
msgid "Hello %1$s! Your last visit was on %2$s"
msgstr "Olá %1$s! Sua última visita foi em %2$s"
msgid "Only one unread message"
msgid_plural "%d unread messages"
msgstr[0] "Só uma mensagem não lida"
msgstr[1] "%d mensagens não lidas"A primeira seção funciona como cabeçalho, com msgid e msgstr vazios de propósito. Descreve o encoding do arquivo, as formas de plural e outras coisas menos importantes.
A segunda traduz uma string simples do inglês para o português do Brasil; a terceira faz o mesmo, mas com substituição via sprintf, para a tradução poder incluir nome e data da visita.
A última é um exemplo de pluralização: singular e plural como msgid em inglês e as traduções correspondentes em msgstr 0 e 1 (na ordem da regra de plural). Também usa substituição para o número aparecer na frase, com %d. Formas plurais sempre têm dois msgid (singular e plural), então evite um idioma complexo como fonte da tradução.
Como você viu, o ID de origem é a frase em inglês. Esse msgid se repete em todos os .po: os outros idiomas têm o mesmo formato e os mesmos msgid, só muda o msgstr.
Sobre chaves de tradução, há duas escolas:
msgid como frase de verdade.
Vantagens:
msgid;msgid em vários arquivos de idioma.msgid como chave única e estruturada.
Descreve o papel da frase no app de forma estruturada, com o template ou a parte onde a string está, em vez do conteúdo.
en.po para o tradutor ler e escrever o fr.po.top_menu.welcome em vez de Hello there, User! na página francesa sem tradução). Isso força a tradução completa antes de publicar — e deixa o erro bem feio na interface. Algumas bibliotecas permitem um idioma de “fallback”, com comportamento parecido com a outra abordagem.O manual do Gettext prefere a primeira: em geral é mais fácil para tradutores e para o usuário quando algo quebra. É o caminho que vamos seguir. Já a documentação do Symfony prefere tradução por palavra-chave, para mudar qualquer tradução sem mexer nos templates.
Num app típico, você chama funções Gettext ao escrever texto estático nas páginas. Essas frases entram nos .po, são traduzidas, compiladas em .mo e o Gettext as usa na hora de renderizar a interface. Juntando o que vimos até aqui, um exemplo passo a passo:
<?php include 'i18n_setup.php' ?>
<div id="header">
<h1><?=sprintf(gettext('Welcome, %s!'), $name)?></h1>
<!-- indentado assim só para ficar legível -->
<?php if ($unread): ?>
<h2><?=sprintf(
ngettext('Only one unread message',
'%d unread messages',
$unread),
$unread)?>
</h2>
<?php endif ?>
</div>
<h1><?=gettext('Introduction')?></h1>
<p><?=gettext('We\'re now translating some strings')?></p>gettext() só traduz um msgid no msgstr do idioma. Também existe o atalho _(), que faz o mesmo;ngettext() faz o mesmo, com regras de plural;dgettext() e dngettext(), que deixam você sobrescrever o domínio numa chamada só. A configuração de domínio vem no próximo exemplo.i18n_setup.php, o mesmo de cima), escolhendo o locale e configurando o Gettext<?php
/**
* Verifica se o $locale informado é suportado no projeto
* @param string $locale
* @return bool
*/
function valid($locale) {
return in_array($locale, ['en_US', 'en', 'pt_BR', 'pt', 'es_ES', 'es']);
}
// define o locale de origem/padrão, só para informação
$lang = 'en_US';
if (isset($_GET['lang']) && valid($_GET['lang'])) {
// o locale pode mudar pela query string
$lang = $_GET['lang']; //sanitize isso!
setcookie('lang', $lang); //fica num cookie para reutilizar
} elseif (isset($_COOKIE['lang']) && valid($_COOKIE['lang'])) {
// se o cookie já existe, reaproveita
$lang = $_COOKIE['lang']; //sanitize isso!
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
// padrão: idiomas que o navegador diz que o usuário aceita
$langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
array_walk($langs, function (&$lang) { $lang = strtr(strtok($lang, ';'), ['-' => '_']); });
foreach ($langs as $browser_lang) {
if (valid($browser_lang)) {
$lang = $browser_lang;
break;
}
}
}
// define o locale global do sistema com o idioma encontrado
putenv("LANG=$lang");
// útil para funções de data (LC_TIME) ou formatação de dinheiro (LC_MONETARY), por exemplo
setlocale(LC_ALL, $lang);
// faz o Gettext procurar ../locales/<lang>/LC_MESSAGES/main.mo
bindtextdomain('main', '../locales');
// indica em que encoding o arquivo deve ser lido
bind_textdomain_codeset('main', 'UTF-8');
// se o app tiver outros domínios, como citado antes, faça o bind deles aqui também
bindtextdomain('forum', '../locales');
bind_textdomain_codeset('forum', 'UTF-8');
// domínio padrão que as chamadas gettext() vão usar
textdomain('main');
// isso procuraria a string em forum.mo em vez de main.mo
// echo dgettext('forum', 'Welcome back!');
?>Uma grande vantagem do Gettext sobre pacotes de i18n de framework é o formato de arquivo, amplo e poderoso. “Poxa, isso é difícil de entender e editar na mão; um array simples seria mais fácil!” Não se engane: apps como o Poedit existem para ajudar — e muito. Você baixa no site, é grátis e roda em todas as plataformas. É fácil de pegar o jeito e, ao mesmo tempo, bem poderoso — usa tudo que o Gettext oferece. Este guia usa o PoEdit 1.8.
Na primeira vez, escolha “File > New…” no menu. Ele pede o idioma na hora:
filtre o idioma para o qual você vai traduzir, ou use o formato que citamos, como
en_US ou pt_BR.
Salve o arquivo — na estrutura de pastas que mostramos. Depois clique em “Extract from sources” e configure a extração e a tradução. Depois você acha tudo isso em “Catalog > Properties”:
gettext() (e irmãs) são chamadas — em geral as pastas de templates/views. É o único ajuste obrigatório;gettext() e funções parecidas aparecem em várias linguagens, mas você pode criar as suas. É aqui que você adiciona esses outros métodos. Falamos disso depois, em “Dicas”.Com isso configurado, ele varre o código em busca das chamadas de localização. Depois de cada varredura o PoEdit mostra um resumo do que entrou e do que saiu. Entradas novas vão vazias para a tabela de tradução; você começa a digitar as versões localizadas. Salve e um .mo é (re)compilado na mesma pasta: pronto, o projeto está internacionalizado.
Como você viu, há dois tipos principais de string localizada: as simples e as com formas de plural. As primeiras têm só duas caixas: origem e string localizada. A string de origem não se edita aqui: Gettext/Poedit não alteram o código-fonte — mude a origem e varra de novo. Dica: clique com o botão direito numa linha e ele mostra os arquivos e as linhas de origem. Já as strings com plural têm duas caixas para as duas origens e abas para configurar as formas finais.
Sempre que o código mudar e as traduções precisarem acompanhar, clique em Refresh: o Poedit varre de novo, remove o que sumiu, mescla o que mudou e adiciona o que é novo. Ele também tenta adivinhar algumas traduções com base nas que você já fez. Esses palpites e as entradas alteradas ganham o marcador “Fuzzy”, sinal de que precisam de revisão, e aparecem dourados na lista. Também serve se a equipe de tradução não tiver certeza de um texto: marque Fuzzy e outra pessoa revisa depois.
Deixe marcado “View > Untranslated entries first”: ajuda muito a não esquecer entrada. Nesse menu você também abre partes da interface que deixam contexto para o tradutor, se precisar.
Se o PHP roda como módulo no Apache (mod_php), o .mo pode ficar em cache. Isso acontece na primeira leitura; para atualizar, às vezes precisa reiniciar o servidor. No Nginx com PHP 5, em geral basta atualizar a página umas poucas vezes. No PHP 7 quase nunca precisa.
Muita gente prefere _() no lugar de gettext(). Várias bibliotecas de i18n de framework usam algo como t() também, para o código traduzido ficar mais curto. Só que esse é o único atalho nativo. Você pode incluir no projeto outros, como __() ou _n() para ngettext(), ou um _r() que junte gettext() e sprintf(). Outras bibliotecas, como o Gettext do php-gettext, também trazem helpers assim.
Nesses casos, você precisa ensinar a ferramenta Gettext a extrair as strings dessas funções novas.
É bem simples. É só um campo no .po ou uma tela de configuração no Poedit. No editor,
a opção fica em “Catalog > Properties > Source keywords”. O Gettext já conhece as funções padrão de várias linguagens; se a lista parecer vazia, é normal. Inclua aí as especificações das funções novas, neste formato:
t() que só devolve a tradução de uma string, especifique t.
O Gettext entende que o único argumento é a string a traduzir;__('one user', '%d users', $number), a especificação é __:1,2, ou seja, a primeira forma é o primeiro argumento e a segunda forma é o segundo. Se o número vier primeiro, a especificação é __:2,3: a primeira forma é o segundo argumento, e assim por diante.Com as regras novas no .po, a próxima varredura puxa as strings novas do mesmo jeito.
Da Wikipedia:
Injeção de dependência é um padrão de projeto que remove dependências fixas no código e permite trocá-las em tempo de execução ou de compilação.
Essa citação deixa o conceito mais complicado do que ele é. Injeção de dependência (DI) é entregar as dependências a um componente: pelo construtor, por chamada de método ou pela atribuição de propriedades. É só isso.
Dá para mostrar a ideia com um exemplo simples — e um pouco ingênuo.
A classe Database precisa de um adapter para falar com o banco. O construtor instancia o adapter e cria uma dependência rígida. Testar fica difícil. A Database fica acoplada demais ao adapter.
<?php
namespace Database;
class Database
{
protected $adapter;
public function __construct()
{
$this->adapter = new MySqlAdapter;
}
}
class MysqlAdapter {}Dá para refatorar com injeção de dependência e afrouxar essa dependência. Aqui você injeta a dependência no construtor e usa promoção de propriedade no construtor. Ela vira propriedade da classe:
<?php
namespace Database;
class Database
{
public function __construct(protected MySqlAdapter $adapter)
{
}
}
class MysqlAdapter {}Agora você entrega a dependência para a Database em vez de ela criar sozinha. Dá para fazer o mesmo com um método que recebe a dependência e atribui. Se $adapter for public, você atribui direto.
Se você já leu sobre injeção de dependência, provavelmente viu Inversão de Controle ou Princípio da Inversão de Dependência. São os problemas complexos que a injeção de dependência resolve.
Inversão de Controle é o que o nome diz: inverter o controle do sistema. A organização fica fora dos objetos. Na injeção de dependência, você afrouxa as dependências: controla e instancia elas em outro lugar.
Há anos os frameworks PHP fazem Inversão de Controle. A dúvida virou: qual parte do controle estamos invertendo, e para onde? Frameworks MVC costumam oferecer um super objeto ou um controller base. Os outros controllers estendem isso para acessar as dependências. Isso é Inversão de Controle. Só que, em vez de afrouxar as dependências, só as move.
A injeção de dependência resolve melhor. Você injeta só o que precisa, quando precisa, sem dependência fixa no código.
O Princípio da Responsabilidade Única fala de atores e arquitetura de alto nível. Diz que “Uma classe deve ter só um motivo para mudar.” Cada classe cuida de uma parte da funcionalidade. O maior ganho é a reutilização. Se a classe faz uma coisa só, você usa ela em outro programa sem mudar nada.
O Princípio Aberto/Fechado fala de design de classe e extensão. Diz que “Entidades de software (classes, módulos, funções etc.) devem estar abertas para extensão e fechadas para modificação.” Precisou de funcionalidade nova? Não altere o código existente. Escreva código novo que o existente vai usar. Na prática: implemente interfaces e faça type-hint nelas, não em classes concretas.
O maior ganho: você estende o código sem mexer no que já existe. Menos tempo de QA. Menos risco de quebrar o app. Deploy mais rápido e com mais confiança.
O Princípio da Substituição de Liskov fala de subtipos e herança. Diz que “Classes filhas nunca devem quebrar as definições de tipo da classe pai.” Nas palavras de Robert C. Martin: “Subtipos devem ser substituíveis pelos tipos base.”
Exemplo: FileInterface define embed(). Audio e Video implementam essa interface. O embed() deve sempre fazer o que você espera. Se depois você criar PDF ou Gist implementando FileInterface, já sabe o que embed() faz. O maior ganho: programas flexíveis e fáceis de configurar. Trocar um objeto de um tipo (ex.: FileInterface) por outro não exige mudar mais nada.
O Princípio da Segregação de Interface (ISP) fala da comunicação entre a lógica de negócio e os clientes. Diz que “Nenhum cliente deve ser forçado a depender de métodos que não usa.” Em vez de uma interface monolítica que todas as classes implementam, ofereça interfaces menores, cada uma de um conceito. A classe implementa uma ou mais delas.
Exemplo: Car ou Bus se interessam por steeringWheel(). Motorcycle ou Tricycle não. O inverso vale para handlebars(). Não faz sentido todos os veículos implementarem os dois. Quebre a interface de origem.
O Princípio da Inversão de Dependência corta o vínculo rígido entre classes distintas. Assim você passa outra classe e ganha funcionalidade nova. Diz: “Dependa de abstrações. Não dependa de concreções.” Em resumo: dependa de interfaces/contratos ou classes abstratas, não de implementações concretas. Dá para refatorar o exemplo anterior e seguir esse princípio.
<?php
namespace Database;
class Database
{
public function __construct(protected AdapterInterface $adapter)
{
}
}
interface AdapterInterface {}
class MysqlAdapter implements AdapterInterface {}Há várias vantagens em Database depender de uma interface em vez de uma implementação concreta.
Imagine que você trabalha em equipe e um colega está fazendo o adapter. No primeiro exemplo, você teria que esperar o adapter ficar pronto para mockar nos testes unitários. Com a dependência sendo uma interface/contrato, você mocka a interface. O colega implementa o adapter em cima desse contrato.
Um ganho ainda maior: o código escala melhor. Se daqui a um ano você quiser migrar para outro tipo de banco, escreve um adapter que implementa a interface original e injeta ele. Sem refatorar o resto. O adapter segue o contrato da interface.
A primeira coisa sobre containers de injeção de dependência: eles não são a mesma coisa que injeção de dependência. O container só facilita aplicar DI. Só que muita gente usa errado e cai no antipadrão Service Location.
Injetar o container de DI como Service Locator nas suas classes cria uma dependência ainda mais rígida do que a que você estava trocando. O código fica menos transparente e mais difícil de testar.
A maioria dos frameworks modernos tem o próprio container de injeção de dependência. Você liga as dependências pela configuração. Na prática, o código do app fica tão limpo e desacoplado quanto o framework em que ele roda.
Muitas vezes o código PHP usa um banco de dados para persistir informação. Você tem algumas opções para conectar e interagir com o banco. Até o PHP 5.1.0, a recomendação era usar drivers nativos como mysqli, pgsql, mssql, etc.
Drivers nativos funcionam bem se você usa um banco só no app. Mas se mistura MySQL com um pouco de MSSQL, ou precisa conectar num Oracle, não dá para usar os mesmos drivers. Cada banco pede uma API nova — e isso vira bagunça.
A extensão mysql do PHP é antiga e foi substituída por duas outras:
O desenvolvimento de mysql parou há muito tempo. Pior: a extensão foi removida oficialmente no PHP 7.0.
Para não vasculhar o php.ini e descobrir qual módulo está em uso, busque mysql_*
no editor. Se aparecerem funções como mysql_connect() e mysql_query(), o mysql
está em uso.
Mesmo sem PHP 7.x ou posterior, adiar essa troca só complica o upgrade depois. O melhor é substituir o mysql por mysqli ou PDO nos seus apps no seu próprio ritmo, sem pressa de última hora.
Se você vai de mysql para mysqli, cuidado com guias preguiçosos que dizem para só trocar mysql_* por mysqli_*. Isso é uma simplificação grosseira e ignora vantagens do mysqli, como bind de parâmetros — também disponível no PDO.
PDO é uma biblioteca de abstração de conexão com banco — incluída no PHP desde a 5.1.0 — que oferece uma interface comum para vários bancos. Por exemplo, você usa praticamente o mesmo código para MySQL ou SQLite:
<?php
// PDO + MySQL
$pdo = new PDO('mysql:host=example.com;dbname=database', 'user', 'password');
$statement = $pdo->query("SELECT some_field FROM some_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);
// PDO + SQLite
$pdo = new PDO('sqlite:/path/db/foo.sqlite');
$statement = $pdo->query("SELECT some_field FROM some_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);O PDO não traduz suas queries SQL nem emula recursos ausentes. Só conecta a vários tipos de banco com a mesma API.
O mais importante: PDO deixa você colocar input externo (IDs, por exemplo) nas queries SQL com segurança, sem se preocupar com SQL injection.
Isso é possível com statements PDO e parâmetros vinculados.
Digamos que um script PHP recebe um ID numérico como parâmetro da query string. Esse ID deve buscar um registro de usuário no banco. Este é o jeito errado:
<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$pdo->query("SELECT name FROM users WHERE id = " . $_GET['id']); // <-- NÃO!Isso é código péssimo. Você está colocando um parâmetro cru da query numa query SQL. Você vai ser hackeado em segundos, com uma prática chamada SQL Injection. Imagine um atacante passando um id inventivo numa URL como http://domain.com/?id=1%3BDELETE+FROM+users. Isso define $_GET['id'] como 1;DELETE FROM users e apaga todos os seus usuários! Em vez disso, sanitiza o ID com parâmetros vinculados do PDO.
<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$stmt = $pdo->prepare('SELECT name FROM users WHERE id = :id');
$id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT); // <-- filtre os dados primeiro (veja [Filtragem de dados](#data_filtering)); especialmente importante em INSERT, UPDATE, etc.
$stmt->bindParam(':id', $id, PDO::PARAM_INT); // <-- o PDO sanitiza automaticamente para SQL
$stmt->execute();Este é o código certo. Usa um parâmetro vinculado num statement PDO. O ID externo é escapado antes de ir para o banco, o que impede SQL injection.
Em escritas, como INSERT ou UPDATE, ainda é crítico filtrar os dados primeiro e sanitizar o resto (remover tags HTML, JavaScript, etc.). O PDO só sanitiza para SQL, não para o seu app.
Conexões de banco consomem recursos. Em outras linguagens, não era raro esgotá-los se as conexões não fossem fechadas. Com PDO, você fecha a conexão implicitamente destruindo o objeto: apague as referências restantes, ou seja, atribua NULL. Se você não fizer isso, o PHP fecha a conexão quando o script termina — a menos que você use persistent connections.
Quem começa a aprender PHP costuma misturar o acesso ao banco com a lógica de apresentação. O código fica assim:
<ul>
<?php
foreach ($db->query('SELECT * FROM table') as $row) {
echo "<li>".$row['field1']." - ".$row['field1']."</li>";
}
?>
</ul>Má prática por vários motivos: difícil de depurar, de testar e de ler. Sem um LIMIT, você joga um monte de linhas na tela.
Há várias formas de resolver isso — depende se você prefere OOP ou programação funcional. O que não pode faltar é alguma separação.
O passo mais básico:
<?php
function getAllFoos($db) {
return $db->query('SELECT * FROM table');
}
$results = getAllFoos($db);
foreach ($results as $row) {
echo "<li>".$row['field1']." - ".$row['field1']."</li>"; // RUIM!!
}Já é um começo. Coloque as duas partes em arquivos diferentes e a separação fica limpa.
Coloque o método numa classe e você tem um Model. Coloque a apresentação num .php simples e você tem uma View. Quase MVC — a arquitetura OOP que a maioria dos frameworks usa.
foo.php
<?php
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8mb4', 'usuario', 'senha');
// Inclua o model
include 'models/FooModel.php';
// Crie uma instância
$fooModel = new FooModel($db);
// Pegue a lista de Foos
$fooList = $fooModel->getAllFoos();
// Mostre a view
include 'views/foo-list.php';models/FooModel.php
<?php
class FooModel
{
public function __construct(protected PDO $db)
{
}
public function getAllFoos() {
return $this->db->query('SELECT * FROM table');
}
}views/foo-list.php
<?php foreach ($fooList as $row): ?>
<li><?= $row['field1'] ?> - <?= $row['field1'] ?></li>
<?php endforeach ?>É o que a maioria dos frameworks modernos faz, só que mais na mão. Você não precisa disso sempre. Mas misturar demais apresentação e banco vira problema na hora de fazer teste unitário do app.
Muitos frameworks trazem a própria camada de abstração, em cima do PDO ou não. Em geral elas emulam recursos de um banco que faltam em outro, encapsulando as queries em métodos PHP. Você ganha abstração de banco de verdade, não só a abstração de conexão que o PDO oferece. Há um pouco de overhead, claro. Se o app precisa rodar em MySQL, PostgreSQL e SQLite, esse custo vale a pena pela clareza do código.
Algumas camadas de abstração seguem os padrões de namespace PSR-0 ou PSR-4 e você instala em qualquer app:
Templates separam a lógica do controller e do domínio da lógica de apresentação. Em geral o template tem o HTML do app, mas também serve para outros formatos, como XML. Muita gente chama de “view”. A view é parte do segundo componente do padrão model–view–controller (MVC).
O principal ganho dos templates é a separação clara entre a lógica de apresentação e o resto do app. O template só exibe conteúdo formatado. Não busca dados, não persiste nada e não faz tarefa complexa. O código fica mais limpo e fácil de ler — especialmente em time, onde quem desenvolve cuida do servidor (controllers, models) e quem desenha cuida do cliente (markup).
Templates também organizam melhor o código de apresentação. Em geral ficam numa pasta “views”, um arquivo por template. Isso incentiva reuso: blocos grandes viram pedaços menores e reutilizáveis, os chamados partials. Cabeçalho e rodapé do site, por exemplo, viram templates que você inclui antes e depois de cada página.
Por fim, conforme a biblioteca, o template pode ganhar segurança ao escapar sozinho conteúdo gerado pelo usuário. Algumas até oferecem sandbox: quem monta o template só acessa variáveis e funções da whitelist.
Templates PHP puro usam código PHP nativo. Faz sentido: PHP já nasceu como linguagem de template. Você mistura PHP com HTML (ou outro markup). Quem já programa em PHP não precisa aprender sintaxe nova, conhece as funções e o editor já destaca e completa o código. Sem etapa de compilação, esses templates costumam ser bem rápidos.
Todo framework PHP moderno tem algum sistema de template. A maioria usa PHP puro por padrão. Fora de frameworks, bibliotecas como Plates ou Aura.View facilitam o trabalho: herança, layouts e extensões.
Com a biblioteca Plates.
<?php // user_profile.php ?>
<?php $this->insert('header', ['title' => 'Perfil do usuário']) ?>
<h1>Perfil do usuário</h1>
<p>Olá, <?=$this->escape($name)?></p>
<?php $this->insert('footer') ?>Com a biblioteca Plates.
<?php // template.php ?>
<html>
<head>
<title><?=$title?></title>
</head>
<body>
<main>
<?=$this->section('content')?>
</main>
</body>
</html><?php // user_profile.php ?>
<?php $this->layout('template', ['title' => 'Perfil do usuário']) ?>
<h1>Perfil do usuário</h1>
<p>Olá, <?=$this->escape($name)?></p>O PHP amadureceu como linguagem orientada a objetos, mas quase não melhorou como linguagem de templates. Templates compilados como Twig, Brainy ou Smarty* preenchem essa lacuna com uma sintaxe feita para templates. Escape automático, herança e estruturas de controle mais simples: fica mais fácil escrever, mais limpo de ler e mais seguro de usar. Dá até para compartilhar o mesmo template entre linguagens; Mustache é um bom exemplo. Como esses templates precisam ser compilados, há um pequeno custo de performance. Com cache certo, esse custo fica mínimo.
*O Smarty tem escape automático, mas esse recurso NÃO vem ligado por padrão.
Com a biblioteca Twig.
{% include 'header.html' with {'title': 'Perfil do usuário'} %}
<h1>Perfil do usuário</h1>
<p>Olá, {{ name }}</p>
{% include 'footer.html' %}Com a biblioteca Twig.
// template.html
<html>
<head>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>// user_profile.html
{% extends "template.html" %}
{% block title %}Perfil do usuário{% endblock %}
{% block content %}
<h1>Perfil do usuário</h1>
<p>Olá, {{ name }}</p>
{% endblock %}Em muitas linguagens “pesadas em Exception”, qualquer coisa que dê errado lança uma Exception. Funciona. O PHP é uma linguagem “leve em Exception”. Tem Exceptions, e cada vez mais o core usa elas com objetos. Mas a maior parte do PHP tenta continuar rodando, a menos que ocorra um erro fatal.
Por exemplo:
$ php -a
php > echo $foo;
Notice: Undefined variable: foo in php shell code on line 1Isso é só um notice. O PHP segue em frente. Confunde quem vem de linguagens “pesadas em Exception”: em Python, por exemplo, referenciar uma variável que não existe lança uma Exception:
$ python
>>> print foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'foo' is not definedA diferença real: o Python estoura com qualquer detalhe, para você ter certeza de que pegou o problema ou o caso extremo. O PHP continua processando até algo extremo acontecer. Aí lança um erro e reporta.
O PHP tem vários níveis de gravidade. Os três tipos mais comuns de mensagem são errors, notices e warnings. Têm níveis diferentes: E_ERROR, E_NOTICE e E_WARNING. Errors são erros fatais em tempo de execução, em geral por falha no seu código. Precisam ser corrigidos: o PHP para. Notices são avisos — o código pode ou não dar problema; a execução não para. Warnings são erros não fatais; a execução também não para.
Outro tipo, em tempo de compilação, são as mensagens E_STRICT. Elas sugerem mudanças no código para melhor interoperabilidade e compatibilidade com versões futuras do PHP.
Dá para mudar o relatório de erros no php.ini e/ou com funções. Com error_reporting() você define o nível de erros durante a execução do script, passando uma das constantes predefinidas. Se você só quer ver Errors e Warnings — sem Notices — configura assim:
<?php
error_reporting(E_ERROR | E_WARNING);Você também controla se os erros aparecem na tela (bom no desenvolvimento) ou ficam ocultos e vão para o log (bom em produção). Mais detalhes na seção Relatório de erros.
Dá para o PHP silenciar erros específicos com o operador de controle de erro @. Você coloca o operador no começo da expressão. Qualquer erro resultado direto dela some.
<?php
echo @$foo['bar'];Isso imprime $foo['bar'] se existir. Se a variável $foo ou a chave 'bar' não existir, retorna null e não imprime nada. Sem o operador, a expressão pode gerar PHP Notice: Undefined variable: foo ou PHP Notice: Undefined index: bar.
Parece uma boa ideia. Tem desvantagens. O PHP trata expressões com @ de um jeito mais lento do que sem @. Otimização prematura é o pai de muita briga, mas se performance importa no seu app ou biblioteca, entenda o custo desse operador.
Segundo: o operador engole o erro por completo. Não exibe. Não manda para o log. No PHP padrão de produção, não tem como desligar o operador de controle de erro. Você pode estar certo de que aquele erro é inofensivo. Um erro pior fica igualmente silencioso.
Se der para evitar o operador de supressão, evite. O código acima, por exemplo, vira isto:
<?php
// Operador de coalescência nula
echo $foo['bar'] ?? '';Um caso em que a supressão pode fazer sentido: fopen() não encontra o arquivo. Você pode checar se o arquivo existe antes de abrir. Se ele for apagado entre a verificação e o fopen() (parece impossível, mas acontece), o fopen() retorna false e lança um erro. Talvez o PHP devesse resolver isso. É um caso em que a supressão parece a única saída.
Como dito: no PHP padrão não dá para desligar o operador de controle de erro. O Xdebug, porém, tem o setting xdebug.scream no ini, que desativa o operador. Você configura no php.ini assim:
xdebug.scream = OnTambém dá para definir em runtime com ini_set:
<?php
ini_set('xdebug.scream', '1')Útil quando você está debugando e suspeita que um erro informativo está silenciado. Use scream com cuidado, e só como ferramenta temporária de debug. Tem muita biblioteca PHP que pode quebrar com o operador de controle de erro desligado.
O PHP consegue ser uma linguagem “pesada em Exception”. Bastam algumas linhas. Na prática, você lança os “errors” como “exceptions” com a classe ErrorException, que estende Exception.
É prática comum em frameworks modernos como Symfony e Laravel. Em modo debug (ou modo dev) os dois exibem um stack trace limpo.
Há pacotes para tratar e reportar errors e exceptions melhor. Tipo Whoops!, que vem na instalação padrão do Laravel e funciona em qualquer framework.
Lançar errors como exceptions no desenvolvimento deixa o tratamento melhor do que o resultado usual. Se você vê uma Exception no desenvolvimento, envolve num catch com instruções específicas. Cada Exception que você captura deixa o app um pouco mais robusto.
Mais detalhes sobre como usar ErrorException no tratamento de erros: Classe ErrorException.
Exceções são padrão na maioria das linguagens populares. Em PHP, muita gente ignora. Linguagens como Ruby usam Exception o tempo todo. Requisição HTTP falhou, query no banco deu errado, imagem não encontrada: o Ruby (ou a gem) lança uma Exception na tela. Você vê o erro na hora.
O PHP é mais frouxo. file_get_contents() costuma devolver só FALSE e um warning.
Frameworks antigos como CodeIgniter devolvem false, gravam um log próprio e às vezes deixam você chamar $this->upload->get_error() para saber o que aconteceu. Você precisa caçar o erro e ler a documentação da classe. Não fica óbvio.
Outro problema: classes que jogam o erro na tela e encerram o processo. Assim ninguém consegue tratar o erro de outro jeito. Você lança Exception para avisar que algo deu errado; quem captura decide o que fazer. Exemplo:
<?php
$email = new Fuel\Email;
$email->subject('Meu assunto');
$email->body('E aí, tudo bem?');
$email->to('fulano@example.com', 'Fulano');
try
{
$email->send();
}
catch(Fuel\Email\ValidationFailedException $e)
{
// A validação falhou
}
catch(Fuel\Email\SendingFailedException $e)
{
// O driver não conseguiu enviar o e-mail
}
finally
{
// Roda com ou sem Exception, antes de o fluxo normal continuar
}A classe genérica Exception quase não dá contexto para debug. Dá para criar um tipo especializado estendendo Exception:
<?php
class ValidationException extends Exception {}Assim você usa vários catch e trata cada Exception de um jeito. Isso pode gerar muitas Exceptions customizadas — algumas desnecessárias, porque as SPL exceptions já vêm na extensão SPL.
Se você implementa o método mágico __call() e alguém chama um método inválido, não lance uma Exception genérica (vaga) nem crie uma só para isso. Faça throw new BadMethodCallException;.
O melhor material que encontrei sobre segurança em PHP é o The 2018 Guide to Building Secure PHP Software, da Paragon Initiative.
Todo desenvolvedor PHP precisa aprender o básico de segurança de apps web. Os temas principais:
Tem gente pronta para explorar o seu app. Endureça a segurança. A The Open Web Application Security Project (OWASP) reuniu uma lista de problemas conhecidos e como se proteger. Leitura obrigatória se você liga para segurança. Survive The Deep End: PHP Security, do Padraic Brady, também é um bom guia de segurança para PHP.
Cedo ou tarde todo mundo faz um app PHP com login. Usuário e senha vão para o banco e depois autenticam o usuário.
É importante gerar o hash da senha antes de gravar. Hash e criptografia são coisas bem diferentes e a confusão é comum.
Hash é irreversível: uma função de uma via só. Gera uma string de tamanho fixo que não dá para reverter na prática. Você compara um hash com outro para saber se vieram da mesma string. A original não volta. Sem hash, se um terceiro acessar o banco sem autorização, todas as contas caem.
Criptografia, ao contrário, é reversível — se você tiver a chave. Serve em outros casos. Para guardar senha, é estratégia ruim.
Cada senha também precisa de um salt próprio: uma string aleatória antes do hash. Isso impede ataque de dicionário e o uso de “rainbow tables” (lista reversa de hashes criptográficos de senhas comuns).
Hash e salt importam: muita gente reusa a mesma senha em vários serviços, e a qualidade costuma ser ruim.
Além disso, use um algoritmo especializado de hash de senha, não uma função de hash criptográfico genérica e rápida (SHA256, por exemplo). A lista curta de algoritmos aceitáveis (em junho de 2018):
Hoje o PHP deixa isso fácil.
Hash de senha com password_hash
No PHP 5.5 chegou password_hash(). Na época usa BCrypt, o algoritmo mais forte que o PHP suportava.
Vai ganhar mais algoritmos no futuro, se precisar. A biblioteca password_compat traz essa API para o PHP >= 5.3.7.
Abaixo geramos o hash de uma string e conferimos contra outra. As origens são diferentes (‘senha-secreta’ vs. ‘senha-errada’), então o login falha.
<?php
require 'password.php';
$passwordHash = password_hash('senha-secreta', PASSWORD_DEFAULT);
if (password_verify('senha-errada', $passwordHash)) {
// Senha correta
} else {
// Senha errada
}password_hash() cuida do salt. O salt fica no próprio hash, junto com o algoritmo e o “cost”. password_verify() extrai isso para conferir a senha. Você não precisa de um campo extra no banco para os salts.
Nunca, jamais, confie em entrada externa no seu código PHP. Sanitiza e valida sempre, antes de usar. As funções filter_var() e filter_input() sanitizam texto e validam formatos (por exemplo, e-mail).
Entrada externa é qualquer coisa: dados de formulário em $_GET e $_POST, alguns valores da superglobal $_SERVER e o corpo da requisição HTTP via fopen('php://input', 'r'). Não é só o que o usuário manda no formulário. Arquivos enviados e baixados, valores de session, dados de cookie e respostas de serviços de terceiros também são entrada externa.
Mesmo depois de gravar, combinar e ler de novo, continua sendo entrada externa. Sempre que você processa, imprime, concatena ou inclui dado no código, pergunte: isso passou por filter de verdade? Dá para confiar?
O filter muda conforme o uso. Entrada externa sem filter no HTML pode executar HTML e JavaScript no seu site. Isso é Cross-Site Scripting (XSS) — um ataque perigoso. Para evitar XSS, sanitiza tudo que veio do usuário antes de imprimir: tire tags HTML com strip_tags() ou escape os caracteres especiais para entidades HTML com htmlentities() ou htmlspecialchars().
Outro exemplo: passar opções para a linha de comando. É extremamente perigoso (e em geral uma má ideia). Se precisar, use escapeshellarg() para sanitizar os argumentos do comando.
Último exemplo: usar entrada externa para escolher um arquivo no sistema de arquivos. Dá para explorar isso trocando o nome por um caminho. Remova "/", "../", null bytes e outros caracteres do caminho, senão o código pode carregar arquivos ocultos, privados ou sensíveis.
Sanitização remove (ou escapa) caracteres ilegais ou inseguros da entrada externa.
Sanitiza a entrada externa antes de colocar no HTML ou numa consulta SQL crua. Com parâmetros vinculados no PDO, o PDO sanitiza por você.
Às vezes você precisa permitir algumas tags HTML seguras na entrada. É difícil acertar. Muita gente evita e usa um formato mais restrito, como Markdown ou BBCode. Se precisar de HTML, use uma lib de lista de permissão como HTML Purifier.
Veja os filters de sanitização
unserialize() em dado de usuário ou de fonte não confiável é perigoso. Um atacante pode instanciar objetos (com propriedades definidas por ele) cujos destructors rodam mesmo se você não usar o objeto. Não desserializa dado em que você não confia.
Se precisar passar dado serializado para o usuário, use um formato padrão e seguro, como JSON (via json_decode e json_encode).
Validação confirma que a entrada externa é o que você espera. No cadastro, por exemplo, valide e-mail, telefone ou idade.
Ao criar arquivos de configuração para seus apps, siga uma destas práticas:
.php. Assim, mesmo que alguém acesse a URL, o conteúdo não sai como texto puro.ATENÇÃO: A partir do PHP 5.4.0, a diretiva register_globals foi removida e não pode mais ser usada. Este aviso é só para quem está atualizando um app legado.
Com register_globals ligado, vários tipos de variável (incluindo as de $_POST, $_GET e $_REQUEST) ficam disponíveis no escopo global do app. Isso gera risco de segurança: o app não consegue saber de onde o dado veio.
Exemplo: $_GET['foo'] vira $foo e pode sobrescrever variáveis já declaradas.
Se você usa PHP < 5.4.0, garanta que register_globals esteja desligado.
O log de erros ajuda a achar os pontos problemáticos do app, mas também pode expor a estrutura do app para o mundo. Para proteger o app de problemas causados pela saída dessas mensagens, configure o servidor de um jeito no desenvolvimento e de outro em produção.
Para exibir todos os erros possíveis no desenvolvimento, configure o php.ini assim:
display_errors = On
display_startup_errors = On
error_reporting = -1
log_errors = OnO valor
-1exibe todos os erros possíveis, mesmo quando versões futuras do PHP adicionarem níveis e constantes. A constanteE_ALLtambém funciona assim a partir do PHP 5.4. — php.net
A constante de nível E_STRICT surgiu no 5.3.0 e não fazia parte de E_ALL. Entrou em E_ALL no 5.4.0. O que isso muda? Para relatar todos os erros no 5.3, use -1 ou E_ALL | E_STRICT.
Todos os erros possíveis por versão do PHP
-1 ou E_ALL-1 ou E_ALL | E_STRICT-1 ou E_ALLPara esconder os erros em produção, configure o php.ini assim:
display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL
log_errors = OnCom essa configuração em produção, os erros continuam no log do servidor web, mas não aparecem para o usuário. Mais detalhes no manual do PHP:
Escrever testes automatizados para o seu código PHP é boa prática e resulta em apps bem construídos. Testes automatizados garantem que o app não quebra quando você altera código ou adiciona funcionalidade. Não ignore.
Há várias ferramentas (ou frameworks) de teste em PHP, com abordagens diferentes. O objetivo é o mesmo: evitar teste manual e equipes grandes de QA só para conferir se a mudança recente não quebrou o que já funcionava.
Da Wikipedia:
Desenvolvimento guiado por testes (TDD) é um processo de desenvolvimento de software baseado na repetição de um ciclo bem curto: primeiro você escreve um teste automatizado que falha e descreve a melhoria ou a função nova; depois escreve o código que faz o teste passar; por fim, refatora até o código ficar aceitável. Kent Beck, a quem se atribui ter desenvolvido ou “redescoberto” a técnica, disse em 2003 que TDD incentiva um design simples e gera confiança.
Há vários tipos de testes que você pode fazer no seu app:
Teste unitário garante que funções, classes e métodos fazem o que devem, do momento em que você os cria até o fim do ciclo de desenvolvimento. Você checa o que entra e o que sai e confirma que a lógica interna está certa. Com injeção de dependência, mocks e stubs, você verifica se as dependências são usadas do jeito certo e amplia a cobertura.
Quando você cria uma classe ou função, escreva um teste unitário para cada comportamento. No mínimo: falha com
argumento ruim, funciona com argumento válido. Assim, mudanças futuras não quebram o que já funcionava. A alternativa é
var_dump() num test.php — e isso não é jeito de montar um app, grande ou pequeno.
Outro uso: contribuir em código aberto. Um teste que falha e mostra o que está quebrado, depois passa com a correção, aumenta muito a chance de o patch ser aceito. Se o seu projeto aceita pull requests, cobre isso como requisito.
PHPUnit é o framework de fato para testes unitários em apps PHP. Há alternativas:
Da Wikipedia:
Teste de integração (às vezes chamado Integration and Testing, abreviado “I&T”) é a fase em que módulos individuais são combinados e testados em grupo. Vem depois dos testes unitários e antes dos testes de validação. Os módulos já testados unitariamente entram, são agrupados em conjuntos maiores, recebem os testes do plano de integração e saem como sistema integrado, pronto para o teste de sistema.
Muitas ferramentas de testes unitários servem também para integração: os princípios são os mesmos.
Também chamados de testes de aceitação. Em vez de só checar unidades isoladas ou a conversa entre elas, você usa ferramentas para criar testes automatizados que exercitam o app de verdade. Em geral usam dados reais e simulam usuários de verdade.
Há dois tipos de Behavior-Driven Development (BDD): SpecBDD e StoryBDD. SpecBDD descreve o comportamento técnico do código. StoryBDD descreve o comportamento de negócio, das funcionalidades e das interações. O PHP tem frameworks para os dois.
No StoryBDD, você escreve histórias legíveis que descrevem o comportamento do app. Essas histórias rodam como testes reais no app. No PHP, o framework de StoryBDD é o Behat, inspirado no Cucumber do Ruby. Ele implementa a DSL Gherkin para descrever o comportamento das funcionalidades.
No SpecBDD, você escreve especificações que descrevem como o código deve se comportar. Em vez de testar uma função ou método, você descreve o comportamento esperado. No PHP, use o PHPSpec. O framework é inspirado no projeto RSpec do Ruby.
Além dos frameworks de testes e de BDD, há frameworks genéricos e bibliotecas auxiliares. Servem em qualquer abordagem.
Você pode fazer deploy de apps PHP em servidores web de produção de várias formas.
O PaaS (plataforma como serviço) entrega a arquitetura de sistema e de rede para rodar apps PHP na web. Quase sem configuração para subir um app ou um framework PHP.
O PaaS virou um jeito comum de fazer deploy, hospedar e escalar apps PHP de qualquer tamanho. A lista de provedores PHP de PaaS está na seção de recursos.
Se você já administra sistemas, ou quer aprender, servidores virtuais ou dedicados dão controle total do ambiente de produção do seu app.
O PHP, com o FastCGI Process Manager (FPM) nativo, combina bem com o nginx, um servidor web leve e de alto desempenho. Usa menos memória que o Apache e lida melhor com mais requisições simultâneas. Isso importa especialmente em servidores virtuais com pouca memória.
PHP e Apache têm uma longa história juntos. O Apache é altamente configurável e tem muitos módulos para estender o que ele faz. É uma escolha comum em servidores compartilhados e fácil de configurar para frameworks PHP e apps de código aberto como WordPress. Por padrão, o Apache usa mais recursos que o nginx e não aguenta tantos visitantes ao mesmo tempo.
O Apache tem várias formas de rodar PHP. A mais comum e mais fácil de configurar é o prefork MPM com mod_php. Não é a mais eficiente em memória, mas é a mais simples de fazer funcionar. É a melhor escolha se você não quer se aprofundar na administração do servidor. Se usar mod_php, você PRECISA usar o prefork MPM.
Se quiser mais desempenho e estabilidade no Apache, use o mesmo FPM do nginx e rode o worker MPM ou o event MPM com mod_fastcgi ou mod_fcgid. Essa configuração usa bem menos memória e é bem mais rápida, mas dá mais trabalho para montar.
No Apache 2.4 ou posterior, use mod_proxy_fcgi para ter bom desempenho com configuração simples.
Se você altera o schema do banco na mão, roda testes na mão e depois atualiza os arquivos na mão, pare. Cada tarefa manual extra no deploy de uma versão nova aumenta a chance de erro grave. Atualização simples, build completo ou integração contínua: automação de build é sua aliada.
Tarefas que você pode automatizar:
Ferramentas de deploy são conjuntos de scripts para tarefas comuns de deploy. Não fazem parte do seu software: agem de fora.
Há várias ferramentas de código aberto para automação de build e deploy. Umas em PHP, outras não. Se a ferramenta serve melhor para o trabalho, use — mesmo que não seja PHP. Exemplos:
Phing controla empacotamento, deploy ou testes a partir de um XML de build. O Phing (baseado no Apache Ant) traz um conjunto rico de tarefas para instalar ou atualizar um app web e aceita tarefas extras em PHP. É sólido, robusto e existe há muito tempo. O ponto fraco: configurar via XML parece datado.
Capistrano é para programadores de nível intermediário a avançado executarem comandos de forma estruturada e repetível em uma ou mais máquinas remotas. Vem pré-configurado para apps Ruby on Rails, mas funciona bem com PHP. Para usar bem o Capistrano, você precisa conhecer Ruby e Rake.
Ansistrano é um par de roles do Ansible para gerenciar o processo de deploy (deploy e rollback) de apps em linguagens de script como PHP, Python e Ruby. É o Capistrano portado para Ansible. Já é usado por bastante empresa PHP.
Deployer é uma ferramenta de deploy escrita em PHP. Simples e funcional. Roda tarefas em paralelo, faz deploy atômico e mantém consistência entre servidores. Tem recipes prontas para Symfony, Laravel, Zend Framework e Yii. O artigo de Younes Rafie, Deploy fácil de apps PHP com Deployer, é um bom tutorial.
Magallanes também é em PHP, com configuração simples em YAML. Suporta vários servidores e ambientes, deploy atômico e tarefas prontas para ferramentas e frameworks comuns.
Gerenciar e configurar muitos servidores é trabalhoso. Há ferramentas para automatizar a infraestrutura: os servidores certos, configurados certo. Muitas integram com os grandes provedores de nuvem (Amazon Web Services, Heroku, DigitalOcean etc.) para gerenciar instâncias, o que facilita escalar o app.
Ansible gerencia a infraestrutura com arquivos YAML. Fácil de começar e dá conta de apps complexos e em grande escala. Tem API para gerenciar instâncias na nuvem e inventário dinâmico com certas ferramentas.
Puppet tem linguagem e tipos de arquivo próprios para gerenciar servidores e configurações. Funciona em modo master/client ou “master-less”. No master/client, os clients consultam o master em intervalos e se atualizam se precisar. No master-less, você envia as mudanças para os nodes.
Chef é um framework poderoso de integração de sistemas, escrito em Ruby. Com ele você monta o ambiente de servidor inteiro ou máquinas virtuais. Integra bem com Amazon Web Services pelo serviço OpsWorks.
Integração contínua é a prática de desenvolvimento em que os membros do time integram o trabalho com frequência, em geral pelo menos uma vez por dia — várias integrações por dia. Muitos times percebem que isso reduz bastante os problemas de integração e permite desenvolver software coeso com mais rapidez.
– Martin Fowler
Há várias formas de implementar integração contínua em PHP. O Travis CI tornou a integração contínua viável até para projetos pequenos. Travis CI é um serviço hospedado de CI. Integra com GitHub e suporta várias linguagens, inclusive PHP. O GitHub tem workflows de integração contínua com GitHub Actions.
Rodar o app em ambientes diferentes no desenvolvimento e na produção gera bugs estranhos na hora do deploy. Em equipe, também é difícil manter cada ambiente de desenvolvimento com a mesma versão de todas as bibliotecas.
Se você desenvolve no Windows e faz deploy no Linux (ou em qualquer coisa que não seja Windows), ou trabalha em equipe, considere usar uma máquina virtual. Parece complicado, mas além dos ambientes de virtualização conhecidos como VMware ou VirtualBox, há outras ferramentas que montam um ambiente virtual em poucos passos.
O Vagrant monta suas boxes virtuais em cima dos ambientes de virtualização conhecidos e configura tudo a partir de um único arquivo. Você pode preparar as boxes na mão ou usar software de provisionamento como Puppet ou Chef. Provisionar a box base garante que várias boxes fiquem iguais e elimina listas longas de comandos de setup. Você também pode destruir a box e recriá-la sem muitos passos manuais. Uma instalação limpa fica fácil.
O Vagrant cria pastas compartilhadas entre o host e a máquina virtual. Você cria e edita os arquivos no host e roda o código na máquina virtual.
O Docker é uma alternativa leve a uma máquina virtual completa — o nome vem dos containers. Um container é um bloco de construção que, no caso mais simples, faz uma tarefa só, por exemplo rodar um servidor web. Uma image é o pacote que você usa para criar o container. O Docker tem um repositório cheio delas.
Um app LAMP típico pode ter três containers: um servidor web, um processo PHP-FPM e o MySQL. Como nas pastas compartilhadas do Vagrant, você deixa os arquivos do app onde estão e diz ao Docker onde encontrá-los.
Você pode criar containers na linha de comando (veja o exemplo abaixo) ou, para facilitar a manutenção, montar um docker-compose.yml no projeto dizendo quais criar e como eles se comunicam.
O Docker ajuda se você desenvolve vários sites e quer a separação de uma máquina virtual por projeto, mas não tem disco nem tempo para manter tudo atualizado. É eficiente: instalação e downloads são mais rápidos, você guarda só uma cópia de cada image por mais que ela seja usada, os containers usam menos RAM e compartilham o mesmo kernel do sistema, então você roda mais servidores ao mesmo tempo. Para e inicia em segundos, sem esperar o servidor inteiro subir.
Depois de instalar o Docker na sua máquina, você sobe um servidor web com um comando.
O comando abaixo baixa um Apache pronto com a versão mais recente do PHP, mapeia /path/to/your/php/files para a raiz do site e você acessa em http://localhost:8080:
docker run -d --name my-php-webserver -p 8080:80 -v /path/to/your/php/files:/var/www/html/ php:apacheIsso inicializa e sobe o container. -d roda em segundo plano. Para parar e iniciar, use docker stop my-php-webserver e docker start my-php-webserver (os outros parâmetros não são necessários de novo).
O comando acima sobe um servidor básico rápido. Dá para ir bem além (e há milhares de images prontas no Docker Hub). Vale aprender a terminologia e ler o guia do usuário do Docker para aproveitar de verdade. Não rode código baixado sem conferir se é seguro — images não oficiais podem estar sem os patches de segurança mais recentes. Na dúvida, fique nos repositórios oficiais.
O site PHPDocker.io gera automaticamente os arquivos de um stack LAMP/LEMP completo, com a versão do PHP e as extensões que você escolher.
O PHP já é rápido. Gargalos aparecem quando você faz conexões remotas, carrega arquivos, etc. Há várias ferramentas para acelerar partes do app ou reduzir quantas vezes essas tarefas pesadas precisam rodar.
Quando o PHP executa um arquivo, primeiro precisa compilá-lo em opcodes (instruções de máquina para a CPU). Se o código-fonte não mudou, os opcodes são os mesmos. Recompilar de novo é desperdício de CPU.
Um opcode cache evita essa compilação repetida. Guarda os opcodes na memória e reusa nas próximas chamadas. Em geral, checa a assinatura ou a data de alteração do arquivo antes, caso tenha havido mudança.
Um opcode cache provavelmente deixa o app bem mais rápido. Desde o PHP 5.5 já vem um embutido — Zend OPcache. Dependendo do pacote ou da distribuição, costuma vir ligado por padrão. Confira opcache.enable e a saída do phpinfo() para ter certeza. Em versões anteriores, use a extensão PECL.
Leia mais sobre opcode caches:
Há casos em que vale colocar objetos individuais em cache: dados caros de obter ou consultas ao banco cujo resultado quase não muda. Use um software de cache de objetos para guardar esses dados na memória e acessá-los depois, bem rápido. Se você grava o resultado no cache depois de buscá-lo e, nas próximas requisições, lê direto dali, o desempenho sobe e a carga nos servidores de banco cai.
Muitas soluções populares de opcode cache também deixam guardar dados customizados. Mais um motivo para usá-las. APCu e WinCache oferecem APIs para gravar dados do PHP no cache em memória.
Os sistemas de cache de objetos em memória mais usados são APCu e Memcached. APCu é uma ótima escolha: API simples para incluir seus dados no cache em memória, fácil de instalar e usar. A limitação real do APCu é ficar preso ao servidor em que está instalado. Memcached, por outro lado, roda como serviço separado e é acessível pela rede: você guarda objetos num armazenamento bem rápido, num ponto central, e vários sistemas leem dali.
Se o cache é compartilhado entre processos PHP depende de como o PHP roda. Com PHP-FPM, o cache é compartilhado entre todos os processos de todos os pools. Quando o PHP roda como app (Fast-)CGI no servidor web, o cache não é compartilhado: cada processo PHP tem seus próprios dados no APCu. Na linha de comando, o cache não é compartilhado e só existe enquanto o comando roda. Leve isso em conta. Considere Memcached: ele não fica amarrado aos processos PHP.
Em rede, APCu costuma ser mais rápido no acesso; Memcached escala mais rápido e mais longe. Se você não vai ter vários servidores rodando o app, ou não precisa dos extras do Memcached, APCu provavelmente é a melhor escolha para cache de objetos.
Exemplo com APCu:
<?php
// verifica se há dados salvos como 'expensive_data' no cache
$data = apcu_fetch('expensive_data');
if ($data === false) {
// dados não estão no cache; grava o resultado da chamada cara para uso posterior
apcu_add('expensive_data', $data = get_expensive_data());
}
print_r($data);PHPDoc é um padrão informal para comentar código PHP. Existem muitas tags diferentes. A lista completa de tags e exemplos está no manual do PHPDoc.
Abaixo, um exemplo de como documentar uma classe com alguns métodos:
<?php
/**
* @author A Name <a.name@example.com>
* @link https://docs.phpdoc.org/
*/
class DateTimeHelper
{
/**
* @param mixed $anything Qualquer valor que possamos converter em um objeto \DateTime
*
* @throws \InvalidArgumentException
*
* @return \DateTime
*/
public function dateTimeFromAnything($anything)
{
$type = gettype($anything);
switch ($type) {
// Código que tenta retornar um objeto \DateTime
}
throw new \InvalidArgumentException(
"Falha ao converter o parâmetro do tipo '{$type}' em objeto DateTime"
);
}
/**
* @param mixed $date Qualquer valor que possamos converter em um objeto \DateTime
*
* @return void
*/
public function printISO8601Date($date)
{
echo $this->dateTimeFromAnything($date)->format('c');
}
/**
* @param mixed $date Qualquer valor que possamos converter em um objeto \DateTime
*/
public function printRFC2822Date($date)
{
echo $this->dateTimeFromAnything($date)->format('r');
}
}A documentação da classe tem a tag @author e a tag @link. @author registra o autor do código e pode se repetir se houver vários autores. @link aponta para um site relacionado ao código.
No primeiro método, a tag @param documenta tipo, nome e descrição do parâmetro. Há também @return e @throws, para o tipo de retorno e as exceções que o método pode lançar.
O segundo e o terceiro métodos são parecidos e, como o primeiro, têm só uma tag @param. A diferença importante no bloco PHPDoc é a presença ou ausência de @return.
@return void deixa explícito que não há retorno. Omitir @return void historicamente significa a mesma coisa: sem retorno.
No começo, é difícil achar gente da comunidade PHP que vale a pena seguir. Uma lista curta para você começar:
Em vez de reinventar a roda, muita gente usa frameworks para montar apps web. O framework abstrai o baixo nível e entrega interfaces simples para tarefas comuns.
Você não precisa de framework em todo projeto. Às vezes PHP puro é o caminho certo. Se precisar, há três tipos:
Micro-frameworks são, no fundo, um wrapper que roteia uma requisição HTTP para um callback, controller, método, etc. o mais rápido possível. Às vezes vêm com algumas bibliotecas extras, tipo wrappers básicos de banco. São usados sobretudo para serviços HTTP remotos.
Muitos frameworks acrescentam um monte de recursos em cima do que o micro-framework já oferece; esses são os full-stack. Costumam vir com ORM, pacotes de autenticação, etc.
Frameworks de componentes são coleções de bibliotecas especializadas, cada uma com um propósito. Você pode juntar componentes de origens diferentes e montar um micro-framework ou um full-stack.
Como dito acima, componentes são outro jeito de criar, distribuir e usar código compartilhado. Há vários repositórios. Os dois principais:
Os dois vêm com ferramenta de linha de comando para instalar e atualizar. O detalhe está em Gestão de dependências.
Também há frameworks feitos de componentes e fornecedores que não entregam framework nenhum. São outra fonte de pacotes — de preferência com pouca ou nenhuma dependência de outros pacotes ou de um framework específico.
Exemplo: você usa o pacote Validation do FuelPHP sem puxar o framework FuelPHP.
Os componentes Illuminate do Laravel vão ficar mais desacoplados do framework. Por enquanto, a lista só traz os que já estão bem separados.
Assine boletins semanais para acompanhar bibliotecas novas, notícias, eventos e avisos, além de outros recursos que saem de vez em quando:
Há boletins semanais em outras plataformas também. Veja esta lista.
Há muitos livros de PHP. Infelizmente alguns já estão velhos e errados. Evite, em especial, livros de “PHP 6” — essa versão nunca vai existir. Depois do 5.6 veio o “PHP 7”, em parte por isso.
Esta seção é um documento vivo de livros recomendados sobre PHP. Quer incluir o seu? Envie um PR. A relevância será avaliada.
A comunidade PHP é grande e diversa. Os membros estão dispostos a ajudar quem está começando. Entre no grupo de usuários PHP (PUG) da sua cidade ou vá a conferências maiores para ver as boas práticas deste guia na prática. Você pode conversar no IRC no canal #phpc em irc.libera.chat e seguir @phpc no Discord, no X ou no Mastodon. Conheça outros desenvolvedores, aprenda assuntos novos e, acima de tudo, faça amigos. Outro recurso da comunidade é o StackOverflow.
Se você mora numa cidade grande, quase sempre tem um grupo de usuários PHP por perto. Ache o PUG local no
PHP.ug. Outras opções: Meetup.com ou uma busca por php user group near me
no seu buscador (por exemplo, Google). Se você mora numa cidade pequena e não tem
PUG local, crie um!
Dois grupos globais merecem destaque: NomadPHP e PHPWomen. O NomadPHP faz encontros online duas vezes por mês, com palestras de alguns dos melhores palestrantes da comunidade PHP. O PHPWomen é um grupo aberto, criado para as mulheres do mundo PHP. Qualquer pessoa que apoie uma comunidade mais diversa pode participar. O PHPWomen oferece rede de apoio, mentoria e educação, e promove um ambiente profissional e acolhedor para mulheres.
A comunidade PHP também organiza conferências regionais e nacionais em vários países. Pessoas conhecidas da comunidade costumam palestrar nesses eventos. É uma boa chance de aprender direto com quem lidera a área.
O ElePHPant é o mascote do PHP: um elefante. Vincent Pontier desenhou o mascote para o projeto em 1998 — pai espiritual de milhares de ElePHPants pelo mundo. Dez anos depois nasceram os pelúcias. Hoje você encontra ElePHPants em várias conferências de PHP e na mesa de muita gente, por diversão e inspiração.