Completed
Pull Request — master (#17)
by
unknown
01:34
created

HasStates::transitionableStates()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 4
nc 3
nop 2
1
<?php
2
3
namespace Spatie\ModelStates;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Database\Eloquent\Builder;
7
use Spatie\ModelStates\Exceptions\InvalidConfig;
8
use Spatie\ModelStates\Exceptions\CouldNotPerformTransition;
9
10
/**
11
 * @mixin \Illuminate\Database\Eloquent\Model
12
 */
13
trait HasStates
14
{
15
    /** @var \Spatie\ModelStates\StateConfig[]|null */
16
    protected static $stateFields = null;
17
18
    abstract protected function registerStates(): void;
19
20
    public static function bootHasStates(): void
21
    {
22
        $serialiseState = function (StateConfig $stateConfig) {
23
            return function (Model $model) use ($stateConfig) {
24
                $value = $model->getAttribute($stateConfig->field);
25
26
                if ($value === null) {
27
                    $value = $stateConfig->defaultStateClass;
28
                }
29
30
                if ($value === null) {
31
                    return;
32
                }
33
34
                $stateClass = $stateConfig->stateClass::resolveStateClass($value);
35
36
                if (! is_subclass_of($stateClass, $stateConfig->stateClass)) {
37
                    throw InvalidConfig::fieldDoesNotExtendState(
38
                        $stateConfig->field,
39
                        $stateConfig->stateClass,
40
                        $stateClass
41
                    );
42
                }
43
44
                $model->setAttribute(
45
                    $stateConfig->field,
46
                    State::resolveStateName($value)
47
                );
48
            };
49
        };
50
51
        $unserialiseState = function (StateConfig $stateConfig) {
52
            return function (Model $model) use ($stateConfig) {
53
                $stateClass = $stateConfig->stateClass::resolveStateClass($model->getAttribute($stateConfig->field));
54
55
                $defaultState = $stateConfig->defaultStateClass
56
                    ? new $stateConfig->defaultStateClass($model)
57
                    : null;
58
59
                $model->setAttribute(
60
                    $stateConfig->field,
61
                    class_exists($stateClass)
62
                        ? new $stateClass($model)
63
                        : $defaultState
64
                );
65
            };
66
        };
67
68
        foreach (self::getStateConfig() as $stateConfig) {
69
            static::retrieved($unserialiseState($stateConfig));
70
            static::created($unserialiseState($stateConfig));
71
            static::saved($unserialiseState($stateConfig));
72
73
            static::updating($serialiseState($stateConfig));
74
            static::creating($serialiseState($stateConfig));
75
            static::saving($serialiseState($stateConfig));
76
        }
77
    }
78
79
    public function initializeHasStates(): void
80
    {
81
        foreach (self::getStateConfig() as $stateConfig) {
82
            if (! $stateConfig->defaultStateClass) {
83
                continue;
84
            }
85
86
            $this->{$stateConfig->field} = new $stateConfig->defaultStateClass($this);
87
        }
88
    }
89
90 View Code Duplication
    public function scopeWhereState(Builder $builder, string $field, $states): Builder
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
91
    {
92
        self::getStateConfig();
93
94
        /** @var \Spatie\ModelStates\StateConfig|null $stateConfig */
95
        $stateConfig = self::getStateConfig()[$field] ?? null;
96
97
        if (! $stateConfig) {
98
            throw InvalidConfig::unknownState($field, $this);
99
        }
100
101
        $abstractStateClass = $stateConfig->stateClass;
102
103
        $stateNames = collect((array) $states)->map(function ($state) use ($abstractStateClass) {
104
            return $abstractStateClass::resolveStateName($state);
105
        });
106
107
        return $builder->whereIn($field, $stateNames);
108
    }
109
110 View Code Duplication
    public function scopeWhereNotState(Builder $builder, string $field, $states): Builder
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
    {
112
        /** @var \Spatie\ModelStates\StateConfig|null $stateConfig */
113
        $stateConfig = self::getStateConfig()[$field] ?? null;
114
115
        if (! $stateConfig) {
116
            throw InvalidConfig::unknownState($field, $this);
117
        }
118
119
        $stateNames = collect((array) $states)->map(function ($state) use ($stateConfig) {
120
            return $stateConfig->stateClass::resolveStateName($state);
121
        });
122
123
        return $builder->whereNotIn($field, $stateNames);
124
    }
125
126
    /**
127
     * @param \Spatie\ModelStates\State|string $state
128
     * @param string|null $field
129
     */
130
    public function transitionTo($state, string $field = null)
131
    {
132
        $stateConfig = self::getStateConfig();
133
134
        if ($field === null && count($stateConfig) > 1) {
135
            throw CouldNotPerformTransition::couldNotResolveTransitionField($this);
136
        }
137
138
        $field = $field ?? reset($stateConfig)->field;
139
140
        $this->{$field}->transitionTo($state);
141
    }
142
143
    /**
144
     * @param string $fromClass
145
     * @param string|null $field
146
     */
147
    public function transitionableStates(string $fromClass, string $field = null)
148
    {
149
        $stateConfig = self::getStateConfig();
150
151
        if ($field === null && count($stateConfig) > 1) {
152
            throw InvalidConfig::fieldNotFound($fromClass, $this);
153
        }
154
155
        $field = $field ?? reset($stateConfig)->field;
156
157
        if (! array_key_exists($field, static::getStateConfig())) {
158
            throw InvalidConfig::unknownState($field, $this);
159
        }
160
161
        return static::getStateConfig()[$field]->transitionableStates($fromClass);
162
    }
163
164
    /**
165
     * @param string $fromClass
166
     * @param string $toClass
167
     *
168
     * @return \Spatie\ModelStates\Transition|string|null
169
     */
170
    public function resolveTransitionClass(string $fromClass, string $toClass)
171
    {
172
        foreach (static::getStateConfig() as $stateConfig) {
173
            $transitionClass = $stateConfig->resolveTransition($this, $fromClass, $toClass);
174
175
            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...
176
                return $transitionClass;
177
            }
178
        }
179
180
        throw CouldNotPerformTransition::notFound($fromClass, $toClass, $this);
181
    }
182
183
    protected function addState(string $field, string $stateClass): StateConfig
184
    {
185
        $stateConfig = new StateConfig($field, $stateClass);
186
187
        static::$stateFields[$stateConfig->field] = $stateConfig;
188
189
        return $stateConfig;
190
    }
191
192
    /**
193
     * @return \Spatie\ModelStates\StateConfig[]
194
     */
195
    private static function getStateConfig(): array
196
    {
197
        if (static::$stateFields === null) {
198
            static::$stateFields = [];
199
200
            (new static)->registerStates();
201
        }
202
203
        return static::$stateFields ?? [];
204
    }
205
}
206