コンテンツにスキップ
Nibiru docsv0.9.2

生産からのパターン

Nibiruアプリケーションで使用されている具体的なパターン — コピーペースト可能なものです。

Stable Reading time ~ 2 min Edit on GitHub

ニブルの生産アプリケーションで繰り返し現れる7つのパターン。それぞれが小さく、コピー&ペーストできるもので、実際のコードベースに基づいています。

1. 細かいコントローラー → モジュールプラグインの委譲

Section titled “1. 細かいコントローラー → モジュールプラグインの委譲”

コントローラーを小さく保ちましょう。ロジックはモジュールプラグインに押し込んでください。

// 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()];
}
}
}

コントローラーは5秒でレビューできます;プラグインは単体テスト可能です。

2. コンテンツソースとしてのCMS

Section titled “2. コンテンツソースとしてのCMS”

テキストとレイアウトを分離します。エディターはCMSモジュールのUI経由でコピーアップデートを行います;テンプレートは開発者所有のままです。

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']
]);
}
}

テンプレートは識別子を通常の変数のように参照します。

<h1>{$hero_title}</h1>
<p>{$hero_intro}</p>

複数のトラッカーとコントローラーの結合なし。

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

各観察者の update($subject) は、関心のあるフィールドのみを取得します。トラッカーの追加は1行の変更です。

複数の名前付きナビゲーション配列を使用してページを構築する代わりに、1つのモノリス構造を使用します。

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>

各 JSON ファイルは小さく、スコープが明確で、編集しやすいです。また、PR での競合もありません。

5. JSON エンドポイントと forwardToJsonHeader

Section titled “5. JSON エンドポイントと forwardToJsonHeader”

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),
]]);
}

ヘッダーは自動的に設定されます;手動で header('Content-Type: application/json') を行わないでください。

6. アクションを経由したマルチステージワークフロー

Section titled “6. アクションを経由したマルチステージワークフロー”

コントローラーアクションにマッピングされたステートマシン:

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/42acceptAction() を実行し、$_REQUEST['id'] = 42 です。各トランジションは小さなアクションであり、永続化と通知は QuotesService プラグインを介して行われます。

7. スキーマファーストモデルと各インテントごとの1つのカスタムメソッド

Section titled “7. スキーマファーストモデルと各インテントごとの1つのカスタムメソッド”

スキーマからモデルを生成し、その後クエリをラップする意図名のメソッドを追加します。

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']
);
}
}

将来的な自分は、findByLogin($login) — 意図 — ではなく生のSQLを読むことになります。

  • フォームでの静的バッファリー漏洩。 常に Form::create() を実行してから構築してください。
  • navigationAction() 内のロジック。 すべてのリクエストで実行され、JSON エンドポイントも含まれます。
  • 構造化された配列なしでの大量の View::assign() 一度に View::assign(['…']) を使用してください。
  • SEO URL がすでに処理するカスタムルート。 /products/<slug>/<id> は無料です。
  • 生成されたモデルの編集。 それらは上書きされます。カスタムメソッド → 子クラスまたは database.overwrite = false