Médio PHP

Fábrica estática com late static binding

Enunciado

Crie uma classe Documento com propriedades titulo e tipo. Adicione um método estático criar que recebe um array e retorna uma nova instância usando static::. Em seguida, crie a classe Relatorio que estende Documento e adiciona a propriedade periodo. Use Documento::criar e Relatorio::criar para criar objetos e exiba seus dados.

Requisitos

  • Documento::criar deve usar new static(...) para permitir late static binding.
  • Relatorio deve estender Documento e ter um construtor que aceita periodo.
  • O programa deve exibir título, tipo e, para Relatorio, também o período.

Código inicial

<?php

class Documento
{
    public function __construct(
        public readonly string $titulo,
        public readonly string $tipo
    ) {}

    // TODO: implemente o método estático criar usando static::
}

class Relatorio extends Documento
{
    // TODO: adicione a propriedade $periodo e o construtor
}

// TODO: use as fábricas e exiba os dados

Saída esperada

Documento: Contrato (PDF)
Relatório: Vendas (XLSX) - Período: 2025
Ver dica

No método criar, use return new static($dados['titulo'], $dados['tipo']);. Em Relatorio, o construtor deve chamar parent::__construct e definir $periodo.

Mostrar solução
<?php

class Documento
{
    public function __construct(
        public readonly string $titulo,
        public readonly string $tipo
    ) {}

    // Fábrica estática que respeita a classe chamada
    public static function criar(array $dados): static
    {
        return new static($dados['titulo'], $dados['tipo']);
    }

    public function exibir(): string
    {
        return "Documento: {$this->titulo} ({$this->tipo})";
    }
}

class Relatorio extends Documento
{
    public function __construct(
        string $titulo,
        string $tipo,
        public readonly string $periodo
    ) {
        parent::__construct($titulo, $tipo);
    }

    public function exibir(): string
    {
        return "Relatório: {$this->titulo} ({$this->tipo}) - Período: {$this->periodo}";
    }
}

// Usando as fábricas
$doc = Documento::criar(['titulo' => 'Contrato', 'tipo' => 'PDF']);
$rel = Relatorio::criar(['titulo' => 'Vendas', 'tipo' => 'XLSX', 'periodo' => '2025']);

echo $doc->exibir() . PHP_EOL;
echo $rel->exibir() . PHP_EOL;

Passo a passo

  1. Documento tem duas propriedades readonly e um construtor que as define.
  2. O método estático criar recebe um array e retorna new static(...). O uso de static em vez de self é crucial: se chamado como Relatorio::criar, static resolve para Relatorio.
  3. Relatorio estende Documento, adiciona $periodo e chama parent::__construct para inicializar as propriedades herdadas.
  4. Relatorio sobrescreve exibir para incluir o período.
  5. Chamamos Documento::criar e Relatorio::criar e exibimos os resultados.

Por que funciona

O late static binding faz com que static:: dentro de criar se refira à classe usada na chamada. Se tivéssemos usado self::, Relatorio::criar tentaria instanciar Documento, e o período não seria definido. Com static::, cada classe filha pode reutilizar a fábrica e ainda criar sua própria instância.

Erros comuns

  • Usar self no lugar de static: return new self(...) faria Relatorio::criar retornar um Documento, perdendo a propriedade periodo.
  • Esquecer de chamar parent::__construct: as propriedades titulo e tipo ficariam indefinidas, causando erro.
  • Tentar acessar $this no método estático: métodos estáticos não têm $this. Use parâmetros e new static.

Outra forma de resolver

Em vez de uma fábrica estática, você pode usar um construtor normal e instanciar diretamente. A fábrica é preferível quando a criação envolve lógica extra (validação, mapeamento de array) ou quando você quer permitir que subclasses personalizem a criação.

Saída esperada

Documento: Contrato (PDF)
Relatório: Vendas (XLSX) - Período: 2025