Skip to content

Commit 4a95e50

Browse files
committed
use local object, never typecast internal data
1 parent 5d1bf4c commit 4a95e50

File tree

3 files changed

+71
-14
lines changed

3 files changed

+71
-14
lines changed

demos/interactive/cardtable.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,11 @@
1212

1313
Header::addTo($app, ['Card displays read-only data of a single record']);
1414

15-
CardTable::addTo($app)->setModel((new Stat($app->db))->loadAny());
15+
$entity = (new Stat($app->db))->loadAny();
16+
$entity->project_code .= ' <b>no reload</b>';
17+
18+
CardTable::addTo($app)->setModel($entity);
19+
20+
// CardTable uses internally atk4_local_object type which uses weak references,
21+
// force GC to test the data are kept referenced correctly
22+
gc_collect_cycles();

src/CardTable.php

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Atk4\Ui;
66

7+
use Atk4\Data\Field;
78
use Atk4\Data\Model;
89

910
/**
@@ -34,12 +35,12 @@ public function setModel(Model $entity, array $columns = null): void
3435
}
3536

3637
$data = [];
37-
foreach ($entity->get() as $key => $value) {
38+
foreach (array_keys($entity->get()) as $key) {
3839
if (in_array($key, $columns, true)) {
3940
$data[] = [
4041
'id' => $key,
4142
'field' => $entity->getField($key)->getCaption(),
42-
'value' => $this->getApp()->uiPersistence->typecastSaveField($entity->getField($key), $value),
43+
'value' => new Model\EntityFieldPair($entity, $key),
4344
];
4445
}
4546
}
@@ -51,17 +52,13 @@ public function setModel(Model $entity, array $columns = null): void
5152
$this->_bypass = false;
5253
}
5354

54-
$this->addDecorator('value', [Table\Column\Multiformat::class, function (Model $row) use ($entity) {
55-
$field = $entity->getField($row->getId());
56-
$ret = $this->decoratorFactory(
57-
$field,
58-
$field->type === 'boolean' ? [Table\Column\Status::class, ['positive' => [true, 'Yes'], 'negative' => [false, 'No']]] : []
59-
);
60-
if ($ret instanceof Table\Column\Money) {
61-
$ret->attr['all']['class'] = ['single line'];
55+
$this->addDecorator('value', [Table\Column\Multiformat::class, function (Model $row, Field $field) {
56+
$c = $this->decoratorFactory($field);
57+
if ($c instanceof Table\Column\Money) {
58+
$c->attr['all']['class'] = ['single line'];
6259
}
6360

64-
return [$ret];
61+
return [$c];
6562
}]);
6663
}
6764
}

src/Table.php

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Atk4\Core\Factory;
88
use Atk4\Data\Field;
99
use Atk4\Data\Model;
10+
use Atk4\Data\Model\EntityFieldPair;
1011
use Atk4\Ui\Js\Jquery;
1112
use Atk4\Ui\Js\JsChain;
1213
use Atk4\Ui\Js\JsExpression;
@@ -389,6 +390,40 @@ public function setModel(Model $model, array $columns = null): void
389390
}
390391
}
391392

393+
public function setSource(array $data, $fields = null): Model
394+
{
395+
// mainly for CardTable to support different field type for different row 1/2
396+
// related with https://github.com/atk4/data/blob/4.0.0/tests/Persistence/StaticTest.php#L142
397+
$dataWithObjects = [];
398+
if (is_array(reset($data))) {
399+
foreach ($data as $rowKey => $row) {
400+
foreach ($row as $k => $v) {
401+
if ($v instanceof EntityFieldPair) {
402+
$dataWithObjects[$row['id']][$k] = $v;
403+
$data[$rowKey][$k] = null;
404+
}
405+
}
406+
}
407+
}
408+
409+
$model = parent::setSource($data, $fields);
410+
411+
foreach ($dataWithObjects as $id => $row) {
412+
$entity = $model->load($id);
413+
foreach ($row as $k => $v) {
414+
$model->getField($k)->type = 'atk4_local_object';
415+
$entity->set($k, $v);
416+
}
417+
$entity->save();
418+
}
419+
$model->onHook(Model::HOOK_BEFORE_LOAD, function () use ($dataWithObjects) {
420+
// useless hook, but make sure the $dataWithObjects is kept referenced from $model
421+
$count = count($dataWithObjects);
422+
});
423+
424+
return $model;
425+
}
426+
392427
protected function renderView(): void
393428
{
394429
if (!$this->columns) {
@@ -420,8 +455,26 @@ protected function renderView(): void
420455
// the same in Lister class
421456
$modelBackup = $this->model;
422457
try {
423-
foreach ($this->model as $this->model) {
424-
$this->currentRow = $this->model;
458+
foreach ($this->model as $entityOrig) {
459+
// mainly for CardTable to support different field type for different row 2/2
460+
$entityCloned = (clone $entityOrig->getModel())->createEntity();
461+
$entityCloned->setId($entityOrig->getId());
462+
\Closure::bind(function () use ($entityOrig, $entityCloned) {
463+
foreach ($entityOrig->data as $k => $v) {
464+
$field = $entityCloned->getField($k);
465+
if ($field->type === 'atk4_local_object' && $v instanceof EntityFieldPair) {
466+
$field->type = $v->getField()->type;
467+
$field->enum = $v->getField()->enum;
468+
$field->values = $v->getField()->values;
469+
$field->ui = $v->getField()->ui;
470+
$v = $v->get();
471+
}
472+
$entityCloned->data[$k] = $v;
473+
}
474+
}, null, Model::class)();
475+
476+
$this->model = $entityCloned;
477+
$this->currentRow = $entityCloned; // TODO we should either drop currentRow property or never update model property
425478
if ($this->hook(self::HOOK_BEFORE_ROW) === false) {
426479
continue;
427480
}

0 commit comments

Comments
 (0)