Developers
Upgrade guide
Before 2.0 the platform used an older set of class names, and 2.0 replaced
most of them. An extension written against JFactory, JText and
JRequest still needs converting. This page is that
translation table, plus what to do about the database when you upgrade a
hub.
This page is for inherited code only. Read it when you have an old
extension to bring forward, or when you are reading a file in this tree that
still uses the old names — there are plenty. Do not use it as a menu. Nothing
new should be written against the legacy names, and nothing new should reach
into the container where a facade exists: write User::get('id'), not
JFactory::getUser() and not App::get('user')->get('id'). The container
call is correct and works; it is simply the long way round, and it is what
the right-hand column of the table below is translating away from. Use
App::get(...) only for the few services that have no facade — the database
driver is the one you will meet — as Facades
sets out.
The conversion is mechanical enough to do in an afternoon for a small
extension. The order that wastes least time: rename the classes, add the
use lines for every facade the file now names, run
php -l over the result, then run
php tools/lint/missing-facade-imports.php <your path> to catch the imports
you missed. That last step is not optional — a missing import is the one
mistake in this list that neither PHP nor your eyes will find.
For the Hubzero 1.x names that changed at the same time, see Release notes.
Directory structure
The tree has two top-level directories:
/app
/core
index.php
core/ is the platform — the framework library, the shipped components,
plugins, modules and templates, and the migrations. app/ is one hub's
own state: its configuration, its overrides, its logs, cache and uploads.
index.php at the root is the only entry point. There is no
administrator/ directory and no api/ directory; both applications are
served from the same front controller.
Structure covers the layout in full.
Constants
| Legacy name | Hubzero |
|---|---|
JPATH_ROOT, JPATH_BASE, JPATH_SITE |
PATH_ROOT |
JPATH_ADMINISTRATOR |
Nothing. The directory does not exist. |
JPATH_COMPONENT |
Component::path($option) |
_JEXEC |
_HZEXEC_ |
| n/a | PATH_APP — ROOT/app, this hub's own data |
| n/a | PATH_CORE — ROOT/core, the framework and shipped extensions |
When including a file from within the same extension, prefer PHP's own
__DIR__ and __FILE__ over any constant:
// This file is ROOT/app/components/com_example/admin/example.php
// dirname(__DIR__) moves up one level, to com_example
require_once dirname(__DIR__) . DS . 'models' . DS . 'foo.php';
require_once __DIR__ . DS . 'controllers' . DS . 'example.php';
Better still, do not include anything. The class loader resolves
Components\Example\Models\Foo to that path on its own; see
Constants and
Structure.
Classes
Most of the conversions are a facade with the J dropped. Facades are
registered as root-namespace aliases, so a namespaced file must import the
one it uses — use Route;. Leave the import out and the name resolves
inside your own namespace instead, which usually still works by accident and
fails outright on the API and CLI clients, in any namespace that already
contains a class of that name, and in anything that runs before the
application has loaded. See
Facades.
JRoute
| Legacy name | Hubzero |
|---|---|
JRoute::_($url) |
Route::url($url) |
JText
Lang replaces JText, and _() and sprintf() merged into one
Lang::txt() that takes a variable number of arguments. Pass more than
one and the translator does the replacement.
// Language file
COM_EXAMPLE_HELLO="Hello!"
COM_EXAMPLE_HELLO_NAME="Hello, %s!"
// Outputs 'Hello!'
echo Lang::txt('COM_EXAMPLE_HELLO');
// Outputs 'Hello, Hubzero!'
echo Lang::txt('COM_EXAMPLE_HELLO_NAME', 'Hubzero');
| Legacy name | Hubzero |
|---|---|
JText::_() |
Lang::txt() |
JText::sprintf() |
Lang::txt() |
JText::plural() |
Lang::txts() |
JText::alt() |
Lang::alt() |
JRequest
Every public JRequest method survives on the request object, so dropping
the J is usually enough:
// Via the application container
$foo = App::get('request')->getVar('foo');
// Via the facade
$foo = Request::getVar('foo');
| Legacy name | Hubzero |
|---|---|
JRequest::* |
Request::* |
JToolbarHelper and JSubMenuHelper
Class name only; the methods and their arguments are unchanged.
// Legacy
JToolbarHelper::publishList();
// Hubzero
Toolbar::publishList();
JSubMenuHelper becomes Submenu, with one difference worth noticing:
the link is routed.
Submenu::addEntry(
Lang::txt('COM_COLLECTIONS_POSTS'),
Route::url('index.php?option=com_collections&controller=posts'),
$controllerName == 'posts'
);
JHtml
This one is not a rename. The legacy API passed everything through JHtml::_()
with a dotted first argument naming the sub-library and the function.
Hubzero makes the sub-library the method and the function the first
argument:
// Legacy
echo JHtml::_('grid.sort', 'COM_COLLECTIONS_COL_TITLE', 'title', $dir, $sort);
echo JHtml::_('behavior.framework');
// Hubzero
echo Html::grid('sort', 'COM_COLLECTIONS_COL_TITLE', 'title', $dir, $sort);
echo Html::behavior('framework');
The sub-libraries are the classes in
core/libraries/Hubzero/Html/Builder/:
access, asset, batch, behavior, category, content,
contentlanguage, grid, input, select, sliders, tabs. There is
no date sub-library; for a relative date use Date::of($d)->relative().
Factory objects
Objects that came from JFactory come from the service container, and most
have a facade in front of them. method() below stands for whatever you
used to call on the legacy object:
// Legacy
$user = JFactory::getUser();
echo $user->get('name');
// Hubzero
echo User::get('name');
| Legacy name | Container | Facade |
|---|---|---|
JFactory::getDbo() |
App::get('db') |
n/a |
JFactory::getUser(), JUser::getInstance() |
App::get('user') |
User::method() |
JFactory::getSession() |
App::get('session') |
Session::method() |
JFactory::getDocument() |
App::get('document') |
Document::method() |
JFactory::getConfig() |
App::get('config') |
Config::method() |
JFactory::getLanguage() |
App::get('lang') |
Lang::method() |
JFactory::getCache() |
App::get('cache.store') |
Cache::method() |
JFactory::getLogger() |
App::get('log')->logger('{name}') |
Log::method() |
Dates
JDate becomes Hubzero\Utility\Date,
reached through the Date facade. With no argument it means now, in UTC.
// The current UTC timestamp in the database's format: "2026-04-03 12:23:56"
echo Date::toSql();
// The current UTC timestamp as a Unix time
echo Date::toUnix();
// The current UTC year: "2026"
echo Date::format('Y');
// Adjusted to the hub's timezone. UTC 12:23 pm on an Eastern hub is "08:23 am"
echo Date::toLocal('g:i a');
Date::of() takes a specific timestamp, and an optional timezone that
defaults to UTC:
echo Date::of('2013-08-12 17:01:34')->format('Y'); // "2013"
echo Date::of('2013-08-12 17:01:34')->toLocal('g:i a'); // "1:01 pm"
See Dates.
Users
Any method called statically on User, other than getInstance(), acts on
the current user — the equivalent of JFactory::getUser()->method().
// Legacy
echo JFactory::getUser()->get('name');
// Hubzero
echo User::get('name');
getInstance() returns the object behind the facade, with or without an
id or username:
$user = User::getInstance(); // the current user
$other = User::getInstance(1234); // JFactory::getUser(1234)
The database
An upgrade is not finished when the files are in place. Schema and data
changes ship as migrations — small PHP
classes under a migrations directory with an up() and a down() —
and muse runs the ones a hub has not seen,
recording each in #__migrations so it never runs twice.
php core/bin/muse migration # dry run: lists what would happen
php core/bin/muse migration -f # actually run them
The dry run is the default, which is the safest thing about the command
and the easiest to miss. -e com_example limits the run to one extension,
-d down reverses, and the full option list is in the
muse migration reference.
Rewritten and checked against 2.4-main @ 348f0057c2 on 2026-09-10.