Completed
Pull Request — master (#16)
by Maksim
01:34
created

MultiUnitSupport   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 272
Duplicated Lines 9.56 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 5
dl 26
loc 272
rs 9.2
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getUnitConversionDataColumns() 0 6 1
A bootMultiUnitSupport() 0 38 4
A calculateMultiUnitConversionData() 0 16 3
A getMultiUnitExistingConversionData() 0 4 1
A getUnitConversionDataPostfix() 0 4 1
A getMultiUnitColumns() 0 4 1
A getMultiUnitFieldSupportedUnits() 0 8 2
A getMultiUnitFieldDefaultUnit() 0 10 2
A getMultiUnitFieldSelectedUnit() 0 10 2
A setMultiUnitFieldSelectedUnit() 10 23 5
B getMultiUnitFieldValueByUnitName() 13 34 9
B getMultiUnitFieldValue() 3 20 6
A isMultiUnitField() 0 4 1
A getMultiUnitFieldUnit() 0 4 2

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

1
<?php
2
3
namespace MaksimM\MultiUnitModels\Traits;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Support\Arr;
7
use MaksimM\MultiUnitModels\Exceptions\NotSupportedMultiUnitField;
8
use MaksimM\MultiUnitModels\Exceptions\NotSupportedMultiUnitFieldUnit;
9
use UnitConverter\Unit\AbstractUnit;
10
11
trait MultiUnitSupport
12
{
13
14
    use ModelConfiguration;
15
16
    protected $unitConversionDataPostfix = '_ucd';
17
    protected $multiUnitColumns = [];
18
    protected $multiUnitSelectedUnits = [];
19
20
    private function getUnitConversionDataColumns()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
21
    {
22
        return array_map(function ($column) {
23
            return $column.$this->getUnitConversionDataPostfix();
24
        }, array_keys($this->getMultiUnitColumns()));
25
    }
26
27
    protected static function bootMultiUnitSupport()
28
    {
29
        //save conversion table if base value is changed
30
        static::creating(function ($model) {
31
            /**
32
             * @var Model|MultiUnitSupport $model
33
             */
34
            foreach ($model->getMultiUnitColumns() as $unitBasedColumn => $options) {
0 ignored issues
show
Bug introduced by
The method getMultiUnitColumns does only exist in MaksimM\MultiUnitModels\Traits\MultiUnitSupport, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
35
                if (isset($model->attributes[$unitBasedColumn])) {
36
                    $model->{$unitBasedColumn.$model->getUnitConversionDataPostfix()} = json_encode(
0 ignored issues
show
Bug introduced by
The method getUnitConversionDataPostfix does only exist in MaksimM\MultiUnitModels\Traits\MultiUnitSupport, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
37
                        $model->calculateMultiUnitConversionData(
0 ignored issues
show
Bug introduced by
The method calculateMultiUnitConversionData does only exist in MaksimM\MultiUnitModels\Traits\MultiUnitSupport, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
38
                            $model->attributes[$unitBasedColumn],
39
                            $model->getMultiUnitFieldUnit($unitBasedColumn),
0 ignored issues
show
Bug introduced by
The method getMultiUnitFieldUnit does only exist in MaksimM\MultiUnitModels\Traits\MultiUnitSupport, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
40
                            $options['supported_units']
41
                        )
42
                    );
43
                    $model->{$unitBasedColumn} = $model->processMultiUnitFieldChanges(
0 ignored issues
show
Bug introduced by
The method processMultiUnitFieldChanges does only exist in MaksimM\MultiUnitModels\Traits\MultiUnitSupport, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
44
                        $unitBasedColumn,
45
                        $model->{$unitBasedColumn}
46
                    );
47
                }
48
            }
49
        });
50
        static::updating(function ($model) {
51
            /**
52
             * @var Model|MultiUnitSupport $model
53
             */
54
            foreach (Arr::only($model->getMultiUnitColumns(), array_keys($model->getDirty())) as $unitBasedColumn => $options) {
0 ignored issues
show
Bug introduced by
The method getMultiUnitColumns does only exist in MaksimM\MultiUnitModels\Traits\MultiUnitSupport, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
55
                $model->{$unitBasedColumn.$model->getUnitConversionDataPostfix()} = json_encode(
0 ignored issues
show
Bug introduced by
The method getUnitConversionDataPostfix does only exist in MaksimM\MultiUnitModels\Traits\MultiUnitSupport, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
56
                    $model->calculateMultiUnitConversionData(
0 ignored issues
show
Bug introduced by
The method calculateMultiUnitConversionData does only exist in MaksimM\MultiUnitModels\Traits\MultiUnitSupport, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
57
                        $model->getDirty()[$unitBasedColumn],
0 ignored issues
show
Bug introduced by
The method getDirty does only exist in Illuminate\Database\Eloquent\Model, but not in MaksimM\MultiUnitModels\Traits\MultiUnitSupport.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
58
                        $model->getMultiUnitFieldUnit($unitBasedColumn, true),
0 ignored issues
show
Bug introduced by
The method getMultiUnitFieldUnit does only exist in MaksimM\MultiUnitModels\Traits\MultiUnitSupport, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
59
                        $options['supported_units']
60
                    )
61
                );
62
            }
63
        });
64
    }
65
66
    /**
67
     * @param              $value
68
     * @param AbstractUnit $unit
69
     * @param string[]     $requiredUnits
70
     *
71
     * @return array|null
72
     */
73
    private function calculateMultiUnitConversionData($value, AbstractUnit $unit, $requiredUnits)
74
    {
75
        if(is_null($value))
76
            return null;
77
78
        $conversionData = [];
79
        foreach ($requiredUnits as $requiredUnitClass) {
80
            /**
81
             * @var AbstractUnit $requiredUnit
82
             */
83
            $requiredUnit = new $requiredUnitClass();
84
            $conversionData[$requiredUnit->getId()] = (new $unit($value))->as($requiredUnit);
85
        }
86
87
        return $conversionData;
88
    }
89
90
    public function getMultiUnitExistingConversionData($field)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
91
    {
92
        return json_decode($this->{$field.$this->getUnitConversionDataPostfix()} ?? null);
93
    }
94
95
    /**
96
     * @return string
97
     */
98
    protected function getUnitConversionDataPostfix()
99
    {
100
        return $this->unitConversionDataPostfix;
101
    }
102
103
    /**
104
     * @return array
105
     */
106
    public function getMultiUnitColumns()
107
    {
108
        return $this->multiUnitColumns;
109
    }
110
111
    /**
112
     * @param $field
113
     *
114
     * @throws NotSupportedMultiUnitField
115
     *
116
     * @return AbstractUnit[]
117
     */
118
    public function getMultiUnitFieldSupportedUnits($field)
119
    {
120
        if ($this->isMultiUnitField($field)) {
121
            return $this->getMultiUnitColumns()[$field]['supported_units'];
122
        }
123
124
        throw new NotSupportedMultiUnitField($field);
125
    }
126
127
    /**
128
     * @param $field
129
     *
130
     * @throws NotSupportedMultiUnitField
131
     *
132
     * @return AbstractUnit
133
     */
134
    public function getMultiUnitFieldDefaultUnit($field)
135
    {
136
        if ($this->isMultiUnitField($field)) {
137
            $unitClass = $this->getMultiUnitColumns()[$field]['default_unit'];
138
139
            return new $unitClass();
140
        }
141
142
        throw new NotSupportedMultiUnitField($field);
143
    }
144
145
    /**
146
     * @param $field
147
     *
148
     * @throws NotSupportedMultiUnitField
149
     *
150
     * @return AbstractUnit
151
     */
152
    public function getMultiUnitFieldSelectedUnit($field)
153
    {
154
        if ($this->isMultiUnitField($field)) {
155
            $unitClass = $this->multiUnitSelectedUnits[$field] ?? $this->getMultiUnitFieldDefaultUnit($field);
156
157
            return new $unitClass();
158
        }
159
160
        throw new NotSupportedMultiUnitField($field);
161
    }
162
163
    /**
164
     * @param $field
165
     * @param string $unit
166
     *
167
     * @throws NotSupportedMultiUnitField
168
     * @throws NotSupportedMultiUnitFieldUnit
169
     */
170
    public function setMultiUnitFieldSelectedUnit($field, $unit)
171
    {
172
        if ($this->isMultiUnitField($field)) {
173
            $found = false;
174 View Code Duplication
            foreach ($this->getMultiUnitFieldSupportedUnits($field) as $unitClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
175
                /**
176
                 * @var AbstractUnit $unit
177
                 */
178
                $supportedUnit = new $unitClass();
179
                if (strtolower($supportedUnit->getId()) == strtolower($unit)) {
180
                    $found = true;
181
                    break;
182
                }
183
            }
184
            if ($found) {
185
                $this->multiUnitSelectedUnits[$field] = $unitClass;
0 ignored issues
show
Bug introduced by
The variable $unitClass seems to be defined by a foreach iteration on line 174. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
186
            } else {
187
                throw new NotSupportedMultiUnitFieldUnit($field, $unit);
188
            }
189
        } else {
190
            throw new NotSupportedMultiUnitField($field);
191
        }
192
    }
193
194
    /**
195
     * @param        $field
196
     * @param string $unit
0 ignored issues
show
Documentation introduced by
Should the type for parameter $unit not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
197
     *
198
     * @throws NotSupportedMultiUnitField
199
     *
200
     * @return mixed
201
     */
202
    public function getMultiUnitFieldValueByUnitName($field, $unit = null)
203
    {
204
        if ($this->isMultiUnitField($field)) {
205
            if (isset($this->{$field})) {
206
                if (is_null($unit)) {
207
                    $unit = $this->getMultiUnitFieldUnit($field);
208
                } else {
209 View Code Duplication
                    foreach ($this->getMultiUnitFieldSupportedUnits($field) as $unitClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
210
                        /**
211
                         * @var AbstractUnit $unit
212
                         */
213
                        $supportedUnit = new $unitClass();
214
                        if (strtolower($supportedUnit->getId()) == strtolower($unit)) {
215
                            $unit = $supportedUnit;
216
                            break;
217
                        }
218
                    }
219
                }
220
                if (is_string($unit)) {
221
                    throw new NotSupportedMultiUnitField($field);
222
                }
223
                $existingConversionData = $this->getMultiUnitExistingConversionData($field);
224 View Code Duplication
                if (!is_null($existingConversionData) && !is_null($existingConversionData->{$unit->getId()})) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
225
                    return $existingConversionData->{$unit->getId()};
226
                }
227
228
                return ($this->getMultiUnitFieldSelectedUnit($field)->setValue($this->{$field} ?? $this->attributes[$field]))->as(new $unit());
0 ignored issues
show
Bug introduced by
The property attributes does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
229
            } else {
230
                return;
231
            }
232
        }
233
234
        throw new NotSupportedMultiUnitField($field);
235
    }
236
237
    /**
238
     * @param                   $field
239
     * @param AbstractUnit|null $unit
240
     *
241
     * @throws NotSupportedMultiUnitField
242
     *
243
     * @return mixed
244
     */
245
    public function getMultiUnitFieldValue($field, AbstractUnit $unit = null)
246
    {
247
        if ($this->isMultiUnitField($field)) {
248
            if (isset($this->{$field})) {
249
                if (is_null($unit)) {
250
                    $unit = $this->getMultiUnitFieldUnit($field);
251
                }
252
                $existingConversionData = $this->getMultiUnitExistingConversionData($field);
253 View Code Duplication
                if (!is_null($existingConversionData) && !is_null($existingConversionData->{$unit->getId()})) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
254
                    return $existingConversionData->{$unit->getId()};
255
                }
256
257
                return ($this->getMultiUnitFieldSelectedUnit($field)->setValue($this->{$field} ?? $this->attributes[$field]))->as(new $unit());
258
            } else {
259
                return;
260
            }
261
        }
262
263
        throw new NotSupportedMultiUnitField($field);
264
    }
265
266
    protected function isMultiUnitField($field)
267
    {
268
        return isset($this->getMultiUnitColumns()[$field]);
269
    }
270
271
    /**
272
     * @param $field
273
     *
274
     * @throws NotSupportedMultiUnitField
275
     *
276
     * @return AbstractUnit
277
     */
278
    protected function getMultiUnitFieldUnit($field, $preferDefault = false)
279
    {
280
        return $preferDefault ? $this->getMultiUnitFieldDefaultUnit($field) : $this->getMultiUnitFieldSelectedUnit($field);
281
    }
282
}
283