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