1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Charcoal\Property; |
4
|
|
|
|
5
|
|
|
use PDO; |
6
|
|
|
use DateTime; |
7
|
|
|
use DateTimeInterface; |
8
|
|
|
use Exception; |
9
|
|
|
use InvalidArgumentException; |
10
|
|
|
|
11
|
|
|
// From 'charcoal-property' |
12
|
|
|
use Charcoal\Property\AbstractProperty; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* Date/Time Property |
16
|
|
|
*/ |
17
|
|
|
class DateTimeProperty extends AbstractProperty |
18
|
|
|
{ |
19
|
|
|
const DEFAULT_MIN = null; |
20
|
|
|
const DEFAULT_MAX = null; |
21
|
|
|
const DEFAULT_FORMAT = 'Y-m-d H:i:s'; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var DateTimeInterface|null |
25
|
|
|
*/ |
26
|
|
|
private $min = self::DEFAULT_MIN; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var DateTimeInterface|null |
30
|
|
|
*/ |
31
|
|
|
private $max = self::DEFAULT_MAX; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var string |
35
|
|
|
*/ |
36
|
|
|
private $format = self::DEFAULT_FORMAT; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @return string |
40
|
|
|
*/ |
41
|
|
|
public function type() |
42
|
|
|
{ |
43
|
|
|
return 'date-time'; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Ensure multiple can not be true for DateTime property. |
48
|
|
|
* |
49
|
|
|
* @see AbstractProperty::setMultiple() |
50
|
|
|
* |
51
|
|
|
* @param boolean $multiple Multiple flag. |
52
|
|
|
* @throws InvalidArgumentException If the multiple argument is true (must be false). |
53
|
|
|
* @return self |
54
|
|
|
*/ |
55
|
|
View Code Duplication |
public function setMultiple($multiple) |
|
|
|
|
56
|
|
|
{ |
57
|
|
|
$multiple = !!$multiple; |
58
|
|
|
if ($multiple === true) { |
59
|
|
|
throw new InvalidArgumentException( |
60
|
|
|
'Multiple can not be TRUE for date/time property.' |
61
|
|
|
); |
62
|
|
|
} |
63
|
|
|
return $this; |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Multiple is always false for DateTime property. |
68
|
|
|
* |
69
|
|
|
* @see AbstractProperty::getMultiple() |
70
|
|
|
* |
71
|
|
|
* @return boolean |
72
|
|
|
*/ |
73
|
|
|
public function getMultiple() |
74
|
|
|
{ |
75
|
|
|
return false; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Ensure `DateTime` object in val. |
80
|
|
|
* |
81
|
|
|
* @see AbstractProperty::parseOne() |
82
|
|
|
* @see AbstractProperty::parseVal() |
83
|
|
|
* |
84
|
|
|
* @param string|DateTimeInterface $val The value to set. |
85
|
|
|
* @return DateTimeInterface|null |
86
|
|
|
*/ |
87
|
|
|
public function parseOne($val) |
88
|
|
|
{ |
89
|
|
|
return $this->dateTimeVal($val); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Convert `DateTime` to input-friendly string. |
94
|
|
|
* |
95
|
|
|
* @see AbstractProperty::inputVal() |
96
|
|
|
* |
97
|
|
|
* @param mixed $val The value to to convert for input. |
98
|
|
|
* @param array $options Unused, optional options. |
99
|
|
|
* @throws Exception If the date/time is invalid. |
100
|
|
|
* @return string|null |
101
|
|
|
*/ |
102
|
|
|
public function inputVal($val, array $options = []) |
103
|
|
|
{ |
104
|
|
|
unset($options); |
105
|
|
|
$val = $this->dateTimeVal($val); |
106
|
|
|
|
107
|
|
|
if ($val instanceof DateTimeInterface) { |
108
|
|
|
return $val->format('Y-m-d H:i:s'); |
109
|
|
|
} else { |
110
|
|
|
return ''; |
111
|
|
|
} |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Convert `DateTime` to SQL-friendly string. |
116
|
|
|
* |
117
|
|
|
* @see StorablePropertyTrait::storageVal() |
118
|
|
|
* |
119
|
|
|
* @param string|DateTime $val Optional. Value to convert to storage format. |
120
|
|
|
* @throws Exception If the date/time is invalid. |
121
|
|
|
* @return string|null |
122
|
|
|
*/ |
123
|
|
|
public function storageVal($val) |
124
|
|
|
{ |
125
|
|
|
$val = $this->dateTimeVal($val); |
126
|
|
|
|
127
|
|
|
if ($val instanceof DateTimeInterface) { |
128
|
|
|
return $val->format('Y-m-d H:i:s'); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
if ($this['allowNull']) { |
132
|
|
|
return null; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
throw new Exception( |
136
|
|
|
'Invalid date/time value. Must be a DateTimeInterface instance.' |
137
|
|
|
); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Format a date/time object to string. |
142
|
|
|
* |
143
|
|
|
* @see AbstractProperty::displayVal() |
144
|
|
|
* |
145
|
|
|
* @param mixed $val The value to to convert for display. |
146
|
|
|
* @param array $options Optional display options. |
147
|
|
|
* @return string |
148
|
|
|
*/ |
149
|
|
|
public function displayVal($val, array $options = []) |
150
|
|
|
{ |
151
|
|
|
$val = $this->dateTimeVal($val); |
152
|
|
|
if ($val === null) { |
153
|
|
|
return ''; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
if (isset($options['format'])) { |
157
|
|
|
$format = $options['format']; |
158
|
|
|
} else { |
159
|
|
|
$format = $this->getFormat(); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
return $val->format($format); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* @param string|DateTime|null $min The minimum allowed value. |
167
|
|
|
* @throws InvalidArgumentException If the date/time is invalid. |
168
|
|
|
* @return self |
169
|
|
|
*/ |
170
|
|
View Code Duplication |
public function setMin($min) |
|
|
|
|
171
|
|
|
{ |
172
|
|
|
if ($min === null) { |
173
|
|
|
$this->min = null; |
174
|
|
|
return $this; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
if (is_string($min)) { |
178
|
|
|
try { |
179
|
|
|
$min = new DateTime($min); |
180
|
|
|
} catch (Exception $e) { |
181
|
|
|
throw new InvalidArgumentException( |
182
|
|
|
'Can not set min: '.$e->getMessage() |
183
|
|
|
); |
184
|
|
|
} |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
if (!($min instanceof DateTimeInterface)) { |
188
|
|
|
throw new InvalidArgumentException( |
189
|
|
|
'Invalid min' |
190
|
|
|
); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
$this->min = $min; |
194
|
|
|
return $this; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* @return DateTimeInterface|null |
199
|
|
|
*/ |
200
|
|
|
public function getMin() |
201
|
|
|
{ |
202
|
|
|
return $this->min; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* @param string|DateTime|null $max The maximum allowed value. |
207
|
|
|
* @throws InvalidArgumentException If the date/time is invalid. |
208
|
|
|
* @return self |
209
|
|
|
*/ |
210
|
|
View Code Duplication |
public function setMax($max) |
|
|
|
|
211
|
|
|
{ |
212
|
|
|
if ($max === null) { |
213
|
|
|
$this->max = null; |
214
|
|
|
return $this; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
if (is_string($max)) { |
218
|
|
|
try { |
219
|
|
|
$max = new DateTime($max); |
220
|
|
|
} catch (Exception $e) { |
221
|
|
|
throw new InvalidArgumentException( |
222
|
|
|
'Can not set min: '.$e->getMessage() |
223
|
|
|
); |
224
|
|
|
} |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
if (!($max instanceof DateTimeInterface)) { |
228
|
|
|
throw new InvalidArgumentException( |
229
|
|
|
'Invalid max' |
230
|
|
|
); |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
$this->max = $max; |
234
|
|
|
return $this; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* @return DateTimeInterface|null |
239
|
|
|
*/ |
240
|
|
|
public function getMax() |
241
|
|
|
{ |
242
|
|
|
return $this->max; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @param string|null $format The date format. |
247
|
|
|
* @throws InvalidArgumentException If the format is not a string. |
248
|
|
|
* @return DateTimeProperty Chainable |
249
|
|
|
*/ |
250
|
|
View Code Duplication |
public function setFormat($format) |
|
|
|
|
251
|
|
|
{ |
252
|
|
|
if ($format === null) { |
253
|
|
|
$format = ''; |
254
|
|
|
} |
255
|
|
|
if (!is_string($format)) { |
256
|
|
|
throw new InvalidArgumentException( |
257
|
|
|
'Format must be a string' |
258
|
|
|
); |
259
|
|
|
} |
260
|
|
|
$this->format = $format; |
261
|
|
|
return $this; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* @return string |
266
|
|
|
*/ |
267
|
|
|
public function getFormat() |
268
|
|
|
{ |
269
|
|
|
return $this->format; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* @return array |
274
|
|
|
*/ |
275
|
|
|
public function validationMethods() |
276
|
|
|
{ |
277
|
|
|
$parentMethods = parent::validationMethods(); |
278
|
|
|
|
279
|
|
|
return array_merge($parentMethods, [ |
280
|
|
|
'min', |
281
|
|
|
'max', |
282
|
|
|
]); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* @return boolean |
287
|
|
|
*/ |
288
|
|
View Code Duplication |
public function validateMin() |
|
|
|
|
289
|
|
|
{ |
290
|
|
|
$min = $this->getMin(); |
291
|
|
|
if (!$min) { |
292
|
|
|
return true; |
293
|
|
|
} |
294
|
|
|
$valid = ($this->val() >= $min); |
|
|
|
|
295
|
|
|
if ($valid === false) { |
296
|
|
|
$this->validator()->error('The date is smaller than the minimum value', 'min'); |
|
|
|
|
297
|
|
|
} |
298
|
|
|
return $valid; |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* @return boolean |
303
|
|
|
*/ |
304
|
|
View Code Duplication |
public function validateMax() |
|
|
|
|
305
|
|
|
{ |
306
|
|
|
$max = $this->getMax(); |
307
|
|
|
if (!$max) { |
308
|
|
|
return true; |
309
|
|
|
} |
310
|
|
|
$valid = ($this->val() <= $max); |
|
|
|
|
311
|
|
|
if ($valid === false) { |
312
|
|
|
$this->validator()->error('The date is bigger than the maximum value', 'max'); |
|
|
|
|
313
|
|
|
} |
314
|
|
|
return $valid; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* @see StorablePropertyTrait::sqlType() |
319
|
|
|
* @return string |
320
|
|
|
*/ |
321
|
|
|
public function sqlType() |
322
|
|
|
{ |
323
|
|
|
return 'DATETIME'; |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* @see StorablePropertyTrait::sqlPdoType() |
328
|
|
|
* @return integer |
329
|
|
|
*/ |
330
|
|
|
public function sqlPdoType() |
331
|
|
|
{ |
332
|
|
|
return PDO::PARAM_STR; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* @param mixed $val Value to convert to DateTime. |
337
|
|
|
* @throws InvalidArgumentException If the value is not a valid datetime. |
338
|
|
|
* @return DateTimeInterface|null |
339
|
|
|
*/ |
340
|
|
|
private function dateTimeVal($val) |
341
|
|
|
{ |
342
|
|
|
if ($val === null || |
343
|
|
|
(is_string($val) && ! strlen(trim($val))) || |
344
|
|
|
(is_array($val) && ! count(array_filter($val, 'strlen'))) |
345
|
|
|
) { |
346
|
|
|
return null; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
if (is_int($val) && $this->isValidTimeStamp($val)) { |
350
|
|
|
$dateTime = new DateTime(); |
351
|
|
|
$val = $dateTime->setTimestamp($val); |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
if (is_string($val)) { |
355
|
|
|
$val = new DateTime($val); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
if (!($val instanceof DateTimeInterface)) { |
359
|
|
|
throw new InvalidArgumentException( |
360
|
|
|
'Val must be a valid date' |
361
|
|
|
); |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
return $val; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
/** |
368
|
|
|
* @param integer|string $timestamp Timestamp. |
369
|
|
|
* @return boolean |
370
|
|
|
*/ |
371
|
|
|
private function isValidTimeStamp($timestamp) |
372
|
|
|
{ |
373
|
|
|
return (is_int($timestamp)) |
374
|
|
|
&& ($timestamp <= PHP_INT_MAX) |
375
|
|
|
&& ($timestamp >= ~PHP_INT_MAX); |
376
|
|
|
} |
377
|
|
|
} |
378
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.