Completed
Pull Request — master (#47)
by Robbie
01:29
created

ControllerExtension::beforeCallActionHandler()   C

Complexity

Conditions 8
Paths 5

Size

Total Lines 34
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 34
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 19
nc 5
nop 2
1
<?php
2
3
namespace LeKoala\DebugBar\Extension;
4
5
use LeKoala\DebugBar\Collector\SilverStripeCollector;
6
use LeKoala\DebugBar\DebugBar;
7
use SilverStripe\Control\Controller;
8
use SilverStripe\Core\Extension;
9
use SilverStripe\Security\Security;
10
11
/**
12
 * A controller extension to log times and render the Debug Bar
13
 */
14
class ControllerExtension extends Extension
15
{
16 View Code Duplication
    public function onBeforeInit()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
17
    {
18
        $class = get_class($this->owner);
19
20
        DebugBar::withDebugBar(function (\DebugBar\DebugBar $debugbar) use ($class) {
21
            // We must set the current controller when it's available and before it's pushed out of stack
22
            $debugbar->getCollector('silverstripe')->setController(Controller::curr());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface DebugBar\DataCollector\DataCollectorInterface as the method setController() does only exist in the following implementations of said interface: LeKoala\DebugBar\Collector\SilverStripeCollector.

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...
23
24
            /** @var $timeData DebugBar\DataCollector\TimeDataCollector */
25
            $timeData = $debugbar['time'];
26
            if (!$timeData) {
27
                return;
28
            }
29
            if ($timeData->hasStartedMeasure('pre_request')) {
30
                $timeData->stopMeasure("pre_request");
31
            }
32
            $timeData->startMeasure("init", "$class init");
33
        });
34
    }
35
36
    public function onAfterInit()
37
    {
38
        // On Security, onAfterInit is called before init() in your Page method
39
        // jQuery is most likely not included yet
40
        if (!$this->owner instanceof Security) {
41
            DebugBar::includeRequirements();
42
        }
43
44
        $class = get_class($this->owner);
45
46
        DebugBar::withDebugBar(function (\DebugBar\DebugBar $debugbar) use ($class) {
47
48
            /** @var $timeData DebugBar\DataCollector\TimeDataCollector */
49
            $timeData = $debugbar['time'];
50
            if (!$timeData) {
51
                return;
52
            }
53
            if ($timeData->hasStartedMeasure("cms_init")) {
54
                $timeData->stopMeasure("cms_init");
55
            }
56
            if ($timeData->hasStartedMeasure("init")) {
57
                $timeData->stopMeasure("init");
58
            }
59
            $timeData->startMeasure("handle", "$class handle request");
60
        });
61
    }
62
63
    /**
64
     * Due to a bug, this could be called twice before 4.0,
65
     * see https://github.com/silverstripe/silverstripe-framework/pull/5173
66
     *
67
     * @param HTTPRequest $request
68
     * @param string $action
69
     */
70
    public function beforeCallActionHandler($request, $action)
71
    {
72
        // This could be called twice
73
        if ($this->owner->beforeCallActionHandlerCalled) {
74
            return;
75
        }
76
77
        // If we don't have an action, getViewer will be called immediatly
78
        // If we have custom routes, request action is different than action
79
        $allParams     = $request->allParams();
80
        $requestAction = null;
81
        if (!empty($allParams['Action'])) {
82
            $requestAction = $allParams['Action'];
83
        }
84
        if (!$this->owner->hasMethod($action) || ($requestAction && $requestAction
85
            != $action)) {
86
            self::clearBuffer();
87
        }
88
89
        $class = get_class($this->owner);
90
        DebugBar::withDebugBar(function (\DebugBar\DebugBar $debugbar) use ($class, $action) {
91
            /* @var $timeData DebugBar\DataCollector\TimeDataCollector */
92
            $timeData = $debugbar['time'];
93
            if (!$timeData) {
94
                return;
95
            }
96
            if ($timeData->hasStartedMeasure("handle")) {
97
                $timeData->stopMeasure("handle");
98
            }
99
            $timeData->startMeasure("action", "$class action $action");
100
        });
101
102
        $this->owner->beforeCallActionHandlerCalled = true;
103
    }
104
105
    /**
106
     * Due to a bug, this is not always called before 4.0,
107
     * see https://github.com/silverstripe/silverstripe-framework/pull/5173
108
     *
109
     * @param HTTPRequest $request
110
     * @param string $action
111
     * @param mixed $result (only in v4.0)
112
     */
113 View Code Duplication
    public function afterCallActionHandler($request, $action, $result)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $result is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
114
    {
115
        self::clearBuffer();
116
117
        $class = get_class($this->owner);
118
        DebugBar::withDebugBar(function (\DebugBar\DebugBar $debugbar) use ($class, $action) {
119
            /* @var $timeData DebugBar\DataCollector\TimeDataCollector */
120
            $timeData = $debugbar['time'];
121
            if (!$timeData) {
122
                return;
123
            }
124
            if ($timeData->hasStartedMeasure("action")) {
125
                $timeData->stopMeasure("action");
126
            }
127
            $timeData->startMeasure(
128
                "after_action",
129
                "$class after action $action"
130
            );
131
        });
132
    }
133
134
    protected static function clearBuffer()
0 ignored issues
show
Coding Style introduced by
clearBuffer uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
135
    {
136
        if (!DebugBar::$bufferingEnabled) {
137
            return;
138
        }
139
        $buffer = ob_get_clean();
140
        if (!empty($buffer)) {
141
            unset($_REQUEST['debug_request']); // Disable further messages that we can't intercept
142
            SilverStripeCollector::setDebugData($buffer);
143
        }
144
    }
145
}
146