Buyable::save()   A
last analyzed

Complexity

Conditions 5
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5.0729

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 13
ccs 6
cts 7
cp 0.8571
rs 9.5222
cc 5
nc 3
nop 1
crap 5.0729
1
<?php
2
namespace UniSharp\Buyable\Traits;
3
4
use UniSharp\Buyable\Models\Spec;
5
use UniSharp\Buyable\Models\Buyable as BuyableModel;
6
7
use InvalidArgumentException;
8
use Illuminate\Database\Eloquent\Builder;
9
use UniSharp\Buyable\Contracts\ProductUnitContract;
10
use Illuminate\Database\Eloquent\Relations\Relation;
11
use UniSharp\Buyable\Contracts\BuyableModelContract;
12
13
trait Buyable
14
{
15
    protected $buyableAttributes = ['vendor'];
16
    protected $specAttributes = ['spec', 'price', 'stock', 'sku', 'sold_qty'];
17
    protected $originalSpec;
18
    protected $spec;
19
    protected $specified = false;
20
    protected $originalBuyable = [];
21
    protected $buyable = [];
22
23 22
    public static function bootBuyable()
24
    {
25
        static::addGlobalScope('with', function (Builder $query) {
26 6
            return $query->with('specs');
27 22
        });
28
29
        static::created(function ($model) {
30 22
            if ($model->isSpecDirty()) {
31 10
                $model->specs()->create($model->getSpecDirty());
32
            }
33
34 22
            $model->buyableModel()->create($model->getBuyableDirty());
35 22
        });
36
37
        static::updated(function ($model) {
38 20
            if ($model->isSpecDirty()) {
39 16
                $model->specs()->updateOrCreate(['name' => $model->getSpecDirty()['name']], $model->getSpecDirty());
40
            }
41
42 20
            if ($model->isBuyableDirty()) {
43 4
                $model->buyableModel()->updateOrCreate(
44
                    [
45 4
                        'buyable_type' => array_flip(Relation::$morphMap)[get_class($model)] ?? get_class($model),
46 4
                        'buyable_id' => $model->id
47
                    ],
48 4
                    $model->getBuyableDirty()
49
                );
50
            }
51 22
        });
52
53
        static::deleted(function ($model) {
54 2
            if ($model->isSpecDirty()) {
55 2
                $model->specs()->delete();
56
            }
57 22
        });
58
59
        static::retrieved(function ($model) {
60 10
            if ($model->buyableModel) {
61 10
                foreach ($model->buyableModel->toArray() as $key => $value) {
62 10
                    $model->setOriginalBuyable($key, $value);
63
                }
64
            }
65 22
        });
66 22
    }
67
68 18
    public function specs()
69
    {
70 18
        return $this->morphMany(get_class(resolve(ProductUnitContract::class)), 'buyable');
0 ignored issues
show
Bug introduced by
It seems like morphMany() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
71
    }
72
73 22
    public function buyableModel()
74
    {
75 22
        return $this->morphOne(get_class(resolve(BuyableModelContract::class)), 'buyable');
0 ignored issues
show
Bug introduced by
It seems like morphOne() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
76
    }
77
78 16
    public function setSpec($key, $value)
79
    {
80 16
        if (!in_array($key, $this->specAttributes)) {
81
            throw new InvalidArgumentException();
82
        }
83
84 16
        $key = $key == 'spec' ? 'name' : $key;
85 16
        $this->spec[$key] = $value;
86
87 16
        $this->specified = true;
88 16
    }
89
90 6
    public function getSpec($key)
91
    {
92 6
        if (!($this->specified || $this->isSingleSpec())) {
93 2
            throw new InvalidArgumentException("Didn't specify a spec or it's not a single spec buyable model");
94
        }
95
96 4
        $key = $key == 'spec' ? 'name' : $key;
97 4
        if ($this->isSingleSpec()) {
98 2
            $this->originalSpec = $this->specs->first();
0 ignored issues
show
Bug introduced by
The property specs 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...
99
        }
100
101 4
        return $this->spec[$key] ?? $this->originalSpec[$key];
102
    }
103
104 4
    public function setBuyable($key, $value)
105
    {
106 4
        if (!in_array($key, $this->buyableAttributes)) {
107
            throw new InvalidArgumentException();
108
        }
109
110 4
        $this->buyable[$key] = $value;
111 4
    }
112
113 10
    public function setOriginalBuyable($key, $value)
114
    {
115 10
        $this->originalBuyable[$key] = $value;
116 10
    }
117
118 4
    public function getBuyable($key)
119
    {
120 4
        if (!in_array($key, $this->buyableAttributes)) {
121
            throw new InvalidArgumentException();
122
        }
123
124 4
        return $this->buyable[$key] ?? $this->originalBuyable[$key] ?? null;
125
    }
126
127 22
    public function fill(array $attributes)
128
    {
129 22
        if (isset($attributes['price'])) {
130 16
            $this->setSpec('spec', 'default');
131 16
            foreach (array_only($attributes, $this->specAttributes) as $key => $value) {
132 16
                $this->setSpec($key, $value);
133
            }
134
        }
135
136 22
        foreach (array_only($attributes, $this->buyableAttributes) as $key => $value) {
137 4
            $this->setBuyable($key, $value);
138
        }
139
140 22
        array_forget($attributes, $this->specAttributes);
141 22
        array_forget($attributes, $this->buyableAttributes);
142 22
        return parent::fill($attributes);
143
    }
144
145 22
    public function getSpecDirty()
146
    {
147 22
        return $this->spec;
148
    }
149
150 22
    public function getBuyableDirty()
151
    {
152 22
        return $this->buyable;
153
    }
154
155
156 6
    public function isSingleSpec()
157
    {
158 6
        return $this->specs->count() == 1;
159
    }
160
161 22 View Code Duplication
    public function setAttribute($key, $value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
162
    {
163 22
        foreach (['spec', 'buyable'] as $type) {
164 22
            $attributes = "{$type}Attributes";
165 22
            $method = "set" . ucfirst($type);
166 22
            if (in_array($key, $this->{$attributes})) {
167 4
                $this->{$method}($key, $value);
168 22
                return $this;
169
            }
170
        }
171
172 22
        return parent::setAttribute($key, $value);
173
    }
174
175 22 View Code Duplication
    public function getAttribute($key)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
176
    {
177 22
        foreach (['spec', 'buyable'] as $type) {
178 22
            $attributes = "{$type}Attributes";
179 22
            $method = "get" . ucfirst($type);
180 22
            if (in_array($key, $this->{$attributes})) {
181 22
                return $this->{$method}($key);
182
            }
183
        }
184 22
        return parent::getAttribute($key);
185
    }
186
187 22
    public function isSpecDirty()
188
    {
189 22
        return is_array($this->getSpecDirty()) && count($this->getSpecDirty()) > 0;
190
    }
191
192 22
    public function isBuyableDirty()
193
    {
194 22
        return is_array($this->getBuyableDirty()) && count($this->getBuyableDirty()) > 0;
195
    }
196
197 22
    public function save(array $options = [])
198
    {
199 22
        if (!parent::save($options)) {
200
            return false;
201
        }
202
203 22
        if ($this->exists && ($this->isSpecDirty() || $this->isBuyableDirty())) {
0 ignored issues
show
Bug introduced by
The property exists 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...
204 20
            $this->fireModelEvent('saved', false);
0 ignored issues
show
Bug introduced by
It seems like fireModelEvent() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
205 20
            $this->fireModelEvent('updated', false);
0 ignored issues
show
Bug introduced by
It seems like fireModelEvent() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
206
        }
207
208 22
        return true;
209
    }
210
211 2
    public function specify($spec)
212
    {
213
        switch (true) {
214 2
            case $spec instanceof Model:
0 ignored issues
show
Bug introduced by
The class UniSharp\Buyable\Traits\Model does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
215
                $this->originalSpec = $spec;
216
                break;
217 2
            case is_numeric($spec):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
218 2
                $this->originalSpec = $this->specs->where('id', $spec)->first();
219 2
                break;
220 2
            case is_string($spec):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
221 2
                $this->originalSpec = $this->specs->where('name', $spec)->first();
222 2
                break;
223
        }
224
225 2
        $this->specified = true;
226 2
        return $this;
227
    }
228
229
    public function getSpecifiedSpec()
230
    {
231
        if ($this->isSingleSpec()) {
232
            return $this->specs->first();
233
        }
234
235
        if ($this->specified) {
236
            return $this->specs->where('id', $this->originalSpec['id']);
237
        }
238
    }
239
240
    public function singleSpecToArray()
241
    {
242
        $array = [];
243
        if ($this->isSingleSpec()) {
244
            foreach ($this->specAttributes as $attribute) {
245
                $array[$attribute] = $this->getSpec($attribute);
246
            }
247
        }
248
249
        return $array;
250
    }
251
252
    public function buyableToArray()
253
    {
254
        foreach ($this->buyableAttributes as $attribute) {
255
            $array[$attribute] = $this->getBuyable($attribute);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$array was never initialized. Although not strictly required by PHP, it is generally a good practice to add $array = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
256
        }
257
258
        return $array;
0 ignored issues
show
Bug introduced by
The variable $array does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
259
    }
260
261
    public function toArray()
262
    {
263
        return array_merge(
264
            $this->attributesToArray(),
0 ignored issues
show
Bug introduced by
The method attributesToArray() does not exist on UniSharp\Buyable\Traits\Buyable. Did you maybe mean toArray()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
265
            $this->relationsToArray(),
0 ignored issues
show
Bug introduced by
The method relationsToArray() does not exist on UniSharp\Buyable\Traits\Buyable. Did you maybe mean toArray()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
266
            $this->singleSpecToArray(),
267
            $this->buyableToArray()
268
        );
269
    }
270
}
271