Completed
Pull Request — master (#505)
by Philipp
03:31
created

ProfilerController::explainAction()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 35
Code Lines 21

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 35
rs 8.439
cc 5
eloc 21
nc 7
nop 3
1
<?php
2
3
/*
4
 * This file is part of the Doctrine Bundle
5
 *
6
 * The code was originally distributed inside the Symfony framework.
7
 *
8
 * (c) Fabien Potencier <[email protected]>
9
 * (c) Doctrine Project, Benjamin Eberlei <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Doctrine\Bundle\DoctrineBundle\Controller;
16
17
use Doctrine\DBAL\Connection;
18
use Doctrine\DBAL\Platforms\SQLServerPlatform;
19
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
20
use Symfony\Component\DependencyInjection\ContainerInterface;
21
use Symfony\Component\HttpFoundation\Response;
22
23
/**
24
 * ProfilerController.
25
 *
26
 * @author Christophe Coevoet <[email protected]>
27
 */
28
class ProfilerController implements ContainerAwareInterface
29
{
30
    /**
31
     * @var ContainerInterface
32
     */
33
    private $container;
34
35
    /**
36
     * {@inheritDoc}
37
     */
38
    public function setContainer(ContainerInterface $container = null)
39
    {
40
        $this->container = $container;
41
    }
42
43
    /**
44
     * Renders the profiler panel for the given token.
45
     *
46
     * @param string  $token          The profiler token
47
     * @param string  $connectionName
48
     * @param integer $query
49
     *
50
     * @return Response A Response instance
51
     */
52
    public function explainAction($token, $connectionName, $query)
53
    {
54
        /** @var $profiler \Symfony\Component\HttpKernel\Profiler\Profiler */
55
        $profiler = $this->container->get('profiler');
56
        $profiler->disable();
57
58
        $profile = $profiler->loadProfile($token);
59
        $queries = $profile->getCollector('db')->getQueries();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\HttpKe...\DataCollectorInterface as the method getQueries() does only exist in the following implementations of said interface: Doctrine\Bundle\Doctrine...r\DoctrineDataCollector, Symfony\Bridge\Doctrine\...r\DoctrineDataCollector.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
60
61
        if (!isset($queries[$connectionName][$query])) {
62
            return new Response('This query does not exist.');
63
        }
64
65
        $query = $queries[$connectionName][$query];
66
        if (!$query['explainable']) {
67
            return new Response('This query cannot be explained.');
68
        }
69
70
        /** @var $connection \Doctrine\DBAL\Connection */
71
        $connection = $this->container->get('doctrine')->getConnection($connectionName);
72
        try {
73
            if ($connection->getDatabasePlatform() instanceof SQLServerPlatform) {
74
                $results = $this->explainSQLServerPlatform($connection, $query);
75
            } else {
76
                $results = $this->explainOtherPlatform($connection, $query);
77
            }
78
        } catch (\Exception $e) {
79
            return new Response('This query cannot be explained.');
80
        }
81
82
        return $this->container->get('templating')->renderResponse('@Doctrine/Collector/explain.html.twig', array(
83
            'data' => $results,
84
            'query' => $query,
85
        ));
86
    }
87
88
    private function explainSQLServerPlatform(Connection $connection, $query)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
89
    {
90
        if (stripos($query['sql'], 'SELECT') === 0) {
91
            $sql = 'SET STATISTICS PROFILE ON; ' . $query['sql'] . '; SET STATISTICS PROFILE OFF;';
92
        } else {
93
            $sql = 'SET SHOWPLAN_TEXT ON; GO; SET NOEXEC ON; ' . $query['sql'] .'; SET NOEXEC OFF; GO; SET SHOWPLAN_TEXT OFF;';
94
        }
95
        $stmt = $connection->executeQuery($sql, $query['params'], $query['types']);
96
        $stmt->nextRowset();
97
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
98
    }
99
100
    private function explainOtherPlatform(Connection $connection, $query)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
101
    {
102
        return $connection->executeQuery('EXPLAIN '.$query['sql'], $query['params'], $query['types'])
103
            ->fetchAll(\PDO::FETCH_ASSOC);
104
    }
105
}
106