Field::authorizedToRead()   B
last analyzed

Complexity

Conditions 7
Paths 4

Size

Total Lines 20
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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