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 comointernal. 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.