Completed
Pull Request — master (#31)
by Brent
01:11
created

HasStates::scopeWhereNotState()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
cc 2
nc 2
nop 3
1
<?php
2
3
namespace Spatie\ModelStates;
4
5
use Illuminate\Support\Collection;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\Builder;
8
use Spatie\ModelStates\Exceptions\InvalidConfig;
9
use Spatie\ModelStates\Exceptions\CouldNotPerformTransition;
10
11
/**
12
 * @mixin \Illuminate\Database\Eloquent\Model
13
 */
14
trait HasStates
15
{
16
    /** @var \Spatie\ModelStates\StateConfig[]|null */
17
    protected static $stateFields = null;
18
19
    abstract protected function registerStates(): void;
20
21
    public function __set($name, $value): void
22
    {
23
        if ($value instanceof State) {
24
            $value->setField($name);
25
        }
26
27
        parent::__set($name, $value);
28
    }
29
30
    public static function bootHasStates(): void
31
    {
32
        $serialiseState = function (StateConfig $stateConfig) {
33
            return function (Model $model) use ($stateConfig) {
34
                $value = $model->getAttribute($stateConfig->field);
35
36
                if ($value === null) {
37
                    $value = $stateConfig->defaultStateClass;
38
                }
39
40
                if ($value === null) {
41
                    return;
42
                }
43
44
                $stateClass = $stateConfig->stateClass::resolveStateClass($value);
45
46
                if (! is_subclass_of($stateClass, $stateConfig->stateClass)) {
47
                    throw InvalidConfig::fieldDoesNotExtendState(
48
                        $stateConfig->field,
49
                        $stateConfig->stateClass,
50
                        $stateClass
51
                    );
52
                }
53
54
                $model->setAttribute(
55
                    $stateConfig->field,
56
                    State::resolveStateName($value)
57
                );
58
            };
59
        };
60
61
        $unserialiseState = function (StateConfig $stateConfig) {
62
            return function (Model $model) use ($stateConfig) {
63
                $stateClass = $stateConfig->stateClass::resolveStateClass($model->getAttribute($stateConfig->field));
64
65
                $defaultState = $stateConfig->defaultStateClass
66
                    ? new $stateConfig->defaultStateClass($model)
67
                    : null;
68
69
                /** @var \Spatie\ModelStates\State $state */
70
                $state = class_exists($stateClass)
71
                    ? new $stateClass($model)
72
                    : $defaultState;
73
74
                $state->setField($stateConfig->field);
75
76
                $model->setAttribute(
77
                    $stateConfig->field,
78
                    $state
79
                );
80
            };
81
        };
82
83
        foreach (self::getStateConfig() as $stateConfig) {
84
            static::retrieved($unserialiseState($stateConfig));
85
            static::created($unserialiseState($stateConfig));
86
            static::saved($unserialiseState($stateConfig));
87
88
            static::updating($serialiseState($stateConfig));
89
            static::creating($serialiseState($stateConfig));
90
            static::saving($serialiseState($stateConfig));
91
        }
92
    }
93
94
    public function initializeHasStates(): void
95
    {
96
        foreach (self::getStateConfig() as $stateConfig) {
97
            if (! $stateConfig->defaultStateClass) {
98
                continue;
99
            }
100
101
            $this->{$stateConfig->field} = new $stateConfig->defaultStateClass($this);
102
        }
103
    }
104
105
    public function scopeWhereState(Builder $builder, string $field, $states): Builder
106
    {
107
        self::getStateConfig();
108
109
        /** @var \Spatie\ModelStates\StateConfig|null $stateConfig */
110
        $stateConfig = self::getStateConfig()[$field] ?? null;
111
112
        if (! $stateConfig) {
113
            throw InvalidConfig::unknownState($field, $this);
114
        }
115
116
        $abstractStateClass = $stateConfig->stateClass;
117
118
        $stateNames = collect((array) $states)->map(function ($state) use ($abstractStateClass) {
119
            return $abstractStateClass::resolveStateName($state);
120
        });
121
122
        return $builder->whereIn($field, $stateNames);
123
    }
124
125
    public function scopeWhereNotState(Builder $builder, string $field, $states): Builder
126
    {
127
        /** @var \Spatie\ModelStates\StateConfig|null $stateConfig */
128
        $stateConfig = self::getStateConfig()[$field] ?? null;
129
130
        if (! $stateConfig) {
131
            throw InvalidConfig::unknownState($field, $this);
132
        }
133
134
        $stateNames = collect((array) $states)->map(function ($state) use ($stateConfig) {
135
            return $stateConfig->stateClass::resolveStateName($state);
136
        });
137
138
        return $builder->whereNotIn($field, $stateNames);
139
    }
140
141
    /**
142
     * @param \Spatie\ModelStates\State|string $state
143
     * @param string|null $field
144
     */
145
    public function transitionTo($state, string $field = null)
146
    {
147
        $stateConfig = self::getStateConfig();
148
149
        if ($field === null && count($stateConfig) > 1) {
150
            throw CouldNotPerformTransition::couldNotResolveTransitionField($this);
151
        }
152
153
        $field = $field ?? reset($stateConfig)->field;
154
155
        $this->{$field}->transitionTo($state);
156
    }
157
158
    public function transitionableStates(string $fromClass, ?string $field = null): array
159
    {
160
        $stateConfig = self::getStateConfig();
161
162
        if ($field === null && count($stateConfig) > 1) {
163
            throw InvalidConfig::fieldNotFound($fromClass, $this);
164
        }
165
166
        $field = $field ?? reset($stateConfig)->field;
167
168
        if (! array_key_exists($field, $stateConfig)) {
169
            throw InvalidConfig::unknownState($field, $this);
170
        }
171
172
        return $stateConfig[$field]->transitionableStates($fromClass);
173
    }
174
175
    /**
176
     * @param string $fromClass
177
     * @param string $toClass
178
     *
179
     * @return \Spatie\ModelStates\Transition|string|null
180
     */
181
    public function resolveTransitionClass(string $fromClass, string $toClass)
182
    {
183
        foreach (static::getStateConfig() as $stateConfig) {
184
            $transitionClass = $stateConfig->resolveTransition($this, $fromClass, $toClass);
185
186
            if ($transitionClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $transitionClass of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
187
                return $transitionClass;
188
            }
189
        }
190
191
        throw CouldNotPerformTransition::notFound($fromClass, $toClass, $this);
192
    }
193
194
    protected function addState(string $field, string $stateClass): StateConfig
195
    {
196
        $stateConfig = new StateConfig($field, $stateClass);
197
198
        static::$stateFields[$stateConfig->field] = $stateConfig;
199
200
        return $stateConfig;
201
    }
202
203
    /**
204
     * @return \Spatie\ModelStates\StateConfig[]
205
     */
206
    private static function getStateConfig(): array
207
    {
208
        if (static::$stateFields === null) {
209
            static::$stateFields = [];
210
211
            (new static)->registerStates();
212
        }
213
214
        return static::$stateFields ?? [];
215
    }
216
217
    public static function getStates(): Collection
218
    {
219
        return collect(static::getStateConfig())
220
            ->map(function ($state) {
221
                return $state->stateClass::all();
222
            });
223
    }
224
225
    public static function getStatesFor(string $column): Collection
226
    {
227
        return static::getStates()->get($column, new Collection);
228
    }
229
230
    public static function getDefaultStates(): Collection
231
    {
232
        return collect(static::getStateConfig())
233
            ->map(function ($state) {
234
                return $state->defaultStateClass;
235
            });
236
    }
237
238
    public static function getDefaultStateFor(string $column): string
239
    {
240
        return static::getDefaultStates()->get($column);
241
    }
242
}
243