224 lines
6.1 KiB
Markdown
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']);
|
|
```
|