Completed
Branch FET-10304-welcome-to-vue (644299)
by
unknown
94:54 queued 83:07
created

EE_Model_Field_Base   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 601
Duplicated Lines 5.66 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 34
loc 601
rs 6.5848
c 0
b 0
f 0
wmc 56
lcom 1
cbo 1

32 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A _construct_finalize() 0 13 1
A get_table_alias() 0 4 1
A get_table_column() 0 4 1
A get_model_name() 0 4 1
A get_name() 0 9 2
A get_nicename() 0 4 1
A is_nullable() 0 4 1
A is_auto_increment() 0 4 1
A get_default_value() 0 4 1
A get_qualified_column() 0 4 1
A prepare_for_get() 0 4 1
A prepare_for_use_in_db() 0 4 1
A prepare_for_set() 0 4 1
A prepare_for_set_from_db() 0 4 1
A prepare_for_pretty_echoing() 0 4 1
A getSchemaDescription() 0 4 1
A getSchemaType() 0 10 3
A setSchemaType() 0 5 1
A getSchemaProperties() 0 4 1
A getSchemaEnum() 0 4 1
A getSchemaFormat() 0 4 1
A setSchemaFormat() 0 5 1
A getSchemaReadonly() 0 4 1
A setSchemaReadOnly() 0 13 2
A get_wpdb_data_type() 0 4 1
C _get_wpdb_data_type() 0 27 8
B _get_wpdb_data_type_for_type_array() 0 26 4
B getSchema() 0 25 4
A is_db_only_field() 0 4 1
B validateSchemaType() 17 42 6
B validateSchemaFormat() 17 35 3

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 EE_Model_Field_Base 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 EE_Model_Field_Base, and based on these observations, apply Extract Interface, too.

1
<?php
2
use EventEspresso\core\entities\interfaces\HasSchemaInterface;
3
4
defined('EVENT_ESPRESSO_VERSION') || exit;
5
6
/**
7
 * EE_Model_Field_Base class
8
 * Base class for all EE_*_Field classes. These classes are for providing information and functions specific to each
9
 * field. They define the field's data type for insertion into the db (eg, if the value should be treated as an int,
10
 * float, or string), what values for the field are acceptable (eg, if setting EVT_ID to a float is acceptable), and
11
 * generally any functionality within EEM_Base or EE_Base_Class which depend on the field's type. (ie, you shouldn't
12
 * need any logic within your model or model object which are dependent on the field's type, ideally). For example,
13
 * EE_Serialized_Text_Field, specifies that any fields of this type should be serialized before insertion into the db
14
 * (prepare_for_insertion_into_db()), should be considered a string when inserting, updating, or using in a where
15
 * clause for any queries (get_wpdb_data_type()), should be unserialized when being retrieved from the db
16
 * (prepare_for_set_from_db()), and whatever else.
17
 *
18
 * @package               Event Espresso
19
 * @subpackage            /core/db_models/fields/EE_Model_Field_Base.php
20
 * @author                Michael Nelson
21
 */
22
abstract class EE_Model_Field_Base implements HasSchemaInterface
23
{
24
    /**
25
     * The alias for the table the column belongs to.
26
     * @var string
27
     */
28
    protected $_table_alias;
29
30
    /**
31
     * The actual db column name for the table
32
     * @var string
33
     */
34
    protected $_table_column;
35
36
37
    /**
38
     * The authoritative name for the table column (used by client code to reference the field).
39
     * @var string
40
     */
41
    protected $_name;
42
43
44
    /**
45
     * A description for the field.
46
     * @var string
47
     */
48
    protected $_nicename;
49
50
51
    /**
52
     * Whether the field is nullable or not
53
     * @var bool
54
     */
55
    protected $_nullable;
56
57
58
    /**
59
     * What the default value for the field should be.
60
     * @var mixed
61
     */
62
    protected $_default_value;
63
64
65
    /**
66
     * Other configuration for the field
67
     * @var mixed
68
     */
69
    protected $_other_config;
70
71
72
    /**
73
     * The name of the model this field is instantiated for.
74
     * @var string
75
     */
76
    protected $_model_name;
77
78
79
    /**
80
     * This should be a json-schema valid data type for the field.
81
     * @link http://json-schema.org/latest/json-schema-core.html#rfc.section.4.2
82
     * @var string
83
     */
84
    private $_schema_type = 'string';
85
86
87
    /**
88
     * If the schema has a defined format then it should be defined via this property.
89
     * @link http://json-schema.org/latest/json-schema-validation.html#rfc.section.7
90
     * @var string
91
     */
92
    private $_schema_format = '';
93
94
95
    /**
96
     * Indicates that the value of the field is managed exclusively by the server/model and not something
97
     * settable by client code.
98
     * @link http://json-schema.org/latest/json-schema-hypermedia.html#rfc.section.4.4
99
     * @var bool
100
     */
101
    private $_schema_readonly = false;
102
103
104
    /**
105
     * @param string $table_column
106
     * @param string $nicename
107
     * @param bool   $nullable
108
     * @param null   $default_value
109
     */
110
    public function __construct($table_column, $nicename, $nullable, $default_value = null)
111
    {
112
        $this->_table_column  = $table_column;
113
        $this->_nicename      = $nicename;
114
        $this->_nullable      = $nullable;
115
        $this->_default_value = $default_value;
116
    }
117
118
119
    /**
120
     * @param $table_alias
121
     * @param $name
122
     * @param $model_name
123
     */
124
    public function _construct_finalize($table_alias, $name, $model_name)
125
    {
126
        $this->_table_alias = $table_alias;
127
        $this->_name        = $name;
128
        $this->_model_name  = $model_name;
129
        /**
130
         * allow for changing the defaults
131
         */
132
        $this->_nicename      = apply_filters('FHEE__EE_Model_Field_Base___construct_finalize___nicename',
133
            $this->_nicename, $this);
134
        $this->_default_value = apply_filters('FHEE__EE_Model_Field_Base___construct_finalize___default_value',
135
            $this->_default_value, $this);
136
    }
137
138
    public function get_table_alias()
139
    {
140
        return $this->_table_alias;
141
    }
142
143
    public function get_table_column()
144
    {
145
        return $this->_table_column;
146
    }
147
148
    /**
149
     * Returns the name of the model this field is on. Eg 'Event' or 'Ticket_Datetime'
150
     *
151
     * @return string
152
     */
153
    public function get_model_name()
154
    {
155
        return $this->_model_name;
156
    }
157
158
    /**
159
     * @throws \EE_Error
160
     * @return string
161
     */
162
    public function get_name()
163
    {
164
        if ($this->_name) {
165
            return $this->_name;
166
        } else {
167
            throw new EE_Error(sprintf(__("Model field '%s' has no name set. Did you make a model and forget to call the parent model constructor?",
168
                "event_espresso"), get_class($this)));
169
        }
170
    }
171
172
    public function get_nicename()
173
    {
174
        return $this->_nicename;
175
    }
176
177
    public function is_nullable()
178
    {
179
        return $this->_nullable;
180
    }
181
182
    /**
183
     * returns whether this field is an auto-increment field or not. If it is, then
184
     * on insertion it can be null. However, on updates it must be present.
185
     *
186
     * @return boolean
187
     */
188
    public function is_auto_increment()
189
    {
190
        return false;
191
    }
192
193
    /**
194
     * The default value in the model object's value domain. See lengthy comment about
195
     * value domains at the top of EEM_Base
196
     *
197
     * @return mixed
198
     */
199
    public function get_default_value()
200
    {
201
        return $this->_default_value;
202
    }
203
204
    /**
205
     * Returns the table alias joined to the table column, however this isn't the right
206
     * table alias if the aliased table is being joined to. In that case, you can use
207
     * EE_Model_Parser::extract_table_alias_model_relation_chain_prefix() to find the table's current alias
208
     * in the current query
209
     *
210
     * @return string
211
     */
212
    public function get_qualified_column()
213
    {
214
        return $this->get_table_alias() . "." . $this->get_table_column();
215
    }
216
217
    /**
218
     * When get() is called on a model object (eg EE_Event), before returning its value,
219
     * call this function on it, allowing us to customize the returned value based on
220
     * the field's type. Eg, we may want ot serialize it, strip tags, etc. By default,
221
     * we simply return it.
222
     *
223
     * @param mixed $value_of_field_on_model_object
224
     * @return mixed
225
     */
226
    public function prepare_for_get($value_of_field_on_model_object)
227
    {
228
        return $value_of_field_on_model_object;
229
    }
230
231
    /**
232
     * When inserting or updating a field on a model object, run this function on each
233
     * value to prepare it for insertion into the db. We may want to add slashes, serialize it, etc.
234
     * By default, we do nothing.
235
     *
236
     * @param mixed $value_of_field_on_model_object
237
     * @return mixed
238
     */
239
    public function prepare_for_use_in_db($value_of_field_on_model_object)
240
    {
241
        return $value_of_field_on_model_object;
242
    }
243
244
    /**
245
     * When creating a brand-new model object, or setting a particular value for one of its fields, this function
246
     * is called before setting it on the model object. We may want to strip slashes, unserialize the value, etc.
247
     * By default, we do nothing.
248
     *
249
     * @param mixed $value_inputted_for_field_on_model_object
250
     * @return mixed
251
     */
252
    public function prepare_for_set($value_inputted_for_field_on_model_object)
253
    {
254
        return $value_inputted_for_field_on_model_object;
255
    }
256
257
258
    /**
259
     * When instantiating a model object from DB results, this function is called before setting each field.
260
     * We may want to serialize the value, etc. By default, we return the value using prepare_for_set() method as that
261
     * is the one child classes will most often define.
262
     *
263
     * @param mixed $value_found_in_db_for_model_object
264
     * @return mixed
265
     */
266
    public function prepare_for_set_from_db($value_found_in_db_for_model_object)
267
    {
268
        return $this->prepare_for_set($value_found_in_db_for_model_object);
269
    }
270
271
    /**
272
     * When echoing a field's value on a model object, this function is run to prepare the value for presentation in a
273
     * webpage. For example, we may want to output floats with 2 decimal places by default, dates as "Monday Jan 12,
274
     * 2013, at 3:23pm" instead of
275
     * "8765678632", or any other modifications to how the value should be displayed, but not modified itself.
276
     *
277
     * @param mixed $value_on_field_to_be_outputted
278
     * @return mixed
279
     */
280
    public function prepare_for_pretty_echoing($value_on_field_to_be_outputted)
281
    {
282
        return $value_on_field_to_be_outputted;
283
    }
284
285
286
    /**
287
     * Returns whatever is set as the nicename for the object.
288
     * @return string
289
     */
290
    public function getSchemaDescription()
291
    {
292
        return $this->get_nicename();
293
    }
294
295
296
    /**
297
     * Returns whatever is set as the $_schema_type property for the object.
298
     * Note: this will automatically add 'null' to the schema if the object is_nullable()
299
     * @return string|array
300
     */
301
    public function getSchemaType()
302
    {
303
        if ($this->is_nullable()) {
304
            $this->_schema_type = (array) $this->_schema_type;
0 ignored issues
show
Documentation Bug introduced by
It seems like (array) $this->_schema_type of type array is incompatible with the declared type string of property $_schema_type.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
305
            if (! in_array('null', $this->_schema_type)) {
306
                $this->_schema_type[] = 'null';
307
            };
308
        }
309
        return $this->_schema_type;
310
    }
311
312
313
    /**
314
     * Sets the _schema_type property.  Child classes should call this in their constructors to override the default state
315
     * for this property.
316
     * @param string|array $type
317
     * @throws InvalidArgumentException
318
     */
319
    protected function setSchemaType($type)
320
    {
321
        $this->validateSchemaType($type);
322
        $this->_schema_type = $type;
0 ignored issues
show
Documentation Bug introduced by
It seems like $type can also be of type array. However, the property $_schema_type is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
323
    }
324
325
326
    /**
327
     * This is usually present when the $_schema_type property is 'object'.  Any child classes will need to override
328
     * this method and return the properties for the schema.
329
     *
330
     * The reason this is not a property on the class is because there may be filters set on the values for the property
331
     * that won't be exposed on construct.  For example enum type schemas may have the enum values filtered.
332
     *
333
     * @return array
334
     */
335
    public function getSchemaProperties()
336
    {
337
        return array();
338
    }
339
340
341
    /**
342
     * If a child class has enum values, they should override this method and provide a simple array
343
     * of the enum values.
344
345
     * The reason this is not a property on the class is because there may be filterable enum values that
346
     * are set on the instantiated object that could be filtered after construct.
347
     *
348
     * @return array
349
     */
350
    public function getSchemaEnum()
351
    {
352
        return array();
353
    }
354
355
356
    /**
357
     * This returns the value of the $_schema_format property on the object.
358
     * @return string
359
     */
360
    public function getSchemaFormat()
361
    {
362
        return $this->_schema_format;
363
    }
364
365
366
    /**
367
     * Sets the schema format property.
368
     * @throws InvalidArgumentException
369
     * @param string $format
370
     */
371
    protected function setSchemaFormat($format)
372
    {
373
        $this->validateSchemaFormat($format);
374
        $this->_schema_format = $format;
375
    }
376
377
378
    /**
379
     * This returns the value of the $_schema_readonly property on the object.
380
     * @return bool
381
     */
382
    public function getSchemaReadonly()
383
    {
384
        return $this->_schema_readonly;
385
    }
386
387
388
    /**
389
     * This sets the value for the $_schema_readonly property.
390
     * @param bool $readonly  (only explicit boolean values are accepted)
391
     */
392
    protected function setSchemaReadOnly($readonly)
393
    {
394
        if (! is_bool($readonly)) {
395
            throw new InvalidArgumentException(
396
                sprintf(
397
                    esc_html__('The incoming argument (%s) must be a boolean.', 'event_espresso'),
398
                    print_r($readonly, true)
399
                )
400
            );
401
        }
402
403
        $this->_schema_readonly = $readonly;
404
    }
405
406
407
408
409
    /**
410
     * Return `%d`, `%s` or `%f` to indicate the data type for the field.
411
     * @uses _get_wpdb_data_type()
412
     *
413
     * @return string
414
     */
415
    public function get_wpdb_data_type()
416
    {
417
        return $this->_get_wpdb_data_type();
418
    }
419
420
421
    /**
422
     * Return `%d`, `%s` or `%f` to indicate the data type for the field that should be indicated in wpdb queries.
423
     * @param string $type  Included if a specific type is requested.
424
     * @uses get_schema_type()
425
     * @return string
426
     */
427
    protected function _get_wpdb_data_type($type='')
428
    {
429
        $type = empty($type) ? $this->getSchemaType() : $type;
430
431
        //if type is an array, then different parsing is required.
432
        if (is_array($type)) {
433
            return $this->_get_wpdb_data_type_for_type_array($type);
434
        }
435
436
        $wpdb_type = '%s';
437
        switch ($type) {
438
            case 'number':
439
                $wpdb_type = '%f';
440
                break;
441
            case 'integer':
442
            case 'boolean':
443
                $wpdb_type = '%d';
444
                break;
445
            case 'object':
446
                $properties = $this->getSchemaProperties();
447
                if (isset($properties['raw'], $properties['raw']['type'])) {
448
                    $wpdb_type = $this->_get_wpdb_data_type($properties['raw']['type']);
449
                }
450
                break; //leave at default
451
        }
452
        return $wpdb_type;
453
    }
454
455
456
457
    protected function _get_wpdb_data_type_for_type_array($type)
458
    {
459
        $type = (array) $type;
460
        //first let's flip because then we can do a faster key check
461
        $type = array_flip($type);
462
463
        //check for things that mean '%s'
464
        if (isset($type['string'],$type['object'],$type['array'])) {
465
            return '%s';
466
        }
467
468
        //if makes it past the above condition and there's float in the array
469
        //then the type is %f
470
        if (isset($type['number'])) {
471
            return '%f';
472
        }
473
474
        //if it makes it above the above conditions and there is an integer in the array
475
        //then the type is %d
476
        if (isset($type['integer'])) {
477
            return '%d';
478
        }
479
480
        //anything else is a string
481
        return '%s';
482
    }
483
484
485
    /**
486
     * This returns elements used to represent this field in the json schema.
487
     *
488
     * @link http://json-schema.org/
489
     * @return array
490
     */
491
    public function getSchema()
492
    {
493
        $schema = array(
494
            'description' => $this->getSchemaDescription(),
495
            'type' => $this->getSchemaType(),
496
            'readonly' => $this->getSchemaReadonly(),
497
        );
498
499
        //optional properties of the schema
500
        $enum = $this->getSchemaEnum();
501
        $properties = $this->getSchemaProperties();
502
        $format = $this->getSchemaFormat();
503
        if ($enum) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $enum of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
504
            $schema['enum'] = $enum;
505
        }
506
507
        if ($properties) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $properties of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
508
            $schema['properties'] = $properties;
509
        }
510
511
        if ($format) {
512
            $schema['format'] = $format;
513
        }
514
        return $schema;
515
    }
516
517
    /**
518
     * Some fields are in the database-only, (ie, used in queries etc), but shouldn't necessarily be part
519
     * of the model objects (ie, client code shouldn't care to ever see their value... if client code does
520
     * want to see their value, then they shouldn't be db-only fields!)
521
     * Eg, when doing events as custom post types, querying the post_type is essential, but
522
     * post_type is irrelevant for EE_Event objects (because they will ALL be of post_type 'esp_event').
523
     * By default, all fields aren't db-only.
524
     *
525
     * @return boolean
526
     */
527
    public function is_db_only_field()
528
    {
529
        return false;
530
    }
531
532
533
    /**
534
     * Validates the incoming string|array to ensure its an allowable type.
535
     * @throws InvalidArgumentException
536
     * @param string|array $type
537
     */
538
    private function validateSchemaType($type)
539
    {
540 View Code Duplication
        if (! (is_string($type) || is_array($type))) {
541
            throw new InvalidArgumentException(
542
                sprintf(
543
                    esc_html__('The incoming argument (%s) must be a string or an array.', 'event_espresso'),
544
                    print_r($type, true)
545
                )
546
            );
547
        }
548
549
        //validate allowable types.
550
        //@link http://json-schema.org/latest/json-schema-core.html#rfc.section.4.2
551
        $allowable_types = array_flip(
552
            array(
553
                'string',
554
                'number',
555
                'null',
556
                'object',
557
                'array',
558
                'boolean',
559
                'integer'
560
            )
561
        );
562
563
        if (is_array($type)) {
564
            foreach ($type as $item_in_type) {
565
                $this->validateSchemaType($item_in_type);
566
            }
567
            return;
568
        }
569
570 View Code Duplication
        if (! isset($allowable_types[$type])) {
571
            throw new InvalidArgumentException(
572
                sprintf(
573
                    esc_html__('The incoming argument (%1$s) must be one of the allowable types: %2$s', 'event_espresso'),
574
                    $type,
575
                    implode(',', array_flip($allowable_types))
576
                )
577
            );
578
        }
579
    }
580
581
582
    /**
583
     * Validates that the incoming format is an allowable string to use for the _schema_format property
584
     * @throws InvalidArgumentException
585
     * @param $format
586
     */
587
    private function validateSchemaFormat($format)
588
    {
589 View Code Duplication
        if (! is_string($format)) {
590
            throw new InvalidArgumentException(
591
                sprintf(
592
                    esc_html__('The incoming argument (%s) must be a string.', 'event_espresso'),
593
                    print_r($format, true)
594
                )
595
            );
596
        }
597
598
        //validate allowable format values
599
        //@link http://json-schema.org/latest/json-schema-validation.html#rfc.section.7
600
        $allowable_formats = array_flip(
601
            array(
602
                'date-time',
603
                'email',
604
                'hostname',
605
                'ipv4',
606
                'ipv6',
607
                'uri',
608
                'uriref'
609
            )
610
        );
611
612 View Code Duplication
        if (! isset($allowable_formats[$format])) {
613
            throw new InvalidArgumentException(
614
                sprintf(
615
                    esc_html__('The incoming argument (%1$s) must be one of the allowable formats: %2$s', 'event_espresso'),
616
                    $format,
617
                    implode(',', array_flip($allowable_formats))
618
                )
619
            );
620
        }
621
    }
622
}