# 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 `ResponseTrait` for standardized responses ### Models - Grouped by domain: `app/Models/Patient/`, `app/Models/Specimen/` - Extend `BaseModel` for automatic UTC date handling - Define `$table`, `$primaryKey`, `$allowedFields` - Use `checkDbError()` for database error detection ## Code Patterns ### Controller Structure ```php 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 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 ```php // 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 ```php 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 ```php $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: ```php AuditService::logData( 'CREATE|UPDATE|DELETE', 'table_name', (string) $recordId, 'entity_name', null, $previousData, $newData, 'Action description', ['metadata' => 'value'] ); ``` ## Route Patterns ```php $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 withBodyFormat('json')->post($this->endpoint, $payload); $result->assertStatus(201); } } ``` **Test Naming:** `test` (e.g., `testCreatePatientValidationFail`) ## Security Best Practices - Use `auth` filter 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 ```php 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']); ```