Skip to content

WordPress Plugin Patterns — Arquitetura Reutilizavel

Padroes comprovados extraidos do middag-account v4. Claude Code: aplique estes padroes ao trabalhar no plugin middag-account.

Companion Rules

RuleFoco
wp-plugin-best-practices.mdChecklist de qualidade (15 secoes)
wp-plugin-workflow.mdCiclo de desenvolvimento (setup ate ship)
wp-plugin-testing-strategy.mdPiramide de testes e cobertura
wp-plugin-security-checklist.mdDeep-dive de seguranca

1. DDD Light + Symfony DI Container

Pattern: Domain logic isolada do WordPress. Symfony DI Container 7.4 para autowiring.

src/
├── Core/              # Container, Kernel, ServiceProvider
├── Domain/{Entity}/   # Pure PHP — ZERO WordPress deps (15 dominios)
├── Api/               # REST controllers (~81 endpoints)
├── Integration/       # HubSpot, Stripe, ISSNet, Banco Inter, Cloudflare
├── UI/                # InertiaAdapter, PageControllers
└── WordPress/         # CMS abstraction layer

Rules:

  • Domain/ NUNCA importa funcoes WP/WC
  • Direcao: WordPress/ -> Domain/, nunca o inverso
  • Constructor injection everywhere, zero static service calls
  • Auto-discovery por sufixo de classe: Service, Repository, Controller, Handler, Hooks, Registrar
  • Integration/ acessa Domain/ via interfaces, nunca direto

15 Dominios v4: Organization, Collaborator, Order, Invoice, TaxInvoice, License, Contract, Document, Entitlement, Environment, Service, ServiceRequest, Quote, Affiliate, Download

Porque funciona: Domain testavel sem WP bootstrap. WP API changes isoladas na camada WordPress/. Integracoes isoladas em Integration/.


2. HookInterface Auto-Discovery

Pattern: Todos WordPress hooks registrados via classes implementando HookInterface.

php
interface HookInterface {
    public function register(): void;
}

HookRegistrar escaneia WordPress/Hook/ por arquivos *Hooks, instancia via Container, chama register().

Beneficios: Sem add_action() espalhados. Cada hook class e single-responsibility. Container injeta dependencias.

Prefixo padrao: middag/ para todos hooks customizados (ex: middag/entitlement/created, middag/order/status_changed).


3. Inertia.js Full Admin App

Pattern: Admin UI completa via Inertia.js v2 + React 19. Nao React Islands.

php
// PHP: InertiaAdapter renderiza pagina completa
class InertiaAdapter {
    public function render(string $component, array $props = []): void {
        // Renderiza o shell HTML com dados Inertia inline
        // React 19 hydrata a pagina completa no client
    }
}
php
// PHP: PageController usa Inertia para resposta
class OrganizationPageController {
    public function index(): void {
        $this->inertia->render('Organization/Index', [
            'organizations' => $this->service->list(),
            'filters' => request()->query(),
        ]);
    }
}
tsx
// React: pagina Inertia recebe props server-side
export default function OrganizationIndex({ organizations, filters }) {
    return (
        <Layout>
            <OrganizationTable data={organizations} filters={filters} />
        </Layout>
    );
}

Build: Vite 6 -> IIFE single bundle (assets/dist/app.js). Tailwind v4. Assets apenas em telas relevantes.

Diferenca chave do React Islands: Inertia gerencia navegacao SPA completa. Nao ha multiplos mount points — ha um unico app React que recebe props do servidor via protocolo Inertia.


4. Repository Bridge (Domain <-> wp_posts)

Pattern: Domain entity e pure PHP. Repository mapeia para/de wp_posts via CPT.

Domain/Organization/OrganizationEntity.php                # Pure PHP, no WP
Domain/Organization/OrganizationRepositoryInterface.php   # Contract
WordPress/Repository/OrganizationRepository.php           # Maps wp_posts <-> OrganizationEntity
WordPress/Repository/CctOrganizationRepository.php        # Maps JetEngine CCT <-> OrganizationEntity

Dual Repository Pattern (migracao):

php
// Toggle alterna entre CCT e wp_posts
if (get_option('middag_migration_complete')) {
    // Usa OrganizationRepository (wp_posts)
} else {
    // Usa CctOrganizationRepository (JetEngine CCT)
}

Domain nunca toca WP_Query ou get_post_meta(). Repository faz todo o mapeamento.


5. Custom Post Type Registration

Pattern: Cada dominio registra seu CPT com prefixo middag_.

php
class OrganizationCptRegistrar {
    public function register(): void {
        register_post_type('middag_organization', [
            'labels'       => [...],
            'public'       => false,
            'show_ui'      => false,  // UI via Inertia, nao WP admin
            'supports'     => ['title', 'custom-fields'],
            'capability_type' => 'middag_organization',
            'map_meta_cap' => true,
        ]);
    }
}

Rules:

  • CPT slugs: middag_{domain} (max 20 chars)
  • show_ui => false — toda UI via Inertia admin app, nao WP admin
  • capability_type custom para controle granular de permissoes
  • Meta keys mapeados no Repository class correspondente
  • Rewrite rules flush na ativacao/desativacao

6. Activation/Deactivation/Uninstall Triad

HookArquivoResponsabilidades
ActivationKernel::onActivate()Criar tables (dbDelta), registrar CPTs, add capabilities, flush rewrites
DeactivationKernel::onDeactivate()Unschedule crons, remover capabilities, flush rewrites
Uninstalluninstall.phpDrop tables, deletar options, limpar transients, remover caps, CPT data

Rule: Activation/deactivation reversiveis. Uninstall destrutivo (apenas no delete do plugin).


7. Entitlement Aggregator (Hub Central)

Pattern: Entitlement e o dominio agregador que conecta todos os outros dominios. Modelo inspirado no Atlassian SEN.

Codigo: {CLASSE}-{ANO}{MES}{SEQ:4d}
Exemplo: PLG-2026040142

7 Classes de Entitlement:

ClasseCodigoDominio Conectado
PluginPLGLicense, Download
AmbienteENVEnvironment
LabsLABService (tipo Labs)
DevDEVService (tipo Dev)
PedidoORDOrder, Invoice
TreinamentoTRN(futuro)
AfiliadoAFLAffiliate

Fluxo:

HubSpot -> Quote -> aceite -> pagamento -> Entitlement auto-criado
                                           -> License criada (se PLG)
                                           -> Environment criado (se ENV)
                                           -> Service criado (se LAB/DEV)

Rule: Todo recurso provisionado para cliente passa pelo Entitlement. E o ponto unico de rastreabilidade.


8. Multi-Integration Pattern (HubSpot/Stripe Dual-Account)

Pattern: Integracoes externas isoladas em Integration/ com interface abstrata.

src/Integration/
├── HubSpot/
│   ├── HubSpotClientInterface.php     # Contract
│   ├── HubSpotClient.php              # HTTP client
│   ├── HubSpotWebhookHandler.php      # Processa webhooks
│   └── HubSpotSyncService.php         # Sincronizacao bidirecional
├── Stripe/
│   ├── StripeClientInterface.php
│   ├── StripeClient.php
│   ├── StripeWebhookHandler.php       # Signature validation
│   └── StripeDualAccountService.php   # Live + Test accounts
├── BancoInter/
│   ├── BancoInterGateway.php          # Override pagador com CNPJ Org
│   └── BancoInterWebhookHandler.php
├── ISSNet/
│   └── ISSNetService.php              # Nota fiscal de servico
└── Cloudflare/
    └── CloudflareService.php          # DNS/SSL para environments

Stripe Dual-Account:

php
// Suporte a contas live e test simultaneas
class StripeDualAccountService {
    public function getClient(string $mode = 'live'): StripeClient {
        return match($mode) {
            'live' => new StripeClient($this->config->liveKey()),
            'test' => new StripeClient($this->config->testKey()),
        };
    }
}

Webhook Security: Cada provider valida assinatura do webhook antes de processar (ver wp-plugin-security-checklist.md).


9. Template System (Theme-Overridable Blocks)

Pattern: Templates em templates/. Theme overrides em themes/child-theme/middag-account/.

Block system: Blocos reordenaveis renderizados por TemplateRenderer. Ordem configuravel via filter. Before/after hooks por bloco.


10. CI/CD Pipeline

develop         -> Quality Gate (test + style + stan)
main            -> Quality Gate + Manual Release
tags/**         -> Build Assets + ZIP + Deploy
pull-requests   -> Quality Gate

Distribuicao: composer install --no-dev + Vite build -> ZIP. .distignore + .gitattributes alinhados.


11. i18n English-First

Source strings sempre em ingles. Traducoes via .po/.mo. Scripts: composer i18n:pot + composer i18n:mo.


12. REST API v4 Conventions

Namespace: middagaccount/v4

Auth tripla:

  1. WP Nonce (usuarios logados via admin)
  2. JWT RS256 (API externa — portal NextJS, mobile)
  3. WC Consumer Keys (integracoes legacy)

Estrutura de rotas por dominio:

/wp-json/middagaccount/v4/organizations
/wp-json/middagaccount/v4/organizations/{id}/collaborators
/wp-json/middagaccount/v4/entitlements
/wp-json/middagaccount/v4/orders
/wp-json/middagaccount/v4/invoices
/wp-json/middagaccount/v4/licenses
/wp-json/middagaccount/v4/environments
/wp-json/middagaccount/v4/services
/wp-json/middagaccount/v4/service-requests
/wp-json/middagaccount/v4/quotes
/wp-json/middagaccount/v4/documents
/wp-json/middagaccount/v4/contracts
/wp-json/middagaccount/v4/affiliates
/wp-json/middagaccount/v4/downloads
/wp-json/middagaccount/v4/tax-invoices

Organization-scoped: Toda request inclui X-Middag-Organization header para multi-tenant isolation.

Response envelope: RestResponse helper garante formato consistente em todos ~81 endpoints.


13. Capability Hierarchy

Capabilities customizadas com user_has_cap filter expansion. Registered na ativacao, removidas na desativacao. Organization-scoped para multi-tenant.


14. Cron with Domain Delegation

WP Cron agenda evento. Handler e thin wrapper que resolve service do DI Container. Business logic em domain service testavel.


Replication Checklist

Ao criar novo dominio no middag-account:

ArquivoProposito
src/Domain/{Name}/{Name}Entity.phpEntidade pura PHP
src/Domain/{Name}/{Name}RepositoryInterface.phpContrato do repositorio
src/Domain/{Name}/{Name}Service.phpLogica de negocio
src/WordPress/Repository/{Name}Repository.phpImplementacao wp_posts
src/WordPress/Repository/Cct{Name}Repository.phpImplementacao JetEngine CCT
src/WordPress/Hook/{Name}Hooks.phpHooks WordPress
src/Api/{Name}Controller.phpREST API controller
src/UI/{Name}PageController.phpInertia page controller
ui/src/Pages/{Name}/Index.tsxReact page component
tests/Unit/Domain/{Name}/{Name}EntityTest.phpUnit test

Extraido de: middag-account v4 — MIDDAG, Abril 2026