Developers
Requests
Everything a browser sends arrives through one object. The Request facade
resolves Hubzero\Http\Request,
which extends Symfony's Request and adds the typed accessors the CMS uses.
You reach for it for one reason: it hands you a value of the type you asked for, or the default, with no branch of your own for "absent", "an array arrived where a scalar was expected", or "somebody typed a word into a number field".
use Request;
$id = Request::getInt('id', 0);
That line cannot return anything but an integer.
Never read the superglobals
$_GET, $_POST and $_REQUEST are not the request. Three concrete
things go wrong.
The router writes into the request object, not into $_GET. On a hub
with search-engine-friendly URLs, /bookings/instrument/12 is parsed by the
router, which puts the result back with setVar($key, $val, 'get') —
Symfony's query bag. So:
foreach ($this->app['router']->parse($request->getUri()) as $key => $val)
{
$request->setVar($key, $val, 'get');
}
$view = $_GET['view']; // undefined index — the URL had no query string
$view = Request::getCmd('view', ''); // 'instrument'
Every menu-routed and SEF-routed page in the tree behaves this way. Code
reading $_GET works on the developer's machine with index.php?option=…
in the URL and breaks on the hub.
$_REQUEST merges sources. A value you meant to accept only from a
posted form can then be supplied in the query string, which is a link
someone can send to a member. The typed accessors take a $hash argument
precisely so you can refuse that.
There is no superglobal outside the web. A muse
command, a cron run, and a test all build a request object without a browser
behind it. Code that reads $_POST cannot run in any of them.
Reading input
Ask for the type you want. Each accessor takes the key, a default, and optionally which part of the request to look in:
use Request;
$id = Request::getInt('id', 0);
$alias = Request::getCmd('alias', '');
$title = Request::getString('title', '', 'post');
$fields = Request::getArray('fields', array(), 'post');
| Method | Returns |
|---|---|
getInt($key, $default = 0, $hash = 'input') |
A leading integer, or the default |
getUInt($key, $default = 0, $hash = 'input') |
The same, made positive |
getFloat($key, $default = 0.0, $hash = 'input') |
A leading decimal number, or the default |
getBool($key, $default = null, $hash = 'input') |
The value cast to boolean |
getWord($key, $default = null, $hash = 'input') |
Letters and underscores only |
getCmd($key, $default = null, $hash = 'input') |
Letters, digits, _, ., -; leading dots stripped |
getString($key, $default = null, $hash = 'input') |
The value as a string |
getArray($key, $default = array(), $hash = 'input') |
The value as an array |
getSimpleArray($key, $default = array(), $hash = 'input') |
The same, with nested arrays dropped |
getVar($key, $default = null, $hash = 'input', ...) |
The raw value, unfiltered |
Which of them to trust with what
getInt(), getUInt(), getFloat() and getString() are the four to
prefer. Each returns its declared type, and each returns the default you
gave when the key is absent.
getInt() and getFloat() match only a leading number. That is a
correction made in this repository, not inherited behaviour: the match used
to be unanchored, so getInt() scavenged digits out of the middle of
arbitrary text and an email address typed into a member-id field —
jesus1993coral@gmail.com — resolved to user 1993. getFloat() was
anchored to match. Both now return the default rather than a number from the
middle of a string.
getCmd() is what task, view and layout names are read with, because its
filter is exactly the character set those may use. Two things about it and
getWord():
- A falsy default does not survive.
Request::getCmd('layout', null)returns'', notnull, because the filter runs over$result ?: ''. Test against''. - The filter strips rather than rejects.
getCmd('view', '')onview=../../etcreturnsetc— the slashes are removed and the leading dots trimmed. Nothing was refused; a name came back that the caller never sent. It is a filter, not a validator, so compare the result against the names you accept.
getBool() is a plain (bool) cast of whatever arrived. A checkbox that
posts nothing when unticked works; a field carrying the string "false" or
"off" comes back true, because a non-empty string is truthy. For
tri-state values read a getCmd() or a getInt() and decide yourself.
getArray() casts, so a scalar submitted where your form expects an array
arrives as a one-element array rather than an error. Validate the shape
before you set() it on a model.
Where to look
The $hash argument names the part of the request:
$hash |
Source |
|---|---|
input (default) |
The method's own bag, falling back to the query string |
post |
The request body |
get |
The query string |
cookie |
Cookies |
server |
Server variables |
files |
$_FILES, returned as the raw array |
Naming it narrows what an attacker can supply. A task that must be posted
should read its fields with 'post', so the same names in a query string
are ignored.
Files, cookies, headers
$file = Request::file('upload'); // one uploaded file
$value = Request::cookie('name', $default);
$agent = Request::header('User-Agent');
$uri = Request::server('REQUEST_URI');
has($key) reports whether a key is present and non-empty, and accepts
several keys at once, returning true only if all are present.
The form token
Every task that changes something must check the token first:
Request::checkToken();
It looks in POST by default, delegates to the
session, and aborts the request with a 403 when
the token is missing or wrong. Pass 'get' or a comma-separated list to
look elsewhere.
Omitting it is the most common security fault in this tree. The task keeps working, so nothing tells you: it just also works when a member is walked onto a page that posts to it from somewhere else.
Request::checkHoneypot() is its companion for public forms: it validates
the hidden field emitted by Hubzero\Spam\Honeypot and returns false —
logging to the spam log — when the form was filled in too fast or by a
robot. Unlike checkToken() it returns rather than aborting, so test the
result.
Remembering state
List screens keep their filters across requests, so that a member who opens
a booking, then goes back, finds the list as they left it. getState()
reads a value from the request if it is there and remembers it, and returns
the remembered value when it is not:
$search = Request::getState(
$this->_option . '.instruments.search',
'search',
''
);
The first argument is the key it is stored under, the second the request
variable, then a default and an optional type (int, word, cmd,
bool, float, string, array). The store is the user's session
registry, so the value outlives the request.
Namespace the key with the component and the screen, as above. The registry
is shared by every extension on the hub, and two screens that both call
their key search will read each other's filter.
What the request knows about itself
| Method | Returns |
|---|---|
method() |
GET, POST, … |
root($pathonly = false) |
The application root, with administrator/api trimmed off |
base($pathonly = false) |
The base URL; pass true for the path alone |
current($query = false) |
The current URL, without the query string unless asked |
path() |
The path portion, always starting with / |
segment($index, $default = null) / segments() |
Path segments, 1-based |
scheme() / host() / ip() |
Where the request came from |
secure() |
Whether it arrived over HTTPS |
ajax() |
Whether it is an XMLHttpRequest |
if (Request::ajax())
{
Response::header('Content-Type', 'application/json');
echo json_encode($data);
App::close();
}
setVar($name, $value, $hash = 'method', $overwrite = true) writes into the
request. The router uses it, and so may a controller handing a value to a
view it is about to dispatch. Never use it as a way of passing data around
inside your own code: a value written there is visible to every later stage
of the request, including plugins you did not write.
Rewritten and checked against 2.4-main @ 348f0057c2 on 2026-09-10.