Completed
Push — master ( 072fa4...a16d14 )
by Alexander
05:24
created

CanBeAdjusted::applyAdjustments()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 15
rs 9.4285
cc 3
eloc 9
nc 3
nop 0
1
<?php
2
3
namespace Mangopixel\Adjuster;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Database\Eloquent\Relations\Relation;
7
use Illuminate\Support\Collection;
8
use Mangopixel\Adjuster\Contracts\Adjustable;
9
use Mangopixel\Adjuster\Exceptions\ModelAdjustedException;
10
11
/**
12
 * This trait is where most of the package logic lives. This trait satisfies the
13
 * entire Adjustable contract and you should only use the trait on classes that
14
 * extend Illuminate\Database\Eloquent\Model.
15
 *
16
 * @package Laravel Adjuster
17
 * @author  Alexander Tømmerås <[email protected]>
18
 * @license The MIT License
19
 */
20
trait CanBeAdjusted
21
{
22
    /**
23
     * A boolean to keep track of wether the model has been adjusted or not.
24
     *
25
     * @var bool
26
     */
27
    protected $adjusted = false;
28
29
    /**
30
     * The booting method of the model trait. This method will be called once the model
31
     * has been booted, and registers an event listener that listens for save calls
32
     * to block if save protection is enabled.
33
     *
34
     * @return void
35
     * @throws ModelAdjustedException
36
     */
37
    protected static function bootCanBeAdjusted()
38
    {
39
        static::saving( function ( Adjustable $model ) {
40
            if ( $model->isAdjusted() && $model->hasSaveProtection() ) {
41
                throw new ModelAdjustedException();
42
            }
43
        } );
44
    }
45
46
    /**
47
     * Adjusts the model by updating an existing record in the adjustments table or adds
48
     * a new one if no previous adjustments are set. All changes will be merged with
49
     * old changes and old changes can be unset using null.
50
     *
51
     * @param  array $changes
52
     * @param  array $attributes
53
     * @return Model|null
54
     */
55
    public function adjust( array $changes, array $attributes = [ ] )
56
    {
57
        $changesColumn = config( 'adjuster.changes_column' );
58
        $adjustment = $this->adjustment()->exists() ? $this->adjustment : app( 'adjuster.model' );
0 ignored issues
show
Bug introduced by
The property adjustment 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...
59
        $existingChanges = collect( $adjustment->$changesColumn );
60
61
        // We will fetch any existing changes from the adjustment and then filter them down
62
        // based on certain criterias. If the value is null or the adjusted value equals
63
        // the original value we will remove the value before persisting the changes.
64
        $changes = $existingChanges->merge( $changes )->filter( function ( $value, $attribute ) {
65
            return ! is_null( $value ) && $this->$attribute !== $value;
66
        } );
67
68
        if ( $changes->isEmpty() ) {
69
            $adjustment->delete();
70
        } else {
71
            $adjustment->fill( $attributes );
72
            $adjustment->$changesColumn = $this->castChanges( $changes, $adjustment );
73
            $this->adjustment()->save( $adjustment );
74
75
            return $adjustment;
76
        }
77
    }
78
79
    /**
80
     * Get the adjustment associated with the adjustable model.
81
     *
82
     * @return Relation
83
     */
84
    public function adjustment():Relation
85
    {
86
        if ( config( 'adjuster.polymorphic' ) ) {
87
            return $this->morphOne( config( 'adjuster.adjustment_model' ), config( 'adjuster.adjustable_column' ) );
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...
88
        } else {
89
            return $this->hasOne( config( 'adjuster.adjustment_model' ), config( 'adjuster.adjustable_column' ) );
0 ignored issues
show
Bug introduced by
It seems like hasOne() 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...
90
        }
91
    }
92
93
    /**
94
     * Fill the model instance with the adjusted values, replacing the original values.
95
     *
96
     * @return self
97
     */
98
    public function applyAdjustments():Model
99
    {
100
        $changes = $this->adjustment->{config( 'adjuster.changes_column' )} ?? null;
101
102
        if ( is_string( $changes ) ) {
103
            $changes = json_decode( $changes, true );
104
        } elseif ( $changes instanceof Collection ) {
105
            $changes = $changes->toArray();
106
        }
107
108
        $this->fill( $changes );
0 ignored issues
show
Bug introduced by
It seems like fill() 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...
109
        $this->adjusted = true;
110
111
        return $this;
112
    }
113
114
    /**
115
     * Checks if the given model has applied the adjustments.
116
     *
117
     * @return bool
118
     */
119
    public function isAdjusted():bool
120
    {
121
        return $this->adjusted;
122
    }
123
124
    /**
125
     * Checks if save protection is enabled. Save protection protects you from persisting
126
     * the model after applying the adjustments.
127
     *
128
     * @return bool
129
     */
130
    public function hasSaveProtection():bool
131
    {
132
        return $this->saveProtection ?? config( 'adjuster.save_protection' );
0 ignored issues
show
Bug introduced by
The property saveProtection 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...
133
    }
134
135
    /**
136
     * Check if the changes attribute has any set casts on the given model, and if so we
137
     * cast the changes collection to the appropiate type.
138
     *
139
     * @param  Collection $changes
140
     * @param  Model      $adjustment
141
     * @return mixed
142
     */
143
    protected function castChanges( Collection $changes, Model $adjustment )
144
    {
145
        switch ( $adjustment->hasCast( config( 'adjuster.changes_column' ) ) ) {
146
            case 'collection':
0 ignored issues
show
Coding Style introduced by
CASE statements must 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.

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

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

Loading history...
147
                return $changes;
148
            case 'array':
0 ignored issues
show
Coding Style introduced by
CASE statements must 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.

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

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

Loading history...
149
            case 'json':
150
                return $changes->toArray();
151
            default:
152
                return $changes->toJson();
153
        }
154
    }
155
}