Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
99.07% covered (success)
99.07%
106 / 107
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
CreatesFromMeta
99.07% covered (success)
99.07%
106 / 107
75.00% covered (warning)
75.00%
3 / 4
40
0.00% covered (danger)
0.00%
0 / 1
 createValueOptions
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 createFromMetadata
98.63% covered (success)
98.63%
72 / 73
0.00% covered (danger)
0.00%
0 / 1
21
 createFromScalar
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
10
 createFromField
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2namespace Apie\Graphql\Concerns;
3
4use Apie\Core\Context\ApieContext;
5use Apie\Core\Enums\ScalarType;
6use Apie\Core\Metadata\Fields\FieldInterface;
7use Apie\Core\Metadata\ItemHashmapMetadata;
8use Apie\Core\Metadata\ItemListMetadata;
9use Apie\Core\Metadata\MetadataFactory;
10use Apie\Core\Metadata\MetadataInterface;
11use Apie\Core\Metadata\NullableMetadataInterface;
12use Apie\Core\Metadata\UnionTypeMetadata;
13use Apie\Core\Utils\ConverterUtils;
14use Apie\Core\ValueObjects\Interfaces\ValueObjectInterface;
15use Apie\Core\ValueObjects\Utils;
16use Apie\Graphql\Types;
17use Apie\Graphql\Types\EnumConversionType;
18use Apie\Graphql\Types\FromMetadataInputType;
19use Apie\Graphql\Types\MapOfType;
20use Apie\TypeConverter\ReflectionTypeFactory;
21use GraphQL\Type\Definition\InputType;
22use GraphQL\Type\Definition\StringType;
23use GraphQL\Type\Definition\Type;
24use GraphQL\Type\Definition\UnionType;
25use GraphQL\Upload\UploadType;
26use Psr\Http\Message\UploadedFileInterface;
27use ReflectionClass;
28
29trait CreatesFromMeta
30{
31    private static function createValueOptions(MetadataInterface $metadata): ?array
32    {
33        $options = $metadata->getValueOptions(new ApieContext());
34        if ($options === null) {
35            return null;
36        }
37        $prefix = $options->hasIntegerKeys() ? 'p' : '';
38        $enumValues = [];
39        foreach ($options as $option) {
40            $enumValues[$prefix . $option->name] = ['_meta' => ['x-prefix' => $prefix], 'name' => $prefix . $option->name, 'value' => $option->value, 'description' => $option->description ?? $option->name];
41        }
42        return $enumValues;
43    }
44
45    public static function createFromMetadata(MetadataInterface $metadata, bool $nullable = false): Type
46    {
47        if ($metadata instanceof NullableMetadataInterface) {
48            $nullable = $nullable || $metadata->allowsNull();
49        }
50        if ($metadata instanceof UnionTypeMetadata) {
51            $metaWithoutNull = $metadata->toSkipNull();
52            
53            if ($metaWithoutNull instanceof UnionTypeMetadata && ScalarType::MIXED === $metaWithoutNull->toScalarType()) {
54                $name = 'UnionOf' . md5(static::class . "::" . $metadata->getDisplayName());
55                $result = Types::createSingleton(
56                    $name,
57                    fn () => new UnionType([
58                        'name' => $name,
59                        'types' => array_map(
60                            fn (MetadataInterface $meta) => self::createFromMetadata($meta),
61                            $metaWithoutNull->getTypes()
62                        ),
63                    ])
64                );
65                if ($nullable) {
66                    return $result;
67                }
68                return Type::nonNull($result);
69            }
70            $metadata = $metaWithoutNull;
71        }
72
73        $class = $metadata->toClass();
74        $options = self::createValueOptions($metadata);
75        if (!empty($options) && $class) {
76            $resourceName = Utils::getDisplayNameForValueObject($class);
77            $result = Types::createSingleton($resourceName, function () use ($class, $resourceName, $options) {
78                return new EnumConversionType([
79                    'name' => $resourceName,
80                    'values' => $options,
81                    'typehint' => $class->name,
82                ]);
83            });
84            if ($nullable) {
85                return $result;
86            }
87            return Type::nonNull($result);
88        }
89
90
91        if ($metadata instanceof ItemListMetadata) {
92            $result = Type::listOf(self::createFromMetadata($metadata->getArrayItemType()));
93            if ($nullable) {
94                return $result;
95            }
96            return Type::nonNull($result);
97        }
98        if ($metadata instanceof ItemHashmapMetadata) {
99            $result = new MapOfType(self::createFromMetadata($metadata->getArrayItemType()));
100            $result = Types::createSingleton($result->name, fn () => $result);
101            if ($nullable) {
102                return $result;
103            }
104            return Type::nonNull($result);
105        }
106        $class = $metadata->toClass();
107        if ($class && in_array(UploadedFileInterface::class, [$class->name, ...$class->getInterfaceNames()])) {
108            if (in_array(InputType::class, (new ReflectionClass(static::class))->getInterfaceNames()) && !in_array(ValueObjectInterface::class, $class->getInterfaceNames())) {
109                $name = Utils::getDisplayNameForValueObject($class) . '_create';
110                $result = Types::createSingleton($name, function () use ($name) {
111                    return new UploadType(['name' => $name]);
112                });
113            } else {
114                $name = Utils::getDisplayNameForValueObject($class);
115                $result = Types::createSingleton($name, function () use ($name) {
116                      
117                    return new StringType([
118                        'name' => $name,
119                        'description' => 'URL to download the file',
120                    ]);
121                });
122            }
123
124            if ($nullable) {
125                return $result;
126            }
127            return Type::nonNull($result);
128        }
129        
130        $scalarType = $metadata->toScalarType($nullable);
131        if ($scalarType === ScalarType::STDCLASS) {
132            $result = new self($metadata);
133            $result = Types::createSingleton(
134                $result->name,
135                fn () => $result
136            );
137            if ($nullable) {
138                return $result;
139            }
140            return Type::nonNull($result);
141        }
142        return self::createFromScalar($scalarType, $nullable);
143    }
144
145    public static function createFromScalar(ScalarType $scalarType, bool $nullable = false): Type
146    {
147        $result = match($scalarType) {
148            ScalarType::STRING => Type::string(),
149            ScalarType::INTEGER => Type::int(),
150            ScalarType::FLOAT => Type::float(),
151            ScalarType::BOOLEAN => Type::boolean(),
152            ScalarType::NULLVALUE => Types::null(),
153            ScalarType::ARRAY => Type::listOf(Types::json()),
154            ScalarType::STDCLASS => new MapOfType(Types::json()),
155            ScalarType::MIXED => Types::json(),
156        };
157        if ($nullable) {
158            return $result;
159        }
160        return Type::nonNull($result);
161    }
162
163    public static function createFromField(FieldInterface $fieldMetadata): Type
164    {
165        $type = $fieldMetadata->getTypehint();
166        $class = ConverterUtils::toReflectionClass($type);
167        $nullable = $fieldMetadata->allowsNull() || !$fieldMetadata->isRequired();
168        $scalar = ScalarType::createFromReflectionType($type, $nullable);
169        
170        if ($class !== null && in_array($scalar, [ScalarType::STDCLASS, ScalarType::MIXED], true)) {
171            // TODO: how to handle getModificationMetadata here? We don't know if we are creating or modifying an object.
172            $method = static::class === FromMetadataInputType::class ? 'getCreationMetadata' : 'getResultMetadata';
173            return self::createFromMetadata(
174                MetadataFactory::getMetadataStrategyForType(
175                    $type ?? ReflectionTypeFactory::createReflectionType('mixed')
176                )->$method(new ApieContext()),
177                $nullable
178            );
179        }
180        
181        return self::createFromScalar($scalar, $nullable);
182    }
183}