Pular para conteúdo

title: Contratos: Interfaces e Contracts @api description: O papel dos contracts na arquitetura e as regras de publicidade e promoção.


Interfaces e Contracts @api

No local_middag, um Contract é uma interface do PHP que define um papel arquitetural específico, permitindo a inversão de dependência (DI) e a extensibilidade de comportamento.


O que é

É a definição técnica do "o que um componente deve fazer". Diferente de uma Facade (que é uma porta de entrada estática), o Contract é um tipo que pode ter múltiplas implementações.

A característica definidora de um Contract na API Pública é a anotação @api. Sem ela, a interface é considerada um detalhe de implementação interno do framework.

Por que existe

O framework utiliza Contracts para: 1. Habilitar Injeção de Dependência: Permitir que serviços recebam dependências via construtor sem saber qual classe concreta está sendo injetada. 2. Permitir Substituição: Dar ao desenvolvedor de uma extension a capacidade de trocar como o framework faz algo (ex: mudar o authorizer padrão pelo seu próprio). 3. Desacoplamento de Camadas: A Camada de Aplicação depende de um Contract de Repositório, não de uma classe que escreve em SQL.

Promoção para @api

Nem toda interface do framework é (ou deve ser) pública. O critério para promover uma interface para @api é rigoroso: * Variação Real: O contrato precisa de múltiplas implementações Reais ou Previstas. * Ponto de Extensão: É necessário que uma Extension possa substituir ou injetar esse comportamento. * Contrato de Consumo: O componente é fundamental para builders de extensions construírem suas próprias lógicas (ex: Repositórios).

Decisões de design

  • Pequeno e Coeso: Seguimos o Princípio da Segregação de Interface (ISP). É preferível ter três contratos pequenos com poucas responsabilidades do que um contrato gigante e difícil de implementar.
  • Preferência de Consumo: Para consumo estático simples, o framework recomenda as Facades. Para consumo via DI, herança de lógica ou substituição estrutural, o caminho indicado são os Contracts @api.
  • Interno por Padrão: Muitos contracts vivem em classes/framework/contract/ apenas por organização, mas permanecem como internal. O diretório não define a estabilidade; a anotação define.

O que não é

  • Não é Burocracia: Não criamos interfaces para todas as classes do framework. Se não há necessidade de variação ou DI, uma dependência concreta em uma classe estável é preferível para reduzir a complexidade.
  • Não é Facade: Contracts exigem Injeção de Dependência (via Construtor). Facades são chamadas estaticamente.
  • Não Substitui Classes-Base: Contracts definem o que fazer (Interface). Classes-Base oferecem como fazer (Implementação Parcial).

Perspectiva para builders de extensions

Como desenvolvedor de extension: 1. Tipagem Estável: Utilize os Contracts @api em seus type-hints. Isso garante que sua extension continuará funcionando se o framework mudar a implementação interna do serviço. 2. DI via Container: Registre sua extension para receber Contracts @api. O Kernel cuidará de injetar a classe concreta correta para você. 3. Implementação de Customização: Se você criar um componente que deve ser usado pelo core (ex: um novo authorizer), implemente o Contract correspondente e registre-o no container durante o seu register().

Exemplo ilustrativo

Consumindo um Contract via DI em uma Extension:

namespace local_middag\extensions\reports\service;

use local_middag\framework\contract\repository\item_repository_interface; // Contract @api

final class report_generator {
    public function __construct(
        private item_repository_interface $repository // Injeção via Contract
    ) {}

    public function generate() {
        $items = $this->repository->find_all_by_type('company');
        // ...
    }
}

Neste exemplo, o report_generator não sabe se o repositório é SQL, se possui cache ou se é um mock de teste. Ele confia apenas no contrato da interface.

Referências