Core
This page explains how the ERP starts, how a request is processed and which classes do the work. Read it once, so the rest of the documentation does not feel like magic.
Three layers
┌───────────────────────────────────────────────────────────────┐
│ Your project (hubleto/custom) │
│ ConfigEnv.php · boot.php · src/apps (Hubleto\App\Custom) │
├───────────────────────────────────────────────────────────────┤
│ Hubleto ERP (hubleto/erp) │
│ Hubleto\Erp\* · Hubleto\App\Community\* (apps/) │
├───────────────────────────────────────────────────────────────┤
│ Hubleto Framework (hubleto/framework) │
│ Hubleto\Framework\* (router, DI, models, events, crons) │
└───────────────────────────────────────────────────────────────┘
Browser: React components of hubleto/react-ui + Loader.tsx of the apps
The framework is a general MVC framework that is also used outside of the ERP. The ERP extends its classes and adds the ERP behavior: ownership of records, the desktop with the sidebar, the translations of user interface, calendar, reports. Read the framework chapter first if you need to know what a class does in general.
Starting the ERP
Every request enters through index.php, which loads boot.php and renders the page:
erp-project: index.php
<?php
// bootstrap
require_once(__DIR__ . "/boot.php");
// render
echo $hubleto->render();
boot.php loads the configuration, the Composer autoloader and the autoloaders of your app repositories, and creates the Loader:
erp-project: boot.php (excerpt)
// env config
$config = [];
require_once(__DIR__ . "/ConfigEnv.php");
// autoloaders
require(($config['releaseFolder'] ?? '.') . "/vendor/autoload.php");
if (is_array($config['appRepositories'])) {
foreach ($config['appRepositories'] as $appRepository) {
if (is_dir($appRepository)) {
require($appRepository . '/autoload.php');
}
}
}
// init main class
$hubleto = new \Hubleto\Erp\Loader($config);
$hubleto->init();
ConfigEnv.php is created once by php hubleto init. It holds only what is needed before the database is available: the paths, URLs, database credentials and SMTP settings.
installer/Templates/ConfigEnv.php.tpl (excerpt)
// dirs
$config['projectFolder'] = __DIR__;
$config['releaseFolder'] = "{{ releaseFolder }}";
$config['logFolder'] = __DIR__ . '/log';
$config['uploadFolder'] = __DIR__ . '/upload';
// urls
$config['rewriteBase'] = "{{ rewriteBase }}";
$config['projectUrl'] = '{{ projectUrl }}';
$config['uploadUrl'] = '{{ projectUrl }}/upload';
$config['assetsUrl'] = '{{ assetsUrl }}';
if (is_dir(__DIR__ . '/assets/compiled')) $config['assetsCompiledUrl'] = '{{ projectUrl }}/assets/compiled';
else $config['assetsCompiledUrl'] = '{{ assetsUrl }}/compiled';
if (is_dir(__DIR__ . '/assets/css/themes')) $config['assetsThemesUrl'] = '{{ projectUrl }}/assets/css/themes';
else $config['assetsThemesUrl'] = '{{ assetsUrl }}/css/themes';
// sanitize dirs and urls based on used release
$config['releaseFolder'] = str_replace('__RELEASE__', $config['release'], $config['releaseFolder']);
$config['assetsUrl'] = str_replace('__RELEASE__', $config['release'], $config['assetsUrl']);
$config['assetsCompiledUrl'] = str_replace('__RELEASE__', $config['release'], $config['assetsCompiledUrl']);
$config['assetsThemesUrl'] = str_replace('__RELEASE__', $config['release'], $config['assetsThemesUrl']);
// db
$config['db_host'] = '{{ dbHost }}';
$config['db_user'] = '{{ dbUser }}';
$config['db_password'] = '{{ dbPassword }}';
$config['db_name'] = '{{ dbName }}';
$config['db_codepage'] = 'utf8mb4';
$config['global_table_prefix'] = '';
Everything else (the settings of apps and the user's preferences) is stored in the database and read with the configuration manager.
What Loader::init() does
Hubleto\Framework\Loader::init() initializes the services in this order. The order matters: the router needs the configuration, and the app manager needs the router to register the routes of apps.
framework: src/Loader.php (excerpt)
public function init(): void
{
try {
$this->db()->init();
$this->config()->init();
$this->router()->init();
$this->authProvider()->init();
$this->permissionsManager()->init();
$this->renderer()->init();
$this->appManager()->init();
$this->eventManager()->init();
} catch (\Exception $e) {
echo "Hubleto init failed: [".get_class($e)."] ".$e->getMessage() . "\n";
echo $e->getTraceAsString() . "\n";
exit;
}
}
Then Hubleto\Erp\Loader::init() sets the language of the user and registers the core API routes (api/search, api/dictionary, api/get-apps-info, ...) and fires onCoreAfterInit.
AppManager::init() is the step that matters to app developers. It finds all enabled apps and for each of them creates the Loader class and calls its init(). This is where your routes, crons and integrations are registered. An exception thrown by the init() of an app is caught and written to the log (App ... failed to init); the other apps keep working, but the failing app registers nothing. When an app "does nothing", check the log first.
A request from the router to the browser
- The router matches the URL to a controller. It tests all routes in the order of registration and the last match wins.
- The renderer creates the controller and checks its permission (see Permissions).
- The controller's
prepareView()fillsviewParamsand chooses the Twig view. - The renderer renders the view and, unless the request is an AJAX call or the controller sets
$hideDefaultDesktop, wraps it in the desktop (sidebar, header, search). - The browser loads the compiled JavaScript.
Loader.tsxof each app registers its React components and thehblreact-*tags in the page are replaced with them. - React components load their data from the generic
api/...endpoints, which call the model'sdescribe*()methods and the record manager.
Classes of the framework and their ERP extensions
| Framework class | ERP class | What the ERP adds |
|---|---|---|
Hubleto\Framework\Loader |
Hubleto\Erp\Loader |
Language of the user, core API routes. |
Hubleto\Framework\Core |
Hubleto\Erp\Core |
Base of services, models, controllers, crons (the shortcuts). |
Hubleto\Framework\App |
Hubleto\Erp\App |
Second sidebar, alerts, badges, demo data, search, settings. |
Hubleto\Framework\Model |
Hubleto\Erp\Model |
Custom columns, AI assistant context, default table and form. |
Hubleto\Framework\RecordManager |
Hubleto\Erp\RecordManager |
Ownership: id_owner, id_manager, id_team, shared_with. |
Hubleto\Framework\Controller |
Hubleto\Erp\Controller |
Breadcrumbs, the app-level permission check, validation of inputs. |
Hubleto\Framework\Controllers\... |
Hubleto\Erp\Controllers\ApiController |
JSON API controller, errors as JSON. |
Hubleto\Framework\Services\Renderer |
Hubleto\Erp\Renderer |
Desktop wrapper and permission check. |
| — | Hubleto\Erp\Cron |
Scheduled job with $schedulingPattern. |
| — | Hubleto\Erp\Calendar |
Base class for the calendar integration of an app. |
| — | Hubleto\Erp\Report, McpTool |
Reports and tools for AI assistants. |
Extension chain between the framework and the ERP.
NOTE Always extend the ERP class (
Hubleto\Erp\Model), not the framework class, in apps.
Services
A service is a singleton class that the framework creates on demand. The most important ones have shortcuts in Hubleto\Framework\Core, so you can call them from any model, controller, cron or app.
| Shortcut | Service | Purpose |
|---|---|---|
$this->router() |
Services\Router |
Routes, URL parameters, redirects. |
$this->renderer() |
Services\Renderer |
Rendering of controllers and views. |
$this->appManager() |
Services\AppManager |
Loading, installing and enabling of apps. |
$this->config() |
Services\ConfigManager |
Configuration in the database. |
$this->env() |
Services\Env |
Paths and URLs from ConfigEnv.php. |
$this->db() |
Services\Db |
Database connection (Eloquent/PDO). |
$this->authProvider() |
Services\AuthProvider |
Signed-in user. |
$this->permissionsManager() |
Services\PermissionsManager |
Permission checks. |
$this->eventManager() |
Services\EventManager |
Events and listeners. |
$this->cronManager() |
Services\CronManager |
Registration and execution of crons. |
$this->translator() |
Services\Translator |
Dictionaries. |
$this->locale() |
Services\Locale |
Languages, currencies, date and number formats. |
$this->logger() |
Services\Logger |
Log files (Monolog). |
$this->sessionManager() |
Services\SessionManager |
Session. |
$this->terminal() |
Services\Terminal |
Output of the command-line tool. |
$this->getService($class) |
Any class | A service of your own, see Services. |
Services and their shortcuts.
Dependency injection
getService() calls DependencyInjection::create(). It creates the object with new the first time and returns the same instance on every other call. A service provider replaces one class with another, and the ERP uses it to swap the framework's services for its own:
src/Loader.php (excerpt)
DependencyInjection::setServiceProviders([
\Hubleto\Framework\Services\Renderer::class => Renderer::class,
\Hubleto\Framework\Services\Env::class => Env::class,
\Hubleto\Framework\Services\Locale::class => Locale::class,
\Hubleto\Framework\Controllers\NotFound::class => Controllers\NotFound::class,
\Hubleto\Framework\Controllers\Desktop::class => \Hubleto\App\Community\Desktop\Controllers\Desktop::class,
]);
A class that is created this way must have a constructor without required parameters. To get a fresh instance, call DependencyInjection::create($class, true).
Configuration
There are two places for the configuration:
| Place | Read with | Used for |
|---|---|---|
ConfigEnv.php |
$this->env()->projectUrl, $this->env()->... |
Paths, URLs, database, SMTP, session salt. Created at install time. |
Table config (database) |
$this->config()->getAsString('path') |
Settings of apps, models and users. Edited in the Settings app. |
Where the configuration lives.
ConfigManager reads and writes a tree of values addressed by a path. Use getAsString(), getAsInteger(), getAsBool(), getAsArray() and getAsJson() with a default value; use save() to persist a value (set() changes it only for the current request). forApp() and forModel() return a manager with the prefix of the app or the model.
// reads the key 'apps/Hubleto\App\Community\Deals/calendarColor'; the app adds the prefix
$color = $this->app->configAsString('calendarColor', '#008000');
// saves the value permanently
$this->app->setConfigAsString('calendarColor', '#ff0000');
$this->app->saveConfig('calendarColor');
Events
The core and the models fire events. An app can listen to them with an event listener.
| Event | Fired when | Arguments |
|---|---|---|
onCoreAfterBootstrap, onCoreAfterInit |
The loader is constructed / initialized. | the loader |
onBeforeValidate, onAfterValidate |
A record is validated before saving. | the model, the record |
onModelBeforeCreate, onModelAfterCreate |
A record is created. | the model, the record |
onModelBeforeUpdate, onModelAfterUpdate |
A record is updated. | the model, the record(s) |
onModelBeforeDelete, onModelAfterDelete |
A record is deleted. | the model, the ID |
onModelAfterLoadRecord, onModelAfterLoadRecords |
Record(s) were loaded. | the model, the record(s) |
onControllerBeforeInit, onControllerBeforePrepareView, onControllerAfterPrepareView, onControllerSetView |
Phases of a controller. | the controller (and the view) |
onMailReceived |
The Mail app downloaded a message. |
the mail, the attachments |
Events fired by the core and the community apps.
User management and authentication
User management is implemented by the Auth app and the permission manager of the Settings app.
| Class | Description |
|---|---|
Hubleto\App\Community\Auth\Loader |
The authentication app: sign-in page, routes, default users. |
Hubleto\App\Community\Auth\AuthProvider |
Implementation of Services\AuthProvider: sign-in, session, "remember me". |
Hubleto\App\Community\Auth\Models\User |
The user: e-mail, password hash, type, language, enabled apps. |
Hubleto\App\Community\Auth\Models\UserRole |
Roles of users. |
Hubleto\App\Community\Auth\Models\UserHasRole |
Which user has which role (a junction model). |
Hubleto\App\Community\Settings\PermissionsManager |
Implementation of Services\PermissionsManager. |
Implementation of user management and authentication.
More in User management and authentication.
Command-line tool and crons
php hubleto ... (from hubleto/erp/cli) initializes the project, installs apps, runs migrations and generates code. See Command-line tool. The scheduled jobs are run by php cron.php that you call from the system cron every minute. See Scheduled jobs.
