6.1 KiB
6.1 KiB
CLQMS Code Style and Conventions
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Classes | PascalCase | PatientController, PatientModel |
| Methods | camelCase | createPatient(), getPatients() |
| Properties | snake_case (legacy) / camelCase (new) | $patient_id / $patientId |
| Constants | UPPER_SNAKE_CASE | MAX_RETRY_COUNT |
| Database Tables | snake_case | patient, patient_visits, order_tests |
| Database Columns | PascalCase (legacy) | PatientID, NameFirst, Birthdate, CreatedAt |
| JSON Fields | PascalCase | "PatientID": "123" |
File and Directory Structure
Controllers
- Grouped by domain in subdirectories:
app/Controllers/Patient/,app/Controllers/Specimen/ - Each controller handles CRUD for its entity
- Use
ResponseTraitfor standardized responses
Models
- Grouped by domain:
app/Models/Patient/,app/Models/Specimen/ - Extend
BaseModelfor automatic UTC date handling - Define
$table,$primaryKey,$allowedFields - Use
checkDbError()for database error detection
Code Patterns
Controller Structure
<?php
namespace App\Controllers\Patient;
use App\Traits\ResponseTrait;
use CodeIgniter\Controller;
use App\Models\Patient\PatientModel;
class PatientController extends Controller {
use ResponseTrait;
protected $db;
protected $model;
protected $rules;
public function __construct() {
$this->db = \Config\Database::connect();
$this->model = new PatientModel();
$this->rules = [ /* validation rules */ ];
}
public function index() { /* ... */ }
public function create() { /* ... */ }
public function show($id) { /* ... */ }
public function update() { /* ... */ }
public function delete() { /* ... */ }
}
Model Structure
<?php
namespace App\Models\Patient;
use App\Models\BaseModel;
use App\Services\AuditService;
class PatientModel extends BaseModel {
protected $table = 'patient';
protected $primaryKey = 'InternalPID';
protected $allowedFields = ['PatientID', 'NameFirst', ...];
protected $useTimestamps = true;
protected $createdField = 'CreateDate';
protected $useSoftDeletes = true;
protected $deletedField = 'DelDate';
public function getPatients($filters = []) { /* ... */ }
public function createPatient($input) { /* ... */ }
private function checkDbError($db, string $context) {
$error = $db->error();
if (!empty($error['code'])) {
throw new \Exception("{$context} failed: {$error['code']} - {$error['message']}");
}
}
}
Validation Rules
- Define in controller constructor as
$this->rules - Use CodeIgniter validation rules:
required,permit_empty,regex_match,max_length, etc. - For nested data, override rules dynamically based on input
Response Format
// Success
return $this->respond([
'status' => 'success',
'message' => 'Operation completed',
'data' => $data
], 200);
// Error
return $this->respond([
'status' => 'failed',
'message' => 'Error description',
'data' => []
], 400);
Note: Custom ResponseTrait automatically converts empty strings to null.
Error Handling
- Use try-catch for JWT and external calls
- Log errors:
log_message('error', $message) - Return structured error responses with appropriate HTTP status codes
try {
$decoded = JWT::decode($token, new Key($key, 'HS256'));
} catch (\Firebase\JWT\ExpiredException $e) {
return $this->respond(['status' => 'failed', 'message' => 'Token expired'], 401);
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'message' => 'Invalid token'], 401);
}
Database Operations
- Use CodeIgniter Query Builder or Model methods
- Use
helper('utc')for UTC date conversion - Wrap multi-table operations in transactions
$this->db->transStart();
// ... database operations
$this->db->transComplete();
if ($this->db->transStatus() === false) {
return $this->respond(['status' => 'error', 'message' => 'Transaction failed'], 500);
}
Audit Logging
Use AuditService::logData() for tracking data changes:
AuditService::logData(
'CREATE|UPDATE|DELETE',
'table_name',
(string) $recordId,
'entity_name',
null,
$previousData,
$newData,
'Action description',
['metadata' => 'value']
);
Route Patterns
$routes->group('api/patient', function ($routes) {
$routes->get('/', 'Patient\PatientController::index');
$routes->post('/', 'Patient\PatientController::create');
$routes->get('(:num)', 'Patient\PatientController::show/$1');
$routes->patch('/', 'Patient\PatientController::update');
$routes->delete('/', 'Patient\PatientController::delete');
});
Testing Guidelines
<?php
namespace Tests\Feature\Patients;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Faker\Factory;
class PatientCreateTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected $endpoint = 'api/patient';
public function testCreatePatientSuccess()
{
$faker = Factory::create('id_ID');
$payload = [...];
$result = $this->withBodyFormat('json')->post($this->endpoint, $payload);
$result->assertStatus(201);
}
}
Test Naming: test<Action><Scenario><ExpectedResult> (e.g., testCreatePatientValidationFail)
Security Best Practices
- Use
authfilter for protected routes - Sanitize user inputs with validation rules
- Use parameterized queries (CodeIgniter Query Builder handles this)
- Store secrets in
.env, never commit to repository
Legacy Field Naming
Database uses PascalCase columns: PatientID, NameFirst, Birthdate, CreatedAt, UpdatedAt
ValueSet/Lookup Usage
use App\Libraries\Lookups;
// Get all lookups
$allLookups = Lookups::getAll();
// Get single lookup formatted for dropdowns
$gender = Lookups::get('gender');
// Get label for a specific key
$label = Lookups::getLabel('gender', '1'); // Returns 'Female'
// Transform database records with lookup text labels
$labeled = Lookups::transformLabels($data, ['Sex' => 'gender']);