SourcesBehavior   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 232
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 81.58%

Importance

Changes 0
Metric Value
wmc 49
lcom 1
cbo 5
dl 0
loc 232
ccs 93
cts 114
cp 0.8158
rs 8.48
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A findSources() 0 14 5
A getSources() 0 13 3
F getSource() 0 99 33
A getFolderById() 0 12 2
A getFolderByVolumeId() 0 4 2
A setMockFolder() 0 4 1
A getFolderByHandle() 0 14 3

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace NerdsAndCompany\Schematic\Behaviors;
4
5
use Craft;
6
use TypeError;
7
use yii\base\Behavior;
8
use craft\base\Model;
9
use craft\records\VolumeFolder;
10
use craft\fields\Users;
11
use NerdsAndCompany\Schematic\Schematic;
12
use NerdsAndCompany\Schematic\Events\SourceMappingEvent;
13
14
/**
15
 * Schematic Sources Behavior.
16
 *
17
 * Sync Craft Setups.
18
 *
19
 * @author    Nerds & Company
20
 * @copyright Copyright (c) 2015-2019, Nerds & Company
21
 * @license   MIT
22
 *
23
 * @see      http://www.nerds.company
24
 */
25
class SourcesBehavior extends Behavior
26
{
27
    /** Hack to be able to avoid the active record call in VolumeFolder::findOne() */
28
    public $mockFolder = null;
29
30
    /**
31
     * Recursively find sources in definition attributes.
32
     *
33
     * @param string $fieldType
34
     * @param array  $attributes
35
     * @param string $indexFrom
36
     * @param string $indexTo
37
     *
38
     * @return array
39
     */
40 32
    public function findSources(string $fieldType, array $attributes, string $indexFrom, string $indexTo): array
41
    {
42 32
        foreach ($attributes as $key => $attribute) {
43 32
            if ($key === 'source') {
44 4
                $attributes[$key] = $this->getSource($fieldType, $attribute, $indexFrom, $indexTo);
45 32
            } elseif ($key === 'sources') {
46 4
                $attributes[$key] = $this->getSources($fieldType, $attribute, $indexFrom, $indexTo);
47 32
            } elseif (is_array($attribute)) {
48 32
                $attributes[$key] = $this->findSources($fieldType, $attribute, $indexFrom, $indexTo);
49
            }
50
        }
51
52 32
        return $attributes;
53
    }
54
55
    /**
56
     * Get sources based on the indexFrom attribute and return them with the indexTo attribute.
57
     *
58
     * @param string       $fieldType
59
     * @param string|array $sources
60
     * @param string       $indexFrom
61
     * @param string       $indexTo
62
     *
63
     * @return array|string
64
     */
65 9
    public function getSources(string $fieldType, $sources, string $indexFrom, string $indexTo)
66
    {
67 9
        $mappedSources = $sources;
68 9
        if (is_array($sources)) {
69 7
            $mappedSources = [];
70 7
            $sources = array_filter($sources);
71 7
            foreach ($sources as $source) {
72 7
                $mappedSources[] = $this->getSource($fieldType, $source, $indexFrom, $indexTo);
73
            }
74
        }
75
76 9
        return $mappedSources;
77
    }
78
79
    /**
80
     * Gets a source by the attribute indexFrom, and returns it with attribute $indexTo.
81
     *
82
     * @TODO Break up and simplify this method
83
     *
84
     * @param string $fieldType
85
     * @param string $source
86
     * @param string $indexFrom
87
     * @param string $indexTo
88
     *
89
     * @return string|null
90
     *
91
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
92
     * @SuppressWarnings(PHPMD.NPathComplexity)
93
     */
94 9
    public function getSource(string $fieldType, string $source = null, string $indexFrom, string $indexTo)
95
    {
96 9
        if (false === strpos($source, ':')) {
97 7
            return $source;
98
        }
99
100
        /** @var Model $sourceObject */
101 9
        $sourceObject = null;
102
103
        // Get service and method by source
104 9
        list($sourceType, $sourceFrom) = explode(':', $source);
105
        switch ($sourceType) {
106 9
            case 'editSite':
107
                $service = Craft::$app->sites;
108
                $method = 'getSiteBy';
109
                break;
110 9
            case 'single':
111 9
            case 'section':
112 9
            case 'createEntries':
113 9
            case 'editPeerEntries':
114 9
            case 'deleteEntries':
115 9
            case 'deletePeerEntries':
116 9
            case 'deletePeerEntryDrafts':
117 9
            case 'editEntries':
118 9
            case 'editPeerEntryDrafts':
119 9
            case 'publishEntries':
120 9
            case 'publishPeerEntries':
121 9
            case 'publishPeerEntryDrafts':
122 5
                $service = Craft::$app->sections;
123 5
                $method = 'getSectionBy';
124 5
                break;
125 9
            case 'assignUserGroup':
126
                $service = Craft::$app->userGroups;
127
                $method = 'getGroupBy';
128
                break;
129 9
            case 'group':
130 7
            case 'editCategories':
131 5
                $service = Users::class == $fieldType ? Craft::$app->userGroups : Craft::$app->categories;
132 5
                $method = 'getGroupBy';
133 5
                break;
134 7
            case 'folder':
135 2
                $service = $this;
136 2
                $method = 'getFolderBy';
137 2
                break;
138 5
            case 'createFoldersInVolume':
139 5
            case 'deleteFilesAndFoldersInVolume':
140 5
            case 'saveAssetInVolume':
141 5
            case 'viewVolume':
142 3
                $service = Craft::$app->volumes;
143 3
                $method = 'getVolumeBy';
144 3
                break;
145 2
            case 'taggroup':
146
                $service = Craft::$app->tags;
147
                $method = 'getTagGroupBy';
148
                break;
149 2
            case 'field':
150 2
                $service = Craft::$app->fields;
151 2
                $method = 'getFieldBy';
152 2
                break;
153
            case 'editGlobalSet':
154
                $service = Craft::$app->globals;
155
                $method = 'getSetBy';
156
                break;
157
            case 'utility':
158
                return $source;
159
        }
160
161
        // Send event
162 9
        $plugin = Craft::$app->controller->module;
163 9
        $event = new SourceMappingEvent([
164 9
            'source' => $source,
165 9
            'service' => $service ?? null,
166 9
            'method' => $method ?? null,
167
        ]);
168 9
        $plugin->trigger($plugin::EVENT_MAP_SOURCE, $event);
169 9
        $service = $event->service;
170 9
        $method = $event->method;
171
172
        // Try service and method
173 9
        if (isset($service) && isset($method) && isset($sourceFrom)) {
174 9
            $method = $method.ucfirst($indexFrom);
175
            try {
176 9
                $sourceObject = $service->$method($sourceFrom);
0 ignored issues
show
Bug introduced by
The method $method cannot be called on $service (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
177
            } catch (TypeError $e) {
178
                Schematic::error('An error occured mapping source '.$source.' from '.$indexFrom.' to '.$indexTo);
179
                Schematic::error($e->getMessage());
180
181
                return null;
182
            }
183
        }
184
185 9
        if ($sourceObject) {
186 8
            return $sourceType.':'.$sourceObject->$indexTo;
187
        }
188
189 1
        Schematic::warning('No mapping found for source '.$source);
190
191 1
        return null;
192
    }
193
194
    /**
195
     * Get a folder by id
196
     * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
197
     *
198
     * @param int $folderId
199
     * @return object
200
     */
201 1
    private function getFolderById(int $folderId): \stdClass
202
    {
203 1
        $folder = Craft::$app->assets->getFolderById($folderId);
204 1
        if ($folder) {
205 1
            $volume = $folder->getVolume();
206
            return  (object) [
207 1
                'id' => $folderId,
208 1
                'handle' => $volume->handle
209
            ];
210
        }
211
        return null;
212
    }
213
214
    /**
215
     * Get folder by volume id
216
     *
217
     * @param int $volumeId
218
     * @return VolumeFolder
219
     */
220 1
    private function getFolderByVolumeId(int $volumeId): VolumeFolder
221
    {
222 1
        return $this->mockFolder ? $this->mockFolder : VolumeFolder::findOne(['volumeId' => $volumeId]);
223
    }
224
225
    /**
226
     * Set a mock folder for the tests
227
     *
228
     * @param VolumeFolder $mockFolder
229
     */
230 1
    public function setMockFolder(VolumeFolder $mockFolder)
231
    {
232 1
        $this->mockFolder = $mockFolder;
233 1
    }
234
235
    /**
236
     * Get a folder by volume handle
237
     * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
238
     *
239
     * @param string $folderHandle
240
     * @return object
241
     */
242 1
    private function getFolderByHandle(string $folderHandle): \stdClass
243
    {
244 1
        $volume = Craft::$app->volumes->getVolumeByHandle($folderHandle);
245 1
        if ($volume) {
246 1
            $folder = $this->getFolderByVolumeId($volume->id);
247 1
            if ($folder) {
248
                return  (object) [
249 1
                    'id' => $folder->id,
250 1
                    'handle' => $folderHandle
251
                ];
252
            }
253
        }
254
        return null;
255
    }
256
}
257