Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Completed
Pull Request — annotations (#407)
by Vincent
22:42 queued 11:44
created

AnnotationParser::getGraphqlInput()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 4
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Overblog\GraphQLBundle\Config\Parser;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use Doctrine\Common\Annotations\AnnotationRegistry;
9
use Overblog\GraphQLBundle\Annotation as GQL;
10
use Symfony\Component\Config\Resource\FileResource;
11
use Symfony\Component\DependencyInjection\ContainerBuilder;
12
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
13
14
class AnnotationParser implements PreParserInterface
15
{
16
    public const CLASSESMAP_CONTAINER_PARAMETER = 'overblog_graphql_types.classes_map';
17
18
    private static $annotationReader = null;
19
    private static $classesMap = [];
20
    private static $providers = [];
21
    private static $doctrineMapping = [];
22
23
    /**
24
     * {@inheritdoc}
25
     *
26
     * @throws \ReflectionException
27
     * @throws InvalidArgumentException
28
     */
29 9
    public static function preParse(\SplFileInfo $file, ContainerBuilder $container, array $configs = []): void
30
    {
31 9
        self::proccessFile($file, $container, $configs, true);
32 9
    }
33
34
    /**
35
     * {@inheritdoc}
36
     *
37
     * @throws \ReflectionException
38
     * @throws InvalidArgumentException
39
     */
40 9
    public static function parse(\SplFileInfo $file, ContainerBuilder $container, array $configs = []): array
41
    {
42 9
        return self::proccessFile($file, $container, $configs);
43
    }
44
45
    /**
46
     * Clear the Annotation parser.
47
     */
48 9
    public static function clear(): void
49
    {
50 9
        self::$classesMap = [];
51 9
        self::$providers = [];
52 9
        self::$annotationReader = null;
53 9
    }
54
55
    /**
56
     * Process a file.
57
     *
58
     * @param \SplFileInfo     $file
59
     * @param ContainerBuilder $container
60
     * @param bool             $resolveClassMap
61
     *
62
     * @throws \ReflectionException
63
     * @throws InvalidArgumentException
64
     */
65 9
    public static function proccessFile(\SplFileInfo $file, ContainerBuilder $container, array $configs, bool $resolveClassMap = false): array
66
    {
67 9
        self::$doctrineMapping = $configs['doctrine']['types_mapping'];
68
69 9
        $rootQueryType = $configs['definitions']['schema']['default']['query'] ?? false;
70 9
        $rootMutationType = $configs['definitions']['schema']['default']['mutation'] ?? false;
71
72 9
        $container->addResource(new FileResource($file->getRealPath()));
73
74 9
        if (!$resolveClassMap) {
75 9
            $container->setParameter(self::CLASSESMAP_CONTAINER_PARAMETER, self::$classesMap);
76
        }
77
78
        try {
79 9
            $fileContent = \file_get_contents($file->getRealPath());
80
81 9
            $shortClassName = \substr($file->getFilename(), 0, -4);
82 9
            if (\preg_match('#namespace (.+);#', $fileContent, $namespace)) {
83 9
                $className = $namespace[1].'\\'.$shortClassName;
84 9
                $namespace = $namespace[1];
85
            } else {
86
                $className = $shortClassName;
87
            }
88
89 9
            $reflexionEntity = new \ReflectionClass($className);
90
91 9
            $classAnnotations = self::getAnnotationReader()->getClassAnnotations($reflexionEntity);
92
93 9
            $properties = [];
94 9
            foreach ($reflexionEntity->getProperties() as $property) {
95 9
                $properties[$property->getName()] = ['property' => $property, 'annotations' => self::getAnnotationReader()->getPropertyAnnotations($property)];
96
            }
97
98 9
            $methods = [];
99 9
            foreach ($reflexionEntity->getMethods() as $method) {
100 9
                $methods[$method->getName()] = ['method' => $method, 'annotations' => self::getAnnotationReader()->getMethodAnnotations($method)];
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
101
            }
102
103 9
            $gqlTypes = [];
104
105 9
            foreach ($classAnnotations as $classAnnotation) {
106 9
                $gqlConfiguration = $gqlType = $gqlName = false;
107
108
                switch (true) {
109 9
                    case $classAnnotation instanceof GQL\Type:
110 9
                        $gqlType = 'type';
111 9
                        $gqlName = $classAnnotation->name ?: $shortClassName;
112 9
                        if (!$resolveClassMap) {
113 9
                            $isRootQuery = ($rootQueryType && $gqlName === $rootQueryType);
114 9
                            $isRootMutation = ($rootMutationType && $gqlName === $rootMutationType);
115 9
                            $currentValue = ($isRootQuery || $isRootMutation) ? \sprintf("service('%s')", self::formatNamespaceForExpression($className)) : 'value';
116
117 9
                            $gqlConfiguration = self::getGraphqlType($classAnnotation, $classAnnotations, $properties, $methods, $namespace, $currentValue);
0 ignored issues
show
Bug introduced by
It seems like $namespace can also be of type array<integer,string>; however, Overblog\GraphQLBundle\C...arser::getGraphqlType() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
118
119 9
                            if ($isRootQuery || $isRootMutation) {
120 9
                                foreach (self::$providers as $className => $providerMethods) {
121 9
                                    $gqlConfiguration['config']['fields'] += self::getGraphqlFieldsFromProvider($className, $providerMethods, $isRootMutation);
122
                                }
123
                            }
124
                        }
125 9
                        break;
126 9
                    case $classAnnotation instanceof GQL\Input:
127 9
                        $gqlType = 'input';
128 9
                        $gqlName = $classAnnotation->name ?: self::suffixName($shortClassName, 'Input');
129 9
                        if (!$resolveClassMap) {
130 9
                            $gqlConfiguration = self::getGraphqlInput($classAnnotation, $classAnnotations, $properties, $namespace);
0 ignored issues
show
Bug introduced by
It seems like $namespace can also be of type array<integer,string>; however, Overblog\GraphQLBundle\C...rser::getGraphqlInput() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
131
                        }
132 9
                        break;
133 9
                    case $classAnnotation instanceof GQL\Scalar:
134 9
                        $gqlType = 'scalar';
135 9
                        if (!$resolveClassMap) {
136 9
                            $gqlConfiguration = self::getGraphqlScalar($className, $classAnnotation, $classAnnotations);
137
                        }
138 9
                        break;
139 9
                    case $classAnnotation instanceof GQL\Enum:
140 9
                        $gqlType = 'enum';
141 9
                        if (!$resolveClassMap) {
142 9
                            $gqlConfiguration = self::getGraphqlEnum($classAnnotation, $classAnnotations, $reflexionEntity->getConstants());
143
                        }
144 9
                        break;
145 9
                    case $classAnnotation instanceof GQL\Union:
146 9
                        $gqlType = 'union';
147 9
                        if (!$resolveClassMap) {
148 9
                            $gqlConfiguration = self::getGraphqlUnion($classAnnotation, $classAnnotations);
149
                        }
150 9
                        break;
151 9
                    case $classAnnotation instanceof GQL\TypeInterface:
152 9
                        $gqlType = 'interface';
153 9
                        if (!$resolveClassMap) {
154 9
                            $gqlConfiguration = self::getGraphqlInterface($classAnnotation, $classAnnotations, $properties, $methods, $namespace);
0 ignored issues
show
Bug introduced by
It seems like $namespace can also be of type array<integer,string>; however, Overblog\GraphQLBundle\C...::getGraphqlInterface() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
155
                        }
156 9
                        break;
157 9
                    case $classAnnotation instanceof GQL\Provider:
158 9
                        if ($resolveClassMap) {
159 9
                            self::$providers[$className] = $methods;
160
                        }
161 9
                        break;
162
                    default:
163 9
                        continue;
164
                }
165
166 9
                if ($gqlType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $gqlType of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
167 9
                    if (!$gqlName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $gqlName of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
168 9
                        $gqlName = $classAnnotation->name ?: $shortClassName;
169
                    }
170
171 9
                    if ($resolveClassMap) {
172 9
                        if (isset(self::$classesMap[$gqlName])) {
173 1
                            throw new InvalidArgumentException(\sprintf('The GraphQL type "%s" has already been registered in class "%s"', $gqlName, self::$classesMap[$gqlName]['class']));
174
                        }
175 9
                        self::$classesMap[$gqlName] = ['type' => $gqlType, 'class' => $className];
176
                    } else {
177 9
                        $gqlTypes += [$gqlName => $gqlConfiguration];
178
                    }
179
                }
180
            }
181
182 9
            return $resolveClassMap ? self::$classesMap : $gqlTypes;
183 1
        } catch (\InvalidArgumentException $e) {
184 1
            throw new InvalidArgumentException(\sprintf('Failed to parse GraphQL annotations from file "%s".', $file), $e->getCode(), $e);
185
        }
186
    }
187
188
    /**
189
     * Retrieve annotation reader.
190
     *
191
     * @return AnnotationReader
192
     */
193 9
    private static function getAnnotationReader()
194
    {
195 9
        if (null === self::$annotationReader) {
196 9
            if (!\class_exists('\\Doctrine\\Common\\Annotations\\AnnotationReader') ||
197 9
                !\class_exists('\\Doctrine\\Common\\Annotations\\AnnotationRegistry')) {
198
                throw new \Exception('In order to use graphql annotation, you need to require doctrine annotations');
199
            }
200
201 9
            AnnotationRegistry::registerLoader('class_exists');
202 9
            self::$annotationReader = new AnnotationReader();
203
        }
204
205 9
        return self::$annotationReader;
206
    }
207
208
    /**
209
     * Create a GraphQL Type configuration from annotations on class, properties and methods.
210
     *
211
     * @param GQL\Type $typeAnnotation
212
     * @param array    $classAnnotations
213
     * @param array    $properties
214
     * @param array    $methods
215
     * @param string   $namespace
216
     * @param string   $currentValue
217
     *
218
     * @return array
219
     */
220 9
    private static function getGraphqlType(GQL\Type $typeAnnotation, array $classAnnotations, array $properties, array $methods, string $namespace, string $currentValue)
221
    {
222 9
        $typeConfiguration = [];
223
224 9
        $fields = self::getGraphqlFieldsFromAnnotations($namespace, $properties, false, false, $currentValue);
225 9
        $fields += self::getGraphqlFieldsFromAnnotations($namespace, $methods, false, true, $currentValue);
226
227 9
        $typeConfiguration['fields'] = $fields;
228 9
        $typeConfiguration += self::getDescriptionConfiguration($classAnnotations);
229
230 9
        if ($typeAnnotation->interfaces) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $typeAnnotation->interfaces of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
231 9
            $typeConfiguration['interfaces'] = $typeAnnotation->interfaces;
232
        }
233
234 9
        if ($typeAnnotation->resolveField) {
235
            $typeConfiguration['resolveField'] = self::formatExpression($typeAnnotation->resolveField);
236
        }
237
238 9
        $publicAnnotation = self::getFirstAnnotationMatching($classAnnotations, 'Overblog\GraphQLBundle\Annotation\IsPublic');
239 9
        if ($publicAnnotation) {
240 9
            $typeConfiguration['fieldsDefaultPublic'] = self::formatExpression($publicAnnotation->value);
241
        }
242
243 9
        $accessAnnotation = self::getFirstAnnotationMatching($classAnnotations, 'Overblog\GraphQLBundle\Annotation\Access');
244 9
        if ($accessAnnotation) {
245 9
            $typeConfiguration['fieldsDefaultAccess'] = self::formatExpression($accessAnnotation->value);
246
        }
247
248 9
        return ['type' => $typeAnnotation->isRelay ? 'relay-mutation-payload' : 'object', 'config' => $typeConfiguration];
249
    }
250
251
    /**
252
     * Create a GraphQL Interface type configuration from annotations on properties.
253
     *
254
     * @param string        $shortClassName
0 ignored issues
show
Bug introduced by
There is no parameter named $shortClassName. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
255
     * @param GQL\Interface $interfaceAnnotation
256
     * @param array         $properties
257
     * @param array         $methods
258
     * @param string        $namespace
259
     *
260
     * @return array
261
     */
262 9
    private static function getGraphqlInterface(GQL\TypeInterface $interfaceAnnotation, array $classAnnotations, array $properties, array $methods, string $namespace)
263
    {
264 9
        $interfaceConfiguration = [];
265
266 9
        $fields = self::getGraphqlFieldsFromAnnotations($namespace, $properties);
267 9
        $fields += self::getGraphqlFieldsFromAnnotations($namespace, $methods, false, true);
268
269 9
        $interfaceConfiguration['fields'] = $fields;
270 9
        $interfaceConfiguration += self::getDescriptionConfiguration($classAnnotations);
271
272 9
        $interfaceConfiguration['resolveType'] = $interfaceAnnotation->resolveType;
273
274 9
        return ['type' => 'interface', 'config' => $interfaceConfiguration];
275
    }
276
277
    /**
278
     * Create a GraphQL Input type configuration from annotations on properties.
279
     *
280
     * @param string    $shortClassName
0 ignored issues
show
Bug introduced by
There is no parameter named $shortClassName. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
281
     * @param GQL\Input $inputAnnotation
282
     * @param array     $properties
283
     * @param string    $namespace
284
     *
285
     * @return array
286
     */
287 9
    private static function getGraphqlInput(GQL\Input $inputAnnotation, array $classAnnotations, array $properties, string $namespace)
288
    {
289 9
        $inputConfiguration = [];
290 9
        $fields = self::getGraphqlFieldsFromAnnotations($namespace, $properties, true);
291
292 9
        $inputConfiguration['fields'] = $fields;
293 9
        $inputConfiguration += self::getDescriptionConfiguration($classAnnotations);
294
295 9
        return ['type' => $inputAnnotation->isRelay ? 'relay-mutation-input' : 'input-object', 'config' => $inputConfiguration];
296
    }
297
298
    /**
299
     * Get a Graphql scalar configuration from given scalar annotation.
300
     *
301
     * @param string     $shortClassName
0 ignored issues
show
Documentation introduced by
There is no parameter named $shortClassName. Did you maybe mean $className?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
302
     * @param string     $className
303
     * @param GQL\Scalar $scalarAnnotation
304
     * @param array      $classAnnotations
305
     *
306
     * @return array
307
     */
308 9
    private static function getGraphqlScalar(string $className, GQL\Scalar $scalarAnnotation, array $classAnnotations)
309
    {
310 9
        $scalarConfiguration = [];
311
312 9
        if ($scalarAnnotation->scalarType) {
313 9
            $scalarConfiguration['scalarType'] = self::formatExpression($scalarAnnotation->scalarType);
314
        } else {
315
            $scalarConfiguration = [
316 9
                'serialize' => [$className, 'serialize'],
317 9
                'parseValue' => [$className, 'parseValue'],
318 9
                'parseLiteral' => [$className, 'parseLiteral'],
319
            ];
320
        }
321
322 9
        $scalarConfiguration += self::getDescriptionConfiguration($classAnnotations);
323
324 9
        return ['type' => 'custom-scalar', 'config' => $scalarConfiguration];
325
    }
326
327
    /**
328
     * Get a Graphql Enum configuration from given enum annotation.
329
     *
330
     * @param string   $shortClassName
0 ignored issues
show
Bug introduced by
There is no parameter named $shortClassName. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
331
     * @param GQL\Enum $enumAnnotation
332
     * @param array    $classAnnotations
333
     * @param array    $constants
334
     *
335
     * @return array
336
     */
337 9
    private static function getGraphqlEnum(GQL\Enum $enumAnnotation, array $classAnnotations, array $constants)
338
    {
339 9
        $enumValues = $enumAnnotation->values ? $enumAnnotation->values : [];
340
341 9
        $values = [];
342
343 9
        foreach ($constants as $name => $value) {
344
            $valueAnnotation = \current(\array_filter($enumValues, function ($enumValueAnnotation) use ($name) {
345 9
                return $enumValueAnnotation->name == $name;
346 9
            }));
347 9
            $valueConfig = [];
348 9
            $valueConfig['value'] = $value;
349
350 9
            if ($valueAnnotation && $valueAnnotation->description) {
351 9
                $valueConfig['description'] = $valueAnnotation->description;
352
            }
353
354 9
            if ($valueAnnotation && $valueAnnotation->deprecationReason) {
355 9
                $valueConfig['deprecationReason'] = $valueAnnotation->deprecationReason;
356
            }
357
358 9
            $values[$name] = $valueConfig;
359
        }
360
361 9
        $enumConfiguration = ['values' => $values];
362 9
        $enumConfiguration += self::getDescriptionConfiguration($classAnnotations);
363
364 9
        return ['type' => 'enum', 'config' => $enumConfiguration];
365
    }
366
367
    /**
368
     * Get a Graphql Union configuration from given union annotation.
369
     *
370
     * @param string    $shortClassName
0 ignored issues
show
Bug introduced by
There is no parameter named $shortClassName. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
371
     * @param GQL\Union $unionAnnotation
372
     * @param array     $classAnnotations
373
     *
374
     * @return array
375
     */
376 9
    private static function getGraphqlUnion(GQL\Union $unionAnnotation, array $classAnnotations): array
377
    {
378 9
        $unionConfiguration = ['types' => $unionAnnotation->types];
379 9
        $unionConfiguration += self::getDescriptionConfiguration($classAnnotations);
380
381 9
        return ['type' => 'union', 'config' => $unionConfiguration];
382
    }
383
384
    /**
385
     * Create Graphql fields configuration based on annotation.
386
     *
387
     * @param string $namespace
388
     * @param array  $propertiesOrMethods
389
     * @param bool   $isInput
390
     * @param bool   $isMethod
391
     * @param string $currentValue
392
     *
393
     * @return array
394
     */
395 9
    private static function getGraphqlFieldsFromAnnotations(string $namespace, array $propertiesOrMethods, bool $isInput = false, bool $isMethod = false, string $currentValue = 'value'): array
396
    {
397 9
        $fields = [];
398 9
        foreach ($propertiesOrMethods as $target => $config) {
399 9
            $annotations = $config['annotations'];
400 9
            $method = $isMethod ? $config['method'] : false;
401 9
            $property = $isMethod ? false : $config['property'];
0 ignored issues
show
Unused Code introduced by
$property is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
402
403 9
            $fieldAnnotation = self::getFirstAnnotationMatching($annotations, 'Overblog\GraphQLBundle\Annotation\Field');
404 9
            $accessAnnotation = self::getFirstAnnotationMatching($annotations, 'Overblog\GraphQLBundle\Annotation\Access');
405 9
            $publicAnnotation = self::getFirstAnnotationMatching($annotations, 'Overblog\GraphQLBundle\Annotation\IsPublic');
406
407 9
            if (!$fieldAnnotation) {
408
                if ($accessAnnotation || $publicAnnotation) {
409
                    throw new InvalidArgumentException(\sprintf('The annotations "@Access" and/or "@Visible" defined on "%s" are only usable in addition of annotation "@Field"', $target));
410
                }
411
                continue;
412
            }
413
414 9
            if ($isMethod && !$method->isPublic()) {
415
                throw new InvalidArgumentException(\sprintf('The Annotation "@Field" can only be applied to public method. The method "%s" is not public.', $target));
416
            }
417
418
            // Ignore field with resolver when the type is an Input
419 9
            if ($fieldAnnotation->resolve && $isInput) {
420
                continue;
421
            }
422
423 9
            $propertyName = $target;
424 9
            $fieldType = $fieldAnnotation->type;
425 9
            $fieldConfiguration = [];
426 9
            if ($fieldType) {
427 9
                $resolvedType = self::resolveClassFromType($fieldType);
428 9
                if ($resolvedType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $resolvedType of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
429 9
                    if ($isInput && !\in_array($resolvedType['type'], ['input', 'scalar', 'enum'])) {
430
                        throw new InvalidArgumentException(\sprintf('The type "%s" on "%s" is a "%s" not valid on an Input @Field. Only Input, Scalar and Enum are allowed.', $fieldType, $target, $resolvedType['type']));
431
                    }
432
                }
433
434 9
                $fieldConfiguration['type'] = $fieldType;
435
            }
436
437 9
            $fieldConfiguration += self::getDescriptionConfiguration($annotations, true);
438
439 9
            if (!$isInput) {
440 9
                $args = [];
0 ignored issues
show
Unused Code introduced by
$args is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
441 9
                $args = self::getArgs($fieldAnnotation->args, $isMethod && !$fieldAnnotation->argsBuilder ? $method : null);
442
443 9
                if (!empty($args)) {
444 9
                    $fieldConfiguration['args'] = $args;
445
                }
446
447 9
                $propertyName = $fieldAnnotation->name ?: $propertyName;
448
449 9
                if ($fieldAnnotation->resolve) {
450 9
                    $fieldConfiguration['resolve'] = self::formatExpression($fieldAnnotation->resolve);
451
                } else {
452 9
                    if ($isMethod) {
453 9
                        $fieldConfiguration['resolve'] = self::formatExpression(\sprintf('@=call(%s.%s, %s)', $currentValue, $target, self::formatArgsForExpression($args)));
454 9
                    } elseif ($fieldAnnotation->name) {
455
                        $fieldConfiguration['resolve'] = self::formatExpression(\sprintf('@=call(%s.%s, [])', $currentValue, $target));
456
                    }
457
                }
458
459 9
                if ($fieldAnnotation->argsBuilder) {
460 9
                    if (\is_string($fieldAnnotation->argsBuilder)) {
461
                        $fieldConfiguration['argsBuilder'] = $fieldAnnotation->argsBuilder;
462 9
                    } elseif (\is_array($fieldAnnotation->argsBuilder)) {
463 9
                        list($builder, $builderConfig) = $fieldAnnotation->argsBuilder;
464 9
                        $fieldConfiguration['argsBuilder'] = ['builder' => $builder, 'config' => $builderConfig];
465
                    } else {
466
                        throw new InvalidArgumentException(\sprintf('The attribute "argsBuilder" on GraphQL annotation "@Field" defined on "%s" must be a string or an array where first index is the builder name and the second is the config.', $target));
467
                    }
468
                }
469
470 9
                if ($fieldAnnotation->fieldBuilder) {
471 9
                    if (\is_string($fieldAnnotation->fieldBuilder)) {
472
                        $fieldConfiguration['builder'] = $fieldAnnotation->fieldBuilder;
473 9
                    } elseif (\is_array($fieldAnnotation->fieldBuilder)) {
474 9
                        list($builder, $builderConfig) = $fieldAnnotation->fieldBuilder;
475 9
                        $fieldConfiguration['builder'] = $builder;
476 9
                        $fieldConfiguration['builderConfig'] = $builderConfig ?: [];
477
                    } else {
478 9
                        throw new InvalidArgumentException(\sprintf('The attribute "argsBuilder" on GraphQL annotation "@Field" defined on "%s" must be a string or an array where first index is the builder name and the second is the config.', $target));
479
                    }
480
                } else {
481 9
                    if (!$fieldType) {
482 9
                        if ($isMethod) {
483 9
                            if ($method->hasReturnType()) {
484
                                try {
485 9
                                    $fieldConfiguration['type'] = self::resolveGraphqlTypeFromReflectionType($method->getReturnType(), 'type').'!';
486
                                } catch (\Exception $e) {
487 9
                                    throw new InvalidArgumentException(\sprintf('The attribute "type" on GraphQL annotation "@Field" is missing on method "%s" and cannot be auto-guessed from type hint "%s"', $target, (string) $method->getReturnType()));
488
                                }
489
                            } else {
490 9
                                throw new InvalidArgumentException(\sprintf('The attribute "type" on GraphQL annotation "@Field" is missing on method "%s" and cannot be auto-guessed as there is not return type hint.', $target));
491
                            }
492
                        } else {
493
                            try {
494 9
                                $fieldConfiguration['type'] = self::guessType($namespace, $annotations);
495
                            } catch (\Exception $e) {
496
                                throw new InvalidArgumentException(\sprintf('The attribute "type" on "@Field" defined on "%s" is required and cannot be auto-guessed : %s.', $target, $e->getMessage()));
497
                            }
498
                        }
499
                    }
500
                }
501
502 9
                if ($accessAnnotation) {
503 9
                    $fieldConfiguration['access'] = self::formatExpression($accessAnnotation->value);
504
                }
505
506 9
                if ($publicAnnotation) {
507 9
                    $fieldConfiguration['public'] = self::formatExpression($publicAnnotation->value);
508
                }
509
            }
510
511 9
            $fields[$propertyName] = $fieldConfiguration;
512
        }
513
514 9
        return $fields;
515
    }
516
517
    /**
518
     * ArgTransformer
519
     *   Transform Arg type hint with enum as newObject(enumClassTypeHint, arg['a'])new EnumClass(arg['a'])
520
     *   Transform Arg type hint input as populate(InputClass, arg['a']).
521
     */
522
523
    /**
524
     * Return fields config from Provider methods.
525
     *
526
     * @param string $className
527
     * @param array  $methods
528
     * @param bool   $isMutation
529
     *
530
     * @return array
531
     */
532 9
    private static function getGraphqlFieldsFromProvider(string $className, array $methods, bool $isMutation = false)
533
    {
534 9
        $fields = [];
535 9
        foreach ($methods as $methodName => $config) {
536 9
            $annotations = $config['annotations'];
537 9
            $method = $config['method'];
538
539 9
            $annotation = self::getFirstAnnotationMatching($annotations, \sprintf('Overblog\\GraphQLBundle\\Annotation\\%s', $isMutation ? 'Mutation' : 'Query'));
540 9
            if (!$annotation) {
541 9
                continue;
542
            }
543
544 9
            $name = $annotation->name ?: $methodName;
545 9
            $type = $annotation->type;
546 9
            $args = self::getArgs($annotation->args, $method);
547 9
            if (!$type) {
548
                if ($method->hasReturnType()) {
549
                    try {
550
                        $type = self::resolveGraphqlTypeFromReflectionType($method->getReturnType(), 'type');
551
                    } catch (\Exception $e) {
552
                        throw new InvalidArgumentException(\sprintf('The attribute "type" on GraphQL annotation "@%s" is missing on method "%s" and cannot be auto-guessed from type hint "%s"', $isMutation ? 'Mutation' : 'Query', $method, (string) $method->getReturnType()));
553
                    }
554
                } else {
555
                    throw new InvalidArgumentException(\sprintf('The attribute "type" on GraphQL annotation "@%s" is missing on method "%s" and cannot be auto-guessed as there is not return type hint.', $isMutation ? 'Mutation' : 'Query', $method));
556
                }
557
            }
558
559 9
            $resolve = \sprintf("@=call(service('%s').%s, %s)", self::formatNamespaceForExpression($className), $methodName, self::formatArgsForExpression($args));
560
561 9
            $fields[$name] = [
562 9
                'type' => $type,
563 9
                'args' => $args,
564 9
                'resolve' => $resolve,
565
            ];
566
        }
567
568 9
        return $fields;
569
    }
570
571
    /**
572
     * Get the config for description & deprecation reason.
573
     *
574
     * @param array $annotations
575
     * @param bool  $withDeprecation
576
     *
577
     * @return array
578
     */
579 9
    private static function getDescriptionConfiguration(array $annotations, bool $withDeprecation = false)
580
    {
581 9
        $config = [];
582 9
        $descriptionAnnotation = self::getFirstAnnotationMatching($annotations, 'Overblog\GraphQLBundle\Annotation\Description');
583 9
        if ($descriptionAnnotation) {
584 9
            $config['description'] = $descriptionAnnotation->value;
585
        }
586
587 9
        if ($withDeprecation) {
588 9
            $deprecatedAnnotation = self::getFirstAnnotationMatching($annotations, 'Overblog\GraphQLBundle\Annotation\Deprecated');
589 9
            if ($deprecatedAnnotation) {
590
                $config['deprecationReason'] = $deprecatedAnnotation->value;
591
            }
592
        }
593
594 9
        return $config;
595
    }
596
597
    /**
598
     * Get args config from an array of @Arg annotation or by auto-guessing if a method is provided.
599
     *
600
     * @param array             $args
601
     * @param \ReflectionMethod $method
602
     *
603
     * @return array
604
     */
605 9
    private static function getArgs(array $args = null, \ReflectionMethod $method = null)
606
    {
607 9
        $config = [];
608 9
        if ($args && !empty($args)) {
609 9
            foreach ($args as $arg) {
610 9
                $config[$arg->name] = ['type' => $arg->type] + ($arg->description ? ['description' => $arg->description] : []);
611
            }
612 9
        } elseif ($method) {
613 9
            $config = self::guessArgs($method);
614
        }
615
616 9
        return $config;
617
    }
618
619 9
    private static function formatArgsForExpression(array $args)
620
    {
621 9
        $mapping = [];
622 9
        foreach ($args as $name => $config) {
623 9
            $mapping[] = \sprintf('%s: "%s"', $name, $config['type']);
624
        }
625
626 9
        return \sprintf('arguments({%s}, args)', \implode(', ', $mapping));
627
    }
628
629
    /**
630
     * Format an array of args to a list of arguments in an expression.
631
     *
632
     * @param array $args
633
     *
634
     * @return string
635
     */
636
    /*
637
    private static function formatArgsForExpression(array $args)
638
    {
639
        $resolvedArgs = [];
640
        foreach ($args as $name => $config) {
641
            $cleanedType = \str_replace(['[', ']', '!'], '', $config['type']);
642
            $definition = self::resolveClassFromType($cleanedType);
643
            $defaultFormat = \sprintf("args['%s']", $name);
644
            if (!$definition) {
645
                $resolvedArgs[] = $defaultFormat;
646
            } else {
647
                switch ($definition['type']) {
648
                    case 'input':
649
                    case 'enum':
650
                        $resolvedArgs[] = \sprintf("input('%s', args['%s'], '%s')", $config['type'], $name, $name);
651
                        break;
652
                    default:
653
                        $resolvedArgs[] = $defaultFormat;
654
                        break;
655
                }
656
            }
657
        }
658
659
        return sprintf("inputs(%s)", \implode(', ', $resolvedArgs));
660
    }
661
     */
662
663
    /**
664
     * Format a namespace to be used in an expression (double escape).
665
     *
666
     * @param string $namespace
667
     *
668
     * @return string
669
     */
670 9
    private static function formatNamespaceForExpression(string $namespace)
671
    {
672 9
        return \str_replace('\\', '\\\\', $namespace);
673
    }
674
675
    /**
676
     * Get the first annotation matching given class.
677
     *
678
     * @param array        $annotations
679
     * @param string|array $annotationClass
680
     *
681
     * @return mixed
682
     */
683 9
    private static function getFirstAnnotationMatching(array $annotations, $annotationClass)
684
    {
685 9
        if (\is_string($annotationClass)) {
686 9
            $annotationClass = [$annotationClass];
687
        }
688
689 9
        foreach ($annotations as $annotation) {
690 9
            foreach ($annotationClass as $class) {
691 9
                if ($annotation instanceof $class) {
692 9
                    return $annotation;
693
                }
694
            }
695
        }
696
697 9
        return false;
698
    }
699
700
    /**
701
     * Format an expression (ie. add "@=" if not set).
702
     *
703
     * @param string $expression
704
     *
705
     * @return string
706
     */
707 9
    private static function formatExpression(string $expression)
708
    {
709 9
        return '@=' === \substr($expression, 0, 2) ? $expression : \sprintf('@=%s', $expression);
710
    }
711
712
    /**
713
     * Suffix a name if it is not already.
714
     *
715
     * @param string $name
716
     * @param string $suffix
717
     *
718
     * @return string
719
     */
720 9
    private static function suffixName(string $name, string $suffix)
721
    {
722 9
        return \substr($name, -\strlen($suffix)) === $suffix ? $name : \sprintf('%s%s', $name, $suffix);
723
    }
724
725
    /**
726
     * Try to guess a field type base on is annotations.
727
     *
728
     * @param string $namespace
729
     * @param array  $annotations
730
     *
731
     * @return string|false
732
     */
733 9
    private static function guessType(string $namespace, array $annotations)
734
    {
735 9
        $columnAnnotation = self::getFirstAnnotationMatching($annotations, 'Doctrine\ORM\Mapping\Column');
736 9
        if ($columnAnnotation) {
737 9
            $type = self::resolveTypeFromDoctrineType($columnAnnotation->type);
738 9
            $nullable = $columnAnnotation->nullable;
739 9
            if ($type) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
740 9
                return $nullable ? $type : \sprintf('%s!', $type);
741
            } else {
742
                throw new \Exception(\sprintf('Unable to auto-guess GraphQL type from Doctrine type "%s"', $columnAnnotation->type));
743
            }
744
        }
745
746
        $associationAnnotations = [
747 9
            'Doctrine\ORM\Mapping\OneToMany' => true,
748
            'Doctrine\ORM\Mapping\OneToOne' => false,
749
            'Doctrine\ORM\Mapping\ManyToMany' => true,
750
            'Doctrine\ORM\Mapping\ManyToOne' => false,
751
        ];
752
753 9
        $associationAnnotation = self::getFirstAnnotationMatching($annotations, \array_keys($associationAnnotations));
754 9
        if ($associationAnnotation) {
755 9
            $target = self::fullyQualifiedClassName($associationAnnotation->targetEntity, $namespace);
756 9
            $type = self::resolveTypeFromClass($target, 'type');
757
758 9
            if ($type) {
759 9
                $isMultiple = $associationAnnotations[\get_class($associationAnnotation)];
760 9
                if ($isMultiple) {
761 9
                    return \sprintf('[%s]!', $type);
762
                } else {
763 9
                    $isNullable = false;
764 9
                    $joinColumn = self::getFirstAnnotationMatching($annotations, 'Doctrine\ORM\Mapping\JoinColumn');
765 9
                    if ($joinColumn) {
766 9
                        $isNullable = $joinColumn->nullable;
767
                    }
768
769 9
                    return \sprintf('%s%s', $type, $isNullable ? '' : '!');
770
                }
771
            } else {
772
                throw new \Exception(\sprintf('Unable to auto-guess GraphQL type from Doctrine target class "%s" (check if the target class is a GraphQL type itself (with a @GQL\Type annotation).', $target));
773
            }
774
        }
775
776
        throw new InvalidArgumentException(\sprintf('No Doctrine ORM annotation found.'));
777
    }
778
779
    /**
780
     * Resolve a FQN from classname and namespace.
781
     *
782
     * @param string $className
783
     * @param string $namespace
784
     *
785
     * @return string
786
     */
787 9
    public static function fullyQualifiedClassName(string $className, string $namespace)
788
    {
789 9
        if (false === \strpos($className, '\\') && $namespace) {
790 9
            return $namespace.'\\'.$className;
791
        }
792
793
        return $className;
794
    }
795
796
    /**
797
     * Resolve a GraphqlType from a doctrine type.
798
     *
799
     * @param string $doctrineType
800
     *
801
     * @return string|false
802
     */
803 9
    private static function resolveTypeFromDoctrineType(string $doctrineType)
804
    {
805 9
        if (isset(self::$doctrineMapping[$doctrineType])) {
806 9
            return self::$doctrineMapping[$doctrineType];
807
        }
808
809 9
        switch ($doctrineType) {
810 9
            case 'integer':
811 9
            case 'smallint':
812 9
            case 'bigint':
813 9
                return 'Int';
814
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
815 9
            case 'string':
816
            case 'text':
817 9
                return 'String';
818
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
819
            case 'bool':
820
            case 'boolean':
821
                return 'Boolean';
822
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
823
            case 'float':
824
            case 'decimal':
825
                return 'Float';
826
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
827
            default:
828
                return false;
829
        }
830
    }
831
832
    /**
833
     * Transform a method arguments from reflection to a list of GraphQL argument.
834
     *
835
     * @param \ReflectionMethod $method
836
     *
837
     * @return array
838
     */
839 9
    private static function guessArgs(\ReflectionMethod $method)
840
    {
841 9
        $arguments = [];
842 9
        foreach ($method->getParameters() as $index => $parameter) {
843 9
            if (!$parameter->hasType()) {
844
                throw new InvalidArgumentException(\sprintf('Argument n°%s "$%s" on method "%s" cannot be auto-guessed as there is not type hint".', $index + 1, $parameter->getName(), $method->getName()));
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
845
            }
846
847
            try {
848 9
                $gqlType = self::resolveGraphqlTypeFromReflectionType($parameter->getType(), 'input');
849
            } catch (\Exception $e) {
850
                throw new InvalidArgumentException(\sprintf('Argument n°%s "$%s" on method "%s" cannot be auto-guessed : %s".', $index + 1, $parameter->getName(), $method->getName(), $e->getMessage()));
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
851
            }
852
853 9
            $argumentConfig = [];
854 9
            if ($parameter->isDefaultValueAvailable()) {
855 9
                $argumentConfig['defaultValue'] = $parameter->getDefaultValue();
856
            } else {
857 9
                $gqlType .= '!';
858
            }
859
860 9
            $argumentConfig['type'] = $gqlType;
861
862 9
            $arguments[$parameter->getName()] = $argumentConfig;
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
863
        }
864
865 9
        return $arguments;
866
    }
867
868
    /**
869
     * Try to guess a GraphQL type from a Reflected Type.
870
     *
871
     * @param \ReflectionType $type
872
     *
873
     * @return string
874
     */
875 9
    private static function resolveGraphqlTypeFromReflectionType(\ReflectionType $type, string $filterGraphqlType = null)
876
    {
877 9
        $stype = (string) $type;
878 9
        if ($type->isBuiltin()) {
879 9
            $gqlType = self::resolveTypeFromPhpType($stype);
880 9
            if (!$gqlType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $gqlType of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
881 9
                throw new \Exception(\sprintf('No corresponding GraphQL type found for builtin type "%s"', $stype));
882
            }
883
        } else {
884
            $gqlType = self::resolveTypeFromClass($stype, $filterGraphqlType);
885
            if (!$gqlType) {
886
                throw new \Exception(\sprintf('No corresponding GraphQL %s found for class "%s"', $filterGraphqlType ?: 'object', $stype));
887
            }
888
        }
889
890 9
        return $gqlType;
891
    }
892
893
    /**
894
     * Resolve a GraphQL Type from a class name.
895
     *
896
     * @param string $className
897
     * @param string $wantedType
898
     *
899
     * @return string|false
900
     */
901 9
    private static function resolveTypeFromClass(string $className, string $wantedType = null)
902
    {
903 9
        foreach (self::$classesMap as $gqlType => $config) {
904 9
            if ($config['class'] === $className) {
905 9
                if (!$wantedType || ($wantedType && $wantedType === $config['type'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $wantedType of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
906 9
                    return $gqlType;
907
                }
908
            }
909
        }
910
911
        return false;
912
    }
913
914
    /**
915
     * Resolve a PHP class from a GraphQL type.
916
     *
917
     * @param string $type
918
     *
919
     * @return string|false
920
     */
921 9
    private static function resolveClassFromType(string $type)
922
    {
923 9
        return self::$classesMap[$type] ?? false;
924
    }
925
926
    /**
927
     * Convert a PHP Builtin type to a GraphQL type.
928
     *
929
     * @param string $phpType
930
     *
931
     * @return string
932
     */
933 9
    private static function resolveTypeFromPhpType(string $phpType)
934
    {
935 9
        switch ($phpType) {
936 9
            case 'boolean':
937 9
            case 'bool':
938
                return 'Boolean';
939 9
            case 'integer':
940 9
            case 'int':
941 9
                return 'Int';
942 9
            case 'float':
943 9
            case 'double':
944
                return 'Float';
945 9
            case 'string':
946 9
                return 'String';
947
            default:
948
                return false;
949
        }
950
    }
951
}
952