Readonly properties e classes readonly
1. Introdução às Readonly Properties
A mutabilidade acidental em objetos é uma das fontes mais comuns de bugs em sistemas orientados a objetos. Quando uma propriedade pode ser alterada em qualquer ponto do ciclo de vida do objeto, o rastreamento de estado se torna complexo e propenso a erros. O PHP 8.1 introduziu uma solução elegante para esse problema: as readonly properties.
Readonly properties são propriedades que só podem receber valor uma única vez, geralmente no construtor. Após a inicialização, qualquer tentativa de modificar seu valor resulta em um erro fatal. Essa característica torna o código mais previsível e seguro, especialmente em contextos onde a imutabilidade é desejada, como em Data Transfer Objects (DTOs) e Value Objects.
2. Sintaxe e Comportamento Básico
A declaração de uma propriedade readonly utiliza a palavra-chave readonly antes do tipo:
class Usuario {
public readonly string $nome;
public readonly int $idade;
public function __construct(string $nome, int $idade) {
$this->nome = $nome;
$this->idade = $idade;
}
}
$usuario = new Usuario('João', 30);
echo $usuario->nome; // João
// $usuario->nome = 'Maria'; // Erro: Cannot modify readonly property
Regras fundamentais:
- A atribuição só pode ocorrer uma vez, no construtor ou na declaração inline
- Não é possível definir valores padrão que dependam de expressões dinâmicas
- Propriedades readonly devem ser tipadas (exceto em classes readonly do PHP 8.2)
class Config {
// Válido: valor constante
public readonly string $versao = '1.0';
// Inválido: expressão dinâmica
// public readonly string $timestamp = time();
public function __construct() {
// Atribuição no construtor também é válida
// $this->timestamp = time();
}
}
3. Readonly Properties com Tipos e Promoção de Construtor
O PHP 8.1 permite combinar readonly com constructor property promotion, criando uma sintaxe concisa para DTOs imutáveis:
class ProdutoDTO {
public function __construct(
public readonly int $id,
public readonly string $nome,
public readonly float $preco,
public readonly ?string $descricao = null
) {}
}
$produto = new ProdutoDTO(1, 'Notebook', 2500.00, 'Notebook de última geração');
echo $produto->nome; // Notebook
Essa abordagem elimina a necessidade de métodos getters e setters, tornando o código mais limpo e expressivo.
4. Limitações e Comportamentos Específicos
Proibição de unset e reassignação:
$produto = new ProdutoDTO(1, 'Mouse', 150.00);
unset($produto->nome); // Erro: Cannot unset readonly property
Comportamento com objetos mutáveis:
Readonly não impede a mutação interna de objetos armazenados:
class Pedido {
public function __construct(
public readonly array $itens
) {}
}
$pedido = new Pedido(['item1', 'item2']);
$pedido->itens[] = 'item3'; // Funciona! A referência do array não mudou
echo count($pedido->itens); // 3
Interação com herança:
Propriedades readonly podem ser herdadas, mas não podem ser sobrescritas com diferentes níveis de visibilidade ou tipos incompatíveis:
class Base {
public readonly string $nome;
}
class Derivada extends Base {
// Não é possível redefinir $nome como writable
// public string $nome; // Erro
}
5. Classes Readonly (PHP 8.2)
O PHP 8.2 expandiu o conceito com classes readonly. Ao declarar uma classe como readonly, todas as suas propriedades tornam-se automaticamente readonly:
readonly class Configuracao {
public string $host;
public int $porta;
public string $usuario;
public function __construct(string $host, int $porta, string $usuario) {
$this->host = $host;
$this->porta = $porta;
$this->usuario = $usuario;
}
}
$config = new Configuracao('localhost', 3306, 'admin');
// $config->host = 'novo-host'; // Erro: propriedade readonly
Restrições importantes:
- Propriedades não podem ter tipos opcionais (nullable) sem valor padrão
- Propriedades dinâmicas são proibidas
- Não é possível declarar propriedades sem tipo (untyped)
readonly class Exemplo {
// public $untyped; // Erro: propriedade untyped não permitida
public string $nome;
public ?string $apelido = null; // Permitido com valor padrão
}
6. Readonly Classes e Herança
Classes readonly podem estender outras classes readonly, mas não classes comuns:
readonly class Base {
public string $nome;
}
readonly class Derivada extends Base {
public string $sobrenome;
}
// class Comum extends Base {} // Erro: não pode estender classe readonly
Sobrescrita de propriedades:
Propriedades readonly podem ser sobrescritas em subclasses, desde que mantenham o mesmo tipo e visibilidade:
readonly class Animal {
public string $especie;
}
readonly class Cachorro extends Animal {
public string $raca;
public function __construct(string $especie, string $raca) {
parent::__construct($especie);
$this->raca = $raca;
}
}
7. Casos de Uso e Boas Práticas
Value Objects imutáveis:
readonly class Endereco {
public function __construct(
public string $rua,
public string $cidade,
public string $cep
) {}
}
$endereco = new Endereco('Rua A', 'São Paulo', '01001-000');
// $endereco->cidade = 'Rio'; // Garantia de imutabilidade
Configurações que não devem mudar:
readonly class AppConfig {
public function __construct(
public string $dbHost = 'localhost',
public int $dbPort = 3306,
public bool $debug = false
) {}
}
Performance e segurança em aplicações concorrentes:
Em ambientes com múltiplas threads ou processos (como aplicações ReactPHP ou Swoole), objetos readonly eliminam race conditions relacionadas à mutação de estado.
8. Comparação com Alternativas e Evolução
Diferenças entre readonly, final e constantes de classe:
| Característica | readonly | final | const |
|---|---|---|---|
| Escopo | Propriedade | Classe/método | Classe |
| Atribuição única | Sim | N/A | Sim |
| Valor dinâmico | Sim (construtor) | N/A | Não |
| Herança | Permite sobrescrita controlada | Impede herança | Permite sobrescrita |
Evolução do PHP:
Antes do PHP 8.1, a imutabilidade era alcançada com propriedades privadas e métodos getters:
// Antes do PHP 8.1
class UsuarioAntigo {
private string $nome;
public function __construct(string $nome) {
$this->nome = $nome;
}
public function getNome(): string {
return $this->nome;
}
}
// Com PHP 8.1+
class UsuarioNovo {
public function __construct(
public readonly string $nome
) {}
}
Expectativas futuras:
A comunidade PHP aguarda funcionalidades como deep readonly (imutabilidade profunda para objetos aninhados) e suporte a readonly em parâmetros de métodos. Essas features estão em discussão para versões futuras do PHP.
Referências
- PHP Manual: Readonly Properties — Documentação oficial sobre a sintaxe e comportamento das propriedades readonly no PHP 8.1.
- PHP Manual: Readonly Classes — Documentação oficial sobre classes readonly introduzidas no PHP 8.2.
- PHP Watch: Readonly Properties in PHP 8.1 — Guia prático com exemplos detalhados sobre o uso de readonly properties.
- Stitcher.io: Readonly Properties in PHP 8.1 — Artigo técnico explicando casos de uso e melhores práticas para propriedades readonly.
- PHP.Watch: Readonly Classes in PHP 8.2 — Análise completa das classes readonly, incluindo restrições e exemplos de herança.
- The PHP Foundation: Readonly Properties RFC — Proposta original e discussão técnica sobre a implementação das propriedades readonly.
- Laravel News: Understanding Readonly Properties — Tutorial prático focado em aplicações Laravel e DTOs imutáveis.