1
<?php
2

3
namespace Nuwave\Lighthouse\Execution\Arguments;
4

5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
7
use Illuminate\Database\Eloquent\Relations\HasMany;
8
use Illuminate\Database\Eloquent\Relations\HasOne;
9
use Illuminate\Database\Eloquent\Relations\MorphMany;
10
use Illuminate\Database\Eloquent\Relations\MorphOne;
11
use Illuminate\Database\Eloquent\Relations\MorphToMany;
12
use Nuwave\Lighthouse\Exceptions\DefinitionException;
13
use Nuwave\Lighthouse\Support\Contracts\ArgResolver;
14
use Nuwave\Lighthouse\Support\Utils;
15
use ReflectionClass;
16
use ReflectionNamedType;
17

18
class ArgPartitioner
19
{
20
    /**
21
     * Partition the arguments into nested and regular.
22
     *
23
     * @param  \Nuwave\Lighthouse\Execution\Arguments\ArgumentSet  $argumentSet
24
     * @return array<\Nuwave\Lighthouse\Execution\Arguments\ArgumentSet>
25
     */
26 1
    public static function nestedArgResolvers(ArgumentSet $argumentSet, $root): array
27
    {
28 1
        $model = $root instanceof Model
29 1
            ? new \ReflectionClass($root)
30 1
            : null;
31

32 1
        foreach ($argumentSet->arguments as $name => $argument) {
33 1
            static::attachNestedArgResolver($name, $argument, $model);
34
        }
35

36 1
        return static::partition(
37
            $argumentSet,
38
            static function (string $name, Argument $argument): bool {
39 1
                return isset($argument->resolver);
40
            }
41
        );
42
    }
43

44
    /**
45
     * Extract all the arguments that correspond to a relation of a certain type on the model.
46
     *
47
     * For example, if the args input looks like this:
48
     *
49
     * [
50
     *  'name' => 'Ralf',
51
     *  'comments' =>
52
     *    ['foo' => 'Bar'],
53
     * ]
54
     *
55
     * and the model has a method "comments" that returns a HasMany relationship,
56
     * the result will be:
57
     * [
58
     *   [
59
     *    'comments' =>
60
     *      ['foo' => 'Bar'],
61
     *   ],
62
     *   [
63
     *    'name' => 'Ralf',
64
     *   ]
65
     * ]
66
     *
67
     * @param  \Nuwave\Lighthouse\Execution\Arguments\ArgumentSet  $argumentSet
68
     * @return array<\Nuwave\Lighthouse\Execution\Arguments\ArgumentSet>
69
     */
70 1
    public static function relationMethods(
71
        ArgumentSet $argumentSet,
72
        Model $model,
73
        string $relationClass
74
    ): array {
75 1
        $modelReflection = new ReflectionClass($model);
76

77 1
        [$relations, $remaining] = static::partition(
78
            $argumentSet,
79
            static function (string $name) use ($modelReflection, $relationClass): bool {
80 1
                return static::methodReturnsRelation($modelReflection, $name, $relationClass);
81
            }
82
        );
83

84 1
        $nonNullRelations = new ArgumentSet();
85 1
        $nonNullRelations->arguments = array_filter(
86 1
            $relations->arguments,
87
            static function (Argument $argument): bool {
88 1
                return null !== $argument->value;
89
            }
90
        );
91

92 1
        return [$nonNullRelations, $remaining];
93
    }
94

95
    /**
96
     * Attach a nested argument resolver to an argument.
97
     *
98
     * @param  \Nuwave\Lighthouse\Execution\Arguments\Argument  $argument
99
     */
100 1
    protected static function attachNestedArgResolver(string $name, Argument &$argument, ?ReflectionClass $model): void
101
    {
102 1
        $resolverDirective = $argument->directives->first(
103 1
            Utils::instanceofMatcher(ArgResolver::class)
104
        );
105

106 1
        if ($resolverDirective) {
107 1
            $argument->resolver = $resolverDirective;
108

109 1
            return;
110
        }
111

112 1
        if (isset($model)) {
113
            $isRelation = static function (string $relationClass) use ($model, $name): bool {
114 1
                return static::methodReturnsRelation($model, $name, $relationClass);
115 1
            };
116

117
            if (
118 1
                $isRelation(HasOne::class)
119 1
                || $isRelation(MorphOne::class)
120
            ) {
121 1
                $argument->resolver = new ResolveNested(new NestedOneToOne($name));
122

123 1
                return;
124
            }
125

126
            if (
127 1
                $isRelation(HasMany::class)
128 1
                || $isRelation(MorphMany::class)
129
            ) {
130 1
                $argument->resolver = new ResolveNested(new NestedOneToMany($name));
131

132 1
                return;
133
            }
134

135
            if (
136 1
                $isRelation(BelongsToMany::class)
137 1
                || $isRelation(MorphToMany::class)
138
            ) {
139 1
                $argument->resolver = new ResolveNested(new NestedManyToMany($name));
140

141 1
                return;
142
            }
143
        }
144
    }
145

146
    /**
147
     * Partition arguments based on a predicate.
148
     *
149
     * The predicate will be called for each argument within the ArgumentSet
150
     * with the following parameters:
151
     * 1. The name of the argument
152
     * 2. The argument itself
153
     *
154
     * Returns an array of two new ArgumentSet instances:
155
     * - the first one contains all arguments for which the predicate matched
156
     * - the second one contains all arguments for which the predicate did not match
157
     *
158
     * @param  \Nuwave\Lighthouse\Execution\Arguments\ArgumentSet  $argumentSet
159
     * @return array<\Nuwave\Lighthouse\Execution\Arguments\ArgumentSet>
160
     */
161 1
    public static function partition(ArgumentSet $argumentSet, \Closure $predicate): array
162
    {
163 1
        $matched = new ArgumentSet();
164 1
        $notMatched = new ArgumentSet();
165

166 1
        foreach ($argumentSet->arguments as $name => $argument) {
167 1
            if ($predicate($name, $argument)) {
168 1
                $matched->arguments[$name] = $argument;
169
            } else {
170 1
                $notMatched->arguments[$name] = $argument;
171
            }
172
        }
173

174
        return [
175 1
            $matched,
176 1
            $notMatched,
177
        ];
178
    }
179

180
    /**
181
     * Does a method on the model return a relation of the given class?
182
     */
183 1
    public static function methodReturnsRelation(
184
        ReflectionClass $modelReflection,
185
        string $name,
186
        string $relationClass
187
    ): bool {
188 1
        if (! $modelReflection->hasMethod($name)) {
189 1
            return false;
190
        }
191

192 1
        $relationMethodCandidate = $modelReflection->getMethod($name);
193

194 1
        $returnType = $relationMethodCandidate->getReturnType();
195 1
        if ($returnType === null) {
196 1
            return false;
197
        }
198

199 1
        if (! $returnType instanceof ReflectionNamedType) {
200 0
            return false;
201
        }
202

203 1
        if (! class_exists($returnType->getName())) {
204 1
            throw new DefinitionException('Class '.$returnType->getName().' does not exist, did you forget to import the Eloquent relation class?');
205
        }
206

207 1
        return is_a($returnType->getName(), $relationClass, true);
208
    }
209
}

Read our documentation on viewing source code .

Loading