CanBeAdjusted::applyAdjustments()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 4
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
        $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 $attribute
151
     * @param  mixed  $value
152
     * @return bool
153
     */
154
    protected function isValidChange( string $attribute, $value ):bool
155
    {
156
        if ( is_null( $value ) || $value === $this->$attribute ) {
157
            return false;
158
        }
159
160
        return $this->isFillable( $attribute );
161
    }
162
163
    /**
164
     * Cast the changes collection to the appropiate type.
165
     *
166
     * @param  Collection $changes
167
     * @param  Model      $adjustment
168
     * @return mixed
169
     */
170
    protected function castChanges( Collection $changes, Model $adjustment )
171
    {
172
        $cast = $adjustment->hasCast( config( 'adjuster.changes_column' ) );
173
174
        switch ( $cast ) {
175
            case 'collection':
176
                return $changes;
177
            case 'array':
178
            case 'json':
179
                return $changes->toArray();
180
            default:
181
                return $changes->toJson();
182
        }
183
    }
184
185
    /**
186
     * Fill the model with an array of attributes.
187
     */
188
    abstract public function fill( array $attributes );
189
190
    /**
191
     * Determine if the given attribute may be mass assigned.
192
     */
193
    abstract public function isFillable( $key );
194
195
    /**
196
     * Define a polymorphic one-to-one relationship.
197
     */
198
    abstract public function morphOne( $related, $name, $type = null, $id = null, $localKey = null );
199
200
    /**
201
     * Define a one-to-one relationship.
202
     */
203
    abstract public function hasOne( $related, $foreignKey = null, $localKey = null );
204
}