1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Charcoal\Property; |
4
|
|
|
|
5
|
|
|
use PDO; |
6
|
|
|
use DomainException; |
7
|
|
|
use InvalidArgumentException; |
8
|
|
|
|
9
|
|
|
// From 'charcoal-property' |
10
|
|
|
use Charcoal\Property\AbstractProperty; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* ID Property |
14
|
|
|
*/ |
15
|
|
|
class IdProperty extends AbstractProperty |
16
|
|
|
{ |
17
|
|
|
const MODE_AUTO_INCREMENT = 'auto-increment'; |
18
|
|
|
const MODE_CUSTOM = 'custom'; |
19
|
|
|
const MODE_UNIQID = 'uniqid'; |
20
|
|
|
const MODE_UUID = 'uuid'; |
21
|
|
|
|
22
|
|
|
const DEFAULT_MODE = self::MODE_AUTO_INCREMENT; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* The ID mode. |
26
|
|
|
* |
27
|
|
|
* One of: |
28
|
|
|
* - `auto-increment` (default). Database auto-increment. |
29
|
|
|
* - `custom`. A user supplied unique identifier. |
30
|
|
|
* - `uniq`. Generated with php's `uniqid()`. |
31
|
|
|
* - `uuid`. A (randomly-generated) universally unique identifier (RFC-4122 v4) . |
32
|
|
|
* |
33
|
|
|
* @var string $mode |
34
|
|
|
*/ |
35
|
|
|
private $mode = self::DEFAULT_MODE; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Retrieve the property type. |
39
|
|
|
* |
40
|
|
|
* @return string |
41
|
|
|
*/ |
42
|
|
|
public function type() |
43
|
|
|
{ |
44
|
|
|
return 'id'; |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Ensure multiple can not be TRUE for ID property (ID must be unique per object). |
49
|
|
|
* |
50
|
|
|
* @see AbstractProperty::setMultiple() |
51
|
|
|
* |
52
|
|
|
* @see AbstractProperty::setMultiple() |
53
|
|
|
* @param boolean $flag The multiple flag. |
54
|
|
|
* @throws InvalidArgumentException If the multiple argument is TRUE (must be FALSE). |
55
|
|
|
* @return self |
56
|
|
|
*/ |
57
|
|
View Code Duplication |
public function setMultiple($flag) |
|
|
|
|
58
|
|
|
{ |
59
|
|
|
$flag = !!$flag; |
60
|
|
|
if ($flag === true) { |
61
|
|
|
throw new InvalidArgumentException( |
62
|
|
|
'The ID property does not support multiple values.' |
63
|
|
|
); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
return $this; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* Multiple is always FALSE for ID property. |
71
|
|
|
* |
72
|
|
|
* @see AbstractProperty::getMultiple() |
73
|
|
|
* @return boolean |
74
|
|
|
*/ |
75
|
|
|
public function getMultiple() |
76
|
|
|
{ |
77
|
|
|
return false; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Ensure l10n can not be TRUE for ID property (ID must be unique per object). |
82
|
|
|
* |
83
|
|
|
* @see AbstractProperty::setL10n() |
84
|
|
|
* @param boolean $flag The l10n, or "translatable" flag. |
85
|
|
|
* @throws InvalidArgumentException If the L10N argument is TRUE (must be FALSE). |
86
|
|
|
* @return self |
87
|
|
|
*/ |
88
|
|
View Code Duplication |
public function setL10n($flag) |
|
|
|
|
89
|
|
|
{ |
90
|
|
|
$flag = !!$flag; |
91
|
|
|
|
92
|
|
|
if ($flag === true) { |
93
|
|
|
throw new InvalidArgumentException( |
94
|
|
|
'The ID property can not be translatable.' |
95
|
|
|
); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
return $this; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* L10N is always FALSE for ID property. |
103
|
|
|
* |
104
|
|
|
* @see AbstractProperty::getL10n() |
105
|
|
|
* @return boolean |
106
|
|
|
*/ |
107
|
|
|
public function getL10n() |
108
|
|
|
{ |
109
|
|
|
return false; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Retrieve the available ID modes. |
114
|
|
|
* |
115
|
|
|
* @return array |
116
|
|
|
*/ |
117
|
|
|
public function availableModes() |
118
|
|
|
{ |
119
|
|
|
return [ |
120
|
|
|
self::MODE_AUTO_INCREMENT, |
121
|
|
|
self::MODE_CUSTOM, |
122
|
|
|
self::MODE_UNIQID, |
123
|
|
|
self::MODE_UUID |
124
|
|
|
]; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Set the allowed ID mode. |
129
|
|
|
* |
130
|
|
|
* @param string $mode The ID mode ("auto-increment", "custom", "uniqid" or "uuid"). |
131
|
|
|
* @throws InvalidArgumentException If the mode is not one of the 4 valid modes. |
132
|
|
|
* @return self |
133
|
|
|
*/ |
134
|
|
|
public function setMode($mode) |
135
|
|
|
{ |
136
|
|
|
$availableModes = $this->availableModes(); |
137
|
|
|
if (!in_array($mode, $availableModes)) { |
138
|
|
|
throw new InvalidArgumentException(sprintf( |
139
|
|
|
'Invalid ID mode. Must be one of "%s"', |
140
|
|
|
implode(', ', $availableModes) |
141
|
|
|
)); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
$this->mode = $mode; |
145
|
|
|
|
146
|
|
|
return $this; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Retrieve the allowed ID mode. |
151
|
|
|
* |
152
|
|
|
* @return string |
153
|
|
|
*/ |
154
|
|
|
public function getMode() |
155
|
|
|
{ |
156
|
|
|
return $this->mode; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Prepare the value for save. |
161
|
|
|
* |
162
|
|
|
* If no ID is set upon first save, then auto-generate it if necessary. |
163
|
|
|
* |
164
|
|
|
* @param mixed $val The value, at time of saving. |
165
|
|
|
* @return mixed |
166
|
|
|
*/ |
167
|
|
|
public function save($val) |
168
|
|
|
{ |
169
|
|
|
if (!$val) { |
170
|
|
|
$val = $this->autoGenerate(); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
return $val; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* @return boolean |
178
|
|
|
*/ |
179
|
|
|
public function validateRequired() |
180
|
|
|
{ |
181
|
|
|
$mode = $this->getMode(); |
182
|
|
|
|
183
|
|
|
if ($mode !== self::MODE_AUTO_INCREMENT) { |
184
|
|
|
return parent::validateRequired(); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
return true; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Auto-generate a value upon first save. |
192
|
|
|
* |
193
|
|
|
* - self::MODE_AUTOINCREMENT |
194
|
|
|
* - null: The auto-generated value should be handled at the database driver level. |
195
|
|
|
* - self::MODE_CUSTOM |
196
|
|
|
* - null: Custom mode must be defined elsewhere. |
197
|
|
|
* - self::MODE_UNIQID |
198
|
|
|
* - A random 13-char `uniqid()` value. |
199
|
|
|
* - self::MODE_UUID |
200
|
|
|
* - A random RFC-4122 UUID value. |
201
|
|
|
* |
202
|
|
|
* @throws DomainException If the mode does not have a value generator. |
203
|
|
|
* @return string|null |
204
|
|
|
*/ |
205
|
|
|
public function autoGenerate() |
206
|
|
|
{ |
207
|
|
|
$mode = $this['mode']; |
208
|
|
|
|
209
|
|
|
if ($mode === self::MODE_UNIQID) { |
210
|
|
|
return uniqid(); |
211
|
|
|
} elseif ($mode === self::MODE_UUID) { |
212
|
|
|
return $this->generateUuid(); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
return null; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Generate a RFC-4122 v4 Universally-Unique Identifier (UUID). |
220
|
|
|
* |
221
|
|
|
* @see http://tools.ietf.org/html/rfc4122#section-4.4 |
222
|
|
|
* @return string |
223
|
|
|
*/ |
224
|
|
|
private function generateUuid() |
225
|
|
|
{ |
226
|
|
|
// Generate a uniq string identifer (valid v4 uuid) |
227
|
|
|
return sprintf( |
228
|
|
|
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x', |
229
|
|
|
// 32 bits for "time_low" flag |
230
|
|
|
mt_rand(0, 0xffff), |
231
|
|
|
mt_rand(0, 0xffff), |
232
|
|
|
// 16 bits for "time_mid" flag |
233
|
|
|
mt_rand(0, 0xffff), |
234
|
|
|
// 16 bits for "time_hi_andVersion" flat (4 most significant bits holds version number) |
235
|
|
|
(mt_rand(0, 0x0fff) | 0x4000), |
236
|
|
|
// 16 bits, 8 bits for "clk_seq_hi_res" flag and 8 bits for "clk_seq_low" flag |
237
|
|
|
// two most significant bits holds zero and one for variant DCE1.1 |
238
|
|
|
(mt_rand(0, 0x3fff) | 0x8000), |
239
|
|
|
// 48 bits for "node" flag |
240
|
|
|
mt_rand(0, 0xffff), |
241
|
|
|
mt_rand(0, 0xffff), |
242
|
|
|
mt_rand(0, 0xffff) |
243
|
|
|
); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Get additional SQL field options. |
248
|
|
|
* |
249
|
|
|
* |
250
|
|
|
* @see StorablePropertyTrait::sqlExtra() |
251
|
|
|
* @return string |
252
|
|
|
*/ |
253
|
|
|
public function sqlExtra() |
254
|
|
|
{ |
255
|
|
|
$mode = $this->getMode(); |
256
|
|
|
|
257
|
|
|
if ($mode === self::MODE_AUTO_INCREMENT) { |
258
|
|
|
return 'AUTO_INCREMENT'; |
259
|
|
|
} else { |
260
|
|
|
return null; |
261
|
|
|
} |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* Get the SQL data type (Storage format). |
266
|
|
|
* |
267
|
|
|
* - For "auto-increment" ids, it is an integer. |
268
|
|
|
* - For "custom" ids, it is a 255-varchar string. |
269
|
|
|
* - For "uniqid" ids, it is a 13-char string. |
270
|
|
|
* - For "uuid" id, it is a 36-char string. |
271
|
|
|
* |
272
|
|
|
* @see StorablePropertyTrait::sqlType() |
273
|
|
|
* @return string The SQL type. |
274
|
|
|
*/ |
275
|
|
|
public function sqlType() |
276
|
|
|
{ |
277
|
|
|
$mode = $this->getMode(); |
278
|
|
|
|
279
|
|
|
if ($mode === self::MODE_AUTO_INCREMENT) { |
280
|
|
|
$dbDriver = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME); |
281
|
|
|
if ($dbDriver === 'sqlite') { |
282
|
|
|
return 'INT'; |
283
|
|
|
} else { |
284
|
|
|
return 'INT(10) UNSIGNED'; |
285
|
|
|
} |
286
|
|
|
} elseif ($mode === self::MODE_UNIQID) { |
287
|
|
|
return 'CHAR(13)'; |
288
|
|
|
} elseif ($mode === self::MODE_UUID) { |
289
|
|
|
return 'CHAR(36)'; |
290
|
|
|
} elseif ($mode === self::MODE_CUSTOM) { |
291
|
|
|
return 'VARCHAR(255)'; |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* Get the PDO data type. |
297
|
|
|
* |
298
|
|
|
* @see StorablePropertyTrait::sqlPdoType() |
299
|
|
|
* @return integer |
300
|
|
|
*/ |
301
|
|
|
public function sqlPdoType() |
302
|
|
|
{ |
303
|
|
|
$mode = $this->getMode(); |
304
|
|
|
|
305
|
|
|
if ($mode === self::MODE_AUTO_INCREMENT) { |
306
|
|
|
return PDO::PARAM_INT; |
307
|
|
|
} else { |
308
|
|
|
return PDO::PARAM_STR; |
309
|
|
|
} |
310
|
|
|
} |
311
|
|
|
} |
312
|
|
|
|
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.