Completed
Branch BUG-10738-inconsistency-in-ses... (cda363)
by
unknown
13:38 queued 12s
created

ModelVersionInfo   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 459
Duplicated Lines 5.66 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 0
Metric Value
dl 26
loc 459
rs 8.5454
c 0
b 0
f 0
wmc 49
lcom 2
cbo 4

19 Methods

Rating   Name   Duplication   Size   Complexity  
B modelChangesBetweenRequestedVersionAndCurrent() 13 13 5
B resourceChangesBetweenRequestedVersionAndCurrent() 13 13 5
B __construct() 0 35 6
A requestedVersion() 0 4 1
A modelChanges() 0 4 1
B modelsForRequestedVersion() 0 19 5
A isModelNameInThisVersion() 0 9 2
A loadModel() 0 17 2
B fieldsOnModelInThisVersion() 0 22 5
A isSubclassOfOne() 0 9 3
A fieldsIgnored() 0 7 1
A fieldIsIgnored() 0 4 1
A fieldsThatHaveRenderedFormat() 0 7 1
A fieldHasRenderedFormat() 0 4 1
A fieldsThatHavePrettyFormat() 0 7 1
A fieldHasPrettyFormat() 0 4 1
A resourceChanges() 0 4 1
A extraResourcePropertiesForModel() 0 12 4
A relationSettings() 0 15 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 ModelVersionInfo 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 ModelVersionInfo, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace EventEspresso\core\libraries\rest_api;
3
4
use EE_Registry;
5
use EED_Core_Rest_Api;
6
7
if (! defined('EVENT_ESPRESSO_VERSION')) {
8
    exit('No direct script access allowed');
9
}
10
11
12
13
/**
14
 * Model_Version_Info
15
 * Class for things that bridge the gap between API resources and PHP models describing
16
 * the underlying data.
17
 * This should really be the only place in the API that is directly aware of models,
18
 * everywhere should go through here to learn about the models and interact with them.
19
 * This is done so the API can serve request`s for a previous version from data
20
 * from the current version of core
21
 *
22
 * @package            Event Espresso
23
 * @subpackage         eea-rest-api
24
 * @author             Mike Nelson
25
 */
26
class ModelVersionInfo
27
{
28
29
    /**
30
     * Constant used in the $_model_changes array to indicate that a model
31
     * was completely new in this version
32
     */
33
    const MODEL_ADDED = 'model_added_in_this_version';
34
35
    /**
36
     * Top-level keys are versions (major and minor version numbers, eg "4.6")
37
     * next-level keys are model names (eg "Event") that underwent some change in that version
38
     * and the value is either Model_Version_Info::model_added (indicating the model is completely NEW in this version),
39
     * or it's an array where the values are model field names,
40
     * or API resource properties (ie, non-model fields that appear in REST API results)
41
     * If a version is missing then we don't know anything about what changes it introduced from the previous version
42
     *
43
     * @var array
44
     */
45
    protected $model_changes = array();
46
47
    /**
48
     * top-level keys are version numbers,
49
     * next-level keys are model CLASSNAMES (even parent classnames),
50
     * and next-level keys are extra resource properties to attach to those models' resources,
51
     * and next-level key-value pairs, where the keys are:
52
     * 'raw', 'type', 'nullable', 'table_alias', 'table_column',  'always_available'
53
     *
54
     * @var array
55
     */
56
    protected $resource_changes = array();
57
58
    /**
59
     * @var string indicating what version of the API was requested
60
     * (eg although core might be at version 4.8.11, they may have sent a request
61
     * for 4.6)
62
     */
63
    protected $requested_version = null;
64
65
    /**
66
     * Keys are model names, values are their classnames.
67
     * We cache this so we only need to calculate this once per request
68
     *
69
     * @var array
70
     */
71
    protected $cached_models_for_requested_version = null;
72
73
    /**
74
     * @var array
75
     */
76
    protected $cached_model_changes_between_requested_version_and_current = null;
77
78
    /**
79
     * @var array
80
     */
81
    protected $cached_resource_changes_between_requested_version_and_current = null;
82
83
    /**
84
     * 2d array where top-level keys are model names, 2nd-level keys are field names
85
     * and values are the actual field objects
86
     *
87
     * @var array
88
     */
89
    protected $cached_fields_on_models = array();
90
91
92
93
    /**
94
     * Model_Version_Info constructor.
95
     *
96
     * @param string $requested_version
97
     */
98
    public function __construct($requested_version)
99
    {
100
        $this->requested_version = (string)$requested_version;
101
        $this->model_changes = array(
102
            '4.8.29' => array(
103
                //first version where the REST API is in EE core, so no need
104
                //to specify how its different from the previous
105
            ),
106
        );
107
        //setup data for "extra" fields added onto resources which don't actually exist on models
108
        $this->resource_changes = apply_filters(
109
            'FHEE__Model_Version_Info___construct__extra_resource_properties_for_models',
110
            array()
111
        );
112
        $defaults = array(
113
            'raw'              => false,
114
            'type'             => 'N/A',
115
            'nullable'         => true,
116
            'table_alias'      => 'N/A',
117
            'table_column'     => 'N/A',
118
            'always_available' => true,
119
        );
120
        foreach ($this->resource_changes as $version => $model_classnames) {
121
            foreach ($model_classnames as $model_classname => $extra_fields) {
122
                foreach ($extra_fields as $fieldname => $field_data) {
123
                    $this->resource_changes[$model_classname][$fieldname]['name'] = $fieldname;
124
                    foreach ($defaults as $attribute => $default_value) {
125
                        if (! isset($this->resource_changes[$model_classname][$fieldname][$attribute])) {
126
                            $this->resource_changes[$model_classname][$fieldname][$attribute] = $default_value;
127
                        }
128
                    }
129
                }
130
            }
131
        }
132
    }
133
134
135
136
    /**
137
     * Returns a slice of Model_Version_Info::model_changes()'s array
138
     * indicating exactly what changes happened between the current core version,
139
     * and the version requested
140
     *
141
     * @return array
142
     */
143 View Code Duplication
    public function modelChangesBetweenRequestedVersionAndCurrent()
144
    {
145
        if ($this->cached_model_changes_between_requested_version_and_current === null) {
146
            $model_changes = array();
147
            foreach ($this->modelChanges() as $version => $models_changed_in_version) {
148
                if ($version <= EED_Core_Rest_Api::core_version() && $version > $this->requestedVersion()) {
149
                    $model_changes[$version] = $models_changed_in_version;
150
                }
151
            }
152
            $this->cached_model_changes_between_requested_version_and_current = $model_changes;
153
        }
154
        return $this->cached_model_changes_between_requested_version_and_current;
155
    }
156
157
158
159
    /**
160
     * Returns a slice of Model_Version_Info::model_changes()'s array
161
     * indicating exactly what changes happened between the current core version,
162
     * and the version requested
163
     *
164
     * @return array
165
     */
166 View Code Duplication
    public function resourceChangesBetweenRequestedVersionAndCurrent()
167
    {
168
        if ($this->cached_resource_changes_between_requested_version_and_current === null) {
169
            $resource_changes = array();
170
            foreach ($this->resourceChanges() as $version => $model_classnames) {
171
                if ($version <= EED_Core_Rest_Api::core_version() && $version > $this->requestedVersion()) {
172
                    $resource_changes[$version] = $model_classnames;
173
                }
174
            }
175
            $this->cached_resource_changes_between_requested_version_and_current = $resource_changes;
176
        }
177
        return $this->cached_resource_changes_between_requested_version_and_current;
178
    }
179
180
181
182
    /**
183
     * If a request was sent to 'wp-json/ee/v4.7/events' this would be '4.7'
184
     *
185
     * @return string like '4.6'
186
     */
187
    public function requestedVersion()
188
    {
189
        return $this->requested_version;
190
    }
191
192
193
194
    /**
195
     * Returns an array describing how the models have changed in each version of core
196
     * that supports the API (starting at 4.6)
197
     * Top-level keys are versions (major and minor version numbers, eg "4.6")
198
     * next-level keys are model names (eg "Event") that underwent some change in that version
199
     * and the value is either NULL (indicating the model is completely NEW in this version),
200
     * or it's an array where fields are value names.
201
     * If a version is missing then we don't know anything about what changes it introduced from the previous version
202
     *
203
     * @return array
204
     */
205
    public function modelChanges()
206
    {
207
        return $this->model_changes;
208
    }
209
210
211
212
    /**
213
     * Takes into account the requested version, and the current version, and
214
     * what changed between the two, and tries to return.
215
     * Analogous to EE_Registry::instance()->non_abstract_db_models
216
     *
217
     * @return array keys are model names, values are their classname
218
     */
219
    public function modelsForRequestedVersion()
220
    {
221
        if ($this->cached_models_for_requested_version === null) {
222
            $all_models_in_current_version = EE_Registry::instance()->non_abstract_db_models;
223
            foreach ($this->modelChangesBetweenRequestedVersionAndCurrent() as $version => $models_changed) {
224
                foreach ($models_changed as $model_name => $new_indicator_or_fields_added) {
225
                    if ($new_indicator_or_fields_added === ModelVersionInfo::MODEL_ADDED) {
226
                        unset($all_models_in_current_version[$model_name]);
227
                    }
228
                }
229
            }
230
            $this->cached_models_for_requested_version = apply_filters(
231
                'FHEE__EventEspresso_core_libraries_rest_api__models_for_requested_version',
232
                $all_models_in_current_version,
233
                $this
234
            );
235
        }
236
        return $this->cached_models_for_requested_version;
237
    }
238
239
240
241
    /**
242
     * Determines if this is a valid model name in the requested version.
243
     * Similar to EE_Registry::instance()->is_model_name(), but takes the requested
244
     * version's models into account
245
     *
246
     * @param string $model_name eg 'Event'
247
     * @return boolean
248
     */
249
    public function isModelNameInThisVersion($model_name)
250
    {
251
        $model_names = $this->modelsForRequestedVersion();
252
        if (isset($model_names[$model_name])) {
253
            return true;
254
        } else {
255
            return false;
256
        }
257
    }
258
259
260
261
    /**
262
     * Wrapper for EE_Registry::instance()->load_model(), but takes the requested
263
     * version's models into account
264
     *
265
     * @param string $model_name
266
     * @return \EEM_Base
267
     * @throws \EE_Error
268
     */
269
    public function loadModel($model_name)
270
    {
271
        if ($this->isModelNameInThisVersion($model_name)) {
272
            return EE_Registry::instance()->load_model($model_name);
273
        } else {
274
            throw new \EE_Error(
275
                sprintf(
276
                    __(
277
                        'Cannot load model "%1$s" because it does not exist in version %2$s of Event Espresso',
278
                        'event_espresso'
279
                    ),
280
                    $model_name,
281
                    $this->requestedVersion()
282
                )
283
            );
284
        }
285
    }
286
287
288
289
    /**
290
     * Gets all the fields that should exist on this model right now
291
     *
292
     * @param \EEM_Base $model
293
     * @return array|\EE_Model_Field_Base[]
294
     */
295
    public function fieldsOnModelInThisVersion($model)
296
    {
297
        if (! isset($this->cached_fields_on_models[$model->get_this_model_name()])) {
298
            //get all model changes between the requested version and current core version
299
            $changes = $this->modelChangesBetweenRequestedVersionAndCurrent();
300
            //fetch all fields currently on this model
301
            $current_fields = $model->field_settings();
302
            //remove all fields that have been added since
303
            foreach ($changes as $version => $changes_in_version) {
304
                if (isset($changes_in_version[$model->get_this_model_name()])
305
                    && $changes_in_version[$model->get_this_model_name()] !== ModelVersionInfo::MODEL_ADDED
306
                ) {
307
                    $current_fields = array_diff_key(
308
                        $current_fields,
309
                        array_flip($changes_in_version[$model->get_this_model_name()])
310
                    );
311
                }
312
            }
313
            $this->cached_fields_on_models = $current_fields;
314
        }
315
        return $this->cached_fields_on_models;
316
    }
317
318
319
320
    /**
321
     * Determines if $object is of one of the classes of $classes. Similar to
322
     * in_array(), except this checks if $object is a subclass of the classnames provided
323
     * in $classnames
324
     *
325
     * @param object $object
326
     * @param array  $classnames
327
     * @return boolean
328
     */
329
    public function isSubclassOfOne($object, $classnames)
330
    {
331
        foreach ($classnames as $classname) {
332
            if (is_a($object, $classname)) {
333
                return true;
334
            }
335
        }
336
        return false;
337
    }
338
339
340
341
    /**
342
     * Returns the list of model field classes that that the API basically ignores
343
     *
344
     * @return array
345
     */
346
    public function fieldsIgnored()
347
    {
348
        return apply_filters(
349
            'FHEE__Controller_Model_Read_fields_ignored',
350
            array('EE_Foreign_Key_Field_Base', 'EE_Any_Foreign_Model_Name_Field')
351
        );
352
    }
353
354
355
356
    /**
357
     * If this field one that should be ignored by the API?
358
     *
359
     * @param EE_Model_Field_Base
360
     * @return boolean
361
     */
362
    public function fieldIsIgnored($field_obj)
363
    {
364
        return $this->isSubclassOfOne($field_obj, $this->fieldsIgnored());
365
    }
366
367
368
369
    /**
370
     * Returns the list of model field classes that have a "raw" and non-raw formats.
371
     * Normally the "raw" versions are only accessible to those who can edit them.
372
     *
373
     * @return array an array of EE_Model_Field_Base child classnames
374
     */
375
    public function fieldsThatHaveRenderedFormat()
376
    {
377
        return apply_filters(
378
            'FHEE__Controller_Model_Read__fields_raw',
379
            array('EE_Post_Content_Field', 'EE_Full_HTML_Field')
380
        );
381
    }
382
383
384
385
    /**
386
     * If this field one that has a raw format
387
     *
388
     * @param EE_Model_Field_Base
389
     * @return boolean
390
     */
391
    public function fieldHasRenderedFormat($field_obj)
392
    {
393
        return $this->isSubclassOfOne($field_obj, $this->fieldsThatHaveRenderedFormat());
394
    }
395
396
397
398
    /**
399
     * Returns the list of model field classes that have a "_pretty" and non-pretty versions.
400
     * The pretty version of the field is NOT query-able or editable, but requires no extra permissions
401
     * to view
402
     *
403
     * @return array an array of EE_Model_Field_Base child classnames
404
     */
405
    public function fieldsThatHavePrettyFormat()
406
    {
407
        return apply_filters(
408
            'FHEE__Controller_Model_Read__fields_pretty',
409
            array('EE_Enum_Integer_Field', 'EE_Enum_Text_Field', 'EE_Money_Field')
410
        );
411
    }
412
413
414
415
    /**
416
     * If this field one that has a pretty equivalent
417
     *
418
     * @param EE_Model_Field_Base
419
     * @return boolean
420
     */
421
    public function fieldHasPrettyFormat($field_obj)
422
    {
423
        return $this->isSubclassOfOne($field_obj, $this->fieldsThatHavePrettyFormat());
424
    }
425
426
427
428
    /**
429
     * Returns an array describing what extra API resource properties have been added through the versions
430
     *
431
     * @return array @see $this->_extra_resource_properties_for_models
432
     */
433
    public function resourceChanges()
434
    {
435
        return $this->resource_changes;
436
    }
437
438
439
440
    /**
441
     * Returns an array where keys are extra resource properties in this version of the API,
442
     * and values are key-value pairs describing the new properties. @see Model_Version::_resource_changes
443
     *
444
     * @param \EEM_Base $model
445
     * @return array
446
     */
447
    public function extraResourcePropertiesForModel($model)
448
    {
449
        $extra_properties = array();
450
        foreach ($this->resourceChangesBetweenRequestedVersionAndCurrent() as $version => $model_classnames) {
451
            foreach ($model_classnames as $model_classname => $properties_added_in_this_version) {
452
                if (is_subclass_of($model, $model_classname)) {
453
                    $extra_properties = array_merge($extra_properties, $properties_added_in_this_version);
454
                }
455
            }
456
        }
457
        return $extra_properties;
458
    }
459
460
461
462
    /**
463
     * Gets all the related models for the specified model. It's good to use this
464
     * in case this model didn't exist for this version or something
465
     *
466
     * @param \EEM_Base $model
467
     * @return \EE_Model_Relation_Base[]
468
     */
469
    public function relationSettings(\EEM_Base $model)
470
    {
471
        $relations = array();
472
        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
473
            if ($this->isModelNameInThisVersion($relation_name)) {
474
                $relations[$relation_name] = $relation_obj;
475
            }
476
        }
477
        //filter the results, but use the old filter name
478
        return apply_filters(
479
            'FHEE__Read__create_entity_from_wpdb_result__related_models_to_include',
480
            $relations,
481
            $model
482
        );
483
    }
484
}
485
486
// End of file Model_Version_Info.php
487