Completed
Push — master ( 664783...554f9d )
by
unknown
15:59
created

MainController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 0
cts 21
cp 0
rs 9.3142
c 0
b 0
f 0
cc 1
eloc 19
nc 1
nop 9
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\Bundle\FrameworkBundle\Templating\EngineInterface;
14
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
15
use Symfony\Component\HttpFoundation\Request;
16
use Symfony\Component\HttpFoundation\Response;
17
use Symfony\Component\Routing\Router;
18
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
19
20
/**
21
 * MainController
22
 *
23
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
24
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
25
 * @link     http://swisscom.ch
26
 */
27
class MainController
28
{
29
    /**
30
     * @var Router
31
     */
32
    private $router;
33
34
    /**
35
     * @var Response
36
     */
37
    private $response;
38
39
    /**
40
     * @var RestUtilsInterface
41
     */
42
    private $restUtils;
43
44
    /**
45
     * @var EngineInterface
46
     */
47
    private $templating;
48
49
    /**
50
     * @var EventDispatcherInterface
51
     */
52
    private $eventDispatcher;
53
54
    /**
55
     * @var ApiDefinitionLoader
56
     */
57
    private $apiLoader;
58
59
    /**
60
     * @var array
61
     */
62
    private $addditionalRoutes;
63
64
    /**
65
     * @var array
66
     */
67
    private $pathWhitelist;
68
69
    /**
70
     * @var array
71
     */
72
    private $proxySourceConfiguration;
73
74
    /**
75
     * @param Router                   $router                   router
76
     * @param Response                 $response                 prepared response
77
     * @param RestUtilsInterface       $restUtils                rest-utils from GravitonRestBundle
78
     * @param EngineInterface          $templating               templating-engine
79
     * @param EventDispatcherInterface $eventDispatcher          event dispatcher
80
     * @param ApiDefinitionLoader      $apiLoader                loader for third party api definition
81
     * @param array                    $additionalRoutes         custom routes
82
     * @param array                    $pathWhitelist            serviec path that always get aded to the main page
83
     * @param array                    $proxySourceConfiguration Set of sources to be recognized by the controller
84
     */
85
    public function __construct(
86
        Router $router,
87
        Response $response,
88
        RestUtilsInterface $restUtils,
89
        EngineInterface $templating,
90
        EventDispatcherInterface $eventDispatcher,
91
        ApiDefinitionLoader $apiLoader,
92
        $additionalRoutes = [],
93
        $pathWhitelist = [],
94
        array $proxySourceConfiguration = []
95
    ) {
96
        $this->router = $router;
97
        $this->response = $response;
98
        $this->restUtils = $restUtils;
99
        $this->templating = $templating;
100
        $this->eventDispatcher = $eventDispatcher;
101
        $this->apiLoader = $apiLoader;
102
        $this->addditionalRoutes = $additionalRoutes;
103
        $this->pathWhitelist = $pathWhitelist;
104
        $this->proxySourceConfiguration = $proxySourceConfiguration;
105
    }
106
107
    /**
108
     * create simple start page.
109
     *
110
     * @return Response $response Response with result or error
111
     */
112
    public function indexAction()
113
    {
114
        $response = $this->response;
115
116
        $mainPage = new \stdClass();
117
        $mainPage->services = $this->determineServices(
118
            $this->restUtils->getOptionRoutes()
119
        );
120
121
        $mainPage->thirdparty = $this->registerThirdPartyServices();
122
123
        $response->setContent(json_encode($mainPage));
124
        $response->setStatusCode(Response::HTTP_OK);
125
        $response->headers->set('Link', $this->prepareLinkHeader());
126
127
        // todo: make sure, that the correct content type is set.
128
        // todo: this should be covered by a kernel.response event listener?
129
        $response->headers->set('Content-Type', 'application/json');
130
131
        return $this->render(
132
            'GravitonCoreBundle:Main:index.json.twig',
133
            array('response' => $response->getContent()),
134
            $response
135
        );
136
    }
137
138
    /**
139
     * Renders a view.
140
     *
141
     * @param string   $view       The view name
142
     * @param array    $parameters An array of parameters to pass to the view
143
     * @param Response $response   A response instance
0 ignored issues
show
Documentation introduced by
Should the type for parameter $response not be null|Response?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
144
     *
145
     * @return Response A Response instance
146
     */
147
    public function render($view, array $parameters = array(), Response $response = null)
148
    {
149
        return $this->templating->renderResponse($view, $parameters, $response);
150
    }
151
152
    /**
153
     * Determines what service endpoints are available.
154
     *
155
     * @param array $optionRoutes List of routing options.
156
     *
157
     * @return array
158
     */
159
    protected function determineServices(array $optionRoutes)
160
    {
161
        $router = $this->router;
162
        foreach ($this->addditionalRoutes as $route) {
163
            $optionRoutes[$route] = null;
164
        }
165
166
        $services = array_map(
167
            function ($routeName) use ($router) {
168
                $routeParts = explode('.', $routeName);
169
                if (count($routeParts) > 3) {
170
                    list($app, $bundle, $rest, $document) = $routeParts;
171
172
                    $schemaRoute = implode('.', [$app, $bundle, $rest, $document, 'canonicalSchema']);
173
174
                    return [
175
                        '$ref' => $router->generate($routeName, [], UrlGeneratorInterface::ABSOLUTE_URL),
176
                        'profile' => $router->generate($schemaRoute, [], UrlGeneratorInterface::ABSOLUTE_URL),
177
                    ];
178
                }
179
            },
180
            array_keys($optionRoutes)
181
        );
182
183
        $sortArr = [];
184
        foreach ($services as $key => $val) {
185
            if ($this->isRelevantForMainPage($val) && !in_array($val['$ref'], $sortArr)) {
186
                $sortArr[$key] = $val['$ref'];
187
            } else {
188
                unset($services[$key]);
189
            }
190
        }
191
192
        // get additional routes
193
        $additionalRoutes = $this->getAdditionalRoutes($sortArr);
194
195
        $services = array_merge($services, $additionalRoutes);
196
197
        array_multisort($sortArr, SORT_ASC, $services);
198
        return $services;
199
    }
200
201
    /**
202
     * gets the additional routes that can be injected by listeners/subscribers
203
     *
204
     * @param array $sortArr array needed for sorting
205
     *
206
     * @return array additional routes
207
     */
208
    private function getAdditionalRoutes(array &$sortArr)
209
    {
210
        $additionalRoutes = [];
211
        $event = new HomepageRenderEvent();
212
        $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...
213
214
        if (!empty($routes)) {
215
            $baseRoute = $this->router->match("/");
216
            $baseUrl = $this->router->generate($baseRoute['_route'], [], UrlGeneratorInterface::ABSOLUTE_URL);
217
            foreach ($routes as $route) {
218
                $thisUrl = $baseUrl.$route['$ref'];
219
                $additionalRoutes[] = [
220
                    '$ref' => $thisUrl,
221
                    'profile' => $baseUrl.$route['profile']
222
                ];
223
                $sortArr[$thisUrl] = $thisUrl;
224
            }
225
        }
226
227
        return $additionalRoutes;
228
    }
229
230
    /**
231
     * Prepares the header field containing information about pagination.
232
     *
233
     * @return string
234
     */
235
    protected function prepareLinkHeader()
236
    {
237
        $links = new LinkHeader(array());
238
        $links->add(
239
            new LinkHeaderItem(
240
                $this->router->generate('graviton.core.rest.app.all', array (), UrlGeneratorInterface::ABSOLUTE_URL),
241
                array ('rel' => 'apps', 'type' => 'application/json')
242
            )
243
        );
244
245
        return (string) $links;
246
    }
247
248
    /**
249
     * tells if a service is relevant for the mainpage
250
     *
251
     * @param array $val value of service spec
252
     *
253
     * @return boolean
254
     */
255
    private function isRelevantForMainPage($val)
256
    {
257
        return (substr($val['$ref'], -1) === '/')
258
            || in_array(parse_url($val['$ref'], PHP_URL_PATH), $this->pathWhitelist);
259
    }
260
261
    /**
262
     * Resolves all third party routes and add schema info
263
     *
264
     * @param array $thirdApiRoutes list of all routes from an API
265
     *
266
     * @return array
267
     */
268
    protected function determineThirdPartyServices(array $thirdApiRoutes)
269
    {
270
        $definition = $this->apiLoader;
271
        $mainRoute = $this->router->generate(
272
            'graviton.core.static.main.all',
273
            array(),
274
            UrlGeneratorInterface::ABSOLUTE_URL
275
        );
276
        $services = array_map(
277
            function ($apiRoute) use ($mainRoute, $definition) {
278
279
                return array (
280
                    '$ref' => $mainRoute.$apiRoute,
281
                    'profile' => $mainRoute."schema/".$apiRoute."/item",
282
                );
283
            },
284
            $thirdApiRoutes
285
        );
286
287
        return $services;
288
    }
289
290
    /**
291
     * Finds configured external apis to be exposed via G2.
292
     *
293
     * @return array
294
     */
295
    private function registerThirdPartyServices()
296
    {
297
        $services = [];
298
299
        // getenv()... it's a workaround for run all tests on travis! will be removed!
300
        if (getenv('USER') !== 'travis' && getenv('HAS_JOSH_K_SEAL_OF_APPROVAL') !== true) {
301
            foreach (array_keys($this->proxySourceConfiguration) as $source) {
302
                foreach ($this->proxySourceConfiguration[$source] as $thirdparty => $option) {
303
                    $this->apiLoader->resetDefinitionLoader();
304
                    $this->apiLoader->setOption($option);
305
                    $this->apiLoader->addOptions($this->decideApiAndEndpoint($option));
306
                    $services[$thirdparty] = $this->determineThirdPartyServices(
307
                        $this->apiLoader->getAllEndpoints(false, true)
308
                    );
309
                }
310
            }
311
        }
312
313
        return $services;
314
    }
315
316
    /**
317
     * get API name and endpoint from the url (third party API)
318
     *
319
     * @param array $config Configuration information ['prefix', 'serviceEndpoint']
320
     *
321
     * @return array
322
     */
323
    protected function decideApiAndEndpoint(array $config)
324
    {
325
        if (array_key_exists('serviceEndpoint', $config)) {
326
            return array (
327
                "apiName" => $config['prefix'],
328
                "endpoint" => $config['serviceEndpoint'],
329
            );
330
        }
331
332
        return [];
333
    }
334
335
    /**
336
     * Return OPTIONS results.
337
     *
338
     * @param Request $request Current http request
339
     *
340
     * @return Response $response Result of the action
341
     */
342
    public function optionsAction(Request $request)
343
    {
344
        $response = $this->response;
345
        $response->setStatusCode(Response::HTTP_NO_CONTENT);
346
        $request->attributes->set('corsMethods', 'GET, OPTIONS');
347
348
        return $response;
349
    }
350
}
351