Skip to content

Commit

Permalink
Add support for Model nesting for Array persistence (#961)
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek committed Jan 16, 2022
1 parent e91bf15 commit 277c607
Show file tree
Hide file tree
Showing 11 changed files with 352 additions and 46 deletions.
76 changes: 60 additions & 16 deletions src/Persistence/Array_.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ private function seedDataAndGetTable(Model $model): Table
*/
public function getRawDataByTable(Model $model, string $table): array
{
$this->seedData($model);
if (!is_object($model->table)) {
$this->seedData($model);
}

$rows = [];
foreach ($this->data[$table]->getRows() as $row) {
Expand Down Expand Up @@ -182,18 +184,34 @@ public function add(Model $model, array $defaults = []): void
}
}

$this->seedData($model);
if (!is_object($model->table)) {
$this->seedData($model);
}
}

private function filterRowDataOnlyModelFields(Model $model, array $rowData): array
private function getPersistenceNameToNameMap(Model $model): array
{
return array_intersect_key($rowData, array_map(fn (Field $f) => $f->short_name, $model->getFields()));
return array_flip(array_map(fn (Field $f) => $f->getPersistenceName(), $model->getFields()));
}

public function tryLoad(Model $model, $id): ?array
private function filterRowDataOnlyModelFields(Model $model, array $rowDataRaw): array
{
$table = $this->seedDataAndGetTable($model);
return array_intersect_key($rowDataRaw, $this->getPersistenceNameToNameMap($model));
}

private function remapLoadRow(Model $model, array $row): array
{
$rowRemapped = [];
$map = $this->getPersistenceNameToNameMap($model);
foreach ($row as $k => $v) {
$rowRemapped[$map[$k]] = $v;
}

return $rowRemapped;
}

public function tryLoad(Model $model, $id): ?array
{
if ($id === self::ID_LOAD_ONE || $id === self::ID_LOAD_ANY) {
$action = $this->action($model, 'select');
$action->generator->rewind(); // TODO needed for some reasons!
Expand All @@ -214,12 +232,30 @@ public function tryLoad(Model $model, $id): ?array
return $row;
}

$row = $table->getRowById($model, $id);
if ($row === null) {
return null;
if (is_object($model->table)) {
$action = $this->action($model, 'select');
$condition = new Model\Scope\Condition('', $id);
$condition->key = $model->getField($model->id_field);
$condition->setOwner($model->createEntity()); // TODO needed for typecasting to apply
$action->filter($condition);
$action->generator->rewind(); // TODO needed for some reasons!

$rowData = $action->getRow();
if ($rowData === null) {
return null;
}
} else {
$table = $this->seedDataAndGetTable($model);

$row = $table->getRowById($model, $id);
if ($row === null) {
return null;
}

$rowData = $this->remapLoadRow($model, $this->filterRowDataOnlyModelFields($model, $row->getData()));
}

return $this->typecastLoadRow($model, $this->filterRowDataOnlyModelFields($model, $row->getData()));
return $this->typecastLoadRow($model, $rowData);
}

protected function insertRaw(Model $model, array $dataRaw)
Expand Down Expand Up @@ -319,11 +355,21 @@ public function export(Model $model, array $fields = null, bool $typecast = true
*/
public function initAction(Model $model, array $fields = null): Action
{
$table = $this->seedDataAndGetTable($model);
if (is_object($model->table)) {
$tableAction = $this->action($model->table, 'select');

$rows = [];
foreach ($table->getRows() as $row) {
$rows[$row->getValue($model->id_field)] = $this->filterRowDataOnlyModelFields($model, $row->getData());
$rows = $tableAction->getRows();
} else {
$table = $this->seedDataAndGetTable($model);

$rows = [];
foreach ($table->getRows() as $row) {
$rows[$row->getValue($model->getField($model->id_field)->getPersistenceName())] = $row->getData();
}
}

foreach ($rows as $rowIndex => $row) {
$rows[$rowIndex] = $this->remapLoadRow($model, $this->filterRowDataOnlyModelFields($model, $row));
}

if ($fields !== null) {
Expand Down Expand Up @@ -374,8 +420,6 @@ protected function applyScope(Model $model, Action $action): void
*/
public function action(Model $model, string $type, array $args = [])
{
$args = (array) $args;

switch ($type) {
case 'select':
$action = $this->initAction($model, $args[0] ?? null);
Expand Down
9 changes: 8 additions & 1 deletion src/Persistence/Array_/Action/RenameColumnIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Atk4\Data\Persistence\Array_\Action;

use Atk4\Data\Exception;

/**
* @internal
*
Expand Down Expand Up @@ -32,7 +34,12 @@ public function current(): array
$row = parent::current();

$keys = array_keys($row);
$keys[array_search($this->origName, $keys, true)] = $this->newName;
$index = array_search($this->origName, $keys, true);
if ($index === false) {
throw (new Exception('Column not found'))
->addMoreInfo('orig_name', $this->origName);
}
$keys[$index] = $this->newName;

return array_combine($keys, $row);
}
Expand Down
6 changes: 3 additions & 3 deletions src/Persistence/Array_/Db/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,12 @@ protected function beforeValuesSet(Row $childRow, $newRowData): void
/**
* TODO rewrite with hash index support.
*
* @param mixed $id
* @param mixed $idRaw
*/
public function getRowById(\Atk4\Data\Model $model, $id): ?Row
public function getRowById(\Atk4\Data\Model $model, $idRaw): ?Row
{
foreach ($this->getRows() as $row) {
if ($row->getValue($model->id_field) === $id) {
if ($row->getValue($model->getField($model->id_field)->getPersistenceName()) === $idRaw) {
return $row;
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/Persistence/Array_/Join.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,17 @@ protected function makeFakeModelWithForeignTable(): Model
$this->getOwner()->assertIsModel();

$modelCloned = clone $this->getOwner();
foreach ($modelCloned->getFields() as $field) {
if ($field->hasJoin() && $field->getJoin()->foreign_table === $this->foreign_table) {
\Closure::bind(fn () => $field->joinName = null, null, \Atk4\Data\Field::class)();
} else {
$modelCloned->removeField($field->short_name);
}
}
$modelCloned->addField($this->id_field, ['type' => 'integer']);
$modelCloned->table = $this->foreign_table;

// @TODO hooks will be fixed on a cloned model, Join should be replaced later by supporting unioned table as a table model
// @TODO hooks will be fixed on a cloned model, foreign_table string name should be replaced with object model

return $modelCloned;
}
Expand Down
7 changes: 6 additions & 1 deletion src/Persistence/Sql/Join.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,12 @@ public function afterInsert(Model $entity): void
$query->setMulti($model->persistence->typecastSaveRow($model, $this->getAndUnsetSaveBuffer($entity)));
$query->set($this->foreign_field, $this->hasJoin() ? $this->getJoin()->getId($entity) : $entity->getId());
$query->mode('insert')->execute(); // TODO IMPORTANT migrate to Model insert
$this->setId($entity, $model->persistence->lastInsertId($model));
$modelForLastInsertId = $model;
while (is_object($modelForLastInsertId->table)) {
$modelForLastInsertId = $modelForLastInsertId->table;
}
// assumes same ID field across all nested models (not needed once migrated to Model insert)
$this->setId($entity, $model->persistence->lastInsertId($modelForLastInsertId));
}

public function beforeUpdate(Model $entity, array &$data): void
Expand Down
2 changes: 1 addition & 1 deletion src/Persistence/Static_.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public function add(Model $model, array $defaults = []): void
$hadData = true;
if (!isset($this->data[$model->table])) {
$hadData = false;
$this->data[$model->table] = true; // @phpstan-ignore-line
$this->data[$model->table] = true;
}
try {
parent::add($model, $defaults);
Expand Down
2 changes: 1 addition & 1 deletion src/Reference/HasOne.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected function init(): void
$v = $this->{$fieldPropRefl->getName()};
$vDefault = \PHP_MAJOR_VERSION < 8
? $fieldPropRefl->getDeclaringClass()->getDefaultProperties()[$fieldPropRefl->getName()]
: $fieldPropRefl->getDefaultValue();
: (null ?? $fieldPropRefl->getDefaultValue()); // @phpstan-ignore-line for PHP 7.x
if ($v !== $vDefault) {
$fieldSeed[$fieldPropRefl->getName()] = $v;
}
Expand Down
29 changes: 13 additions & 16 deletions src/Schema/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -319,32 +319,29 @@ public function getDb(array $tableNames = null, bool $noId = false): array
$tableNames = array_values($tableNames);
}

$ret = [];

$resAll = [];
foreach ($tableNames as $table) {
$data2 = [];

$s = $this->db->dsql();
$data = $s->table($table)->getRows();
$query = $this->db->dsql();
$rows = $query->table($table)->getRows();

foreach ($data as &$row) {
foreach ($row as &$val) {
if (is_int($val)) {
$val = (int) $val;
}
$res = [];
$idColumnName = null;
foreach ($rows as $row) {
if ($idColumnName === null) {
$idColumnName = isset($row['_id']) ? '_id' : 'id';
}

if ($noId) {
unset($row['id']);
$data2[] = $row;
unset($row[$idColumnName]);
$res[] = $row;
} else {
$data2[$row['id']] = $row;
$res[$row[$idColumnName]] = $row;
}
}

$ret[$table] = $data2;
$resAll[$table] = $res;
}

return $ret;
return $resAll;
}
}

0 comments on commit 277c607

Please sign in to comment.