Passed
Push — master ( c9730e...948ea5 )
by Alexander
02:26
created

LoaderBehavior::init()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.072

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 5
cp 0.8
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 2
nop 0
crap 3.072
1
<?php
2
3
namespace Horat1us\Yii\Behaviors;
4
5
use yii\base\Behavior;
6
use yii\base\InvalidConfigException;
7
use yii\base\Model;
8
9
use yii\db\ActiveQuery;
10
use yii\db\ActiveRecord;
11
12
use yii\helpers\Inflector;
13
use yii\web\NotFoundHttpException;
14
15
/**
16
 * Class LoaderBehavior
17
 * @package Horat1us\Yii\Behaviors
18
 *
19
 * @todo tests
20
 */
21
class LoaderBehavior extends Behavior
22
{
23
    /**
24
     * Callback, returns identifier value (will be used in filtering)
25
     * @see targetAttribute
26
     * @see queryFilter
27
     *
28
     * ```php
29
     * <?php
30
     * $id = function(): string {
31
     *  return \Yii::$app->request->post('some-post-value');
32
     * };
33
     * ```
34
     *
35
     * Or string - name of get parameter
36
     *
37
     * @var string|callable
38
     */
39
    public $id = 'id';
40
41
    /**
42
     * ActiveRecord class
43
     * will be used for creating query
44
     *
45
     * @var string
46
     */
47
    public $targetClass;
48
49
    /**
50
     * ActiveRecord attribute that will be used for auto filtering
51
     * @var string
52
     */
53
    public $targetAttribute = 'id';
54
55
    /**
56
     * Custom query filter.
57
     * Receives query as first argument and id value as second
58
     * Have to return ActiveQuery:
59
     *
60
     * ```php
61
     * <?php
62
     * $queryFilter = function(\yii\db\ActiveQuery $query, string $id) {
63
     *  return $query->andWhere(['=', 'some_attribute', $id + 1]);
64
     * };
65
     * ```
66
     *
67
     * @var callable
68
     */
69
    public $queryFilter;
70
71
    /**
72
     * Model attribute to load record to
73
     * Default value will be counted from targetClass::tableName method
74
     *
75
     * For example: if your active record table name is `post_comment_author`
76
     * this value will be set to `postCommentAuthor`
77
     *
78
     * @see ActiveRecord::tableName()
79
     * @see Inflector::camelize()
80
     *
81
     * @var string
82
     */
83
    public $attribute;
84
85
    /**
86
     * Callable that receives record if it found
87
     *
88
     * ```php
89
     * <?php
90
     * $load = function(\yii\db\ActiveRecord $record) use($model) {
91
     *  $model->dependency = $record;
92
     * };
93
     * ```
94
     *
95
     * @var callable
96
     */
97
    public $load;
98
99
    /**
100
     * Callable that receives id and should find record or entity
101
     *
102
     * ```php
103
     * $query = function(int $id): ?ActiveRecord {
104
     *  return SomeRecord::find()->andWhere(['=', 'id', $id])->one();
105
     * }
106
     * ```
107
     *
108
     * @var
109
     */
110
    public $query;
111
112
    /** @var callable */
113
    public $notFoundCallback;
114
115 1
    public function events()
116
    {
117
        return [
118 1
            Model::EVENT_BEFORE_VALIDATE => [$this, 'load'],
119
        ];
120
    }
121
122 1
    public function init()
123
    {
124 1
        parent::init();
125 1
        if (!empty($this->targetClass) && empty($this->attribute)) {
126
            /** @see ActiveRecord::tableName() */
127
            $this->attribute = lcfirst(Inflector::camelize(call_user_func([$this->targetClass, 'tableName'])));
128
        }
129 1
    }
130
131
    /**
132
     * @throws NotFoundHttpException
133
     * @throws InvalidConfigException
134
     */
135 1
    public function load(): void
136
    {
137 1
        $id = null;
138
139 1
        if (is_string($this->id)) {
140 1
            $id = \Yii::$app->request->get($this->id);
141
        } elseif (is_callable($this->id)) {
142
            $id = call_user_func($this->id, $this);
143
        }
144
145 1
        if (empty($id)) {
146 1
            $this->notFound($id);
147
            return;
148
        }
149
150
        $object = $this->query($id);
151
        if (!$object instanceof $this->targetClass) {
152
            $this->notFound($id);
153
            return;
154
        }
155
156
        $this->inject($object);
157
    }
158
159
    protected function inject(object $object): void
160
    {
161
        if (is_callable($this->load)) {
162
            call_user_func($this->load, $object);
163
            return;
164
        }
165
166
        $closure = function (object $value, string $attribute) {
167
            $this->{$attribute} = $value;
168
        };
169
        $closure->call($this->owner, $object, $this->attribute);
170
    }
171
172
    /**
173
     * @param int $id
174
     * @return null|object
175
     * @throws InvalidConfigException
176
     */
177
    protected function query(int $id): ?object
178
    {
179
        if (is_callable($this->query)) {
180
            return call_user_func($this->query, $id);
181
        }
182
183
        $query = call_user_func([$this->targetClass, 'find']);
184
        if (is_callable($this->queryFilter)) {
185
            $query = call_user_func($this->queryFilter, $query, $id);
186
        } elseif (!empty($this->targetAttribute)) {
187
            $query->andWhere(['=', $this->targetAttribute, $id]);
188
        } else {
189
            throw new InvalidConfigException("Query filter or target attribute have to be configured.");
190
        }
191
192
        return $query->one();
193
    }
194
195
    /**
196
     * @param mixed $id
197
     * @throws NotFoundHttpException
198
     */
199 1
    protected function notFound($id = null): void
200
    {
201 1
        if (is_callable($this->notFoundCallback)) {
202
            call_user_func($this->notFoundCallback, $id);
203
            return;
204
        }
205
206 1
        throw new NotFoundHttpException($id ? "Resource {$id} not found" : "Resource id was not specified.");
207
    }
208
}
209