Completed
Push — master ( decfed...00ae50 )
by Narcotic
61:33
created

MainController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 0
cts 12
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 17
nc 1
nop 8
crap 2

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * controller for start page
4
 */
5
6
namespace Graviton\CoreBundle\Controller;
7
8
use Graviton\CoreBundle\Event\HomepageRenderEvent;
9
use Graviton\ProxyBundle\Service\ApiDefinitionLoader;
10
use Graviton\RestBundle\HttpFoundation\LinkHeader;
11
use Graviton\RestBundle\HttpFoundation\LinkHeaderItem;
12
use Graviton\RestBundle\Service\RestUtilsInterface;
13
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\Response;
16
use Symfony\Component\Routing\Router;
17
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
18
19
/**
20
 * MainController
21
 *
22
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
23
 * @license  https://opensource.org/licenses/MIT MIT License
24
 * @link     http://swisscom.ch
25
 */
26
class MainController
27
{
28
    /**
29
     * @var Router
30
     */
31
    private $router;
32
33
    /**
34
     * @var Response
35
     */
36
    private $response;
37
38
    /**
39
     * @var RestUtilsInterface
40
     */
41
    private $restUtils;
42
43
    /**
44
     * @var EventDispatcherInterface
45
     */
46
    private $eventDispatcher;
47
48
    /**
49
     * @var ApiDefinitionLoader
50
     */
51
    private $apiLoader;
52
53
    /**
54
     * @var array
55
     */
56
    private $addditionalRoutes;
57
58
    /**
59
     * @var array
60
     */
61
    private $pathWhitelist;
62
63
    /**
64
     * @var array
65
     */
66
    private $proxySourceConfiguration;
67
68
    /**
69
     * @param Router                   $router                   router
70
     * @param Response                 $response                 prepared response
71
     * @param RestUtilsInterface       $restUtils                rest-utils from GravitonRestBundle
72
     * @param EventDispatcherInterface $eventDispatcher          event dispatcher
73
     * @param ApiDefinitionLoader      $apiLoader                loader for third party api definition
74
     * @param array                    $additionalRoutes         custom routes
75
     * @param array                    $pathWhitelist            serviec path that always get aded to the main page
76
     * @param array                    $proxySourceConfiguration Set of sources to be recognized by the controller
77
     */
78
    public function __construct(
79
        Router $router,
80
        Response $response,
81
        RestUtilsInterface $restUtils,
82
        EventDispatcherInterface $eventDispatcher,
83
        ApiDefinitionLoader $apiLoader,
84
        $additionalRoutes = [],
85
        $pathWhitelist = [],
86
        array $proxySourceConfiguration = []
87
    ) {
88
        $this->router = $router;
89
        $this->response = $response;
90
        $this->restUtils = $restUtils;
91
        $this->eventDispatcher = $eventDispatcher;
92
        $this->apiLoader = $apiLoader;
93
        $this->addditionalRoutes = $additionalRoutes;
94
        $this->pathWhitelist = $pathWhitelist;
95
        $this->proxySourceConfiguration = $proxySourceConfiguration;
96
    }
97
98
    /**
99
     * create simple start page.
100
     *
101
     * @return Response $response Response with result or error
102
     */
103
    public function indexAction()
104
    {
105
        $response = $this->response;
106
107
        $mainPage = new \stdClass();
108
        $mainPage->services = $this->determineServices(
109
            $this->restUtils->getOptionRoutes()
110
        );
111
112
        $mainPage->thirdparty = $this->registerThirdPartyServices();
113
114
        $response->setContent(json_encode($mainPage));
115
        $response->setStatusCode(Response::HTTP_OK);
116
        $response->headers->set('Link', $this->prepareLinkHeader());
117
118
        // todo: make sure, that the correct content type is set.
119
        // todo: this should be covered by a kernel.response event listener?
120
        $response->headers->set('Content-Type', 'application/json');
121
122
        return $response;
123
    }
124
125
    /**
126
     * Determines what service endpoints are available.
127
     *
128
     * @param array $optionRoutes List of routing options.
129
     *
130
     * @return array
131
     */
132
    protected function determineServices(array $optionRoutes)
133
    {
134
        $router = $this->router;
135
        foreach ($this->addditionalRoutes as $route) {
136
            $optionRoutes[$route] = null;
137
        }
138
139
        $services = array_map(
140
            function ($routeName) use ($router) {
141
                $routeParts = explode('.', $routeName);
142
                if (count($routeParts) > 3) {
143
                    list($app, $bundle, $rest, $document) = $routeParts;
144
145
                    $schemaRoute = implode('.', [$app, $bundle, $rest, $document, 'canonicalSchema']);
146
147
                    return [
148
                        '$ref' => $router->generate($routeName, [], UrlGeneratorInterface::ABSOLUTE_URL),
149
                        'profile' => $router->generate($schemaRoute, [], UrlGeneratorInterface::ABSOLUTE_URL),
150
                    ];
151
                }
152
            },
153
            array_keys($optionRoutes)
154
        );
155
156
        $sortArr = [];
157
        foreach ($services as $key => $val) {
158
            if ($this->isRelevantForMainPage($val) && !in_array($val['$ref'], $sortArr)) {
159
                $sortArr[$key] = $val['$ref'];
160
            } else {
161
                unset($services[$key]);
162
            }
163
        }
164
165
        // get additional routes
166
        $additionalRoutes = $this->getAdditionalRoutes($sortArr);
167
168
        $services = array_merge($services, $additionalRoutes);
169
170
        array_multisort($sortArr, SORT_ASC, $services);
171
        return $services;
172
    }
173
174
    /**
175
     * gets the additional routes that can be injected by listeners/subscribers
176
     *
177
     * @param array $sortArr array needed for sorting
178
     *
179
     * @return array additional routes
180
     */
181
    private function getAdditionalRoutes(array &$sortArr)
182
    {
183
        $additionalRoutes = [];
184
        $event = new HomepageRenderEvent();
185
        $routes = $this->eventDispatcher->dispatch(HomepageRenderEvent::EVENT_NAME, $event)->getRoutes();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\EventDispatcher\Event as the method getRoutes() does only exist in the following sub-classes of Symfony\Component\EventDispatcher\Event: Graviton\CoreBundle\Event\HomepageRenderEvent. 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...
186
187
        if (!empty($routes)) {
188
            $baseRoute = $this->router->match("/");
189
            $baseUrl = $this->router->generate($baseRoute['_route'], [], UrlGeneratorInterface::ABSOLUTE_URL);
190
            foreach ($routes as $route) {
191
                $thisUrl = $baseUrl.$route['$ref'];
192
                $additionalRoutes[] = [
193
                    '$ref' => $thisUrl,
194
                    'profile' => $baseUrl.$route['profile']
195
                ];
196
                $sortArr[$thisUrl] = $thisUrl;
197
            }
198
        }
199
200
        return $additionalRoutes;
201
    }
202
203
    /**
204
     * Prepares the header field containing information about pagination.
205
     *
206
     * @return string
207
     */
208
    protected function prepareLinkHeader()
209
    {
210
        $links = new LinkHeader(array());
211
        $links->add(
212
            new LinkHeaderItem(
213
                $this->router->generate('graviton.core.rest.app.all', array (), UrlGeneratorInterface::ABSOLUTE_URL),
214
                array ('rel' => 'apps', 'type' => 'application/json')
215
            )
216
        );
217
218
        return (string) $links;
219
    }
220
221
    /**
222
     * tells if a service is relevant for the mainpage
223
     *
224
     * @param array $val value of service spec
225
     *
226
     * @return boolean
227
     */
228
    private function isRelevantForMainPage($val)
229
    {
230
        return (substr($val['$ref'], -1) === '/')
231
            || in_array(parse_url($val['$ref'], PHP_URL_PATH), $this->pathWhitelist);
232
    }
233
234
    /**
235
     * Resolves all third party routes and add schema info
236
     *
237
     * @param array $thirdApiRoutes list of all routes from an API
238
     *
239
     * @return array
240
     */
241
    protected function determineThirdPartyServices(array $thirdApiRoutes)
242
    {
243
        $definition = $this->apiLoader;
244
        $mainRoute = $this->router->generate(
245
            'graviton.core.static.main.all',
246
            [],
247
            UrlGeneratorInterface::ABSOLUTE_URL
248
        );
249
        $services = array_map(
250
            function ($apiRoute) use ($mainRoute, $definition) {
251
252
                return array (
253
                    '$ref' => $mainRoute.$apiRoute,
254
                    'profile' => $mainRoute."schema/".$apiRoute."/item",
255
                );
256
            },
257
            $thirdApiRoutes
258
        );
259
260
        return $services;
261
    }
262
263
    /**
264
     * Finds configured external apis to be exposed via G2.
265
     *
266
     * @return array
267
     */
268
    private function registerThirdPartyServices()
269
    {
270
        $services = [];
271
272
        // getenv()... it's a workaround for run all tests on travis! will be removed!
273
        if (getenv('USER') !== 'travis' && getenv('HAS_JOSH_K_SEAL_OF_APPROVAL') !== true) {
274
            foreach (array_keys($this->proxySourceConfiguration) as $source) {
275
                foreach ($this->proxySourceConfiguration[$source] as $thirdparty => $option) {
276
                    $this->apiLoader->resetDefinitionLoader();
277
                    $this->apiLoader->setOption($option);
278
                    $this->apiLoader->addOptions($this->decideApiAndEndpoint($option));
279
                    $services[$thirdparty] = $this->determineThirdPartyServices(
280
                        $this->apiLoader->getAllEndpoints(false, true)
281
                    );
282
                }
283
            }
284
        }
285
286
        return $services;
287
    }
288
289
    /**
290
     * get API name and endpoint from the url (third party API)
291
     *
292
     * @param array $config Configuration information ['prefix', 'serviceEndpoint']
293
     *
294
     * @return array
295
     */
296
    protected function decideApiAndEndpoint(array $config)
297
    {
298
        if (array_key_exists('serviceEndpoint', $config)) {
299
            return array (
300
                "apiName" => $config['prefix'],
301
                "endpoint" => $config['serviceEndpoint'],
302
            );
303
        }
304
305
        return [];
306
    }
307
308
    /**
309
     * Return OPTIONS results.
310
     *
311
     * @param Request $request Current http request
312
     *
313
     * @return Response $response Result of the action
314
     */
315
    public function optionsAction(Request $request)
316
    {
317
        $response = $this->response;
318
        $response->setStatusCode(Response::HTTP_NO_CONTENT);
319
        $request->attributes->set('corsMethods', 'GET, OPTIONS');
320
321
        return $response;
322
    }
323
}
324