Última revisão & mantenedores
O original em inglês deste documento foi revisado pela última vez em 3 de julho de 2014. Ele foi alterado pela última vez em 3 de julho de 2014.
O original em inglês é mantido por Alex Cabal.
Converse com ele se achar que ele possa te ajudar em algo, ou ainda se tiver sugestões ou correções para o documento original em inglês.
Introdução
O PHP é uma linguagem complexa que, por anos, sofreu com reviravoltas, mudanças de direções, extensões forçadas e hacks. Ele é bastante inconsistente e, às vezes, até bugado. Cada versão é única com suas funcionalidades, estranhezas e peculiaridades, e fica difícil acompanhar que versão tem quais problemas. É fácil entender por que ele é tão odiado de vem em quando.
Apesar disso, ele é a linguagem mais popular da web atualmente. Por causa da sua longa história, você encontrará um grande número de tutoriais de como fazer as coisas básicas, como criptografia de senhas e acesso a bancos de dados. O problema é que você tem uma chance bem grande de, em cinco tutoriais, encontrar cinco jeitos diferentes de se fazer cada uma dessas coisas. Qual deles é o jeito "certo"? Algum dos outros jeitos tem bugs escondidos ou armadilhas? É realmente bem difícil de saber e você ficará vagando eternamente na internet em busca da resposta perfeita.
Essa também é uma das razões que fazem com que novos programadores PHP sejam frequentemente criticados por código feio, ultrapassado e inseguro. Mas eles não podem melhorar isso se o primeiro resultado de uma busca no Google for um artigo de quatro anos atrás ensinando uma técnica de cinco anos atrás!
Este documento tenta mirar nesse problema. Ele é uma tentativa de compilar um conjunto de instruções básicas para o que podem ser consideradas melhores práticas para problemas e tarefas comuns no PHP. Se uma tarefa de baixo nível tem muitas soluções possíveis e confusas no PHP, ela deve estar aqui.
O que é
Este é um guia sugerindo a melhor direção a tomar quando você der de cara com uma das tarefas de baixo nível, que um programador PHP frequentemente deve encontrar, e que sejam pouco claras em virtude das muitas opções que o PHP oferece. Por exemplo: conectar com um banco de dados é uma tarefa frequente com uma gama enorme de soluções possíveis em PHP, nem todas sendo tão boas assim — por isso, ela está aqui neste documento.
Esta é uma série de soluções pequenas e introdutórias. Os exemplos devem te entregar tudo que precisa para configurações básicas. Você deve fazer sua própria pesquisa para torná-las algo útil para você.
Ele mostra o que consideramos o PHP mais moderno possível. No entanto, isso significa que se você estiver usando uma versão mais antiga do PHP, algumas das funcionalidades necessárias para se aproveitar das soluções daqui podem não estar disponíveis para você.
Este é um documento vivo e faremos o melhor possível para mantê-lo atualizado com a constante evolução do PHP.
O que não é
Este documento não é um tutorial de PHP. Você deve aprender as questões básicas de sintaxe da linguagem em algum outro lugar.
Não é um guia para problemas comuns das aplicações web, como armazenamento de cookies, cache, estilo de código, documentação etc.
Não é um guia de segurança. Apesar de abordar algumas questões relacionadas à segurança, é esperado que você faça sua própria pesquisa no tocante à segurança de suas aplicações PHP. Em particular, você deve revisar cuidadosamente qualquer solução proposta aqui antes de implementá-la, afinal, seu código é problema seu.
Não é um defensor de nenhum estilo de código, padrão de desenvolvimento ou framework.
Não é um defensor de nenhum modo para realização de tarefas de mais alto nível como gerenciamento de usuários, sistemas de acesso etc. Esse documento é estritamente focado em tarefas de baixo nível que podem ser, devido à longa história do PHP, confusas ou pouco claras.
Não é a solução definitiva, nem mesmo a única solução. Alguns dos métodos descritos abaixo podem não ser os melhores para sua situação em particular e sempre existem vários jeitos diferentes de se chegar ao mesmo fim. Em particular, aplicações com alto tráfego podem se beneficiar de soluções mais complexas para alguns dos problemas citados.
Que versão do PHP você está usando?
PHP 5.5.9-1ubuntu4.2, instalado no Ubuntu 14.04 LTS.
O PHP é a tartaruga de 100 anos do mundo web. Seu casco está marcado por uma história rica, complicada e retorcida. Num ambiente de hospedagem compartilhada, a configuração dele pode restringir suas possibilidades.
A fim de mantermos um pouco de sanidade, vamos focar apenas em uma versão do PHP. Em 30 de abril de 2014, essa versão é a PHP 5.5.9-1ubuntu4.2. Essa é a versão instalada usando o apt-get num servidor Ubuntu 14.04 LTS. Em outras palavras, esse é o padrão seguro que muitos utilizam.
Talvez você descubra que algumas dessas soluções funcionam em versões diferentes, ou mais antigas, do PHP. Se isso acontecer, cabe a você pesquisar as implicações dos possíveis bugs ou problemas de segurança dessas versões antigas.
Armazenando senhas
Use as funções embutidas de password hashing para fazer hash e comparar senhas.
Hash é a maneira padrão de proteger uma senha de usuário antes de armazená-la numa banco de dados. Muitos dos algoritmos comuns, como o md5 ou até o sha1, são inseguros para armazenar senhas porque hackers podem facilmente crackear senhas hasheadas com esses algoritmos.
O PHP fornece uma biblioteca embutida para hash de senhas que usa o algoritmo bcrypt, atualmente considerado o melhor algoritmo para hash de senhas.
Exemplo
<?php
// Faz o hash da senha. $hashedPassword será uma string de 60 caracteres.
$hashedPassword = password_hash('my super cool password', PASSWORD_DEFAULT);
// Agora você pode armazenar de forma segura o conteúdo de $hashedPassword no seu banco de dados!
// Verifica se um usuário forneceu a senha correta comparando o que ele digitou com nosso hash
password_verify('the wrong password', $hashedPassword); // false
password_verify('my super cool password', $hashedPassword); // true
?>
Pegadinhas
- Muitas fontes irão recomendar que você também aplique um "salt" na sua senha antes de fazer o hash. Essa é uma boa ideia, e a função password_hash() já aplica o salt para você. Isso significa que você não precisa aplicar o salt por conta própria.
Mais informações
Conectando e consultando um banco de dados MySQL
Use a PDO e sua funcionalidade de prepared statement.
Existem muitas formas de conectar um banco de dados MySQL no PHP. A PDO (PHP Data Objects) é a mais nova e robusta delas. A PDO tem uma interface consistente entre vários tipos diferentes de bancos de dados, usa uma abordagem orientada a objetos e suporta mais funcionalidades oferecidas pelos bancos de dados mais novos.
Você deve usar as funções de prepared statement (instruções preparadas) do PDO para ajudar na prevenção de ataques de SQL injection (injeção de SQL). Usar a função bindValue() garante que seu SQL está seguro contra ataques de SQL injection de primeira ordem. (Ela não é 100% segura no entanto, leia Mais informações para mais detalhes. ) No passado, isso era conseguido com algumas combinações misteriosas de funções "magic quote". O PDO torna toda aquela gosma desnecessária.
Exemplo
<?php
try{
// Cria uma nova conexão.
// Você vai provavelmente querer trocar o hostname por localhost no seu primeiro parâmetro.
// Veja que declaramos o charset (codificação de caracteres) como utf8mb4. Isso alerta a conexão de que passaremos dados UTF-8. Talvez nem seja obrigatório dependendo da sua configuração, mas pode te livrar de dores de cabeça se você estiver tentando armazenar strings Unicode em seu banco de dados. Veja "Pegadinhas".
// As opções PDO que passamos são as seguintes:
// \PDO::ATTR_ERRMODE habilita as exceções para erros. É opcional, mas pode ser útil.
// \PDO::ATTR_PERSISTENT desabilita conexões persistentes, que podem causar problemas de concorrência em certos casos. Veja "Pegadinhas".
$link = new \PDO( 'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
'your-username',
'your-password',
array(
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_PERSISTENT => false
)
);
$handle = $link->prepare('select Username from Users where UserId = ? or Username = ? limit ?');
// Bug do PHP: se você não especificar PDO::PARAM_INT, a PDO pode colocar aspas no argumento. Isso pode bagunçar algumas queries do MySQL que não esperam que números inteiros tenham aspas em volta.
// Veja: https://bugs.php.net/bug.php?id=44639
// Se você não tiver certeza se um valor que está passando é um número inteiro, use a função is_int().
$handle->bindValue(1, 100, PDO::PARAM_INT);
$handle->bindValue(2, 'Bilbo Baggins');
$handle->bindValue(3, 5, PDO::PARAM_INT);
$handle->execute();
// Usar o método fetchAll() pode ser muito custoso se você estiver consultando um número realmente grande de registros.
// Se esse é o caso, você pode usar o método fetch() e percorrer cada resultado num laço linha por linha.
// Você também pode retornar arrays e outras coisas em vez de objetos. Veja a documentação da PDO para detalhes.
$result = $handle->fetchAll(\PDO::FETCH_OBJ);
foreach($result as $row){
print($row->Username);
}
}
catch(\PDOException $ex){
print($ex->getMessage());
}
?>
Pegadinhas
- Não passar o parâmetro PDO::PARAM_INT quando faz o binding de variáveis do tipo integer pode fazer com que o PDO coloque aspas em volta delas às vezes. Isso pode estragar certas queries do MySQL. Veja esse relatório de bug.
- Não definir o charset como utf8mb4 na sua string de conexão pode fazer com que os dados Unicode sejam armazenados incorretamente no seu banco de dados dependendo de sua configuração.
- Mesmo se você declarar seu charset como utf8mb4, garanta que suas tabelas reais no banco de dados também estejam. Se quiser saber porque usamos o utf8mb4 em vez de apenas utf8, veja a seção PHP e UTF-8.
- Habilitar conexões persistentes possivelmente levará a problemas estranhos relacionados a concorrência. Este não é um problema do PHP, é um problema em nível de aplicação. Conexões persistentes são seguras de se usar desde que você considere as consequências. Veja esta pergunta no Stack Overflow.
- Você pode executar mais de uma instrução SQL em uma única chamada execute(). Apenas separe as instruções por ponto-e-vírgula. Ubuntu 12.04 (PHP <= 5.3.10): Se fizer isso, cuidado com esse bug, que não foi arrumado na versão do PHP que vem no Ubuntu 12.04.
Mais informações
- Manual do PHP: PDO
- Why you should be using PHP's PDO for database access (Por que você deveria usar a PDO do PHP para acesso a banco de dados)
- Stack Overflow: PHP PDO vs normal mysql_connect
- Stack Overflow: Are PDO prepared statements sufficient to prevent SQL injection? (Prepared statements da PDO são suficientes para prevenir SQL injection?)
- Stack Overflow: Whether to use "SET NAMES" ("SET NAMES" deve ser utilizado?)
Autoloading de classes (carregamento automático de classes)
Use a função spl_autoload_register() para registrar sua função de autoloading.
O PHP tem várias maneiras de carregar automaticamente arquivos contendo classes que não tinham sido carregadas ainda. A maneira mais antiga é usar uma função global mágica chamada __autoload(). No entanto, você só pode ter uma função __autoload definida por vez, assim, se você estiver incluindo uma biblioteca que também usou a função __autoload(), acontecerá um conflito.
O jeito correto de lidar com isso é dar a sua função de autoload um nome único, depois registrá-la com a função spl_autoload_register(). Essa função permite que mais de uma função __autoload() seja definida, dessa forma você não conflita com nenhum outro código que tenha um __autoload() próprio.
Exemplo
<?php
// Primeiro, defina sua função autoload.
function MyAutoload($className){
include_once($className . '.php');
}
// Depois, registre-a no PHP.
spl_autoload_register('MyAutoload');
// Faça o teste!
// Como não incluímos um arquivo com a definição do objeto MyClass, nosso autoloader irá localizar e incluir o arquivo MyClass.php.
// Nesse exemplo, assuma que a classe MyClass está definida no arquivo MyClass.php.
$var = new MyClass();
?>
Mais informações
Aspas duplas versus aspas simples olhando pela performance
Não importa, de verdade.
Já deu muito o que falar a discussão se devemos definir strings com aspas simples (') ou aspas duplas ("). Strings com aspas simples não são interpretadas, assim, tudo que você colocar na string será mostrado. Strings com aspas duplas são interpretadas e variáveis PHP na string também são. Adicionalmente, caracteres de escape, como \n para quebra de linha e \t para tabulação, não são interpretados nas strings com aspas simples, mas são interpretados nas strings com aspas duplas.
Como as strings com aspas duplas são avaliadas em tempo de execução, a teoria é que usar strings com aspas simples aumentaria a performance pois o PHP não teria que interpretar cada uma das strings. Mesmo com isso sendo verdade num certo ponto, para as aplicações médias do dia-a-dia a diferença é tão pequena que realmente não importa. Assim, para uma aplicação média, não importa o que você escolha. Para aplicações extremamente requisitadas, pode fazer um pouco de diferença. Tome sua decisão dependendo do que sua aplicação vai precisar, mas, não importa qual escolha, seja consistente.
Mais informações
define() versus const
Use define() a menos que você tenha que se preocupar com legibilidade, constantes de classe ou micro-otimizações.
No PHP tradicionalmente constantes são definidas usando a função define(). Mas, em algum momento, o PHP passou a também poder declarar constantes através da palavra-chave const. Qual delas você devia usar para quando for definir suas constantes?
A resposta está nas pequenas diferenças entre as duas maneiras.
- a função define() define constantes em tempo de execução, enquanto a palavra-chave const define constantes em tempo de compilação. Isso dá a const um ganho bem pequeno de velocidade, mas não o suficiente para se preocupar com isso, a menos que você esteja construindo um software de larga escala.
- define() coloca as constantes no escopo global, embora você possa incluir namespaces ao nome de sua constante. Isso significa que você não pode usar define() para definir constantes de classe.
- define() permite que você use expressões tanto no nome da constante quanto em seu valor, diferente de const, que não permite nenhum dos dois. Isso torna define() muito mais flexível.
- define() pode ser chamada dentro de um bloco if(), enquanto const não.
Exemplo
<?php
// Vejamos como os dois métodos tratam namespaces
namespace MiddleEarth\Creatures\Dwarves;
const GIMLI_ID = 1;
define('MiddleEarth\Creatures\Elves\LEGOLAS_ID', 2);
echo(\MiddleEarth\Creatures\Dwarves\GIMLI_ID); // 1
echo(\MiddleEarth\Creatures\Elves\LEGOLAS_ID); // 2; veja que usamos define(), mas o namespace ainda é reconhecido
// Agora declaramos algumas constantes com bits deslocados representando modos de entrar em Mordor.
define('TRANSPORT_METHOD_SNEAKING', 1 << 0); // OK!
const TRANSPORT_METHOD_WALKING = 1 << 1; // Erro de compilação! const não pode usar expressões como valor
// Em seguida, constantes condicionais.
define('HOBBITS_FRODO_ID', 1);
if($isGoingToMordor){
define('TRANSPORT_METHOD', TRANSPORT_METHOD_SNEAKING); // OK!
const PARTY_LEADER_ID = HOBBITS_FRODO_ID // Erro de compilação: const não pode ser usado em um bloco if
}
// Por fim, constantes de classe
class OneRing{
const MELTING_POINT_CELSIUS = 1000000; // OK!
define('MELTING_POINT_ELVISH_DEGREES', 200); // Erro de compilação: não se pode usar define() dentro de uma classe
}
?>
Como no fim define() é mais flexível, você deveria usá-lo para evitar dor de cabeça, a menos que você realmente precise de constantes de classe. Usar const geralmente resulta em código mais legível, mas perde-se na flexibilidade.
Não importa qual você use, seja consistente!
Mais informações
Cache de opcode PHP
Que sorte: o PHP já tem embutido o cache de opcode!
Em versões antigas do PHP, cada vez que um script era executado ele precisava ser compilado do início, mesmo se ele já tivesse sido compilado anteriormente. Os caches de opcode eram softwares adicionais que guardavam versões de código PHP previamente compilados, acelerando um pouco as coisas. Haviam muitas opções de cache que você poderia usar.
Felizmente para nós, a versão do PHP que vem com o Ubuntu 14.04 inclui um cache de opcode embutido que vem ligado por padrão. Assim, você não precisa fazer nada!
Para a posteridade, abaixo seguem algumas instruções de como usar um cache de opcode no Ubuntu 12.04, que não vem com um embutido.
Mais informações
PHP e Memcached
Se precisar de cache distribuído, use a biblioteca cliente do Memcached. Caso contrário, use o APCu.
A caching system can often improve your app's performance. Memcached is a popular choice and it works with many languages, including PHP.
However, when it comes to accessing a Memcached server from a PHP script, you have two different and very stupidly named choices of client library: Memcache and Memcached. They're different libraries with almost the same name, and both are used to access a Memcached instance.
It turns out that the Memcached library is the one that best implements the Memcached protocol. It includes a few useful features that the Memcache library doesn't, and seems to be the one most actively developed.
However if you don't need to access a Memcached instance from a series of distributed servers, then use APCu instead. APCu is supported by the PHP project and has much of the same functionality as Memcached.
Instalação da biblioteca cliente do Memcached
After you install the Memcached server, you need to install the Memcached client library. Without the library, your PHP scripts won't be able to communicate with the Memcached server.
You can install the Memcached client library on Ubuntu 14.04 by running this command in your terminal:
sudo apt-get install php5-memcached
Ou usar o APCu
Before Ubuntu 14.04, the APC project was both an opcode cache and a Memcached-like key-value store. Since the version of PHP that ships with 14.04 now includes a built-in opcode cache, APC was split into the APCu project, which is essentially APC's key-value storage functionality—AKA the "user cache", or the "u" in APCu—without the opcode-cache parts.
Instalação do APCu
You can install APCu on Ubuntu 14.04 by running this command in your terminal:
sudo apt-get install php5-apcu
Exemplo
<?php
// Store some values in the APCu cache. We can optionally pass a time-to-live, but in this example the values will live forever until they're garbage-collected by APCu.
apc_store('username-1532', 'Frodo Baggins');
apc_store('username-958', 'Aragorn');
apc_store('username-6389', 'Gandalf');
// After storing these values, any PHP script can access them, no matter when it's run!
$value = apc_fetch('username-958', $success);
if($success === true)
print($value); // Aragorn
$value = apc_fetch('username-1', $success); // $success will be set to boolean false, because this key doesn't exist.
if($success !== true) // Note the !==, this checks for true boolean false, not "falsey" values like 0 or empty string.
print('Key not found');
apc_delete('username-958'); // This key will no longer be available.
?>
Mais informações
PHP e regex
Use a família de funções PCRE (preg_*)
PHP has two different ways of using regular expressions: the PCRE (Perl-compatible, preg_*) functions and the POSIX (POSIX extended, ereg_*) functions.
Each family of functions uses a slightly different flavor of regular expression. Luckily for us, the POSIX functions have been deprecated since PHP 5.3.0. Because of this, you should never write new code using the POSIX functions. Always use the PCRE functions, which are the preg_* functions.
Mais informações
Servindo o PHP num servidor web
Use PHP-FPM.
There are several ways of configuring a web server to serve PHP. The traditional (and terrible) way is to use Apache's mod_php. Mod_php attaches PHP to Apache itself, but Apache does a very bad job of managing it. You'll suffer from severe memory problems as soon as you get any kind of real traffic.
Two new options soon became popular: mod_fastcgi and mod_fcgid. Both of these keep a limited number of PHP processes running, and Apache sends requests to these interfaces to handle PHP execution on its behalf. Because these libraries limit how many PHP processes are alive, memory usage is greatly reduced without affecting performance.
Some smart people created an implementation of fastcgi that was specially designed to work really well with PHP, and they called it PHP-FPM. This was the standard solution for web servers since 12.04.
In the years since Ubuntu 12.04, Apache introduced a new method of interacting with PHP-FPM: mod_proxy_fcgi. Unfortunately, the version of Apache that ships with Ubuntu 14.04 has a few problems with this module, including not being able to use socket-based connections, issues with mod_rewrite, and issues with 404 and similar pages. Because of these problems you should stick with the tried-and-true method of working with PHP-FPM that we used in Ubuntu 12.04.
The following example is for Apache 2.4.7, but PHP-FPM also works for other web servers like Nginx.
Instalação do PHP-FPM e do Apache
You can install PHP-FPM and Apache on Ubuntu 14.04 by running these command in your terminal:
sudo apt-get install apache2-mpm-event libapache2-mod-fastcgi php5-fpm
sudo a2enmod actions alias fastcgi
Note that we must use apache2-mpm-event (or apache2-mpm-worker), not apache2-mpm-prefork or apache2-mpm-threaded.
Next, we'll configure our Apache virtualhost to route PHP requests to the PHP-FPM process. Place the following in your Apache configuration file (in Ubuntu 14.04 the default one is /etc/apache2/sites-available/000-default.conf).
<Directory />
Require all granted
</Directory>
<VirtualHost *:80>
Action php5-fcgi /php5-fcgi
Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi
FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -socket /var/run/php5-fpm.socket -idle-timeout 120 -pass-header Authorization
<FilesMatch "\.php$">
SetHandler php5-fcgi
</FilesMatch>
</VirtualHost>
Finally, restart Apache and the FPM process:
sudo service apache2 restart && sudo service php5-fpm restart
Pegadinhas
- Using the AddHandler directive instead of the SetHandler directive can be a security risk. AddHandler will execute PHP in files that have ".php" anywhere in the file name. So if a user uploads evil.php.gif using your file upload form, you might be in for a nasty surprise.
Mais informações
Envio de email
Use PHPMailer.
Tested with PHPMailer 5.2.7.
PHP provides a mail() function that looks enticingly simple and easy. Unfortunately, like a lot of things in PHP, its simplicity is deceptive and using it at face value can lead to serious security problems.
Email is a set of protocols with an even more tortured history than PHP. Suffice it to say that there are so many gotchas in sending email that just being in the same room as PHP's mail() function should give you the shivers.
PHPMailer is a popular and well-aged open-source library that provides an easy interface for sending mail securely. It takes care of the gotchas for you so you can concentrate on more important things.
Exemplo
<?php
// Include the PHPMailer library
require_once('phpmailer-5.2.7/PHPMailerAutoload.php');
// Passing 'true' enables exceptions. This is optional and defaults to false.
$mailer = new PHPMailer(true);
// Send a mail from Bilbo Baggins to Gandalf the Grey
// Set up to, from, and the message body. The body doesn't have to be HTML; check the PHPMailer documentation for details.
$mailer->Sender = 'bbaggins@example.com';
$mailer->AddReplyTo('bbaggins@example.com', 'Bilbo Baggins');
$mailer->SetFrom('bbaggins@example.com', 'Bilbo Baggins');
$mailer->AddAddress('gandalf@example.com');
$mailer->Subject = 'The finest weed in the South Farthing';
$mailer->MsgHTML('<p>You really must try it, Gandalf!</p><p>-Bilbo</p>');
// Set up our connection information.
$mailer->IsSMTP();
$mailer->SMTPAuth = true;
$mailer->SMTPSecure = 'ssl';
$mailer->Port = 465;
$mailer->Host = 'my smtp host';
$mailer->Username = 'my smtp username';
$mailer->Password = 'my smtp password';
// All done!
$mailer->Send();
?>
Mais informações
Validação de endereços de email
Use a função filter_var().
A common task your web app might need to do is to check if a user has entered a valid email address. You'll no doubt find online a dizzying range of complex regular expressions that all claim to solve this problem, but the easiest way is to use PHP's built-in filter_var() function, which can validate email addresses.
Exemplo
<?php
filter_var('sgamgee@example.com', FILTER_VALIDATE_EMAIL); // Returns "sgamgee@example.com". This is a valid email address.
filter_var('sauron@mordor', FILTER_VALIDATE_EMAIL); // Returns boolean false! This is *not* a valid email address.
?>
Mais informações
Sanitização de HTML, entrada e saída
Use a função htmlentities() para sanitização simples e a biblioteca HTML Purifier para sanitização complexa.
Tested with HTML Purifier 4.6.0.
When displaying user input in any web application, it's essential to "sanitize" it first to remove any potentially dangerous HTML. A malicious user can craft HTML that, if outputted directly by your web app, can be dangerous to the person viewing it.
While it may be tempting to use regular expressions to sanitize HTML, do not do this. HTML is a complex language and it's virtually guaranteed that any attempt you make at using regular expressions to sanitize HTML will fail.
You might also find advice suggesting you use the strip_tags() function. While strip_tags() is technically safe to use, it's a "dumb" function in the sense that if the input is invalid HTML (say, is missing an ending tag), then strip_tags() might remove much more content than you expected. As such it's not a great choice either, because non-technical users often use the < and > characters in communications.
If you read the section on validating email addresses, you might also be considering using the filter_var() function. However the filter_var() function has problems with line breaks, and requires non-intuitive configuration to closely mirror the htmlentities() function. As such it's not a good choice either.
Sanitização para requisitos simples
If your web app only needs to completely escape (and thus render harmless, but not remove entirely) HTML, use PHP's built-in htmlentities() function. This function is much faster than HTML Purifier, because it doesn't perform any validation on the HTML—it just escapes everything.
htmlentities() differs from its cousin htmlspecialchars() in that it encodes all applicable HTML entities, not just a small subset.
Exemplo
<?php
// Oh no! The user has submitted malicious HTML, and we have to display it in our web app!
$evilHtml = '<div onclick="xss();">Mua-ha-ha! Twiddling my evil mustache...</div>';
// Use the ENT_QUOTES flag to make sure both single and double quotes are escaped.
// Use the UTF-8 character encoding if you've stored the text as UTF-8 (as you should have).
// See the UTF-8 section in this document for more details.
$safeHtml = htmlentities($evilHtml, ENT_QUOTES, 'UTF-8'); // $safeHtml is now fully escaped HTML. You can output $safeHtml to your users without fear!
?>
Sanitização para requisitos complexos
For many web apps, simply escaping HTML isn't enough. You probably want to entirely remove any HTML, or allow a small subset of HTML through. To do this, use the HTML Purifier library.
HTML Purifier is a well-tested but slow library. That's why you should use htmlentities() if your requirements aren't that complex, because it will be much, much faster.
HTML Purifier has the advantage over strip_tags() because it validates the HTML before sanitizing it. That means if the user has inputted invalid HTML, HTML Purifier has a better chance of preserving the intended meaning of the HTML than strip_tags() does. It's also highly customizable, allowing you to whitelist a subset of HTML to keep in the output.
The downside is that it's quite slow, it requires some setup that might not be feasible in a shared hosting environment, and the documentation is often complex and unclear. The following example is a basic configuration; check the documentation to read about the more advanced features HTML Purifier offers.
Exemplo
<?php
// Include the HTML Purifier library
require_once('htmlpurifier-4.6.0/HTMLPurifier.auto.php');
// Oh no! The user has submitted malicious HTML, and we have to display it in our web app!
$evilHtml = '<div onclick="xss();">Mua-ha-ha! Twiddling my evil mustache...</div>';
// Set up the HTML Purifier object with the default configuration.
$purifier = new HTMLPurifier(HTMLPurifier_Config::createDefault());
$safeHtml = $purifier->purify($evilHtml); // $safeHtml is now sanitized. You can output $safeHtml to your users without fear!
?>
Pegadinhas
- Using htmlentities() with the wrong character encoding can result in surprising output. Always make sure that you specify a character encoding when calling the function, and that it matches the encoding of the string being sanitized. See the UTF-8 section for more details.
- Always include the ENT_QUOTES and character encoding parameters when using htmlentities(). By default, htmlentities() doesn't encode single quotes. What a dumb default!
- HTML Purifier is extremely slow for complex HTML. Consider setting up a caching solution like APC to store the sanitized result for later use.
Mais informações
PHP e UTF-8
Não existe uma solução curta. Seja cuidadoso, detalhista e consistente.
UTF-8 in PHP sucks. Sorry.
Right now PHP does not support Unicode at a low level. There are ways to ensure that UTF-8 strings are processed OK, but it's not easy, and it requires digging in to almost all levels of the web app, from HTML to SQL to PHP. We'll aim for a brief, practical summary.
UTF-8 no nível do PHP
The basic string operations, like concatenating two strings and assigning strings to variables, don't need anything special for UTF-8. However most string functions, like strpos() and strlen(), do need special consideration. These functions often have an mb_* counterpart: for example, mb_strpos() and mb_strlen(). Together, these counterpart functions are called the Multibyte String Functions. The multibyte string functions are specifically designed to operate on Unicode strings.
You must use the mb_* functions whenever you operate on a Unicode string. For example, if you use substr() on a UTF-8 string, there's a good chance the result will include some garbled half-characters. The correct function to use would be the multibyte counterpart, mb_substr().
The hard part is remembering to use the mb_* functions at all times. If you forget even just once, your Unicode string has a chance of being garbled during further processing.
Not all string functions have an mb_* counterpart. If there isn't one for what you want to do, then you might be out of luck.
Additionally, you should use the mb_internal_encoding() function at the top of every PHP script you write (or at the top of your global include script), and the mb_http_output() function right after it if your script is outputting to a browser. Explicitly defining the encoding of your strings in every script will save you a lot of headaches down the road.
Finally, many PHP functions that operate on strings have an optional parameter letting you specify the character encoding. You should always explicitly indicate UTF-8 when given the option. For example, htmlentities() has an option for character encoding, and you should always specify UTF-8 if dealing with such strings.
UTF-8 no nível do MySQL
If your PHP script accesses MySQL, there's a chance your strings could be stored as non-UTF-8 strings in the database even if you follow all of the precautions above.
To make sure your strings go from PHP to MySQL as UTF-8, make sure your database and tables are all set to the utf8mb4 character set and collation, and that you use the utf8mb4 character set in the PDO connection string. For an example, see the section on connecting to and querying a MySQL database. This is critically important.
Note that you must use the `utf8mb4` character set for complete UTF-8 support, not the `utf8` character set! See Mais informações for why.
UTF-8 no nível do navegador
Use the mb_http_output() function to ensure that your PHP script outputs UTF-8 strings to your browser. In your HTML, include the charset <meta> tag in your page's <head> tag.
Exemplo
<?php
// Tell PHP that we're using UTF-8 strings until the end of the script
mb_internal_encoding('UTF-8');
// Tell PHP that we'll be outputting UTF-8 to the browser
mb_http_output('UTF-8');
// Our UTF-8 test string
$string = 'Êl síla erin lû e-govaned vîn.';
// Transform the string in some way with a multibyte function
// Note how we cut the string at a non-Ascii character for demonstration purposes
$string = mb_substr($string, 0, 15);
// Connect to a database to store the transformed string
// See the PDO example in this document for more information
// Note that we define the character set as utf8mb4 in the PDO connection string
$link = new \PDO( 'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
'your-username',
'your-password',
array(
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_PERSISTENT => false
)
);
// Store our transformed string as UTF-8 in our database
// Your DB and tables are in the utf8mb4 character set and collation, right?
$handle = $link->prepare('insert into ElvishSentences (Id, Body) values (?, ?)');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->bindValue(2, $string);
$handle->execute();
// Retrieve the string we just stored to prove it was stored correctly
$handle = $link->prepare('select * from ElvishSentences where Id = ?');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->execute();
// Store the result into an object that we'll output later in our HTML
$result = $handle->fetchAll(\PDO::FETCH_OBJ);
?><!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>UTF-8 test page</title>
</head>
<body>
<?php
foreach($result as $row){
print($row->Body); // This should correctly output our transformed UTF-8 string to the browser
}
?>
</body>
</html>
Mais informações
Trabalhando com datas e horários
Use a classe DateTime.
In the bad old days of PHP we had to work with dates and times using a bewildering combination of date(), gmdate(), date_timezone_set(), strtotime(), and so on. Sadly you'll still find lots of tutorials online featuring these difficult and old-fashioned functions.
Fortunately for us, the version of PHP we're talking about features the much friendlier DateTime class. This class encapsulates all the functionality and more of the old date functions in one easy-to-use class, with the bonus of making time zone conversions much simpler. Always use the DateTime class for creating, comparing, changing, and displaying dates in PHP.
Exemplo
<?php
// Construct a new UTC date. Always specify UTC unless you really know what you're doing!
$date = new DateTime('2011-05-04 05:00:00', new DateTimeZone('UTC'));
// Add ten days to our initial date
$date->add(new DateInterval('P10D'));
echo($date->format('Y-m-d h:i:s')); // 2011-05-14 05:00:00
// Sadly we don't have a Middle Earth timezone
// Convert our UTC date to the PST (or PDT, depending) time zone
$date->setTimezone(new DateTimeZone('America/Los_Angeles'));
// Note that if you run this line yourself, it might differ by an hour depending on daylight savings
echo($date->format('Y-m-d h:i:s')); // 2011-05-13 10:00:00
$later = new DateTime('2012-05-20', new DateTimeZone('UTC'));
// Compare two dates
if($date < $later)
echo('Yup, you can compare dates using these easy operators!');
// Find the difference between two dates
$difference = $date->diff($later);
echo('The 2nd date is ' . $difference['days'] . ' later than 1st date.');
?>
Pegadinhas
If you don't specify a time zone, DateTime::__construct() will set the resulting date's time zone to the time zone of the computer you're running on. This can lead to spectacular headaches later on. Always specify the UTC time zone when creating new dates unless you really know what you're doing.
If you use a Unix timestamp in DateTime::__construct(), the time zone will always be set to UTC regardless of what you specify in the second argument.
Passing zeroed dates (e.g. "0000-00-00", a value commonly produced by MySQL as the default value in a DateTime column) to DateTime::__construct() will result in a nonsensical date, not "0000-00-00".
Using DateTime::getTimestamp() on 32-bit systems will not represent dates past 2038. 64-bit systems are OK.
Mais informações
Verificando se um valor é null ou false
Use o operador === para verificar valores null e o boleano false.
PHP's loose typing system offers many different ways of checking a variable's value. However it also presents a lot of problems. Using == to check if a value is null or false can return false positives if the value is actually an empty string or 0. isset() checks whether a variable has a value, not whether that value is null or false, so it's not appropriate to use here.
The is_null() function accurately checks if a value is null, and the is_bool() function checks if it's a boolean value (like false), but there's an even better option: the === operator. === checks if the values are identical, which is not the same as equivalent in PHP's loosely-typed world. It's also slightly faster than is_null() and is_bool(), and can be considered by some to be cleaner than using a function for comparison.
Exemplo
<?php
$x = 0;
$y = null;
// Is $x null?
if($x == null)
print('Oops! $x is 0, not null!');
// Is $y null?
if(is_null($y))
print('Great, but could be faster.');
if($y === null)
print('Perfect!');
// Does the string abc contain the character a?
if(strpos('abc', 'a'))
// GOTCHA! strpos returns 0, indicating it wishes to return the position of the first character.
// But PHP interpretes 0 as false, so we never reach this print statement!
print('Found it!');
//Solution: use !== (the opposite of ===) to see if strpos() returns 0, or boolean false.
if(strpos('abc', 'a') !== false)
print('Found it for real this time!');
?>
Pegadinhas
- When testing the return value of a function that can return either 0 or boolean false, like strpos(), always use === and !==, or you'll run in to problems.
Mais informações
Sugestões e correções
Thanks for reading! If you haven't figured it out already, PHP is complex and filled with pitfalls. Since I'm only human, there might be mistakes in this document.
If you'd like to contribute to this document with suggestions or corrections, please contact me using the information in the last revised & maintainers section.