Cristian Bernardes
Senior PHP | Node.js Developer | Expert em Arquitetura de Software, Laravel, Docker & Microsserviços
📌 Introdução
O ecossistema PHP moderno vai muito além de simplesmente escrever código que funciona. Existe um conjunto de técnicas avançadas, padrões arquiteturais e otimizações que separam desenvolvedores competentes de verdadeiros mestres da linguagem.
Este artigo explora técnicas avançadas que raramente são discutidas na comunidade brasileira, mas que têm impacto direto na qualidade, performance e manutenibilidade de aplicações enterprise. Se você já domina os fundamentos, está pronto para o próximo nível.
"Código elegante não é aquele que simplesmente funciona, mas aquele que evolui com graça ao longo do tempo." - Uncle Bob Martin
1. 🎯 Generator Pipelines para Processamento de Streams
O Poder dos Generators Encadeados
Generators são subestimados pela maioria dos desenvolvedores. Enquanto muitos os usam apenas para economizar memória, poucos exploram seu verdadeiro potencial: criar pipelines de processamento de dados extremamente eficientes e elegantes.
Caso real: Uma aplicação de análise de logs que processava arquivos de 50GB estava consumindo toda a memória do servidor. Ao implementar generator pipelines, o consumo de memória caiu para 2MB constantes, independente do tamanho do arquivo.
Implementação Avançada
<?php
class StreamPipeline
{
private array $stages = [];
public function pipe(callable $transformer): self
{
$this->stages[] = $transformer;
return $this;
}
public function process(iterable $source): Generator
{
$current = $this->ensureGenerator($source);
foreach ($this->stages as $stage) {
$current = $stage($current);
}
yield from $current;
}
private function ensureGenerator(iterable $source): Generator
{
if ($source instanceof Generator) {
return $source;
}
return (function() use ($source) {
yield from $source;
})();
}
}
// Funções de transformação reutilizáveis
function filter(callable $predicate): callable
{
return function(iterable $items) use ($predicate): Generator {
foreach ($items as $key => $item) {
if ($predicate($item, $key)) {
yield $key => $item;
}
}
};
}
function map(callable $transformer): callable
{
return function(iterable $items) use ($transformer): Generator {
foreach ($items as $key => $item) {
yield $key => $transformer($item, $key);
}
};
}
function take(int $limit): callable
{
return function(iterable $items) use ($limit): Generator {
$count = 0;
foreach ($items as $key => $item) {
if ($count++ >= $limit) break;
yield $key => $item;
}
};
}
function chunk(int $size): callable
{
return function(iterable $items) use ($size): Generator {
$buffer = [];
foreach ($items as $item) {
$buffer[] = $item;
if (count($buffer) >= $size) {
yield $buffer;
$buffer = [];
}
}
if (!empty($buffer)) {
yield $buffer;
}
};
}
Exemplo Prático: Processamento de Logs
<?php
class LogAnalyzer
{
private StreamPipeline $pipeline;
public function __construct()
{
$this->pipeline = new StreamPipeline();
}
public function analyzeLogFile(string $filepath): array
{
$statistics = [
'total_errors' => 0,
'error_types' => [],
'peak_hour' => null
];
$results = $this->pipeline
->pipe($this->readLargeFile($filepath))
->pipe($this->parseLogLine())
->pipe(filter(fn($log) => $log['level'] === 'ERROR'))
->pipe(map(fn($log) => $this->enrichLogData($log)))
->pipe(chunk(1000))
->process([]);
foreach ($results as $batch) {
$this->processBatch($batch, $statistics);
}
return $statistics;
}
private function readLargeFile(string $filepath): callable
{
return function() use ($filepath): Generator {
$handle = fopen($filepath, 'r');
if (!$handle) {
throw new RuntimeException("Cannot open file: {$filepath}");
}
try {
while (($line = fgets($handle)) !== false) {
yield $line;
}
} finally {
fclose($handle);
}
};
}
private function parseLogLine(): callable
{
return function(Generator $lines): Generator {
foreach ($lines as $line) {
if (preg_match('/^\[(.*?)\] (\w+): (.*)$/', $line, $matches)) {
yield [
'timestamp' => strtotime($matches[1]),
'level' => $matches[2],
'message' => $matches[3]
];
}
}
};
}
private function enrichLogData(array $log): array
{
$log['hour'] = date('H', $log['timestamp']);
$log['error_type'] = $this->classifyError($log['message']);
return $log;
}
private function classifyError(string $message): string
{
if (str_contains($message, 'database')) return 'DATABASE';
if (str_contains($message, 'network')) return 'NETWORK';
if (str_contains($message, 'timeout')) return 'TIMEOUT';
return 'GENERAL';
}
private function processBatch(array $batch, array &$statistics): void
{
$statistics['total_errors'] += count($batch);
foreach ($batch as $log) {
$type = $log['error_type'];
$statistics['error_types'][$type] =
($statistics['error_types'][$type] ?? 0) + 1;
}
}
}
Benefícios Mensuráveis
- Memória: Consumo constante, independente do volume de dados
- Performance: Processamento lazy evita operações desnecessárias
- Manutenibilidade: Pipeline declarativo e fácil de testar
- Composição: Transformações reutilizáveis em diferentes contextos
2. 🔐 Sealed Classes e Type Narrowing
Garantindo Exhaustividade com Match
Com PHP 8.0+, podemos usar a expressão match combinada com enums e análise estática para garantir que todos os casos possíveis sejam tratados em tempo de compilação.
Caso real: Uma fintech eliminou 12 bugs de produção relacionados a estados não tratados de transações ao implementar sealed classes com verificação exhaustiva.
Implementação
<?php
// PHP 8.1+ com Enums
enum PaymentStatus: string
{
case PENDING = 'pending';
case PROCESSING = 'processing';
case COMPLETED = 'completed';
case FAILED = 'failed';
case REFUNDED = 'refunded';
case CANCELLED = 'cancelled';
}
enum PaymentMethod: string
{
case CREDIT_CARD = 'credit_card';
case DEBIT_CARD = 'debit_card';
case PIX = 'pix';
case BOLETO = 'boleto';
case WALLET = 'wallet';
}
abstract class PaymentResult
{
private function __construct() {}
}
final class PaymentSuccess extends PaymentResult
{
public function __construct(
public readonly string $transactionId,
public readonly float $amount,
public readonly \DateTimeImmutable $processedAt
) {
parent::__construct();
}
}
final class PaymentFailure extends PaymentResult
{
public function __construct(
public readonly string $errorCode,
public readonly string $errorMessage,
public readonly bool $isRetryable
) {
parent::__construct();
}
}
final class PaymentPending extends PaymentResult
{
public function __construct(
public readonly string $referenceId,
public readonly \DateTimeImmutable $expiresAt
) {
parent::__construct();
}
}
class PaymentProcessor
{
public function processPayment(
float $amount,
PaymentMethod $method,
array $data
): PaymentResult {
// Garantia de exhaustividade via match
return match ($method) {
PaymentMethod::CREDIT_CARD => $this->processCreditCard($amount, $data),
PaymentMethod::DEBIT_CARD => $this->processDebitCard($amount, $data),
PaymentMethod::PIX => $this->processPix($amount, $data),
PaymentMethod::BOLETO => $this->processBoleto($amount, $data),
PaymentMethod::WALLET => $this->processWallet($amount, $data),
// Se adicionarmos um novo método e não tratarmos aqui,
// PHP 8.0+ lança um UnhandledMatchError
};
}
public function getStatusMessage(PaymentStatus $status): string
{
return match ($status) {
PaymentStatus::PENDING => 'Aguardando processamento',
PaymentStatus::PROCESSING => 'Processando pagamento...',
PaymentStatus::COMPLETED => 'Pagamento confirmado',
PaymentStatus::FAILED => 'Pagamento falhou',
PaymentStatus::REFUNDED => 'Pagamento estornado',
PaymentStatus::CANCELLED => 'Pagamento cancelado',
};
}
public function handlePaymentResult(PaymentResult $result): array
{
// Type narrowing com match
return match (true) {
$result instanceof PaymentSuccess => [
'status' => 'success',
'transaction_id' => $result->transactionId,
'amount' => $result->amount,
'processed_at' => $result->processedAt->format('c')
],
$result instanceof PaymentFailure => [
'status' => 'failure',
'error' => $result->errorMessage,
'code' => $result->errorCode,
'can_retry' => $result->isRetryable
],
$result instanceof PaymentPending => [
'status' => 'pending',
'reference' => $result->referenceId,
'expires_at' => $result->expiresAt->format('c')
],
};
}
private function processCreditCard(float $amount, array $data): PaymentResult
{
// Implementação real
return new PaymentSuccess(
uniqid('txn_'),
$amount,
new \DateTimeImmutable()
);
}
// Outros métodos de processamento...
private function processDebitCard(float $amount, array $data): PaymentResult { /* ... */ }
private function processPix(float $amount, array $data): PaymentResult { /* ... */ }
private function processBoleto(float $amount, array $data): PaymentResult { /* ... */ }
private function processWallet(float $amount, array $data): PaymentResult { /* ... */ }
}
Vantagens
- Segurança de Tipo: Impossível esquecer de tratar um caso
- Refatoração Segura: Adicionar novos casos gera erros em tempo de compilação
- Documentação Viva: O código expressa claramente todos os estados possíveis
- Zero Runtime Overhead: Verificações acontecem em análise estática
3. ⚡ WeakMap para Cache Inteligente
Gerenciamento Automático de Memória
WeakMaps (PHP 8.0+) permitem criar caches que se limpam automaticamente quando objetos não são mais referenciados, eliminando vazamentos de memória em sistemas de longa duração.
Caso real: Um sistema de processamento contínuo que rodava 24/7 tinha memory leaks que forçavam reinicializações diárias. Com WeakMaps, o consumo de memória estabilizou e as reinicializações foram eliminadas.
Implementação
<?php
class SmartCache
{
private \WeakMap $cache;
private array $metadata;
public function __construct()
{
$this->cache = new \WeakMap();
$this->metadata = [];
}
public function remember(object $key, callable $factory): mixed
{
if (isset($this->cache[$key])) {
$this->metadata[spl_object_id($key)]['hits']++;
return $this->cache[$key];
}
$value = $factory();
$this->cache[$key] = $value;
$this->metadata[spl_object_id($key)] = [
'cached_at' => time(),
'hits' => 0
];
return $value;
}
public function has(object $key): bool
{
return isset($this->cache[$key]);
}
public function forget(object $key): void
{
unset($this->cache[$key]);
unset($this->metadata[spl_object_id($key)]);
}
public function getStats(): array
{
return [
'entries' => count($this->cache),
'metadata' => $this->metadata
];
}
}
class User
{
public function __construct(
public readonly int $id,
public readonly string $email
) {}
}
class UserRepository
{
private SmartCache $cache;
public function __construct(
private \PDO $db,
?SmartCache $cache = null
) {
$this->cache = $cache ?? new SmartCache();
}
public function findById(int $id): ?User
{
$user = new User($id, ''); // Objeto temporário para chave
return $this->cache->remember($user, function() use ($id) {
$stmt = $this->db->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$data = $stmt->fetch(\PDO::FETCH_ASSOC);
return $data ? new User($data['id'], $data['email']) : null;
});
}
public function getCacheStats(): array
{
return $this->cache->getStats();
}
}
Memoization com WeakMap
<?php
class Memoizer
{
private \WeakMap $results;
public function __construct()
{
$this->results = new \WeakMap();
}
public function memoize(object $context, string $method, array $args = []): mixed
{
if (!isset($this->results[$context])) {
$this->results[$context] = [];
}
$key = $method . ':' . serialize($args);
if (!isset($this->results[$context][$key])) {
$this->results[$context][$key] = $context->$method(...$args);
}
return $this->results[$context][$key];
}
}
class ExpensiveCalculator
{
private Memoizer $memoizer;
public function __construct()
{
$this->memoizer = new Memoizer();
}
public function fibonacci(int $n): int
{
return $this->memoizer->memoize($this, 'calculateFibonacci', [$n]);
}
private function calculateFibonacci(int $n): int
{
if ($n <= 1) return $n;
return $this->fibonacci($n - 1) + $this->fibonacci($n - 2);
}
public function complexCalculation(array $data): float
{
return $this->memoizer->memoize($this, 'performCalculation', [$data]);
}
private function performCalculation(array $data): float
{
// Cálculo complexo e custoso
return array_sum($data) * log(count($data) + 1);
}
}
4. 🎭 Named Arguments com Variadic Functions
Flexibilidade com Type Safety
Named arguments (PHP 8.0+) combinados com variadic functions criam APIs extremamente flexíveis sem sacrificar a segurança de tipos.
Implementação de Query Builder Fluente
<?php
class QueryBuilder
{
private string $table;
private array $selects = ['*'];
private array $wheres = [];
private array $joins = [];
private array $orderBy = [];
private ?int $limit = null;
private ?int $offset = null;
public function __construct(private \PDO $connection) {}
public function table(string $table): self
{
$this->table = $table;
return $this;
}
public function select(string ...$columns): self
{
$this->selects = $columns;
return $this;
}
public function where(
string $column,
mixed $operator = null,
mixed $value = null,
string $boolean = 'AND'
): self {
// Se apenas 2 argumentos, assume '=' como operador
if ($value === null) {
$value = $operator;
$operator = '=';
}
$this->wheres[] = compact('column', 'operator', 'value', 'boolean');
return $this;
}
public function whereIn(string $column, array $values, string $boolean = 'AND'): self
{
$this->wheres[] = [
'column' => $column,
'operator' => 'IN',
'value' => $values,
'boolean' => $boolean
];
return $this;
}
public function whereBetween(
string $column,
mixed $min,
mixed $max,
string $boolean = 'AND'
): self {
$this->wheres[] = [
'column' => $column,
'operator' => 'BETWEEN',
'value' => [$min, $max],
'boolean' => $boolean
];
return $this;
}
public function join(
string $table,
string $first,
string $operator,
string $second,
string $type = 'INNER'
): self {
$this->joins[] = compact('table', 'first', 'operator', 'second', 'type');
return $this;
}
public function orderBy(string $column, string $direction = 'ASC'): self
{
$this->orderBy[] = [$column, strtoupper($direction)];
return $this;
}
public function limit(int $limit): self
{
$this->limit = $limit;
return $this;
}
public function offset(int $offset): self
{
$this->offset = $offset;
return $this;
}
public function get(): array
{
$sql = $this->toSql();
$stmt = $this->connection->prepare($sql);
$stmt->execute($this->getBindings());
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
public function first(): ?array
{
$this->limit(1);
$results = $this->get();
return $results[0] ?? null;
}
private function toSql(): string
{
$sql = 'SELECT ' . implode(', ', $this->selects);
$sql .= ' FROM ' . $this->table;
foreach ($this->joins as $join) {
$sql .= " {$join['type']} JOIN {$join['table']}";
$sql .= " ON {$join['first']} {$join['operator']} {$join['second']}";
}
if (!empty($this->wheres)) {
$sql .= ' WHERE ';
$conditions = [];
foreach ($this->wheres as $i => $where) {
if ($i > 0) {
$conditions[] = $where['boolean'];
}
if ($where['operator'] === 'IN') {
$placeholders = implode(',', array_fill(0, count($where['value']), '?'));
$conditions[] = "{$where['column']} IN ({$placeholders})";
} elseif ($where['operator'] === 'BETWEEN') {
$conditions[] = "{$where['column']} BETWEEN ? AND ?";
} else {
$conditions[] = "{$where['column']} {$where['operator']} ?";
}
}
$sql .= implode(' ', $conditions);
}
if (!empty($this->orderBy)) {
$sql .= ' ORDER BY ';
$orders = array_map(
fn($order) => "{$order[0]} {$order[1]}",
$this->orderBy
);
$sql .= implode(', ', $orders);
}
if ($this->limit !== null) {
$sql .= ' LIMIT ' . $this->limit;
}
if ($this->offset !== null) {
$sql .= ' OFFSET ' . $this->offset;
}
return $sql;
}
private function getBindings(): array
{
$bindings = [];
foreach ($this->wheres as $where) {
if (is_array($where['value'])) {
$bindings = array_merge($bindings, $where['value']);
} else {
$bindings[] = $where['value'];
}
}
return $bindings;
}
}
// Uso com named arguments
$pdo = new \PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$builder = new QueryBuilder($pdo);
$users = $builder
->table('users')
->select('id', 'name', 'email')
->where(column: 'status', value: 'active')
->whereBetween(column: 'created_at', min: '2025-01-01', max: '2025-12-31')
->whereIn(column: 'role', values: ['admin', 'moderator'])
->orderBy(column: 'name', direction: 'ASC')
->limit(limit: 10)
->get();
5. 🔄 Event Sourcing com Snapshots
Arquitetura Baseada em Eventos
Event Sourcing registra todas as mudanças de estado como uma sequência de eventos, permitindo reconstruir o estado em qualquer ponto no tempo.
Caso real: Uma plataforma de trading implementou Event Sourcing e conseguiu reduzir disputas de clientes em 87%, pois todo histórico de operações era auditável e reconstrutível.
Implementação
<?php
abstract class DomainEvent
{
public function __construct(
public readonly string $aggregateId,
public readonly \DateTimeImmutable $occurredAt,
public readonly int $version
) {}
abstract public function toArray(): array;
}
class AccountCreated extends DomainEvent
{
public function __construct(
string $aggregateId,
public readonly string $ownerName,
public readonly string $currency,
\DateTimeImmutable $occurredAt,
int $version
) {
parent::__construct($aggregateId, $occurredAt, $version);
}
public function toArray(): array
{
return [
'owner_name' => $this->ownerName,
'currency' => $this->currency
];
}
}
class MoneyDeposited extends DomainEvent
{
public function __construct(
string $aggregateId,
public readonly float $amount,
public readonly string $description,
\DateTimeImmutable $occurredAt,
int $version
) {
parent::__construct($aggregateId, $occurredAt, $version);
}
public function toArray(): array
{
return [
'amount' => $this->amount,
'description' => $this->description
];
}
}
class MoneyWithdrawn extends DomainEvent
{
public function __construct(
string $aggregateId,
public readonly float $amount,
public readonly string $description,
\DateTimeImmutable $occurredAt,
int $version
) {
parent::__construct($aggregateId, $occurredAt, $version);
}
public function toArray(): array
{
return [
'amount' => $this->amount,
'description' => $this->description
];
}
}
class BankAccount
{
private string $id;
private string $ownerName;
private string $currency;
private float $balance = 0;
private int $version = 0;
private array $uncommittedEvents = [];
public static function create(string $id, string $ownerName, string $currency): self
{
$account = new self();
$account->recordThat(new AccountCreated(
$id,
$ownerName,
$currency,
new \DateTimeImmutable(),
1
));
return $account;
}
public function deposit(float $amount, string $description): void
{
if ($amount <= 0) {
throw new \InvalidArgumentException('Amount must be positive');
}
$this->recordThat(new MoneyDeposited(
$this->id,
$amount,
$description,
new \DateTimeImmutable(),
$this->version + 1
));
}
public function withdraw(float $amount, string $description): void
{
if ($amount <= 0) {
throw new \InvalidArgumentException('Amount must be positive');
}
if ($amount > $this->balance) {
throw new \RuntimeException('Insufficient funds');
}
$this->recordThat(new MoneyWithdrawn(
$this->id,
$amount,
$description,
new \DateTimeImmutable(),
$this->version + 1
));
}
public static function reconstituteFrom(array $events): self
{
$account = new self();
foreach ($events as $event) {
$account->apply($event);
}
return $account;
}
private function recordThat(DomainEvent $event): void
{
$this->apply($event);
$this->uncommittedEvents[] = $event;
}
private function apply(DomainEvent $event): void
{
match (true) {
$event instanceof AccountCreated => $this->applyAccountCreated($event),
$event instanceof MoneyDeposited => $this->applyMoneyDeposited($event),
$event instanceof MoneyWithdrawn => $this->applyMoneyWithdrawn($event),
};
$this->version = $event->version;
}
private function applyAccountCreated(AccountCreated $event): void
{
$this->id = $event->aggregateId;
$this->ownerName = $event->ownerName;
$this->currency = $event->currency;
$this->balance = 0;
}
private function applyMoneyDeposited(MoneyDeposited $event): void
{
$this->balance += $event->amount;
}
private function applyMoneyWithdrawn(MoneyWithdrawn $event): void
{
$this->balance -= $event->amount;
}
public function getUncommittedEvents(): array
{
return $this->uncommittedEvents;
}
public function clearUncommittedEvents(): void
{
$this->uncommittedEvents = [];
}
public function getId(): string
{
return $this->id;
}
public function getBalance(): float
{
return $this->balance;
}
public function getVersion(): int
{
return $this->version;
}
}
class EventStore
{
public function __construct(private \PDO $connection) {}
public function append(string $aggregateId, array $events): void
{
$this->connection->beginTransaction();
try {
foreach ($events as $event) {
$stmt = $this->connection->prepare(
'INSERT INTO events (aggregate_id, event_type, event_data, occurred_at, version)
VALUES (?, ?, ?, ?, ?)'
);
$stmt->execute([
$event->aggregateId,
get_class($event),
json_encode($event->toArray()),
$event->occurredAt->format('Y-m-d H:i:s'),
$event->version
]);
}
$this->connection->commit();
} catch (\Exception $e) {
$this->connection->rollBack();
throw $e;
}
}
public function getEventsFor(string $aggregateId): array
{
$stmt = $this->connection->prepare(
'SELECT * FROM events WHERE aggregate_id = ? ORDER BY version ASC'
);
$stmt->execute([$aggregateId]);
$events = [];
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$events[] = $this->deserializeEvent($row);
}
return $events;
}
private function deserializeEvent(array $data): DomainEvent
{
$eventClass = $data['event_type'];
$eventData = json_decode($data['event_data'], true);
// Reconstrói o evento baseado no tipo
return match ($eventClass) {
AccountCreated::class => new AccountCreated(
$data['aggregate_id'],
$eventData['owner_name'],
$eventData['currency'],
new \DateTimeImmutable($data['occurred_at']),
$data['version']
),
MoneyDeposited::class => new MoneyDeposited(
$data['aggregate_id'],
$eventData['amount'],
$eventData['description'],
new \DateTimeImmutable($data['occurred_at']),
$data['version']
),
MoneyWithdrawn::class => new MoneyWithdrawn(
$data['aggregate_id'],
$eventData['amount'],
$eventData['description'],
new \DateTimeImmutable($data['occurred_at']),
$data['version']
),
};
}
}
// Uso
$pdo = new \PDO('mysql:host=localhost;dbname=eventstore', 'user', 'pass');
$eventStore = new EventStore($pdo);
// Criar conta e realizar operações
$account = BankAccount::create('acc-001', 'João Silva', 'BRL');
$account->deposit(1000.00, 'Depósito inicial');
$account->withdraw(250.00, 'Pagamento conta de luz');
$account->deposit(500.00, 'Salário');
// Salvar eventos
$eventStore->append($account->getId(), $account->getUncommittedEvents());
$account->clearUncommittedEvents();
// Reconstruir estado a partir dos eventos
$events = $eventStore->getEventsFor('acc-001');
$reconstructedAccount = BankAccount::reconstituteFrom($events);
echo "Saldo: " . $reconstructedAccount->getBalance(); // 1250.00
6. 🧬 Property Hooks (PHP 8.4+)
Simplificando Getters e Setters
Property hooks permitem adicionar lógica diretamente em propriedades, eliminando a necessidade de métodos getter/setter verbosos.
Implementação
<?php
class Money
{
public function __construct(
public float $amount {
set {
if ($value < 0) {
throw new \InvalidArgumentException('Amount cannot be negative');
}
$this->amount = round($value, 2);
}
},
public string $currency {
set => strtoupper($value)
}
) {}
public float $formatted {
get => number_format($this->amount, 2, ',', '.');
}
}
class User
{
private array $_permissions = [];
public function __construct(
public string $email {
set {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('Invalid email');
}
$this->email = strtolower($value);
}
},
public string $password {
set => password_hash($value, PASSWORD_ARGON2ID)
},
public \DateTimeImmutable $createdAt {
get => $this->createdAt ?? new \DateTimeImmutable()
}
) {}
public array $permissions {
get => $this->_permissions,
set {
$this->_permissions = array_unique(array_map('strtoupper', $value));
}
}
public bool $isAdmin {
get => in_array('ADMIN', $this->permissions)
}
}
class Product
{
private float $_price;
private float $_discount = 0;
public function __construct(
public string $name,
float $price
) {
$this->price = $price;
}
public float $price {
get => $this->_price,
set {
if ($value <= 0) {
throw new \InvalidArgumentException('Price must be positive');
}
$this->_price = $value;
}
}
public float $discount {
get => $this->_discount,
set {
if ($value < 0 || $value > 100) {
throw new \InvalidArgumentException('Discount must be between 0 and 100');
}
$this->_discount = $value;
}
}
public float $finalPrice {
get => $this->price * (1 - $this->discount / 100)
}
public float $savings {
get => $this->price - $this->finalPrice
}
}
// Uso
$product = new Product('Notebook', 5000.00);
$product->discount = 15;
echo $product->finalPrice; // 4250.00
echo $product->savings; // 750.00
7. 🎨 Pipeline Pattern com Middleware
Processamento Encadeado Elegante
O Pipeline Pattern permite processar dados através de uma série de etapas independentes, criando fluxos de processamento modulares e testáveis.
Implementação Robusta
<?php
interface PipelineStage
{
public function process(mixed $payload, \Closure $next): mixed;
}
class Pipeline
{
private array $stages = [];
public function pipe(PipelineStage|callable $stage): self
{
$this->stages[] = $stage;
return $this;
}
public function send(mixed $payload): mixed
{
$pipeline = array_reduce(
array_reverse($this->stages),
fn($next, $stage) => fn($payload) => $this->executeStage($stage, $payload, $next),
fn($payload) => $payload
);
return $pipeline($payload);
}
private function executeStage(PipelineStage|callable $stage, mixed $payload, \Closure $next): mixed
{
if ($stage instanceof PipelineStage) {
return $stage->process($payload, $next);
}
return $stage($payload, $next);
}
}
// Stages de processamento de pedido
class ValidateOrder implements PipelineStage
{
public function process(mixed $payload, \Closure $next): mixed
{
if (empty($payload['items'])) {
throw new \RuntimeException('Order must have items');
}
if (!isset($payload['customer_id'])) {
throw new \RuntimeException('Customer ID is required');
}
return $next($payload);
}
}
class CalculateTotals implements PipelineStage
{
public function process(mixed $payload, \Closure $next): mixed
{
$subtotal = array_reduce(
$payload['items'],
fn($sum, $item) => $sum + ($item['price'] * $item['quantity']),
0
);
$tax = $subtotal * 0.08; // 8% de imposto
$shipping = $subtotal > 100 ? 0 : 15;
$payload['subtotal'] = $subtotal;
$payload['tax'] = $tax;
$payload['shipping'] = $shipping;
$payload['total'] = $subtotal + $tax + $shipping;
return $next($payload);
}
}
class ApplyDiscounts implements PipelineStage
{
public function __construct(private array $discountRules) {}
public function process(mixed $payload, \Closure $next): mixed
{
$discount = 0;
foreach ($this->discountRules as $rule) {
if ($rule->applies($payload)) {
$discount += $rule->calculate($payload);
}
}
$payload['discount'] = $discount;
$payload['total'] -= $discount;
return $next($payload);
}
}
class CheckInventory implements PipelineStage
{
public function __construct(private InventoryService $inventory) {}
public function process(mixed $payload, \Closure $next): mixed
{
foreach ($payload['items'] as $item) {
if (!$this->inventory->isAvailable($item['product_id'], $item['quantity'])) {
throw new \RuntimeException(
"Product {$item['product_id']} is not available in requested quantity"
);
}
}
return $next($payload);
}
}
class ReserveInventory implements PipelineStage
{
public function __construct(private InventoryService $inventory) {}
public function process(mixed $payload, \Closure $next): mixed
{
$reservations = [];
foreach ($payload['items'] as $item) {
$reservations[] = $this->inventory->reserve(
$item['product_id'],
$item['quantity']
);
}
$payload['reservations'] = $reservations;
return $next($payload);
}
}
class SaveOrder implements PipelineStage
{
public function __construct(private \PDO $db) {}
public function process(mixed $payload, \Closure $next): mixed
{
$this->db->beginTransaction();
try {
$stmt = $this->db->prepare(
'INSERT INTO orders (customer_id, subtotal, tax, shipping, discount, total, status)
VALUES (?, ?, ?, ?, ?, ?, ?)'
);
$stmt->execute([
$payload['customer_id'],
$payload['subtotal'],
$payload['tax'],
$payload['shipping'],
$payload['discount'],
$payload['total'],
'pending'
]);
$orderId = $this->db->lastInsertId();
$payload['order_id'] = $orderId;
foreach ($payload['items'] as $item) {
$stmt = $this->db->prepare(
'INSERT INTO order_items (order_id, product_id, quantity, price)
VALUES (?, ?, ?, ?)'
);
$stmt->execute([
$orderId,
$item['product_id'],
$item['quantity'],
$item['price']
]);
}
$this->db->commit();
return $next($payload);
} catch (\Exception $e) {
$this->db->rollBack();
throw $e;
}
}
}
class NotifyCustomer implements PipelineStage
{
public function __construct(private EmailService $email) {}
public function process(mixed $payload, \Closure $next): mixed
{
$this->email->send(
$payload['customer_email'],
'Pedido Confirmado',
"Seu pedido #{$payload['order_id']} foi confirmado!"
);
return $next($payload);
}
}
// Uso
class OrderProcessor
{
public function __construct(
private \PDO $db,
private InventoryService $inventory,
private EmailService $email
) {}
public function processOrder(array $orderData): array
{
$pipeline = new Pipeline();
$result = $pipeline
->pipe(new ValidateOrder())
->pipe(new CalculateTotals())
->pipe(new ApplyDiscounts([]))
->pipe(new CheckInventory($this->inventory))
->pipe(new ReserveInventory($this->inventory))
->pipe(new SaveOrder($this->db))
->pipe(new NotifyCustomer($this->email))
->send($orderData);
return $result;
}
}
8. 🔍 Advanced Error Handling com Result Objects
Substituindo Exceptions por Result Types
Result objects oferecem uma alternativa às exceptions para tratamento de erros, tornando o fluxo de erro explícito e type-safe.
Implementação
<?php
abstract class Result
{
abstract public function isSuccess(): bool;
abstract public function isFailure(): bool;
}
final class Success extends Result
{
public function __construct(private readonly mixed $value) {}
public function getValue(): mixed
{
return $this->value;
}
public function isSuccess(): bool
{
return true;
}
public function isFailure(): bool
{
return false;
}
public function map(callable $fn): self
{
return new self($fn($this->value));
}
public function flatMap(callable $fn): Result
{
return $fn($this->value);
}
public function getOrElse(mixed $default): mixed
{
return $this->value;
}
public function orElse(callable $fn): self
{
return $this;
}
}
final class Failure extends Result
{
public function __construct(
private readonly string $message,
private readonly ?string $code = null,
private readonly ?array $context = null
) {}
public function getMessage(): string
{
return $this->message;
}
public function getCode(): ?string
{
return $this->code;
}
public function getContext(): ?array
{
return $this->context;
}
public function isSuccess(): bool
{
return false;
}
public function isFailure(): bool
{
return true;
}
public function map(callable $fn): self
{
return $this;
}
public function flatMap(callable $fn): self
{
return $this;
}
public function getOrElse(mixed $default): mixed
{
return $default;
}
public function orElse(callable $fn): Result
{
return $fn($this);
}
}
class UserService
{
public function __construct(
private \PDO $db,
private EmailValidator $emailValidator
) {}
public function createUser(string $email, string $password): Result
{
return $this->validateEmail($email)
->flatMap(fn($email) => $this->validatePassword($password))
->flatMap(fn() => $this->checkEmailAvailability($email))
->flatMap(fn() => $this->saveUser($email, $password));
}
private function validateEmail(string $email): Result
{
if (!$this->emailValidator->isValid($email)) {
return new Failure(
'Invalid email format',
'INVALID_EMAIL',
['email' => $email]
);
}
return new Success($email);
}
private function validatePassword(string $password): Result
{
if (strlen($password) < 8) {
return new Failure(
'Password must be at least 8 characters',
'WEAK_PASSWORD'
);
}
if (!preg_match('/[A-Z]/', $password)) {
return new Failure(
'Password must contain uppercase letter',
'WEAK_PASSWORD'
);
}
if (!preg_match('/[0-9]/', $password)) {
return new Failure(
'Password must contain number',
'WEAK_PASSWORD'
);
}
return new Success($password);
}
private function checkEmailAvailability(string $email): Result
{
$stmt = $this->db->prepare('SELECT COUNT(*) FROM users WHERE email = ?');
$stmt->execute([$email]);
if ($stmt->fetchColumn() > 0) {
return new Failure(
'Email already registered',
'EMAIL_EXISTS',
['email' => $email]
);
}
return new Success(true);
}
private function saveUser(string $email, string $password): Result
{
try {
$stmt = $this->db->prepare(
'INSERT INTO users (email, password, created_at) VALUES (?, ?, ?)'
);
$stmt->execute([
$email,
password_hash($password, PASSWORD_ARGON2ID),
date('Y-m-d H:i:s')
]);
return new Success([
'user_id' => $this->db->lastInsertId(),
'email' => $email
]);
} catch (\PDOException $e) {
return new Failure(
'Failed to create user',
'DATABASE_ERROR',
['error' => $e->getMessage()]
);
}
}
}
// Uso
$userService = new UserService($pdo, new EmailValidator());
$result = $userService->createUser('joao@example.com', 'SecurePass123');
if ($result->isSuccess()) {
$userData = $result->getValue();
echo "User created successfully! ID: {$userData['user_id']}";
} else {
$error = $result;
echo "Error: {$error->getMessage()} (Code: {$error->getCode()})";
if ($context = $error->getContext()) {
print_r($context);
}
}
// Encadeamento com tratamento
$result = $userService->createUser('invalid-email', 'weak')
->map(fn($user) => [...$user, 'status' => 'active'])
->getOrElse(['status' => 'failed']);
🏆 Conclusão
As técnicas apresentadas neste artigo representam o estado da arte em desenvolvimento PHP moderno. Cada uma delas resolve problemas reais de forma elegante e eficiente:
- Generator Pipelines para processamento eficiente de grandes volumes de dados
- Sealed Classes para garantir exhaustividade e type safety
- WeakMaps para gerenciamento automático de memória
- Named Arguments para APIs flexíveis e expressivas
- Event Sourcing para auditoria completa e temporal queries
- Property Hooks para encapsulamento limpo sem boilerplate
- Pipeline Pattern para fluxos de processamento modulares
- Result Objects para tratamento de erros explícito e type-safe
📚 Referências e Recursos
Documentação Oficial
Leitura Adicional
- "Domain-Driven Design" - Eric Evans
- "Patterns of Enterprise Application Architecture" - Martin Fowler
- "PHP: The Right Way" - Modern PHP best practices
Ferramentas Recomendadas
- PHPStan (Level 9): Análise estática avançada
- Psalm: Type checking rigoroso
- Rector: Refatoração automatizada
- PHP-CS-Fixer: Code style consistency
👨💻 Sobre o Autor
Cristian Bernardes
Senior PHP Developer com quase 20 anos de experiência em arquiteturas escaláveis, otimização de performance e desenvolvimento de sistemas críticos. Especialista em Laravel, Docker, microserviços e padrões arquiteturais avançados.
Conecte-se:
💡 Gostou do conteúdo? Compartilhe com sua rede e ajude outros desenvolvedores a elevar suas skills em PHP!
Última atualização: Dezembro de 2025
Top comments (0)