Best Practices
Code conventions, guidelines, and best practices for Health Checker plugin development.
Code Organization
File Structure
plugins/healthchecker/yourplugin/
├── language/
│ └── en-GB/
│ ├── plg_healthchecker_yourplugin.ini
│ └── plg_healthchecker_yourplugin.sys.ini
├── media/
│ ├── logo.svg
│ └── css/
│ └── styles.css
├── services/
│ └── provider.php
├── src/
│ ├── Extension/
│ │ └── YourPlugin.php
│ └── Checks/
│ ├── FirstCheck.php
│ ├── SecondCheck.php
│ └── ThirdCheck.php
└── yourplugin.xmlNaming Conventions
Check Classes:
php
// ✅ Good
class ApiConnectionCheck
class BackupStatusCheck
class CacheSizeCheck
// ❌ Avoid
class Check1
class MyCheck
class TestingCheckCheck Slugs:
php
// ✅ Good
'yourplugin.api_connection'
'yourplugin.backup_status'
'yourplugin.cache_size'
// ❌ Avoid
'yourplugin.check1'
'api_connection' // Missing provider prefix
'yourplugin.api-connection' // Use underscores, not hyphensCheck Design
Single Responsibility
Each check should test ONE thing:
php
// ✅ Good - Single purpose
class ApiKeyConfiguredCheck extends AbstractHealthCheck
{
protected function performCheck(): HealthCheckResult
{
$apiKey = $this->getConfigValue('api_key');
return $apiKey
? $this->good('API key configured')
: $this->critical('API key not configured');
}
}
// ❌ Bad - Multiple responsibilities
class ApiCheck extends AbstractHealthCheck
{
protected function performCheck(): HealthCheckResult
{
// Checks key, connectivity, AND permissions
// Split into 3 separate checks
}
}Fast Execution
Target: Under 1 second per check
php
// ✅ Good - Fast
protected function performCheck(): HealthCheckResult
{
$exists = file_exists('/path/to/file');
return $exists ? $this->good('File exists') : $this->critical('Missing');
}
// ❌ Bad - Slow
protected function performCheck(): HealthCheckResult
{
sleep(10); // Never block!
$this->scanEntireFilesystem(); // Too slow!
$this->downloadLargeFile(); // Don't do this!
}Read-Only Operations
NEVER modify anything in a health check:
php
// ✅ Good - Read only
protected function performCheck(): HealthCheckResult
{
$cacheSize = $this->getCacheDirectorySize();
return $cacheSize > 1000000
? $this->warning('Cache is large. Consider clearing.')
: $this->good('Cache size is reasonable.');
}
// ❌ Bad - Modifies data
protected function performCheck(): HealthCheckResult
{
$this->clearCache(); // NEVER modify!
$this->deleteFiles(); // NEVER delete!
$this->updateDatabase(); // NEVER update!
return $this->good('Cache cleared');
}Result Messages
Be Specific
php
// ✅ Good - Specific
return $this->critical(
'PHP memory limit is 128M but 256M is recommended for optimal performance. ' .
'Current usage: 96M. Contact your hosting provider to increase this limit.'
);
// ❌ Bad - Vague
return $this->critical('Memory is low');Be Actionable
php
// ✅ Good - Actionable
return $this->warning(
'Page caching is disabled. Enable it at: ' .
'System → Global Configuration → System → Cache: ON'
);
// ❌ Bad - Not actionable
return $this->warning('Caching could be better');Include Context
php
// ✅ Good - With context
return $this->warning(sprintf(
'Last backup is %d days old. Recommended: daily backups. ' .
'Visit Components → Akeeba Backup to run a backup.',
$daysSinceBackup
));
// ❌ Bad - No context
return $this->warning('Old backup');Error Handling
Let Base Class Handle
php
// ✅ Good - Automatic error handling
protected function performCheck(): HealthCheckResult
{
// Exceptions automatically caught and returned as critical
$db = $this->getDatabase();
$result = $db->loadResult(); // May throw
return $this->good('Success');
}Handle Specific Cases
php
// ✅ Good - Handle expected exceptions
protected function performCheck(): HealthCheckResult
{
try {
$response = $this->makeApiCall();
return $this->good('API accessible');
} catch (TimeoutException $e) {
return $this->warning('API timed out. Retry later.');
} catch (AuthException $e) {
return $this->critical('Invalid API credentials');
} catch (\Exception $e) {
// Let base class handle unexpected errors
throw $e;
}
}Performance
Cache When Appropriate
php
// ✅ Good - Cache expensive operations
protected function performCheck(): HealthCheckResult
{
static $result = null;
if ($result === null) {
$result = $this->expensiveOperation();
}
return $result ? $this->good('OK') : $this->warning('Issue');
}Lazy Loading
php
// ✅ Good - Only load when needed
protected function performCheck(): HealthCheckResult
{
// Quick check first
if (!$this->isFeatureEnabled()) {
return $this->good('Feature disabled (expected)');
}
// Expensive check only if needed
return $this->performDetailedCheck();
}Testing
Test All Status Levels
Ensure your check can return all three statuses:
php
// ✅ Complete - All statuses possible
protected function performCheck(): HealthCheckResult
{
$value = $this->getValue();
if ($value < 10) {
return $this->critical('Value critically low');
}
if ($value < 50) {
return $this->warning('Value below recommended');
}
return $this->good('Value is optimal');
}
// ❌ Incomplete - Only two statuses
protected function performCheck(): HealthCheckResult
{
return $this->isEnabled()
? $this->good('Enabled')
: $this->warning('Disabled');
// What about critical? When would that occur?
}Test Edge Cases
php
// ✅ Good - Handles edge cases
protected function performCheck(): HealthCheckResult
{
$value = $this->getValue();
// Handle null/empty
if ($value === null || $value === '') {
return $this->critical('Value not set');
}
// Handle invalid type
if (!is_numeric($value)) {
return $this->warning('Value is not numeric');
}
// Normal cases
return (int)$value > 0
? $this->good('Value is positive')
: $this->warning('Value is zero or negative');
}Documentation
Class Documentation
Every check must include header documentation:
php
/**
* API Connection Health Check
*
* Verifies that the API endpoint is accessible and responding correctly.
*
* WHY THIS CHECK IS IMPORTANT:
* The API connection is required for all sync operations. Without a working
* connection, data sync will fail and users will see errors.
*
* RESULT MEANINGS:
*
* GOOD: API is accessible and responding with status 200.
*
* WARNING: API is accessible but responding slowly (>2 seconds) or with
* non-200 status. Check may indicate degraded service.
*
* CRITICAL: API is completely unreachable, credentials are invalid, or
* network connectivity is blocked. Requires immediate attention.
*/
final class ApiConnectionCheck extends AbstractHealthCheck
{
// Implementation
}Code Comments
Comment non-obvious logic:
php
// ✅ Good - Explains why
protected function performCheck(): HealthCheckResult
{
// We multiply by 1.5 because MySQL reports compressed size,
// but we need to account for index overhead
$estimatedSize = $reportedSize * 1.5;
return $estimatedSize > $limit
? $this->warning('Size approaching limit')
: $this->good('Size is reasonable');
}
// ❌ Bad - Comments what (obvious from code)
protected function performCheck(): HealthCheckResult
{
// Get the value
$value = $this->getValue();
// Return good if true
return $value ? $this->good('Good') : $this->warning('Bad');
}Language Keys
Consistent Format
ini
; ✅ Good
PLG_HEALTHCHECKER_YOURPLUGIN_CHECK_YOURPLUGIN_API_CONNECTION_TITLE="API Connection"
PLG_HEALTHCHECKER_YOURPLUGIN_CHECK_YOURPLUGIN_BACKUP_STATUS_TITLE="Backup Status"
PLG_HEALTHCHECKER_YOURPLUGIN_CATEGORY_YOURPLUGIN="Your Plugin"
; ❌ Avoid
PLG_HEALTHCHECKER_YOURPLUGIN_API="API"
PLG_HEALTHCHECKER_YOURPLUGIN_CHECK1="Check 1"Descriptive Titles
ini
; ✅ Good - Clear and specific
PLG_HEALTHCHECKER_YOURPLUGIN_CHECK_YOURPLUGIN_CACHE_SIZE_TITLE="Cache Directory Size"
PLG_HEALTHCHECKER_YOURPLUGIN_CHECK_YOURPLUGIN_API_KEY_CONFIGURED_TITLE="API Key Configuration"
; ❌ Bad - Vague
PLG_HEALTHCHECKER_YOURPLUGIN_CHECK_YOURPLUGIN_CHECK1_TITLE="Check 1"
PLG_HEALTHCHECKER_YOURPLUGIN_CHECK_YOURPLUGIN_STATUS_TITLE="Status"Security
Sanitize Output
php
// ✅ Good - Sanitized
protected function performCheck(): HealthCheckResult
{
$path = $this->getPath();
$safePath = htmlspecialchars($path, ENT_QUOTES, 'UTF-8');
return $this->warning("File not found at: {$safePath}");
}
// ❌ Bad - Unsanitized (XSS risk)
protected function performCheck(): HealthCheckResult
{
$userInput = $_GET['path']; // Never do this!
return $this->warning("Path: {$userInput}");
}Don't Expose Secrets
php
// ✅ Good - Masked
protected function performCheck(): HealthCheckResult
{
$apiKey = $this->getApiKey();
$masked = substr($apiKey, 0, 4) . '...' . substr($apiKey, -4);
return $this->warning("Invalid API key: {$masked}");
}
// ❌ Bad - Exposes secret
protected function performCheck(): HealthCheckResult
{
$apiKey = $this->getApiKey();
return $this->warning("Invalid API key: {$apiKey}");
}Checklist
Before releasing your health check plugin:
- [ ] All checks extend AbstractHealthCheck
- [ ] Database is injected when creating checks
- [ ] Provider metadata registered
- [ ] All checks have unique slugs with provider prefix
- [ ] Language keys follow naming convention
- [ ] Check titles are descriptive
- [ ] Result messages are specific and actionable
- [ ] All three statuses (good/warning/critical) are possible
- [ ] Checks are read-only (no modifications)
- [ ] Checks execute in under 1 second
- [ ] Error handling is appropriate
- [ ] Code is documented (class headers)
- [ ] No secrets exposed in messages
- [ ] Tested in Joomla admin interface
Next Steps
- Examples - See complete working examples
- Quick Start - Build your first plugin
- API Reference - Complete API documentation