Dia 16 — Programação orientada a objetos: PHP e MySQL

Informatica · Conteudo · publicado em 02/10/2026
Dia 16 de 17

Programação orientada a objetos

Até aqui o PHP foi escrito como sequência de comandos e funções soltas. Este dia dá a forma que a arquitetura em camadas exige: `class`, `new`, encapsulamento com `private`, herança e interfaces, para que o model e o controller virem objetos com contrato. É também onde entra o Composer e o `autoload` PSR-4, que é o que faz `use App\Model\Aluno` encontrar o arquivo certo sozinho.

Aula 1

Classes e objetos

Classe é o desenho, objeto é a peça. O new cria a instância, o construtor prepara os dados, e $this dá acesso ao próprio objeto de dentro do método.

Declarando a classe

final class Aluno
{
    public string $nome;
    public float $nota;

    public function __construct(string $nome, float $nota)
    {
        $this->nome = $nome;
        $this->nota = $nota;
    }

    public function aprovado(float $minima = 7.0): bool
    {
        return $this->nota >= $minima;
    }
}

$ana   = new Aluno('Ana Souza', 8.5);
$bruno = new Aluno('Bruno Lima', 6.0);

$ana->nota = 9.0 muda só o $ana. Cada objeto tem os seus valores, mesmo saindo da mesma classe.

__construct roda sozinho no new, e não existe caminho para criar o objeto sem passar por ele. Com tipo declarado no parâmetro, quem chama com o tipo errado recebe TypeError na hora.

$this e o construtor

$this é o próprio objeto. Dentro de aprovado(), $this->nota é a nota daquele aluno, sem a nota precisar ser passada como argumento.

O new aceita argumento nomeado, que evita trocar a ordem por engano:

$carla = new Aluno(nota: 9.25, nome: 'Carla Dias');

A promoção de construtor declara a propriedade e atribui em uma linha, e o valor padrão vem depois do obrigatório:

final class Curso
{
    public function __construct(
        public string $nome,
        public int $vagas = 40,
        public ?string $turno = null,
    ) {
    }
}

?string aceita null; string puro não aceita, e receber null lança TypeError.

Funções que perguntam sobre o objeto

FunçãoDevolve
get_class($objeto)o nome da classe, como string
$objeto instanceof Alunotrue se o objeto é daquela classe, seguindo a herança
spl_object_id($objeto)um inteiro que identifica o objeto nesta execução
method_exists($objeto, 'aprovado')true se o método existe

spl_object_id() devolve valores diferentes para objetos diferentes, e o mesmo valor para a mesma variável: é assim que se confirma que são dois objetos independentes.

Objetos entram em array e passam por array_filter(), array_map() e sort() como qualquer outro valor.

__toString(): string é o método que permite echo $objeto. Sem ele, o PHP dá erro ao tentar converter o objeto em string.

Exemplo

<?php
declare(strict_types=1);

// Classes e objetos: class, new, propriedade, metodo, $this e __construct.

echo "=== 1. O que e uma classe e o que e um objeto ===\n";
echo "  A classe e o desenho; o objeto e a peca feita a partir do desenho.\n";
echo "  Cada objeto tem os proprios valores de propriedade, mesmo saindo\n";
echo "  da mesma classe. E por isso que um aluno nao vira o outro ao trocar o\n";
echo "  valor da nota.\n";

// --- A classe ---
final class Aluno
{
    public string $nome;
    public float $nota;

    public function __construct(string $nome, float $nota)
    {
        $this->nome = $nome;
        $this->nota = $nota;
    }

    public function aprovado(float $minima = 7.0): bool
    {
        return $this->nota >= $minima;
    }

    public function situacao(): string
    {
        return $this->aprovado() ? 'aprovado' : 'reprovado';
    }
}

$ana   = new Aluno('Ana Souza', 8.5);
$bruno = new Aluno('Bruno Lima', 6.0);

echo "\n=== 2. Dois objetos da mesma classe, dados separados ===\n";
printf("  \$ana:   %-12s %.2f  %s\n", $ana->nome, $ana->nota, $ana->situacao());
printf("  \$bruno: %-12s %.2f  %s\n", $bruno->nome, $bruno->nota, $bruno->situacao());
echo "  \$bruno->nota = 9.0 muda so o Bruno:\n";
$bruno->nota = 9.0;
printf("  \$ana:   %.2f  \$bruno: %.2f\n", $ana->nota, $bruno->nota);

// --- $this ---
echo "\n=== 3. \$this e o proprio objeto ===\n";
echo "  Dentro do metodo, \$this aponta para o objeto que chamou.\n";
echo "  Por isso aprovado() le \$this->nota de cada aluno, sem receber nada\n";
echo "  como argumento.\n";

// --- O construtor ---
echo "\n=== 4. O construtor roda sozinho no new ===\n";
echo "  \$ana = new Aluno('Ana Souza', 8.5) chama __construct na hora.\n";
echo "  Nao existe objeto sem passar por ele: os argumentos ficam obrigatorios\n";
echo "  e o tipo declarado (\$nome string, \$nota float) e conferido na entrada.\n";
echo "\n  new aceita nomeado, o que evita trocar a ordem dos argumentos:\n";
$carla = new Aluno(nota: 9.25, nome: 'Carla Dias');
printf("  %-12s %.2f\n", $carla->nome, $carla->nota);

// --- Propriedade tipada ---
echo "\n=== 5. Propriedade com tipo e valor padrao ===\n";
$codigo = <<<'PHP'
final class Curso
{
    public function __construct(
        public string $nome,
        public int $vagas = 40,
        public ?string $turno = null,
    ) {
    }
}
PHP;
echo $codigo . "\n";
echo "  A promocao de construtor declara a propriedade e atribui em uma linha.\n";
echo "  ?string aceita null; string puro nao aceita null e lanca TypeError.\n";

// --- get_class, instanceof, spl_object_id ---
echo "\n=== 6. Perguntar o que e um objeto ===\n";
printf("  get_class(\$ana):        %s\n", get_class($ana));
printf("  \$ana instanceof Aluno:  %s\n", var_export($ana instanceof Aluno, true));
printf("  \$ana instanceof DateTime:%s\n", var_export($ana instanceof DateTime, true));
printf("  spl_object_id(\$ana):     %d\n", spl_object_id($ana));
printf("  spl_object_id(\$bruno):  %d\n", spl_object_id($bruno));
echo "  Dois ids diferentes confirmam que sao dois objetos independentes.\n";

// --- O array de objetos ---
echo "\n=== 7. Objetos dentro de array, como qualquer outro valor ===\n";
$turma = [new Aluno('Ana Souza', 8.5), new Aluno('Bruno Lima', 6.0), $carla];
foreach ($turma as $aluno) {
    printf("  %-12s %5.2f  %s\n", $aluno->nome, $aluno->nota, $aluno->situacao());
}
printf("  aprovados: %d de %d\n", count(array_filter($turma, fn (Aluno $a): bool => $a->aprovado())), count($turma));

// --- __toString ---
echo "\n=== 8. __toString permite imprimir o objeto direto ===\n";
final class Moeda
{
    public function __construct(private float $valor)
    {
    }

    public function __toString(): string
    {
        return number_format($this->valor, 2, ',', '.');
    }
}

$preco = new Moeda(1250.5);
echo "  {$preco} reais\n";
echo "  Sem __toString, echo em um objeto daria Error: Object of class Moeda\n";
echo "  could not be converted to string.\n";

Saída real

=== 1. O que e uma classe e o que e um objeto ===
  A classe e o desenho; o objeto e a peca feita a partir do desenho.
  Cada objeto tem os proprios valores de propriedade, mesmo saindo
  da mesma classe. E por isso que um aluno nao vira o outro ao trocar o
  valor da nota.

=== 2. Dois objetos da mesma classe, dados separados ===
  $ana:   Ana Souza    8.50  aprovado
  $bruno: Bruno Lima   6.00  reprovado
  $bruno->nota = 9.0 muda so o Bruno:
  $ana:   8.50  $bruno: 9.00

=== 3. $this e o proprio objeto ===
  Dentro do metodo, $this aponta para o objeto que chamou.
  Por isso aprovado() le $this->nota de cada aluno, sem receber nada
  como argumento.

=== 4. O construtor roda sozinho no new ===
  $ana = new Aluno('Ana Souza', 8.5) chama __construct na hora.
  Nao existe objeto sem passar por ele: os argumentos ficam obrigatorios
  e o tipo declarado ($nome string, $nota float) e conferido na entrada.

  new aceita nomeado, o que evita trocar a ordem dos argumentos:
  Carla Dias   9.25

=== 5. Propriedade com tipo e valor padrao ===
final class Curso
{
    public function __construct(
        public string $nome,
        public int $vagas = 40,
        public ?string $turno = null,
    ) {
    }
}
  A promocao de construtor declara a propriedade e atribui em uma linha.
  ?string aceita null; string puro nao aceita null e lanca TypeError.

=== 6. Perguntar o que e um objeto ===
  get_class($ana):        Aluno
  $ana instanceof Aluno:  true
  $ana instanceof DateTime:false
  spl_object_id($ana):     1
  spl_object_id($bruno):  2
  Dois ids diferentes confirmam que sao dois objetos independentes.

=== 7. Objetos dentro de array, como qualquer outro valor ===
  Ana Souza     8.50  aprovado
  Bruno Lima    6.00  reprovado
  Carla Dias    9.25  aprovado
  aprovados: 2 de 3

=== 8. __toString permite imprimir o objeto direto ===
  1.250,50 reais
  Sem __toString, echo em um objeto daria Error: Object of class Moeda
  could not be converted to string.
Aula 2

Visibilidade, estáticos e encapsulamento

Visibilidade decide quem enxerga o quê, static decide quem pertence a quem. Encapsulamento é o efeito prático dos dois juntos: a escrita passa por um método que sabe aplicar a regra.

As três visibilidades

PalavraOnde é visível
publicde fora: $obj->propriedade e $obj->metodo()
protecteddentro da classe e das classes filhas
privatedentro da própria classe, e só dela

private no construtor e nos parâmetros vira propriedade private pela promoção de construtor. private readonly acrescenta a regra: só o construtor escreve, depois disso o valor é fixo.

Encapsulamento

final class Conta
{
    private float $saldo = 0.0;

    public function getSaldo(): float
    {
        return $this->saldo;
    }

    public function sacar(float $valor): bool
    {
        if ($valor <= 0 || $valor > $this->saldo) {
            return false;
        }
        $this->saldo -= $valor;

        return true;
    }
}

Com public float $saldo, qualquer um escreve $conta->saldo = -1000 e o saldo fica negativo. Com private, a escrita passa por sacar() ou depositar(), que recusam o valor. Tentar escrever de fora gera Error: Cannot access private property.

protected existe para a classe filha alcançar o que o pai escondeu de fora. O filho não alcança o private do pai: precisa de protected, ou de um método público que exponha o valor.

static e constantes

static marca o membro como pertencente à classe. O acesso é com ::, sem objeto, e a propriedade estática é uma só para todos.

final class Registro
{
    private static int $total = 0;

    public static function total(): int
    {
        return self::$total;
    }
}

echo Registro::total();

O que não pode ser estático é dado que varia por pessoa. Uma propriedade estática é compartilhada por todos os objetos, então a nota do aluno B sobrescreveria a do aluno A. static vai em contador, cache, registro e configuração.

public const NOME = valor; é acessado com Classe::NOME, e o valor é fixo desde a primeira execução.

self, static e parent

PalavraAcessa
self::a classe onde o método foi escrito
static::a classe que foi chamada, com os mesmos métodos
parent::a classe pai, para chamar construtor ou método sobrescrito

instanceof segue a herança: um ContaCorrente também é Conta. Para comparar a classe exata, o caminho é get_class($objeto) === 'ContaCorrente'.

Exemplo

<?php
declare(strict_types=1);

// Visibilidade (public, private, protected), membros estaticos, constantes de
// classe, encapsulamento com getter e setter, e instanceof.

echo "=== 1. As tres visibilidades ===\n";
$visibilidades = [
    'public'    => 'visivel de fora: $obj->propriedade e $obj->metodo()',
    'protected' => 'visivel so dentro da classe e das classes filhas',
    'private'   => 'visivel so dentro da propria classe',
];
foreach ($visibilidades as $palavra => $onde) {
    printf("  %-10s %s\n", $palavra, $onde);
}

echo "\n=== 2. A classe com encapsulamento de verdade ===\n";

class Conta
{
    private float $saldo = 0.0;
    public const MOEDA = 'R$';
    public const SALDO_MINIMO = 0.0;

    public function __construct(private readonly string $titular)
    {
    }

    public function getSaldo(): float
    {
        return $this->saldo;
    }

    public function getTitular(): string
    {
        return $this->titular;
    }

    public function depositar(float $valor): void
    {
        if ($valor <= 0) {
            throw new InvalidArgumentException('O deposito tem de ser positivo.');
        }
        $this->saldo += $valor;
    }

    public function sacar(float $valor): bool
    {
        if ($valor <= 0 || $valor > $this->saldo) {
            return false;
        }
        $this->saldo -= $valor;

        return true;
    }
}

$conta = new Conta('Ana Souza');

echo "  titular:  {$conta->getTitular()}   (private: so o getter da leitura)\n";
printf("  saldo:    %s %.2f\n", Conta::MOEDA, $conta->getSaldo());

$conta->depositar(500.00);
$conta->depositar(150.25);
printf("  depois de dois depositos: %s %.2f\n", Conta::MOEDA, $conta->getSaldo());

// --- Por que private, e nao public ---
echo "\n=== 3. O que acontece sem encapsular ===\n";
echo "  Com public float \$saldo, qualquer um faz \$conta->saldo = -1000.\n";
echo "  Com private, a escrita passa por depositar() e sacar(), que recusam\n";
echo "  valor invalido. A regra fica no lugar que a sabe aplicar.\n";

var_dump($conta->sacar(200.00));
printf("  depois do saque:        %s %.2f\n", Conta::MOEDA, $conta->getSaldo());
var_dump($conta->sacar(99999.00));
printf("  saque maior que o saldo recusado, saldo intacto: %s %.2f\n", Conta::MOEDA, $conta->getSaldo());

try {
    $conta->depositar(-50);
} catch (InvalidArgumentException $e) {
    echo '  deposito negativo recusado: ' . $e->getMessage() . "\n";
}

echo "\n=== 4. protected so aparece na classe filha ===\n";
final class ContaCorrente extends Conta
{
    public function __construct(string $titular, private float $limite = 0.0)
    {
        parent::__construct($titular);
    }

    public function debitarDoLimite(float $valor): bool
    {
        if ($valor <= 0 || $valor > $this->limite) {
            return false;
        }
        $this->limite -= $valor;   // acesso a propriedade private da propria classe

        return true;
    }
}

echo "  A classe filha alcanca \$this->limite, que e private dela mesma.\n";
echo "  Ela nao alcanca \$saldo, que e private da classe pai: para isso,\n";
echo "  o pai precisaria de protected ou de um metodo que expusesse o saldo.\n";

// --- Escrever em private de fora da classe ---
echo "\n=== 5. A propriedade private na barra a escrita de fora ===\n";
$codigo = <<<'PHP'
$conta->saldo = -1000;    // Error: Cannot access private property Conta::$saldo
PHP;
echo $codigo . "\n";
echo "  Um unico ponto e um '=' ja produzem Error fatal, e o script para.\n";

// --- Constantes de classe ---
echo "\n=== 6. Constantes de classe com const ===\n";
printf("  Conta::MOEDA       %s\n", Conta::MOEDA);
printf("  Conta::SALDO_MINIMO %s\n", var_export(Conta::SALDO_MINIMO, true));
echo "  Classe::CONSTANTE e o mesmo formato de Foo::BAR. Nao ha -> aqui:\n";
echo "  o sinal de dois pontos duplo acessa a propria classe, sem objeto.\n";

// --- Estaticos ---
echo "\n=== 7. static: metodo que pertence a classe, nao ao objeto ===\n";

final class Registro
{
    private static int $total = 0;

    public function __construct(private string $nome)
    {
        self::$total++;
    }

    public static function total(): int
    {
        return self::$total;
    }

    public static function zerar(): void
    {
        self::$total = 0;
    }
}

$a = new Registro('Ana');
$b = new Registro('Bruno');
$c = new Registro('Carla');

printf("  apos criar %d registros: Registro::total() = %d\n", 3, Registro::total());
echo "  \$a, \$b e \$c nao tem contador proprio: o contador e um so, da classe.\n";
echo "  E o mesmo estado que um contador estatico de tela, de visitor ou de\n";
echo "  item de carrinho, que precisa ser visto de qualquer lugar.\n";

Registro::zerar();
printf("  depois de Registro::zerar(): %d\n", Registro::total());

echo "\n  O que NAO pode ser estatico: estado que pertence a cada objeto.\n";
echo "  Uma propriedade estatica e compartilhada por todos, entao a nota do\n";
echo "  aluno B sobrescreveria a do aluno A. static vai em contador, cache\n";
echo "  e configuracao, nunca em dado que varia por pessoa.\n";

// --- self, static, parent ---
echo "\n=== 8. self, static e parent ===\n";
$palavras = [
    'self::'   => 'a classe onde o metodo foi escrito (a propria)',
    'static::' => 'a classe que foi chamada, com os mesmos metodos (heranca de metodo)',
    'parent::' => 'a classe pai, para chamar construtor ou sobrescrever algo',
];
foreach ($palavras as $op => $significado) {
    printf("  %-10s %s\n", $op, $significado);
}

// --- instanceof ---
echo "\n=== 9. instanceof com heranca ===\n";
$declarado = [$conta, new ContaCorrente('Bruno Lima', 300.0)];
foreach ($declarado as $obj) {
    printf("  %-16s instanceof Conta: %s\n", get_class($obj), var_export($obj instanceof Conta, true));
}
echo "  ContaCorrente tambem e Conta, porque instanceof segue a heranca.\n";
echo "  Para checar a classe exata, use get_class(\$obj) === 'ContaCorrente'.\n";

Saída real

=== 1. As tres visibilidades ===
  public     visivel de fora: $obj->propriedade e $obj->metodo()
  protected  visivel so dentro da classe e das classes filhas
  private    visivel so dentro da propria classe

=== 2. A classe com encapsulamento de verdade ===
  titular:  Ana Souza   (private: so o getter da leitura)
  saldo:    R$ 0.00
  depois de dois depositos: R$ 650.25

=== 3. O que acontece sem encapsular ===
  Com public float $saldo, qualquer um faz $conta->saldo = -1000.
  Com private, a escrita passa por depositar() e sacar(), que recusam
  valor invalido. A regra fica no lugar que a sabe aplicar.
bool(true)
  depois do saque:        R$ 450.25
bool(false)
  saque maior que o saldo recusado, saldo intacto: R$ 450.25
  deposito negativo recusado: O deposito tem de ser positivo.

=== 4. protected so aparece na classe filha ===
  A classe filha alcanca $this->limite, que e private dela mesma.
  Ela nao alcanca $saldo, que e private da classe pai: para isso,
  o pai precisaria de protected ou de um metodo que expusesse o saldo.

=== 5. A propriedade private na barra a escrita de fora ===
$conta->saldo = -1000;    // Error: Cannot access private property Conta::$saldo
  Um unico ponto e um '=' ja produzem Error fatal, e o script para.

=== 6. Constantes de classe com const ===
  Conta::MOEDA       R$
  Conta::SALDO_MINIMO 0.0
  Classe::CONSTANTE e o mesmo formato de Foo::BAR. Nao ha -> aqui:
  o sinal de dois pontos duplo acessa a propria classe, sem objeto.

=== 7. static: metodo que pertence a classe, nao ao objeto ===
  apos criar 3 registros: Registro::total() = 3
  $a, $b e $c nao tem contador proprio: o contador e um so, da classe.
  E o mesmo estado que um contador estatico de tela, de visitor ou de
  item de carrinho, que precisa ser visto de qualquer lugar.
  depois de Registro::zerar(): 0

  O que NAO pode ser estatico: estado que pertence a cada objeto.
  Uma propriedade estatica e compartilhada por todos, entao a nota do
  aluno B sobrescreveria a do aluno A. static vai em contador, cache
  e configuracao, nunca em dado que varia por pessoa.

=== 8. self, static e parent ===
  self::     a classe onde o metodo foi escrito (a propria)
  static::   a classe que foi chamada, com os mesmos metodos (heranca de metodo)
  parent::   a classe pai, para chamar construtor ou sobrescrever algo

=== 9. instanceof com heranca ===
  Conta            instanceof Conta: true
  ContaCorrente    instanceof Conta: true
  ContaCorrente tambem e Conta, porque instanceof segue a heranca.
  Para checar a classe exata, use get_class($obj) === 'ContaCorrente'.
Aula 3

Herança, abstração e interfaces

São quatro relações diferentes, e a escolha entre elas é a que define a estrutura do código. Herança é "é um", interface é "faz", classe abstrata é o modelo, e trait é o código compartilhado sem parentesco.

Herança

extends faz a classe filha herdar o que o pai já tem, e acrescentar o que falta. PHP tem um pai só por classe: não existe herança múltipla de classe.

abstract class Pagamento
{
    public function __construct(protected float $valor)
    {
    }

    abstract public function descricao(): string;   // a filha promete cumprir
}

final class Pix extends Pagamento
{
    public function __construct(float $valor, private string $chave)
    {
        parent::__construct($valor);   // o construtor do pai roda aqui
    }

    public function descricao(): string
    {
        return sprintf('Pix de R$ %.2f', $this->valor);
    }
}

parent::__construct() é obrigatório quando o pai tem construtor: sem a chamada, as propriedades do pai ficam vazias. parent:: também serve dentro de um método sobrescrito, para chamar a versão do pai e acrescentar algo.

O polimorfismo é o efeito: o código chama $pagamento->descricao() e recebe a resposta do objeto. O chamador não muda, o comportamento sim.

abstract, interface e final

abstract numa classe impede a instanciação: ela serve de modelo, e new nela dá Error: Cannot instantiate abstract class. abstract num método declara que a filha tem de implementar, e o corpo fica por conta dela.

interface lista o que tem de existir, sem implementar nada. A classe cumpre com implements, e pode implementar várias ao mesmo tempo.

abstract classinterface
Pode ter método com corposimnão
Pode ter propriedadesimnão
Quantas por classeuma, por extendsquantas, por implements
Serve para"é um" com código em comum"faz", sem caminho único

final impede a extensão ou a sobrescrita: final class não pode ter filha, e final public function não pode ser sobrescrito. Serve para o que não deve mudar, como o formato de um recibo.

Trait

trait RegistroLog
{
    public function registrar(string $mensagem): void
    {
        printf("[log] %s (em %s)\n", $mensagem, static::class);
    }
}

final class Pedido { use RegistroLog; }
final class Cupom   { use RegistroLog; }

O trait copia o método para dentro da classe, sem hierarquia. É a resposta para "as duas classes precisam disso" quando o "é um" não se aplica. static::class dentro dele mostra de qual classe o método foi chamado.

A regra da sobrescrita

A filha pode receber os mesmos tipos, ou mais amplos, e devolver o mesmo tipo, ou mais específico. Devolver algo mais amplo quebra quem chamou, e o PHP recusa.

Exemplo

<?php
declare(strict_types=1);

// Heranca (extends), classe abstrata (abstract), interface (implements),
// sobrescrita, parent, final e trait.

echo "=== 1. extends: a classe filha aproveita o que o pai ja faz ===\n";
echo "  A filha herda metodos e propriedades, e pode acrescentar os dela.\n";
echo "  Ela tem exatamente um pai: PHP nao tem heranca multipla de classe.\n";

abstract class Pagamento
{
    public function __construct(protected float $valor)
    {
    }

    abstract public function descricao(): string;   // a filha promete cumprir

    final public function recibo(): string
    {
        return "recibo: " . $this->descricao();
    }
}

final class Pix extends Pagamento
{
    public function __construct(float $valor, private string $chave)
    {
        parent::__construct($valor);   // o construtor do pai roda aqui
    }

    public function descricao(): string
    {
        return sprintf('Pix de R$ %.2f para a chave %s', $this->valor, $this->chave);
    }
}

final class Cartao extends Pagamento
{
    public function __construct(float $valor, private int $parcelas)
    {
        parent::__construct($valor);
    }

    public function descricao(): string
    {
        return sprintf('Cartao de R$ %.2f em %dx', $this->valor, $this->parcelas);
    }
}

$pagamentos = [new Pix(150.00, '[email protected]'), new Cartao(89.90, 3)];

echo "\n=== 2. O mesmo metodo, resultado diferente ===\n";
foreach ($pagamentos as $p) {
    printf("  %-46s %s\n", get_class($p), $p->descricao());
}
echo "\n  A variavel descricao() e a mesma chamada em dois objetos. O metodo\n";
echo "  que responde e o de cada um. E o polimorfismo: o codigo do chamador\n";
echo "  nao muda, o comportamento sim.\n";

echo "\n=== 3. parent:: chama o metodo do pai ===\n";
echo "  parent::__construct(\$valor) roda o construtor de Pagamento, que e o\n";
echo "  que preenche \$this->valor. Sem essa chamada, \$valor ficaria vazio.\n";
echo "  parent tambem serve dentro de metodo sobrescrito:\n";
echo "  parent::recibo() . 'extra' chama a versao do pai e continua a sua.\n";

// --- final ---
echo "\n=== 4. final: metodo ou classe que ninguem estende ===\n";
$codigo = <<<'PHP'
final class Cartao extends Pagamento   // Error: Class Cartao cannot extend final class Pagamento

final class Cartao extends Pagamento
{
    final public function recibo(): string { }   // Error: Cannot override final method
}
PHP;
echo $codigo . "\n";
echo "  No exemplo, recibo() e final: a classe filha nao pode trocar a forma\n";
echo "  do recibo, senao dois pagamentos nao teriam o mesmo formato.\n";

// --- abstract ---
echo "\n=== 5. A classe abstrata serve de modelo, nao se instancia ===\n";
$codigo = <<<'PHP'
$p = new Pagamento(100);   // Error: Cannot instantiate abstract class Pagamento
PHP;
echo $codigo . "\n";
echo "  abstract na e so um metodo sem corpo: a classe inteira nao pode ser\n";
echo "  instanciada, e existe para ser herdada. Serve quando a regra vale para\n";
echo "  todas as filhas e ninguem deve usar a forma base.\n";

// --- interface ---
echo "\n=== 6. interface: o que o codigo precisa saber, e so isso ===\n";

interface Exportavel
{
    public function exportar(): string;

    public function formato(): string;
}

final class Relatorio implements Exportavel
{
    public function __construct(private array $linhas)
    {
    }

    public function exportar(): string
    {
        return implode("\n", $this->linhas);
    }

    public function formato(): string
    {
        return 'txt';
    }
}

final class Planilha implements Exportavel
{
    public function exportar(): string
    {
        return 'a;1;2';
    }

    public function formato(): string
    {
        return 'csv';
    }
}

$exportacoes = [new Relatorio(['id;nome', '1;Ana']), new Planilha()];
foreach ($exportacoes as $e) {
    printf("  %-12s formato %-4s -> %s\n", get_class($e), $e->formato(), $e->exportar());
}
echo "\n  Uma classe pode implementar varias interfaces ao mesmo tempo:\n";
echo "  final class Planilha implements Exportavel, Enviavel\n";

echo "\n=== 7. abstract e interface, lado a lado ===\n";
$comparacao = [
    'abstract class' => 'pode ter metodo implementado e propriedade; a filha herda o que existe',
    'interface'      => 'so o que tem de existir; nada e implementado na interface',
    'implements'     => 'promete cumprir os metodos da interface',
    'extends'        => 'aproveita a implementacao de uma unica classe',
];
foreach ($comparacao as $palavra => $papel) {
    printf("  %-15s %s\n", $palavra, $papel);
}
echo "\n  Heranca e para 'e um': um Pix e um Pagamento. Interface e para\n";
echo "  'faz': um pagamento e exportavel, um banco e exportavel.\n";

// --- trait ---
echo "\n=== 8. trait: o mesmo codigo em classes que nao tem parentesco ===\n";

trait RegistroLog
{
    public function registrar(string $mensagem): void
    {
        printf("  [log] %s (em %s)\n", $mensagem, static::class);
    }
}

final class Pedido
{
    use RegistroLog;
}

final class Cupom
{
    use RegistroLog;
}

(new Pedido())->registrar('pedido criado');
(new Cupom())->registrar('cupom aplicado');

echo "\n  static::class mostra de qual classe o metodo foi chamado, e nao de\n";
echo "  qual trait o codigo foi copiado. Sem heranca, sem interface: a trait\n";
echo "  e o jeito de compartilhar codigo quando o 'e um' nao se aplica.\n";

// --- sobrescrita e compatibilidade ---
echo "\n=== 9. Regra de compatibilidade na sobrescrita ===\n";
echo "  O metodo da filha pode receber: os mesmos tipos, ou mais amplos, e\n";
echo "  devolver: o mesmo tipo, ou mais especifico.\n";
echo "  Devolver algo mais amplo quebra quem chamou, entao o PHP recusa com\n";
echo "  um erro de compatibilidade de tipo. A regra vale para o return covariant.\n";

Saída real

=== 1. extends: a classe filha aproveita o que o pai ja faz ===
  A filha herda metodos e propriedades, e pode acrescentar os dela.
  Ela tem exatamente um pai: PHP nao tem heranca multipla de classe.

=== 2. O mesmo metodo, resultado diferente ===
  Pix                                            Pix de R$ 150.00 para a chave [email protected]
  Cartao                                         Cartao de R$ 89.90 em 3x

  A variavel descricao() e a mesma chamada em dois objetos. O metodo
  que responde e o de cada um. E o polimorfismo: o codigo do chamador
  nao muda, o comportamento sim.

=== 3. parent:: chama o metodo do pai ===
  parent::__construct($valor) roda o construtor de Pagamento, que e o
  que preenche $this->valor. Sem essa chamada, $valor ficaria vazio.
  parent tambem serve dentro de metodo sobrescrito:
  parent::recibo() . 'extra' chama a versao do pai e continua a sua.

=== 4. final: metodo ou classe que ninguem estende ===
final class Cartao extends Pagamento   // Error: Class Cartao cannot extend final class Pagamento

final class Cartao extends Pagamento
{
    final public function recibo(): string { }   // Error: Cannot override final method
}
  No exemplo, recibo() e final: a classe filha nao pode trocar a forma
  do recibo, senao dois pagamentos nao teriam o mesmo formato.

=== 5. A classe abstrata serve de modelo, nao se instancia ===
$p = new Pagamento(100);   // Error: Cannot instantiate abstract class Pagamento
  abstract na e so um metodo sem corpo: a classe inteira nao pode ser
  instanciada, e existe para ser herdada. Serve quando a regra vale para
  todas as filhas e ninguem deve usar a forma base.

=== 6. interface: o que o codigo precisa saber, e so isso ===
  Relatorio    formato txt  -> id;nome
1;Ana
  Planilha     formato csv  -> a;1;2

  Uma classe pode implementar varias interfaces ao mesmo tempo:
  final class Planilha implements Exportavel, Enviavel

=== 7. abstract e interface, lado a lado ===
  abstract class  pode ter metodo implementado e propriedade; a filha herda o que existe
  interface       so o que tem de existir; nada e implementado na interface
  implements      promete cumprir os metodos da interface
  extends         aproveita a implementacao de uma unica classe

  Heranca e para 'e um': um Pix e um Pagamento. Interface e para
  'faz': um pagamento e exportavel, um banco e exportavel.

=== 8. trait: o mesmo codigo em classes que nao tem parentesco ===
  [log] pedido criado (em Pedido)
  [log] cupom aplicado (em Cupom)

  static::class mostra de qual classe o metodo foi chamado, e nao de
  qual trait o codigo foi copiado. Sem heranca, sem interface: a trait
  e o jeito de compartilhar codigo quando o 'e um' nao se aplica.

=== 9. Regra de compatibilidade na sobrescrita ===
  O metodo da filha pode receber: os mesmos tipos, ou mais amplos, e
  devolver: o mesmo tipo, ou mais especifico.
  Devolver algo mais amplo quebra quem chamou, entao o PHP recusa com
  um erro de compatibilidade de tipo. A regra vale para o return covariant.
Aula 4

Composer e autoload

Composer resolve duas coisas: baixar biblioteca e carregar classes sem require manual. O arquivo composer.json declara as duas coisas, e composer install gera o carregador.

O fluxo

composer install

O que ele faz: lê o composer.json, baixa as bibliotecas para vendor/, gera vendor/autoload.php e registra o carregador. A partir daí, o projeto precisa de uma linha só:

require __DIR__ . '/vendor/autoload.php';

$repo = new Escola\Repository\AlunoRepository($pdo);   // sem require da classe

O composer.json e o composer.lock vão para o git. A pasta vendor/ não: ela é reconstruída com composer install, e versionar biblioteca de terceiro vira conflito de versão em conflito de código.

O composer.json

{
    "name": "escola/alunos",
    "require": {
        "php": ">=8.2",
        "vlucas/phpdotenv": "^5.6"
    },
    "autoload": {
        "psr-4": {
            "Escola\\": "src/"
        }
    }
}

^5.6 quer dizer versão 5.6 ou mais nova, dentro do mesmo major. As versões exatas ficam no composer.lock, e é ele que garante que todo mundo instala o mesmo conjunto.

Depois de criar ou renomear uma classe, o autoload precisa ser regerado: composer dump-autoload.

namespace, use e PSR-4

PSR-4 é a regra que converte namespace em caminho: a barra invertida do namespace vira barra no diretório.

NamespaceArquivo
Escola\src/
Escola\Repository\src/Repository/
Escola\Repository\AlunoRepositorysrc/Repository/AlunoRepository.php

O nome do arquivo tem que casar com o nome da classe, letra por letra.

<?php
declare(strict_types=1);

namespace Escola\Repository;

use PDO;

final class AlunoRepository
{
    public function __construct(private PDO $pdo) { }
}

namespace declara onde a classe mora. use cria um apelido para um nome de outro espaço de nomes, para não repetir \PDO em cada linha — o use não carrega arquivo, ele só dá o apelido.

O autoload escrito à mão

É o mesmo princípio, e mostra o que o Composer faz por baixo:

spl_autoload_register(static function (string $classe) use ($raiz): void {
    $caminho = $raiz . '/' . $classe . '.php';

    if (is_file($caminho)) {
        require $caminho;
    }
});

O PHP chama essa função sempre que encontra um nome de classe que ainda não carregou, e a partir daí o new funciona sem require. Com namespace, a conversão é substr() depois do prefixo e str_replace('\\', '/') no resto.

O use ($raiz) no parâmetro da função anônima importa a pasta de fora: sem ele, a função não enxerga a variável.

Exemplo

<?php
declare(strict_types=1);

// Composer, autoload, PSR-4, namespace e use.
//
// Roda sem o Composer instalado: a parte de namespace e de classe aparece
// como texto, porque um arquivo so pode declarar uma classe sem espaco de
// nome, e o exemplo precisa das duas classes lado a lado. O que roda de
// verdade e o autoload manual, que segue o mesmo principio que o do Composer:
// o PHP pergunta "onde esta esta classe?" e alguem responde com um caminho.

echo "=== 1. O problema que o Composer resolve ===\n";
echo "  Sem autoload, quem usa uma classe precisa dizer de onde ela vem:\n";
echo "  require_once __DIR__ . '/src/Repository/AlunoRepository.php';\n";
echo "  Um esquecimento da linha require vira erro em tempo de execucao,\n";
echo "  longe do arquivo que usa a classe. O autoload tira esse trabalho.\n";

// --- O que o Composer faz ---
echo "\n=== 2. O que o Composer faz na pratica ===\n";
$passos = [
    'composer.json'   => 'a lista de bibliotecas e o mapa de autoload do projeto',
    'composer install' => 'baixa as bibliotecas em vendor/ e gera o autoload',
    'vendor/autoload.php' => 'um arquivo require que registra o carregador automatico',
    'require "vendor/autoload.php"' => 'a unica linha de que o projeto precisa',
];
foreach ($passos as $coisa => $faz) {
    printf("  %-38s %s\n", $coisa, $faz);
}
echo "\n  composer.json e composer.lock vao para o git. A pasta vendor/ nao:\n";
echo "  ela e reconstruida com composer install, e versionar biblioteca de\n";
echo "  terceiro vira conflito de versao em conflito de codigo.\n";

// --- O composer.json ---
echo "\n=== 3. O composer.json com PSR-4 ===\n";
$json = <<<'JSON'
{
    "name": "escola/alunos",
    "require": {
        "php": ">=8.2"
    },
    "autoload": {
        "psr-4": {
            "Escola\\": "src/"
        }
    }
}
JSON;
echo $json . "\n";
echo "\n  A regra do PSR-4: a barra invertida do namespace vira barra no caminho.\n";
echo "  Escola\\Repository\\AlunoRepository  ->  src/Repository/AlunoRepository.php\n";

// --- A tabela de correspondencia ---
echo "\n=== 4. Como o namespace vira caminho ===\n";
$casos = [
    'Escola\\'                       => 'src/',
    'Escola\\Repository\\'            => 'src/Repository/',
    'Escola\\Repository\\AlunoRepository' => 'src/Repository/AlunoRepository.php',
];
foreach ($casos as $namespace => $arquivo) {
    printf("  %-40s %s\n", $namespace, $arquivo);
}
echo "\n  A classe fica dentro do namespace com a mesma barra invertida:\n";
echo "  namespace Escola\\Repository;   e a pasta e src/Repository/.\n";
echo "  O nome do arquivo tem que casar com o nome da classe, letra por letra.\n";

// --- O namespace e o use ---
echo "\n=== 5. namespace e use ===\n";
$codigo = <<<'PHP'
<?php
declare(strict_types=1);

namespace Escola\Repository;

use PDO;

final class AlunoRepository
{
    public function __construct(private PDO $pdo) { }
}
PHP;
echo $codigo . "\n";
echo "  namespace Escola\\Repository  declara onde a classe mora.\n";
echo "  use PDO importa o nome de outro espaco de nome, para nao repetir\n";
echo "  \\PDO em cada linha. O use nao carrega arquivo: ele so cria um apelido.\n";

// --- Varias classes no mesmo arquivo ---
echo "\n=== 6. Por que o exemplo nao roda o namespace ===\n";
echo "  Um arquivo declara um namespace so, e o autoload do PSR-4 espera um\n";
echo "  arquivo por classe. Por isso o exemplo mostra o namespace em texto e\n";
echo "  roda o autoload de verdade, logo abaixo, com nomes sem espaco de nome.\n";

// --- O autoload manual ---
echo "\n=== 7. O mesmo principio, escrito a mao ===\n";

// A classe e gravada num arquivo proprio, num diretorio temporario, para que
// o carregador abaixo tenha o que encontrar. Num projeto de verdade, esse
// arquivo seria src/Repository/AlunoRepository.php e o carregador do PSR-4
// usaria a barra invertida do namespace virando barra no caminho.
$raiz = sys_get_temp_dir() . '/aula-psr4/src/Repository';
if (!is_dir($raiz)) {
    mkdir($raiz, 0777, true);
}
$caminhoDaClasse = $raiz . '/AlunoRepository.php';
file_put_contents($caminhoDaClasse, <<<'PHP'
<?php
declare(strict_types=1);

final class AlunoRepository
{
    public function __construct(private array $banco)
    {
    }

    public function todos(): array
    {
        return $this->banco;
    }
}
PHP);

// Este arquivo nao tem namespace, entao a conversao usa so o nome da classe.
// Com namespace, seria substr() depois do prefixo e str_replace('\\', '/').
// A pasta entra por use, porque uma funcao sem capture nao enxerga variavel
// de fora: sem isso, $raiz chegaria vazia no carregador.
spl_autoload_register(static function (string $classe) use ($raiz): void {
    $caminho = $raiz . '/' . $classe . '.php';

    if (is_file($caminho)) {
        require $caminho;
    }
});

echo "  Arquivo criado: {$caminhoDaClasse}\n";
echo "  spl_autoload_register grava um carregador: o PHP chama ele sempre que\n";
echo "  encontra um nome de classe que ainda nao foi carregado. Com ele, o\n";
echo "  new AlunoRepository() abaixo funciona sem nenhum require manual.\n";

echo "\n=== 8. O new sem require: o carregador e acionado pelo proprio new ===\n";
printf("  antes do new, class_exists sem autoload: %s\n",
    var_export(class_exists('AlunoRepository', false), true));

$repo = new AlunoRepository([
    ['id' => 1, 'nome' => 'Ana Souza'],
    ['id' => 2, 'nome' => 'Bruno Lima'],
]);

printf("  depois do new, class_exists sem autoload: %s\n",
    var_export(class_exists('AlunoRepository', false), true));
printf("  registros carregados do arquivo: %d\n", count($repo->todos()));

// O arquivo temporario so serviu para a demonstracao.
unlink($caminhoDaClasse);

// --- O composer.json completo de um projeto com biblioteca ---
echo "\n=== 9. Um composer.json com biblioteca de terceiros ===\n";
$json = <<<'JSON'
{
    "name": "escola/alunos",
    "require": {
        "php": ">=8.2",
        "vlucas/phpdotenv": "^5.6"
    },
    "autoload": {
        "psr-4": {
            "Escola\\": "src/"
        }
    },
    "require-dev": {
        "phpunit/phpunit": "^11.0"
    }
}
JSON;
echo $json . "\n";
echo "\n  ^5.6 significa: versao 5.6 ou mais nova, dentro do mesmo major.\n";
echo "  As versoes ficam no composer.lock; e ele que garante que todo mundo\n";
echo "  instala exatamente o mesmo conjunto de bibliotecas.\n";

Saída real

=== 1. O problema que o Composer resolve ===
  Sem autoload, quem usa uma classe precisa dizer de onde ela vem:
  require_once __DIR__ . '/src/Repository/AlunoRepository.php';
  Um esquecimento da linha require vira erro em tempo de execucao,
  longe do arquivo que usa a classe. O autoload tira esse trabalho.

=== 2. O que o Composer faz na pratica ===
  composer.json                          a lista de bibliotecas e o mapa de autoload do projeto
  composer install                       baixa as bibliotecas em vendor/ e gera o autoload
  vendor/autoload.php                    um arquivo require que registra o carregador automatico
  require "vendor/autoload.php"          a unica linha de que o projeto precisa

  composer.json e composer.lock vao para o git. A pasta vendor/ nao:
  ela e reconstruida com composer install, e versionar biblioteca de
  terceiro vira conflito de versao em conflito de codigo.

=== 3. O composer.json com PSR-4 ===
{
    "name": "escola/alunos",
    "require": {
        "php": ">=8.2"
    },
    "autoload": {
        "psr-4": {
            "Escola\\": "src/"
        }
    }
}

  A regra do PSR-4: a barra invertida do namespace vira barra no caminho.
  Escola\Repository\AlunoRepository  ->  src/Repository/AlunoRepository.php

=== 4. Como o namespace vira caminho ===
  Escola\                                  src/
  Escola\Repository\                       src/Repository/
  Escola\Repository\AlunoRepository        src/Repository/AlunoRepository.php

  A classe fica dentro do namespace com a mesma barra invertida:
  namespace Escola\Repository;   e a pasta e src/Repository/.
  O nome do arquivo tem que casar com o nome da classe, letra por letra.

=== 5. namespace e use ===
<?php
declare(strict_types=1);

namespace Escola\Repository;

use PDO;

final class AlunoRepository
{
    public function __construct(private PDO $pdo) { }
}
  namespace Escola\Repository  declara onde a classe mora.
  use PDO importa o nome de outro espaco de nome, para nao repetir
  \PDO em cada linha. O use nao carrega arquivo: ele so cria um apelido.

=== 6. Por que o exemplo nao roda o namespace ===
  Um arquivo declara um namespace so, e o autoload do PSR-4 espera um
  arquivo por classe. Por isso o exemplo mostra o namespace em texto e
  roda o autoload de verdade, logo abaixo, com nomes sem espaco de nome.

=== 7. O mesmo principio, escrito a mao ===
  Arquivo criado: /tmp/aula-psr4/src/Repository/AlunoRepository.php
  spl_autoload_register grava um carregador: o PHP chama ele sempre que
  encontra um nome de classe que ainda nao foi carregado. Com ele, o
  new AlunoRepository() abaixo funciona sem nenhum require manual.

=== 8. O new sem require: o carregador e acionado pelo proprio new ===
  antes do new, class_exists sem autoload: false
  depois do new, class_exists sem autoload: true
  registros carregados do arquivo: 2

=== 9. Um composer.json com biblioteca de terceiros ===
{
    "name": "escola/alunos",
    "require": {
        "php": ">=8.2",
        "vlucas/phpdotenv": "^5.6"
    },
    "autoload": {
        "psr-4": {
            "Escola\\": "src/"
        }
    },
    "require-dev": {
        "phpunit/phpunit": "^11.0"
    }
}

  ^5.6 significa: versao 5.6 ou mais nova, dentro do mesmo major.
  As versoes ficam no composer.lock; e ele que garante que todo mundo
  instala exatamente o mesmo conjunto de bibliotecas.