Completed
Push — master ( 65007d...6786a3 )
by Kenji
03:02
created

CIPHPUnitTestRequest::addCallablePreConstructor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/**
3
 * Part of CI PHPUnit Test
4
 *
5
 * @author     Kenji Suzuki <https://github.com/kenjis>
6
 * @license    MIT License
7
 * @copyright  2015 Kenji Suzuki
8
 * @link       https://github.com/kenjis/ci-phpunit-test
9
 */
10
11
class CIPHPUnitTestRequest
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
12
{
13
	protected $testCase;
14
15
	/**
16
	 * @var CIPHPUnitTestSuperGlobal
17
	 */
18
	protected $superGlobal;
19
20
	/**
21
	 * @var CIPHPUnitTestRouter
22
	 */
23
	protected $router;
24
25
	/**
26
	 * @var callable[] callable called post controller constructor
27
	 */
28
	protected $callables = [];
29
	
30
	/**
31
	 * @var callable[] callable called pre controller constructor
32
	 */
33
	protected $callablePreConstructors = [];
34
35
	protected $enableHooks = false;
36
	
37
	/**
38
	 * @var CI_Hooks
39
	 */
40
	protected $hooks;
41
42
	public function __construct(PHPUnit_Framework_TestCase $testCase)
43
	{
44
		$this->testCase = $testCase;
45
		$this->superGlobal = new CIPHPUnitTestSuperGlobal();
46
		$this->router = new CIPHPUnitTestRouter();
47
	}
48
49
	/**
50
	 * Set HTTP request header
51
	 * 
52
	 * @param string $name  header name
53
	 * @param string $value value
54
	 */
55
	public function setHeader($name, $value)
56
	{
57
		$this->superGlobal->set_SERVER_HttpHeader($name, $value);
58
	}
59
60
	/**
61
	 * Set (and Reset) callable
62
	 * 
63
	 * @param callable $callable function to run after controller instantiation
64
	 */
65
	public function setCallable(callable $callable)
66
	{
67
		$this->callables = [];
68
		$this->callables[] = $callable;
69
	}
70
71
	/**
72
	 * Add callable
73
	 * 
74
	 * @param callable $callable function to run after controller instantiation
75
	 */
76
	public function addCallable(callable $callable)
77
	{
78
		$this->callables[] = $callable;
79
	}
80
81
	/**
82
	 * Set (and Reset) callable pre constructor
83
	 * 
84
	 * @param callable $callable function to run before controller instantiation
85
	 */
86
	public function setCallablePreConstructor(callable $callable)
87
	{
88
		$this->callablePreConstructors = [];
89
		$this->callablePreConstructors[] = $callable;
90
	}
91
92
	/**
93
	 * Add callable pre constructor
94
	 * 
95
	 * @param callable $callable function to run before controller instantiation
96
	 */
97
	public function addCallablePreConstructor(callable $callable)
98
	{
99
		$this->callablePreConstructors[] = $callable;
100
	}
101
102
	/**
103
	 * Enable Hooks for Controllres
104
	 * This enables only pre_controller, post_controller_constructor, post_controller
105
	 */
106
	public function enableHooks()
107
	{
108
		$this->enableHooks = true;
109
		$this->hooks =& load_class('Hooks', 'core');
110
	}
111
112
	/**
113
	 * Request to Controller
114
	 *
115
	 * @param string       $http_method HTTP method
116
	 * @param array|string $argv        array of controller,method,arg|uri
117
	 * @param array|string $params      POST params/GET params|raw_input_stream
118
	 */
119
	public function request($http_method, $argv, $params = [])
0 ignored issues
show
Coding Style introduced by
request uses the super-global variable $_SERVER 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...
120
	{
121
		if (is_string($argv))
122
		{
123
			$argv = ltrim($argv, '/');
124
		}
125
126
		// Set super globals
127
		$_SERVER['REQUEST_METHOD'] = $http_method;
128
		$this->superGlobal->set_GET($argv, $params);
129
		$this->superGlobal->set_POST($params);
130
		$this->superGlobal->set_SERVER_REQUEST_URI($argv);
131
132
		try {
133
			if (is_array($argv))
134
			{
135
				return $this->callControllerMethod(
136
					$http_method, $argv, $params
137
				);
138
			}
139
			else
140
			{
141
				return $this->requestUri($http_method, $argv, $params);
142
			}
143
		}
144
		// redirect()
145
		catch (CIPHPUnitTestRedirectException $e)
146
		{
147
			if ($e->getCode() === 0)
148
			{
149
				set_status_header(200);
150
			}
151
			else
152
			{
153
				set_status_header($e->getCode());
154
			}
155
			$CI =& get_instance();
156
			$CI->output->_status['redirect'] = $e->getMessage();
157
		}
158
		// show_404()
159
		catch (CIPHPUnitTestShow404Exception $e)
160
		{
161
			$this->processError($e);
162
			return $e->getMessage();
163
		}
164
		// show_error()
165
		catch (CIPHPUnitTestShowErrorException $e)
166
		{
167
			$this->processError($e);
168
			return $e->getMessage();
169
		}
170
	}
171
172
	protected function processError(Exception $e)
173
	{
174
		set_status_header($e->getCode());
175
	}
176
177
	/**
178
	 * Call Controller Method
179
	 *
180
	 * @param string       $http_method    HTTP method
181
	 * @param array        $argv           controller, method [, arg1, ...]
182
	 * @param array|string $request_params POST params/GET params|raw_input_stream
183
	 */
184
	protected function callControllerMethod($http_method, $argv, $request_params)
0 ignored issues
show
Unused Code introduced by
The parameter $http_method 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...
Coding Style introduced by
callControllerMethod uses the super-global variable $_SERVER 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...
185
	{
186
		$_SERVER['argv'] = array_merge(['index.php'], $argv);
187
188
		$class  = ucfirst($argv[0]);
189
		$method = $argv[1];
190
191
		// Remove controller and method
192
		array_shift($argv);
193
		array_shift($argv);
194
195
//		$request = [
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
196
//			'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
197
//			'class' => $class,
198
//			'method' => $method,
199
//			'params' => $argv,
200
//			'$_GET' => $_GET,
201
//			'$_POST' => $_POST,
202
//		];
203
//		var_dump($request, $_SERVER['argv']);
204
205
		// Reset CodeIgniter instance state
206
		reset_instance();
207
208
		$this->setRawInputStream($request_params);
209
210
		// 404 checking
211
		if (! class_exists($class) || ! method_exists($class, $method))
212
		{
213
			// If 404, CodeIgniter instance is not created yet. So create it here
214
			// Because we need CI->output->_status to store info
215
			$CI =& get_instance();
216
			if ($CI instanceof CIPHPUnitTestNullCodeIgniter)
217
			{
218
				new CI_Controller();
219
			}
220
221
			show_404($class.'::'.$method . '() is not found');
222
		}
223
224
		$params = $argv;
225
226
		return $this->createAndCallController($class, $method, $params);
227
	}
228
229
	/**
230
	 * Request to URI
231
	 *
232
	 * @param string       $http_method    HTTP method
233
	 * @param string       $uri            URI string
234
	 * @param array|string $request_params POST params/GET params|raw_input_stream
235
	 */
236
	protected function requestUri($http_method, $uri, $request_params)
0 ignored issues
show
Unused Code introduced by
The parameter $http_method 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...
Coding Style introduced by
requestUri uses the super-global variable $_SERVER 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...
237
	{
238
		$_SERVER['argv'] = ['index.php', $uri];
239
240
		// Force cli mode because if not, it changes URI (and RTR) behavior
241
		$cli = is_cli();
242
		set_is_cli(TRUE);
0 ignored issues
show
Unused Code introduced by
The call to the function set_is_cli() seems unnecessary as the function has no side-effects.
Loading history...
243
244
		// Reset CodeIgniter instance state
245
		reset_instance();
246
247
		$this->setRawInputStream($request_params);
248
249
		// Get route
250
		list($class, $method, $params) = $this->router->getRoute();
251
252
		// Restore cli mode
253
		set_is_cli($cli);
0 ignored issues
show
Unused Code introduced by
The call to the function set_is_cli() seems unnecessary as the function has no side-effects.
Loading history...
254
255
//		$request = [
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
256
//			'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
257
//			'class' => $class,
258
//			'method' => $method,
259
//			'params' => $params,
260
//			'$_GET' => $_GET,
261
//			'$_POST' => $_POST,
262
//		];
263
//		var_dump($request, $_SERVER['argv']);
264
265
		return $this->createAndCallController($class, $method, $params);
266
	}
267
268
	protected function callHook($hook)
269
	{
270
		if ($this->enableHooks)
271
		{
272
			$this->hooks->call_hook($hook);
273
		}
274
	}
275
276
	protected function setRawInputStream($string)
277
	{
278
		if (is_string($string))
279
		{
280
			$INPUT =& load_class('Input', 'core');
281
			CIPHPUnitTestReflection::setPrivateProperty(
282
				$INPUT,
283
				'_raw_input_stream',
284
				$string
285
			);
286
		}
287
	}
288
289
	protected function createAndCallController($class, $method, $params)
290
	{
291
		ob_start();
292
293
		$this->callHook('pre_controller');
294
295
		// Run callablePreConstructor
296
		if ($this->callablePreConstructors !== [])
297
		{
298
			foreach ($this->callablePreConstructors as $callable)
299
			{
300
				$callable();
301
			}
302
		}
303
304
		// Create controller
305
		$controller = new $class;
306
		$CI =& get_instance();
307
308
		// Set CodeIgniter instance to TestCase
309
		$this->testCase->setCI($CI);
310
311
		// Set default response code 200
312
		set_status_header(200);
313
		// Run callable
314
		if ($this->callables !== [])
315
		{
316
			foreach ($this->callables as $callable)
317
			{
318
				$callable($CI);
319
			}
320
		}
321
322
		$this->callHook('post_controller_constructor');
323
324
		// Call controller method
325
		call_user_func_array([$controller, $method], $params);
326
		$output = ob_get_clean();
327
328
		if ($output == '')
329
		{
330
			$output = $CI->output->get_output();
331
		}
332
333
		$this->callHook('post_controller');
334
335
		return $output;
336
	}
337
338
	/**
339
	 * Get HTTP Status Code Info
340
	 * 
341
	 * @return array ['code' => code, 'text' => text]
342
	 * @throws LogicException
343
	 */
344
	public function getStatus()
345
	{
346
		$CI =& get_instance();
347
		if (! isset($CI->output->_status))
348
		{
349
			throw new LogicException('Status code is not set. You must call $this->request() first');
350
		}
351
352
		return $CI->output->_status;
353
	}
354
}
355