Carbonated   D
last analyzed

Complexity

Total Complexity 90

Size/Duplication

Total Lines 520
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 6
Bugs 2 Features 0
Metric Value
wmc 90
c 6
b 2
f 0
lcom 1
cbo 1
dl 0
loc 520
rs 4.8717

31 Methods

Rating   Name   Duplication   Size   Complexity  
A carbonatedTimestamps() 0 7 2
A carbonatedDates() 0 4 2
A carbonatedTimes() 0 4 2
A carbonatedAttributes() 0 4 1
A carbonatedAttributeType() 0 12 4
A carbonatedTimestampFormat() 0 4 2
A carbonatedDateFormat() 0 4 2
A carbonatedTimeFormat() 0 4 2
A jsonTimestampFormat() 0 4 2
A jsonDateFormat() 0 4 2
A jsonTimeFormat() 0 4 2
A jsonTimezone() 0 10 2
A databaseTimestampFormat() 0 4 2
A databaseDateFormat() 0 4 2
A databaseTimeFormat() 0 4 2
D carbonInstances() 0 38 10
A getWithCarbonAttribute() 0 11 1
B carbonatedTimezone() 0 13 5
A databaseTimezone() 0 10 3
B carbonatedAccessor() 0 27 6
B carbonatedMutator() 0 34 6
A requestIsJson() 0 4 1
A getDates() 0 4 1
A freshTimestamp() 0 6 1
B toArray() 0 15 5
A jsonSerialize() 0 4 1
C getAttributeValue() 0 25 7
B setAttribute() 0 21 6
A setCarbonInstances() 0 7 2
A ensureProperty() 0 13 3
A useLocalizedFormats() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Carbonated often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Carbonated, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace ThisVessel;
4
5
use Carbon\Carbon;
6
use Request;
7
8
trait Carbonated
9
{
10
    /**
11
     * Store carbon instances for reuse.
12
     *
13
     * @var object
14
     */
15
    protected $carbonInstances;
16
17
    /**
18
     * Indicate whether accessors should be overridden to return carbon instances.
19
     *
20
     * @var bool
21
     */
22
    protected $returnCarbon = false;
23
24
    /**
25
     * Get the attributes that should be handled as carbonated timestamps.
26
     *
27
     * @return array
28
     */
29
    public function carbonatedTimestamps()
30
    {
31
        // Add default fields for schema builder's timestamps() and softDeletes().
32
        $defaults = [static::CREATED_AT, static::UPDATED_AT, 'deleted_at'];
33
34
        return $this->ensureProperty($this, 'carbonatedTimestamps') ? array_unique(array_merge($this->carbonatedTimestamps, $defaults)) : $defaults;
35
    }
36
37
    /**
38
     * Get the attributes that should be handled as carbonated dates.
39
     *
40
     * @return array
41
     */
42
    public function carbonatedDates()
43
    {
44
        return $this->ensureProperty($this, 'carbonatedDates') ? (array) $this->carbonatedDates : [];
45
    }
46
47
    /**
48
     * Get the attributes that should be handled as carbonated times.
49
     *
50
     * @return array
51
     */
52
    public function carbonatedTimes()
53
    {
54
        return $this->ensureProperty($this, 'carbonatedTimes') ? (array) $this->carbonatedTimes : [];
55
    }
56
57
    /**
58
     * Get all attributes that should be handled by carbonated.
59
     *
60
     * @return array
61
     */
62
    public function carbonatedAttributes()
63
    {
64
        return array_merge($this->carbonatedTimestamps(), $this->carbonatedDates(), $this->carbonatedTimes());
65
    }
66
67
    /**
68
     * Get carbonated attribute type.
69
     *
70
     * @param string $key
71
     *
72
     * @return string
73
     */
74
    public function carbonatedAttributeType($key)
75
    {
76
        if (in_array($key, $this->carbonatedTimestamps())) {
77
            return 'timestamp';
78
        } elseif (in_array($key, $this->carbonatedDates())) {
79
            return 'date';
80
        } elseif (in_array($key, $this->carbonatedTimes())) {
81
            return 'time';
82
        }
83
84
        return false;
85
    }
86
87
    /**
88
     * Get the intended timestamp format for view output.
89
     *
90
     * @return string
91
     */
92
    public function carbonatedTimestampFormat()
93
    {
94
        return $this->ensureProperty($this, 'carbonatedTimestampFormat') ? (string) $this->carbonatedTimestampFormat : 'M d, Y g:ia';
95
    }
96
97
    /**
98
     * Get the intended date format for view output.
99
     *
100
     * @return string
101
     */
102
    public function carbonatedDateFormat()
103
    {
104
        return $this->ensureProperty($this, 'carbonatedDateFormat') ? (string) $this->carbonatedDateFormat : 'M d, Y';
105
    }
106
107
    /**
108
     * Get the intended date format for view output.
109
     *
110
     * @return string
111
     */
112
    public function carbonatedTimeFormat()
113
    {
114
        return $this->ensureProperty($this, 'carbonatedTimeFormat') ? (string) $this->carbonatedTimeFormat : 'g:ia';
115
    }
116
117
    /**
118
     * Get the intended timezone for view output.
119
     *
120
     * @return string
121
     */
122
    public function carbonatedTimezone()
123
    {
124
        // Check for $carbonatedTimezone property in model.
125
        if ($this->ensureProperty($this, 'carbonatedTimezone')) {
126
            return (string) $this->carbonatedTimezone;
127
        } // If not, check for an authenticated user with a $timezone property.
128
        elseif (class_exists(\Auth::class) && \Auth::check() && \Auth::user()->timezone) {
129
            return (string) \Auth::user()->timezone;
130
        }
131
132
        // Otherwise use same timezone as database.
133
        return $this->databaseTimezone();
134
    }
135
136
    /**
137
     * Get the intended timestamp for json output.
138
     *
139
     * @return string
140
     */
141
    public function jsonTimestampFormat()
142
    {
143
        return $this->ensureProperty($this, 'jsonTimestampFormat') ? (string) $this->jsonTimestampFormat : $this->databaseTimestampFormat();
144
    }
145
146
    /**
147
     * Get the intended date for json output.
148
     *
149
     * @return string
150
     */
151
    public function jsonDateFormat()
152
    {
153
        return $this->ensureProperty($this, 'jsonDateFormat') ? (string) $this->jsonDateFormat : $this->databaseDateFormat();
154
    }
155
156
    /**
157
     * Get the intended time for json output.
158
     *
159
     * @return string
160
     */
161
    public function jsonTimeFormat()
162
    {
163
        return $this->ensureProperty($this, 'jsonTimeFormat') ? (string) $this->jsonTimeFormat : $this->databaseTimeFormat();
164
    }
165
166
    /**
167
     * Get the intended timezone for json output.
168
     *
169
     * @return string
170
     */
171
    public function jsonTimezone()
172
    {
173
        // Check for $jsonTimezone property in model.
174
        if ($this->ensureProperty($this, 'jsonTimezone')) {
175
            return (string) $this->jsonTimezone;
176
        }
177
178
        // Otherwise use same timezone as database.
179
        return $this->databaseTimezone();
180
    }
181
182
    /**
183
     * Get the intended database format for timestamp storage.
184
     *
185
     * @return string
186
     */
187
    public function databaseTimestampFormat()
188
    {
189
        return $this->ensureProperty($this, 'databaseTimestampFormat') ? (string) $this->databaseTimestampFormat : 'Y-m-d H:i:s';
190
    }
191
192
    /**
193
     * Get the intended database format for date storage.
194
     *
195
     * @return string
196
     */
197
    public function databaseDateFormat()
198
    {
199
        return $this->ensureProperty($this, 'databaseDateFormat') ? (string) $this->databaseDateFormat : 'Y-m-d';
200
    }
201
202
    /**
203
     * Get the intended database format for time storage.
204
     *
205
     * @return string
206
     */
207
    public function databaseTimeFormat()
208
    {
209
        return $this->ensureProperty($this, 'databaseTimeFormat') ? (string) $this->databaseTimeFormat : 'H:i:s';
210
    }
211
212
    /**
213
     * Get the intended timezone for database storage.
214
     *
215
     * @return string
216
     */
217
    public function databaseTimezone()
218
    {
219
        // Check for $databaseTimezone property in model.
220
        if ($this->ensureProperty($this, 'databaseTimezone')) {
221
            return (string) $this->databaseTimezone;
222
        }
223
224
        // Otherwise use app's timezone configuration.
225
        return (string) function_exists('config') ? config('app.timezone') : 'UTC';
226
    }
227
228
    /**
229
     * Store and return carbon instances for reuse.
230
     *
231
     * @return object
232
     */
233
    protected function carbonInstances()
234
    {
235
        // Check if date/time fields have already been carbonated.
236
        if ($this->carbonInstances) {
237
            return $this->carbonInstances;
238
        }
239
240
        // If not, get timezones.
241
        $databaseTimezone = $this->databaseTimezone();
242
        $carbonatedTimezone = $this->carbonatedTimezone();
243
244
        $fieldFormats = [];
245
246
        // Get database field formats.
247
        foreach ($this->carbonatedTimestamps() as $field) {
248
            $fieldFormats[$field] = $this->databaseTimestampFormat();
249
        }
250
        foreach ($this->carbonatedDates() as $field) {
251
            $fieldFormats[$field] = $this->databaseDateFormat();
252
        }
253
        foreach ($this->carbonatedTimes() as $field) {
254
            $fieldFormats[$field] = $this->databaseTimeFormat();
255
        }
256
257
        // Create Carbon instances.
258
        $carbonInstances = [];
259
        foreach ($fieldFormats as $field => $format) {
260
            $value = isset($this->attributes[$field]) ? $this->attributes[$field] : null;
261
            $carbonInstance = $value ? Carbon::createFromFormat($format, $value, $databaseTimezone) : null;
262
            $carbonInstances[$field] = $carbonInstance ? $carbonInstance->timezone($carbonatedTimezone) : null;
263
        }
264
265
        // Store Carbon instances for future use.
266
        $this->carbonInstances = isset($carbonInstances) ? (object) $carbonInstances : null;
267
268
        // Return Carbon instances.
269
        return $this->carbonInstances;
270
    }
271
272
    /**
273
     * Return a clone of $this object and modify it's accessors.
274
     *
275
     * @return $this
276
     */
277
    public function getWithCarbonAttribute()
278
    {
279
        // Clone $this to preserve it's state.
280
        $clone = clone $this;
281
282
        // Modify clone's accessors.
283
        $clone->returnCarbon = true;
284
285
        // Return clone.
286
        return $clone;
287
    }
288
289
    /**
290
     * Access and format for front end.
291
     *
292
     * @param string $key
293
     * @param bool   $json
294
     *
295
     * @return string
296
     */
297
    public function carbonatedAccessor($key, $json = false)
298
    {
299
        // Initial accesor setup.
300
        $accessorType = $json ? 'json' : 'carbonated';
301
        $fieldType = $this->carbonatedAttributeType($key);
302
303
        // Get output format and timezone for conversion.
304
        $outputFormat = $this->{$accessorType.ucfirst($fieldType).'Format'}();
305
        $outputTimezone = $this->{$accessorType.'Timezone'}();
306
307
        // Get Carbon instance.
308
        /** @var Carbon $carbonInstance */
309
        $carbonInstance = $this->carbonInstances()->$key;
310
311
        if (!$carbonInstance) {
312
            return;
313
        }
314
315
        // Return formatted value.
316
        $timezonedInstance = $carbonInstance->timezone($outputTimezone);
317
318
        if ($this->useLocalizedFormats()) {
319
            return $timezonedInstance ? utf8_encode($timezonedInstance->formatLocalized($outputFormat)) : null;
320
        }
321
322
        return $carbonInstance ? $timezonedInstance->format($outputFormat) : null;
323
    }
324
325
    /**
326
     * Mutate to a storable value for database.
327
     *
328
     * @param string $key
329
     * @param mixed  $value
330
     *
331
     * @return string
332
     */
333
    public function carbonatedMutator($key, $value)
334
    {
335
        // Get type.
336
        $fieldType = $this->carbonatedAttributeType($key);
337
338
        // Get database format and timezone.
339
        $databaseFormat = $this->{'database'.ucfirst($fieldType).'Format'}();
340
        $databaseTimezone = $this->databaseTimezone();
341
342
        // If value is DateTime instance, convert to Carbon instance.
343
        if ($value instanceof \DateTime) {
344
            $value = Carbon::instance($value);
345
        }
346
347
        // It value is Carbon intance, return storable value.
348
        if ($value instanceof Carbon) {
349
            return $value->timezone($databaseTimezone)->format($databaseFormat);
350
        }
351
352
        // Otherwise, get input format and timezone for conversion.
353
        if (static::requestIsJson()) {
354
            $inputFormat = $this->{'json'.ucfirst($fieldType).'Format'}();
355
            $inputTimezone = $this->jsonTimezone();
356
        } else {
357
            $inputFormat = $this->{'carbonated'.ucfirst($fieldType).'Format'}();
358
            $inputTimezone = $this->carbonatedTimezone();
359
        }
360
361
        // Convert to Carbon instance.
362
        $carbonInstance = $value ? Carbon::createFromFormat($inputFormat, $value, $inputTimezone) : null;
363
364
        // Return storable value.
365
        return $carbonInstance ? $carbonInstance->timezone($databaseTimezone)->format($databaseFormat) : null;
366
    }
367
368
    /**
369
     * Check if request is some type of JSON (purposefully more lenient than current Request::isJson() helper).
370
     *
371
     * @return bool
372
     */
373
    public static function requestIsJson()
374
    {
375
        return request()->isJson();
376
    }
377
378
    /**
379
     * Override default getDates() to allow created_at and updated_at handling by carbonated.
380
     *
381
     * @return array
382
     */
383
    public function getDates()
384
    {
385
        return (array) $this->dates;
386
    }
387
388
    /**
389
     * Override default freshTimestamp() to be more explicit in setting timezone for storage.
390
     *
391
     * @return \Carbon\Carbon
392
     */
393
    public function freshTimestamp()
394
    {
395
        $databaseTimezone = $this->databaseTimezone();
396
397
        return Carbon::now($databaseTimezone);
398
    }
399
400
    /**
401
     * Override default toArray() to include our own accessors.
402
     *
403
     * @param bool $useJsonAccessors
404
     *
405
     * @return array
406
     */
407
    public function toArray($useJsonAccessors = false)
408
    {
409
        $attributes = $this->attributesToArray();
410
411
        // If returning JSON output, reference our own accessors for relevant date/time fields.
412
        if ($useJsonAccessors) {
413
            foreach ($attributes as $key => $value) {
414
                if (!$this->hasGetMutator($key) && in_array($key, $this->carbonatedAttributes())) {
415
                    $attributes[$key] = $this->carbonatedAccessor($key, true);
416
                }
417
            }
418
        }
419
420
        return array_merge($attributes, $this->relationsToArray());
421
    }
422
423
    /**
424
     * Override default jsonSerialize() to set $useJsonAccessors parameter.
425
     *
426
     * @return array
427
     */
428
    public function jsonSerialize()
429
    {
430
        return $this->toArray(true);
431
    }
432
433
    /**
434
     * Override default getAttributeValue() to include our own accessors.
435
     *
436
     * @param string $key
437
     *
438
     * @return mixed
439
     */
440
    public function getAttributeValue($key)
441
    {
442
        $value = $this->getAttributeFromArray($key);
443
444
        // Check for the presence of an accessor in our model.
445
        if ($this->hasGetMutator($key)) {
446
            return $this->mutateAttribute($key, $value);
447
        }
448
449
        // If no accessor found, reference our own accessors for relevant date/time fields.
450
        if (in_array($key, $this->carbonatedAttributes())) {
451
            $value = $this->returnCarbon ? $this->carbonInstances()->$key : $this->carbonatedAccessor($key);
452
        }
453
454
        // Otherwise, revert to default Eloquent behavour.
455
        if ($this->hasCast($key)) {
456
            $value = $this->castAttribute($key, $value);
457
        } elseif (in_array($key, $this->getDates())) {
458
            if (!is_null($value)) {
459
                return $this->asDateTime($value);
460
            }
461
        }
462
463
        return $value;
464
    }
465
466
    /**
467
     * Override default setAttribute() to include our own mutators.
468
     *
469
     * @param string $key
470
     * @param mixed  $value
471
     *
472
     * @return void
473
     */
474
    public function setAttribute($key, $value)
475
    {
476
        // Check for the presence of a mutator in our model.
477
        if ($this->hasSetMutator($key)) {
478
            $method = 'set'.studly_case($key).'Attribute';
479
480
            return $this->{$method}($value);
481
        } // If no mutator found, reference our own mutators for relevant date/time fields.
482
        elseif (in_array($key, $this->carbonatedAttributes())) {
483
            $value = $this->carbonatedMutator($key, $value);
484
        } // Otherwise, revert to default Eloquent behavour.
485
        elseif (in_array($key, $this->getDates()) && $value) {
486
            $value = $this->fromDateTime($value);
487
        }
488
489
        if ($this->isJsonCastable($key)) {
490
            $value = json_encode($value);
491
        }
492
493
        $this->attributes[$key] = $value;
494
    }
495
496
    /**
497
     * @param object $carbonInstances
498
     */
499
    public function setCarbonInstances($carbonInstances)
500
    {
501
        if (!is_object($carbonInstances)) {
502
            throw new \InvalidArgumentException('carbonInstances must be an object.');
503
        }
504
        $this->carbonInstances = $carbonInstances;
505
    }
506
507
    private function ensureProperty($instance, $propertyName)
508
    {
509
        if (!property_exists($instance, $propertyName)) {
510
            return false;
511
        }
512
513
        // Check property value for null and false values
514
        if (empty($instance->{$propertyName})) {
515
            return false;
516
        }
517
518
        return true;
519
    }
520
521
    private function useLocalizedFormats()
522
    {
523
        $localize = config('carbonated.localization', false);
524
525
        return $localize;
526
    }
527
}
528