User Tools

Site Tools


nvg-cockpit:dxo-base-model

This is an old revision of the document!


_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: construct(int $id = 0, array $initial = []) * Primary key: id (int) * Table: child classes set protected ?string $table * Caching: per-class, per-id in-memory cache (TTL = 10s) * Consistency: outbound edges are (re)registered on every save * Deletion: always call \_destroyMySelf() – routes through Consistency Manager (policy-aware) * GlobalOptions: accepts GlobalOptions::XYZ or \Lib\GlobalOptions::XYZ – 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 GlobalOptionsraw 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.
nvg-cockpit/dxo-base-model.1762880666.txt.gz · Last modified: by admin