Completed
Push — master ( d90979...e3ea1b )
by Alexander
03:31
created

CanBeAdjusted::isValidChange()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.2
c 0
b 0
f 0
cc 4
eloc 6
nc 3
nop 2
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
        $adjustment = $this->adjustment()->exists() ? $this->adjustment : app( 'adjuster.model' );
58
        $changes = $this->mergeAndFilterChanges( $changes, $adjustment );
59
60
        if ( $changes->isEmpty() ) {
61
            $adjustment->delete();
62
        } else {
63
            $adjustment->fill( $attributes );
64
            $adjustment->{config( 'adjuster.changes_column' )} = $this->castChanges( $changes, $adjustment );
65
            $this->adjustment()->save( $adjustment );
66
67
            return $adjustment;
68
        }
69
    }
70
71
    /**
72
     * Get the adjustment associated with the adjustable model.
73
     *
74
     * @return Relation
75
     */
76
    public function adjustment():Relation
77
    {
78
        if ( config( 'adjuster.polymorphic' ) ) {
79
            return $this->morphOne( config( 'adjuster.adjustment_model' ), config( 'adjuster.adjustable_column' ) );
80
        } else {
81
            return $this->hasOne( config( 'adjuster.adjustment_model' ), config( 'adjuster.adjustable_column' ) );
82
        }
83
    }
84
85
    /**
86
     * Fill the model instance with the adjusted values, replacing the original values.
87
     *
88
     * @return $this
89
     */
90
    public function applyAdjustments():Model
91
    {
92
        $changes = $this->adjustment->{config( 'adjuster.changes_column' )} ?? null;
93
94
        if ( $changes instanceof Collection ) {
95
            $changes = $changes->toArray();
96
        } elseif ( is_string( $changes ) ) {
97
            $changes = json_decode( $changes, true );
98
        } elseif ( is_null( $changes ) ) {
99
            return $this;
100
        }
101
102
        $this->adjusted = true;
103
104
        return $this->fill( $changes );
105
    }
106
107
    /**
108
     * Checks if the given model has applied the adjustments.
109
     *
110
     * @return bool
111
     */
112
    public function isAdjusted():bool
113
    {
114
        return $this->adjusted;
115
    }
116
117
    /**
118
     * Checks if save protection is enabled. Save protection protects you from persisting
119
     * the model after applying the adjustments.
120
     *
121
     * @return bool
122
     */
123
    public function hasSaveProtection():bool
124
    {
125
        return $this->saveProtection ?? config( 'adjuster.save_protection' );
126
    }
127
128
    /**
129
     * We will fetch any existing changes from the adjustment and then filter them down
130
     * based on certain criterias. Then we will return the changes converted to the
131
     * correct data type depending on set casts on the Adjustment model.
132
     *
133
     * @param  array $changes
134
     * @param  Model $adjustment
135
     * @return Collection
136
     */
137
    protected function mergeAndFilterChanges( array $changes, Model $adjustment ):Collection
138
    {
139
        $existingChanges = collect( $adjustment->{config( 'adjuster.changes_column' )} );
140
141
        return $existingChanges->merge( $changes )->filter( function ( $value, $attribute ) {
142
            return $this->isValidChange( $attribute, $value );
143
        } );
144
    }
145
146
    /**
147
     * Checks if a given attribute value pair is valid. The value should not be null and
148
     * not be equal the original value, and the attribute should actually be fillable.
149
     *
150
     * @param  string $changes
0 ignored issues
show
Bug introduced by
There is no parameter named $changes. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
151
     * @param  mixed  $value
152
     * @return bool
153
     */
154
    protected function isValidChange( string $attribute, $value ):bool
155
    {
156
        if ( ! $this->isFillable( $attribute ) ) {
0 ignored issues
show
Bug introduced by
It seems like isFillable() 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...
157
            return false;
158
        }
159
160
        if ( is_null( $value ) || $value === $this->$attribute ) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !(is_null($value)...= $this->{$attribute});.
Loading history...
161
            return false;
162
        }
163
164
        return true;
165
    }
166
167
    /**
168
     * Cast the changes collection to the appropiate type.
169
     *
170
     * @param  Collection $changes
171
     * @param  Model      $adjustment
172
     * @return mixed
173
     */
174
    protected function castChanges( Collection $changes, Model $adjustment )
175
    {
176
        $cast = $adjustment->hasCast( config( 'adjuster.changes_column' ) );
177
178
        switch ( $cast ) {
179
            case 'collection':
180
                return $changes;
181
            case 'array':
182
            case 'json':
183
                return $changes->toArray();
184
            default:
185
                return $changes->toJson();
186
        }
187
    }
188
189
    /**
190
     * Fill the model with an array of attributes.
191
     */
192
    abstract public function fill( array $attributes );
193
194
    /**
195
     * Define a polymorphic one-to-one relationship.
196
     */
197
    abstract public function morphOne( $related, $name, $type = null, $id = null, $localKey = null );
198
199
    /**
200
     * Define a one-to-one relationship.
201
     */
202
    abstract public function hasOne( $related, $foreignKey = null, $localKey = null );
203
}