Skip to content

Commit 992ac4d

Browse files
authored
Deduplicate Field properties (#891)
1 parent 336695f commit 992ac4d

File tree

7 files changed

+258
-374
lines changed

7 files changed

+258
-374
lines changed

src/Field.php

+42-210
Original file line numberDiff line numberDiff line change
@@ -17,186 +17,11 @@
1717
class Field implements Expressionable
1818
{
1919
use DiContainerTrait;
20+
use Model\FieldPropertiesTrait;
2021
use Model\JoinLinkTrait;
2122
use ReadableCaptionTrait;
2223
use TrackableTrait;
2324

24-
// {{{ Properties
25-
26-
/**
27-
* Default value of field.
28-
*
29-
* @var mixed
30-
*/
31-
public $default;
32-
33-
/**
34-
* Field type.
35-
*
36-
* Values are: 'string', 'text', 'boolean', 'integer', 'money', 'float',
37-
* 'date', 'datetime', 'time', 'array', 'object'.
38-
* Can also be set to unspecified type for your own custom handling.
39-
*
40-
* @var string
41-
*/
42-
public $type;
43-
44-
/**
45-
* For several types enum can provide list of available options. ['blue', 'red'].
46-
*
47-
* @var array|null
48-
*/
49-
public $enum;
50-
51-
/**
52-
* For fields that can be selected, values can represent interpretation of the values,
53-
* for instance ['F'=>'Female', 'M'=>'Male'];.
54-
*
55-
* @var array|null
56-
*/
57-
public $values;
58-
59-
/**
60-
* If value of this field is defined by a model, this property
61-
* will contain reference link.
62-
*
63-
* @var string|null
64-
*/
65-
protected $referenceLink;
66-
67-
/**
68-
* Actual field name.
69-
*
70-
* @var string|null
71-
*/
72-
public $actual;
73-
74-
/**
75-
* Is it system field?
76-
* System fields will be always loaded and saved.
77-
*
78-
* @var bool
79-
*/
80-
public $system = false;
81-
82-
/**
83-
* Setting this to true will never actually load or store
84-
* the field in the database. It will action as normal,
85-
* but will be skipped by load/iterate/update/insert.
86-
*
87-
* @var bool
88-
*/
89-
public $never_persist = false;
90-
91-
/**
92-
* Setting this to true will never actually store
93-
* the field in the database. It will action as normal,
94-
* but will be skipped by update/insert.
95-
*
96-
* @var bool
97-
*/
98-
public $never_save = false;
99-
100-
/**
101-
* Is field read only?
102-
* Field value may not be changed. It'll never be saved.
103-
* For example, expressions are read only.
104-
*
105-
* @var bool
106-
*/
107-
public $read_only = false;
108-
109-
/**
110-
* Defines a label to go along with this field. Use getCaption() which
111-
* will always return meaningful label (even if caption is null). Set
112-
* this property to any string.
113-
*
114-
* @var string
115-
*/
116-
public $caption;
117-
118-
/**
119-
* Array with UI flags like editable, visible and hidden.
120-
*
121-
* @var array
122-
*/
123-
public $ui = [];
124-
125-
/**
126-
* Mandatory field must not be null. The value must be set, even if
127-
* it's an empty value.
128-
*
129-
* Can contain error message for UI.
130-
*
131-
* @var bool|string
132-
*/
133-
public $mandatory = false;
134-
135-
/**
136-
* Required field must have non-empty value. A null value is considered empty too.
137-
*
138-
* Can contain error message for UI.
139-
*
140-
* @var bool|string
141-
*/
142-
public $required = false;
143-
144-
/**
145-
* Should we use typecasting when saving/loading data to/from persistence.
146-
*
147-
* Value can be array [$typecast_save_callback, $typecast_load_callback].
148-
*
149-
* @var bool|array|null
150-
*/
151-
public $typecast;
152-
153-
/**
154-
* Should we use serialization when saving/loading data to/from persistence.
155-
*
156-
* Value can be array [$encode_callback, $decode_callback].
157-
*
158-
* @var bool|array|string|null
159-
*/
160-
public $serialize;
161-
162-
/**
163-
* Persisting format for type = 'date', 'datetime', 'time' fields.
164-
*
165-
* For example, for date it can be 'Y-m-d', for datetime - 'Y-m-d H:i:s.u' etc.
166-
*
167-
* @var string
168-
*/
169-
public $persist_format;
170-
171-
/**
172-
* Persisting timezone for type = 'date', 'datetime', 'time' fields.
173-
*
174-
* For example, 'IST', 'UTC', 'Europe/Riga' etc.
175-
*
176-
* @var string
177-
*/
178-
public $persist_timezone = 'UTC';
179-
180-
/**
181-
* DateTime class used for type = 'data', 'datetime', 'time' fields.
182-
*
183-
* For example, 'DateTime', 'Carbon\Carbon' etc.
184-
*
185-
* @var string
186-
*/
187-
public $dateTimeClass = \DateTime::class;
188-
189-
/**
190-
* Timezone class used for type = 'data', 'datetime', 'time' fields.
191-
*
192-
* For example, 'DateTimeZone', 'Carbon\CarbonTimeZone' etc.
193-
*
194-
* @var string
195-
*/
196-
public $dateTimeZoneClass = \DateTimeZone::class;
197-
198-
// }}}
199-
20025
// {{{ Core functionality
20126

20227
/**
@@ -485,6 +310,25 @@ public function setNull(): self
485310
return $this;
486311
}
487312

313+
/**
314+
* @param mixed $value
315+
*
316+
* @return mixed
317+
*/
318+
private function typecastSaveField($value, bool $allowDummyPersistence = false)
319+
{
320+
$persistence = $this->getOwner()->persistence;
321+
if ($persistence === null) {
322+
if ($allowDummyPersistence) {
323+
$persistence = (new \ReflectionClass(Persistence\Sql::class))->newInstanceWithoutConstructor();
324+
} else {
325+
$this->getOwner()->checkPersistence();
326+
}
327+
}
328+
329+
return $persistence->typecastSaveRow($this->getOwner(), [$this->short_name => $value])[$this->getPersistenceName()];
330+
}
331+
488332
/**
489333
* Compare new value of the field with existing one without retrieving.
490334
* In the trivial case it's same as ($value == $model->get($name)) but this method can be used for:
@@ -501,32 +345,17 @@ public function compare($value, $value2 = null): bool
501345
$value2 = $this->get();
502346
}
503347

504-
// TODO code below is not nice, we want to replace it, the purpose of the code is simply to
505-
// compare if typecasted values are the same using strict comparison (===) or nor
506-
$typecastFunc = function ($v) {
507-
// do not typecast null values, because that implies calling normalize() which tries to validate that value can't be null in case field value is required
508-
if ($v === null) {
509-
return $v;
510-
}
511-
512-
if ($this->getOwner()->persistence === null) {
513-
$v = $this->normalize($v);
514-
515-
// without persistence, we can not do a lot with non-scalar types, but as DateTime
516-
// is used often, fix the compare for them
517-
// TODO probably create and use a default persistence
518-
if (is_scalar($v)) {
519-
return (string) $v;
520-
} elseif ($v instanceof \DateTimeInterface) {
521-
return $v->getTimestamp() . '.' . $v->format('u');
522-
}
523-
524-
return serialize($v);
348+
$typecastFunc = function ($value): ?string {
349+
// do not typecast null values, because that implies calling normalize()
350+
// which tries to validate if value is not null in case field value is required
351+
if ($value === null) {
352+
return null;
525353
}
526354

527-
return (string) $this->getOwner()->persistence->typecastSaveRow($this->getOwner(), [$this->short_name => $v])[$this->getPersistenceName()];
355+
return (string) $this->typecastSaveField($value, true);
528356
};
529357

358+
// compare if typecasted values are the same using strict comparison
530359
return $typecastFunc($value) === $typecastFunc($value2);
531360
}
532361

@@ -562,24 +391,27 @@ public function useAlias(): bool
562391
*/
563392
public function getQueryArguments($operator, $value): array
564393
{
565-
$skipValueTypecast = [
394+
$typecastField = $this;
395+
$allowArray = true;
396+
if (in_array($operator, [
566397
Scope\Condition::OPERATOR_LIKE,
567398
Scope\Condition::OPERATOR_NOT_LIKE,
568399
Scope\Condition::OPERATOR_REGEXP,
569400
Scope\Condition::OPERATOR_NOT_REGEXP,
570-
];
571-
572-
if (!in_array($operator, $skipValueTypecast, true)) {
573-
if (is_array($value)) {
574-
$value = array_map(function ($option) {
575-
return $this->getOwner()->persistence->typecastSaveField($this, $option);
576-
}, $value);
577-
} else {
578-
$value = $this->getOwner()->persistence->typecastSaveField($this, $value);
579-
}
401+
], true)) {
402+
$typecastField = new self(['type' => 'string']);
403+
$typecastField->setOwner(new Model($this->getOwner()->persistence, ['table' => false]));
404+
$typecastField->short_name = $this->short_name;
405+
$allowArray = false;
580406
}
581407

582-
return [$this, $operator, $value];
408+
return [
409+
$this,
410+
$operator,
411+
is_array($value) && $allowArray
412+
? array_map(fn ($value) => $typecastField->typecastSaveField($value), $value)
413+
: $typecastField->typecastSaveField($value),
414+
];
583415
}
584416

585417
// }}}

src/Model.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -1530,9 +1530,7 @@ public function save(array $data = [])
15301530
throw new Exception('Model::save() with 2nd param $to_persistence is no longer supported');
15311531
}
15321532

1533-
if (!$this->persistence) {
1534-
throw new Exception('Model is not associated with any persistence');
1535-
}
1533+
$this->checkPersistence();
15361534

15371535
if ($this->read_only) {
15381536
throw new Exception('Model is read-only and cannot be saved');

0 commit comments

Comments
 (0)