Passed
Push — master ( ab49c9...f82b12 )
by Nicolaas
03:45
created

FindEditableObjects::checkForValidMethods()   F

Complexity

Conditions 25
Paths 134

Size

Total Lines 79
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 25
eloc 42
c 1
b 0
f 0
nc 134
nop 3
dl 0
loc 79
rs 3.8833

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Sunnysideup\SiteWideSearch\Helpers;
4
5
use SilverStripe\Core\Config\Config;
6
use SilverStripe\Core\Config\Configurable;
7
use SilverStripe\Core\Extensible;
8
use SilverStripe\Core\Injector\Injectable;
9
use SilverStripe\Core\Injector\Injector;
10
use SilverStripe\ORM\DataList;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\ORM\UnsavedRelationList;
13
use Sunnysideup\CmsEditLinkField\Api\CMSEditLinkAPI;
14
15
class FindEditableObjects
16
{
17
    use Extensible;
18
    use Configurable;
19
    use Injectable;
20
21
    /**
22
     * @var string
23
     */
24
    private const CACHE_NAME = 'FindEditableObjectsCache';
25
26
    protected $additionalCacheName = '';
27
    protected $relationTypesCovered = [];
28
29
    protected $excludedClasses = [];
30
31
    /**
32
     * format is as follows:
33
     * ```php
34
     *      [
35
     *          'valid_methods_edit' => [
36
     *              ClassNameA => false, // tested and does not have any available methods
37
     *              ClassNameB => MethodName1, // tested found method MethodName1 that can be used.
38
     *              ClassNameC => MethodName2, // tested found method MethodName2 that can be used.
39
     *              ClassNameD => false, // tested and does not have any available methods
40
     *          ],
41
     *          'valid_methods_view' => [
42
     *              ClassNameA => false, // tested and does not have any available methods
43
     *              ClassNameB => MethodName1, // tested found method MethodName1 that can be used.
44
     *              ClassNameC => MethodName2, // tested found method MethodName2 that can be used.
45
     *              ClassNameD => false, // tested and does not have any available methods
46
     *          ],
47
     *          'valid_methods_view_links' => [
48
     *              [ClassNameX_IDY] => 'MyLinkView',
49
     *              [ClassNameX_IDZ] => 'MyLinkView',
50
     *          ],
51
     *          'valid_methods_edit_links' => [
52
     *              [ClassNameX_IDY] => 'MyLinkEdit',
53
     *              [ClassNameX_IDZ] => 'MyLinkEdit',
54
     *          ],
55
     *          'rels' =>
56
     *              'ClassNameY' => [
57
     *                  'MethodA' => RelationClassNameB,
58
     *                  'MethodC' => RelationClassNameD,
59
     *              ],
60
     *          ],
61
     *          'validMethods' => [
62
     *              'valid_methods_edit' => [
63
     *                  'A',
64
     *                  'B',
65
     *              ]
66
     *              'valid_methods_view' => [
67
     *                  'A',
68
     *                  'B',
69
     *              ]
70
     *          ]
71
     *     ]
72
     * ```
73
     * we use false to be able to use empty to work out if it has been tested before.
74
     *
75
     * @var array
76
     */
77
    protected $cache = [
78
        'valid_methods_edit',
79
        'valid_methods_view',
80
        'valid_methods_image',
81
        'valid_methods_view_links',
82
        'valid_methods_edit_links',
83
        'valid_methods_image_links',
84
        'rels',
85
        'validMethods' => [
86
            'valid_methods_edit' => [],
87
            'valid_methods_view' => [],
88
            'valid_methods_image' => [],
89
        ],
90
    ];
91
92
    private static $max_relation_depth = 3;
93
94
    private static $valid_methods_edit = [
95
        'CMSEditLink',
96
        'getCMSEditLink',
97
    ];
98
99
    private static $valid_methods_view = [
100
        'getLink',
101
        'Link',
102
    ];
103
104
    private static $valid_methods_image = [
105
        'StripThumbnail',
106
        'CMSThumbnail',
107
        'getCMSThumbnail',
108
    ];
109
110
    public function getFileCache()
111
    {
112
        return Injector::inst()->get(Cache::class);
113
    }
114
115
    public function initCache(string $additionalCacheName): self
116
    {
117
        $this->additionalCacheName = $additionalCacheName;
118
        $this->cache = $this->getFileCache()->getCacheValues(self::CACHE_NAME . '_' . $this->additionalCacheName);
119
120
        return $this;
121
    }
122
123
    public function saveCache(): self
124
    {
125
        $this->getFileCache()->setCacheValues(self::CACHE_NAME . '_' . $this->additionalCacheName, $this->cache);
126
127
        return $this;
128
    }
129
130
    public function setExcludedClasses(array $excludedClasses): self
131
    {
132
        $this->excludedClasses = $excludedClasses;
133
134
        return $this;
135
    }
136
137
    /**
138
     * returns an link to an object that can be edited in the CMS.
139
     *
140
     * @param mixed $dataObject
141
     */
142
    public function getCMSEditLink($dataObject): string
143
    {
144
        return $this->getLinkInner($dataObject, 'valid_methods_edit');
145
    }
146
147
    /**
148
     * returns a link to an object that can be viewed.
149
     *
150
     * @param mixed $dataObject
151
     */
152
    public function getLink($dataObject): string
153
    {
154
        return $this->getLinkInner($dataObject, 'valid_methods_view');
155
    }
156
157
    /**
158
     * returns link to a thumbnail.
159
     *
160
     * @param mixed $dataObject
161
     */
162
    public function getCMSThumbnail($dataObject): string
163
    {
164
        return $this->getLinkInner($dataObject, 'valid_methods_image');
165
    }
166
167
    /**
168
     * returns an link to an object that can be viewed.
169
     *
170
     * @param mixed $dataObject
171
     */
172
    protected function getLinkInner($dataObject, string $type): string
173
    {
174
        $typeKey = $type . '_links';
175
        $key = $dataObject->ClassName . $dataObject->ID;
176
        $result = $this->cache[$typeKey][$key] ?? false;
177
        if (false === $result) {
178
            $this->relationTypesCovered = [];
179
            $result = $this->checkForValidMethods($dataObject, $type);
180
            $this->cache[$typeKey][$key] = $result;
181
        }
182
183
        return $result;
184
    }
185
186
    protected function checkForValidMethods($dataObject, string $type, ?int $relationDepth = 0): string
187
    {
188
        //too many iterations!
189
        if ($relationDepth > (int) $this->Config()->get('max_relation_depth')) {
190
            return '';
191
        }
192
193
        $validMethods = $this->getValidMethods($type);
194
195
        $this->relationTypesCovered[$dataObject->ClassName] = false;
196
197
        // quick return
198
        if (isset($this->cache[$type][$dataObject->ClassName]) && false !== $this->cache[$type][$dataObject->ClassName]) {
199
            $validMethod = $this->cache[$type][$dataObject->ClassName];
200
            if ($dataObject->hasMethod($validMethod)) {
201
                return (string) $dataObject->{$validMethod}();
202
            }
203
            // last resort - is there a variable with this name?
204
            return (string) $dataObject->{$validMethod};
205
        }
206
207
        if ($this->classCanBeIncluded($dataObject->ClassName)) {
208
            if (empty($this->cache[$type][$dataObject->ClassName]) || false !== $this->cache[$type][$dataObject->ClassName]) {
209
                foreach ($validMethods as $validMethod) {
210
                    $outcome = null;
211
                    if ($dataObject->hasMethod($validMethod)) {
212
                        $outcome = $dataObject->{$validMethod}();
213
                    } elseif (!empty($dataObject->{$validMethod})) {
214
                        $outcome = $dataObject->{$validMethod};
215
                    }
216
217
                    if ($outcome) {
218
                        $this->cache[$type][$dataObject->ClassName] = $validMethod;
219
220
                        return (string) $outcome;
221
                    }
222
                }
223
            }
224
225
            if ('valid_methods_edit' === $type) {
226
                if (class_exists(CMSEditLinkAPI::class)) {
227
                    $link = CMSEditLinkAPI::find_edit_link_for_object($dataObject);
228
                    if ($link) {
229
                        return (string) $link;
230
                    }
231
                }
232
            }
233
        }
234
235
        // there is no match for this one, but we can search relations ...
236
        $this->cache[$type][$dataObject->ClassName] = true;
237
        ++$relationDepth;
238
        foreach ($this->getRelations($dataObject) as $relationName => $relType) {
239
            $outcome = null;
240
            //TODO: no support for link through relations yet!
241
            if (is_array($relType)) {
242
                continue;
243
            }
244
245
            if (!isset($this->relationTypesCovered[$relType])) {
246
                $rels = $dataObject->{$relationName}();
247
                if ($rels) {
248
                    if ($rels instanceof DataList) {
249
                        if(!$rels instanceof UnsavedRelationList) {
250
                            $rels = $rels->first();
251
                        }
252
                    }
253
                    if ($rels && $rels instanceof DataObject && $rels->exists()) {
254
                        $outcome = $this->checkForValidMethods($rels, $type, $relationDepth);
255
                    }
256
                }
257
            }
258
259
            if ($outcome) {
260
                return $outcome;
261
            }
262
        }
263
264
        return '';
265
    }
266
267
    protected function getRelations($dataObject): array
268
    {
269
        if (!isset($this->cache['rels'][$dataObject->ClassName])) {
270
            $this->cache['rels'][$dataObject->ClassName] = array_merge(
271
                Config::inst()->get($dataObject->ClassName, 'belongs_to'),
272
                Config::inst()->get($dataObject->ClassName, 'has_one'),
273
                Config::inst()->get($dataObject->ClassName, 'has_many'),
274
                Config::inst()->get($dataObject->ClassName, 'belongs_many_many'),
275
                Config::inst()->get($dataObject->ClassName, 'many_many')
276
            );
277
            foreach ($this->cache['rels'][$dataObject->ClassName] as $key => $value) {
278
                if (!(is_string($value) && class_exists($value) && $this->classCanBeIncluded($value))) {
279
                    unset($this->cache['rels'][$dataObject->ClassName][$key]);
280
                }
281
            }
282
        }
283
284
        return $this->cache['rels'][$dataObject->ClassName];
285
    }
286
287
    protected function getValidMethods(string $type): array
288
    {
289
        if (!isset($this->cache['validMethods'][$type])) {
290
            $this->cache['validMethods'][$type] = $this->Config()->get($type);
291
        }
292
293
        return $this->cache['validMethods'][$type];
294
    }
295
296
    /**
297
     * it either is NOT in the excluded list or it is in the included list.
298
     *
299
     * @param string $dataObjectClassName
300
     * @return boolean
301
     */
302
    protected function classCanBeIncluded(string $dataObjectClassName): bool
303
    {
304
        if(count($this->excludedClasses)) {
305
            if(!class_exists($dataObjectClassName)) {
306
                return false;
307
            }
308
            return !in_array($dataObjectClassName, $this->excludedClasses, true);
309
        }
310
        user_error('Please set excludedClasses', E_USER_NOTICE);
311
        return false;
312
    }
313
}
314