Completed
Push — master ( 3aac06...366db0 )
by Adrien
01:53
created

Types::isEntity()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Doctrine;
6
7
use Doctrine\Common\Annotations\AnnotationRegistry;
8
use Doctrine\ORM\EntityManager;
9
use GraphQL\Doctrine\Definition\EntityIDType;
10
use GraphQL\Doctrine\Factory\InputTypeFactory;
11
use GraphQL\Doctrine\Factory\ObjectTypeFactory;
12
use GraphQL\Type\Definition\InputObjectType;
13
use GraphQL\Type\Definition\Type;
14
15
/**
16
 * Registry of types to manage all GraphQL types
17
 *
18
 * This is the entry point for the library.
19
 */
20
class Types
21
{
22
    /**
23
     * @var array mapping of type name to type instances
24
     */
25
    private $types = [];
26
27
    /**
28
     * @var ObjectTypeFactory
29
     */
30
    private $objectTypeFactory;
31
32
    /**
33
     * @var InputTypeFactory
34
     */
35
    private $inputTypeFactory;
36
37
    /**
38
     * @var EntityManager
39
     */
40
    private $entityManager;
41
42 16
    public function __construct(EntityManager $entityManager, array $customTypeMapping = [])
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $entityManager. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
43
    {
44 16
        $this->types = $this->getPhpToGraphQLMapping();
45 16
        $this->entityManager = $entityManager;
46 16
        $this->objectTypeFactory = new ObjectTypeFactory($this, $entityManager);
47 16
        $this->inputTypeFactory = new InputTypeFactory($this, $entityManager);
48
49 16
        $entityManager->getConfiguration()->newDefaultAnnotationDriver();
50 16
        AnnotationRegistry::registerLoader('class_exists');
51
52 16
        foreach ($customTypeMapping as $phpType => $graphQLType) {
53 16
            $instance = $this->createInstance($graphQLType);
54 16
            $this->registerInstance($phpType, $instance);
55
        }
56 16
    }
57
58
    /**
59
     * Always return the same instance of `Type` for the given class name
60
     *
61
     * All entity getter methods will be exposed, unless specified otherwise
62
     * with annotations.
63
     *
64
     * @param string $className the class name of either a scalar type (`PostStatus::class`), or an entity (`Post::class`)
65
     * @return Type
66
     */
67 14
    public function get(string $className): Type
68
    {
69 14
        $key = $this->normalizedClassName($className);
70
71 14
        if (!isset($this->types[$key])) {
72 12
            $instance = $this->createInstance($className);
73 11
            $this->registerInstance($key, $instance);
74
        }
75
76 13
        return $this->types[$key];
77
    }
78
79
    /**
80
     * Returns an input type for the given entity to be used in mutations
81
     *
82
     * All entity setter methods will be exposed, unless specified otherwise
83
     * with annotations.
84
     *
85
     * @param string $className the class name of an entity (`Post::class`)
86
     * @return InputObjectType
87
     */
88 2 View Code Duplication
    public function getInput(string $className): InputObjectType
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
89
    {
90 2
        $this->throwIfNotEntity($className);
91 2
        $key = Utils::getInputTypeName($className);
92
93 2
        if (!isset($this->types[$key])) {
94 2
            $instance = $this->inputTypeFactory->create($className);
95 2
            $this->registerInstance($key, $instance);
96
        }
97
98 2
        return $this->types[$key];
99
    }
100
101
    /**
102
     * Returns an special ID type for the given entity
103
     *
104
     * This is mostly useful for internal usage when a getter has an entity
105
     * as parameter. This type will automatically load the entity from DB, so
106
     * the resolve functions can use a real instance of entity instead of an ID.
107
     *
108
     * @param string $className the class name of an entity (`Post::class`)
109
     * @return EntityIDType
110
     */
111 2 View Code Duplication
    public function getId(string $className): EntityIDType
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
112
    {
113 2
        $this->throwIfNotEntity($className);
114 2
        $key = Utils::getIDTypeName($className);
115
116 2
        if (!isset($this->types[$key])) {
117 2
            $instance = new EntityIDType($this->entityManager, $className);
118 2
            $this->registerInstance($key, $instance);
119
        }
120
121 2
        return $this->types[$key];
122
    }
123
124
    /**
125
     * Register the given type in our internal registry
126
     * @param string $key
127
     * @param Type $instance
128
     */
129 16
    private function registerInstance(string $key, Type $instance): void
130
    {
131 16
        $this->types[$key] = $instance;
132 16
        $this->types[$instance->name] = $instance;
133 16
    }
134
135
    /**
136
     * Create an instance of either a custom, scalar or ObjectType
137
     * @param string $className
138
     * @return Type
139
     */
140 16
    private function createInstance(string $className): Type
141
    {
142 16
        if (is_a($className, Type::class, true)) {
143 16
            return new $className();
144
        }
145
146 10
        $this->throwIfNotEntity($className);
147
148 9
        return $this->objectTypeFactory->create($className);
149
    }
150
151
    /**
152
     * Checks if a className is a valid doctrine entity
153
     *
154
     * @return bool
155
     */
156 12
    public function isEntity(string $className): bool
157
    {
158 12
        return class_exists($className) && !$this->entityManager->getMetadataFactory()->isTransient($className);
159
    }
160
161
    /**
162
     * Returns the list of native GraphQL types
163
     * @return array
164
     */
165 16
    private function getPhpToGraphQLMapping(): array
166
    {
167
        return [
168 16
            'id' => Type::id(),
169 16
            'bool' => Type::boolean(),
170 16
            'int' => Type::int(),
171 16
            'float' => Type::float(),
172 16
            'string' => Type::string(),
173
        ];
174
    }
175
176
    /**
177
     * Throw an exception if the class name is not Doctrine entity
178
     * @param string $className
179
     * @throws \UnexpectedValueException
180
     */
181 12
    private function throwIfNotEntity(string $className): void
182
    {
183 12
        if (!$this->isEntity($className)) {
184 1
            throw new \UnexpectedValueException('Given class name `' . $className . '` is not a Doctrine entity. Either register a custom GraphQL type for `' . $className . '` when instantiating `' . self::class . '`, or change the usage of that class to something else.');
185
        }
186 11
    }
187
188
    /**
189
     * Remove the leading `\` that may exists in FQCN
190
     * @param string $className
191
     * @return string
192
     */
193 14
    private function normalizedClassName(string $className): string
194
    {
195 14
        return ltrim($className, '\\');
196
    }
197
}
198