Code Convention
Coding standards, conventions, and best practices for all development within the UNA CMS platform. Adherence to these guidelines is crucial for creating code that is consistent, readable, maintainable, secure, and performant.
Scope: These conventions cover PHP, Database (SQL), JavaScript, CSS/Styling, module/template structure, API development, security practices, and documentation. They blend UNA-specific requirements with modern, industry-standard best practices, including relevant PHP Standard Recommendations (PSR). Where conflicts arise, UNA-specific conventions take precedence within the UNA codebase.
General Principles
Effective UNA development is guided by the following core principles:
Clean, Readable Code
Write code that is straightforward, easy to understand, and maintainable. Achieve this through:
- Meaningful and consistent naming for variables, functions, classes, etc.
- Focused functions/methods adhering to the Single Responsibility Principle.
- Consistent formatting, indentation, and use of whitespace.
- Clear comments explaining why complex logic exists, not just what it does.
Security-First Approach
Security is paramount. Integrate security considerations throughout the development lifecycle:
- Secure By Design: Proactively identify and mitigate potential threats during planning and coding.
- Input Validation: Rigorously validate and sanitize all external input (GET, POST, COOKIE, API data, etc.). Never trust user data.
- Output Escaping: Properly escape all data before rendering it in HTML, JavaScript, or other contexts to prevent Cross-Site Scripting (XSS).
- Database Security: Exclusively use prepared statements for all database interactions to prevent SQL injection.
- Permission Checks: Utilize the Access Control List (ACL) system to enforce authorization.
- Error Handling: Implement secure error handling that logs details without exposing sensitive information to users.
Consistency Across the Platform
Maintain uniformity across different parts of UNA for a cohesive developer experience:
- Adhere to the standard module directory structure.
- Use established naming conventions consistently.
- Employ common design patterns for recurring tasks (e.g., fetching data, rendering views).
- Follow UNA's established practices for configuration, installation, and integration.
Performance Considerations
Build efficient and scalable code:
- Optimize database queries; use appropriate indexes.
- Implement caching where beneficial (object cache, template cache, data cache).
- Minimize resource consumption (memory, CPU).
- Write code that performs well under load.
Documentation Requirements
Ensure code is well-documented for clarity and future maintenance:
- Use PHPDoc blocks for all classes, methods, properties, and functions.
- Clearly document parameters, return types, potential exceptions (
@throws
). - Provide concise explanations for the purpose of code blocks.
- Keep documentation synchronized with code changes.
PHP Coding Standards
PHP Version Compatibility
Develop code compatible with the minimum required PHP version specified for the target UNA version. Leverage modern PHP features where appropriate and supported.
File Organization
- PHP Tags: Always use the full
<?php ?>
tags. Use the short echo tag<?= $variable ?>
for outputting variables in templates where appropriate. Never use short tags<? ?>
. - Closing Tag: Omit the closing PHP tag
?>
at the end of files containing only PHP code. This prevents accidental whitespace/output issues. - File Encoding: All PHP files must be saved using UTF-8 encoding without BOM (Byte Order Mark).
- Line Endings: Use Unix-style line endings (LF -
\n
).
Namespaces and Imports (use
Statements)
-
Namespacing: All classes must be placed within a namespace following PSR-4 structure (e.g.,
namespace Bx\Posts\Classes;
). -
Declaration: The
namespace
declaration must be the first statement in the file after the opening<?php
tag. -
Importing: Use
use
statements to import classes, functions, or constants. Groupuse
statements below thenamespace
declaration.<?php namespace Bx\MyModule\Classes; use BxDol; // Import core class if needed use BxDolModuleQuery; // Import another core class use Bx\MyModule\Data\Constants; // Import constants class use function Bx\MyModule\Helpers\format_data; // Import a function class MyModuleClass extends BxDolModule { // ... class implementation ... }
-
Global Namespace: Avoid directly referencing classes from the global namespace unless absolutely necessary. Prefer
use
statements.
Side Effects
A PHP file should either declare symbols (classes, functions, constants, etc.) or execute logic with side effects (e.g., generating output, modifying settings), but should not do both. Template files (.html
) are an exception where declaration and output are mixed.
Naming Conventions
Consistency in naming is vital for readability.
Class Names
Use PascalCase
. Class names must include standard UNA prefixes:
- Core System:
BxDol...
(e.g.,BxDolModule
,BxDolPermalinks
) - Core Template Engine:
BxTempl...
(e.g.,BxTemplFormView
,BxTemplFunctions
) - Module Classes:
Bx[Vendor][Module]...
(e.g.,BxPostsModule
,BxPhotosConfig
,BxPersonsDb
). Vendor and Module names are alsoPascalCase
.
class BxPostsModule extends BxBaseModTextModule {}
class BxPhotosDb extends BxBaseModGeneralDb {}
class BxArtificerTemplate extends BxBaseModTemplate {} // Example for a template class
Variable Names
Use camelCase
.
- Hungarian Notation (Common but Optional): While not strictly enforced for new code, much of the existing UNA codebase uses a prefix indicating the variable's expected type. Understanding this convention is helpful. If used, apply it consistently within its scope.
o
- Object ($oModuleDb
)a
- Array ($aItems
)s
- String ($sTitle
)i
- Integer ($iCount
)f
- Float ($fPrice
)b
- Boolean ($bIsEnabled
)is
- Boolean (alternative, often for properties:$isPublic
)r
- Resource ($rDbConnection
)mixed
- Mixed Type ($mixedResult
)
- Choose descriptive names.
$sUserName = 'JohnDoe';
$iProfileId = BxDolProfile::getInstance()->id();
$aModuleConfig = $this->_oConfig-> CNF; // Example using Hungarian notation
Function and Method Names
Use camelCase
.
- Method names should clearly indicate their purpose.
- Module service methods are typically prefixed with
service
. - Module action methods (page controllers) are often prefixed with
action
. - Private/protected methods may be prefixed with an underscore (
_
) in older code, but modern practice prefers usingprivate
andprotected
visibility keywords without the underscore prefix.
function bx_get_site_info() {} // Example global function (use sparingly)
public function getUserProfile($iUserId) {}
protected function _prepareData(array $aData) {} // Older style
private function calculatePermissions() {} // Preferred style
public function serviceGetItems($iLimit = 10) {}
public function actionViewHomepage() {}
Constants
Use ALL_CAPS
with underscores (_
) separating words.
- Class Constants:
const MAX_RETRIES = 3;
- Global Constants:
define('BX_SECURITY_KEY', '...');
(Use sparingly; prefer class constants or configuration settings).
Database Tables and Fields
- Tables: Use the module-specific prefix (defined in
config.php
, consisting of[vendor]_[module]_
), followed by a descriptive name insnake_case
. Core system tables usesys_
.- Example:
bx_posts_entries
,bx_photos_albums
,sys_options
,sys_profiles
.
- Example:
- Fields (Columns): Use descriptive names in
snake_case
.- Example:
author_id
,entry_title
,date_created
,allow_view_to
.
- Example:
File Names
File names should generally match the main class or component they contain, using PascalCase
for class files (e.g., BxPostsModule.php
) and snake_case
or kebab-case
for template or configuration files where appropriate.
Code Structure and Formatting
Indentation and Whitespace
- Use 4 spaces per indentation level. Do not use tabs.
- Use blank lines judiciously to separate logical blocks of code (e.g., between methods, before complex control structures).
- Use a single space after commas in argument lists and array definitions.
- Use single spaces around comparison (
==
,!=
,>
), assignment (=
), concatenation (.
), logical (&&
,||
), mathematical (+
,-
), and ternary (? :
) operators.
Line Length
- There's no strict hard limit, but aim for lines no longer than 120 characters for better readability.
- Break long lines (e.g., complex conditions, long function calls, string concatenation) at logical points, indenting the continued line(s).
Braces ({}
) and Control Structures
- Classes and Methods: Opening brace (
{
) goes on the same line as the declaration. Closing brace (}
) goes on a new line, aligned with the declaration. - Control Structures (
if
,else
,elseif
,for
,foreach
,while
,switch
,try
,catch
,finally
): Opening brace ({
) goes on the next line, indented to the same level as the control structure keyword. Closing brace (}
) goes on a new line, aligned with the control structure keyword. - Spacing: Place one space after the control structure keyword (
if
,while
, etc.) and before the opening parenthesis(
. - Parentheses: No space after the opening parenthesis
(
or before the closing parenthesis)
. - Always Use Braces: Use curly braces for all control structure bodies, even if the body contains only a single statement.
class MyClass
{ // Brace on same line for class
public function myMethod($iValue)
{ // Brace on same line for method
if ($iValue > 10)
{ // Brace on next line for control structure
$this->doSomething();
}
else if ($iValue > 0) // Correct spacing
{
$this->doSomethingElse();
}
else
{
$this->doDefault();
}
foreach ($this->aItems as $sKey => $mixedValue)
{
// Loop body
}
}
}
switch
Statements
- Format the
switch
statement itself like other control structures (brace on next line). - Indent
case
statements one level from theswitch
. - Indent the code within each
case
one level from thecase
statement. - Include a
break;
(orreturn;
,throw;
) at the end of each non-emptycase
block unless fall-through is explicitly intended and commented. - Include a
default:
case where appropriate.
switch ($sType)
{
case 'admin':
case 'moderator': // Example fall-through (use sparingly)
$this->grantPrivileges();
break; // Don't forget break
case 'member':
$this->grantStandardAccess();
break;
default:
$this->grantGuestAccess();
}
Ternary Operator (? :
)
- Use only for simple, short conditional assignments. Prefer
if
/else
for complex logic. - Maintain spacing around
?
and:
.
$sStatusText = ($bIsActive) ? _t('_active') : _t('_inactive');
String Concatenation
- Use the concatenation operator (
.
) with spaces around it. - For complex strings, consider
sprintf
or breaking concatenation across multiple lines.
$sMessage = _t('_welcome_user') . ' ' . $sUserName . '!';
$sUrl = BX_DOL_URL_ROOT . $oModule->_oConfig->getBaseUri() . 'view/' . $sEntryUri;
Object-Oriented Programming (OOP)
Class Structure
Organize class contents logically and consistently:
- Constants (
const
) - Properties (static first, then instance; public, protected, private)
- Constructor (
__construct
) - Static Methods
- Public Methods
- Protected Methods
- Private Methods
- Magic Methods (
__get
,__toString
, etc.)
Inheritance
- Extend appropriate UNA base classes (e.g.,
BxDolModule
,BxBaseModGeneralDb
,BxDolForm
). - Crucially: Always call the parent constructor (
parent::__construct(...)
) within your class's constructor if inheriting from a class with a constructor. Pass required arguments.
Singleton Pattern
Certain core UNA classes are singletons (ensuring only one instance exists). If creating a similar system-wide service, consider implementing the pattern correctly:
- Private constructor (
__construct
). - Private static property to hold the instance (e.g.,
private static $_instance = null;
). - Public static
getInstance()
method. - Prevent cloning (
__clone
) and unserialization (__wakeup
) if necessary.
use BxDolSingleton; // Use the trait for convenience
class MySystemService implements BxDolSingletonInterface
{
use BxDolSingleton; // Implements getInstance() and prevents cloning/wakeup
protected function __construct()
{
// Initialization logic
}
// ... other methods ...
}
// Usage:
$oService = MySystemService::getInstance();
Interfaces and Traits
- Use interfaces (
interface
) to define contracts for classes. Implement all required methods. - Use traits (
trait
) to reuse code horizontally across different class hierarchies. Use them judiciously to avoid hidden dependencies.
Visibility (public
, protected
, private
)
- Declare visibility explicitly for all properties and methods.
- Default to
private
unless broader access is required. Useprotected
for methods/properties intended for use by child classes. Usepublic
only for the class's external API. - Prefer getter/setter methods over public properties for controlled access, especially if validation or logic is involved.
Database Interaction
Security - The Golden Rule
Never, ever directly insert unvalidated or unescaped data into SQL queries. Always use prepared statements and proper input processing.
Input Processing and Prepared Statements
-
Process Input: Sanitize data received from users or external sources using
bx_process_input()
before using it in a query. Choose the appropriate data type flag (e.g.,BX_DATA_INT
,BX_DATA_TEXT
,BX_DATA_HTML
).$iEntryId = bx_process_input(bx_get('id'), BX_DATA_INT); // Get ID from GET, ensure it's an integer $sTitle = bx_process_input($_POST['title'], BX_DATA_TEXT); // Get title from POST, sanitize for plain text
-
Prepare Query: Use
BxDolDb::prepare()
to create the SQL query with placeholders (?
or:named
). Pass the processed variables as arguments toprepare()
.// In your module's Db class (e.g., BxPostsDb) public function getEntryById($iEntryId) { $sSql = $this->prepare("SELECT * FROM `{$this->_sPrefix}entries` WHERE `id` = ? AND `status` = 'active'", $iEntryId); return $this->getRow($sSql); } public function insertEntry($iAuthorId, $sTitle, $sText) { $sSql = $this->prepare("INSERT INTO `{$this->_sPrefix}entries` SET `author_id` = ?, `title` = ?, `text` = ?, `created` = UNIX_TIMESTAMP()", $iAuthorId, $sTitle, $sText); return $this->query($sSql); // Or: return $this->lastId(); if you need the inserted ID }
-
Execute Query: Use
BxDolDb::getRow()
,::getAll()
,::getOne()
,::query()
,::getPairs()
, etc. to execute the prepared SQL string.
Deprecated Functions: Do not use process_db_input
, process_pass_data
, or BxDolDb::unescape
. These are outdated and less secure than the bx_process_input
+ prepare
approach.
Module Database Classes
Encapsulate all SQL queries related to a module within its dedicated database class (e.g., BxPostsDb
extending BxBaseModGeneralDb
or similar). This promotes organization and maintainability.
SQL Style
- Keywords: Write SQL keywords in
UPPERCASE
(SELECT
,FROM
,WHERE
,INSERT
,UPDATE
,DELETE
,JOIN
,ON
,GROUP BY
,ORDER BY
,LIMIT
, etc.). - Identifiers: Enclose table and column names in backticks (
`
). - Formatting: Use indentation and line breaks for readability in complex queries. Alias tables (
AS
) for clarity in joins.
SELECT
`p`.`id`,
`p`.`entry_title`,
`pr`.`name` AS `author_name`,
COUNT(`c`.`id`) AS `comment_count`
FROM
`bx_posts_entries` AS `p`
INNER JOIN
`sys_profiles` AS `pr` ON `p`.`author_id` = `pr`.`id`
LEFT JOIN
`bx_posts_comments` AS `c` ON `p`.`id` = `c`.`entry_id`
WHERE
`p`.`status` = 'active'
AND `p`.`allow_view_to` = 3 -- Example visibility check
GROUP BY
`p`.`id`
ORDER BY
`p`.`date_created` DESC
LIMIT 10;
Transactions
Use database transactions (beginTransaction
, commit
, rollback
) when performing multiple related database operations that must succeed or fail together.
$oDb = BxDolDb::getInstance();
$oDb->beginTransaction();
try {
$oDb->query("UPDATE `accounts` SET `balance` = `balance` - 100 WHERE `id` = 1");
$oDb->query("UPDATE `accounts` SET `balance` = `balance` + 100 WHERE `id` = 2");
$oDb->commit();
} catch (Exception $e) {
$oDb->rollback();
BxDolErrorHandler::getInstance()->log($e);
// Handle failure appropriately
}
Database Schema
- Use appropriate data types (
INT
,VARCHAR
,TEXT
,TIMESTAMP
,DECIMAL
, etc.). - Define primary keys (usually
id
as anINT UNSIGNED AUTO_INCREMENT
). - Use foreign keys where applicable to enforce relational integrity.
- Create indexes (
INDEX
,UNIQUE
,FULLTEXT
) on columns frequently used inWHERE
clauses,JOIN
conditions, orORDER BY
clauses.
Security Practices
Security is not optional. Reiterate and expand on key practices:
Input Validation
- Validate all external data (type, format, range, length).
- Use
bx_process_input()
with the correct type flag as the primary sanitization mechanism before database use or complex processing.
Output Escaping
- Escape all data being outputted to prevent XSS. Use the appropriate UNA function based on the context:
bx_process_output($sData, BX_DATA_TEXT)
: For plain text content within HTML.bx_process_output($sData, BX_DATA_HTML)
: For content that should contain safe HTML (use with extreme caution, ideally after purification).bx_html_attribute($sValue)
: For outputting within HTML attributes (e.g.,value="..."
,title="..."
).bx_js_string($sValue)
: For outputting a string within JavaScript code.
- Be mindful of the context. Escaping for HTML attributes is different from escaping for JavaScript.
- Consider implementing Content Security Policy (CSP) headers for an additional layer of defense.
CSRF Protection
Use UNA's built-in Cross-Site Request Forgery protection for any state-changing actions (POST requests, deletions, settings changes, etc.):
- Include Token in Forms: Use
bx_get_csrf_token_key()
andbx_get_csrf_token()
to add the hidden input field to your forms. Template functions often handle this automatically for forms generated viaBxDolForm
. - Verify Token on Server: Check the submitted token using
bx_verify_csrf_token()
before processing the request.
// In your form (.html template or BxDolForm array)
<input type="hidden" name="<?= bx_get_csrf_token_key(); ?>" value="<?= bx_get_csrf_token(); ?>" />
// In your PHP handler (e.g., module action method)
if (!bx_verify_csrf_token(bx_get('csrf_token'))) { // bx_get handles POST/GET
// CSRF token mismatch - reject request
echo 'Invalid CSRF token';
exit;
}
// Proceed with processing the request...
SQL Injection Prevention
Reiterated: Always use prepared statements (BxDolDb::prepare
) with data sanitized via bx_process_input
.
Authentication and Authorization (ACL)
- Rely on UNA's core authentication (
isLogged
,getProfileId
). - Use the Access Control List (ACL) system (
BxDolACL::getInstance()->isAllowed(...)
) to check permissions before allowing users to perform actions or view content. Define appropriate actions for your module.
JavaScript Conventions
- Naming: Use
camelCase
for variables and functions. UsePascalCase
for classes or constructor functions. - Style: Follow general JavaScript best practices. Use linters (like ESLint) with a standard configuration if possible. Be consistent with indentation (usually 2 or 4 spaces), brace style, and comments.
- Strict Mode: Use
'use strict';
at the beginning of your scripts or functions to enable stricter parsing and error handling. - Avoid Globals: Encapsulate code within classes (
class MyComponent { ... }
), objects (var MyModule = { ... };
), or IIFEs ((function() { ... })();
) to prevent polluting the global namespace. Follow the pattern used by UNA's core JS classes (e.g.,BxDolMenu
). - jQuery: jQuery is widely used in UNA. Follow standard jQuery practices when interacting with it.
CSS / Styling Conventions
-
Naming:
- Use descriptive, lowercase class names separated by hyphens (
-
) (kebab-case). Example:.bx-posts-entry-title
,.bx-form-input-required
. - Prefix module-specific classes with a module identifier (e.g.,
.bx-posts-
). - Consider using BEM (Block, Element, Modifier) naming (
.block__element--modifier
) for complex, reusable components to improve clarity and reduce selector conflicts. Example:.bx-menu-main__item--active
.
- Use descriptive, lowercase class names separated by hyphens (
-
Formatting:
- Use consistent indentation (usually 2 or 4 spaces).
- Place each property:value pair on its own line.
- Include a space after the colon (
:
). - End every declaration with a semicolon (
;
). - Place the opening brace (
{
) on the same line as the selector, separated by a space. - Place the closing brace (
}
) on its own line..bx-popup-wrapper { display: none; /* Example comment */ position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 1050; }
.bx-popup-wrapper__content { position: relative; background-color: #fff; padding: 20px; border-radius: 5px; margin: 50px auto; max-width: 600px; }
-
Organization: Group related styles. Use comments (
/* ... */
) to delineate logical sections (e.g., Base Styles, Layout, Components, Module-Specific). -
Tailwind CSS: Newer templates (like Artificer) heavily utilize Tailwind CSS. Development primarily involves applying utility classes directly in HTML templates. Custom CSS should focus on base styles, component abstractions (
@apply
), or overrides where utilities are insufficient. Follow Tailwind's design principles and configuration practices.
Module Development Guidelines
Refer to the dedicated Module Development documentation for full details. Key conventions include:
- Structure: Follow the standard directory layout (
classes/
,template/
,install/
,data/
, etc.). - Required Classes: Typically include
Module
,Config
,Db
,Template
,Form
(if forms are used),Search
(if searchable),Installer
. Extend appropriate base classes. - Configuration (
config.php
): Define module metadata, dependencies, database prefix, CNF constants, pages/URIs, service calls, settings categories, etc. - Service Methods: Implement public methods prefixed with
service
for inter-module communication and API exposure. - Hooks/Alerts: Use the
BxDolAlerts
system to react to or trigger events across the platform. Implement theresponse
method in your module class if handling alerts. - Installation: Implement
install
anduninstall
logic in theInstaller
class (database schema, settings, permissions, alerts, pages, etc.).
Template Development Guidelines
Refer to the dedicated Template Development documentation for full details. Key conventions include:
- Structure: Place custom templates in
/templates/[template_name]/
. Organize CSS, JS, images, and module overrides logically. - Overrides: To customize system or module appearance, copy the relevant
.html
,.css
, or.js
files to the corresponding path within your custom template directory (/templates/[your_template]/modules/[vendor]/[module_name]/...
). The system prioritizes files in the active template. - Asset Management: Use
BxDolTemplate::addCss()
,::addJs()
to include assets. Utilize "Mixes" (defined in templateconfig.php
) for combining and minifying CSS/JS for production. - Template Functions: Use UNA's template functions (
__function_name__
,_t(...)
, includes, loops, conditions) within.html
files.
API Development Guidelines
- Authentication: Support API Key (Bearer token) and potentially OAuth2 (if the module is installed). Check authentication early in API request handling.
- Endpoints: Typically exposed via module service methods. Follow RESTful principles where applicable (use appropriate HTTP methods, clear resource URIs). Standard entry point is often
/api.php?r=module/method
. - Response: Return data in JSON format. Include clear status indicators (success/error) and meaningful messages. Use standard HTTP status codes.
- Error Handling: Catch exceptions and return structured JSON error responses with appropriate HTTP status codes (e.g., 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 500 Internal Server Error). Do not expose raw error messages or stack traces in production API responses.
Error Handling and Logging
- Exception Handling: Use
try...catch
blocks for operations that might fail (database queries, file operations, API calls). - Logging: Use
BxDolErrorHandler::getInstance()->log($messageOrException)
to log errors or important events. Check logs in the/logs/
directory during development and troubleshooting. - User Feedback: Catch exceptions gracefully. Log the detailed error for administrators/developers, but show a generic, user-friendly error message (e.g., using
_t('_error_occured')
) on the frontend. Avoid exposing technical details like stack traces to end-users in production. - Error Reporting: Configure PHP error reporting appropriately for development (show errors) and production (log errors, don't display).
Documentation
Code Comments
- PHPDoc: Use PHPDoc blocks (
/** ... */
) for all classes, methods, functions, and non-trivial properties.- Include a brief summary description.
- Use
@param type $variableName Description
for all parameters. - Use
@return type Description
for the return value. - Use
@throws ExceptionType Description
for potential exceptions. - Use
@see OtherClass::method
for references. - Use
@package Vendor\Module
for classes./**
- Retrieves a list of posts by a specific author.
- Fetches active posts, applying pagination and basic permission checks.
- @param int $iAuthorId The profile ID of the author.
- @param int $iStart The starting index for pagination.
- @param int $iLimit The maximum number of posts to return.
- @return array An array of post data arrays, or an empty array if none found.
- @throws InvalidArgumentException If author ID is invalid.
*/
public function getPostsByAuthor($iAuthorId, $iStart = 0, $iLimit = 10)
{
if ($iAuthorId <= 0) {
throw new \InvalidArgumentException("Author ID must be positive.");
}
// ... implementation using $this->_oDb ...
}
- Inline Comments: Use
//
for single-line comments to explain complex or non-obvious logic within methods. Avoid commenting on obvious code. Focus on the why.
Tools and Resources
- IDE: A modern IDE with strong PHP support (e.g., PhpStorm, VS Code with extensions) is highly recommended.
- Version Control: Use Git for all development. Follow standard branching workflows (e.g., Gitflow) if collaborating.
- Database Tool: A GUI tool for database management (e.g., phpMyAdmin, MySQL Workbench, DBeaver).
- Debugging: Use Xdebug for step-debugging PHP code. Utilize browser developer tools for frontend issues.
- Code Quality: Consider using tools like PHP_CodeSniffer (for standards compliance), PHPStan/Psalm (for static analysis), and PHPMD (for detecting potential issues).