Completed
Push — master ( 7e5136...d90979 )
by Alexander
03:30
created

CanBeAdjusted::mergeAndFilterChanges()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 1
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 ( is_null( $changes ) ) {
95
            return $this;
96
        } elseif ( is_string( $changes ) ) {
97
            $changes = json_decode( $changes, true );
98
        } elseif ( $changes instanceof Collection ) {
99
            $changes = $changes->toArray();
100
        }
101
102
        $this->fill( $changes );
103
        $this->adjusted = true;
104
105
        return $this;
106
    }
107
108
    /**
109
     * Checks if the given model has applied the adjustments.
110
     *
111
     * @return bool
112
     */
113
    public function isAdjusted():bool
114
    {
115
        return $this->adjusted;
116
    }
117
118
    /**
119
     * Checks if save protection is enabled. Save protection protects you from persisting
120
     * the model after applying the adjustments.
121
     *
122
     * @return bool
123
     */
124
    public function hasSaveProtection():bool
125
    {
126
        return $this->saveProtection ?? config( 'adjuster.save_protection' );
127
    }
128
129
    /**
130
     * We will fetch any existing changes from the adjustment and then filter them down
131
     * based on certain criterias. Then we will return the changes converted to the
132
     * correct data type depending on set casts on the Adjustment model.
133
     *
134
     * @param  arrray $changes
0 ignored issues
show
Documentation introduced by
Should the type for parameter $changes not be array?

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...
135
     * @param  Model  $adjustment
136
     * @return mixed
137
     */
138
    protected function mergeAndFilterChanges( array $changes, Model $adjustment )
139
    {
140
        $existingChanges = collect( $adjustment->{config( 'adjuster.changes_column' )} );
141
142
        return $existingChanges->merge( $changes )->filter( function ( $value, $attribute ) {
143
            return ! is_null( $value ) && $this->$attribute !== $value;
144
        } );
145
    }
146
147
    /**
148
     * Cast the changes collection to the appropiate type.
149
     *
150
     * @param  Collection $changes
151
     * @param  Model      $adjustment
152
     * @return mixed
153
     */
154
    protected function castChanges( Collection $changes, Model $adjustment )
155
    {
156
        $cast = $adjustment->hasCast( config( 'adjuster.changes_column' ) );
157
158
        switch ( $cast ) {
159
            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...
160
                return $changes;
161
            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...
162
            case 'json':
163
                return $changes->toArray();
164
            default:
165
                return $changes->toJson();
166
        }
167
    }
168
169
    /**
170
     * Fill the model with an array of attributes.
171
     */
172
    abstract public function fill( array $attributes );
173
174
    /**
175
     * Define a polymorphic one-to-one relationship.
176
     */
177
    abstract public function morphOne( $related, $name, $type = null, $id = null, $localKey = null );
178
179
    /**
180
     * Define a one-to-one relationship.
181
     */
182
    abstract public function hasOne( $related, $foreignKey = null, $localKey = null );
183
}