Passed
Pull Request — master (#124)
by Erik
05:08
created

Field::getWith()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Bakery\Fields;
4
5
use Illuminate\Support\Arr;
6
use Bakery\Support\Arguments;
7
use Bakery\Support\TypeRegistry;
8
use Illuminate\Support\Facades\Gate;
9
use Bakery\Types\Definitions\RootType;
10
use function Bakery\is_callable_tuple;
11
use GraphQL\Type\Definition\ResolveInfo;
12
use Illuminate\Auth\Access\AuthorizationException;
13
14
class Field
15
{
16
    /**
17
     * @var \Bakery\Support\TypeRegistry
18
     */
19
    protected $registry;
20
21
    /**
22
     * @var \GraphQL\Type\Definition\Type
23
     */
24
    protected $type;
25
26
    /**
27
     * @var array
28
     */
29
    protected $args;
30
31
    /**
32
     * @var string
33
     */
34
    protected $name;
35
36
    /**
37
     * @var string
38
     */
39
    protected $description;
40
41
    /**
42
     * @var string
43
     */
44
    protected $accessor;
45
46
    /**
47
     * @var bool
48
     */
49
    protected $list = false;
50
51
    /**
52
     * @var bool
53
     */
54
    protected $nullable = false;
55
56
    /**
57
     * @var bool
58
     */
59
    protected $nullableItems = false;
60
61
    /**
62
     * @var bool
63
     */
64
    protected $fillable = true;
65
66
    /**
67
     * @var array
68
     */
69
    protected $with;
70
71
    /**
72
     * @var bool
73
     */
74
    protected $searchable = false;
75
76
    /**
77
     * @var bool
78
     */
79
    protected $unique = false;
80
81
    /**
82
     * @var mixed
83
     */
84
    protected $storePolicy;
85
86
    /**
87
     * @var mixed
88
     */
89
    protected $viewPolicy;
90
91
    /**
92
     * @var callable
93
     */
94
    protected $resolver;
95
96
    /**
97
     * Construct a new field.
98
     *
99
     * @param \Bakery\Support\TypeRegistry $registry
100
     * @param \Bakery\Types\Definitions\RootType|null $type
101
     */
102
    public function __construct(TypeRegistry $registry, RootType $type = null)
103
    {
104
        $this->registry = $registry;
105
106
        if ($type) {
107
            $this->type = $type;
0 ignored issues
show
Documentation Bug introduced by
It seems like $type of type Bakery\Types\Definitions\RootType is incompatible with the declared type GraphQL\Type\Definition\Type of property $type.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
108
        }
109
    }
110
111
    /**
112
     * @return \Bakery\Support\TypeRegistry
113
     */
114
    public function getRegistry(): TypeRegistry
115
    {
116
        return $this->registry;
117
    }
118
119
    /**
120
     * @param \Bakery\Support\TypeRegistry $registry
121
     * @return $this
122
     */
123
    public function setRegistry(TypeRegistry $registry): self
124
    {
125
        $this->registry = $registry;
126
127
        return $this;
128
    }
129
130
    /**
131
     * Define the type of the field.
132
     * This method can be overridden.
133
     *
134
     * @return \Bakery\Types\Definitions\RootType
135
     */
136
    protected function type(): RootType
137
    {
138
        return $this->type;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->type returns the type GraphQL\Type\Definition\Type which is incompatible with the type-hinted return Bakery\Types\Definitions\RootType.
Loading history...
139
    }
140
141
    /**
142
     * Return the type of the field.
143
     *
144
     * @return \Bakery\Types\Definitions\RootType
145
     */
146
    public function getType(): RootType
147
    {
148
        $type = $this->type();
149
150
        $type->nullable($this->isNullable());
151
        $type->list($this->isList());
152
        $type->nullableItems($this->hasNullableItems());
153
154
        return $type->setRegistry($this->getRegistry());
155
    }
156
157
    /**
158
     * Return if the field represents a relationship.
159
     */
160
    public function isRelationship(): bool
161
    {
162
        return $this instanceof EloquentField || $this instanceof PolymorphicField;
163
    }
164
165
    /**
166
     * Define the name of the field.
167
     *
168
     * This method can be overridden when extending the Field.
169
     *
170
     * @return string
171
     */
172
    protected function name(): string
173
    {
174
        return $this->name;
175
    }
176
177
    /**
178
     * Get the name of the type.
179
     *
180
     * @return string
181
     */
182
    public function getName(): string
183
    {
184
        return $this->name();
185
    }
186
187
    /**
188
     * Set the name of the type.
189
     *
190
     * @param string $name
191
     * @return $this
192
     */
193
    public function setName(string $name): self
194
    {
195
        $this->name = $name;
196
197
        return $this;
198
    }
199
200
    /**
201
     * Get the description of the field.
202
     *
203
     * @return string
204
     */
205
    public function getDescription(): ?string
206
    {
207
        return $this->description;
208
    }
209
210
    /**
211
     * Set the description of the field.
212
     *
213
     * @param string $description
214
     * @return $this
215
     */
216
    public function description(string $description): self
217
    {
218
        $this->description = $description;
219
220
        return $this;
221
    }
222
223
    /**
224
     * Return the name of the database column associated with this field.
225
     *
226
     * @return string|null
227
     */
228
    public function getAccessor(): ?string
229
    {
230
        return $this->accessor;
231
    }
232
233
    /**
234
     * Define the name of the database column associated with this field.
235
     *
236
     * @param string $accessor
237
     * @return Field
238
     */
239
    public function accessor(string $accessor): self
240
    {
241
        $this->accessor = $accessor;
242
243
        return $this;
244
    }
245
246
    /**
247
     * Set the args of the field.
248
     *
249
     * @param array $args
250
     * @return $this
251
     */
252
    public function args(array $args): self
253
    {
254
        $this->args = $args;
255
256
        return $this;
257
    }
258
259
    /**
260
     * Get the args of the field.
261
     *
262
     * @return array
263
     */
264
    public function getArgs(): array
265
    {
266
        return $this->args ?? [];
267
    }
268
269
    /**
270
     * @param bool $list
271
     * @return $this
272
     */
273
    public function list(bool $list = true): self
274
    {
275
        $this->list = $list;
276
277
        return $this;
278
    }
279
280
    /**
281
     * @return bool
282
     */
283
    public function isList(): bool
284
    {
285
        return $this->list;
286
    }
287
288
    /**
289
     * Set if the field is nullable.
290
     *
291
     * @param bool $nullable
292
     * @return $this
293
     */
294
    public function nullable(bool $nullable = true): self
295
    {
296
        $this->nullable = $nullable;
297
298
        return $this;
299
    }
300
301
    /**
302
     * Return if the field is nullable.
303
     *
304
     * @return bool
305
     */
306
    public function isNullable(): bool
307
    {
308
        return $this->nullable;
309
    }
310
311
    /**
312
     * Set if the field has nullable items.
313
     *
314
     * @param bool $nullable
315
     * @return \Bakery\Fields\Field
316
     */
317
    public function nullableItems(bool $nullable = true): self
318
    {
319
        $this->nullableItems = $nullable;
320
321
        return $this;
322
    }
323
324
    /**
325
     * Return if the field has nullable items.
326
     *
327
     * @return bool
328
     */
329
    public function hasNullableItems(): bool
330
    {
331
        return $this->nullableItems;
332
    }
333
334
    /**
335
     * Set if the field is fillable.
336
     *
337
     * @param bool $fillable
338
     * @return $this
339
     */
340
    public function fillable(bool $fillable = true): self
341
    {
342
        $this->fillable = $fillable;
343
344
        return $this;
345
    }
346
347
    /**
348
     * Set the field to read only.
349
     *
350
     * @return $this
351
     */
352
    public function readOnly(): self
353
    {
354
        $this->fillable(false);
355
356
        return $this;
357
    }
358
359
    /**
360
     * Return if the field is fillable.
361
     *
362
     * @return bool
363
     */
364
    public function isFillable(): bool
365
    {
366
        return $this->fillable;
367
    }
368
369
    /**
370
     * Set the relations that should be eager loaded.
371
     *
372
     * @param  string[]|string  $relations
373
     * @return Field
374
     */
375
    public function with($relations): self
376
    {
377
        $this->with = Arr::wrap($relations);
378
379
        return $this;
380
    }
381
382
    /**
383
     * Get the relations that should be eager loaded.
384
     */
385
    public function getWith(): ?array
386
    {
387
        return $this->with;
388
    }
389
390
    /**
391
     * Set if the field is searchable.
392
     *
393
     * @param bool $searchable
394
     * @return \Bakery\Fields\Field
395
     */
396
    public function searchable(bool $searchable = true): self
397
    {
398
        $this->searchable = $searchable;
399
400
        return $this;
401
    }
402
403
    /**
404
     * Return if the field is searchable.
405
     *
406
     * @return bool
407
     */
408
    public function isSearchable(): bool
409
    {
410
        return $this->searchable;
411
    }
412
413
    /**
414
     * Set if the field is unique.
415
     *
416
     * @param bool $unique
417
     * @return \Bakery\Fields\Field
418
     */
419
    public function unique(bool $unique = true): self
420
    {
421
        $this->unique = $unique;
422
423
        return $this;
424
    }
425
426
    /**
427
     * Return if the field is unique.
428
     *
429
     * @return bool
430
     */
431
    public function isUnique(): bool
432
    {
433
        return $this->unique;
434
    }
435
436
    /**
437
     * Set the story policy.
438
     *
439
     * @param $policy
440
     * @return \Bakery\Fields\Field
441
     */
442
    public function storePolicy($policy): self
443
    {
444
        $this->storePolicy = $policy;
445
446
        return $this;
447
    }
448
449
    /**
450
     * Set the store policy with a callable.
451
     *
452
     * @param callable $closure
453
     * @return $this
454
     */
455
    public function canStore(callable $closure): self
456
    {
457
        return $this->storePolicy($closure);
458
    }
459
460
    /**
461
     * Set the store policy with a reference to a policy method.
462
     *
463
     * @param string $policy
464
     * @return $this
465
     */
466
    public function canStoreWhen(string $policy): self
467
    {
468
        return $this->storePolicy($policy);
469
    }
470
471
    /**
472
     * Set the view policy.
473
     *
474
     * @param $policy
475
     * @return $this
476
     */
477
    public function viewPolicy($policy): self
478
    {
479
        $this->viewPolicy = $policy;
480
481
        return $this;
482
    }
483
484
    /**
485
     * Set the store policy with a callable.
486
     *
487
     * @param callable $closure
488
     * @return $this
489
     */
490
    public function canSee(callable $closure = null): self
491
    {
492
        return $this->viewPolicy($closure);
493
    }
494
495
    /**
496
     * Set the store policy with a reference to a policy method.
497
     *
498
     * @param string $policy
499
     * @return $this
500
     */
501
    public function canSeeWhen(string $policy): self
502
    {
503
        return $this->viewPolicy($policy);
504
    }
505
506
    /**
507
     * @return mixed
508
     */
509
    public function getViewPolicy()
510
    {
511
        return $this->viewPolicy;
512
    }
513
514
    /**
515
     * Set the resolver.
516
     *
517
     * @param $resolver
518
     * @return $this
519
     */
520
    public function resolve(callable $resolver): self
521
    {
522
        $this->resolver = $resolver;
523
524
        return $this;
525
    }
526
527
    /**
528
     * Resolve the field.
529
     *
530
     * @param $root
531
     * @param array $args
532
     * @param $context
533
     * @param \GraphQL\Type\Definition\ResolveInfo $info
534
     * @return mixed|null
535
     * @throws AuthorizationException
536
     */
537
    public function resolveField($root, array $args, $context, ResolveInfo $info)
538
    {
539
        $accessor = $this->getAccessor() ?: $info->fieldName;
540
        $args = new Arguments($args);
541
542
        if (isset($this->viewPolicy)) {
543
            if (! $this->authorizeToRead($root, $info->fieldName)) {
544
                return null;
545
            }
546
        }
547
548
        if (isset($this->resolver)) {
549
            return call_user_func_array($this->resolver, [$root, $accessor, $args, $context, $info]);
550
        }
551
552
        return self::defaultResolver($root, $accessor, $args, $context, $info);
553
    }
554
555
    /**
556
     * Determine if the current user can read the field of the model or throw an exception if not nullable.
557
     *
558
     * @param mixed $source
559
     * @param string $fieldName
560
     * @return bool
561
     * @throws \Illuminate\Auth\Access\AuthorizationException
562
     */
563
    public function authorizeToRead($source, $fieldName)
564
    {
565
        $result = $this->authorizedToRead($source);
566
        if ($result || $this->nullable) {
567
            return $result;
568
        }
569
570
        throw new AuthorizationException('Cannot read property "'.$fieldName.'" of '.get_class($source));
571
    }
572
573
    /**
574
     * Determine if the current user can read the field of the model.
575
     *
576
     * @param mixed $source
577
     * @return bool
578
     */
579
    public function authorizedToRead($source): bool
580
    {
581
        $policy = $this->viewPolicy;
582
583
        // Check if there is a policy.
584
        if (! $policy) {
585
            return true;
586
        }
587
588
        // Check if the policy method is callable
589
        if (($policy instanceof \Closure || is_callable_tuple($policy)) && $policy($source)) {
590
            return true;
591
        }
592
593
        // Check if there is a policy with this name
594
        if (is_string($policy) && Gate::check($policy, $source)) {
595
            return true;
596
        }
597
598
        return false;
599
    }
600
601
    /**
602
     * Determine if the current user can store the value on the model or throw an exception.
603
     *
604
     * @param mixed $source
605
     * @param mixed $value
606
     * @param string $fieldName
607
     * @return bool
608
     * @throws \Illuminate\Auth\Access\AuthorizationException
609
     */
610
    public function authorizeToStore($source, $value, $fieldName): bool
611
    {
612
        if ($this->authorizedToRead($source)) {
613
            $result = $this->authorizedToStore($source, $value);
614
615
            if ($result) {
616
                return $result;
617
            }
618
        }
619
620
        throw new AuthorizationException('Cannot set property "'.$fieldName.'" of '.get_class($source));
621
    }
622
623
    /**
624
     * Determine if the current user can store the value on the model.
625
     *
626
     * @param mixed $source
627
     * @param mixed $value
628
     * @return bool
629
     */
630
    public function authorizedToStore($source, $value): bool
631
    {
632
        $policy = $this->storePolicy;
633
634
        // Check if there is a policy.
635
        if (! $policy) {
636
            return true;
637
        }
638
639
        // Check if the policy method is a closure.
640
        if (($policy instanceof \Closure || is_callable_tuple($policy)) && $policy($source, $value)) {
641
            return true;
642
        }
643
644
        // Check if there is a policy with this name
645
        if (is_string($policy) && Gate::check($policy, [$source, $value])) {
646
            return true;
647
        }
648
649
        return false;
650
    }
651
652
    /**
653
     * Convert the field to an array.
654
     *
655
     * @return array
656
     */
657
    public function toArray()
658
    {
659
        return [
660
            'type' => $this->getType()->toType(),
661
            'args' => collect($this->getArgs())->map(function (RootType $type) {
662
                return $type->toType();
663
            })->toArray(),
664
            'resolve' => [$this, 'resolveField'],
665
            'description' => $this->getDescription(),
666
        ];
667
    }
668
669
    /**
670
     * The default resolver for resolving the value of the type.
671
     * This gets called when there is no custom resolver defined.
672
     *
673
     * @param string $accessor
674
     * @param Arguments $args
675
     * @param $root
676
     * @param $context
677
     * @param \GraphQL\Type\Definition\ResolveInfo $info
678
     * @return mixed|null
679
     */
680
    public static function defaultResolver($root, string $accessor, Arguments $args, $context, ResolveInfo $info)
681
    {
682
        $property = null;
683
684
        if (Arr::accessible($root)) {
685
            $property = $root[$accessor];
686
        } elseif (is_object($root)) {
687
            $property = $root->{$accessor};
688
        }
689
690
        return $property instanceof \Closure ? $property($args, $root, $context, $info) : $property;
691
    }
692
693
    /**
694
     * Invoked when the object is being serialized.
695
     * Returns the field that should be serialized.
696
     *
697
     * @return array
698
     */
699
    public function __sleep()
700
    {
701
        return ['registry'];
702
    }
703
}
704