Completed
Pull Request — master (#532)
by
unknown
03:36
created

ProfilerController::explainAction()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 37
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 37
rs 8.439
cc 6
eloc 23
nc 10
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\Driver\ASE\ASEStatement;
19
use Doctrine\DBAL\Platforms\ASEPlatform;
20
use Doctrine\DBAL\Platforms\SQLServerPlatform;
21
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
22
use Symfony\Component\DependencyInjection\ContainerInterface;
23
use Symfony\Component\HttpFoundation\Response;
24
25
/**
26
 * ProfilerController.
27
 *
28
 * @author Christophe Coevoet <[email protected]>
29
 */
30
class ProfilerController implements ContainerAwareInterface
31
{
32
    /**
33
     * @var ContainerInterface
34
     */
35
    private $container;
36
37
    /**
38
     * {@inheritDoc}
39
     */
40
    public function setContainer(ContainerInterface $container = null)
41
    {
42
        $this->container = $container;
43
    }
44
45
    /**
46
     * Renders the profiler panel for the given token.
47
     *
48
     * @param string  $token          The profiler token
49
     * @param string  $connectionName
50
     * @param integer $query
51
     *
52
     * @return Response A Response instance
53
     */
54
    public function explainAction($token, $connectionName, $query)
55
    {
56
        /** @var $profiler \Symfony\Component\HttpKernel\Profiler\Profiler */
57
        $profiler = $this->container->get('profiler');
58
        $profiler->disable();
59
60
        $profile = $profiler->loadProfile($token);
61
        $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...
62
63
        if (!isset($queries[$connectionName][$query])) {
64
            return new Response('This query does not exist.');
65
        }
66
67
        $query = $queries[$connectionName][$query];
68
        if (!$query['explainable']) {
69
            return new Response('This query cannot be explained.');
70
        }
71
72
        /** @var $connection \Doctrine\DBAL\Connection */
73
        $connection = $this->container->get('doctrine')->getConnection($connectionName);
74
        try {
75
            if ($connection->getDatabasePlatform() instanceof SQLServerPlatform) {
76
                $results = $this->explainSQLServerPlatform($connection, $query);
77
            } else if ($connection->getDatabasePlatform() instanceof ASEPlatform) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Platforms\ASEPlatform does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
78
                $results = $this->explainASEServerPlatform($connection, $query);
79
            } else {
80
                $results = $this->explainOtherPlatform($connection, $query);
81
            }
82
        } catch (\Exception $e) {
83
            return new Response('This query cannot be explained.');
84
        }
85
86
        return $this->container->get('templating')->renderResponse('@Doctrine/Collector/explain.html.twig', array(
87
            'data' => $results,
88
            'query' => $query,
89
        ));
90
    }
91
92
    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...
93
    {
94
        if (stripos($query['sql'], 'SELECT') === 0) {
95
            $sql = 'SET STATISTICS PROFILE ON; ' . $query['sql'] . '; SET STATISTICS PROFILE OFF;';
96
        } else {
97
            $sql = 'SET SHOWPLAN_TEXT ON; GO; SET NOEXEC ON; ' . $query['sql'] .'; SET NOEXEC OFF; GO; SET SHOWPLAN_TEXT OFF;';
98
        }
99
        $stmt = $connection->executeQuery($sql, $query['params'], $query['types']);
100
        $stmt->nextRowset();
101
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
102
    }
103
104
    private function explainASEServerPlatform(Connection $connection, $query)
105
    {
106
        $sql = 'SET SHOWPLAN ON SET NOEXEC ON ' . $query['sql'] .' SET NOEXEC OFF SET SHOWPLAN OFF';
107
        $stmt = $connection->executeQuery($sql, $query['params'], $query['types']);
108
109
        if ($stmt instanceof ASEStatement) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Driver\ASE\ASEStatement does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
110
            $data = $stmt->getMessages();
111
            $data = implode("", $data);
112
            return array(array($data));
113
        }
114
115
        return array();
116
    }
117
118
    private function explainOtherPlatform(Connection $connection, $query)
119
    {
120
        return $connection->executeQuery('EXPLAIN '.$query['sql'], $query['params'], $query['types'])
121
            ->fetchAll(\PDO::FETCH_ASSOC);
122
    }
123
}
124