clqms-be/.serena/memories/code_style_and_conventions.md

224 lines
6.1 KiB
Markdown

# 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
<?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
<?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
```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
<?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 `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']);
```