User Tools

Site Tools


nvg-cockpit:dxo-base-model

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
nvg-cockpit:dxo-base-model [2025/11/11 17:08] – [Quick facts] adminnvg-cockpit:dxo-base-model [Unknown date] (current) – removed - external edit (Unknown date) 127.0.0.1
Line 1: Line 1:
-====== _DXOBaseModel (PHP) ====== 
-Generic Base Model providing shared ORM-like functionality. 
- 
-  * **Author:** dynaxio AG 
-  * **Created:** 2025-11-06 
-  * **Updated:** 2025-11-11 
-  * **Version:** 1.4.1 
-  * **License:** See ''LICENSE.md'' in the project root. 
- 
-Provides dynamic getters/setters, automatic persistence, per-class caching, **consistency-edges** registration, and **policy-aware deletion** via ''_destroyMySelf()''.   
-Edges and policies may use ''\Lib\GlobalOptions::*'' identifiers for table names (resolved to physical tables at runtime). 
- 
----- 
- 
- 
- 
- 
- 
-===== Quick facts ===== 
- 
-  * **Constructor:** <code php>__construct(int $id = 0, array $initial = [])</code> 
-  * **Primary key:** <code php>$id</code> (int) 
-  * **Table:** child classes set <code php>protected ?string $table</code> 
-  * **Caching:** per-class, per-id in-memory cache (TTL = 10s) 
-  * **Consistency:** outbound edges are (re)registered on every save 
-  * **Deletion:** always call <code php>_destroyMySelf()</code> → routes through Consistency Manager (policy-aware) 
-  * **GlobalOptions:** accepts <code php>GlobalOptions::XYZ</code> or <code php>\Lib\GlobalOptions::XYZ</code> → resolved to raw table names 
-  *  
----- 
- 
-===== Example: minimal child model ===== 
-<code php> 
-namespace Lib\Model; 
- 
-use Lib\_DXOBaseModel; 
- 
-class Partner extends _DXOBaseModel 
-{ 
-    protected ?string $table = null; 
- 
-    // Outbound relations from Partner rows 
-    protected array $edgesMap = [ 
-        'owner_user_id' => [ 
-            'relation'   => 'partner:owner', 
-            'dst_table'  => 'GlobalOptions::CPTDB_TBL_USERS', 
-        ], 
-        'account_key' => [ 
-            'relation'   => 'partner:account', 
-            'dst_table'  => 'GlobalOptions::CPTDB_TBL_ACCOUNTS', 
-            'resolve_by' => 'uniqueid', // external key → accounts.id 
-        ], 
-    ]; 
- 
-    public function __construct(int $id = 0, array $initial = []) 
-    { 
-        $this->table = \Lib\GlobalOptions::CPTDB_TBL_PARTNERS(); 
-        parent::__construct($id, $initial); 
-    } 
-} 
-</code> 
- 
----- 
- 
-===== Properties ===== 
-^ Property ^ Type ^ Purpose ^ 
-| ''protected \PDO $pdo'' | PDO | Shared DB handle. | 
-| ''protected ?Utils $utils'' | Utils\|null | Logger & helpers. | 
-| ''public int $id'' | int | Primary key. | 
-| ''public array $record'' | array | Full row (assoc). | 
-| ''protected ?string $table'' | string\|null | Physical table name (child sets). | 
-| ''protected ?array $allowedFields'' | string[]\|null | Optional whitelist for setters. | 
-| ''protected array $readOnlyFields'' | string[] | Defaults to ''[''id'']''. | 
-| ''protected array $validators'' | array<string,callable> | ''fn($val,$field,$this):bool''. | 
-| ''protected array $edgesMap'' | array | Outbound edges (field → relation config). | 
-| ''protected const RECORD_CACHE_TTL'' | int | Cache TTL (default 10s). | 
-| ''protected static array $recordCache'' | array | In-memory cache by class/id. | 
-| ''protected ?string $tableSpec'' | string\|null | Optional canonical source spec (e.g. ''GlobalOptions::XYZ''). | 
- 
----- 
- 
-===== Construction lifecycle ===== 
-<code php> 
-public function __construct(int $id = 0, array $initial = []) 
-</code> 
-* If ''$id > 0'': loads the record. 
-* If ''$id == 0'': creates an empty row (''insertEmptyRow()''), then loads. 
-* Populates ''$this->record'' and ''$this->id''. 
- 
-''insertEmptyRow()'' uses an empty insert: 
- 
-<code php> 
-INSERT INTO `<table>` () VALUES () 
-</code> 
- 
-On success, the new ''id'' is stored and the record is reloaded. 
- 
----- 
- 
-===== Magic accessors ===== 
-Use dynamic getters/setters: ''getXxx()'' / ''setXxx($val)''. ''Xxx'' is converted to ''snake_case''. 
- 
-<code php> 
-$partner = new Partner(123); 
-$name    = $partner->getName();      // reads $record['name'] 
-$ok      = $partner->setName('ACME'); // validates, saves, re-registers edges 
-</code> 
- 
-Guards: 
-* ''$allowedFields'' (whitelist) and ''$readOnlyFields'' (defaults to ''id'') are enforced. 
-* Per-field validators can be added via ''$validators['field'] = fn($val,$field,$self) => bool''. 
- 
----- 
- 
-===== Persistence ===== 
-==== Save ==== 
-<code php> 
-protected function _save(): false|int 
-</code> 
-* If the row exists → ''UPDATE'' changed fields. 
-* If not → ''INSERT'' (or empty insert when ''$record'' is empty). 
-* On success: 
-  * invalidates the cache, 
-  * **registers outbound edges**, 
-  * returns ''id''. 
- 
-==== Load (with cache) ==== 
-<code php> 
-protected function _load(): false|array 
-</code> 
-* Per-class, per-id cache with TTL (''RECORD_CACHE_TTL''). 
-* Falls back to DB if expired/missing. 
- 
----- 
- 
-===== Table name resolution ===== 
-<code php> 
-protected function fetchTableName(): string|false 
-protected function resolveTableName(string $maybe): string 
-</code> 
- 
-* ''fetchTableName()'': returns ''$this->table'', logs error if missing. 
-* ''resolveTableName()'': accepts ''GlobalOptions::X'' or ''\Lib\GlobalOptions::X'' and calls the static method to resolve to a **raw** table name; otherwise returns input. 
- 
----- 
- 
-===== Consistency edges ===== 
-Outbound edges are derived from ''$edgesMap'' and the current ''$record''. 
- 
-==== edgesFromSelf() ==== 
-<code php> 
-public function edgesFromSelf(): array 
-</code> 
- 
-Returns an array of edges: 
- 
-<code> 
-[ 
-  'relation'   => string, 
-  'dst_table'  => string,     // canonical: 'GlobalOptions::XYZ' or raw table 
-  'dst_id'     => string,     // left as provided (string or numeric) 
-  'resolve_by' => string|null,// e.g. 'uniqueid' 
-  'meta'       => array, 
-] 
-</code> 
- 
-Notes: 
-* ''dst_id'' is cast to **string** to allow unique external keys. 
-* If ''resolve_by'' is present, the manager may resolve the key → numeric ''id'' before storing the edge. 
- 
-==== registerConsistencyEdges() ==== 
-<code php> 
-protected function registerConsistencyEdges(): void 
-</code> 
-* Skips if ''\Lib\Model\_ConsistencyManager'' is not available. 
-* Determines ''$srcSpec'' (''$this->tableSpec'' if set, else physical table). 
-* Removes all existing outbound edges for ''(src_table, src_id)''. 
-* Recreates edges via ''ensureLink()'' and forwards ''resolve_by'' when configured. 
- 
----- 
- 
-===== Policy-aware deletion ===== 
-<code php> 
-public function _destroyMySelf(): bool 
-</code> 
-* Validates ''id'' and ''table''. 
-* If ''\Lib\Model\_ConsistencyManager'' exists, delegates: 
-  <code php> 
-  $mgr->deleteNode(['table' => $this->table, 'id' => $this->id]); 
-  </code> 
-* The manager reads ''ConsistencyPolicy::map()'' and applies **RESTRICT / NULLIFY / CASCADE / DETACH**. 
-* Fallback: hard delete (not recommended). 
- 
-**Always delete via** ''_destroyMySelf()''. 
- 
----- 
- 
-===== Utilities ===== 
-<code php> 
-public function getId(): int 
-public function invalidateRecordCache(): void 
-protected function toSnakeCase(string $name): string 
-</code> 
- 
----- 
- 
-===== Logging & error handling ===== 
-* No exceptions are thrown to the caller in normal flow; failures are logged via ''Utils->log(level)'' with levels like ''debug'', ''warning'', ''error''. 
-* Methods return ''false'' on error (or ''0'' for IDs), enabling simple guard patterns. 
- 
----- 
- 
-===== Worked snippets ===== 
-**Create, set, save** 
-<code php> 
-$p = new Partner(0, ['name' => 'Init']); 
-$p->setName('ACME SA');        // UPDATE (after empty insert), edges re-registered 
-$id = $p->getId(); 
-</code> 
- 
-**Policy-aware delete** 
-<code php> 
-$p = new Partner(123); 
-if (!$p->_destroyMySelf()) { 
-    // Deletion was restricted by policy (see logs) 
-} 
-</code> 
- 
-**Custom validator** 
-<code php> 
-$this->validators['email'] = function($val) { 
-    return filter_var($val, FILTER_VALIDATE_EMAIL) !== false; 
-}; 
-</code> 
- 
-**Restrict writable fields** 
-<code php> 
-protected ?array $allowedFields = ['name','email','owner_user_id']; 
-protected array  $readOnlyFields = ['id','created_at']; 
-</code> 
- 
----- 
- 
-===== Interop with Consistency Manager ===== 
-* ''edgesFromSelf()'' emits canonical ''dst_table'' specs (''GlobalOptions::XYZ'' or raw). 
-* ''registerConsistencyEdges()'' calls ''_ConsistencyManager::ensureLink()'': 
-  * Resolves ''GlobalOptions'' → **raw tables** before insert. 
-  * Optionally resolves destination by ''resolve_by'' (e.g. ''uniqueid'') to numeric ''id''. 
-  * Stores edges with ''src_id'' (int) and ''dst_id'' (string or resolved numeric string). 
- 
-See the **Consistency Manager** page for schema, policies, and the reindex CLI. 
- 
----- 
- 
-===== Changelog ===== 
-  * **1.4.1 (2025-11-11):** Restore ''insertEmptyRow()'' and fix method name. 
-  * **1.4.0 (2025-11-11):** Add GlobalOptions table-name resolver for edges/policy. 
-  * **1.3.0 (2025-11-11):** Single-method delete approach (always call ''_destroyMySelf()''). 
-  * **1.2.0 (2025-11-11):** Edges + policy-aware ''_destroyMySelf()''. 
-  * **1.1.0 (2025-11-06):** Convert to non-exception flow using ''Utils->log()''. 
-  * **1.0.0 (2025-11-06):** Created. 
-