1
|
|
|
<?php |
2
|
|
|
namespace NOSQL\Dto\Model; |
3
|
|
|
|
4
|
|
|
use MongoDB\BSON\UTCDateTime; |
5
|
|
|
use NOSQL\Exceptions\NOSQLValidationException; |
6
|
|
|
use NOSQL\Services\Base\NOSQLBase; |
7
|
|
|
use PSFS\base\dto\Dto; |
8
|
|
|
use PSFS\base\types\Api; |
9
|
|
|
use PSFS\base\types\helpers\InjectorHelper; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Class NOSQLModelDto |
13
|
|
|
* @package NOSQL\Dto\Model |
14
|
|
|
*/ |
15
|
|
|
abstract class NOSQLModelDto extends Dto { |
16
|
|
|
/** |
17
|
|
|
* @var string |
18
|
|
|
* @label Model identifier |
19
|
|
|
*/ |
20
|
|
|
protected $_id; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* @var \DateTime |
24
|
|
|
* @label Last update at |
25
|
|
|
*/ |
26
|
|
|
protected $_last_update; |
27
|
|
|
|
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var string |
31
|
|
|
* @label List name in string |
32
|
|
|
*/ |
33
|
|
|
protected $__name__; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @return string |
37
|
|
|
*/ |
38
|
|
|
public function getPk() |
39
|
|
|
{ |
40
|
|
|
return $this->_id; |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
public function resetPk() { |
44
|
|
|
$this->_id = null; |
45
|
|
|
$this->_last_update = null; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @param string $id |
50
|
|
|
* @throws NOSQLValidationException |
51
|
|
|
*/ |
52
|
|
|
public function setPk(string $id) |
53
|
|
|
{ |
54
|
|
|
if(!empty($this->_id)) { |
55
|
|
|
throw new NOSQLValidationException(t('Primary key already defined'), NOSQLValidationException::NOSQL_VALIDATION_ID_ALREADY_DEFINED); |
56
|
|
|
} |
57
|
|
|
$this->_id = $id; |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* @param string $format |
62
|
|
|
* @return \DateTime|string |
63
|
|
|
*/ |
64
|
|
|
public function getLastUpdate($format = null) |
65
|
|
|
{ |
66
|
|
|
$value = $this->_last_update; |
67
|
|
|
if(null !== $format) { |
68
|
|
|
|
69
|
|
|
} |
70
|
|
|
return $value; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @param \DateTime|null $last_update |
75
|
|
|
* @throws \Exception |
76
|
|
|
*/ |
77
|
|
|
public function setLastUpdate($last_update = null) |
78
|
|
|
{ |
79
|
|
|
$this->_last_update = $last_update ?: new UTCDateTime(); |
|
|
|
|
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @return string |
84
|
|
|
*/ |
85
|
|
|
public function getName() |
86
|
|
|
{ |
87
|
|
|
return $this->__name__; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @param string $_name__ |
92
|
|
|
*/ |
93
|
|
|
public function setName($_name__) |
94
|
|
|
{ |
95
|
|
|
$this->__name__ = $_name__; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* @param bool $throwException |
100
|
|
|
* @return array |
101
|
|
|
* @throws NOSQLValidationException |
102
|
|
|
* @throws \ReflectionException |
103
|
|
|
*/ |
104
|
|
|
public function validate($throwException = false) { |
105
|
|
|
$errors = []; |
106
|
|
|
$reflection = new \ReflectionClass(get_called_class()); |
107
|
|
|
foreach($reflection->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { |
108
|
|
|
$required = InjectorHelper::checkIsRequired($property->getDocComment()); |
109
|
|
|
$value = $property->getValue($this); |
110
|
|
|
if($required && $this->checkEmpty($value)) { |
111
|
|
|
if($throwException) { |
112
|
|
|
throw new NOSQLValidationException(t('Empty value for property ') . $property->getName(), NOSQLValidationException::NOSQL_VALIDATION_REQUIRED); |
113
|
|
|
} else { |
114
|
|
|
$errors[] = $property->getName(); |
115
|
|
|
} |
116
|
|
|
} else { |
117
|
|
|
$this->checkType($throwException, $property, $value, $errors); |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
return $errors; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
protected function checkEmpty($value) { |
124
|
|
|
return empty($value) && 0 !== $value && '0' !== $value; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
public function toArray() |
128
|
|
|
{ |
129
|
|
|
$array = parent::toArray(); |
130
|
|
|
if(null !== $this->getPk()) { |
|
|
|
|
131
|
|
|
$array['_id'] = $this->getPk(); |
132
|
|
|
} |
133
|
|
|
if(null !== $this->getName()) { |
|
|
|
|
134
|
|
|
$array[Api::API_LIST_NAME_FIELD] = $this->getName(); |
135
|
|
|
} |
136
|
|
|
$array['_last_update'] = $this->getLastUpdate(\DateTime::ATOM); |
137
|
|
|
return $array; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* @param $throwException |
142
|
|
|
* @param \ReflectionProperty $property |
143
|
|
|
* @param $value |
144
|
|
|
* @param array $errors |
145
|
|
|
* @throws NOSQLValidationException |
146
|
|
|
*/ |
147
|
|
|
private function checkType($throwException, \ReflectionProperty &$property, $value, array &$errors) |
148
|
|
|
{ |
149
|
|
|
$type = InjectorHelper::extractVarType($property->getDocComment()); |
|
|
|
|
150
|
|
|
switch (strtolower($type)) { |
|
|
|
|
151
|
|
|
case NOSQLBase::NOSQL_TYPE_LONG: |
152
|
|
|
case NOSQLBase::NOSQL_TYPE_INTEGER: |
153
|
|
|
case NOSQLBase::NOSQL_TYPE_DOUBLE: |
154
|
|
|
if (!is_numeric($value)) { |
155
|
|
|
$errors[] = $property->getName(); |
156
|
|
|
} else { |
157
|
|
|
if(NOSQLBase::NOSQL_TYPE_INTEGER === strtolower($type)) { |
158
|
|
|
$property->setValue($this, (integer)$value); |
159
|
|
|
} else { |
160
|
|
|
$property->setValue($this, (float)$value); |
161
|
|
|
} |
162
|
|
|
} |
163
|
|
|
break; |
164
|
|
|
case NOSQLBase::NOSQL_TYPE_ENUM: |
165
|
|
|
$values = InjectorHelper::getValues($property->getDocComment()); |
166
|
|
|
if (!in_array($value, $values)) { |
|
|
|
|
167
|
|
|
$errors[] = $property->getName(); |
168
|
|
|
} |
169
|
|
|
break; |
170
|
|
|
case NOSQLBase::NOSQL_TYPE_OBJECT: |
171
|
|
|
$property->setValue($this, json_decode($value, true)); |
172
|
|
|
break; |
173
|
|
|
case NOSQLBase::NOSQL_TYPE_ARRAY: |
174
|
|
|
if (!is_array($value)) { |
175
|
|
|
$errors[] = $property->getName(); |
176
|
|
|
} |
177
|
|
|
break; |
178
|
|
|
case NOSQLBase::NOSQL_TYPE_BOOLEAN: |
179
|
|
|
if (!in_array($value, [true, false, 0, 1])) { |
180
|
|
|
$errors[] = $property->getName(); |
181
|
|
|
} |
182
|
|
|
$property->setValue($this, (bool)$value); |
183
|
|
|
break; |
184
|
|
|
case NOSQLBase::NOSQL_TYPE_DATE: |
185
|
|
|
case NOSQLBase::NOSQL_TYPE_TIMESTAMP: |
186
|
|
|
$dateTime = new \DateTime($value, new \DateTimeZone('UTC')); |
187
|
|
|
if(!$dateTime) { |
|
|
|
|
188
|
|
|
$errors[] = $property->getName(); |
189
|
|
|
} else { |
190
|
|
|
$dateTime->setTimezone(new \DateTimeZone(date_default_timezone_get())); |
191
|
|
|
$property->setValue($this, new UTCDateTime($dateTime->getTimestamp()*1000)); |
192
|
|
|
} |
193
|
|
|
break; |
194
|
|
|
} |
195
|
|
|
if (in_array($property->getName(), $errors) && $throwException) { |
196
|
|
|
throw new NOSQLValidationException(t('Format not valid for property ') . $property->getName(), NOSQLValidationException::NOSQL_VALIDATION_NOT_VALID); |
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
} |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.