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. Group use statements below the namespace 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 also PascalCase.
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 using private and protected 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 in snake_case. Core system tables use sys_.
    • Example: bx_posts_entries, bx_photos_albums, sys_options, sys_profiles.
  • Fields (Columns): Use descriptive names in snake_case.
    • Example: author_id, entry_title, date_created, allow_view_to.

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 the switch.
  • Indent the code within each case one level from the case statement.
  • Include a break; (or return;, throw;) at the end of each non-empty case 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:

  1. Constants (const)
  2. Properties (static first, then instance; public, protected, private)
  3. Constructor (__construct)
  4. Static Methods
  5. Public Methods
  6. Protected Methods
  7. Private Methods
  8. 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. Use protected for methods/properties intended for use by child classes. Use public 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

  1. 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
  2. Prepare Query: Use BxDolDb::prepare() to create the SQL query with placeholders (? or :named). Pass the processed variables as arguments to prepare().

    // 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
    }
  3. 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 an INT UNSIGNED AUTO_INCREMENT).
  • Use foreign keys where applicable to enforce relational integrity.
  • Create indexes (INDEX, UNIQUE, FULLTEXT) on columns frequently used in WHERE clauses, JOIN conditions, or ORDER 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.):

  1. Include Token in Forms: Use bx_get_csrf_token_key() and bx_get_csrf_token() to add the hidden input field to your forms. Template functions often handle this automatically for forms generated via BxDolForm.
  2. 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. Use PascalCase 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.
  • 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 the response method in your module class if handling alerts.
  • Installation: Implement install and uninstall logic in the Installer 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 template config.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).
On This Page