A WordPress specific solution to the empty headers in CLI mode issue.
What’s the problem with headers?
While developing the WordPress functional module for wp-browser I came across the need to make requests to WordPress and test its output headers.
The typical case is the one where the test does not see the user log in but still hitting the /wp-admin/index.php
file: the auth_redirect
function will fire and in turn the wp_redirect
one will set a new Location
header to redirect the user to the /wp-logig.php
file.
Then auth_redirect
will stop the request handling with an exit()
call.
The issue with this is that functional tests are meant to run in the console and calls to the headers_list()
function will always return an empty array when called in CLI mode.
The code below will have different outcomes depending on it running in CLI mode or not:
header('One: one');
header('Two: two');
header('Three: three');
// in CLI mode `$headers` will be empty!
$headers = headers_list();
So the problems lies in tests running, by definition, in CLI mode…
A workaround specific to WordPress
While WordPress reliance on calls to the header
, exit
and die
functions makes tracking headers in tests impossible the uniformity of the calls are makes it easy to work around the issue.
All the calls to the header()
function will happen in the wp_redirect
function and that is a “pluggable” function; in WordPress terms it means the function is wrapped in a conditional block:
if( !function_exists('wp_redirect')) {
function wp_redirect( $location = '', $status = 302) {
// ...
}
}
I’ve copied the function body to a file included before WordPress is loaded and added a line taking care of storing the just set header in a variable that I will be later able to read even in CLI mode:
<?php
global $cli_headers;
/**
* An associative array in the [header => status] format.
*/
$cli_headers = [];
/**
* Copy and paste of WordPress original function where headers are but stored
* before sending to avoid CLI limitations.
*
* @param $location
* @param int $status
* @return bool
*/
function wp_redirect($location, $status = 302)
{
// [original function code omitted]
// this is the call the original function will do
header("Location: $location", true, $status);
// store the just set header in the global to retrieve it later
global $cli_headers;
$cli_headers["Location: $location"] = $status;
return true;
}
Later in my test code headers can now be fetched accessing the $cli_headers
global variable.