1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Data\Persistence;
6:
7: use Atk4\Data\Model;
8:
9: /**
10: * Implements a very basic array-access pattern:.
11: *
12: * $m = new Model(Persistence\Static_(['hello', 'world']));
13: * $m->load(1);
14: *
15: * echo $m->get('name'); // world
16: */
17: class Static_ extends Array_
18: {
19: /** @var string This will be the title field for the model. */
20: public $titleFieldForModel;
21:
22: /** @var array<string, array<mixed>> Populate the following fields for the model. */
23: public $fieldsForModel = [];
24:
25: /**
26: * @param array<int|string, mixed> $data
27: */
28: public function __construct(array $data = [])
29: {
30: if (count($data) > 0 && !is_array(reset($data))) {
31: $dataOrig = $data;
32: $data = [];
33: foreach ($dataOrig as $k => $v) {
34: $data[] = ['id' => $k, 'name' => $v];
35: }
36: }
37:
38: // detect types from values
39: $fieldTypes = [];
40: foreach ($data as $row) {
41: foreach ($row as $k => $v) {
42: if (isset($fieldTypes[$k])) {
43: continue;
44: }
45:
46: if (is_bool($v)) {
47: $fieldType = 'boolean';
48: } elseif (is_int($v)) {
49: $fieldType = 'integer';
50: } elseif (is_float($v)) {
51: $fieldType = 'float';
52: } elseif ($v instanceof \DateTimeInterface) {
53: $fieldType = 'datetime';
54: } elseif (is_array($v)) {
55: $fieldType = 'json';
56: } elseif (is_object($v)) {
57: $fieldType = 'object';
58: } elseif ($v !== null) {
59: $fieldType = 'string';
60: } else {
61: $fieldType = null;
62: }
63:
64: $fieldTypes[$k] = $fieldType;
65: }
66: }
67: foreach ($fieldTypes as $k => $fieldType) {
68: if ($fieldType === null) {
69: $fieldTypes[$k] = 'string';
70: }
71: }
72:
73: if (isset($fieldTypes['name'])) {
74: $this->titleFieldForModel = 'name';
75: } elseif (isset($fieldTypes['title'])) {
76: $this->titleFieldForModel = 'title';
77: }
78:
79: $defTypes = [];
80: $keyOverride = [];
81: $mustOverride = false;
82: foreach ($fieldTypes as $k => $fieldType) {
83: $defTypes[$k] = ['type' => $fieldType];
84:
85: // id information present, use it instead
86: if ($k === 'id') {
87: $mustOverride = true;
88: }
89:
90: // if title is not set, use first key
91: if (!$this->titleFieldForModel) {
92: if (is_int($k)) {
93: $keyOverride[$k] = 'name';
94: $this->titleFieldForModel = 'name';
95: $mustOverride = true;
96:
97: continue;
98: }
99:
100: $this->titleFieldForModel = $k;
101: }
102:
103: if (is_int($k)) {
104: $keyOverride[$k] = 'field' . $k;
105: $mustOverride = true;
106: } else {
107: $keyOverride[$k] = $k;
108: }
109: }
110:
111: if ($mustOverride) {
112: $dataOrig = $data;
113: $data = [];
114: foreach ($dataOrig as $k => $row) {
115: $row = array_combine($keyOverride, $row);
116: if (isset($row['id'])) {
117: $k = $row['id'];
118: }
119: $data[$k] = $row;
120: }
121: }
122:
123: $this->fieldsForModel = array_combine($keyOverride, $defTypes);
124:
125: parent::__construct($data);
126: }
127:
128: #[\Override]
129: public function add(Model $model, array $defaults = []): void
130: {
131: if ($model->idField && !$model->hasField($model->idField)) {
132: // init model, but prevent array persistence data seeding, id field with correct type must be setup first
133: \Closure::bind(function () use ($model, $defaults) {
134: $hadData = true;
135: if (!isset($this->data[$model->table])) {
136: $hadData = false;
137: $this->data[$model->table] = true;
138: }
139: try {
140: parent::add($model, $defaults);
141: } finally {
142: if (!$hadData) {
143: unset($this->data[$model->table]);
144: }
145: }
146: }, $this, Array_::class)();
147: \Closure::bind(static function () use ($model) {
148: $model->_persistence = null;
149: }, null, Model::class)();
150:
151: if (isset($this->fieldsForModel[$model->idField])) {
152: $model->getField($model->idField)->type = $this->fieldsForModel[$model->idField]['type'];
153: }
154: }
155: $this->addMissingFieldsToModel($model);
156:
157: parent::add($model, $defaults);
158: }
159:
160: /**
161: * Automatically adds missing model fields.
162: */
163: protected function addMissingFieldsToModel(Model $model): void
164: {
165: if ($this->titleFieldForModel) {
166: $model->titleField = $this->titleFieldForModel;
167: }
168:
169: foreach ($this->fieldsForModel as $field => $def) {
170: if ($model->hasField($field)) {
171: continue;
172: }
173:
174: $model->addField($field, $def);
175: }
176: }
177: }
178: