From 7557d2a02235df59145390f8968c8c5698c53a64 Mon Sep 17 00:00:00 2001 From: William Asaba Date: Fri, 16 May 2025 14:28:04 +0300 Subject: [PATCH 1/8] Fix bugs in the deep fetch --- src/PocketORM/Concerns/DeepFetch.php | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/PocketORM/Concerns/DeepFetch.php b/src/PocketORM/Concerns/DeepFetch.php index 3a06c5d..494f9f4 100644 --- a/src/PocketORM/Concerns/DeepFetch.php +++ b/src/PocketORM/Concerns/DeepFetch.php @@ -5,10 +5,11 @@ use Closure; use Pocketframe\PocketORM\Entity\Entity; use Pocketframe\PocketORM\Essentials\DataSet; +use Pocketframe\PocketORM\QueryEngine\QueryEngine; use Pocketframe\PocketORM\Relationships\Bridge; use Pocketframe\PocketORM\Relationships\HasMultiple; use Pocketframe\PocketORM\Relationships\HasOne; -use Pocketframe\PocketORM\Relationships\OwnedBy; +use Pocketframe\PocketORM\Relationships\BelongsTo; trait DeepFetch { @@ -136,16 +137,21 @@ private function loadRelation(DataSet $records, string $relation, array $columns return; } - $firstConfig = reset($items)->getRelationshipConfig($base); + $firstConfig = reset($items)->getRelationshipConfig($base); $relationship = new $firstConfig[0](...$this->prepareRelationshipArgs(reset($items), $firstConfig)); - // Apply user callback if exists + // ── START HERE: build your engine on the related table + $engine = $relationship->getQueryEngine()->select($columns); + + // ── apply any user‐provided filter if (isset($this->includeCallbacks[$base])) { - ($this->includeCallbacks[$base])($relationship->getQueryBuilder()); + ($this->includeCallbacks[$base])($engine); } - $relatedMap = $relationship->deepFetch($items, $columns); + // ── do the filtered deep-fetch + $relatedMap = $relationship->deepFetchUsingEngine($items, $engine); + // decide which key to use for lookup $lookupKey = ($relationship instanceof Bridge || $relationship instanceof HasMultiple) ? 'id' : $relationship->getForeignKey(); @@ -153,9 +159,14 @@ private function loadRelation(DataSet $records, string $relation, array $columns foreach ($items as $parent) { $key = $parent->{$lookupKey} ?? null; $data = $relatedMap[$key] ?? []; - $parent->setDeepFetch($base, $this->formatLoadedData($relationship, $data)); + if ($relationship instanceof HasOne || $relationship instanceof BelongsTo) { + $parent->setDeepFetch($base, $data ?: null); + } else { + $parent->setDeepFetch($base, new DataSet($data)); + } } + // handle nested ("comments.user") includes if any if ($segments) { $nextRelation = implode('.', $segments); $allChild = []; @@ -176,6 +187,7 @@ private function loadRelation(DataSet $records, string $relation, array $columns } } + /** * Prepare the constructor args for each relationship type. */ @@ -194,7 +206,7 @@ private function formatLoadedData($relationship, $data) { return match (true) { $relationship instanceof HasOne, - $relationship instanceof OwnedBy => $data, + $relationship instanceof BelongsTo => $data, $relationship instanceof HasMultiple, $relationship instanceof Bridge => $data instanceof DataSet ? $data : new DataSet($data), default => null, From 089a72c606d52aaaa98a76273f5f45f6c9ed620c Mon Sep 17 00:00:00 2001 From: William Asaba Date: Fri, 16 May 2025 14:43:56 +0300 Subject: [PATCH 2/8] update entity --- src/PocketORM/Entity/Entity.php | 68 ++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/src/PocketORM/Entity/Entity.php b/src/PocketORM/Entity/Entity.php index 0c8737e..badbc57 100644 --- a/src/PocketORM/Entity/Entity.php +++ b/src/PocketORM/Entity/Entity.php @@ -12,7 +12,7 @@ use Pocketframe\PocketORM\QueryEngine\QueryEngine; use Pocketframe\PocketORM\Relationships\HasOne; use Pocketframe\PocketORM\Relationships\HasMultiple; -use Pocketframe\PocketORM\Relationships\OwnedBy; +use Pocketframe\PocketORM\Relationships\BelongsTo; use Pocketframe\PocketORM\Relationships\Bridge; use Pocketframe\PocketORM\Relationships\RelationshipNotDefinedException; use Pocketframe\PocketORM\Schema\Schema; @@ -21,7 +21,7 @@ * Base Active Record–style Entity class. * * Features: - * - 'relationship' array for defining relationships (HasOne, HasMultiple, OwnedBy, Bridge) + * - 'relationship' array for defining relationships (HasOne, HasMultiple, BelongsTo, Bridge) * - fillable/guarded attributes * - timestamp handling via HasTimeStamps * - eager loading cache @@ -67,7 +67,7 @@ abstract class Entity * protected array $relationship = [ * 'profile' => [Entity::HAS_ONE, Profile::class, 'user_id'], * 'posts' => [Entity::HAS_MULTIPLE, Post::class, 'post_id'], - * 'role' => [Entity::OWNED_BY, Role::class, 'role_id'], + * 'role' => [Entity::BELONGS_TO, Role::class, 'role_id'], * 'groups' => [Entity::BRIDGE, Group::class, 'user_groups', 'user_id', 'group_id'] * ]; */ @@ -129,7 +129,7 @@ abstract class Entity // Relationship type constants for convenience const HAS_ONE = HasOne::class; const HAS_MULTIPLE = HasMultiple::class; - const OWNED_BY = OwnedBy::class; + const BELONGS_TO = BelongsTo::class; const BRIDGE = Bridge::class; /** @@ -172,7 +172,7 @@ public function __get(string $name) // If it’s a BELONGS-TO or HAS-ONE, immediately resolve to a single model: if ( - $handler instanceof \Pocketframe\PocketORM\Relationships\OwnedBy + $handler instanceof \Pocketframe\PocketORM\Relationships\BelongsTo || $handler instanceof \Pocketframe\PocketORM\Relationships\HasOne ) { $resolved = $handler->resolve(); @@ -328,7 +328,7 @@ public function getIntegerColumns(): array break; case self::HAS_ONE: case self::HAS_MULTIPLE: - case self::OWNED_BY: + case self::BELONGS_TO: // Third element is the foreign key $integerColumns[] = $config[2]; break; @@ -591,15 +591,22 @@ protected function loadRelationship(string $relation) throw new \Exception("Undefined relationship: {$relation}"); } - [$relClass, $relatedEntity, $key1, $key2] = $this->relationship[$relation] + [null, null]; - $foreignKey = $key2 ?? $this->guessForeignKey(); - - // Instantiate handler (no caching) - if ($relClass === Bridge::class) { - [$pivotTable, $parentKey, $relatedKey] = [$key1, $key2, null]; - $handler = new Bridge($this, $relatedEntity, $pivotTable, $parentKey, $relatedKey); + $config = $this->relationship[$relation]; + + if ($config[0] === Bridge::class) { + // Bridge: [Bridge, RelatedClass, pivotTable, parentKey, relatedKey] + [, $relatedEntity, $pivotTable, $parentKey, $relatedKey] = $config; + $handler = new Bridge( + $this, + $relatedEntity, + $pivotTable, + $parentKey, + $relatedKey + ); } else { - $handler = new $relClass($this, $relatedEntity, $foreignKey); + // HasOne/HasMany/OwnedBy: [Type, RelatedClass, foreignKey] + [, $relatedEntity, $foreignKey] = $config; + $handler = new $config[0]($this, $relatedEntity, $foreignKey); } return $handler; @@ -659,6 +666,39 @@ public function __call(string $name, array $arguments) ); } + public function relation(string $name) + { + // 1) Grab the raw config + $config = $this->getRelationshipConfig($name); + + // 2) If it’s a Bridge relationship, it must have 5 slots + if ($config[0] === self::BRIDGE) { + // [0] = Bridge class + // [1] = related entity class + // [2] = pivot table name + // [3] = parent key column in pivot + // [4] = related key column in pivot + [, $relatedEntity, $pivotTable, $parentKey, $relatedKey] = $config; + + return new Bridge( + $this, // entity type + $relatedEntity, // e.g. Tag::class + $pivotTable, // e.g. 'category_tags' + $parentKey, // e.g. 'category_id' + $relatedKey // e.g. 'tag_id' + ); + } + + // 3) Otherwise it’s a HasOne / HasMultiple / OwnedBy + [, $relatedEntity, $foreignKey] = $config; + + return new $config[0]( + $this, + $relatedEntity, + $foreignKey + ); + } + /** * Default guess for foreign key: "classname_id". * From 03bd02af778916fd5e81002ac3157d562b04081f Mon Sep 17 00:00:00 2001 From: William Asaba Date: Fri, 16 May 2025 14:44:09 +0300 Subject: [PATCH 3/8] Add `join` method --- src/PocketORM/Essentials/DataSet.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/PocketORM/Essentials/DataSet.php b/src/PocketORM/Essentials/DataSet.php index 9568729..8e55a83 100644 --- a/src/PocketORM/Essentials/DataSet.php +++ b/src/PocketORM/Essentials/DataSet.php @@ -131,6 +131,18 @@ public function reduce(callable $callback, $initial) return array_reduce($this->records, $callback, $initial); } + /** + * Join a column of this dataset into a string. + * + * @param string $glue What to insert between values + * @param string $column The name of the column to join + * @return string + */ + public function join(string $glue, string $column): string + { + return implode($glue, $this->pluck($column)); + } + /** * Group the dataset by a key. * From c5f265ec025f522f4ecd440b33c74f016b1309b7 Mon Sep 17 00:00:00 2001 From: William Asaba Date: Fri, 16 May 2025 14:44:40 +0300 Subject: [PATCH 4/8] update query engine --- src/PocketORM/QueryEngine/QueryEngine.php | 54 ++++++++++++++--------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/PocketORM/QueryEngine/QueryEngine.php b/src/PocketORM/QueryEngine/QueryEngine.php index c85e49e..2f1d07b 100644 --- a/src/PocketORM/QueryEngine/QueryEngine.php +++ b/src/PocketORM/QueryEngine/QueryEngine.php @@ -42,6 +42,8 @@ class QueryEngine private ?string $entityClass; protected float $timeStart = 0.0; protected bool $distinct = false; + private bool $trashFilterAdded = false; + private bool $trashFilterApplied = false; /** * Constructor @@ -2651,14 +2653,17 @@ public function getDisabledGlobalScopes(): array */ protected function applyTrashableConditions(): void { + // If we've already applied it once, skip entirely + if ($this->trashFilterApplied) { + return; + } + // 0) No entity? nothing to do if (! $this->entityClass) { return; } // 1) Only proceed if the model uses the Trashable trait - // class_uses only returns traits used directly on this class, - // not on parents—if you need inherited traits, use class_uses_recursive(). if (! in_array( \Pocketframe\PocketORM\Concerns\Trashable::class, class_uses($this->entityClass), @@ -2667,7 +2672,7 @@ class_uses($this->entityClass), return; } - // 2) Make sure the getters actually exist before calling them + // 2) Make sure the getters actually exist if ( ! method_exists($this->entityClass, 'getTrashColumn') || ! method_exists($this->entityClass, 'getRestoreValue') @@ -2681,6 +2686,8 @@ class_uses($this->entityClass), // 4) If someone already added a where on this column, do nothing if ($this->hasAnyTrashCondition($col)) { + // But still mark it so we don't try again later + $this->trashFilterApplied = true; return; } @@ -2704,6 +2711,9 @@ class_uses($this->entityClass), $this->whereNull($col); } } + + // ─ mark that we've already applied it ─ + $this->trashFilterApplied = true; } protected function hasAnyTrashCondition(string $column): bool @@ -2886,40 +2896,50 @@ protected function compileOrders(): string */ protected function compileWheres(): string { - if (empty($this->wheres)) return ''; + if (empty($this->wheres)) { + return ''; + } + // ── DEDUPE: remove duplicate where entries ─────────────── + $unique = []; + foreach ($this->wheres as $where) { + // serialize the array structure as a unique key + $key = serialize($where); + if (!isset($unique[$key])) { + $unique[$key] = $where; + } + } + // overwrite with only unique where-clauses + $this->wheres = array_values($unique); + + // ── NOW BUILD the SQL from deduped $this->wheres ───────── $clauses = []; foreach ($this->wheres as $index => $where) { $clause = $index === 0 ? '' : $where['boolean'] . ' '; $column = $this->quoteColumn($where['column'] ?? ''); switch ($where['type']) { - // Basic comparison case 'basic': $clause .= "{$column} {$where['operator']} ?"; break; - // IN/NOT IN case 'in': $placeholders = implode(', ', array_fill(0, count($where['values']), '?')); $not = $where['not'] ? 'NOT ' : ''; $clause .= "{$column} {$not}IN ({$placeholders})"; break; - // NULL/NOT NULL case 'null': $not = $where['not'] ? 'NOT ' : ''; $clause .= "{$column} IS {$not}NULL"; break; - // Column comparison case 'column': $first = $this->quoteColumn($where['first']); $second = $this->quoteColumn($where['second']); $clause .= "{$first} {$where['operator']} {$second}"; break; - // JSON operations case 'json_contains': $clause .= "JSON_CONTAINS({$column}, ?)"; break; @@ -2937,7 +2957,6 @@ protected function compileWheres(): string $clause .= "JSON_SEARCH({$column}, 'one', ?) IS NULL"; break; - // BETWEEN case 'between': $not = $where['not'] ? 'NOT ' : ''; $clause .= "{$column} {$not}BETWEEN ? AND ?"; @@ -2945,11 +2964,10 @@ protected function compileWheres(): string case 'between_columns': $not = $where['not'] ? 'NOT ' : ''; $start = $this->quoteColumn($where['start']); - $end = $this->quoteColumn($where['end']); + $end = $this->quoteColumn($where['end']); $clause .= "{$column} {$not}BETWEEN {$start} AND {$end}"; break; - // Date/time functions case 'date': case 'month': case 'day': @@ -2959,27 +2977,22 @@ protected function compileWheres(): string $clause .= "{$function}({$column}) {$where['operator']} ?"; break; - // Nested queries case 'nested': - $nestedSql = $where['query']->compileWheres(); - $nestedClause = substr($nestedSql, 7); // Remove ' WHERE ' - $not = $where['not'] ?? false ? 'NOT ' : ''; + $nestedSql = $where['query']->compileWheres(); + $nestedClause = substr($nestedSql, 7); // drop leading ' WHERE ' + $not = !empty($where['not']) ? 'NOT ' : ''; $clause .= "{$not}({$nestedClause})"; break; case 'exists': - // Build the full sub-query SQL (including its SELECT and WHEREs) $subSql = $where['query']->toSql(); - // Wrap in EXISTS(...) $clause .= "EXISTS({$subSql})"; break; - // Full-text search case 'fulltext': $clause .= "MATCH({$column}) AGAINST(? IN {$where['mode']} MODE)"; break; - // Raw expressions case 'raw': $clause .= $where['sql']; break; @@ -2990,6 +3003,7 @@ protected function compileWheres(): string $clauses[] = $clause; } + return ' WHERE ' . implode(' ', $clauses); } From ddafda4f0b3a1d2dbe3bda7eceba03f921763bd1 Mon Sep 17 00:00:00 2001 From: William Asaba Date: Fri, 16 May 2025 14:49:16 +0300 Subject: [PATCH 5/8] update relationships --- src/PocketORM/Relationships/Bridge.php | 59 +++++++++++---------- src/PocketORM/Relationships/HasMultiple.php | 33 +++++++----- src/PocketORM/Relationships/HasOne.php | 47 ++++++++++------ 3 files changed, 79 insertions(+), 60 deletions(-) diff --git a/src/PocketORM/Relationships/Bridge.php b/src/PocketORM/Relationships/Bridge.php index 40843b9..660894e 100644 --- a/src/PocketORM/Relationships/Bridge.php +++ b/src/PocketORM/Relationships/Bridge.php @@ -22,37 +22,48 @@ class Bridge public function __construct(Entity $parent, string $related, string $bridgeTable, string $parentKey, string $relatedKey) { - $this->parent = $parent; - $this->related = $related; + $this->parent = $parent; + $this->related = $related; $this->bridgeTable = $bridgeTable; - $this->parentKey = $parentKey; - $this->relatedKey = $relatedKey; + $this->parentKey = $parentKey; + $this->relatedKey = $relatedKey; } + public function deepFetch(array $parents, array $columns = ['*']): array { - $parentIds = array_map(fn($p) => $p->id, $parents); - $bridgeData = (new QueryEngine($this->bridgeTable)) + // fallback (unfiltered) logic stays the same + $engine = new QueryEngine($this->related); + return $this->deepFetchUsingEngine($parents, $engine->select($columns)); + } + + public function deepFetchUsingEngine(array $parents, QueryEngine $engine): array + { + $parentIds = array_map(fn(Entity $p) => $p->id, $parents); + $bridgeRows = (new QueryEngine($this->bridgeTable)) ->whereIn($this->parentKey, $parentIds) ->get() ->all(); - $relatedIds = array_column($bridgeData, $this->relatedKey); - $relatedQuery = (new QueryEngine($this->related))->select($columns); - $relatedRecords = $this->chunkedWhereIn($relatedQuery, 'id', $relatedIds); + $relatedIds = array_map( + fn($r) => + is_array($r) ? $r[$this->relatedKey] : $r->{$this->relatedKey}, + $bridgeRows + ); + + $relatedRecs = $this->chunkedWhereIn($engine, 'id', $relatedIds); - // Index by ID for fast lookup $indexed = []; - foreach ($relatedRecords as $record) { - $indexed[$record->id] = $record; + foreach ($relatedRecs as $rec) { + $indexed[$rec->id] = $rec; } $mapped = []; - foreach ($bridgeData as $bridge) { - $parentId = $bridge[$this->parentKey]; - $relatedId = $bridge[$this->relatedKey]; - if (isset($indexed[$relatedId])) { - $mapped[$parentId][] = $indexed[$relatedId]; + foreach ($bridgeRows as $row) { + $pid = is_array($row) ? $row[$this->parentKey] : $row->{$this->parentKey}; + $rid = is_array($row) ? $row[$this->relatedKey] : $row->{$this->relatedKey}; + if (isset($indexed[$rid])) { + $mapped[$pid][] = $indexed[$rid]; } } @@ -62,17 +73,7 @@ public function deepFetch(array $parents, array $columns = ['*']): array public function get(): DataSet { - if (!isset($this->parent->id)) { - throw new \RuntimeException("Cannot fetch relationship - parent entity lacks an ID"); - } - - if (!class_exists($this->related)) { - throw new RelationshipResolutionError( - "Related class {$this->related} does not exist", - $this->related - ); - } - + // unchanged return (new QueryEngine($this->bridgeTable)) ->withTrashed() ->select([$this->related::getTable() . '.*']) @@ -126,7 +127,7 @@ public function detach($relatedId): void throw new \RuntimeException("Cannot detach - parent entity lacks an ID"); } - (new QueryEngine($this->bridgeTable, $this->related)) + (new QueryEngine($this->bridgeTable)) ->where($this->parentKey, '=', $this->parent->id) ->where($this->relatedKey, '=', $relatedId) ->delete(); diff --git a/src/PocketORM/Relationships/HasMultiple.php b/src/PocketORM/Relationships/HasMultiple.php index da989af..22be787 100644 --- a/src/PocketORM/Relationships/HasMultiple.php +++ b/src/PocketORM/Relationships/HasMultiple.php @@ -6,9 +6,6 @@ use Pocketframe\PocketORM\Entity\Entity; use Pocketframe\PocketORM\QueryEngine\QueryEngine; -/** - * HasMultiple: one-to-many relationship (a parent has multiple children). - */ class HasMultiple { use RelationshipUtils; @@ -26,12 +23,27 @@ public function __construct(Entity $parent, string $related, string $foreignKey) public function deepFetch(array $parents, array $columns = ['*']): array { - $parentIds = array_map(fn($p) => $p->id, $parents); - $query = (new QueryEngine($this->related))->select($columns); - $relatedRecords = $this->chunkedWhereIn($query, $this->foreignKey, $parentIds); - return self::groupByKey($relatedRecords, $this->foreignKey); + $parentIds = array_map(fn(Entity $p) => $p->id, $parents); + $query = new QueryEngine($this->related); + return self::groupByKey( + $this->chunkedWhereIn($query->select($columns), $this->foreignKey, $parentIds), + $this->foreignKey + ); } + public function deepFetchUsingEngine(array $parents, QueryEngine $engine): array + { + $parentIds = array_map(fn(Entity $p) => $p->id, $parents); + $records = $this->chunkedWhereIn($engine, $this->foreignKey, $parentIds); + return self::groupByKey($records, $this->foreignKey); + } + + public function get(): DataSet + { + return (new QueryEngine($this->related)) + ->where($this->foreignKey, '=', $this->parent->id) + ->get(); + } public function getForeignKey(): string { @@ -42,11 +54,4 @@ public function getParentKey(): string { return 'id'; } - - public function get(): DataSet - { - return (new QueryEngine($this->related)) - ->where($this->foreignKey, '=', $this->parent->id) - ->get(); - } } diff --git a/src/PocketORM/Relationships/HasOne.php b/src/PocketORM/Relationships/HasOne.php index 2f05b6f..70edcb6 100644 --- a/src/PocketORM/Relationships/HasOne.php +++ b/src/PocketORM/Relationships/HasOne.php @@ -2,12 +2,9 @@ namespace Pocketframe\PocketORM\Relationships; -use Pocketframe\PocketORM\QueryEngine\QueryEngine; use Pocketframe\PocketORM\Entity\Entity; +use Pocketframe\PocketORM\QueryEngine\QueryEngine; -/** - * HasOne: one-to-one relationship where the parent "has one" child. - */ class HasOne { use RelationshipUtils; @@ -25,27 +22,33 @@ public function __construct(Entity $parent, string $related, string $foreignKey) public function deepFetch(array $parents, array $columns = ['*']): array { - $parentIds = array_map(fn($p) => $p->id, $parents); - $query = (new QueryEngine($this->related))->select($columns); - $relatedRecords = $this->chunkedWhereIn($query, $this->foreignKey, $parentIds); - // For HasOne, group by foreign key, but only keep the first found (if multiple) + $parentIds = array_map(fn(Entity $p) => $p->id, $parents); + $query = new QueryEngine($this->related); + $records = $this->chunkedWhereIn($query->select($columns), $this->foreignKey, $parentIds); + $grouped = []; - foreach ($relatedRecords as $record) { - $fk = $record->{$this->foreignKey} ?? null; + foreach ($records as $rec) { + $fk = $rec->{$this->foreignKey} ?? null; if ($fk !== null && !isset($grouped[$fk])) { - $grouped[$fk] = $record; + $grouped[$fk] = $rec; } } return $grouped; } - public function getForeignKey(): string - { - return $this->foreignKey; - } - public function getParentKey(): string + public function deepFetchUsingEngine(array $parents, QueryEngine $engine): array { - return 'id'; + $parentIds = array_map(fn(Entity $p) => $p->id, $parents); + $records = $this->chunkedWhereIn($engine, $this->foreignKey, $parentIds); + + $grouped = []; + foreach ($records as $rec) { + $fk = $rec->{$this->foreignKey} ?? null; + if ($fk !== null && !isset($grouped[$fk])) { + $grouped[$fk] = $rec; + } + } + return $grouped; } public function get(): ?object @@ -54,4 +57,14 @@ public function get(): ?object ->where($this->foreignKey, '=', $this->parent->id) ->first(); } + + public function getForeignKey(): string + { + return $this->foreignKey; + } + + public function getParentKey(): string + { + return 'id'; + } } From 5b0a259a09e216edae183f883ba27a756d091ad1 Mon Sep 17 00:00:00 2001 From: William Asaba Date: Fri, 16 May 2025 14:49:29 +0300 Subject: [PATCH 6/8] wip --- src/PocketORM/Relationships/RelationshipUtils.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PocketORM/Relationships/RelationshipUtils.php b/src/PocketORM/Relationships/RelationshipUtils.php index b03cc1e..f04c634 100644 --- a/src/PocketORM/Relationships/RelationshipUtils.php +++ b/src/PocketORM/Relationships/RelationshipUtils.php @@ -45,4 +45,9 @@ protected static function groupByKey(array $records, string $key): array } return $grouped; } + + public function getQueryEngine(): QueryEngine + { + return new QueryEngine($this->related); + } } From eb88bd29e6fda5402d5d4ad15914b0b27fa87df6 Mon Sep 17 00:00:00 2001 From: William Asaba Date: Fri, 16 May 2025 14:49:34 +0300 Subject: [PATCH 7/8] wip --- src/TemplateEngine/AttributeBag.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/TemplateEngine/AttributeBag.php b/src/TemplateEngine/AttributeBag.php index f56faad..558b380 100644 --- a/src/TemplateEngine/AttributeBag.php +++ b/src/TemplateEngine/AttributeBag.php @@ -1,5 +1,4 @@ Date: Fri, 16 May 2025 14:49:50 +0300 Subject: [PATCH 8/8] Rename the relationship --- src/PocketORM/Relationships/BelongsTo.php | 73 +++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/PocketORM/Relationships/BelongsTo.php diff --git a/src/PocketORM/Relationships/BelongsTo.php b/src/PocketORM/Relationships/BelongsTo.php new file mode 100644 index 0000000..158ef54 --- /dev/null +++ b/src/PocketORM/Relationships/BelongsTo.php @@ -0,0 +1,73 @@ +parent = $parent; + $this->related = $related; + $this->foreignKey = $foreignKey; + } + + public function deepFetch(array $parents, array $columns = ['*']): array + { + $fks = array_map(fn(Entity $p) => $p->{$this->foreignKey}, $parents); + $query = new QueryEngine($this->related); + $recs = $this->chunkedWhereIn($query->select($columns), $this->getParentKey(), $fks); + + $grouped = []; + foreach ($recs as $rec) { + $id = $rec->{$this->getParentKey()} ?? null; + if ($id !== null) { + $grouped[$id] = $rec; + } + } + return $grouped; + } + + public function deepFetchUsingEngine(array $parents, QueryEngine $engine): array + { + $fks = array_map(fn(Entity $p) => $p->{$this->foreignKey}, $parents); + $recs = $this->chunkedWhereIn($engine, $this->getParentKey(), $fks); + $grouped = []; + foreach ($recs as $rec) { + $id = $rec->{$this->getParentKey()} ?? null; + if ($id !== null) { + $grouped[$id] = $rec; + } + } + return $grouped; + } + + public function resolve(): ?object + { + $fk = $this->parent->{$this->foreignKey}; + if ($fk === null) { + return null; + } + return (new QueryEngine($this->related)) + ->where($this->getParentKey(), '=', $fk) + ->first(); + } + + public function getForeignKey(): string + { + return $this->foreignKey; + } + + public function getParentKey(): string + { + return 'id'; + } +}