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

MainController::getAdditionalRoutes()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 0
cts 19
cp 0
rs 9.3142
c 0
b 0
f 0
cc 3
eloc 14
nc 2
nop 1
crap 12
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