Failed Conditions
Pull Request — master (#1)
by Arnold
05:51
created

Module::initContainer()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
crap 2
1
<?php
2
3
namespace Jasny\Codeception;
4
5
use Jasny\Router;
6
use Codeception\Configuration;
7
use Codeception\Lib\Framework;
8
use Codeception\TestInterface;
9
use Jasny\Codeception\Connector;
10
use Psr\Http\Message\ServerRequestInterface;
11
use Psr\Http\Message\ResponseInterface;
12
use Interop\Container\ContainerInterface;
13
14
/**
15
 * Module for running functional tests using Jasny MVC
16
 */
17
class Module extends Framework
18
{
19
    /**
20
     * Required configuration fields
21
     * @var array
22
     */
23
    protected $requiredFields = ['container'];
24
    
25
    /**
26
     * @var Router
27
     */
28
    public $router;
29
    
30
    /**
31
     * @var ServerRequestInterface 
32
     */
33
    public $baseRequest;
34
    
35
    /**
36
     * @var ResponseInterface 
37
     */
38
    public $baseResponse;
39
    
40
    
41
    /**
42
     * Load the container by including the file.
43
     * @codeCoverageIgnore
44
     * 
45
     * @param string $file
46
     * @return ContainerInterface
47
     */
48
    protected function loadContainer($file)
49
    {
50
        return include $file;
51
    }
52
    
53
    /**
54
     * Get the container.
55
     * 
56
     * @return ContainerInterface
57
     */
58 3
    protected function initContainer()
59
    {
60 3
        $container = $this->loadContainer(Configuration::projectDir() . $this->config['container']);
61
62 3
        if (!$container instanceof ContainerInterface) {
63 1
            throw new \UnexpectedValueException("Failed to get a container from '{$this->config['container']}'");
64
        }
65
66 2
        return $container;
67
    }
68
    
69
    /**
70
     * Check if the response writes to the output buffer
71
     * 
72
     * @return boolean
73
     */
74 7
    protected function usesOutputBuffer()
75
    {
76 7
        return isset($this->baseResponse) && $this->baseResponse->getBody()->getMetadata('uri') === 'php://output';
77
    }
78
    
79
    /**
80
     * Enable output buffering
81
     * 
82
     * @throws \RuntimeException
83
     */
84 2
    protected function startOutputBuffering()
85
    {
86 2
        if ($this->obGetLevel() === 0) {
87 2
            $this->obStart();
88 2
        }
89
90 2
        if ($this->obGetLevel() < 1) {
91 1
            throw new \RuntimeException("Failed to start output buffering");
92
        }
93 1
    }
94
    
95
    /**
96
     * Disable output buffering
97
     */
98 1
    protected function stopOutputBuffering()
99
    {
100 1
        $this->obClean();
101 1
    }
102
103
    
104
    /**
105
     * Initialize the module
106
     */
107 3
    public function _initialize()
108
    {
109 3
        $container = $this->initContainer();
110
        
111 2
        $this->router = $container->get(Router::class);
112
113 2
        if ($container->has(ServerRequestInterface::class)) {
114 1
            $this->baseRequest = $container->get(ServerRequestInterface::class);
115 1
        }
116
        
117 2
        if ($container->has(ResponseInterface::class)) {
118 1
            $this->baseResponse = $container->get(ResponseInterface::class);
119 1
        }
120 2
    }
121
    
122
    /**
123
     * Call before suite
124
     * 
125
     * @param array $settings
126
     */
127 4
    public function _beforeSuite($settings = [])
128
    {
129 4
        parent::_beforeSuite($settings);
130
        
131 4
        if ($this->usesOutputBuffer()) {
132 2
            $this->startOutputBuffering();
133 1
        }
134 3
    }
135
    
136
    /**
137
     * Call after suite
138
     */
139 3
    public function _afterSuite()
140
    {
141 3
        if ($this->usesOutputBuffer()) {
142 1
            $this->stopOutputBuffering();
143 1
        }
144
        
145 3
        parent::_afterSuite();
146 3
    }
147
    
148
    /**
149
     * Before each test
150
     * 
151
     * @param TestInterface $test
152
     */
153 4
    public function _before(TestInterface $test)
154
    {
155 4
        $this->client = new Connector();
156 4
        $this->client->setRouter($this->router);
157
158 4
        if (isset($this->baseRequest)) {
159 2
            $this->client->setBaseRequest($this->baseRequest);
160 2
        }
161
        
162 4
        if (isset($this->baseResponse)) {
163 2
            $this->client->setBaseResponse($this->baseResponse);
164 2
        }
165
        
166 4
        parent::_before($test);
167 4
    }
168
    
169
    /**
170
     * After each test
171
     * 
172
     * @param TestInterface $test
173
     */
174 3
    public function _after(TestInterface $test)
175
    {
176 3
        if ($this->sessionStatus() === PHP_SESSION_ACTIVE) {
177 1
            $this->sessionAbort();
178 1
        }
179
180 3
        if (isset($this->client)) {
181 1
            $this->client->reset();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\BrowserKit\Client as the method reset() does only exist in the following sub-classes of Symfony\Component\BrowserKit\Client: Jasny\Codeception\Connector. 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...
182
            
183 1
            if (isset($this->baseRequest)) {
184 1
                $this->baseRequest = $this->client->getBaseRequest();
0 ignored issues
show
Bug introduced by
The method getBaseRequest() does not exist on Symfony\Component\BrowserKit\Client. Did you maybe mean request()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
185 1
            }
186
            
187 1
            if (isset($this->baseResponse)) {
188 1
                $this->baseResponse = $this->client->getBaseResponse();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\BrowserKit\Client as the method getBaseResponse() does only exist in the following sub-classes of Symfony\Component\BrowserKit\Client: Jasny\Codeception\Connector. 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...
189 1
            }
190 1
        }
191
192
193 3
        parent::_after($test);
194 3
    }
195
    
196
    
197
    /**
198
     * Wrapper around `ob_start()`
199
     * @codeCoverageIgnore
200
     */
201
    protected function obStart()
202
    {
203
        ob_start();
204
    }
205
    
206
    /**
207
     * Wrapper around `ob_get_level()`
208
     * @codeCoverageIgnore
209
     * 
210
     * @return int
211
     */
212
    protected function obGetLevel()
213
    {
214
        return ob_get_level();
215
    }
216
    
217
    /**
218
     * Wrapper around `ob_clean()`
219
     * @codeCoverageIgnore
220
     */
221
    protected function obClean()
222
    {
223
        ob_clean();
224
    }
225
    
226
    /**
227
     * Wrapper around `session_status()`
228
     * @codeCoverageIgnore
229
     * 
230
     * @return int
231
     */
232
    protected function sessionStatus()
233
    {
234
        return session_status();
235
    }
236
    
237
    /**
238
     * Wrapper around `session_abort()`
239
     * @codeCoverageIgnore
240
     */
241
    protected function sessionAbort()
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...
242
    {
243
        return session_abort();
244
    }
245
}
246