Builder::getModel()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 10
cc 3
nc 3
nop 0
crap 3
1
<?php
2
3
namespace Spinen\ClickUp\Support;
4
5
use BadMethodCallException;
6
use GuzzleHttp\Exception\GuzzleException;
7
use Illuminate\Support\Arr;
8
use Illuminate\Support\Collection as LaravelCollection;
9
use Spinen\ClickUp\Concerns\HasClient;
10
use Spinen\ClickUp\Exceptions\InvalidRelationshipException;
11
use Spinen\ClickUp\Exceptions\ModelNotFoundException;
12
use Spinen\ClickUp\Exceptions\NoClientException;
13
use Spinen\ClickUp\Exceptions\TokenException;
14
use Spinen\ClickUp\Space;
15
use Spinen\ClickUp\Task;
16
use Spinen\ClickUp\Team;
17
use Spinen\ClickUp\User;
18
19
/**
20
 * Class Builder
21
 *
22
 * @package Spinen\ClickUp\Support
23
 *
24
 * @property Collection $spaces
25
 * @property Collection $tasks
26
 * @property Collection $teams
27
 * @property Collection $workspaces
28
 * @property User $user
29
 *
30
 * @method spaces
31
 * @method tasks
32
 * @method teams
33
 * @method workspaces
34
 */
35
class Builder
36
{
37
    use HasClient;
38
39
    /**
40
     * Class to cast the response
41
     *
42
     * @var string
43
     */
44
    protected $class;
45
46
    /**
47
     * Model instance
48
     *
49
     * @var Model
50
     */
51
    protected $model;
52
53
    /**
54
     * Parent model instance
55
     *
56
     * @var Model
57
     */
58
    protected $parentModel;
59
60
    /**
61
     * Map of potential parents with class name
62
     *
63
     * @var array
64
     */
65
    protected $rootModels = [
66
        'spaces'     => Space::class,
67
        'tasks'      => Task::class,
68
        'teams'      => Team::class,
69
        'workspaces' => Team::class,
70
    ];
71
72
    /**
73
     * Properties to filter the response
74
     *
75
     * @var array
76
     */
77
    protected $wheres = [];
78
79
    /**
80
     * Magic method to make builders for root models
81
     *
82
     * @param string $name
83
     * @param $arguments
84
     *
85
     * @return mixed
86
     * @throws BadMethodCallException
87
     * @throws ModelNotFoundException
88
     * @throws NoClientException
89
     */
90 5
    public function __call($name, $arguments)
91
    {
92 5
        if (!$this->parentModel && array_key_exists($name, $this->rootModels)) {
93 4
            return $this->newInstanceForModel($this->rootModels[$name]);
94
        }
95
96 1
        throw new BadMethodCallException(sprintf("Call to undefined method [%s]", $name));
97
    }
98
99
    /**
100
     * Magic method to make builders appears as properties
101
     *
102
     * @param string $name
103
     *
104
     * @return Collection|Model|null
105
     * @throws GuzzleException
106
     * @throws InvalidRelationshipException
107
     * @throws ModelNotFoundException
108
     * @throws NoClientException
109
     * @throws TokenException
110
     */
111 6
    public function __get($name)
112
    {
113 6
        if ($name === 'user') {
114 1
            return $this->newInstanceForModel(User::class)
115 1
                        ->get()
116 1
                        ->first();
117
        }
118
119
        // Only return builders as properties, when not a child
120 5
        if (!$this->parentModel && array_key_exists($name, $this->rootModels)) {
121 4
            return $this->{$name}()
122 4
                        ->get();
123
        }
124
125 1
        return null;
126
    }
127
128
    /**
129
     * Create instance of class and save via API
130
     *
131
     * @param array $attributes
132
     *
133
     * @return Model
134
     * @throws InvalidRelationshipException
135
     */
136 2
    public function create(array $attributes): Model
137
    {
138 2
        return tap(
139 2
            $this->make($attributes),
140
            function (Model $model) {
141 1
                $model->save();
142 1
            }
143
        );
144
    }
145
146
    /**
147
     * Get Collection of class instances that match query
148
     *
149
     * @param array|string $properties to pull
150
     *
151
     * @return Collection|Model
152
     * @throws GuzzleException
153
     * @throws InvalidRelationshipException
154
     * @throws NoClientException
155
     * @throws TokenException
156
     */
157 13
    public function get($properties = ['*'])
158
    {
159 13
        $properties = Arr::wrap($properties);
160
161
        // Call API to get the response
162 13
        $response = $this->getClient()
163 13
                         ->request($this->getPath());
164
165
        // Peel off the key if exist
166 13
        $response = $this->peelWrapperPropertyIfNeeded(Arr::wrap($response));
167
168
        // Convert to a collection of filtered objects casted to the class
169 13
        return (new Collection((array_values($response) === $response) ? $response : [$response]))->map(
170
            function ($items) use ($properties) {
171
                    // Cast to class with only the requested, properties
172 6
                    return $this->getModel()
173 6
                                ->newFromBuilder(
174 6
                                    $properties === ['*']
175 5
                                        ? (array)$items
176 1
                                        : collect($items)
177 1
                                            ->only($properties)
178 6
                                            ->toArray()
179
                                )
180 6
                                ->setClient($this->getClient());
181 13
            }
182
        );
183
    }
184
185
    /**
186
     * Get the model instance being queried.
187
     *
188
     * @return Model
189
     * @throws InvalidRelationshipException
190
     */
191 77
    public function getModel(): Model
192
    {
193 77
        if (!$this->class) {
194 1
            throw new InvalidRelationshipException();
195
        }
196
197 76
        if (!$this->model) {
198 76
            $this->model = (new $this->class([], $this->parentModel))->setClient($this->client);
199
        }
200
201 76
        return $this->model;
202
    }
203
204
205
    /**
206
     * Get the path for the resource with the where filters
207
     *
208
     * @param string|null $extra
209
     *
210
     * @return string|null
211
     * @throws InvalidRelationshipException
212
     */
213 14
    public function getPath($extra = null): ?string
214
    {
215 14
        return $this->getModel()
216 14
                    ->getPath($extra, $this->wheres);
217
    }
218
219
    /**
220
     * Find specific instance of class
221
     *
222
     * @param integer|string $id
223
     * @param array|string $properties to pull
224
     *
225
     * @return Model
226
     * @throws GuzzleException
227
     * @throws InvalidRelationshipException
228
     * @throws NoClientException
229
     * @throws TokenException
230
     */
231 1
    public function find($id, $properties = ['*']): Model
232
    {
233 1
        return $this->where($this->getModel()->getKeyName(), $id)
234 1
                    ->get($properties)
235 1
                    ->first();
236
    }
237
238
    /**
239
     * New up a class instance, but not saved
240
     *
241
     * @param array|null $attributes
242
     *
243
     * @return Model
244
     * @throws InvalidRelationshipException
245
     */
246 3
    public function make(array $attributes = []): Model
247
    {
248
        // TODO: Make sure that the model supports "creating"
249 3
        return $this->getModel()
250 2
                    ->newInstance($attributes);
251
    }
252
253
    /**
254
     * Create new Builder instance
255
     *
256
     * @return $this
257
     * @throws ModelNotFoundException
258
     * @throws NoClientException
259
     */
260 7
    public function newInstance(): self
261
    {
262 7
        return (new static())->setClass($this->class)
263 7
                             ->setClient($this->getClient())
264 7
                             ->setParent($this->parentModel);
265
    }
266
267
    /**
268
     * Create new Builder instance for a specific model
269
     *
270
     * @param string $model
271
     *
272
     * @return $this
273
     * @throws ModelNotFoundException
274
     * @throws NoClientException
275
     */
276 6
    public function newInstanceForModel($model): self
277
    {
278 6
        return $this->newInstance()
279 6
                    ->setClass($model);
280
    }
281
282
    /**
283
     * Peel of the wrapping property if it exist.
284
     *
285
     * @param array $properties
286
     *
287
     * @return array
288
     * @throws InvalidRelationshipException
289
     */
290 13
    protected function peelWrapperPropertyIfNeeded(array $properties): array
291
    {
292
        // Check for single response
293 13
        if (array_key_exists(
294 13
            $this->getModel()
295 13
                 ->getResponseKey(),
296
            $properties
297
        )) {
298 1
            return $properties[$this->getModel()
299 1
                                    ->getResponseKey()];
300
        }
301
302
        // Check for collection of responses
303 12
        if (array_key_exists(
304 12
            $this->getModel()
305 12
                 ->getResponseCollectionKey(),
306
            $properties
307
        )) {
308 1
            return $properties[$this->getModel()
309 1
                                    ->getResponseCollectionKey()];
310
        }
311
312 11
        return $properties;
313
    }
314
315
    /**
316
     * Set the class to cast the response
317
     *
318
     * @param string $class
319
     *
320
     * @return $this
321
     * @throws ModelNotFoundException
322
     */
323 77
    public function setClass($class): self
324
    {
325 77
        $this->class = $class;
326
327 77
        if (!is_null($class) && !class_exists($this->class)) {
0 ignored issues
show
introduced by
The condition is_null($class) is always false.
Loading history...
328 1
            throw new ModelNotFoundException(sprintf("The model [%s] not found.", $this->class));
329
        }
330
331 76
        return $this;
332
    }
333
334
    /**
335
     * Set the parent model
336
     *
337
     * @param Model $parent
338
     *
339
     * @return $this
340
     */
341 59
    public function setParent(?Model $parent): self
342
    {
343 59
        $this->parentModel = $parent;
344
345 59
        return $this;
346
    }
347
348
    /**
349
     * Add property to filter the collection
350
     *
351
     * @param string $property
352
     * @param mixed $value
353
     *
354
     * @return $this
355
     * @throws InvalidRelationshipException
356
     */
357 35
    public function where($property, $value = true): self
358
    {
359 35
        $value = is_a($value, LaravelCollection::class) ? $value->toArray() : $value;
360
361
        // If looking for a specific model, then set the id
362 35
        if ($property === $this->getModel()->getKeyName()) {
363 35
            $this->getModel()->{$property} = $value;
364
365 35
            return $this;
366
        }
367
368 1
        $this->wheres[$property] = $value;
369
370 1
        return $this;
371
    }
372
373
    /**
374
     * Shortcut to where property id
375
     *
376
     * @param integer|string $id
377
     *
378
     * @return $this
379
     * @throws InvalidRelationshipException
380
     */
381 34
    public function whereId($id): self
382
    {
383 34
        return $this->where($this->getModel()->getKeyName(), $id);
384
    }
385
386
    /**
387
     * Shortcut to where property is false
388
     *
389
     * @param string $property
390
     *
391
     * @return $this
392
     * @throws InvalidRelationshipException
393
     */
394 1
    public function whereNot($property): self
395
    {
396 1
        return $this->where($property, false);
397
    }
398
}
399