Ir al contenido
Nibiru docsv0.9.2

Patrones de Producción

Patrones concretos utilizados en las aplicaciones de Nibiru en producción — listo para copiar y pegar.

Stable Reading time ~ 3 min Edit on GitHub

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.

// thin
class erpController extends Controller {
public function syncAction(): void {
View::forwardToJsonHeader();
$result = \Nibiru\Module\Erp\Plugin\Sync::run();
View::assign(['data' => $result]);
}
}
``````php
// fat
class 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.

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>

Varios rastreadores sin acoplamiento con el controlador.

$analytics = new Analytics();
$analytics->attach(new Plugin\Matomo());
$analytics->attach(new Plugin\Plausible());
$analytics->trackPageView(); / calls notify() internally

Cada observador extrae solo los campos que le interesan con su update($subject). Agregar un rastreador es un cambio de una línea.

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.

  • 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. Use View::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.