Conventions
Conventions make a code base predictable. The rules below are the conventions of the Hubleto ERP and its community apps. Follow them in your apps, and the code generators, the AI assistants and other developers will find their way around.
The complete rules for creating an app, with examples, are in the App creation checklist. This page is the short reference.
Code style
| Language | Rule |
|---|---|
| PHP | PSR-12 with 2 spaces instead of 4 (.php-cs-fixer.dist.php). declare(strict_types=1); is welcome. Static analysis with PHPStan, level 10 (phpstan.neon). |
| TypeScript | 2 spaces, function components with hooks, single quotes for attributes in JSX. |
| Twig, YAML, JSON | 2 spaces. |
| SQL | Table and column names in snake_case. Table names are plural. |
Code style.
Run the formatter and the analyser from the root of your project:
./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php src/apps/MyApp
./vendor/bin/phpstan analyse src/apps/MyApp
Names
Namespaces and folders
| Code | Namespace | Folder (project) | Folder (hubleto/erp) |
|---|---|---|---|
| Hubleto Framework | Hubleto\Framework |
vendor/hubleto/framework/src |
— |
| Hubleto ERP core | Hubleto\Erp |
vendor/hubleto/erp/src |
src |
| Community apps | Hubleto\App\Community\AppName |
vendor/hubleto/erp/apps/AppName |
apps/AppName |
| Custom apps (yours) | Hubleto\App\Custom\AppName |
src/apps/AppName |
— |
| Crons, listeners, ...of the project | HubletoProject\... |
src/crons, src/event-listeners |
— |
Namespaces.
Class names are PascalCase, files have the name of the class (Customer.php), folders inside an app are PascalCase too. See Namespaces.
What a class is called
| Thing | Name | Example |
|---|---|---|
| App | plural noun or a function | Customers, Invoices, PriceLists, HrLeave |
| Model | singular noun | Customer, Invoice, PriceList |
| Record manager | the name of its model | Models\RecordManagers\Customer |
| Migration | <Model>_<NNNN> |
Customer_0001, Customer_0002 |
| Junction model | <Left>Has<Right> |
UserHasRole |
| CRUD controller | plural noun, one per model | Controllers\Customers |
| API controller | verb + noun, in Controllers/Api |
Api\GenerateQuotationPdf |
| Board controller | in Controllers/Boards |
Boards\MostValuableDeals |
| Cron | verb + noun, in Crons |
Crons\SendMails |
| Event listener | what it does, in EventListeners |
EventListeners\InvalidateScopeCache |
| Extendible | what it provides, in Extendibles |
Extendibles\AppMenu |
| Service | in Services |
Services\Counter |
| React table / form | Table + plural / Form + singular |
TableCustomers.tsx, FormCustomer.tsx |
Class names.
URLs
| Thing | Rule | Example |
|---|---|---|
| Root of an app | rootUrlSlug in manifest.yaml, kebab-case |
price-lists |
| List | <slug>, <slug>/<model-plural> |
deals, deals/tags, invoices/items |
| Detail | <url>/<id> |
deals/5 |
| New record | <url>/add |
deals/add |
| API | <slug>/api/<verb-noun> |
invoices/api/generate-invoice-number |
| Board | <slug>/board/<name> |
price-lists/board/expiring |
| Settings | <slug>/settings |
leads/settings |
URLs.
Database
| Thing | Rule | Example |
|---|---|---|
| Table | $table of the model, plural snake_case |
deals, deal_activities |
| Foreign key column | id_ + singular name of the referenced thing |
id_customer, id_owner, id_workflow_step |
| Relation | UPPER_CASE | CUSTOMER, OWNER |
| Virtual column | starts with virt_ |
virt_next_activity_date |
| Junction table | <left>_has_<right> |
user_has_roles |
Database.
Application design
- The model is the single source of truth. Define columns, labels, enums and lookups in
describeColumns(). Do not repeat them in React. - Thin controllers, thin views. A controller sets the view, a view renders a React component.
- The record manager owns queries. Use
prepareReadQuery()for every read so that ownership is respected. - Business logic in services (
Services/), not in controllers or the Loader. - Loose coupling. Talk to other apps through managers, events, extendibles and
FormCustomizer. Never edit another app. - Always call
parent::inonBefore*andonAfter*callbacks of a model, ininit(), and inprepareReadQuery(). - Translate everything the user can see (
$this->translate(),translate(),T.translate()). - Prefer arrays to objects for records. Do not create DTOs.
Routing conventions
Register the routes of a model with $this->router()->crud('slug', Controllers\Model::class). Regular expressions of other routes begin with ^, end with $ and start with the slug of the app. The last matching route wins. More in Routing.
Views conventions
Views are Twig templates with the same name as their controller. They render a hblreact-* tag or HTML with the CSS classes of Hubleto. Twig syntax inside this documentation must be escaped. See Twig views.
Git and delivery
- Commit messages say what changed in the app, in the present tense.
- The
.gitignoreof the project skeleton already excludesConfigEnv.php,vendor/,log/,upload/andcomposer.lock. Do not commitnode_modules/and compiled assets either. - Deliver a custom app as a
.zipthat unpacks intosrc/apps. It containsmanifest.yaml, the PHP code,Loader.tsxand the components, but notnode_modules.
