Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Model nesting for Array persistence #961

Merged
merged 16 commits into from
Jan 16, 2022
Merged
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;
}
}
Loading