Test Setup Failed
Push — master ( 6592af...c06444 )
by Chauncey
08:19
created

IdProperty::validateRequired()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 0
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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