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 — master (#96)
by Jérémiah
05:48
created

TypeWithOutputFieldsDefinition::getBuilder()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4.0058

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 25
ccs 13
cts 14
cp 0.9286
rs 8.5806
cc 4
eloc 14
nc 4
nop 2
crap 4.0058
1
<?php
2
3
/*
4
 * This file is part of the OverblogGraphQLBundle package.
5
 *
6
 * (c) Overblog <http://github.com/overblog/>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Overblog\GraphQLBundle\Config;
13
14
use Overblog\GraphQLBundle\Definition\Builder\MappingInterface;
15
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
16
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
17
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
18
19
abstract class TypeWithOutputFieldsDefinition extends TypeDefinition
20
{
21
    const BUILDER_FIELD_TYPE = 'field';
22
    const BUILDER_ARGS_TYPE = 'args';
23
24
    /**
25
     * @var MappingInterface[]
26
     */
27
    private static $argsBuilderClassMap = [
28
        'Relay::ForwardConnection' => 'Overblog\GraphQLBundle\Relay\Connection\ForwardConnectionArgsDefinition',
29
        'Relay::BackwardConnection' => 'Overblog\GraphQLBundle\Relay\Connection\BackwardConnectionArgsDefinition',
30
        'Relay::Connection' => 'Overblog\GraphQLBundle\Relay\Connection\ConnectionArgsDefinition',
31
    ];
32
33
    /**
34
     * @var MappingInterface[]
35
     */
36
    private static $fieldBuilderClassMap = [
37
        'Relay::Mutation' => 'Overblog\GraphQLBundle\Relay\Mutation\MutationFieldDefinition',
38
        'Relay::GlobalId' => 'Overblog\GraphQLBundle\Relay\Node\GlobalIdFieldDefinition',
39
        'Relay::Node' => 'Overblog\GraphQLBundle\Relay\Node\NodeFieldDefinition',
40
        'Relay::PluralIdentifyingRoot' => 'Overblog\GraphQLBundle\Relay\Node\PluralIdentifyingRootFieldDefinition',
41
    ];
42
43 1
    public static function addArgsBuilderClass($name, $argBuilderClass)
44
    {
45 1
        self::checkBuilderClass($argBuilderClass, 'args');
46
47 1
        self::$argsBuilderClassMap[$name] = $argBuilderClass;
48 1
    }
49
50 1
    public static function addFieldBuilderClass($name, $fieldBuilderClass)
51
    {
52 1
        self::checkBuilderClass($fieldBuilderClass, 'field');
53
54 1
        self::$fieldBuilderClassMap[$name] = $fieldBuilderClass;
55 1
    }
56
57 1
    protected static function checkBuilderClass($builderClass, $type)
58
    {
59 1
        $interface = 'Overblog\\GraphQLBundle\\Definition\\Builder\\MappingInterface';
60
61 1
        if (!is_string($builderClass)) {
62
            throw new \InvalidArgumentException(
63
                sprintf('%s builder class should be string, but "%s" given.', ucfirst($type), gettype($builderClass))
64
            );
65
        }
66
67 1
        if (!class_exists($builderClass)) {
68
            throw new \InvalidArgumentException(
69
                sprintf('%s builder class "%s" not found.', ucfirst($type), $builderClass)
70
            );
71
        }
72
73 1
        if (!is_subclass_of($builderClass, $interface)) {
74
            throw new \InvalidArgumentException(
75
                sprintf(
76
                    '%s builder class should be instance of "%s", but "%s" given.',
77
                    ucfirst($type),
78
                    $interface,
79
                    $builderClass
80
                )
81
            );
82
        }
83 1
    }
84
85
    /**
86
     * @param string $name
87
     * @param string $type
88
     *
89
     * @return MappingInterface
90
     *
91
     * @throws InvalidConfigurationException if builder class not define
92
     */
93 8
    protected function getBuilder($name, $type)
94
    {
95 8
        static $builders = [];
96 8
        if (isset($builders[$type][$name])) {
97 5
            return $builders[$type][$name];
98
        }
99
100 6
        $builderClassMap = self::${$type.'BuilderClassMap'};
101
102 6
        if (isset($builderClassMap[$name])) {
103 5
            return $builders[$type][$name] = new $builderClassMap[$name]();
104
        }
105
        // deprecated relay builder name ?
106 1
        $newName = 'Relay::'.rtrim($name, 'Args');
107 1
        if (isset($builderClassMap[$newName])) {
108 1
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
109 1
                sprintf('The "%s" %s builder is deprecated as of 0.7 and will be removed in 0.8. Use "%s" instead.', $name, $type, $newName),
110
                E_USER_DEPRECATED
111 1
            );
112
113 1
            return $builders[$type][$newName] = new $builderClassMap[$newName]();
114
        }
115
116
        throw new InvalidConfigurationException(sprintf('%s builder "%s" not found.', ucfirst($type), $name));
117
    }
118
119 18
    protected function outputFieldsSelection($name)
120
    {
121 18
        $builder = new TreeBuilder();
122 18
        $node = $builder->root($name);
123
        $node
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method requiresAtLeastOneElement() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\ArrayNodeDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
124 18
            ->isRequired()
125 18
            ->requiresAtLeastOneElement();
126
127
        /* @var ArrayNodeDefinition $prototype */
128 18
        $prototype = $node->useAttributeAsKey('name', false)->prototype('array');
129
130
        $prototype
131
            // build args if argsBuilder exists
132 18
            ->beforeNormalization()
133
                ->ifTrue(function ($field) {
134 12
                    return isset($field['argsBuilder']);
135 18
                })
136
                ->then(function ($field) {
137 4
                    $argsBuilderName = null;
138
139 4
                    if (is_string($field['argsBuilder'])) {
140 4
                        $argsBuilderName = $field['argsBuilder'];
141 4
                    } elseif (isset($field['argsBuilder']['builder']) && is_string($field['argsBuilder']['builder'])) {
142 1
                        $argsBuilderName = $field['argsBuilder']['builder'];
143 1
                    }
144
145 4
                    if ($argsBuilderName) {
146 4
                        $args = $this->getBuilder($argsBuilderName, static::BUILDER_ARGS_TYPE)->toMappingDefinition([]);
147 4
                        $field['args'] = isset($field['args']) && is_array($field['args']) ? array_merge($args, $field['args']) : $args;
148 4
                    }
149
150 4
                    unset($field['argsBuilder']);
151
152 4
                    return $field;
153 18
                })
154 18
            ->end()
155
            // build field if builder exists
156 18
            ->beforeNormalization()
157
                ->always(function ($field) {
158 12
                    $fieldBuilderName = null;
159
160 12
                    if (isset($field['builder']) && is_string($field['builder'])) {
161 6
                        $fieldBuilderName = $field['builder'];
162 6
                        unset($field['builder']);
163 12
                    } elseif (is_string($field)) {
164 2
                        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
165
                            'The builder short syntax (Field: Builder => Field: {builder: Builder}) is deprecated as of 0.7 and will be removed in 0.8. '.
166 2
                            'It will be replaced by the field type short syntax (Field: Type => Field: {type: Type})',
167
                            E_USER_DEPRECATED
168 2
                        );
169 2
                        $fieldBuilderName = $field;
170 2
                    }
171
172 12
                    $builderConfig = [];
173 12
                    if (isset($field['builderConfig'])) {
174 5
                        if (is_array($field['builderConfig'])) {
175 5
                            $builderConfig = $field['builderConfig'];
176 5
                        }
177 5
                        unset($field['builderConfig']);
178 5
                    }
179
180 12
                    if ($fieldBuilderName) {
181 6
                        $buildField = $this->getBuilder($fieldBuilderName, static::BUILDER_FIELD_TYPE)->toMappingDefinition($builderConfig);
182 6
                        $field = is_array($field) ? array_merge($buildField, $field) : $buildField;
183 6
                    }
184
185 12
                    return $field;
186 18
                })
187 18
            ->end();
188
189
        $prototype
0 ignored issues
show
Bug introduced by
The method useAttributeAsKey() does not exist on Symfony\Component\Config...\Builder\NodeDefinition. Did you maybe mean attribute()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
190 18
            ->children()
191 18
                ->append($this->typeSelection())
192 18
                ->arrayNode('args')
193 18
                    ->info('Array of possible type arguments. Each entry is expected to be an array with following keys: name (string), type')
194 18
                    ->useAttributeAsKey('name', false)
195 18
                    ->prototype('array')
196
                        // Allow arg type short syntax (Arg: Type => Arg: {type: Type})
197 18
                        ->beforeNormalization()
198
                            ->ifTrue(function ($options) {
199 9
                                return is_string($options);
200 18
                            })
201 18
                            ->then(function ($options) {
202 1
                                return ['type' => $options];
203 18
                            })
204 18
                        ->end()
205 18
                        ->children()
206 18
                            ->append($this->typeSelection(true))
207 18
                            ->append($this->descriptionSection())
208 18
                            ->append($this->defaultValueSection())
209 18
                        ->end()
210 18
                    ->end()
211 18
                ->end()
212 18
                ->variableNode('resolve')
213 18
                    ->info('Value resolver (expression language can be use here)')
214 18
                ->end()
215 18
                ->append($this->descriptionSection())
216 18
                ->append($this->deprecationReasonSelection())
217 18
                ->variableNode('access')
218 18
                    ->info('Access control to field (expression language can be use here)')
219 18
                ->end()
220 18
                ->variableNode('complexity')
221 18
                    ->info('Custom complexity calculator.')
222 18
                ->end()
223 18
            ->end();
224
225 18
        return $node;
226
    }
227
}
228