Field   D
last analyzed

Complexity

Total Complexity 80

Size/Duplication

Total Lines 705
Duplicated Lines 15.18 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 107
loc 705
rs 4.4444
c 0
b 0
f 0
wmc 80
lcom 1
cbo 5

40 Methods

Rating   Name   Duplication   Size   Complexity  
A type() 0 4 1
A placeholder() 0 4 1
A display() 0 4 1
A getModel() 0 4 1
A setterGetter() 8 9 3
A set() 0 6 1
A get() 9 10 3
A __toString() 0 4 1
A caption() 11 12 3
A hint() 0 4 1
A group() 0 4 1
A readonly() 0 4 1
A editable() 0 4 1
A allowHTML() 0 4 1
A searchable() 0 4 1
A sortable() 0 4 1
A actual() 0 4 1
A system() 7 8 2
A hidden() 0 4 1
A length() 0 4 1
A defaultValue() 8 8 2
A visible() 0 4 1
B listData() 22 24 5
A emptyText() 0 4 1
A onField() 0 4 1
A setModel() 0 4 1
A setValueList() 0 4 1
A enum() 0 4 1
A updateModifyQuery() 8 13 3
A refModel() 0 14 3
A datatype() 0 4 1
B getSQL() 0 16 7
A getExpr() 0 7 3
A mandatory() 0 4 1
A from() 0 12 3
B updateSelectQuery() 0 19 7
A updateInsertQuery() 8 13 3
C getBooleanValue() 26 26 7
A required() 0 4 1
A calculated() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Field 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 Field, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Class implementing field of relational database. One object is created
4
 * for every field in a model. Essentially this object is responsible for
5
 * storing information about meta-information and assisting model in
6
 * query creation where particular field is included.
7
 */
8
class Field extends AbstractModel
9
{
10
    public $type = 'string';
11
    public $readonly = false;
12
    public $system = false;
13
    public $hidden = false;
14
    public $editable = true;
15
    public $visible = true;
16
    public $display = null;
17
    public $caption = null;
18
    public $placeholder = null;
19
    public $hint = null;
20
    public $group = null;
21
    public $allowHTML = false;
22
    public $sortable = false;
23
    public $searchable = false;
24
    public $mandatory = false;
25
    public $has_default_value = false;
26
    public $defaultValue = null;
27
    public $emptyText = null;
28
    public $auto_track_element = true;
29
    public $listData = null;
30
    public $theModel = null;
31
32
    public $relation = null;
33
    public $actual_field = null;
34
35
    public $onField = null;
36
37
    /**
38
     * Joins
39
     * @see from()
40
     * @var array
41
     */
42
    public $relations = array();
43
44
    /** @var SQL_Model */
45
    public $owner;
46
47
    /**
48
     * Implementation of generic setter-getter method which supports "UNDEFINED"
49
     * constant. This method is used by all other sette-getters.
50
     *
51
     * @param string $type  Corresponds to the name of property of a field
52
     * @param mixed  $value New value for a property.
53
     *
54
     * @return mixed|$this new or current property (if value is UNDEFINED)
55
     */
56 View Code Duplication
    public function setterGetter($type, $value = UNDEFINED)
57
    {
58
        if ($value === UNDEFINED) {
59
            return isset($this->$type) ? $this->$type : null;
60
        }
61
        $this->$type = $value;
62
63
        return $this;
64
    }
65
66
    /**
67
     * Sets the value of the field. Identical to $model[$fieldname]=$value.
68
     *
69
     * @param mixed $value new value
70
     *
71
     * @return $this
72
     */
73
    public function set($value = null)
74
    {
75
        $this->owner->set($this->short_name, $value);
76
77
        return $this;
78
    }
79
80
    /**
81
     * Get the value of the field of a loaded model. If model is not loaded
82
     * will return default value instead.
83
     *
84
     * @return mixed current value of a field
85
     */
86 View Code Duplication
    public function get()
87
    {
88
        if ($this->owner->loaded()
89
            || isset($this->owner->data[$this->short_name])
90
        ) {
91
            return $this->owner->get($this->short_name);
92
        }
93
94
        return $this->defaultValue();
95
    }
96
97
    /**
98
     * If field is accidentally converted to string, provide some
99
     * descriptive information.
100
     *
101
     * @return string descriptive
102
     */
103
    public function __toString()
104
    {
105
        return get_class($this)." ['".$this->short_name."']".' of '.$this->owner;
106
    }
107
108
    /**
109
     * Logical type of model field. This universal type is recognized by
110
     * view controllers (such as Controller_MVCForm, Controller_MVCGrid to
111
     * convert into supported field types.
112
     *
113
     * @param string $t new value
114
     *
115
     * @return string|$this current value if $t=UNDEFINED
116
     */
117
    public function type($t = UNDEFINED)
118
    {
119
        return $this->setterGetter('type', $t);
120
    }
121
122
    /**
123
     * Sets field caption which will be used by forms, grids and other view
124
     * elements as a label. The caption will be localized through $app->_().
125
     *
126
     * @param string $t new value
127
     *
128
     * @return string|$this current value if $t=UNDEFINED
129
     */
130 View Code Duplication
    public function caption($t = UNDEFINED)
131
    {
132
        if ($t === UNDEFINED && !$this->caption) {
133
            return ucwords(strtr(
134
                preg_replace('/_id$/', '', $this->short_name),
135
                '_',
136
                ' '
137
            ));
138
        }
139
140
        return $this->setterGetter('caption', $t);
141
    }
142
143
    /**
144
     * Sets field hint which will be used by forms.
145
     *
146
     * @param string $t new value
147
     *
148
     * @return string|$this current value if $t=UNDEFINED
149
     */
150
    public function hint($t = UNDEFINED)
151
    {
152
        return $this->setterGetter('hint', $t);
153
    }
154
155
    /**
156
     * Sets field placeholder (gray text inside input when it's empty).
157
     *
158
     * @param string $t new value
159
     *
160
     * @return string|$this current value if $t=UNDEFINED
161
     */
162
    public function placeholder($t = UNDEFINED)
163
    {
164
        return $this->setterGetter('placeholder', $t);
165
    }
166
167
    /**
168
     * While you may use visible(), editable() to include or exclude fields
169
     * from appearing in certain scenarios, you can also define a group which
170
     * you then can display instead of listing all fields manually inside
171
     * setModel(). Read more about Actual Fields.
172
     *
173
     * @param string $t new value
174
     *
175
     * @return string|$this current value if $t=UNDEFINED
176
     */
177
    public function group($t = UNDEFINED)
178
    {
179
        return $this->setterGetter('group', $t);
180
    }
181
182
    /**
183
     * Read only setting will affect the way how field is presented by views.
184
     * While model field is still writable directly, the Form will not try to
185
     * change the value of this field.
186
     *
187
     * @param bool $t new value
188
     *
189
     * @return bool|$this current value if $t=UNDEFINED
190
     */
191
    public function readonly($t = UNDEFINED)
192
    {
193
        return $this->setterGetter('readonly', $t);
194
    }
195
196
    /**
197
     * Mandatory setting will affect the way how field is presented by views.
198
     * For example, form can display asterisk near the field or add additional
199
     * validation.
200
     * This property will not affect the direct use of the field inside
201
     * model. If you would like that your model complains about empty fields,
202
     * you should edit beforeSave hook.
203
     *
204
     * @param bool $t new value
205
     *
206
     * @return bool|$this current value if $t=UNDEFINED
207
     */
208
    public function mandatory($t = UNDEFINED)
209
    {
210
        return $this->setterGetter('mandatory', $t);
211
    }
212
213
    /**
214
     * Set editable to false, if you want to exclude field from forms
215
     * or other means of editing data. This does not affect the actual model
216
     * values.
217
     *
218
     * @param bool $t new value
219
     *
220
     * @return bool|$this current value if $t=UNDEFINED
221
     */
222
    public function editable($t = UNDEFINED)
223
    {
224
        return $this->setterGetter('editable', $t);
225
    }
226
227
    /**
228
     * Configures the behavior of Form to disable tag stripping form user input.
229
     * By default all tags are stripped, setting this property to true will
230
     * no longer strip tags.
231
     *
232
     * @param bool $t new value
233
     *
234
     * @return bool|$this current value if $t=UNDEFINED
235
     */
236
    public function allowHTML($t = UNDEFINED)
237
    {
238
        return $this->setterGetter('allowHTML', $t);
239
    }
240
241
    /**
242
     * Setting searchable(true) will instruct Filter and similar views that
243
     * it should be possible to perform search by this field.
244
     *
245
     * @param bool $t new value
246
     *
247
     * @return bool|$this current value if $t=UNDEFINED
248
     */
249
    public function searchable($t = UNDEFINED)
250
    {
251
        return $this->setterGetter('searchable', $t);
252
    }
253
254
    /**
255
     * Will instruct Grid and similar views that the sorting controls must be
256
     * enabled for this field.
257
     *
258
     * @param bool $t new value
259
     *
260
     * @return bool|$this current value if $t=UNDEFINED
261
     */
262
    public function sortable($t = UNDEFINED)
263
    {
264
        return $this->setterGetter('sortable', $t);
265
    }
266
267
    /**
268
     * Normally views will attempt to pick most suitable way to present field.
269
     * For example, type='date' will be presented with DatePicker field in form.
270
     * You might be using add-ons or might have created your own field class.
271
     * If you would like to use it to present the field, use display(). If you
272
     * specify string it will be used by all views, otherwise specify it as
273
     * associtive array:.
274
     *
275
     *     $field->display(array('form'=>'line','grid'=>'button'));
276
     *
277
     * @param mixed $t new value
278
     *
279
     * @return mixed|$this current value if $t=UNDEFINED
280
     */
281
    public function display($t = UNDEFINED)
282
    {
283
        return $this->setterGetter('display', $t);
284
    }
285
286
    /**
287
     * In most cases $model['field'] would match "field" inside a database. In
288
     * some cases, however, you would want to use different database field. This
289
     * can happen when you join multiple tables and 'field' appears in multiple
290
     * tables.
291
     *
292
     * You can specify actual field when you declare a field within a model:
293
     *
294
     *     $model->addField('modelfield','dbfield');
295
     *
296
     * If you are unable to use addField (such as using custom field class),
297
     * you can use actual() modifier:
298
     *
299
     *     $model->add('filestore/File','modelfield')->actual('dbfield');
300
     *
301
     * Another potential use is if your database structure does not match
302
     * model convention:
303
     *
304
     *     $model->hasOne('Book')->actual('IDBOOK');
305
     *
306
     * @param string $t new value
307
     *
308
     * @return string|$this current value if $t=UNDEFINED
309
     */
310
    public function actual($t = UNDEFINED)
311
    {
312
        return $this->setterGetter('actual_field', $t);
313
    }
314
315
    /**
316
     * Marking field as system will cause it to always be loaded, even if
317
     * it's not requested through Actual Fields. It will also hide the field
318
     * making it dissapear from Grids and Forms. A good examples of system
319
     * fields are "id" or "created_dts".
320
     *
321
     * @param bool $t new value
322
     *
323
     * @return bool|$this current value if $t=UNDEFINED
324
     */
325 View Code Duplication
    public function system($t = UNDEFINED)
326
    {
327
        if ($t === true) {
328
            $this->editable(false)->visible(false);
329
        }
330
331
        return $this->setterGetter('system', $t);
332
    }
333
334
    /**
335
     * Hide field. Not sure!
336
     *
337
     * @param bool $t new value
338
     *
339
     * @return bool|$this current value if $t=UNDEFINED
340
     */
341
    public function hidden($t = UNDEFINED)
342
    {
343
        return $this->setterGetter('hidden', $t);
344
    }
345
346
    /**
347
     * This will provide a HTML settings on a field for maximum field size.
348
     * The length of a field will not be enforced by this setting.
349
     *
350
     * @param int $t new value
351
     *
352
     * @return int|$this current value if $t=UNDEFINED
353
     */
354
    public function length($t = UNDEFINED)
355
    {
356
        return $this->setterGetter('length', $t);
357
    }
358
359
    /**
360
     * Default Value is used inside forms when you present them without loaded
361
     * data. This does not change how model works, which will simply avoid
362
     * including unchanged field into insert/update queries.
363
     *
364
     * @param bool $t new value
365
     *
366
     * @return bool|$this current value if $t=UNDEFINED
367
     */
368 View Code Duplication
    public function defaultValue($t = UNDEFINED)
369
    {
370
        if ($t !== UNDEFINED) {
371
            $this->has_default_value = true;
372
        }
373
374
        return $this->setterGetter('defaultValue', $t);
375
    }
376
377
    /**
378
     * Controls field appearance in Grid or similar views.
379
     *
380
     * @param bool $t new value
381
     *
382
     * @return bool|$this current value if $t=UNDEFINED
383
     */
384
    public function visible($t = UNDEFINED)
385
    {
386
        return $this->setterGetter('visible', $t);
387
    }
388
389
    /**
390
     * Supplies a list data for multi-value fields (selects, radio buttons,
391
     * checkbox area, autocomplete). You may also use enum(). This setting
392
     * is typically used with a static falues (Male / Female), if your field
393
     * values could be described through a different model, use setModel()
394
     * or better yet - hasOne().
395
     *
396
     * @param array $t Array( id => val )
397
     *
398
     * @return array|$this current value if $t=UNDEFINED
399
     */
400 View Code Duplication
    public function listData($t = UNDEFINED)
401
    {
402
        if ($this->type() === 'boolean' && $t !== UNDEFINED) {
403
            $this->owner->addHook('afterLoad,afterUpdate,afterInsert', function ($m) use ($t) {
404
                // Normalize boolean data
405
                $val = !array_search($m->data[$this->short_name], $t);
406
                if ($val === false) {
407
                    return;
408
                } // do nothing
409
                $m->data[$this->short_name] = (boolean) $val;
410
            });
411
412
            $this->owner->addHook('beforeUpdate,beforeInsert', function ($m, &$data) use ($t) {
413
                // De-Normalize boolean data
414
                $val = (int) (!$m->get($this->short_name));
415
                if (!isset($t[$val])) {
416
                    return;
417
                }  // do nothing
418
                $data->set($this->short_name, $t[$val]);
419
            });
420
        }
421
422
        return $this->setterGetter('listData', $t);
423
    }
424
425
    /**
426
     * What to display when nothing is selected or entered? This will be
427
     * displayed on a drop-down when no value is selected: ("Choose ..")
428
     * if you are using this setting with a text field it will set a
429
     * placeholder HTML property.
430
     *
431
     * @param string $t new value
432
     *
433
     * @return string|$this current value if $t=UNDEFINED
434
     */
435
    public function emptyText($t = UNDEFINED)
436
    {
437
        return $this->setterGetter('emptyText', $t);
438
    }
439
440
    /**
441
     * Pass a callback to onField() which is executed with the argument
442
     * containing a field view, when model is associated with a form.
443
     *
444
     * @param callback $c new value
445
     *
446
     * @return string|$this current value if $t=UNDEFINED
447
     */
448
    public function onField($c = UNDEFINED)
449
    {
450
        return $this->setterGetter('onField', $c);
451
    }
452
453
    /**
454
     * Will execute setModel() on a field. Some fields will change their
455
     * behaviour with this. The value is a string (either Model_Book or Book)
456
     * but you might be able to use object also.
457
     *
458
     * I suggest to use $model->hasOne($model) instead of setModel($model)
459
     *
460
     * @param string $t new value
461
     *
462
     * @return string|$this current value if $t=UNDEFINED
463
     */
464
    public function setModel($t = UNDEFINED)
465
    {
466
        return $this->setterGetter('theModel', $t);
467
    }
468
469
    /**
470
     * Returns current model. This is different than other setters getters,
471
     * but it's done to keep consistency with the rest of Agile Toolkit.
472
     *
473
     * @return string current associated model Class
474
     */
475
    public function getModel()
476
    {
477
        return $this->theModel;
478
    }
479
480
    /**
481
     * Same as listData().
482
     *
483
     * @param array $t Array( id => val )
484
     *
485
     * @return array current value if $t=UNDEFINED
486
     */
487
    public function setValueList($t)
488
    {
489
        return $this->listData($t);
490
    }
491
492
    /**
493
     * Similar to listData() but accepts array of values instead of hash:.
494
     *
495
     *     listData(array(1=>'Male', 2=>'Female'));
496
     *     enum(array('male','female'));
497
     *
498
     * The value will be stored in database and also displayed to user.
499
     *
500
     * @param array $t Array( id => val )
501
     *
502
     * @return $this
503
     */
504
    public function enum($t)
505
    {
506
        return $this->listData(array_combine($t, $t));
507
    }
508
509
    /**
510
     * Binds the field to a relation (returned by join() function).
511
     *
512
     * @param mixed $m the result of join() function
513
     *
514
     * @return $this|object or the relation if $m is UNDEFINED
515
     */
516
    public function from($m = UNDEFINED)
517
    {
518
        if ($m === UNDEFINED) {
519
            return $this->relation;
520
        } elseif (is_object($m)) {
521
            $this->relation = $m;
522
        } else {
523
            $this->relations = $this->owner->relations[$m];
524
        }
525
526
        return $this;
527
    }
528
529
    /**
530
     * Modifies specified query to include this particular field.
531
     *
532
     * @param DB_dsql $select
533
     *
534
     * @return $this
535
     */
536
    public function updateSelectQuery($select)
537
    {
538
        $p = null;
539
        if (!empty($this->owner->relations)) {
540
            $p = $this->owner->table_alias ?: $this->owner->table;
541
        }
542
543
        if ($this->relation) {
544
            $select->field($this->actual_field ?: $this->short_name, $this->relation->short_name, $this->short_name);
545
        } elseif (!(is_null($this->actual_field)) && $this->actual_field != $this->short_name) {
546
            $select->field($this->actual_field, $p, $this->short_name);
547
548
            return $this;
549
        } else {
550
            $select->field($this->short_name, $p);
551
        }
552
553
        return $this;
554
    }
555
556
    /**
557
     * Modify insert query to set value of this field.
558
     *
559
     * @param DB_dsql $insert
560
     *
561
     * @return $this
562
     */
563 View Code Duplication
    public function updateInsertQuery($insert)
564
    {
565
        if ($this->relation) {
566
            $insert = $this->relation->dsql;
567
        }
568
569
        $insert->set(
570
            $this->actual_field ?: $this->short_name,
571
            $this->getSQL()
572
        );
573
574
        return $this;
575
    }
576
577
    /**
578
     * Modify update query to set value of this field.
579
     *
580
     * @param DB_dsql $modify
581
     *
582
     * @return $this
583
     */
584 View Code Duplication
    public function updateModifyQuery($modify)
585
    {
586
        if ($this->relation) {
587
            $modify = $this->relation->dsql;
588
        }
589
590
        $modify->set(
591
            $this->actual_field ?: $this->short_name,
592
            $this->getSQL()
593
        );
594
595
        return $this;
596
    }
597
598
    /**
599
     * Converts true/false into boolean representation according to the "enum".
600
     *
601
     * @param mixed $value
602
     *
603
     * @return int
604
     */
605 View Code Duplication
    public function getBooleanValue($value)
606
    {
607
        if ($value === null) {
608
            return;
609
        }
610
        if ($this->listData) {
611
            reset($this->listData);
612
            list(, $yes_value) = each($this->listData);
613
            list(, $no_value) = each($this->listData);
614
            if ($no_value === null) {
615
                $no_value = '';
616
            }
617
            /* not to convert N to Y */
618
            if ($yes_value == $value) {
619
                return $yes_value;
620
            }
621
            if ($no_value == $value) {
622
                return $no_value;
623
            }
624
        } else {
625
            $yes_value = 1;
626
            $no_value = 0;
627
        }
628
629
        return $value ? $yes_value : $no_value;
630
    }
631
632
    /**
633
     * Get value of this field formatted for SQL. Redefine if you need to convert.
634
     *
635
     * @return mixed
636
     */
637
    public function getSQL()
638
    {
639
        $val = $this->owner->get($this->short_name);
640
        if ($this->type == 'boolean') {
641
            $val = $this->getBooleanValue($val);
642
        }
643
        if ($val == ''
644
            && !$this->mandatory
645
            && ($this->listData || $this instanceof Field_Reference)
646
            && $this->type != 'boolean'
647
        ) {
648
            $val = null;
649
        }
650
651
        return $val;
652
    }
653
654
    /**
655
     * Returns field of this model.
656
     *
657
     * @return string
658
     */
659
    public function getExpr()
660
    {
661
        $q = $this->owner->_dsql();
662
663
        return $q->bt($this->relation ? $this->relation->short_name : $q->main_table).'.'.
664
            $q->bt($this->actual_field ?: $this->short_name);
665
    }
666
667
668
669
    // {{{ Deprecated methods
670
671
    /**
672
     * @deprecated 4.3.0 use mandatory() instead
673
     *
674
     * @param bool $t new value
675
     *
676
     * @return bool|$this current value if $t=UNDEFINED
677
     */
678
    public function required($t = UNDEFINED)
679
    {
680
        return $this->mandatory($t);
681
    }
682
683
    /** @deprecated 4.3.0 use hasOne instead */
684
    public function refModel($m)
685
    {
686
        if ($m == 'Model_Filestore_File') {
687
            return $this->add('filestore/Field_File');
688
        }
689
        $this->destroy();
690
        $fld = $this->add('Field_Reference');
691
692
        foreach ((Array) $this as $key => $val) {
693
            $fld->$key = $val;
694
        }
695
696
        return $this->owner->add($fld)->setModel(str_replace('Model_', '', $m));
697
    }
698
699
    /** @deprecated 4.3.0 use type($v) instead */
700
    public function datatype($v = UNDEFINED)
701
    {
702
        return $this->type($v);
703
    }
704
705
    /** @deprecated 4.3.0 use addExpression() instead */
706
    public function calculated($v = UNDEFINED)
707
    {
708
        throw $this->exception('calculated() field property is obsolete. Use addExpression() instead');
709
    }
710
711
    // }}}
712
}
713