Patrones de Producción
Patrones concretos utilizados en las aplicaciones de Nibiru en producción — listo para copiar y pegar.
Siete patrones que aparecen repetidamente en las aplicaciones de producción de Nibiru. Cada uno es pequeño, listo para copiar y pegar, y se basa en una base de código real.
1. Controlador delgado → delegación a módulo complemento
Sección titulada «1. Controlador delgado → delegación a módulo complemento»Mantén los controladores pequeños. Inserta la lógica en los complementos de módulos.
// thinclass erpController extends Controller { public function syncAction(): void { View::forwardToJsonHeader(); $result = \Nibiru\Module\Erp\Plugin\Sync::run(); View::assign(['data' => $result]); }}``````php// fatclass Sync extends Erp { public static function run(): array { $svc = AlphaplanSyncService::getInstance(); try { return ['success' => true, 'changes' => $svc->syncAbDocuments()]; } catch (\Throwable $e) { return ['success' => false, 'error' => $e->getMessage()]; } }}El controlador se puede revisar en 5 segundos; el complemento es probable con pruebas unitarias.
2. CMS como fuente de contenido
Sección titulada «2. CMS como fuente de contenido»Desacopla el texto del diseño. Los editores actualizan el contenido a través de la interfaz de usuario del módulo CMS; las plantillas permanecen bajo propiedad de los desarrolladores.
public function pageAction() { $path = $this->getController() . '/' . $this->getRequest('_action', 'page'); foreach (Cms::init($this->getController()) ->loadCmsTemplateTextsByControllerPath($path, $this->language) as $t) { View::assign([ $t['cms_template_texts_text_identifier'] => $t['cms_template_texts_text_content'] ]); }}Las plantillas hacen referencia a los identificadores como si fueran variables ordinarias:
<h1>{$hero_title}</h1><p>{$hero_intro}</p>3. Análisis basado en observadores
Sección titulada «3. Análisis basado en observadores»Varios rastreadores sin acoplamiento con el controlador.
$analytics = new Analytics();$analytics->attach(new Plugin\Matomo());$analytics->attach(new Plugin\Plausible());$analytics->trackPageView(); / calls notify() internallyCada observador extrae solo los campos que le interesan con su update($subject). Agregar un rastreador es un cambio de una línea.
4. Composición de navegación múltiple
Sección titulada «4. Composición de navegación múltiple»Construye páginas con varios arreglos de navegación nombrados en lugar de una estructura monolítica.
public function navigationAction() { foreach (['head', 'main', 'social', 'footer'] as $name) { JsonNavigation::getInstance()->loadJsonNavigationArray($name); }}``````smarty<header>{include file="navigation.tpl" array=$head}</header><aside>{include file="navigation.tpl" array=$main}</aside><footer>{include file="navigation.tpl" array=$footer}</footer>Cada archivo JSON es pequeño, con un alcance limitado, fácil de editar y libre de conflictos en las solicitudes de pull (PR).
5. Puntos finales JSON con forwardToJsonHeader
Sección titulada «5. Puntos finales JSON con forwardToJsonHeader»Contrato estándar para AJAX:
public function searchAction() { View::forwardToJsonHeader(); $q = trim($_REQUEST['q'] ?? ''); if (strlen($q) < 2) { View::assign(['data' => ['results' => []]]); return; } View::assign(['data' => [ 'results' => MachineryScout::index()->search($q), ]]);}Los encabezados se establecen automáticamente; no se requiere un header('Content-Type: application/json') manual.
6. Flujo de trabajo multi-etapa a través de acciones
Sección titulada «6. Flujo de trabajo multi-etapa a través de acciones»Máquinas de estado mapeadas en acciones del controlador:
class quotesController extends Controller { public function pageAction() { /* list view */ } public function detailAction() { /* one quote */ } public function acceptAction() { /* state transition: open → accepted */ } public function rejectAction() { /* state transition: open → rejected */ } public function archiveAction() { /* state transition: any → archived */ }}/quotes/accept/42 ejecuta acceptAction() con $_REQUEST['id'] = 42. Cada transición es una pequeña acción; la persistencia y la notificación pasan por un complemento de QuotesService.
7. Modelos basados en esquema con un método personalizado por intención
Sección titulada «7. Modelos basados en esquema con un método personalizado por intención»Genera el modelo a partir del esquema, luego agrega métodos con nombres de intención que envuelvan tus consultas:
class users extends Db{ const TABLE = ['table' => 'users', 'field' => [/* … */]]; public function __construct() { self::initTable(self::TABLE); }
public function findByLogin(string $login): ?array { return Pdo::fetchRow('SELECT * FROM users WHERE user_login = :l', [':l' => $login]) ?: null; }
public function activeStandardUsers(): array { return Pdo::fetchAll( 'SELECT * FROM users WHERE user_account_active = 1 AND user_role = :r', [':r' => 'standard'] ); }}El futuro tú que lee el sitio de llamada ve findByLogin($login) — la intención — no SQL crudo.
Patrones anti a evitar
Sección titulada «Patrones anti a evitar»- Fuga de búfer estático en formularios. Siempre use
Form::create()antes de construir. - Lógica en
navigationAction(). Se ejecuta en cada solicitud, incluyendo puntos finales JSON. - Asignación masiva de
View::assign()sin un array estructurado. UseView::assign(['…'])una vez. - Rutas personalizadas para lo que ya hace la URL SEO.
/products/<slug>/<id>está disponible. - Edición de modelos generados. Se sobrescriben. Métodos personalizados → clase hija o
database.overwrite = false.