Completed
Push — master ( 4bb4eb...402013 )
by Arthur
03:35
created

SchemaFactory::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
c 2
b 1
f 1
dl 0
loc 11
rs 9.4285
cc 1
eloc 9
nc 1
nop 3
1
<?php
2
3
namespace Arthem\GraphQLMapper\Schema;
4
5
use Arthem\GraphQLMapper\Mapping\Cache\CacheDriverInterface;
6
use Arthem\GraphQLMapper\Mapping\Driver\DriverInterface;
7
use Arthem\GraphQLMapper\Mapping\Field;
8
use Arthem\GraphQLMapper\Mapping\FieldContainer;
9
use Arthem\GraphQLMapper\Mapping\Guess\MappingGuesserManager;
10
use Arthem\GraphQLMapper\Mapping\InterfaceType;
11
use Arthem\GraphQLMapper\Mapping\MappingNormalizer;
12
use Arthem\GraphQLMapper\Mapping\SchemaContainer;
13
use Arthem\GraphQLMapper\Schema\Resolve\CallableResolver;
14
use Arthem\GraphQLMapper\Schema\Resolve\ResolverInterface;
15
use GraphQL\Schema;
16
use GraphQL\Type\Definition as GQLDefinition;
17
18
class SchemaFactory
19
{
20
    /**
21
     * @var string
22
     */
23
    protected $cacheKey = 'Arthem:GraphQL:Mapping';
24
25
    /**
26
     * @var CacheDriverInterface
27
     */
28
    private $cacheDriver;
29
30
    /**
31
     * @var DriverInterface
32
     */
33
    private $driver;
34
35
    /**
36
     * @var TypeResolver
37
     */
38
    private $typeResolver;
39
40
    /**
41
     * @var ResolverInterface[]
42
     */
43
    private $resolveFactories = [];
44
45
    /**
46
     * @var MappingNormalizer
47
     */
48
    private $normalizer;
49
50
    /**
51
     * @var MappingGuesserManager
52
     */
53
    private $guesser;
54
55
    /**
56
     * @param DriverInterface            $driver
57
     * @param TypeResolver               $typeResolver
58
     * @param MappingGuesserManager|null $guesser
59
     */
60
    public function __construct(
61
        DriverInterface $driver,
62
        TypeResolver $typeResolver,
63
        MappingGuesserManager $guesser = null
64
    ) {
65
        $this->driver       = $driver;
66
        $this->typeResolver = $typeResolver;
67
        $this->guesser      = $guesser;
68
        $this->normalizer   = new MappingNormalizer();
69
        $this->addResolver(new CallableResolver());
70
    }
71
72
    /**
73
     * @param CacheDriverInterface $cacheDriver
74
     */
75
    public function setCacheDriver(CacheDriverInterface $cacheDriver = null)
76
    {
77
        $this->cacheDriver = $cacheDriver;
78
    }
79
80
    /**
81
     * @return Schema
82
     */
83
    public function createSchema()
84
    {
85
        $schemaContainer = $this->getSchemaContainer();
86
87
        foreach ($schemaContainer->getInterfaces() as $type) {
88
            $GQLType = $this->createInterface($type);
89
            $this->typeResolver->addType($type->getName(), $GQLType);
90
        }
91
92
        foreach ($schemaContainer->getTypes() as $type) {
93
            $GQLType = $this->createType($type);
94
            $this->typeResolver->addType($type->getName(), $GQLType);
95
        }
96
97
        $querySchema  = $schemaContainer->getQuerySchema();
98
        $mutationType = $schemaContainer->getMutationSchema();
99
        $queryType    = null !== $querySchema ? $this->createType($querySchema) : null;
100
        $mutationType = null !== $mutationType ? $this->createType($mutationType) : null;
101
102
        return new Schema($queryType, $mutationType);
103
    }
104
105
    /**
106
     * @return SchemaContainer
107
     */
108
    private function getSchemaContainer()
109
    {
110
        if (null !== $this->cacheDriver) {
111
            $schemaContainer = $this->cacheDriver->load();
112
            if (false !== $schemaContainer) {
113
                return $schemaContainer;
114
            }
115
        }
116
117
        return $this->loadSchemaContainer();
118
    }
119
120
    /**
121
     * @return SchemaContainer
122
     */
123
    private function loadSchemaContainer()
124
    {
125
        $schemaContainer = new SchemaContainer();
126
        $this->driver->load($schemaContainer);
127
        if (null !== $this->guesser) {
128
            $this->guesser->guess($schemaContainer);
129
        }
130
        $this->normalizer->normalize($schemaContainer);
131
132
        if (null !== $this->cacheDriver) {
133
            $this->cacheDriver->save($schemaContainer);
134
        }
135
136
        return $schemaContainer;
137
    }
138
139
    /**
140
     * @param InterfaceType $type
141
     * @return GQLDefinition\InterfaceType
142
     */
143
    private function createInterface(InterfaceType $type)
144
    {
145
        if (null !== $type->getFields()) {
146
            $this->prepareFields($type->getFields());
147
        }
148
        $type = new GQLDefinition\InterfaceType($type->toMapping());
149
150
        return $type;
151
    }
152
153
    /**
154
     * @param FieldContainer $type
155
     * @return GQLDefinition\ObjectType
156
     */
157
    private function createType(FieldContainer $type)
158
    {
159
        if (null !== $type->getFields()) {
160
            $this->prepareFields($type->getFields());
161
        }
162
163
        switch ($type->getInternalType()) {
164
            case 'ObjectType':
165
                $type = new GQLDefinition\ObjectType($type->toMapping());
166
                break;
167
            case 'EnumType':
168
                $type = new GQLDefinition\EnumType($type->toMapping());
169
                break;
170
        }
171
172
        return $type;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $type; (GraphQL\Type\Definition\...e\Definition\ObjectType) is incompatible with the return type documented by Arthem\GraphQLMapper\Sch...hemaFactory::createType of type GraphQL\Type\Definition\ObjectType.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
173
    }
174
175
    /**
176
     * @param Field[] $fields
177
     */
178
    private function prepareFields(array $fields)
179
    {
180
        foreach ($fields as $field) {
181
182
            if (null !== $field->getArguments()) {
183
                $this->prepareFields($field->getArguments());
184
            }
185
186
            $resolveConfig = $field->getResolveConfig();
187
            if (isset($resolveConfig['handler'])) {
188
                $handler  = $resolveConfig['handler'];
189
                $resolver = $this->resolveFactories[$handler]->getFunction($resolveConfig, $field);
190
                $field->setResolve($resolver);
191
            }
192
193
            $typeName = $field->getType();
194
            if (empty($typeName)) {
195
                throw new \InvalidArgumentException(sprintf('Missing type for field "%s"', $field->getName()));
196
            }
197
            $field->setResolvedType(function () use ($typeName) {
198
                return $this->typeResolver->resolveType($typeName);
199
            });
200
        }
201
    }
202
203
    /**
204
     * @param ResolverInterface $factory
205
     */
206
    public function addResolver(ResolverInterface $factory)
207
    {
208
        $this->resolveFactories[$factory->getName()] = $factory;
209
    }
210
}
211