Skip to content

PHP Folha de referência

Linguagem de scripting de propósito geral popular para desenvolvimento web.

01

Básicos

Variáveis, Tipos & Constantes

Variáveis PHP começam com $ e são dinamicamente tipadas. PHP 7.4+ suporta typed properties. Use define() para constantes de runtime e const para constantes de compile-time (mais rápido). PHP 8.1 introduziu enums — um sistema de enumeração tipado superior a constantes de classe. Sempre declare tipos quando possível (PHP 7+) para detecção precoce de erros e melhor suporte de IDE.

php
<?php
$name = "Alice";          // string
$age = 30;                // integer
$price = 19.99;           // float (double)
$active = true;           // boolean
$items = [1, 2, 3];       // array
$null = null;             // null

// constants
define("MAX_USERS", 100); // runtime constant
const PI = 3.14159;       // compile-time constant
echo MAX_USERS;            // no $ prefix

// PHP 8.1+ enums
enum Status: string {
    case Active = 'active';
    case Inactive = 'inactive';
}
$s = Status::Active;

Echo, Print & Debugging

echo é um constructo de linguagem (não uma função) — mais rápido para saída. print retorna 1 então pode ser usado em expressões. printf/sprintf usam especificadores de formato estilo C (%s string, %d int, %f float, %x hex). var_dump() é a principal ferramenta de debugging — mostra tipos e valores. error_log() grava no log de erros do PHP ou syslog. Em produção, nunca exponha saída de debug aos usuários.

php
<?php
// echo: no return, multiple args (fastest)
echo "Hello", " ", "World";

// print: returns 1, single arg
print "Hello World";

// printf: formatted output
printf("Name: %s, Age: %d, Price: %.2f", "Alice", 30, 19.99);

// debugging output
print_r($array);           // human-readable
var_dump($variable);       // type + value (detailed)
var_export($array, true);  // valid PHP code (for caching)

// sprintf: return formatted string (don't print)
$log = sprintf("[%s] %s", date('H:i:s'), "Started");
error_log($log);           // log to error log

Operadores & Comparações

Sempre use === (comparação estrita) para evitar bugs de coerção de tipo. == converte tipos antes de comparar, levando a resultados surpreendentes (0 == 'abc' era true no PHP 7). O operador spaceship (<=>) retorna -1/0/1 — útil para usort. O operador null coalescing (??) é a maneira idiomática de fornecer defaults. O operador null-safe (?->) (PHP 8+) faz short-circuit de cadeias de métodos em null, substituindo verificações verbose de isset().

php
<?php
// arithmetic
$sum = 10 + 3;       // 13
$mod = 10 % 3;       // 1
$pow = 2 ** 3;       // 8 (PHP 5.6+)

// comparison: == vs ===
echo (0 == "abc");   // true (loose, PHP 7-); false (PHP 8+)
echo (0 === "abc");  // false (strict — type + value)
echo ("1" == 1);     // true (loose)
echo ("1" === 1);    // false (strict)

// spaceship operator (PHP 7+)
echo 1 <=> 2;        // -1 (less than)
echo 2 <=> 2;        // 0 (equal)
echo 3 <=> 2;        // 1 (greater than)

// null coalescing
$name = $input ?? "default";    // if $input is null
$deep = $data['user']['name'] ?? "Anonymous";

// null safe operator (PHP 8+)
$country = $user?->getAddress()?->country; // null if any step is null

Superglobals & Web

Superglobals são arrays associativos integrados disponíveis em todos os escopos. $_GET e $_POST contêm entrada do usuário — SEMPRE sanitize/valide antes de usar. filter_input() é mais seguro que acesso direto. Nunca confie em valores $_SERVER que podem ser spoofados por clientes (como HTTP_USER_AGENT). Sempre chame exit após header('Location:') — o PHP continua executando caso contrário. Inicie sessions com session_start() antes de qualquer saída.

php
<?php
// superglobals available everywhere
$_GET['name'];           // query string params
$_POST['email'];         // form POST data
$_REQUEST['x'];          // GET + POST + COOKIE
$_SERVER['HTTP_HOST'];   // server/env info
$_SERVER['REQUEST_METHOD']; // GET, POST, etc.
$_COOKIE['session'];     // cookies
$_FILES['upload'];       // file uploads
$_SESSION['user_id'];    // session data (after session_start())

// get client IP
$ip = $_SERVER['REMOTE_ADDR'];

// check request method
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING);
}

// redirect
header("Location: /dashboard");
exit; // always exit after redirect

Include & Require

include/require executam o arquivo especificado. require causa um fatal error se o arquivo estiver faltando (use para dependências críticas); include apenas avisa (use para templates opcionais). As variantes _once rastreiam arquivos incluídos para prevenir dupla inclusão — essencial para definições de função/classe. O autoloader do Composer (require_once 'vendor/autoload.php') elimina o gerenciamento manual de include. Arquivos podem retornar valores, tornando-os úteis para configuração.

php
<?php
// include: warning on failure, continues
include 'header.php';
include_once 'config.php'; // only once

// require: fatal error on failure, stops
require 'database.php';
require_once 'vendor/autoload.php'; // Composer autoload

// _once variants prevent re-declaration errors
// use require for critical files (DB config, autoloaders)
// use include for optional files (templates)

// return values from included files
$config = include 'config.php';
// config.php: return ['db' => 'mysql://...', 'debug' => true];
02

Strings

Funções de String

PHP tem 100+ funções de string. strpos() retorna false se não encontrado — use === false para verificar (0 é uma posição válida). str_replace() pode receber arrays para search/replace. substr() suporta offsets negativos (do final). Para strings multibyte (UTF-8), use equivalentes mb_* (mb_strlen, mb_substr) — strlen conta bytes, não caracteres. Sempre defina default_charset='UTF-8' e use funções mb_* para texto non-ASCII.

php
<?php
$s = "Hello, World";

// length and case
echo strlen($s);              // 12
echo str_word_count($s);      // 2
echo strtoupper($s);          // HELLO, WORLD
echo strtolower($s);          // hello, world
echo ucfirst("hello");        // Hello
echo ucwords("hello world");  // Hello World

// search and replace
echo strpos($s, "World");     // 7 (false if not found)
echo str_replace("o", "0", $s); // Hell0, W0rld
echo substr($s, 0, 5);        // Hello
echo substr($s, -5);          // World
echo strrev($s);              // dlroW ,olleH

// trim
echo trim("  hi  ");          // "hi"
echo ltrim("  hi");           // "hi"
echo rtrim("hi  ");           // "hi"

Interpolação de String & Heredoc

Strings com aspas duplas interpolam variáveis; strings com aspas simples não (use aspas simples para texto literal — ligeiramente mais rápido). Use {$var} para expressões complexas (propriedades de objeto, acesso a array, chamadas de método). Heredoc (<<<ID) é ideal para strings multi-linha como SQL ou HTML — interpola variáveis. Nowdoc (<<<'ID') é a versão não-interpretadora, útil para padrões regex com backslashes.

php
<?php
$name = "Alice";
$age = 30;

// double quotes: variable interpolation
echo "Hello, $name!";          // Hello, Alice!
echo "Hello, {$name}!";        // Hello, Alice! (braces for clarity)
echo "Age: {$age}";            // Age: 30

// single quotes: no interpolation (faster)
echo 'Hello, $name';           // Hello, $name (literal)

// complex expressions need braces
echo "Result: {$obj->method()}";
echo "Item: {$array['key']}";

// heredoc: multi-line, interpolates
$sql = <<<SQL
    SELECT * FROM users
    WHERE name = '$name'
    AND age > $age
SQL;

// nowdoc: multi-line, NO interpolation (like single quotes)
$regex = <<<'REGEX'
    \d{3}-\d{4}
REGEX;

sprintf & Formatação

sprintf() é essencial para construir strings formatadas com segurança — diferentemente da interpolação de string, ele lida com conversões de tipo e padding. Use %d para inteiros (não %s) para garantir formatação numérica. number_format() formata números com separadores de milhares — crítico para exibição de moeda. Sempre use sprintf para fragmentos SQL em prepared statements (embora prepared statements ainda sejam obrigatórios para entrada do usuário).

php
<?php
// sprintf: format string (returns, doesn't print)
$formatted = sprintf(
    "%-10s | %5d | %8.2f",
    "Alice", 42, 19.99
);
echo $formatted;  // "Alice      |    42 |    19.99"

// format specifiers:
// %s string, %d int, %f float, %x hex, %b binary, %c char
// %5d  right-padded to width 5
// %-5d left-padded
// %05d zero-padded
// %.2f 2 decimal places
// %8.2f width 8, 2 decimals

// numbered placeholders
echo sprintf("Hi %1$s, bye %1$s", "Alice"); // Hi Alice, bye Alice

// number_format
echo number_format(1234567.891, 2);  // 1,234,567.89
echo number_format(1234567.89, 2, ',', '.'); // European format

Regex (PCRE)

PHP usa PCRE (Perl-Compatible Regular Expressions) com delimitadores /pattern/. preg_match retorna 1 se correspondido, 0 se não (use ===, não ==, já que 0 é falsy). Sempre valide entrada do usuário com regex mas não confie apenas nisso — use filter_var() para emails, URLs. preg_replace é poderoso mas pode ser lento em strings grandes. Use [^...] para whitelist de caracteres em vez de blacklist. A flag i torna a correspondência case-insensitive.

php
<?php
// preg_match: test pattern (returns 0 or 1)
if (preg_match('/^[a-z]+$/i', "Hello")) {
    echo "alphabetic";
}

// capture groups
preg_match('/(d{4})-(d{2})-(d{2})/', "2024-01-15", $matches);
// $matches[0] = "2024-01-15" (full match)
// $matches[1] = "2024" (group 1)
// $matches[2] = "01"
// $matches[3] = "15"

// preg_match_all: all matches
preg_match_all('/d+/', "a1 b22 c333", $matches);
// $matches[0] = ["1", "22", "333"]

// preg_replace: substitute
$clean = preg_replace('/[^a-z0-9]/i', '', "Hello, World!"); // HelloWorld

// preg_split: split by pattern
$parts = preg_split('/[s,]+/', "one, two,three  four");
// ["one", "two", "three", "four"]

Multibyte & Encoding

As funções de string padrão do PHP são orientadas a bytes, não a caracteres — elas quebram em caracteres multibyte (UTF-8, Chinês, emoji). Sempre use funções mb_* (mb_strlen, mb_substr, mb_strpos, mb_strtoupper) para texto non-ASCII. Defina mb_internal_encoding('UTF-8') no início da sua aplicação. Use JSON_UNESCAPED_UNICODE para manter Chinês/emoji legível na saída JSON. Essa é uma fonte comum de bugs em aplicações internacionais.

php
<?php
// UTF-8 strings: use mb_* functions
$text = "Café";  // 4 chars, 5 bytes (é = 2 bytes)

echo strlen($text);      // 5 (bytes!)
echo mb_strlen($text);   // 4 (characters!)

echo substr($text, 0, 3);    // "Caf" (might break UTF-8!)
echo mb_substr($text, 0, 3); // "Caf" (safe)

// case conversion
echo strtoupper("straße");    // "STRAßE" (wrong!)
echo mb_strtoupper("straße"); // "STRAßE" (correct, locale-aware)

// encoding detection
$encoding = mb_detect_encoding($text);
$utf8 = mb_convert_encoding($text, 'UTF-8', 'auto');

// set internal encoding
mb_internal_encoding('UTF-8');
mb_regex_encoding('UTF-8');

// JSON with UTF-8
$json = json_encode($data, JSON_UNESCAPED_UNICODE);
03

Arrays

Arrays Indexados & Associativos

Arrays PHP são na verdade hash maps ordenados — funcionam tanto como listas quanto como dicionários. Arrays indexados auto-atribuem chaves numéricas; arrays associativos usam chaves string. isset() retorna false para valores null; array_key_exists() retorna true mesmo para null. unset() remove um elemento mas não reindexa. Para uma lista verdadeira (sem espaços), use array_values() para reindexar após deleção. PHP 8.1+ tem um tipo de array readonly.

php
<?php
// indexed array (numeric keys)
$nums = [10, 20, 30];
$nums[] = 40;              // append
echo $nums[0];             // 10
echo count($nums);         // 4

// associative array (string keys)
$user = [
    "name" => "Alice",
    "age" => 30,
    "email" => "[email protected]",
];
echo $user["name"];        // Alice
$user["phone"] = "555-1234"; // add key

// mixed keys
$mixed = [0 => "a", "name" => "b", 5 => "c"];

// check key existence
if (isset($user["email"])) { /* ... */ }
if (array_key_exists("name", $user)) { /* ... */ }

// remove element
unset($user["phone"]);

Multidimensional & Iteração

Arrays multidimensionais são arrays de arrays. foreach é a maneira idiomática de iterar — é mais rápido e legível que loops for. Use &$value para modificar elementos in-place (sempre faça unset da referência após o loop para evitar bugs). array_column() extrai uma única coluna de um array 2D — extremamente útil para transformar result sets de banco de dados. Arrays PHP mantêm ordem de inserção.

php
<?php
$users = [
    ["name" => "Alice", "age" => 30],
    ["name" => "Bob", "age" => 25],
    ["name" => "Carol", "age" => 35],
];

// iterate with key + value
foreach ($users as $index => $user) {
    echo "$index: {$user['name']} ({$user['age']})\n";
}

// modify by reference
foreach ($users as &$user) {
    $user['age'] += 1; // increment each age
}
unset($user); // break reference!

// nested iteration
$matrix = [[1, 2], [3, 4], [5, 6]];
foreach ($matrix as $row) {
    foreach ($row as $cell) {
        echo $cell . " ";
    }
    echo "\n";
}

// extract column
$names = array_column($users, 'name'); // ["Alice", "Bob", "Carol"]

Funções de Array: map, filter, reduce

array_map, array_filter e array_reduce são o trio de programação funcional para arrays. Arrow functions (fn() =>) os tornam concisos. array_filter preserva chaves — use array_values() para reindexar se necessário. array_merge reindexa chaves numéricas mas preserva chaves string (valores posteriores sobrescrevem). array_column, array_chunk e array_slice são essenciais para manipulação de dados. Essas funções são a espinha dorsal do processamento de dados em PHP.

php
<?php
$nums = [1, 2, 3, 4, 5];

// map: transform each element
$doubled = array_map(fn($n) => $n * 2, $nums);  // [2, 4, 6, 8, 10]

// filter: keep elements matching condition
$evens = array_filter($nums, fn($n) => $n % 2 === 0);  // [2, 4]

// reduce: accumulate to single value
$sum = array_reduce($nums, fn($carry, $n) => $carry + $n, 0);  // 15

// walk: like map but modifies in place (by reference)
array_walk($nums, fn(&$n) => $n *= 2);

// combining arrays
$merged = array_merge([1, 2], [3, 4]);        // [1, 2, 3, 4]
$combined = array_combine(['a', 'b'], [1, 2]); // ['a' => 1, 'b' => 2]
$sliced = array_slice($nums, 1, 2);            // [2, 3]
$chunked = array_chunk($nums, 2);              // [[1,2], [3,4], [5]]

Ordenando Arrays

Funções de sort do PHP modificam o array in-place (pass by reference). sort/rsort reindexam; asort/arsort preservam chaves. usort com uma função de comparação (usando <=>) ordena por lógica customizada. natsort() faz ordenação natural (img2 antes de img10) — essencial para nomes de arquivo. Para arrays multidimensionais, use usort com uma closure que compara o campo desejado. O operador spaceship (<=>) simplifica funções de comparação.

php
<?php
$nums = [3, 1, 4, 1, 5, 9, 2, 6];

// sort by value (reindexes)
sort($nums);                          // [1, 1, 2, 3, 4, 5, 6, 9]
rsort($nums);                         // descending

// sort preserving keys
asort($nums);   // ascending, preserve keys
arsort($nums);  // descending, preserve keys

// sort by key
ksort($nums);   // by key ascending
krsort($nums);  // by key descending

// custom sort with callback
$users = [["name" => "Bob", "age" => 25], ["name" => "Alice", "age" => 30]];
usort($users, fn($a, $b) => $a['age'] <=> $b['age']);
// sorted by age ascending

// natural sort (for strings with numbers)
$files = ["img10.jpg", "img2.jpg", "img1.jpg"];
natsort($files); // ["img1.jpg", "img2.jpg", "img10.jpg"]
sort($files);    // ["img1.jpg", "img10.jpg", "img2.jpg"] (wrong!)

Inspeção & Manipulação de Arrays

in_array com strict=true (terceiro param) previne bugs de coerção de tipo. array_search retorna a chave (use === false para verificar). array_push/pop implementam LIFO (stack); array_shift/unshift implementam FIFO (queue) — mas shift é O(n). Para grandes queues, use SplQueue ou SplDoublyLinkedList. array_unique preserva chaves. array_diff/intersect comparam valores; use array_diff_key/intersect_key para comparação baseada em chave.

php
<?php
$arr = [1, 2, 3, 4, 5];

// inspection
echo count($arr);                  // 5
echo in_array(3, $arr);            // true
echo in_array("3", $arr, true);    // false (strict)
echo array_search(3, $arr);        // 2 (key, false if not found)
print_r(array_keys($arr));         // [0, 1, 2, 3, 4]
print_r(array_values($arr));       // [1, 2, 3, 4, 5]

// stack/queue operations
array_push($arr, 6);    // push to end
$last = array_pop($arr); // pop from end
$first = array_shift($arr); // remove from front
array_unshift($arr, 0); // add to front

// set operations
$unique = array_unique([1, 2, 2, 3]); // [1, 2, 3]
$diff = array_diff([1, 2, 3], [2, 3, 4]); // [1] (in first, not second)
$intersect = array_intersect([1, 2, 3], [2, 3, 4]); // [2, 3]

// flip and reverse
$flipped = array_flip(['a' => 1, 'b' => 2]); // [1 => 'a', 2 => 'b']
$reversed = array_reverse([1, 2, 3]); // [3, 2, 1]
04

Controle de Fluxo

If / Else / Elseif

PHP usa elseif (uma palavra) — não 'else if' com espaço (embora isso também funcione). A sintaxe alternativa (if: ... endif;) é útil em templates HTML para evitar confusão de correspondência de chaves. O operador ternário é right-associative — evite aninhar. O operador null coalescing assignment (??=) define um valor apenas se for atualmente null — perfeito para inicialização lazy de defaults de config.

php
<?php
$score = 85;

if ($score >= 90) {
    $grade = "A";
} elseif ($score >= 80) {
    $grade = "B";
} elseif ($score >= 70) {
    $grade = "C";
} else {
    $grade = "F";
}

// alternative syntax (for templates)
if ($score >= 90):
    echo "Excellent";
elseif ($score >= 80):
    echo "Good";
else:
    echo "Try harder";
endif;

// ternary
$status = $age >= 18 ? "adult" : "minor";

// null coalescing assignment (PHP 7.4+)
$config['timeout'] ??= 30; // set if not set

Switch & Match

switch usa comparação loose (==) e requer break para prevenir fall-through — uma fonte comum de bugs. match (PHP 8+) usa comparação estrita (===), retorna um valor diretamente, e lança uma exceção se nenhum braço corresponder (sem falhas silenciosas). match é o substituto moderno do switch quando você precisa de um valor. Use switch para casos complexos de múltiplas declarações; use match para seleção simples de valor. Sempre inclua um caso default.

php
<?php
// switch: loose comparison (==)
$day = "Mon";
switch ($day) {
    case "Mon":
    case "Tue":
    case "Wed":
        echo "Weekday";
        break;
    case "Sat":
    case "Sun":
        echo "Weekend";
        break;
    default:
        echo "Unknown";
}

// match (PHP 8+): strict comparison (===), returns value
$status = 404;
$message = match($status) {
    200, 201 => "Success",
    301, 302 => "Redirect",
    404 => "Not Found",
    500 => "Server Error",
    default => "Unknown",
};
// match throws UnhandledMatchError if no match and no default

Loops: for, while, foreach, do-while

foreach é o loop idiomático para arrays — é mais rápido e seguro que for com count(). Use continue para pular iterações e break para sair. PHP não tem break/continue rotulado (diferente de Java/Rust). Para arrays associativos, foreach ($arr as $key => $value) é o padrão. do-while roda pelo menos uma vez — útil para validação de entrada. Evite modificar o array durante foreach (use um array separado para resultados).

php
<?php
// for loop
for ($i = 0; $i < 5; $i++) {
    echo $i;  // 01234
}

// foreach (most common in PHP)
$fruits = ["apple", "banana", "cherry"];
foreach ($fruits as $fruit) {
    echo $fruit;
}

// foreach with key
foreach ($fruits as $index => $fruit) {
    echo "$index: $fruit";
}

// while
$count = 0;
while ($count < 3) {
    echo $count++;
}

// do-while (runs at least once)
do {
    $line = readline("> ");
} while ($line !== "quit");

// break and continue
for ($i = 0; $i < 10; $i++) {
    if ($i === 3) continue; // skip 3
    if ($i === 7) break;    // stop at 7
    echo $i;
}

Controle de Fluxo em Templates

A sintaxe de controle alternativa do PHP (if:/elseif:/else:/endif;, foreach:/endforeach;) é projetada para templates HTML. <?= $var ?> é abreviação de <?php echo $var; ?> — sempre use em templates para legibilidade. Sempre escape saída com htmlspecialchars() para prevenir XSS. A separação da lógica PHP e apresentação HTML é a base de sistemas de templating como Twig e Blade, que oferecem sintaxe mais limpa e escaping automático.

php
<?php // template file ?>
<?php if ($user->isAdmin()): ?>
    <div class="admin-panel">Admin Tools</div>
<?php elseif ($user->isEditor()): ?>
    <div class="editor-tools">Edit Tools</div>
<?php else: ?>
    <div class="user-view">Read Only</div>
<?php endif; ?>

<?php foreach ($products as $p): ?>
    <div class="product">
        <?= htmlspecialchars($p['name']) ?>
        - $<?= number_format($p['price'], 2) ?>
    </div>
<?php endforeach; ?>

<?php // shorthand echo ?>
<h1><?= $title ?></h1>

<?php // ternary in templates ?>
<span class="<?= $active ? 'on' : 'off' ?>"><?= $active ? 'Active' : 'Inactive' ?></span>

Exceções & Tratamento de Erros

PHP 7+ usa exceções para a maioria dos erros. Sempre capture tipos de exceção específicos (não apenas Exception) para lidar com diferentes falhas apropriadamente. finally sempre executa — use para limpeza (fechar arquivos, conexões). Exceções customizadas estendem Exception e adicionam contexto de domínio. PHP 8+ permite capturar múltiplos tipos de exceção com |. Configure PDO para exception mode para tratamento de erro consistente. Nunca capture exceções sem logar — falhas silenciosas escondem bugs.

php
<?php
// try / catch / finally
try {
    $pdo = new PDO("mysql:host=localhost;dbname=test", "user", "pass");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    error_log("DB connection failed: " . $e->getMessage());
    die("Service unavailable");
} finally {
    // always runs, even after return/throw
    echo "Cleanup";
}

// custom exception
class ValidationException extends Exception {
    public function __construct(string $field, string $message = "") {
        parent::__construct("$field: $message");
    }
}

// throw
if (empty($email)) {
    throw new ValidationException("email", "is required");
}

// catch multiple types (PHP 8+)
try {
    riskyOperation();
} catch (PDOException | RuntimeException $e) {
    // catches either type
    log_error($e);
}
05

Funções

Definindo Funções

PHP 7+ suporta parâmetros tipados e tipos de retorno (int, string, array, ?Type para nullable). PHP 8+ adiciona named arguments (pula defaults, reordena params), union types (int|string), e mixed type. Parâmetros variadic (...$nums) coletam argumentos extras em um array. O operador spread (...$arr) desempacota um array como argumentos. Pass by reference (&) modifica o original — use com moderação pois torna o código mais difícil de raciocinar.

php
<?php
// basic function with return type
function add(int $a, int $b): int {
    return $a + $b;
}

// default parameters
function greet(string $name, string $greeting = "Hello"): string {
    return "$greeting, $name!";
}

// named arguments (PHP 8+)
echo greet(name: "Alice", greeting: "Hi");

// variadic functions
function sum(int ...$nums): int {
    return array_sum($nums);
}
echo sum(1, 2, 3, 4);  // 10

// spread operator
$nums = [1, 2, 3];
echo sum(...$nums);  // 6

// pass by reference
function increment(int &$n): void {
    $n++;
}
$x = 5;
increment($x);
echo $x;  // 6

Arrow Functions & Closures

Arrow functions (fn() =>) são closures concisas de expressão única que capturam automaticamente variáveis externas por valor. Closures tradicionais (function() use ($var)) são necessárias para corpos multi-linha ou captura por referência (&$var). Closures são essenciais para array_map, array_filter, usort e event handlers. Arrow functions não podem ter declarações (sem if, for) — use closures tradicionais para lógica complexa. Closures são objetos first-class (classe Closure).

php
<?php
// arrow function (PHP 7.4+): single expression, auto-capture
$square = fn($x) => $x * $x;
echo $square(5);  // 25

// auto-captures outer variables by value
$multiplier = 3;
$multiply = fn($x) => $x * $multiplier;
echo $multiply(5);  // 15

// traditional closure (multi-line, explicit capture)
$factor = 10;
$scale = function ($x) use ($factor) {
    return $x * $factor + 1;
};

// capture by reference
$count = 0;
$increment = function () use (&$count) {
    $count++;
};
$increment();
echo $count;  // 1

// closures as callbacks
$nums = [1, 2, 3, 4];
$evens = array_filter($nums, fn($n) => $n % 2 === 0);
$doubled = array_map(fn($n) => $n * 2, $nums);

Escopo de Variáveis & Globals

PHP tem escopo em nível de função — variáveis definidas fora de uma função NÃO são acessíveis dentro sem 'global' ou $GLOBALS. Evite 'global' — cria dependências ocultas e dificulta testes. Use dependency injection em vez disso. Variáveis static persistem entre chamadas de função mas têm escopo na função — útil para caching/memoization mas pode causar problemas em processos de longa execução. Closures devem capturar explicitamente variáveis com 'use'.

php
<?php
$global = "I'm global";

function testScope(): void {
    // echo $global; // ERROR: not in scope
    global $global;  // import global
    echo $global;    // OK

    // $GLOBALS superglobal (alternative)
    echo $GLOBALS['global'];
}

// static variables: persist across calls
function counter(): int {
    static $count = 0;
    return ++$count;
}
echo counter();  // 1
echo counter();  // 2
echo counter();  // 3

// closures don't see outer scope by default
$outer = "hello";
$closure = function () {
    // echo $outer; // ERROR
};
$closure2 = function () use ($outer) {
    echo $outer;  // OK, captured
};

Declarações de Tipo & Strict Types

declare(strict_types=1) deve ser a primeira declaração — impõe verificação estrita de tipo (sem coerção) para todo o arquivo. Sem isso, PHP coage tipos (int 5 passado para um param string vira '5'). Sempre use strict types em código novo. PHP 8+ adiciona union types, mixed, never (função nunca retorna), e static (retorna a classe). Sintaxe first-class callable (func(...)) cria closures de qualquer callable — mais limpa que referências de função.

php
<?php
// declare strict types (FIRST line of file)
declare(strict_types=1);

function divide(float $a, float $b): float {
    if ($b === 0.0) {
        throw new DivisionByZeroError();
    }
    return $a / $b;
}

// nullable types (?Type or Type|null)
function findUser(?int $id): ?string {
    if ($id === null) return null;
    return "User $id";
}

// union types (PHP 8+)
function process(int|string $input): int|string {
    return is_int($input) ? $input * 2 : strtoupper($input);
}

// return types: void, never, mixed, static
function log(string $msg): void { /* no return */ }
function redirect(): never { header("Location: /"); exit; }

// first-class callable syntax (PHP 8.1+)
$func = strlen(...);  // creates Closure from function
echo $func("hello");  // 5

Generators & Yield

Generators (funções com yield) produzem valores lazymente — não computam todos os valores antecipadamente, economizando memória. Isso é essencial para processar arquivos ou datasets grandes. yield pausa a função, retornando um valor; a função resume quando o próximo valor é solicitado. Generators implementam Iterator, então funcionam com foreach. Use generators para: processamento de arquivos, iteração de linhas de banco de dados, sequências infinitas e pipelines. Eles também podem yield pares key=>value e aceitar valores via send().

php
<?php
// generator: memory-efficient iteration
function readLines(string $file): Generator {
    $handle = fopen($file, 'r');
    while (($line = fgets($handle)) !== false) {
        yield trim($line);
    }
    fclose($handle);
}

foreach (readLines("large.txt") as $line) {
    echo $line;  // one line at a time, low memory
}

// infinite generator
function fibonacci(): Generator {
    [$a, $b] = [0, 1];
    while (true) {
        yield $a;
        [$a, $b] = [$b, $a + $b];
    }
}

// take first 10
$fib = fibonacci();
for ($i = 0; $i < 10; $i++) {
    echo $fib->current() . " ";
    $fib->next();
}

// yield with key
function pairs(): Generator {
    yield 'a' => 1;
    yield 'b' => 2;
    yield 'c' => 3;
}
06

OOP & Classes

Classe, Propriedades & Construtor

Constructor promotion do PHP 8 elimina boilerplate — declare propriedades como parâmetros do construtor. Visibilidade de propriedade: public (qualquer lugar), protected (classe + subclasses), private (apenas classe). readonly (PHP 8.1) previne modificação após inicialização. self refere-se à classe atual; static refere-se à classe chamadora (para late static binding). Use static:: em vez de self:: em hierarquias de herança para polimorfismo adequado.

php
<?php
class Person {
    // typed properties (PHP 7.4+)
    public string $name;
    protected int $age;
    private string $email;
    public readonly string $id;  // PHP 8.1+

    // constructor promotion (PHP 8+)
    public function __construct(
        string $name,
        int $age,
        string $email = "",
        string $id = ""
    ) {
        $this->name = $name;
        $this->age = $age;
        $this->email = $email;
        $this->id = $id;
    }

    // methods
    public function greet(): string {
        return "Hi, I'm {$this->name}";
    }

    // static method
    public static function create(string $name): self {
        return new self($name, 0);
    }
}

$p = new Person("Alice", 30);
echo $p->greet();
echo Person::create("Bob")->name;

Herança & Classes Abstratas

Classes abstratas não podem ser instanciadas — elas definem um template para subclasses. Métodos abstratos devem ser implementados por subclasses concretas. PHP suporta apenas herança única (um extends). Use final para prevenir herança/override quando a implementação não deve mudar. Membros protected são acessíveis em subclasses — use para APIs internos. Sempre chame parent::__construct() se o pai tem um construtor. instanceof verifica tipo: if ($dog instanceof Animal).

php
<?php
abstract class Animal {
    protected string $name;

    public function __construct(string $name) {
        $this->name = $name;
    }

    // abstract method: must be implemented by subclasses
    abstract public function speak(): string;

    // concrete method: inherited as-is
    public function describe(): string {
        return "{$this->name} says {$this->speak()}";
    }
}

class Dog extends Animal {
    public function speak(): string {
        return "Woof";
    }
}

class Cat extends Animal {
    public function speak(): string {
        return "Meow";
    }
}

$dog = new Dog("Rex");
echo $dog->describe();  // Rex says Woof

// final class/method: cannot be extended/overridden
final class Singleton { /* ... */ }

Interfaces & Traits

Interfaces definem contratos — classes podem implementar múltiplas interfaces (diferente da herança única). Todos os métodos de interface devem ser public. Traits fornecem reuso de código sem herança — são 'copy-paste' em nível de linguagem. Traits podem ter propriedades, métodos e até métodos abstratos. Use traits para preocupações cross-cutting (timestamps, logging, soft deletes). Resolução de conflito: use TraitA::method insteadof TraitB quando traits têm o mesmo nome de método.

php
<?php
// interface: contract (no implementation)
interface Comparable {
    public function compareTo(object $other): int;
}

interface JsonSerializable {
    public function jsonSerialize(): mixed;
}

// a class can implement multiple interfaces
class Product implements Comparable, JsonSerializable {
    public function __construct(public float $price) {}

    public function compareTo(object $other): int {
        return $this->price <=> $other->price;
    }

    public function jsonSerialize(): mixed {
        return ['price' => $this->price];
    }
}

// trait: reusable code (horizontal reuse)
trait Timestampable {
    public DateTime $createdAt;

    public function setCreatedAt(): void {
        $this->createdAt = new DateTime();
    }

    public function age(): DateInterval {
        return $this->createdAt->diff(new DateTime());
    }
}

class Article {
    use Timestampable; // use trait
}

Magic Methods

Magic methods são métodos especiais que interceptam operações de objeto. __get/__set implementam property overloading (propriedades dinâmicas). __toString habilita string casting. __invoke torna objetos callable. __clone roda ao clonar (clone $obj). Use com moderação — eles adicionam 'mágica' difícil de rastrear. __get/__set são úteis para data transfer objects ou lazy loading. Sempre documente comportamento mágico claramente. PHP 8.2 deprecia propriedades dinâmicas — use __get/__set ou #[AllowDynamicProperties].

php
<?php
class Magic {
    private array $data = [];

    // called when accessing undefined property
    public function __get(string $name): mixed {
        return $this->data[$name] ?? null;
    }

    // called when setting undefined property
    public function __set(string $name, mixed $value): void {
        $this->data[$name] = $value;
    }

    // called when isset() or empty() on undefined property
    public function __isset(string $name): bool {
        return isset($this->data[$name]);
    }

    // called when object is used as string
    public function __toString(): string {
        return json_encode($this->data);
    }

    // called when object is called as function
    public function __invoke(string $arg): string {
        return "Called with: $arg";
    }

    // called on clone
    public function __clone(): void {
        $this->data = []; // reset on clone
    }
}

$m = new Magic();
$m->foo = "bar";       // __set
echo $m->foo;          // __get -> "bar"
echo $m;               // __toString -> {"foo":"bar"}
echo $m("test");       // __invoke

Namespaces & Autoloading

Namespaces previnem colisões de nome de classe — como packages em Java. O namespace deve ser a primeira declaração. use importa classes (com aliases opcionais: use Foo\Bar as B). Autoloading PSR-4 mapeia namespaces para paths de arquivo: App\Models\User → src/Models/User.php. O autoloader do Composer (require 'vendor/autoload.php') lida com isso automaticamente. Sempre use namespaces em PHP moderno. O \\ em strings é um backslash escapado (separador de namespace).

php
<?php
// file: src/Models/User.php
namespace App\Models;

use App\Database\Connection;
use App\Exceptions\UserNotFoundException;

class User {
    private Connection $db;

    public function __construct(Connection $db) {
        $this->db = $db;
    }

    public function find(int $id): ?self {
        // ...
        throw new UserNotFoundException("User $id not found");
    }
}

// composer.json (PSR-4 autoloading)
// {
//   "autoload": {
//     "psr-4": { "App\\": "src/" }
//   }
// }

// usage
use App\Models\User;
$user = new User($db);
07

Web, Formulários & File I/O

Tratamento de Formulários & Validação

Sempre valide no lado servidor — validação client-side é para UX, não segurança. filter_input/filter_var com FILTER_VALIDATE_* retornam false em entrada inválida. Trim strings antes da validação. Use prepared statements para inserts de banco de dados. Tokens CSRF previnem cross-site request forgery — gere por session e verifique em POST. bin2hex(random_bytes(32)) gera um token criptograficamente seguro. Nunca confie em entrada do usuário — valide, sanitize e escape.

php
<?php
// process POST form
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = trim($_POST['name'] ?? '');
    $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
    $age = filter_var($_POST['age'] ?? 0, FILTER_VALIDATE_INT);

    $errors = [];

    if (empty($name)) {
        $errors[] = "Name is required";
    } elseif (strlen($name) > 100) {
        $errors[] = "Name too long";
    }

    if ($email === false) {
        $errors[] = "Valid email required";
    }

    if ($age === false || $age < 18) {
        $errors[] = "Must be 18+";
    }

    if (empty($errors)) {
        // process valid data
        // save to database, redirect, etc.
        header("Location: /success");
        exit;
    }
}

// CSRF token
session_start();
$token = bin2hex(random_bytes(32));
$_SESSION['csrf'] = $token;
// in form: <input type="hidden" name="csrf" value="<?= $token ?>">

Sessions & Cookies

Sessions armazenam dados no servidor (identificados por um cookie de session ID). session_start() deve ser chamado antes de qualquer saída (ou use ob_start()). Armazene dados mínimos em sessions — elas consomem memória do servidor. Para cookies, sempre defina secure (apenas HTTPS), httponly (previne acesso XSS), e samesite (proteção CSRF). Destrua sessions adequadamente: faça unset de variáveis, destrua a session, limpe o cookie. Para apps escaláveis, use um session handler backed por Redis/banco de dados em vez de arquivos.

php
<?php
// start session (must be before any output)
session_start();

// set session data
$_SESSION['user_id'] = 42;
$_SESSION['username'] = "Alice";
$_SESSION['login_time'] = time();

// read session data
$userId = $_SESSION['user_id'] ?? null;

// check if logged in
function isLoggedIn(): bool {
    return isset($_SESSION['user_id']);
}

// destroy session
session_unset();     // clear variables
session_destroy();   // destroy session
setcookie(session_name(), '', time() - 3600, '/'); // clear cookie

// cookies
setcookie("theme", "dark", [
    'expires' => time() + 86400 * 30, // 30 days
    'path' => '/',
    'secure' => true,     // HTTPS only
    'httponly' => true,   // not accessible via JS
    'samesite' => 'Strict', // CSRF protection
]);

// read cookie
$theme = $_COOKIE['theme'] ?? 'light';

File I/O

file_get_contents/file_put_contents são convenientes para arquivos pequenos. Para arquivos grandes, use fopen/fread/fwrite com streams. fgetcsv/fputcsv lidam com formato CSV (incluindo quoting/escaping). json_decode com true retorna arrays associativos (objetos por padrão). Sempre verifique file_exists e trate erros (permissões, disco cheio). Para uploads de arquivo, use move_uploaded_file() para segurança. Bloqueie arquivos com flock() ao escrever concorrentemente.

php
<?php
// read entire file
$content = file_get_contents("data.txt");
$lines = file("data.txt", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

// write file
file_put_contents("output.txt", "Hello World");
file_put_contents("log.txt", "entry\n", FILE_APPEND); // append

// CSV
$csv = fopen("data.csv", "r");
while (($row = fgetcsv($csv)) !== false) {
    print_r($row); // array of column values
}
fclose($csv);

// write CSV
$out = fopen("export.csv", "w");
fputcsv($out, ["Name", "Email", "Age"]);
fputcsv($out, ["Alice", "[email protected]", 30]);
fclose($out);

// JSON
$data = json_decode(file_get_contents("config.json"), true); // assoc array
file_put_contents("config.json", json_encode($data, JSON_PRETTY_PRINT));

// file info
file_exists("data.txt");  // bool
filesize("data.txt");     // bytes
filemtime("data.txt");    // modification timestamp
is_dir("folder");         // bool

Uploads de Arquivo

Uploads de arquivo vêm através de $_FILES, não $_POST. Sempre valide: verifique error code, verifique MIME type com finfo (não $_FILES['type'] que é fornecido pelo cliente e spoofável), imponha limites de tamanho, e gere nomes de arquivo seguros (nunca confie no nome original). move_uploaded_file() é uma função de segurança — verifica se o arquivo foi enviado via HTTP POST. Armazene uploads fora da web root ou sirva através do PHP para prevenir acesso direto. Considere escanear uploads por malware.

php
<?php
// HTML: <form method="POST" enctype="multipart/form-data">
//   <input type="file" name="document">
//   <input type="submit">
// </form>

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $file = $_FILES['document'];

    // $file contains:
    // ['name'] => original filename
    // ['type'] => MIME type (unreliable!)
    // ['tmp_name'] => temporary path
    // ['error'] => UPLOAD_ERR_OK (0) on success
    // ['size'] => file size in bytes

    if ($file['error'] !== UPLOAD_ERR_OK) {
        die("Upload error: " . $file['error']);
    }

    // validate
    $allowedTypes = ['application/pdf', 'image/jpeg', 'image/png'];
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mimeType = $finfo->file($file['tmp_name']);

    if (!in_array($mimeType, $allowedTypes)) {
        die("Invalid file type");
    }

    if ($file['size'] > 5 * 1024 * 1024) { // 5MB
        die("File too large");
    }

    // move to permanent location
    $dest = "uploads/" . uniqid() . "_" . $file['name'];
    move_uploaded_file($file['tmp_name'], $dest);
    echo "Uploaded to: $dest";
}

cURL & HTTP Requests

cURL é o cliente HTTP padrão em PHP — lida com HTTPS, redirecionamentos, cookies e autenticação. Sempre defina CURLOPT_RETURNTRANSFER para obter a resposta como string (caso contrário é impressa). Defina timeouts para evitar hangs. Para requests simples, file_get_contents com stream_context funciona mas falta recursos. Para produção, use Guzzle (composer require guzzlehttp/guzzle) ou Symfony HTTP Client — eles oferecem melhores APIs, retry logic e conformidade PSR-18.

php
<?php
// GET request
$ch = curl_init("https://api.example.com/users");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode === 200) {
    $data = json_decode($response, true);
    print_r($data);
}

// POST request with JSON
$ch = curl_init("https://api.example.com/users");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode(['name' => 'Alice']),
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Authorization: Bearer ' . $token,
    ],
]);
$response = curl_exec($ch);
curl_close($ch);

// simpler: file_get_contents with context
$context = stream_context_create([
    'http' => [
        'method' => 'POST',
        'header' => "Content-Type: application/json\r\n",
        'content' => json_encode(['name' => 'Alice']),
    ],
]);
$result = file_get_contents("https://api.example.com/users", false, $context);
08

Banco de Dados (PDO)

Conexão PDO & Básicos

PDO (PHP Data Objects) é a camada padrão de abstração de banco de dados — suporta MySQL, PostgreSQL, SQLite e mais. Sempre defina ERRMODE_EXCEPTION para tratamento adequado de erros e ATTR_EMULATE_PREPARES=false para prepared statements reais (melhor segurança). FETCH_ASSOC retorna arrays associativos (use FETCH_OBJ para objetos, FETCH_CLASS para mapear a classes). Sempre use charset utf8mb4 para suporte Unicode completo (incluindo emoji). Armazene a conexão em um singleton ou DI container.

php
<?php
// connect (always use exception mode)
$dsn = "mysql:host=localhost;dbname=test;charset=utf8mb4";
$pdo = new PDO($dsn, "username", "password", [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false, // use real prepared statements
]);

// simple query
$stmt = $pdo->query("SELECT * FROM users LIMIT 5");
$users = $stmt->fetchAll(); // array of associative arrays

// fetch one row
$user = $pdo->query("SELECT * FROM users WHERE id = 1")->fetch();

// fetch column
$count = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();

// execute (no params)
$pdo->exec("DELETE FROM logs WHERE created_at < '2023-01-01'");
$deleted = $pdo->rowCount(); // affected rows

Prepared Statements (Prevenção de SQL Injection)

Prepared statements são OBRIGATÓRIOS para qualquer query com entrada do usuário — eles separam a estrutura SQL dos dados, tornando injection impossível. Use ? para parâmetros posicionais ou :name para parâmetros nomeados. Para cláusulas IN, você deve construir a string de placeholder dinamicamente (mas os valores ainda são parameterizados). lastInsertId() retorna o último valor auto-increment. Nunca concatene entrada do usuário em SQL — mesmo com funções de escape. Esta é a regra de segurança #1 em PHP.

php
<?php
// prepared statements: THE way to prevent SQL injection
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ? AND status = ?");
$stmt->execute([$email, 'active']);
$user = $stmt->fetch();

// named parameters (more readable)
$stmt = $pdo->prepare(
    "INSERT INTO users (name, email, age) VALUES (:name, :email, :age)"
);
$stmt->execute([
    ':name' => 'Alice',
    ':email' => '[email protected]',
    ':age' => 30,
]);
$id = $pdo->lastInsertId(); // get auto-increment ID

// IN clause with prepared statements
$ids = [1, 2, 3, 4];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($ids);
$users = $stmt->fetchAll();

// NEVER do this (SQL injection!):
// $pdo->query("SELECT * FROM users WHERE name = '$_GET[name]'");

Transações & Tratamento de Erros

Transações garantem atomicidade — todas as operações têm sucesso ou todas falham. beginTransaction/commit/rollBack envolvem a unidade de trabalho. Sempre envolva transações em try/catch e faça roll back em qualquer exceção. PDO lança PDOException em erros (com ERRMODE_EXCEPTION). Mantenha transações curtas para reduzir lock contention. Para transações aninhadas, use savepoints ou um transaction manager. Nunca deixe uma transação aberta — sempre commit ou roll back.

php
<?php
try {
    $pdo->beginTransaction();

    $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?")
        ->execute([$amount, $fromId]);

    $pdo->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?")
        ->execute([$amount, $toId]);

    // log transaction
    $pdo->prepare("INSERT INTO transfers (from_id, to_id, amount) VALUES (?, ?, ?)")
        ->execute([$fromId, $toId, $amount]);

    $pdo->commit();
    echo "Transfer complete";
} catch (PDOException $e) {
    $pdo->rollBack(); // undo all changes
    error_log("Transfer failed: " . $e->getMessage());
    throw new RuntimeException("Transfer failed", 0, $e);
}

// check if in transaction
if ($pdo->inTransaction()) {
    $pdo->commit();
}

Padrões de Fetch de Dados

Escolha o fetch mode certo para seu caso de uso. FETCH_ASSOC é o mais comum (array com nomes de coluna). FETCH_CLASS mapeia linhas a objetos — ótimo para domain models. FETCH_KEY_PAIR cria maps id=>value (para dropdowns). FETCH_GROUP agrupa linhas pela primeira coluna — útil para relacionamentos one-to-many. Para grandes result sets, use fetch() em um loop em vez de fetchAll() para economizar memória. Sempre feche cursores com $stmt->closeCursor() quando terminar.

php
<?php
$stmt = $pdo->prepare("SELECT id, name, email FROM users WHERE active = 1");
$stmt->execute();

// fetch modes
$row = $stmt->fetch(PDO::FETCH_ASSOC);  // ['id' => 1, 'name' => 'Alice']
$row = $stmt->fetch(PDO::FETCH_NUM);    // [1, 'Alice', '[email protected]']
$row = $stmt->fetch(PDO::FETCH_BOTH);   // both (default)
$obj = $stmt->fetch(PDO::FETCH_OBJ);    // stdClass with properties

// fetch all
$all = $stmt->fetchAll(PDO::FETCH_ASSOC);

// fetch into class
class User {
    public int $id;
    public string $name;
}
$stmt->setFetchMode(PDO::FETCH_CLASS, User::class);
$users = $stmt->fetchAll(); // array of User objects

// fetch key-value pairs
$pairs = $pdo->query("SELECT id, name FROM users")
    ->fetchAll(PDO::FETCH_KEY_PAIR); // [1 => 'Alice', 2 => 'Bob']

// fetch grouped
$grouped = $pdo->query("SELECT dept, name FROM employees")
    ->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_ASSOC);
// ['IT' => [['name' => 'Alice']], 'HR' => [['name' => 'Bob']]]

Melhores Práticas de Banco de Dados

O padrão repository separa acesso a dados da lógica de negócios — tornando o código testável (mock o PDO) e manutenível. Use dependency injection para passar a conexão PDO. Nunca crie novas conexões PDO por query — reutilize uma única conexão (ou pool). Para apps de alto tráfego, considere um connection pooler (ProxySQL para MySQL, PgBouncer para PostgreSQL). Sempre faça profile de queries lentas com EXPLAIN e adicione índices apropriados. Considere um ORM (Doctrine, Eloquent) para domínios complexos.

php
<?php
// 1. Connection singleton (or use DI container)
class Database {
    private static ?PDO $instance = null;

    public static function conn(): PDO {
        if (self::$instance === null) {
            self::$instance = new PDO(
                "mysql:host=localhost;dbname=app;charset=utf8mb4",
                "user", "pass",
                [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                 PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
            );
        }
        return self::$instance;
    }
}

// 2. Repository pattern
class UserRepository {
    public function __construct(private PDO $db) {}

    public function findById(int $id): ?array {
        $stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
        $stmt->execute([$id]);
        $user = $stmt->fetch();
        return $user ?: null;
    }
}

// 3. Always close statements (or let them go out of scope)
// 4. Use LIMIT for queries that might return huge results
// 5. Index columns used in WHERE, JOIN, ORDER BY
// 6. Use EXPLAIN to analyze slow queries
09

Data/Hora & Segurança

Data & Hora

As funções de data do PHP usam o timezone do servidor por padrão — sempre defina date_default_timezone_set('Asia/Shanghai') ou use DateTimeZone explicitamente. A classe DateTime é orientada a objetos e lida com timezones, intervals e formatação melhor que funções procedurais. strtotime() faz parse de descrições de data em inglês ('next Monday', '+1 month') — conveniente mas pode ser surpreendente em limites de mês. Para matemática de data, use DateTime::diff() e DateInterval. Sempre armazene datas em UTC em bancos de dados.

php
<?php
// current time
$now = date('Y-m-d H:i:s');           // 2024-06-15 14:30:00
$timestamp = time();                   // Unix timestamp
$dt = new DateTime();                  // DateTime object

// formatting
echo date('Y-m-d');                    // 2024-06-15
echo date('d/m/Y H:i:s');              // 15/06/2024 14:30:00
echo date('l, F j, Y');                // Saturday, June 15, 2024

// create from string
$dt = new DateTime('2024-01-15');
$dt = DateTime::createFromFormat('d/m/Y', '15/01/2024');

// modify dates
$dt->modify('+1 month');
$dt->modify('-2 days');
$tomorrow = date('Y-m-d', strtotime('tomorrow'));
$nextWeek = date('Y-m-d', strtotime('+1 week'));

// difference
$start = new DateTime('2024-01-01');
$end = new DateTime('2024-12-31');
$diff = $start->diff($end);
echo $diff->days;  // 365

// timezone
$dt = new DateTime('now', new DateTimeZone('Asia/Shanghai'));
$dt->setTimezone(new DateTimeZone('UTC'));

Password Hashing & Segurança

password_hash() usa bcrypt (ou Argon2 se disponível) com geração automática de salt — nunca faça seu próprio hashing. password_verify() verifica senhas contra hashes com segurança (comparação constant-time para prevenir timing attacks). password_needs_rehash() permite que você upgrade hashes quando aumenta o cost factor. Para tokens aleatórios (CSRF, API keys, password resets), sempre use random_bytes() — não rand() ou mt_rand() que são previsíveis. Use hash_hmac para message authentication.

php
<?php
// hash password (bcrypt by default)
$hash = password_hash("mypassword", PASSWORD_DEFAULT);
// $2y$10$... (includes algorithm, cost, salt)

// verify password
if (password_verify($input, $hash)) {
    echo "Valid password";
}

// check if hash needs rehash (algorithm upgrade)
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
    $newHash = password_hash($input, PASSWORD_DEFAULT);
    // update stored hash
}

// NEVER use md5() or sha1() for passwords!
// NEVER store plaintext passwords!

// generate secure random
$token = bin2hex(random_bytes(32));    // 64-char hex string
$bytes = random_bytes(16);             // raw bytes
$int = random_int(1, 1000);            // cryptographically secure

// hash for integrity (not passwords)
$checksum = hash('sha256', $data);
$hmac = hash_hmac('sha256', $data, $secretKey);

Output Escaping & Prevenção de XSS

XSS é a vulnerabilidade web #1 — sempre escape saída com base no contexto. htmlspecialchars() para HTML (ENT_QUOTES escapa tanto aspas simples quanto duplas). urlencode() para URLs. json_encode() com hex flags para contextos JavaScript. Nunca confie em entrada do usuário — escape na saída, não na entrada (você pode precisar dos dados brutos em outro lugar). Defina headers Content-Security-Policy como defense-in-depth. Considere um template engine (Twig, Blade) que auto-escapa por padrão.

php
<?php
// XSS: Cross-Site Scripting — always escape output!
$name = $_GET['name']; // could be: <script>alert('xss')</script>

// HTML context
echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
// converts < > " ' & to HTML entities

// in HTML template
?>
<p>Hello, <?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?></p>
<input value="<?= htmlspecialchars($value, ENT_QUOTES, 'UTF-8') ?>">

<?php
// URL context
$url = "https://example.com/search?q=" . urlencode($query);

// JavaScript context
$json = json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT);
?>
<script>
    var data = <?= $json ?>;
</script>

<?php
// Content Security Policy header
header("Content-Security-Policy: default-src 'self'; script-src 'self'");

JSON & Respostas de API

json_encode/decode são as funções JSON padrão. Sempre defina Content-Type: application/json para respostas de API. Use JSON_UNESCAPED_UNICODE para manter Chinês/emoji legível (caso contrário eles se tornam \uXXXX). json_decode com true retorna arrays associativos (mais comum em PHP). Sempre verifique json_last_error() após decodificar JSON não confiável. Para REST APIs, defina HTTP status codes apropriados (200, 201, 400, 404, 500) e use estrutura de resposta consistente.

php
<?php
// encode PHP array/object to JSON
$data = ['name' => 'Alice', 'age' => 30, 'hobbies' => ['reading', 'coding']];
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
// {"name": "Alice", "age": 30, "hobbies": ["reading", "coding"]}

// decode JSON to PHP
$obj = json_decode($json);       // stdClass object
$arr = json_decode($json, true); // associative array

// handle errors
$data = json_decode($badJson, true);
if (json_last_error() !== JSON_ERROR_NONE) {
    throw new RuntimeException("JSON error: " . json_last_error_msg());
}

// API response
header('Content-Type: application/json; charset=utf-8');
http_response_code(200);
echo json_encode([
    'status' => 'success',
    'data' => $users,
    'meta' => ['page' => 1, 'total' => 100],
], JSON_UNESCAPED_UNICODE);

// API request handling
$input = json_decode(file_get_contents('php://input'), true);

Composer & Gerenciamento de Dependências

Composer é o package manager do PHP — essencial para PHP moderno. require especifica dependências de produção; require-dev para desenvolvimento (testes, etc.). Autoloading PSR-4 mapeia namespaces a diretórios. Sempre commite composer.json e composer.lock (bloqueia versões exatas). Use composer install (do lock) em produção, composer update para obter o mais recente. Packages populares: Monolog (logging), Guzzle (HTTP), PHPUnit (testing), Symfony components, Laravel framework. Nunca commite o diretório vendor/.

php
<?php
// composer.json
// {
//   "require": {
//     "monolog/monolog": "^3.0",
//     "guzzlehttp/guzzle": "^7.0"
//   },
//   "autoload": {
//     "psr-4": {"App\\": "src/"}
//   }
// }

// install: composer install
// update:  composer update
// add:    composer require monolog/monolog

// autoload (at the top of your app)
require 'vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$log = new Logger('app');
$log->pushHandler(new StreamHandler('app.log', Logger::WARNING));
$log->warning('User not found', ['user_id' => 42]);

// environment variables (vlucas/phpdotenv)
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$dbHost = $_ENV['DB_HOST'];
10

Sessions & Cookies

Definindo & Lendo Cookies

Cookies armazenam pequenas quantidades de dados no navegador do usuário. setcookie() deve ser chamado antes de qualquer saída HTML (define HTTP headers). A flag httponly previne que JavaScript leia o cookie (mitiga XSS), e secure garante que só seja enviado por HTTPS. Cookies são enviados com cada request para o domínio/path correspondente, então não armazene dados grandes. Para dados sensíveis, use sessions em vez disso (dados ficam no servidor). Sempre valide e sanitize valores de cookie — eles vêm do cliente e podem ser adulterados.

php
// Set a cookie (must be before any output!)
setcookie("user", "Alice", time() + 3600, "/", "", true, true);
// params: name, value, expiry, path, domain, secure, httponly

// Read cookies (from $_COOKIE superglobal)
if (isset($_COOKIE['user'])) {
    echo "Welcome back, " . htmlspecialchars($_COOKIE['user']);
}

// Delete a cookie: set expiry in the past
setcookie("user", "", time() - 3600, "/");

// Cookie limitations:
// - Sent with every HTTP request (adds bandwidth)
// - Max 4KB per cookie, ~50 per domain
// - Stored client-side (don't store sensitive data!)
// - httponly=true prevents JavaScript access (XSS protection)
// - secure=true sends only over HTTPS

Gerenciamento de Session

Sessions armazenam dados no servidor, identificados por um session ID armazenado em um cookie. Diferente de cookies, dados de session não são visíveis ao usuário (mais seguro para dados sensíveis). session_start() deve ser chamado em toda página que usa sessions, antes de qualquer saída. session_regenerate_id(true) previne ataques de session fixation criando um novo ID e deletando o antigo — chame-o após login. Sempre destrua sessions no logout. Para apps em larga escala, armazene sessions em Redis/banco de dados em vez de arquivos (padrão) para compartilhar entre múltiplos servidores.

php
// Start or resume a session (must be before output)
session_start();

// Store data in the session (lives on server, identified by session ID cookie)
$_SESSION['user_id'] = 42;
$_SESSION['username'] = 'alice';
$_SESSION['cart'] = ['item1', 'item2'];

// Read session data
if (isset($_SESSION['user_id'])) {
    echo "User: " . $_SESSION['username'];
}

// Remove a single session variable
unset($_SESSION['cart']);

// Destroy the entire session
session_unset();   // clear all variables
session_destroy(); // destroy session data on server
setcookie(session_name(), '', time() - 3600, '/'); // clear session cookie

// Regenerate ID to prevent session fixation attacks
session_regenerate_id(true);

Configuração de Session (php.ini)

Configuração de segurança de session é crítica. cookie_httponly previne XSS de roubar session IDs. cookie_secure garante que sessions só funcionem sobre HTTPS. SameSite=Strict previne CSRF (o cookie não é enviado em requests cross-site). use_strict_mode rejeita session IDs não inicializados. gc_maxlifetime define o timeout de inatividade. Para deployments multi-servidor, implemente um SessionHandlerInterface customizado para armazenar sessions em um banco de dados ou Redis — o armazenamento padrão baseado em arquivo não funciona entre servidores. Sempre configure essas opções em produção.

php
// php.ini session settings (or ini_set at runtime)
ini_set('session.cookie_lifetime', 0);     // 0 = until browser closes
ini_set('session.cookie_httponly', 1);     // prevent JS access
ini_set('session.cookie_secure', 1);       // HTTPS only
ini_set('session.cookie_samesite', 'Strict'); // CSRF protection
ini_set('session.use_strict_mode', 1);     // reject uninitialized IDs
ini_set('session.gc_maxlifetime', 1800);   // 30 min inactivity timeout

// Custom session handler (store in database/Redis)
class MySessionHandler implements SessionHandlerInterface {
    public function open($savePath, $sessionName) { /* connect DB */ }
    public function close() { /* close DB */ }
    public function read($id) { /* fetch from DB */ }
    public function write($id, $data) { /* save to DB */ }
    public function destroy($id) { /* delete from DB */ }
    public function gc($maxlifetime) { /* cleanup old sessions */ }
}
session_set_save_handler(new MySessionHandler(), true);
session_start();

Flash Messages (Notificações One-Time)

Flash messages são notificações baseadas em session mostradas uma vez (ex.: 'Saved successfully!') depois automaticamente limpas. O padrão: armazene a mensagem em $_SESSION no request POST, depois leia e faça unset no próximo request GET. Isso implementa o padrão Post/Redirect/Get (PRG) — após uma submissão de formulário, redirecione para prevenir re-submissão no refresh, e mostre a flash message na página redirecionada. Frameworks como Laravel ($request->session()->flash()) e Symfony fornecem suporte integrado a flash messages.

php
// Flash messages: shown once, then deleted
session_start();

// Set a flash message (e.g., after form submission)
$_SESSION['flash'] = [
    'type' => 'success',
    'message' => 'Item added to cart!'
];

// On the next page, display and clear:
if (isset($_SESSION['flash'])) {
    $flash = $_SESSION['flash'];
    unset($_SESSION['flash']);  // delete so it shows only once
    echo "<div class='alert alert-{$flash['type']}'>{$flash['message']}</div>";
}

// Helper function pattern
function flash(string $key, string $value = null): ?string {
    if ($value !== null) {
        $_SESSION['flash_' . $key] = $value;
        return null;
    }
    $val = $_SESSION['flash_' . $key] ?? null;
    unset($_SESSION['flash_' . $key]);
    return $val;
}

Melhores Práticas de Segurança de Session

Segurança de session exige múltiplas camadas. Regenere o session ID após login para prevenir ataques de fixation (onde um atacante define um session ID conhecido). Rastreie last_activity para implementar idle timeouts. Opcionalmente, vincule sessions a IP/user-agent para detecção de hijacking (nota: isso pode causar falsos positivos com redes móveis que mudam IPs). Nunca armazene senhas ou números de cartão de crédito em sessions — armazene apenas um user ID e busque dados sensíveis do banco de dados quando necessário. Sempre use HTTPS em produção para prevenir interceptação de session ID.

php
// 1. Always start session before output
session_start();

// 2. Regenerate ID after login (prevent fixation)
if ($loginSuccessful) {
    session_regenerate_id(true);
    $_SESSION['user_id'] = $user->id;
}

// 3. Validate session on each request
if (isset($_SESSION['user_id'])) {
    // Check session hasn't expired
    if (isset($_SESSION['last_activity']) &&
        time() - $_SESSION['last_activity'] > 1800) {
        session_unset();
        session_destroy();
        header('Location: /login');
        exit;
    }
    $_SESSION['last_activity'] = time();

    // Optional: verify IP/user-agent hasn't changed (anti-hijacking)
    if ($_SESSION['ip'] !== $_SERVER['REMOTE_ADDR']) {
        session_destroy();
        exit('Session hijacking detected');
    }
}

// 4. Use prepared statements for session storage in DB
// 5. Set secure cookie flags (see previous example)
// 6. Never store sensitive data (passwords, CC numbers) in sessions
11

Desenvolvimento de REST API

Tratando JSON Requests & Responses

REST APIs trocam JSON. Diferente de submissões de formulário (que populam $_POST), requests JSON devem ser lidos de php://input e decodificados com json_decode. Sempre defina Content-Type: application/json para respostas e use http_response_code() para HTTP status codes adequados. Valide toda entrada — json_decode não garante a estrutura esperada. Use o operador null coalescing (??) para acesso seguro. JSON_PRETTY_PRINT é útil para debugging mas omita-o em produção para payloads menores.

php
// Get JSON from request body (not $_POST for JSON!)
$json = file_get_contents('php://input');
$data = json_decode($json, true);  // true = associative array

if (json_last_error() !== JSON_ERROR_NONE) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid JSON']);
    exit;
}

// Process the data
$name = $data['name'] ?? '';
$email = filter_var($data['email'] ?? '', FILTER_VALIDATE_EMAIL);

// Return JSON response
header('Content-Type: application/json');
http_response_code(200);  // 200 OK, 201 Created, 400 Bad Request, etc.
echo json_encode([
    'success' => true,
    'data' => ['id' => 1, 'name' => $name],
    'message' => 'User created'
], JSON_PRETTY_PRINT);

Routing & Métodos HTTP

REST APIs mapeiam métodos HTTP para operações CRUD: GET (read), POST (create), PUT/PATCH (update), DELETE (delete). Routing corresponde o método + URL path a um handler. Use preg_match para rotas parameterizadas (ex.: /api/users/42). Em produção, use uma biblioteca de router (FastRoute, Symfony Routing) ou framework (Laravel, Slim) para routing mais limpo, middleware e dependency injection. Sempre retorne HTTP status codes apropriados: 200 (OK), 201 (Created), 204 (No Content), 400 (Bad Request), 404 (Not Found), 500 (Server Error).

php
// Simple REST router based on method + path
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path = trim($path, '/');

// Route table: method => [pattern => handler]
switch (true) {
    case ($method === 'GET' && $path === 'api/users'):
        echo json_encode(getAllUsers());
        break;
    case ($method === 'GET' && preg_match('#^api/users/(\d+)$#', $path, $m)):
        echo json_encode(getUser((int)$m[1]));
        break;
    case ($method === 'POST' && $path === 'api/users'):
        $data = json_decode(file_get_contents('php://input'), true);
        echo json_encode(createUser($data));
        http_response_code(201);
        break;
    case ($method === 'PUT' && preg_match('#^api/users/(\d+)$#', $path, $m)):
        $data = json_decode(file_get_contents('php://input'), true);
        echo json_encode(updateUser((int)$m[1], $data));
        break;
    case ($method === 'DELETE' && preg_match('#^api/users/(\d+)$#', $path, $m)):
        deleteUser((int)$m[1]);
        http_response_code(204);  // No Content
        break;
    default:
        http_response_code(404);
        echo json_encode(['error' => 'Not found']);
}

Autenticação de API (JWT)

JWT habilita autenticação stateless — o servidor não precisa armazenar sessions. O token contém um payload (user ID, expiry) assinado com uma secret key. O cliente envia o token no header Authorization (Bearer token). O servidor verifica a assinatura para garantir que o token não foi adulterado. JWT é ótimo para APIs e microservices (sem armazenamento de session compartilhado necessário). No entanto, JWTs não podem ser revogados antes do expiry — use tempos de expiry curtos e uma estratégia de refresh token. Em produção, use a biblioteca firebase/php-jwt em vez de implementar cripto você mesmo. Nunca armazene dados sensíveis no payload do JWT — é apenas base64-encoded, não encrypted.

php
// JWT (JSON Web Token) authentication flow
// 1. Login: verify credentials, issue JWT
function login($email, $password) {
    $user = findUserByEmail($email);
    if ($user && password_verify($password, $user['password_hash'])) {
        // Create JWT: header.payload.signature
        $header = base64url_encode(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
        $payload = base64url_encode(json_encode([
            'user_id' => $user['id'],
            'exp' => time() + 3600  // expires in 1 hour
        ]));
        $signature = hash_hmac('sha256', "$header.$payload", SECRET_KEY, true);
        $jwt = "$header.$payload." . base64url_encode($signature);
        return $jwt;
    }
    return null;
}

// 2. Verify JWT on protected routes
function verifyJWT($token) {
    $parts = explode('.', $token);
    if (count($parts) !== 3) return false;
    [$header, $payload, $signature] = $parts;
    $expected = base64url_encode(
        hash_hmac('sha256', "$header.$payload", SECRET_KEY, true)
    );
    if (!hash_equals($expected, $signature)) return false;
    $data = json_decode(base64url_decode($payload), true);
    return ($data['exp'] ?? 0) > time() ? $data : false;
}

// Use firebase/php-jwt library in production!

Validação & Sanitização de Entrada

Validação de entrada é crítica para segurança de API. filter_var do PHP fornece validadores integrados (FILTER_VALIDATE_EMAIL, FILTER_VALIDATE_INT, FILTER_VALIDATE_URL) com opções como range min/max. Sempre valide no lado servidor — validação client-side é para UX, não segurança. Sanitize strings com htmlspecialchars para prevenir XSS ao exibir HTML. Para JSON APIs, retorne 422 (Unprocessable Entity) para erros de validação com mensagens descritivas. Considere usar uma biblioteca de validação (Respect/Validation, Symfony Validator) para regras complexas. Nunca confie em entrada do usuário — valide tipo, comprimento, formato e regras de negócio.

php
// Validate and sanitize API input
function validateUserInput(array $data): array {
    $errors = [];

    // Required fields
    if (empty($data['name'])) {
        $errors[] = 'Name is required';
    }

    // Email validation
    $email = filter_var($data['email'] ?? '', FILTER_VALIDATE_EMAIL);
    if (!$email) {
        $errors[] = 'Valid email is required';
    }

    // Age: integer between 18 and 120
    $age = filter_var($data['age'] ?? null, FILTER_VALIDATE_INT, [
        'options' => ['min_range' => 18, 'max_range' => 120]
    ]);
    if ($age === false) {
        $errors[] = 'Age must be between 18 and 120';
    }

    // String sanitization (remove tags, trim)
    $name = htmlspecialchars(trim($data['name'] ?? ''), ENT_QUOTES, 'UTF-8');

    // URL validation
    $website = filter_var($data['website'] ?? '', FILTER_VALIDATE_URL);

    return ['errors' => $errors, 'data' => compact('email', 'age', 'name')];
}

$result = validateUserInput($input);
if (!empty($result['errors'])) {
    http_response_code(422);  // Unprocessable Entity
    echo json_encode(['errors' => $result['errors']]);
    exit;
}

CORS (Cross-Origin Resource Sharing)

CORS controla quais domínios podem acessar sua API a partir de um navegador. Navegadores enviam um request preflight OPTIONS para requests non-simple (PUT/DELETE, headers customizados). Seu servidor deve responder com os headers Access-Control-Allow-* apropriados. Para segurança, especifique origins exatas em vez de '*' (especialmente com credentials). Access-Control-Allow-Credentials: true é necessário se a API usa cookies ou headers Authorization. Vary: Origin diz a caches que a resposta varia por origin. CORS mal configurado pode expor sua API a qualquer site — sempre faça whitelist de origins confiáveis.

php
// Enable CORS for API requests from browsers
header('Access-Control-Allow-Origin: https://example.com');  // specific origin
// OR: header('Access-Control-Allow-Origin: *');  // any origin (less secure)
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Allow-Credentials: true');  // for cookies/auth
header('Access-Control-Max-Age: 86400');  // cache preflight for 24h

// Handle preflight OPTIONS request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

// Dynamic origin (check against whitelist)
$allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins)) {
    header('Access-Control-Allow-Origin: ' . $origin);
    header('Vary: Origin');  // important for caching
}
12

Segurança (XSS, CSRF, SQL Injection)

Prevenindo XSS (Cross-Site Scripting)

XSS ocorre quando dados não confiáveis são inseridos em HTML sem escaping, permitindo que atacantes executem JavaScript nos navegadores das vítimas. A correção: sempre escape saída com htmlspecialchars (converte <, >, &, ", ' para entidades HTML). Contextos diferentes precisam de escaping diferente: corpo HTML (htmlspecialchars), atributos HTML (htmlspecialchars com ENT_QUOTES), JavaScript (json_encode), URLs (urlencode). Headers Content Security Policy (CSP) adicionam defense-in-depth restringindo de onde scripts podem carregar. Nunca use eval(), innerHTML, ou document.write() com entrada do usuário. Frameworks como Twig e Blade auto-escapam por padrão.

php
// XSS: attacker injects malicious JavaScript into your page
// BAD: outputting user input without escaping
echo "<p>" . $_GET['name'] . "</p>";
// If name = <script>alert('xss')</script>, it executes!

// GOOD: escape output with htmlspecialchars
echo "<p>" . htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8') . "</p>";
// & < > " ' are converted to HTML entities

// For HTML attributes:
echo '<input value="' . htmlspecialchars($value, ENT_QUOTES) . '">';

// For JavaScript context (JSON encode):
echo '<script>var name = ' . json_encode($name) . ';</script>';

// For URL parameters:
echo '<a href="?q=' . urlencode($query) . '">Search</a>';

// Content Security Policy (CSP) header — defense in depth
header("Content-Security-Policy: default-src 'self'; script-src 'self'");

// Use prepared statements (prevents SQL injection too)

Prevenindo CSRF (Cross-Site Request Forgery)

CSRF engana o navegador de um usuário autenticado para enviar um request ao seu site (ex.: uma transferência de dinheiro) sem seu conhecimento. A defesa: inclua um token imprevisível em formulários que o atacante não pode adivinhar. O token é armazenado na session e verificado na submissão. Use hash_equals() para comparação timing-safe (previne timing attacks). Para chamadas AJAX/API, cookies SameSite=Strict e exigir headers customizados (como X-Requested-With) fornecem proteção. Requests GET nunca devem modificar dados (eles podem ser disparados por tags de imagem ou links). Frameworks como Laravel e Symfony têm middleware CSRF integrado.

php
// CSRF: attacker tricks a logged-in user into submitting a form
// to your site (using their session cookie)

// Defense: anti-CSRF tokens
session_start();

// Generate a token (once per session)
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// Include token in forms as a hidden field
echo '<form method="POST" action="/transfer">';
echo '<input type="hidden" name="csrf_token" value="' . $_SESSION['csrf_token'] . '">';
echo '<input type="text" name="amount">';
echo '<button type="submit">Transfer</button>';
echo '</form>';

// Verify token on POST/PUT/DELETE requests
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token = $_POST['csrf_token'] ?? '';
    if (!hash_equals($_SESSION['csrf_token'], $token)) {
        http_response_code(403);
        die('CSRF token validation failed');
    }
    // Process the form...
}

// For APIs: use SameSite cookies + custom headers
// (browsers block cross-origin requests without explicit CORS permission)

Prevenindo SQL Injection

SQL injection é a vulnerabilidade web #1 — permite que atacantes leiam/modifiquem/deletem todo seu banco de dados. A correção universal: prepared statements (queries parameterizadas). A estrutura da query e os dados são enviados separadamente, então entrada do usuário nunca pode ser interpretada como SQL. Nunca concatene entrada do usuário em queries. prepare/execute do PDO lida com escaping automaticamente. Faça bind de parâmetros com seus tipos (PDO::PARAM_INT, PDO::PARAM_STR). Para cláusulas IN com itens variáveis, gere placeholders dinamicamente. Defina PDO::ATTR_EMULATE_PREPARES como false para prepared statements reais do lado servidor (melhor segurança).

php
// SQL injection: attacker manipulates queries via unescaped input
// BAD: string concatenation
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
// If id = "1 OR 1=1", returns ALL users!
// If id = "1; DROP TABLE users; --", deletes the table!

// GOOD: prepared statements with parameter binding
$pdo = new PDO('mysql:host=localhost;dbname=myapp', $user, $pass);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);  // real prepared statements

$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);

// Multiple parameters:
$stmt = $pdo->prepare('INSERT INTO users (name, email, age) VALUES (:name, :email, :age)');
$stmt->execute([
    ':name' => $name,
    ':email' => $email,
    ':age' => $age
]);

// IN clause with variable arguments:
$ids = [1, 2, 3];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($ids);

Password Hashing & Autenticação

password_hash() usa bcrypt (ou Argon2) com um salt aleatório — o padrão da indústria para armazenamento de senhas. O salt é embutido no hash, então você não o gerencia separadamente. password_verify() compara com segurança a entrada contra o hash armazenado (timing-safe). password_needs_rehash() permite que você upgrade hashes quando aumenta cost factors ou troca de algoritmo — verifica se o hash corresponde às configurações atuais e re-hasheia no próximo login. Nunca use MD5, SHA1, ou texto plano para senhas — eles são trivialmente quebráveis. Imponha políticas de senha forte mas prefira comprimento sobre complexidade (NIST recomenda 8+ chars mínimo).

php
// NEVER store plain-text or MD5/SHA1 passwords!
// Use password_hash() (bcrypt/argon2 by default)

// Hash a password (on registration)
$password = $_POST['password'];
$hash = password_hash($password, PASSWORD_DEFAULT);
// PASSWORD_DEFAULT = bcrypt (or argon2id in PHP 7.3+)
// Store $hash in the database

// Verify a password (on login)
if (password_verify($inputPassword, $storedHash)) {
    // Password is correct
    session_regenerate_id(true);
    $_SESSION['user_id'] = $user['id'];
} else {
    echo "Invalid credentials";
}

// Rehash if algorithm was upgraded (migration)
if (password_verify($input, $hash) &&
    password_needs_rehash($hash, PASSWORD_DEFAULT)) {
    $newHash = password_hash($input, PASSWORD_DEFAULT);
    // Update database with $newHash
}

// Password requirements validation
if (strlen($password) < 8 ||
    !preg_match('/[A-Z]/', $password) ||
    !preg_match('/[a-z]/', $password) ||
    !preg_match('/[0-9]/', $password)) {
    echo "Password must be 8+ chars with upper, lower, and number";
}

Segurança de Upload de Arquivo

Uploads de arquivo são um grande vetor de ataque. Nunca confie em $_FILES['type'] (definido pelo navegador, facilmente falsificado) — use finfo para detectar o MIME type real. Nunca use o nome de arquivo fornecido pelo usuário (pode conter path traversal como ../../script.php) — gere um nome aleatório. Armazene uploads fora da web root ou em um diretório com execução PHP desativada. Para imagens, re-encode-as (imagecreatefromjpeg + imagejpeg) para remover código PHP embutido escondido em dados EXIF. Limite o tamanho do arquivo para prevenir denial-of-service. Valide extensão, MIME type e magic bytes. Considere escanear uploads com um antivírus (ClamAV) para segurança adicional.

php
// Secure file upload handling
$upload = $_FILES['avatar'];

// 1. Check for upload errors
if ($upload['error'] !== UPLOAD_ERR_OK) {
    die('Upload failed: error ' . $upload['error']);
}

// 2. Validate file size
$maxSize = 2 * 1024 * 1024;  // 2MB
if ($upload['size'] > $maxSize) {
    die('File too large (max 2MB)');
}

// 3. Validate MIME type (don't trust $_FILES['type']!)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($upload['tmp_name']);
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($mimeType, $allowedTypes)) {
    die('Invalid file type');
}

// 4. Generate a safe filename (never use user-supplied name)
$safeName = bin2hex(random_bytes(16)) . '.jpg';

// 5. Store OUTSIDE the web root (or in a non-executable dir)
$dest = __DIR__ . '/uploads/' . $safeName;
if (!move_uploaded_file($upload['tmp_name'], $dest)) {
    die('Failed to save file');
}

// 6. For images: re-encode to strip malicious metadata
$img = imagecreatefromjpeg($dest);
imagejpeg($img, $dest, 90);  // re-encode strips embedded PHP/scripts
13

cURL & HTTP Requests

cURL Básico GET & POST

cURL é o cliente HTTP mais poderoso do PHP, suportando GET, POST, métodos customizados, headers, cookies e SSL. Sempre defina CURLOPT_RETURNTRANSFER para obter a resposta como string (caso contrário é ecoada diretamente). CURLOPT_TIMEOUT previne hangs em servidores lentos. Para POST com JSON, defina headers Content-Type e Content-Length explicitamente. Verifique curl_errno() para erros de conexão e curl_getinfo(CURLINFO_HTTP_CODE) para o status HTTP. Sempre feche handles cURL com curl_close() para liberar recursos. Para código mais simples, considere Guzzle (um wrapper de cURL com API mais limpa).

php
// GET request
$ch = curl_init('https://api.example.com/users');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
    echo 'Error: ' . curl_error($ch);
}
curl_close($ch);
$data = json_decode($response, true);

// POST request with JSON body
$ch = curl_init('https://api.example.com/users');
$payload = json_encode(['name' => 'Alice', 'email' => '[email protected]']);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Authorization: Bearer ' . $token,
        'Content-Length: ' . strlen($payload)
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
curl_close($ch);

cURL com Autenticação & Cookies

cURL suporta múltiplos métodos de autenticação. CURLOPT_USERPWD define HTTP Basic Auth. Bearer tokens vão no header Authorization. Para sessions baseadas em cookie (como logar em um site), use CURLOPT_COOKIEJAR para salvar cookies e CURLOPT_COOKIEFILE para enviá-los em requests subsequentes — isso mantém uma session entre múltiplas chamadas cURL. Use um arquivo temporário para cookies e limpe-o com unlink(). Para chamadas de API, prefira auth baseada em token (Bearer) sobre cookies. Sempre use HTTPS (cURL verifica SSL por padrão — não desabilite CURLOPT_SSL_VERIFYPEER em produção).

php
// Basic Auth
$ch = curl_init('https://api.example.com/data');
curl_setopt($ch, CURLOPT_USERPWD, 'username:password');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);

// Bearer token (API key)
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer ' . $apiKey,
    'Accept: application/json'
]);

// Cookie-based session (login then reuse cookies)
$cookieFile = tempnam(sys_get_temp_dir(), 'cookie');

// Step 1: Login (saves cookies)
$ch = curl_init('https://example.com/login');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query(['user' => 'alice', 'pass' => 'secret']),
    CURLOPT_COOKIEJAR => $cookieFile,   // save cookies here
    CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
curl_close($ch);

// Step 2: Access protected page (sends saved cookies)
$ch = curl_init('https://example.com/dashboard');
curl_setopt_array($ch, [
    CURLOPT_COOKIEFILE => $cookieFile,  // send cookies from here
    CURLOPT_RETURNTRANSFER => true,
]);
$dashboard = curl_exec($ch);
curl_close($ch);
unlink($cookieFile);  // cleanup

Downloads de Arquivo & Streaming

Para downloads de arquivos grandes, use CURLOPT_FILE para gravar diretamente em um file handle — isso evita carregar a resposta inteira na memória. CURLOPT_FOLLOWLOCATION segue redirecionamentos HTTP (301, 302). Para streaming (ex.: dados em tempo real), use CURLOPT_WRITEFUNCTION para processar chunks conforme chegam — útil para APIs que fazem stream de dados. CURLOPT_PROGRESSFUNCTION monitora progresso de download/upload. Defina um CURLOPT_TIMEOUT generoso para arquivos grandes. Para uploads muito grandes, use CURLOPT_INFILE para fazer stream de um arquivo em vez de carregar na memória. Sempre feche file handles e handles cURL para prevenir leaks de recursos.

php
// Download a file to disk
$ch = curl_init('https://example.com/large-file.zip');
$fp = fopen('downloaded.zip', 'w');
curl_setopt_array($ch, [
    CURLOPT_FILE => $fp,           // write directly to file
    CURLOPT_FOLLOWLOCATION => true, // follow redirects
    CURLOPT_TIMEOUT => 300,        // 5 min for large files
]);
curl_exec($ch);
fclose($fp);
curl_close($ch);

// Stream response with callback (progress monitoring)
$ch = curl_init('https://example.com/stream');
curl_setopt_array($ch, [
    CURLOPT_WRITEFUNCTION => function($ch, $chunk) {
        echo $chunk;  // stream to output
        ob_flush();
        flush();
        return strlen($chunk);
    },
    CURLOPT_PROGRESSFUNCTION => function($ch, $dlTotal, $dlNow, $ulTotal, $ulNow) {
        if ($dlTotal > 0) {
            printf("
Progress: %.1f%%", ($dlNow / $dlTotal) * 100);
        }
        return 0;
    },
]);
curl_exec($ch);
curl_close($ch);

Requests Concorrentes (Multi cURL)

curl_multi_exec roda múltiplos requests HTTP em paralelo — dramaticamente mais rápido que requests sequenciais quando você precisa de dados de múltiplos endpoints. O padrão: crie um multi handle, adicione handles cURL individuais, execute o multi handle em um loop (curl_multi_exec + curl_multi_select para eficiência), depois colete resultados. Isso é útil para agregar dados de múltiplas APIs, prefetching de recursos, ou operações em lote. Para concorrência mais avançada, considere ReactPHP ou Amp (frameworks PHP async). Note que multi-cURL ainda bloqueia o processo PHP — para async real, use event loops ou message queues.

php
// Fetch multiple URLs in parallel (much faster than sequential)
$urls = [
    'https://api.example.com/users',
    'https://api.example.com/posts',
    'https://api.example.com/comments'
];

$multi = curl_multi_init();
$handles = [];

// Create individual handles
foreach ($urls as $i => $url) {
    $handles[$i] = curl_init($url);
    curl_setopt($handles[$i], CURLOPT_RETURNTRANSFER, true);
    curl_multi_add_handle($multi, $handles[$i]);
}

// Execute all requests concurrently
$active = null;
do {
    $status = curl_multi_exec($multi, $active);
    if ($active) {
        curl_multi_select($multi);  // wait for activity
    }
} while ($active && $status === CURLM_OK);

// Collect results
$responses = [];
foreach ($handles as $i => $ch) {
    $responses[$i] = json_decode(curl_multi_getcontent($ch), true);
    curl_multi_remove_handle($multi, $ch);
    curl_close($ch);
}
curl_multi_close($multi);

// $responses[0], [1], [2] now contain all three API responses

Usando Guzzle (Cliente HTTP Moderno)

Guzzle é o cliente HTTP padrão para PHP moderno — muito mais limpo que cURL raw. Ele fornece uma API fluente, objetos request/response compatíveis com PSR-7, middleware (logging, retry) e requests async via Promises. A opção 'json' auto-encode o body e define Content-Type. getAsync/postAsync retornam Promises para requests concorrentes sem complexidade de multi-cURL. Tratamento de exceções é integrado: RequestException captura erros HTTP (4xx, 5xx). Guzzle é usado pela maioria dos frameworks (o HTTP client do Laravel envolve Guzzle). Instale via Composer: composer require guzzlehttp/guzzle.

php
// Guzzle is the most popular PHP HTTP client (composer require guzzlehttp/guzzle)
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

$client = new Client([
    'base_uri' => 'https://api.example.com',
    'timeout'  => 30,
    'headers'  => ['Accept' => 'application/json'],
]);

// GET request
$response = $client->get('/users/42');
$body = json_decode($response->getBody(), true);
echo $response->getStatusCode();  // 200

// POST with JSON
$response = $client->post('/users', [
    'json' => ['name' => 'Alice', 'email' => '[email protected]'],
    'headers' => ['Authorization' => 'Bearer ' . $token],
]);

// Concurrent requests (Promise-based)
$promises = [
    'users' => $client->getAsync('/users'),
    'posts' => $client->getAsync('/posts'),
];
$results = GuzzleHttp\Promise\Utils::settle($promises)->wait();

// Error handling with try/catch
try {
    $response = $client->get('/nonexistent');
} catch (RequestException $e) {
    echo $e->getResponse()->getStatusCode();  // 404
}
14

DateTime Aprofundado

Criando & Manipulando DateTime

DateTime é a classe robusta de data/hora do PHP. DateTimeImmutable é preferido sobre DateTime — retorna um novo objeto na modificação, prevenindo bugs de mutação acidental (crítico quando a mesma data é usada em múltiplos lugares). createFromFormat faz parse de formatos customizados. modify() aceita expressões relativas como '+1 week' ou 'last day of next month'. Sempre especifique timezones explicitamente para evitar comportamento dependente da config do servidor. Para matemática de data (adicionar intervals), use DateInterval ('P1D' = 1 dia, 'P2W' = 2 semanas, 'PT2H' = 2 horas) com add()/sub().

php
$now = new DateTime();  // current time
$now->setTimezone(new DateTimeZone('Asia/Shanghai'));

// From string (many formats supported)
$date = new DateTime('2024-06-15 14:30:00');
$date = new DateTime('first day of next month');
$date = new DateTime('next Friday');

// From format (precise parsing)
$date = DateTime::createFromFormat('d/m/Y', '15/06/2024');

// Modify dates with relative expressions
$date->modify('+1 week');
$date->modify('-2 days');
$date->modify('last day of this month');
$date->modify('+1 year 3 months');

// Immutable version (recommended — doesn't modify original)
$dt = new DateTimeImmutable('2024-06-15');
$next = $dt->modify('+1 day');  // $dt unchanged, $next is new object
echo $dt->format('Y-m-d');   // 2024-06-15 (unchanged!)
echo $next->format('Y-m-d'); // 2024-06-16

Formatação & Timezones

format() usa letras de padrão para customizar saída — Y (ano de 4 dígitos), m (mês de 2 dígitos), d (dia de 2 dígitos), H (24-hour), i (minutos), s (segundos). Para ISO 8601 (usado em APIs), use 'Y-m-d\TH:i:sP' ou o atalho 'c'. Conversão de timezone: crie com o timezone de origem, depois setTimezone para converter. Sempre armazene datas em UTC no banco de dados e converta para o timezone do usuário apenas para exibição. O banco de dados de timezones do PHP é abrangente (inclui regras de DST). Use DateTimeZone::listIdentifiers() para obter todas as zonas suportadas. O caractere de escape 'T' em format() produz um 'T' literal (para ISO 8601).

php
// Format codes (most common):
// Y=2024  y=24  m=06  n=6  d=15  j=15
// H=14 (24h)  h=02 (12h)  i=30  s=00  A=PM  a=pm
// D=Mon  l=Monday  M=Jun  F=June  N=1 (Mon=1..Sun=7)

$date = new DateTime('2024-06-15 14:30:00');
echo $date->format('Y-m-d H:i:s');  // 2024-06-15 14:30:00
echo $date->format('l, F j, Y');    // Saturday, June 15, 2024
echo $date->format('Y-m-d\TH:i:sP'); // 2024-06-15T14:30:00+08:00 (ISO 8601)

// Timezone conversion
$utc = new DateTime('2024-06-15 14:00:00', new DateTimeZone('UTC'));
$utc->setTimezone(new DateTimeZone('Asia/Shanghai'));
echo $utc->format('H:i');  // 22:00 (UTC+8)

// List supported timezones
$tzs = DateTimeZone::listIdentifiers();
// ['UTC', 'America/New_York', 'Asia/Shanghai', ...]

// Get timezone offset
$tz = new DateTimeZone('America/Los_Angeles');
$offset = $tz->getOffset(new DateTime());  // seconds from UTC

Date Intervals & Diferenças

DateInterval representa uma duração de tempo usando o formato de duração ISO 8601 (P1Y2M3DT4H5M6S). add() e sub() aplicam intervals a datas. diff() retorna um DateInterval representando a diferença entre duas datas — a propriedade 'days' dá o total de dias, enquanto 'y', 'm', 'd' dão decomposição em componentes. A propriedade 'invert' indica direção (1 se a segunda data for anterior). Cuidado com aritmética de mês: adicionar 'P1M' a Jan 31 dá Mar 2 (Fev tem 28-29 dias), não Fev 31. Para cálculos de dias úteis, itere e pule fins de semana/feriados manualmente ou use uma biblioteca como nesbot/carbon.

php
// DateInterval: represents a duration
$interval = new DateInterval('P1Y2M3DT4H5M6S');
// P = period, 1Y = 1 year, 2M = 2 months, 3D = 3 days
// T = time separator, 4H = 4 hours, 5M = 5 minutes, 6S = 6 seconds

$date = new DateTime('2024-06-15');
$date->add(new DateInterval('P1M'));  // +1 month → 2024-07-15
$date->sub(new DateInterval('P10D')); // -10 days → 2024-07-05

// Difference between two dates
$d1 = new DateTime('2024-01-01');
$d2 = new DateTime('2024-12-31');
$diff = $d1->diff($d2);
echo $diff->days;     // 365 (total days)
echo $diff->m;        // 0 (months component)
echo $diff->y;        // 0 (years component)
echo $diff->format('%a days');  // 365 days
echo $diff->format('%y years, %m months, %d days');  // 0 years, 11 months, 30 days

// Check if inverted (d2 < d1)
echo $diff->invert;   // 0 if d2 >= d1, 1 if d2 < d1

DatePeriod (Iterando Intervalos de Data)

DatePeriod itera sobre um intervalo de datas em um intervalo especificado — perfeito para gerar calendários, relatórios ou eventos recorrentes. O construtor recebe (start, interval, end) ou (start, interval, recurrences). A data final é exclusiva. Casos de uso comuns: gerar todos os dias em um mês para uma visualização de calendário, listar períodos de pagamento, ou criar cronogramas de eventos recorrentes. Use iterator_to_array() para materializar o período em um array. Para regras de recorrência complexas (ex.: 'toda 2ª terça-feira'), considere uma biblioteca dedicada como rrule (regras de recorrência RFC 5545).

php
// DatePeriod iterates over a range of dates
$start = new DateTime('2024-06-01');
$end = new DateTime('2024-06-10');
$interval = new DateInterval('P1D');  // 1 day

$period = new DatePeriod($start, $interval, $end);
foreach ($period as $day) {
    echo $day->format('Y-m-d (D)') . "\n";
}
// 2024-06-01 (Sat)
// 2024-06-02 (Sun)
// ... through 2024-06-09 (end is exclusive)

// Generate next 12 months
$start = new DateTime('first day of this month');
$interval = new DateInterval('P1M');
$period = new DatePeriod($start, $interval, 12);  // 12 recurrences
foreach ($period as $month) {
    echo $month->format('F Y') . "\n";
}

// Every Monday for a year
$start = new DateTime('next Monday');
$end = new DateTime('+1 year');
$period = new DatePeriod($start, new DateInterval('P1W'), $end);
$mondays = iterator_to_array($period);

Biblioteca Carbon (DateTime Aprimorado)

Carbon estende DateTime com uma API fluente e expressiva — é o padrão de fato no ecossistema PHP (usado pelo Laravel). diffForHumans() produz '5 days ago', '3 hours from now' — perfeito para timestamps de UI. A API fluente encadeia métodos (addYear()->subMonth()->endOfMonth()). Métodos de comparação (isWeekend, isPast, isToday) simplificam verificações comuns. Localização suporta 50+ idiomas para saída legível por humanos. Carbon 3 (2024+) é imutável por padrão. Instale via Composer: composer require nesbot/carbon. Se você está usando Laravel, o Carbon já está incluído.

php
// Carbon: the most popular DateTime library (composer require nesbot/carbon)
use Carbon\Carbon;

$now = Carbon::now('Asia/Shanghai');
$tomorrow = Carbon::tomorrow();
$lastWeek = Carbon::now()->subWeek();

// Human-readable differences
echo Carbon::now()->diffForHumans(Carbon::now()->subDays(5));
// "5 days ago"
echo Carbon::now()->addHours(3)->diffForHumans();
// "3 hours from now"

// Fluent API
$date = Carbon::create(2024, 6, 15, 14, 30, 0)
    ->addYear()
    ->subMonth()
    ->endOfMonth()
    ->setTimezone('UTC');

// Comparison methods
if ($date->isWeekend()) { echo "Weekend!"; }
if ($date->isPast()) { echo "Past"; }
if ($date->isFuture()) { echo "Future"; }
if ($date->isToday()) { echo "Today"; }
if ($date->isLeapYear()) { echo "Leap year"; }

// Localization
Carbon::setLocale('zh');
echo Carbon::now()->subDay()->diffForHumans();  // "1天前"
15

Namespaces & Autoloading

Básicos de Namespace

Namespaces organizam código em packages hierárquicos, prevenindo colisões de nome de classe entre bibliotecas. A declaração namespace deve ser a primeira declaração (após declare()). A declaração 'use' importa classes de outros namespaces — coloque declarações use no topo do arquivo. Aliasing (as) resolve conflitos quando duas classes têm o mesmo nome. O backslash inicial (\DateTime) refere-se ao namespace global. Namespaces PHP usam backslashes (\) como separadores, mapeando para estrutura de diretórios em autoloading PSR-4. Declarações group use (use App\Models\{User, Post}) reduzem boilerplate.

php
<?php
// Namespaces prevent name collisions (like packages in Java)
namespace App\Services;

class UserService {
    public function find($id) { /* ... */ }
}

// Using namespaced classes
use App\Services\UserService;
use App\Models\User;

$service = new UserService();
$user = new User();

// Aliasing (resolve conflicts or shorten names)
use App\Services\UserService as USvc;
use App\Models\User as UserModel;

// Global namespace (backslash prefix)
$now = new \DateTime();  // \ means root/global namespace
$array = new \ArrayObject();

// Multiple use statements grouped
use App\Models\{User, Post, Comment};
use App\Services\{UserService, PostService};

Padrão de Autoloading PSR-4

PSR-4 é a especificação padrão de autoloading — mapeia namespaces para paths de diretório, então você nunca precisa de declarações require/include manuais. A regra: App\Services\UserService mapeia para src/Services/UserService.php (App\ → src/). Configure o mapeamento na seção autoload do composer.json. Após adicionar novas classes, rode 'composer dump-autoload' para regenerar o class map. O arquivo vendor/autoload.php (gerado pelo Composer) lida com o carregamento — inclua-o uma vez no seu entry point (index.php). PSR-4 impõe que nomes de classe correspondam a nomes de arquivo (UserService → UserService.php).

php
// PSR-4: namespace structure maps to file paths
// App\Services\UserService → src/Services/UserService.php

// composer.json PSR-4 configuration:
// {
//   "autoload": {
//     "psr-4": {
//       "App\\": "src/"
//     }
//   }
// }

// File: src/Services/UserService.php
namespace App\Services;

class UserService {
    public function getUser($id) {
        return "User $id";
    }
}

// File: src/Models/User.php
namespace App\Models;

class User {
    public $name;
    public function __construct($name) {
        $this->name = $name;
    }
}

// After adding classes, regenerate autoloader:
// $ composer dump-autoload

// The autoloader is included once in your entry point:
require __DIR__ . '/vendor/autoload.php';

$service = new App\Services\UserService();
$user = new App\Models\User('Alice');

Autoloading Sem Composer (spl_autoload)

spl_autoload_register registra uma função que é chamada quando uma classe ainda não foi carregada — ela recebe o fully-qualified class name e deve requerer o arquivo correspondente. Você pode registrar múltiplos autoloaders (eles são chamados em ordem). Isso é o que o Composer usa internamente. Para produção, sempre use o autoloader PSR-4 do Composer — é otimizado, lida com edge cases e gera class maps para lookups mais rápidos. Use spl_autoload_register diretamente apenas para projetos minúsculos ou quando o Composer não está disponível. O parâmetro 'true' em class_exists() aciona autoloading se a classe não estiver carregada.

php
<?php
// Simple autoloader for small projects (no Composer needed)
spl_autoload_register(function ($className) {
    // Convert namespace separators to directory separators
    $file = __DIR__ . '/src/' . str_replace('\\', '/', $className) . '.php';

    // App\Services\UserService → src/App/Services/UserService.php
    // Adjust prefix mapping as needed:
    $file = str_replace('App/', '', $file);  // remove 'App' prefix

    if (file_exists($file)) {
        require $file;
    }
});

// Now classes are loaded automatically on first use
$service = new App\Services\UserService();

// Multiple autoloaders (called in order until one loads the class)
spl_autoload_register(function ($class) {
    $path = __DIR__ . '/lib/' . $class . '.php';
    if (file_exists($path)) require $path;
});

// Check if a class is autoloadable
if (class_exists('App\Helper', true)) {  // true = attempt autoload
    $helper = new App\Helper();
}

Constantes & Funções de Namespace

Namespaces podem conter constantes e funções, não apenas classes. Importe-as com 'use const' e 'use function' (PHP 5.6+). Isso é útil para constantes de configuração e funções utilitárias. Chamadas de função/constante não qualificadas têm um comportamento de fallback: o PHP primeiro procura no namespace atual, depois cai para o namespace global. É por isso que você pode chamar strlen() sem backslash — mas para performance e clareza, prefixe funções globais com \ em código namespaced. Imports group (use App\Config\{const DB_HOST, function connect}) reduzem verbosidade.

php
<?php
namespace App\Config;

// Constants in namespaces
const DB_HOST = 'localhost';
const DB_PORT = 3306;

// Functions in namespaces
function connect() {
    return 'Connected to ' . DB_HOST;
}

// Using namespaced constants and functions
use const App\Config\DB_HOST;
use function App\Config\connect;

echo DB_HOST;      // localhost
echo connect();    // Connected to localhost

// Or with fully-qualified names:
echo \App\Config\DB_HOST;
echo \App\Config\connect();

// Namespace-level use for multiple imports
use App\Config\{const DB_HOST, const DB_PORT, function connect};

// Fallback: unqualified function calls fall back to global
// if not found in current namespace
namespace App;
$len = strlen('hello');  // calls global strlen() (fallback)

Classes Anônimas & Autoloading

Classes anônimas (PHP 7+) permitem criar objetos simples e one-off sem definir uma classe nomeada — útil para interfaces, mock objects e callbacks. Elas podem implementar interfaces, estender classes, ter construtores e usar traits. A classe é gerada em runtime com um nome auto-gerado (class@anonymous). Classes anônimas são carregadas imediatamente (sem autoloading necessário). Use-as para: padrões de strategy simples, test doubles/mocks, event listeners e DTOs. Para classes reutilizáveis, sempre defina classes nomeadas com autoloading PSR-4 adequado. Classes anônimas são especialmente úteis em testes para criar stubs leves.

php
<?php
namespace App\Factory;

// Anonymous class (PHP 7+): create a one-off class inline
interface Logger {
    public function log(string $msg): void;
}

class App {
    private $logger;
    public function setLogger(Logger $logger) {
        $this->logger = $logger;
    }
}

$app = new App();
$app->setLogger(new class implements Logger {
    public function log(string $msg): void {
        echo "[LOG] $msg\n";
    }
});

// Anonymous class with constructor
$comparator = new class($ascending = true) {
    private $asc;
    public function __construct(bool $asc) { $this->asc = $asc; }
    public function compare($a, $b): int {
        return $this->asc ? $a <=> $b : $b <=> $a;
    }
};

// Get the auto-generated class name
echo get_class($comparator);  // class@anonymous...
16

OOP Aprofundado (Traits, Interfaces, Abstract)

Classes & Métodos Abstratos

Classes abstratas fornecem uma base com implementação compartilhada que subclasses estendem. Elas não podem ser instanciadas diretamente. Métodos abstratos definem um contrato (apenas assinatura) que subclasses concretas devem implementar — isso é o 'template method pattern'. Diferente de interfaces, classes abstratas podem ter propriedades, construtores e métodos concretos. Use classes abstratas quando subclasses compartilham implementação significativa (o relacionamento 'is-a'). Use interfaces quando você só precisa de um contrato que qualquer classe pode implementar (o relacionamento 'can-do'). Uma classe pode estender apenas uma classe abstrata mas implementar múltiplas interfaces.

php
<?php
// Abstract class: can't be instantiated, may have abstract methods
abstract class Animal {
    protected $name;

    public function __construct(string $name) {
        $this->name = $name;
    }

    // Abstract method: must be implemented by subclasses
    abstract public function makeSound(): string;

    // Concrete method: shared implementation
    public function describe(): string {
        return $this->name . " says " . $this->makeSound();
    }
}

class Dog extends Animal {
    public function makeSound(): string {
        return "Woof!";
    }
}

class Cat extends Animal {
    public function makeSound(): string {
        return "Meow!";
    }
}

$dog = new Dog("Rex");
echo $dog->describe();  // Rex says Woof!
// new Animal("test");  // Error: cannot instantiate abstract class

Interfaces & Implementação Múltipla

Interfaces definem um contrato — assinaturas de método sem implementação. Uma classe pode implementar múltiplas interfaces (diferente da herança única para classes). Interfaces habilitam polimorfismo: qualquer classe implementando Comparable pode ser ordenada, independentemente do seu tipo concreto. Use interfaces para definir capacidades (Comparable, Serializable, Iterable) que cruzam hierarquias de classe. Type hinting com interfaces (function sort(Comparable $a)) é mais flexível que classes concretas. Herança de interface (interface A extends B, C) combina contratos. PHP moderno também suporta constantes de interface e métodos estáticos em interfaces.

php
<?php
// Interface: pure contract (no implementation)
interface Comparable {
    public function compareTo($other): int;
}

interface Serializable {
    public function serialize(): string;
    public function unserialize(string $data): void;
}

// A class can implement MULTIPLE interfaces
class Product implements Comparable, Serializable {
    private $price;

    public function __construct(float $price) {
        $this->price = $price;
    }

    public function compareTo($other): int {
        return $this->price <=> $other->price;  // spaceship operator
    }

    public function serialize(): string {
        return serialize($this->price);
    }

    public function unserialize(string $data): void {
        $this->price = unserialize($data);
    }
}

// Type hinting with interfaces
function sortItems(array $items): array {
    usort($items, fn($a, $b) => $a->compareTo($b));
    return $items;
}

// Interface inheritance
interface Repository extends Comparable, Serializable {
    public function find(int $id): ?object;
}

Traits (Reuso de Código Sem Herança)

Traits fornecem reuso horizontal de código — métodos que podem ser 'colados' em qualquer classe sem herança. Isso resolve o problema do diamante (PHP tem herança única). Usos comuns de trait: logging, padrão singleton, soft deletes, timestamps. Uma classe pode usar múltiplas traits. Quando traits têm métodos conflitantes, use 'insteadof' para escolher uma e 'as' para aliasar a outra. Traits podem ter métodos abstratos (forçando a classe que usa a implementá-los) e métodos/propriedades estáticos. Cuidado para não abusar de traits — elas podem tornar o código mais difícil de rastrear. Prefira composition (injetar dependências) sobre traits para comportamento complexo.

php
<?php
// Trait: reusable method groups (PHP's answer to multiple inheritance)
trait Logger {
    protected function log(string $msg, string $level = 'INFO'): void {
        echo "[$level] " . date('Y-m-d H:i:s') . " $msg\n";
        // In real code: write to file/database
    }
}

trait Singleton {
    private static $instance;
    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

class UserService {
    use Logger, Singleton;

    public function findUser($id) {
        $this->log("Finding user $id");
        return "User $id";
    }
}

$svc = UserService::getInstance();
$svc->findUser(42);  // [INFO] 2024-06-15 14:30:00 Finding user 42

// Conflict resolution when traits have same method
trait A { public function hello() { return 'A'; } }
trait B { public function hello() { return 'B'; } }
class C {
    use A, B {
        B::hello insteadof A;  // use B's hello
        A::hello as helloFromA; // alias A's as helloFromA
    }
}

Late Static Binding (static:: vs self::)

Late Static Binding (LSB) é a diferença entre self:: (compile-time, sempre refere-se à classe definidora) e static:: (runtime, refere-se à classe chamadora). Isso importa em herança: se Base tem um método usando self::$table, ele sempre vê $table do Base mesmo quando chamado em Child. Usar static::$table faz com que veja $table do Child. LSB é essencial para padrões factory (new static() cria instâncias da classe chamadora), ActiveRecord (cada model tem sua própria table) e o padrão singleton. O return type 'static' (PHP 8+) declara que o método retorna uma instância da classe chamadora.

php
<?php
class Base {
    protected static $table = 'base';

    public static function getTable(): string {
        // self:: refers to the class where the method is DEFINED
        return self::$table;  // always 'base'
    }

    public static function getTableStatic(): string {
        // static:: refers to the class that was CALLED (runtime)
        return static::$table;  // late static binding
    }

    public static function create(): static {
        // 'static' return type + new static() = factory pattern
        return new static();
    }
}

class Child extends Base {
    protected static $table = 'child';
}

echo Child::getTable();        // 'base' (self:: = Base)
echo Child::getTableStatic();  // 'child' (static:: = Child)
echo get_class(Child::create()); // 'Child' (new static = Child)

// self:: is resolved at compile time (the defining class)
// static:: is resolved at runtime (the calling class)
// This is "Late Static Binding" — essential for factory patterns

Magic Methods

Magic methods são métodos especiais que interceptam operações de objeto. __get/__set criam propriedades dinâmicas (útil para data transfer objects, ORMs). __toString habilita echo $object. __invoke torna um objeto callable como uma função. __isset/__unset suportam isset()/unset() em propriedades dinâmicas. __debugInfo customiza saída do var_dump. Outros magic methods: __construct, __destruct, __clone (para deep cloning), __call/__callStatic (para métodos indefinidos, habilita APIs fluentes e mixins), __serialize/__unserialize (substitui __sleep/__wakeup no PHP 7.4+). Use magic methods com moderação — eles adicionam comportamento 'mágico' que pode ser difícil de debugar. Documente-os claramente.

php
<?php
class MagicBox {
    private $data = [];

    // Called when accessing undefined properties
    public function __get($name) {
        return $this->data[$name] ?? null;
    }

    // Called when setting undefined properties
    public function __set($name, $value) {
        $this->data[$name] = $value;
    }

    // Called when isset() or empty() on undefined property
    public function __isset($name): bool {
        return isset($this->data[$name]);
    }

    // Called when unset() on undefined property
    public function __unset($name): void {
        unset($this->data[$name]);
    }

    // Called when object is used as string
    public function __toString(): string {
        return json_encode($this->data);
    }

    // Called when object is called as function
    public function __invoke($arg) {
        return "Called with $arg";
    }

    // Called for var_dump/debugging
    public function __debugInfo(): array {
        return ['keys' => array_keys($this->data)];
    }
}

$box = new MagicBox();
$box->name = "Alice";       // __set
echo $box->name;            // __get → Alice
echo $box;                  // __toString → {"name":"Alice"}
echo $box("test");          // __invoke → Called with test
17

Gerenciamento de Packages com Composer

Básicos do composer.json

composer.json é o manifesto para projetos PHP. require lista dependências de produção com constraints de versão (^ permite updates minor, ~ permite patch). autoload define mapeamento PSR-4 de namespace para diretório. require-dev contém dependências apenas de desenvolvimento. Rode composer install para configurar o projeto.

php
{
    "name": "myorg/myapp",
    "type": "project",
    "require": {
        "php": ">=8.1",
        "monolog/monolog": "^3.0",
        "symfony/console": "^7.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^11.0"
    },
    "autoload": {
        "psr-4": { "MyApp\\": "src/" }
    }
}

Instalando & Atualizando

composer install lê composer.lock para versões exatas (builds reproduzíveis). composer require adiciona um package e resolve dependências. composer update busca versões mais recentes dentro das constraints. Use --no-dev para produção. --optimize-autoloader converte PSR-4 para classmap para autoloading mais rápido em produção.

php
# Install all dependencies from composer.lock
composer install

# Add a package (modifies composer.json)
composer require monolog/monolog
composer require --dev phpunit/phpunit

# Update all packages to latest allowed versions
composer update

# Update a single package
composer update monolog/monolog

# Production install (no dev, optimized autoloader)
composer install --no-dev --optimize-autoloader

# Show installed packages
composer show

Constraints de Versão

Caret (^) é a constraint mais comum: permite mudanças que não modificam o dígito non-zero mais à esquerda. Tilde (~) trava no nível de patch. Para versões 0.x, ^0.3 permite 0.3.x mas não 0.4. Sempre use constraints para obter security patches enquanto evita breaking changes. Fixe versões exatas em composer.lock para reprodutibilidade.

php
"require": {
    // Caret: >=1.2.0, <2.0.0 (allows minor+patch)
    "vendor/pkg": "^1.2",

    // Tilde: >=1.2.0, <1.3.0 (patch only)
    "vendor/pkg2": "~1.2",

    // Exact version
    "vendor/pkg3": "1.2.3",

    // Range
    "vendor/pkg4": ">=1.0 <2.0",

    // Wildcard
    "vendor/pkg5": "1.2.*",

    // Stability flags
    "vendor/pkg6": "dev-main",
    "vendor/pkg7": "2.0@beta"
}

Autoloading PSR-4

Autoloading PSR-4 mapeia prefixos de namespace para diretórios: MyApp\Services\UserService resolve para src/Services/User.php. Rode composer dump-autoload após adicionar novas classes. Para produção, use --optimize para gerar um classmap (um lookup de array em vez de verificações de sistema de arquivos). Autoloading classmap escaneia diretórios e é o mais rápido para codebases fixos.

php
// PSR-4: "MyApp\\": "src/" means
// MyApp\User -> src/User.php
// MyApp\Services\Auth -> src/Services/Auth.php

namespace MyApp\Services;

class UserService
{
    public function find(int $id): ?User
    {
        // ...
    }
}

// After running composer dump-autoload:
// require 'vendor/autoload.php';
// $service = new \MyApp\Services\UserService();

// Classmap (faster for production)
// "autoload": { "classmap": ["src/", "lib/"] }

Scripts & Hooks

Scripts do Composer definem comandos específicos do projeto. Rode com composer <name>. Eventos integrados (post-install-cmd, post-update-cmd, pre-autoload-dump) disparam automaticamente. Scripts podem referenciar outros scripts com @name. Use scripts para padronizar fluxos de trabalho de desenvolvimento entre membros da equipe.

php
{
    "scripts": {
        "test": "phpunit",
        "lint": "phpcs --standard=PSR12 src/",
        "fix": "phpcbf --standard=PSR12 src/",
        "post-install-cmd": [
            "MyApp\\Setup::postInstall",
            "php artisan migrate"
        ],
        "post-update-cmd": "@post-install-cmd"
    }
}

// Run: composer test, composer lint, etc.
18

cURL Avançado

Multi-Request (Paralelo)

curl_multi_exec roda múltiplos requests em paralelo, reduzindo dramaticamente o tempo total para chamadas de API em lote. curl_multi_select bloqueia até haver atividade, evitando busy-waiting. Sempre feche handles e o multi handle para liberar recursos. Esta é a fundação de scraping HTTP de alta performance e agregação de API.

php
<?php
$urls = [
    'https://api.example.com/users',
    'https://api.example.com/posts',
    'https://api.example.com/comments',
];

$multi = curl_multi_init();
$handles = [];

foreach ($urls as $i => $url) {
    $handles[$i] = curl_init($url);
    curl_setopt($handles[$i], CURLOPT_RETURNTRANSFER, true);
    curl_setopt($handles[$i], CURLOPT_TIMEOUT, 10);
    curl_multi_add_handle($multi, $handles[$i]);
}

do {
    $status = curl_multi_exec($multi, $active);
    if ($active) curl_multi_select($multi);
} while ($active && $status === CURLM_OK);

foreach ($handles as $i => $ch) {
    $responses[$i] = curl_multi_getcontent($ch);
    curl_multi_remove_handle($multi, $ch);
}
curl_multi_close($multi);

Streaming de Respostas

CURLOPT_WRITEFUNCTION fornece um callback para cada chunk da resposta, habilitando processamento streaming de arquivos grandes sem carregá-los inteiramente na memória. Retorne o comprimento do chunk para sinalizar consumo. Isso é essencial para baixar arquivos grandes, processar APIs de streaming, ou fazer parse de CSV/JSON incrementalmente.

php
<?php
$ch = curl_init('https://example.com/large-file.csv');
$file = fopen('download.csv', 'w');

curl_setopt($ch, CURLOPT_FILE, $file);  // Write to file
// OR use callback for streaming processing:
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $chunk) use ($file) {
    fwrite($file, $chunk);
    // Or parse incrementally:
    // $lines = explode("\n", $chunk);
    return strlen($chunk);  // Must return bytes consumed
});

curl_exec($ch);
fclose($file);
curl_close($ch);

Autenticação & Cookies

Defina headers customizados com CURLOPT_HTTPHEADER para autenticação (Bearer tokens, API keys). COOKIEJAR/COOKIEFILE persistem cookies entre requests para auth baseada em session. CURLOPT_USERPWD define HTTP Basic Auth. Para POST, defina CURLOPT_POSTFIELDS com JSON e o header Content-Type. Sempre defina Accept para controlar o formato de resposta.

php
<?php
$ch = curl_init('https://api.example.com/data');

// Bearer token
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer ' . $token,
    'Content-Type: application/json',
]);

// Cookie jar (persist across requests)
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');   // Save
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');  // Load

// Basic auth
curl_setopt($ch, CURLOPT_USERPWD, 'username:password');

// POST with JSON body
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['key' => 'value']));

$response = curl_exec($ch);

Tratamento de Erros & Retries

Sempre verifique o valor de retorno de curl_exec (false em caso de falha) e curl_error para a mensagem. curl_getinfo fornece o código de status HTTP, tempo e informações de redirecionamento. Implemente backoff exponencial para retries a fim de lidar com limites de taxa e falhas transitórias. Distinga entre erros de rede (erro do curl) e erros HTTP (código de status).

php
<?php
function fetchWithRetry(string $url, int $max = 3): ?string
{
    for ($attempt = 1; $attempt <= $max; $attempt++) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);

        $response = curl_exec($ch);
        $error = curl_error($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($response !== false && $code >= 200 && $code < 300) {
            return $response;
        }

        if ($attempt < $max) {
            sleep(pow(2, $attempt));  // Exponential backoff
        }
    }
    throw new RuntimeException("Failed: {$error}");
}

Referência de Opções do cURL

CURLOPT_FOLLOWLOCATION segue redirecionamentos HTTP (3xx). Sempre mantenha SSL_VERIFYPEER true em produção para prevenir ataques MITM; baixe cacert.pem de curl.haxx.se. CURLOPT_ENCODING ativa compressão. Use CURLOPT_VERBOSE com STDERR para depurar problemas de conexão. Defina timeouts razoáveis para evitar travamentos.

php
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  // Return not echo
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);   // Follow redirects
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);

// SSL (keep VERIFYPEER true in production!)
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_CAINFO, '/path/to/cacert.pem');

// Performance
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_ENCODING, 'gzip');

// Debug
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_STDERR, fopen('curl.log', 'w'));
19

Processamento de Imagens (GD)

Criando & Carregando Imagens

imagecreatetruecolor cria uma imagem true color (milhões de cores). imagecolorallocate registra uma cor e retorna um identificador. imagecreatefromjpeg/png/webp carrega arquivos existentes. Sempre verifique o valor de retorno (false em caso de falha). Use imagesx/imagesy para obter dimensões. Libere memória com imagedestroy quando terminar.

php
<?php
// Create a blank image
$img = imagecreatetruecolor(400, 300);

// Allocate colors
$white = imagecolorallocate($img, 255, 255, 255);
$red = imagecolorallocate($img, 255, 0, 0);

// Fill background
imagefill($img, 0, 0, $white);

// Load existing images
$photo = imagecreatefromjpeg('photo.jpg');
$png = imagecreatefrompng('logo.png');
$webp = imagecreatefromwebp('image.webp');

// Get dimensions
$width = imagesx($photo);
$height = imagesy($photo);

Desenhando Formas & Texto

GD fornece primitivas de desenho: retângulos, elipses, linhas, polígonos e arcos. Variantes preenchidas (imagefilled*) desenham formas sólidas. imagettftext renderiza fontes TrueType com controle de ângulo e tamanho. Sempre envie um cabeçalho Content-Type antes de exibir dados de imagem. Chame imagedestroy para liberar memória.

php
<?php
$img = imagecreatetruecolor(400, 300);
$white = imagecolorallocate($img, 255, 255, 255);
$red = imagecolorallocate($img, 255, 0, 0);
$blue = imagecolorallocate($img, 0, 0, 255);
imagefill($img, 0, 0, $white);

// Shapes
imagerectangle($img, 50, 50, 150, 120, $red);
imagefilledrectangle($img, 200, 50, 300, 120, $blue);
imageellipse($img, 100, 200, 80, 80, $red);
imageline($img, 0, 0, 400, 300, $red);

// Text with TrueType font
imagettftext($img, 20, 0, 50, 270, $red, 'arial.ttf', 'Hello GD!');

header('Content-Type: image/png');
imagepng($img);
imagedestroy($img);

Redimensionamento & Recorte

imagecopyresampled produz resultados de qualidade superior a imagecopyresized (usa interpolação). Mantenha a proporção calculando as dimensões a partir da original. Para thumbnails, recorte o centro em um quadrado para um layout consistente. Sempre destrua as imagens de origem após copiar para evitar vazamentos de memória em processamento em lote.

php
<?php
function resizeImage($src, $maxW, $maxH) {
    list($w, $h) = getimagesize($src);
    $ratio = min($maxW / $w, $maxH / $h);
    $newW = (int)($w * $ratio);
    $newH = (int)($h * $ratio);

    $srcImg = imagecreatefromjpeg($src);
    $dstImg = imagecreatetruecolor($newW, $newH);

    // High-quality resampling
    imagecopyresampled($dstImg, $srcImg, 0, 0, 0, 0,
        $newW, $newH, $w, $h);

    imagedestroy($srcImg);
    return $dstImg;
}

// Center crop to square
function cropSquare($src, $size) {
    list($w, $h) = getimagesize($src);
    $min = min($w, $h);
    $x = (int)(($w - $min) / 2);
    $y = (int)(($h - $min) / 2);
    $dst = imagecreatetruecolor($size, $size);
    $img = imagecreatefromjpeg($src);
    imagecopyresampled($dst, $img, 0, 0, $x, $y, $size, $size, $min, $min);
    return $dst;
}

Filtros & Efeitos

imagefilter aplica efeitos integrados: grayscale, brightness (intervalo -255 a 255), contrast (negativo aumenta), blur, detecção de bordas, negate e colorize (RGB + alpha). Pixelate cria um efeito de mosaico. Estes são rápidos, porém básicos; para efeitos avançados, use ImageMagick (extensão Imagick) que suporta matrizes de convolução e filtros personalizados.

php
<?php
$img = imagecreatefromjpeg('photo.jpg');

// Built-in filters
imagefilter($img, IMG_FILTER_GRAYSCALE);      // B&W
imagefilter($img, IMG_FILTER_BRIGHTNESS, 30);  // Brighten
imagefilter($img, IMG_FILTER_CONTRAST, -20);   // More contrast
imagefilter($img, IMG_FILTER_GAUSSIAN_BLUR);   // Blur
imagefilter($img, IMG_FILTER_EDGEDETECT);      // Edge detection
imagefilter($img, IMG_FILTER_NEGATE);          // Invert colors

// Colorize (tint)
imagefilter($img, IMG_FILTER_COLORIZE, 0, 0, 100, 0);  // Blue tint

// Pixelate
imagefilter($img, IMG_FILTER_PIXELATE, 10, true);

imagepng($img, 'filtered.png');
imagedestroy($img);

Marcas d'Água & Composição

imagecopymerge sobrepõe uma imagem sobre outra com opacidade ajustável (0-100). Marcas d'água PNG com canais alpha se misturam naturalmente. Para marcas d'água de texto, use imagecolorallocatealpha para texto semitransparente. A qualidade do imagejpeg varia de 0 (pior) a 100 (melhor); 75-90 é um bom equilíbrio para a web. Sempre destrua ambas as imagens.

php
<?php
$photo = imagecreatefromjpeg('photo.jpg');
$watermark = imagecreatefrompng('logo.png');

$pw = imagesx($photo); $ph = imagesy($photo);
$ww = imagesx($watermark); $wh = imagesy($watermark);

// Position: bottom-right with 20px padding
$destX = $pw - $ww - 20;
$destY = $ph - $wh - 20;

// Merge with 50% opacity
imagecopymerge($photo, $watermark, $destX, $destY, 0, 0, $ww, $wh, 50);

// Text watermark
$color = imagecolorallocatealpha($photo, 255, 255, 255, 60);
imagettftext($photo, 30, 0, 20, $ph - 20, $color, 'arial.ttf', '© 2025');

imagejpeg($photo, 'watermarked.jpg', 90);  // 90% quality
imagedestroy($photo);
imagedestroy($watermark);
20

Sessões & Cookies Aprofundado

Segurança de Sessão

Sessões seguras exigem: cookies HttpOnly (sem acesso via JavaScript), flag Secure (apenas HTTPS), SameSite=Strict (proteção CSRF) e modo estrito (rejeitar IDs de sessão não inicializados). Sempre regenere o ID da sessão após mudanças de privilégio (login, acesso administrativo) para prevenir fixação de sessão. Use um nome de sessão personalizado para evitar revelar o PHP.

php
<?php
// php.ini or runtime configuration
ini_set('session.cookie_httponly', 1);    // No JS access
ini_set('session.cookie_secure', 1);       // HTTPS only
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.use_strict_mode', 1);     // Reject uninitialized IDs
ini_set('session.gc_maxlifetime', 3600);   // 1 hour

session_name('APP_SID');  // Custom name
session_start();

// Regenerate ID after login (prevent fixation)
session_regenerate_id(true);

$_SESSION['user_id'] = 123;
$_SESSION['login_time'] = time();

Manipulador de Sessão Personalizado

Manipuladores de sessão personalizados armazenam dados de sessão em bancos de dados, Redis ou Memcached em vez de arquivos. Implemente SessionHandlerInterface com os métodos open, close, read, write, destroy e gc. O armazenamento em banco de dados permite compartilhamento de sessão entre múltiplos servidores (balanceamento de carga). Sempre use consultas parametrizadas para prevenir injeção de SQL em IDs de sessão.

php
<?php
class DbSessionHandler implements SessionHandlerInterface
{
    private PDO $pdo;
    public function __construct(PDO $pdo) { $this->pdo = $pdo; }

    public function open($path, $name): bool { return true; }
    public function close(): bool { return true; }

    public function read($id): string|false {
        $stmt = $this->pdo->prepare(
            'SELECT data FROM sessions WHERE id = ? AND expires > ?'
        );
        $stmt->execute([$id, time()]);
        return $stmt->fetchColumn() ?: '';
    }

    public function write($id, $data): bool {
        $exp = time() + (int)ini_get('session.gc_maxlifetime');
        return $this->pdo->prepare(
            'REPLACE INTO sessions (id, data, expires) VALUES (?, ?, ?)'
        )->execute([$id, $data, $exp]);
    }

    public function destroy($id): bool {
        return $this->pdo->prepare('DELETE FROM sessions WHERE id = ?')
            ->execute([$id]);
    }

    public function gc($max): int|false {
        return $this->pdo->prepare('DELETE FROM sessions WHERE expires < ?')
            ->execute([time()]);
    }
}

session_set_save_handler(new DbSessionHandler($pdo), true);
session_start();

Gerenciamento de Cookies

Use a forma de array de opções de setcookie (PHP 7.3+) para clareza e para definir SameSite. Cookies Secure exigem HTTPS. HttpOnly previne roubo de cookies via XSS. SameSite=Lax bloqueia POST cross-site (suficiente para a maioria da proteção CSRF); Strict bloqueia todas as requisições cross-site. Exclua cookies definindo a expiração no passado com o mesmo caminho/domínio.

php
<?php
// Set a cookie with all security options
setcookie('preferences', json_encode(['theme' => 'dark']), [
    'expires' => time() + 86400 * 30,  // 30 days
    'path' => '/',
    'domain' => '.example.com',
    'secure' => true,                   // HTTPS only
    'httponly' => true,                 // No JavaScript access
    'samesite' => 'Lax'                 // CSRF protection
]);

// Read cookies
$theme = $_COOKIE['preferences'] ?? 'default';

// Delete a cookie (set expiration in the past)
setcookie('preferences', '', [
    'expires' => time() - 3600,
    'path' => '/',
]);

Mensagens Flash

Mensagens flash armazenam notificações únicas na sessão, exibidas após um redirecionamento (padrão Post/Redirect/Get). A mensagem é definida antes do redirecionamento e limpa após a exibição. Isso evita avisos de reenvio e mantém a UI limpa. Armazene como um array para múltiplas mensagens. Limpe imediatamente após a leitura para evitar exibição obsoleta.

php
<?php
// Set a flash message (one-time notification)
function flash(string $key, string $message): void {
    $_SESSION['_flash'][$key] = $message;
}

// Get and clear flash message
function getFlash(string $key): ?string {
    $msg = $_SESSION['_flash'][$key] ?? null;
    unset($_SESSION['_flash'][$key]);
    return $msg;
}

// Usage in controller
flash('success', 'Item saved!');
header('Location: /items');
exit;

// In view after redirect
if ($msg = getFlash('success')) {
    echo "<div class='alert'>$msg</div>";
}

Autenticação JWT

JWT permite autenticação stateless: o servidor não armazena dados de sessão, tornando-o ideal para APIs e microsserviços. O token contém claims (ID do usuário, função, expiração) assinadas com um segredo. Compromissos: tokens não podem ser revogados facilmente (use expiração curta + refresh tokens), e eles aumentam o tamanho da requisição. Use cookies HttpOnly para prevenir roubo de token via XSS.

php
<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

// Generate token on login
$payload = [
    'user_id' => 123,
    'role' => 'admin',
    'iat' => time(),           // Issued at
    'exp' => time() + 3600,    // Expires in 1 hour
];
$token = JWT::encode($payload, $secretKey, 'HS256');

// Send to client (cookie or Authorization header)
setcookie('auth_token', $token, [
    'expires' => time() + 3600,
    'httponly' => true,
    'secure' => true,
    'samesite' => 'Lax',
]);

// Verify on each request
try {
    $decoded = JWT::decode(
        $_COOKIE['auth_token'],
        new Key($secretKey, 'HS256')
    );
    $userId = $decoded->user_id;
} catch (Exception $e) {
    http_response_code(401);
    exit('Unauthorized');
}
21

REST API Aprofundado

Roteamento & Tratamento de Requisições

APIs REST mapeiam métodos HTTP para operações CRUD: GET (ler), POST (criar), PUT/PATCH (atualizar), DELETE (excluir). Faça parse do caminho da URL para identificação de recursos. Leia o corpo da requisição de php://input para POST/PUT. Sempre retorne códigos de status HTTP apropriados (200, 201, 400, 404, 500) e respostas JSON com cabeçalho Content-Type.

php
<?php
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', trim($path, '/'));

switch (true) {
    case $method === 'GET' && $segments[0] === 'users':
        if (isset($segments[1])) getUser((int)$segments[1]);
        else listUsers();
        break;

    case $method === 'POST' && $segments[0] === 'users':
        $data = json_decode(file_get_contents('php://input'), true);
        createUser($data);
        break;

    case $method === 'PUT' && $segments[0] === 'users' && isset($segments[1]):
        $data = json_decode(file_get_contents('php://input'), true);
        updateUser((int)$segments[1], $data);
        break;

    default:
        http_response_code(404);
        echo json_encode(['error' => 'Not Found']);
}

Resposta & Códigos de Status

Sempre defina Content-Type: application/json para respostas de API. Use códigos de status corretos: 200 (OK), 201 (Created), 204 (No Content), 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found), 422 (Unprocessable Entity), 429 (Too Many Requests), 500 (Server Error). Inclua detalhes de erro para depuração, mas nunca exiba stack traces em produção.

php
<?php
function jsonResponse($data, int $status = 200): void
{
    http_response_code($status);
    header('Content-Type: application/json');
    echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    exit;
}

// Success responses
jsonResponse(['data' => $users], 200);          // OK
jsonResponse(['data' => $user], 201);           // Created

// Error responses
jsonResponse(['error' => 'Validation failed'], 400);
jsonResponse(['error' => 'Unauthorized'], 401);
jsonResponse(['error' => 'Forbidden'], 403);
jsonResponse(['error' => 'Not Found'], 404);
jsonResponse(['error' => 'Server error'], 500);

Paginação & Filtragem

Implemente paginação com LIMIT/OFFSET e retorne metadados (total, página atual, total de páginas). Valide e sanitize colunas de ordenação contra uma whitelist para prevenir injeção de SQL. Limite per_page para evitar consultas excessivas. Use LIKE para busca com wildcards. Retorne metadados de paginação em um objeto meta separado dos dados.

php
<?php
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = min(100, max(1, (int)($_GET['per_page'] ?? 20)));
$offset = ($page - 1) * $perPage;

// Whitelist sort columns (prevent SQL injection)
$sort = in_array($_GET['sort'] ?? '', ['name', 'email'])
    ? $_GET['sort'] : 'id';
$order = strtolower($_GET['order'] ?? 'asc') === 'desc' ? 'DESC' : 'ASC';

$sql = "SELECT * FROM users ORDER BY $sort $order LIMIT ? OFFSET ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$perPage, $offset]);
$users = $stmt->fetchAll();

// Total count for pagination metadata
$total = (int)$pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();

jsonResponse([
    'data' => $users,
    'meta' => [
        'page' => $page,
        'per_page' => $perPage,
        'total' => $total,
        'total_pages' => ceil($total / $perPage),
    ],
]);

Limitação de Taxa

A limitação de taxa previne abuso de API. Use algoritmos de janela fixa (simples) ou janela deslizante (mais preciso). Armazene contadores em Redis para sistemas distribuídos. Retorne cabeçalhos X-RateLimit (Limit, Remaining, Reset) para que clientes possam se autorregular. HTTP 429 com Retry-After informa aos clientes quando tentar novamente. Para produção, use Redis ou um limitador de taxa dedicado.

php
<?php
function checkRateLimit(int $userId, int $max = 100, int $window = 3600): bool
{
    $file = sys_get_temp_dir() . "/rate_{$userId}_" . floor(time() / $window);
    $count = file_exists($file) ? (int)file_get_contents($file) : 0;

    if ($count >= $max) {
        $reset = (floor(time() / $window) + 1) * $window;
        header("X-RateLimit-Limit: $max");
        header("X-RateLimit-Remaining: 0");
        header("Retry-After: " . ($reset - time()));
        http_response_code(429);
        echo json_encode(['error' => 'Rate limit exceeded']);
        return false;
    }

    file_put_contents($file, $count + 1);
    header("X-RateLimit-Remaining: " . ($max - $count - 1));
    return true;
}

if (!checkRateLimit($userId)) exit;

Versionamento de API

Estratégias de versionamento de API: prefixo de URL (/v1/) é o mais explícito e amigável a cache; cabeçalho Accept é RESTful, mas mais difícil de testar. Documente APIs com anotações OpenAPI (Swagger). Gere docs interativas com ferramentas como swagger-php. Versione desde o início; mudanças que quebram compatibilidade exigem uma nova versão. Descontinue versões antigas com cabeçalho Sunset.

php
<?php
// Versioning via URL prefix: /v1/users, /v2/users
$version = $segments[0] ?? 'v1';

// Versioning via Accept header
// Accept: application/vnd.myapp.v2+json
preg_match('/vnd\.myapp\.(v\d+)\+json/',
    $_SERVER['HTTP_ACCEPT'] ?? '', $m);
$version = $m[1] ?? 'v1';

// OpenAPI/Swagger documentation
/**
 * @OA\Get(path="/api/users",
 *   @OA\Response(response=200, description="List users")
 * )
 */
22

Segurança Aprofundada (XSS/CSRF)

Prevenção de XSS

XSS (Cross-Site Scripting) injeta scripts maliciosos em páginas web. Evite-o codificando a saída com base no contexto: htmlspecialchars para HTML, json_encode para JavaScript, urlencode para URLs. ENT_QUOTES escapa tanto aspas simples quanto duplas. Content-Security-Policy (CSP) adiciona defesa em profundidade restringindo fontes de scripts. Nunca confie na entrada do usuário.

php
<?php
// Output encoding (prevents XSS)
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');

// JavaScript string (use json_encode)
echo '<script>var name = ' . json_encode($name) . ';</script>';

// URL parameter
echo 'redirect=' . urlencode($url);

// Content-Security-Policy header
header("Content-Security-Policy: default-src 'self'; script-src 'self'");

// Disable inline scripts
header("X-XSS-Protection: 1; mode=block");

Proteção CSRF

CSRF (Cross-Site Request Forgery) engana usuários para que executem ações indesejadas. Evite-o com tokens anti-CSRF: gere um token aleatório por sessão, inclua-o em formulários como um campo oculto e verifique em POST/PUT/DELETE. Use hash_equals para comparação segura contra timing attacks. Para AJAX, envie o token em um cabeçalho personalizado. Cookies SameSite=Strict fornecem proteção adicional.

php
<?php
// Generate CSRF token
function csrfToken(): string {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

// Verify CSRF token (timing-safe)
function verifyCsrf(string $token): bool {
    return !empty($_SESSION['csrf_token'])
        && hash_equals($_SESSION['csrf_token'], $token);
}

// In the form
echo '<input type="hidden" name="csrf_token" value="' . csrfToken() . '">';

// On POST request
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!verifyCsrf($_POST['csrf_token'] ?? '')) {
        http_response_code(403);
        die('CSRF token validation failed');
    }
}

Prevenção de Injeção de SQL

A injeção de SQL permite que atacantes executem SQL arbitrário. Sempre use prepared statements com consultas parametrizadas: o banco de dados separa a lógica SQL dos dados, tornando a injeção impossível. Nunca concatene entrada do usuário em strings SQL. Para consultas dinâmicas (cláusulas IN, ORDER BY), construa a estrutura SQL com placeholders e passe valores como parâmetros.

php
<?php
// BAD: string concatenation (SQL injection vulnerable)
$sql = "SELECT * FROM users WHERE name = '" . $_GET['name'] . "'";
// Attack: ?name=' OR '1'='1

// GOOD: prepared statements
$stmt = $pdo->prepare('SELECT * FROM users WHERE name = ? AND status = ?');
$stmt->execute([$_GET['name'], 'active']);
$users = $stmt->fetchAll();

// Named parameters
$stmt = $pdo->prepare(
    'INSERT INTO users (name, email) VALUES (:name, :email)'
);
$stmt->execute([':name' => $name, ':email' => $email]);

// Dynamic IN clause (still safe)
$ids = [1, 2, 3];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($ids);

Hashing de Senhas

Nunca armazene senhas em texto puro. password_hash usa bcrypt (ou Argon2, se disponível) com geração automática de salt. O hash inclui o algoritmo, custo e salt, então password_verify pode verificar em qualquer formato. Use password_needs_rehash para atualizar hashes quando você aumentar o fator de custo ou trocar de algoritmo. Argon2 é recomendado para novas aplicações.

php
<?php
// Hash a password (uses bcrypt by default, auto-salts)
$hash = password_hash('myPassword123', PASSWORD_DEFAULT);
// $2y$10$... (bcrypt with cost 10)

// Verify a password
if (password_verify($inputPassword, $storedHash)) {
    // Check if rehash is needed (algorithm upgrade)
    if (password_needs_rehash($storedHash, PASSWORD_DEFAULT)) {
        $newHash = password_hash($inputPassword, PASSWORD_DEFAULT);
        // Update stored hash in database
    }
    // Log user in
}

// Argon2 (PHP 7.2+, requires libsodium)
$hash = password_hash('password', PASSWORD_ARGON2ID, [
    'memory_cost' => 65536,  // 64 MB
    'time_cost'   => 4,      // iterations
    'threads'     => 2,
]);

Validação de Entrada

Sempre valide a entrada no servidor (a validação no cliente é apenas para UX). Use filter_input com FILTER_VALIDATE_* para verificação de tipos e FILTER_SANITIZE_* para limpeza. Para regras personalizadas, use regex ou bibliotecas de validação dedicadas (Respect/Validation, Symfony Validator). Use uma abordagem de whitelist: aceite apenas campos conhecidos, rejeite todo o resto.

php
<?php
// Validate email
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
if ($email === false) {
    $errors[] = 'Invalid email';
}

// Validate integer with range
$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT, [
    'options' => ['min_range' => 1, 'max_range' => 150]
]);

// Validate URL
$url = filter_input(INPUT_POST, 'website', FILTER_VALIDATE_URL);

// Custom validation with regex
function validateUsername(string $u): ?string {
    if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $u)) {
        return 'Username must be 3-20 alphanumeric chars';
    }
    return null;
}

// Whitelist approach for arrays
$allowed = ['name', 'email', 'age'];
$input = array_intersect_key($_POST, array_flip($allowed));
23

Namespaces & Autoloading Aprofundado

Declaração de Namespace

Namespaces evitam colisões de nomes de classes e organizam o código hierarquicamente. A declaração namespace deve ser a primeira instrução. use importa classes, funções e constantes. Aliases (as) resolvem conflitos. O namespacing do PHP usa barras invertidas. O padrão PSR-4 mapeia separadores de namespace para separadores de diretório: MyApp\Services\UserService -> src/Services/UserService.php.

php
<?php
// File: src/Services/UserService.php
namespace MyApp\Services;

use MyApp\Models\User;
use MyApp\Repositories\UserRepository;
use MyApp\Exceptions\NotFoundException;

class UserService
{
    public function __construct(
        private UserRepository $repo
    ) {}

    public function find(int $id): User
    {
        $user = $this->repo->findById($id);
        if (!$user) {
            throw new NotFoundException("User {$id} not found");
        }
        return $user;
    }
}

Autoloading PSR-4

PSR-4 é a especificação padrão de autoloading: um prefixo de namespace mapeia para um diretório base, e cada separador de namespace se torna um separador de diretório. O Composer gera o autoloader que resolve nomes de classes para caminhos de arquivo automaticamente. Execute composer dump-autoload após adicionar novas classes. O autoloader só carrega classes quando elas são referenciadas pela primeira vez (lazy loading).

php
// composer.json
{
    "autoload": {
        "psr-4": {
            "MyApp\\": "src/",
            "Tests\\": "tests/"
        }
    }
}

// After composer install:
require __DIR__ . '/vendor/autoload.php';

// Now all classes auto-load:
use MyApp\Services\UserService;
use MyApp\Controllers\UserController;

// PSR-4 rules:
// MyApp\User          -> src/User.php
// MyApp\Services\Auth -> src/Services/Auth.php
// MyApp\Models\User\Profile -> src/Models/User/Profile.php

Autoloader Personalizado

spl_autoload_register adiciona uma função à pilha do autoloader. Quando uma classe é referenciada, mas não carregada, o PHP chama cada autoloader registrado em ordem até que um carregue a classe. Múltiplos autoloaders podem coexistir (ex.: um para PSR-4, um para classes legadas). Sempre verifique se o arquivo existe antes de requerê-lo para evitar erros. O Composer usa esse mecanismo internamente.

php
<?php
spl_autoload_register(function (string $class): void {
    $prefix = 'MyApp\\';
    $baseDir = __DIR__ . '/src/';

    // Check if class uses our namespace prefix
    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;  // Not our class
    }

    // Get relative class name
    $relativeClass = substr($class, $len);

    // Replace namespace separators with directory separators
    $file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';

    if (file_exists($file)) {
        require $file;
    }
});

// Multiple autoloaders can be registered (stack-based)
spl_autoload_register(function ($class) {
    $path = __DIR__ . '/legacy/' . $class . '.php';
    if (file_exists($path)) require $path;
});

Autoloading Classmap & Files

O autoloading classmap examina diretórios no momento do dump-autoload e constrói um array mapeando nomes de classes para caminhos de arquivo. Isso é mais rápido que PSR-4 (uma busca em array vs. verificações de sistema de arquivos) e é recomendado para produção. files carrega arquivos específicos a cada requisição, útil para funções auxiliares e constantes que não podem ser autoloaded como classes. Use --optimize para o classmap de produção.

php
{
    "autoload": {
        "psr-4": { "MyApp\\": "src/" },
        "classmap": ["src/Legacy/", "lib/OldClasses.php"],
        "files": ["src/helpers.php", "src/constants.php"]
    }
}

// classmap: scans directories and builds class-to-file map
// Faster than PSR-4 for production (no filesystem checks)

// files: always-loaded files (for functions and constants)
// src/helpers.php:
<?php
function dd($var) { var_dump($var); die; }
function env(string $key, $default = null) { /* ... */ }

Resolução de Namespace

Em código com namespace, nomes de classes não qualificados resolvem primeiro por imports, depois pelo namespace atual. Classes integradas (DateTime, PDO, Exception) vivem no namespace global; referencie-as com uma barra invertida inicial ou importe-as. Funções e constantes recorrem ao namespace global se não forem encontradas localmente. Use FQCN (barra invertida inicial) para referências absolutas.

php
<?php
namespace MyApp\Services;

use MyApp\Models\User;

class UserService
{
    public function create(): User
    {
        // User resolves to MyApp\Models\User (imported)
        return new User();
    }

    public function find(): \MyApp\Models\User
    {
        // Fully Qualified Class Name (leading backslash)
        return new \MyApp\Models\User();
    }

    public function date(): \DateTime
    {
        // Global classes need backslash or import
        return new \DateTime();
    }
}
24

Generators & Yield

Generator Básico

Generators produzem valores de forma preguiçosa com yield, um de cada vez, sem construir a coleção inteira na memória. Isso é eficiente em memória para sequências grandes ou infinitas. A função retorna um objeto Generator que implementa Iterator. Cada yield pausa a execução, retomando na próxima iteração. Use generators para ler arquivos grandes, cursores de banco de dados e sequências computadas.

php
<?php
function rangeGen(int $start, int $end, int $step = 1): Generator
{
    for ($i = $start; $i <= $end; $i += $step) {
        yield $i;
    }
}

// Iterate without loading all values into memory
foreach (rangeGen(1, 5) as $value) {
    echo $value . ' ';  // 1 2 3 4 5
}

// Memory efficient for large sequences
foreach (rangeGen(1, 1000000) as $value) {
    if ($value % 100000 === 0) echo "Reached {$value}\n";
}

Yield de Pares Chave-Valor

Generators podem produzir pares chave-valor usando a sintaxe yield key => value, assim como arrays associativos. Isso preserva as chaves através de transformações. Para filtrar valores, simplesmente não os produza com yield. O Generator mantém sua posição na iteração, então você pode construir processamento em estilo de pipeline onde cada generator transforma ou filtra o fluxo.

php
<?php
function mapGen(array $data): Generator
{
    foreach ($data as $key => $value) {
        yield $key => strtoupper($value);
    }
}

foreach (mapGen(['a' => 'hello', 'b' => 'world']) as $key => $value) {
    echo "{$key} => {$value}\n";
}
// a => HELLO
// b => WORLD

// Filter by not yielding unwanted values
function filterGen(array $data): Generator
{
    foreach ($data as $value) {
        if ($value > 0) yield $value;
    }
}

Enviando Valores para Generators

O método send() passa um valor para o generator, que se torna o resultado da expressão yield. Isso permite comunicação bidirecional, útil para coroutines e máquinas de estado. current() inicia o generator. getReturn() recupera o valor de retorno após o generator ser concluído. O bloco finally executa quando o generator é destruído, permitindo limpeza de recursos.

php
<?php
function accumulator(): Generator
{
    $total = 0;
    while (true) {
        $value = yield $total;
        if ($value === null) break;
        $total += $value;
    }
    return $total;
}

$gen = accumulator();
$gen->current();  // Start: 0
$gen->send(10);   // Returns 10
$gen->send(20);   // Returns 30
$gen->send(5);    // Returns 35
$gen->send(null); // Triggers return
echo $gen->getReturn();  // 35

Yield from (Delegação)

yield from delega para outro generator, array ou Traversable, achatando seus valores no generator externo. O valor de retorno do generator interno fica disponível para o generator externo. Isso permite composição: construa pipelines complexos a partir de generators simples. yield from também é mais eficiente que iterar e produzir yield manualmente.

php
<?php
function innerGen(): Generator
{
    yield 1;
    yield 2;
    return 'inner done';
}

function outerGen(): Generator
{
    yield 0;
    $result = yield from innerGen();
    echo "Inner returned: {$result}\n";
    yield 3;
}

foreach (outerGen() as $value) {
    echo $value . ' ';  // 0 1 2 3
}
// Inner returned: inner done

// Yield from arrays
function mixedGen(): Generator {
    yield from [10, 20, 30];
    yield from new ArrayObject([40, 50]);
}

Casos de Uso Práticos

Generators se destacam no processamento de fluxos de dados grandes ou infinitos: leitura de arquivos linha por linha, iteração de cursor de banco de dados, busca paginada de APIs e sequências matemáticas. O padrão take() limita um generator infinito. Generators compõem bem: encadeie dados através de múltiplos generators para filtragem, mapeamento e redução. A memória permanece constante independentemente do tamanho dos dados.

php
<?php
// 1. Read large files line by line (memory efficient)
function readLines(string $file): Generator {
    $handle = fopen($file, 'r');
    while (!feof($handle)) {
        $line = fgets($handle);
        if ($line !== false) yield rtrim($line);
    }
    fclose($handle);
}

foreach (readLines('large.log') as $line) {
    if (str_contains($line, 'ERROR')) {
        echo "{$line}\n";
    }
}

// 2. Infinite sequence with take()
function naturals(): Generator {
    $n = 1;
    while (true) yield $n++;
}

function take(Generator $gen, int $n): Generator {
    for ($i = 0; $i < $n; $i++) {
        yield $gen->current();
        $gen->next();
    }
}

foreach (take(naturals(), 5) as $num) {
    echo $num . ' ';  // 1 2 3 4 5
}
25

Segurança

Prevenção de Injeção de SQL

A injeção de SQL ocorre quando a entrada do usuário é concatenada em SQL. Sempre use prepared statements com consultas parametrizadas. PDO e MySQLi ambos os suportam. Nunca confie na entrada do usuário. Valide e sanitize todos os dados externos.

php
// BAD: vulnerable
$sql = "SELECT * FROM users WHERE name = '" . $_POST['name'] . "'";
// GOOD: prepared statements
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = :name");
$stmt->execute([':name' => $_POST['name']]);
$users = $stmt->fetchAll();

Prevenção de XSS

XSS (Cross-Site Scripting) injeta scripts maliciosos. htmlspecialchars converte caracteres especiais em entidades HTML. ENT_QUOTES escapa tanto aspas simples quanto duplas. Sempre escape ao exibir dados do usuário em HTML. Use cabeçalhos Content-Security-Policy para defesa em profundidade.

php
// BAD: output without escaping
echo $_GET['name'];
// GOOD: escape output
echo htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');
// For HTML attributes
echo 'value="' . htmlspecialchars($value, ENT_QUOTES) . '"';

Hashing de Senhas

password_hash usa bcrypt (ou argon2) com geração automática de salt. Nunca use md5 ou sha1 para senhas. password_verify verifica uma senha contra um hash. password_needs_rehash permite atualizar algoritmos de hash. O salt é incorporado na string de hash.

php
// Hash a password
$hash = password_hash('mypassword', PASSWORD_DEFAULT);
// Verify
if (password_verify('mypassword', $hash)) {
    echo 'Valid password';
}
// Check if needs rehash
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
    $newHash = password_hash('mypassword', PASSWORD_DEFAULT);
}

Proteção CSRF

CSRF (Cross-Site Request Forgery) engana usuários para ações indesejadas. Gere um token aleatório por sessão. Inclua-o em formulários como um campo oculto. Verifique em POST usando hash_equals (comparação segura contra timing attacks). Cookies SameSite fornecem proteção adicional.

php
session_start();
if (empty($_SESSION['token'])) {
    $_SESSION['token'] = bin2hex(random_bytes(32));
}
// In form
echo '<input type="hidden" name="token" value="' . $_SESSION['token'] . '">';
// Verify
if (!hash_equals($_SESSION['token'], $_POST['token'] ?? '')) {
    die('CSRF token mismatch');
}

Segurança de Sessão

cookie_httponly previne acesso via JavaScript. cookie_secure garante apenas HTTPS. samesite=Strict previne CSRF. use_strict_mode rejeita IDs de sessão não inicializados. session_regenerate_id previne fixação de sessão. Sempre regenere após mudanças de privilégio.

php
// Secure session settings
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);  // HTTPS only
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.use_strict_mode', 1);
session_start();
// Regenerate ID after login
session_regenerate_id(true);

Was this helpful?