# AGENTS.md - Code Guidelines for CLQMS > **CLQMS (Clinical Laboratory Quality Management System)** - A headless REST API backend for clinical laboratory workflows built with CodeIgniter 4. --- ## Build, Test & Lint Commands ### Running Tests ```bash # Run all tests ./vendor/bin/phpunit # Run a specific test file ./vendor/bin/phpunit tests/feature/Patients/PatientCreateTest.php # Run a specific test method ./vendor/bin/phpunit --filter testCreatePatientSuccess tests/feature/Patients/PatientCreateTest.php # Run tests with coverage ./vendor/bin/phpunit --coverage-html build/logs/html # Run tests by suite ./vendor/bin/phpunit --testsuite App ``` ### CodeIgniter CLI Commands ```bash # Run spark commands php spark # Generate migration php spark make:migration # Generate model php spark make:model # Generate controller php spark make:controller # Generate seeder php spark make:seeder # Run migrations php spark migrate # Rollback migrations php spark migrate:rollback ``` ### Composer Commands ```bash # Install dependencies composer install # Run tests via composer composer test # Update autoloader composer dump-autoload ``` --- ## Code Style Guidelines ### PHP Standards - **PHP Version**: 8.1+ - **PSR-4 Autoloading**: `App\` maps to `app/`, `Config\` maps to `app/Config/` - **PSR-12 Coding Style** (follow where applicable) ### Naming Conventions | Element | Convention | Example | |---------|-----------|---------| | Classes | PascalCase | `PatientController` | | Methods | camelCase | `createPatient()` | | Properties | snake_case (legacy) / camelCase (new) | `$patient_id` / `$patientId` | | Constants | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` | | Tables | snake_case | `patient_visits` | | Columns | PascalCase (legacy) | `PatientID`, `NameFirst` | | JSON fields | PascalCase | `"PatientID": "123"` | ### File Organization ``` app/ ├── Config/ # Configuration files ├── Controllers/ # API controllers (grouped by feature) │ ├── Patient/ │ ├── Organization/ │ └── Specimen/ ├── Models/ # Data models ├── Filters/ # Request filters (Auth, CORS) ├── Traits/ # Reusable traits ├── Libraries/ # Custom libraries ├── Helpers/ # Helper functions └── Database/ ├── Migrations/ └── Seeds/ ``` ### Imports & Namespaces - Always use fully qualified namespaces at the top - Group imports: Framework first, then App, then external - Use statements must be in alphabetical order within groups ```php model = new \App\Models\ExampleModel(); } // GET /example public function index() { // Implementation } // POST /example public function create() { // Implementation } } ``` ### Response Format All API responses must use the standardized format: ```php // Success response return $this->respond([ 'status' => 'success', 'message' => 'Operation completed', 'data' => $data ], 200); // Error response return $this->respond([ 'status' => 'failed', 'message' => 'Error description', 'data' => [] ], 400); ``` ### Error Handling - Use try-catch for JWT operations and external calls - Return structured error responses with appropriate HTTP status codes - Log errors using CodeIgniter's logging: `log_message('error', $message)` ```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's Query Builder or Model methods - Prefer parameterized queries over raw SQL - Use transactions for multi-table operations ```php $this->db->transStart(); // ... database operations $this->db->transComplete(); if ($this->db->transStatus() === false) { return $this->respond(['status' => 'error', 'message' => 'Transaction failed'], 500); } ``` ### Testing Guidelines #### Test Structure ```php withBodyFormat('json') ->post($this->endpoint, $payload); $result->assertStatus(201); } } ``` #### Test Naming - Use descriptive method names: `test` - Example: `testCreatePatientValidationFail`, `testCreatePatientSuccess` #### Test Status Codes - 200: Success (GET, PATCH) - 201: Created (POST) - 400: Validation Error - 401: Unauthorized - 404: Not Found - 500: Server Error ### API Design - **Base URL**: `/api/` - **Authentication**: JWT token via HttpOnly cookie - **Content-Type**: `application/json` - **HTTP Methods**: - `GET` - Read - `POST` - Create - `PATCH` - Update (partial) - `DELETE` - Delete ### Routes Pattern ```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'); }); ``` ### Security Guidelines - Always use the `auth` filter for protected routes - Sanitize all user inputs - Use parameterized queries to prevent SQL injection - Store JWT secret in `.env` file - Never commit `.env` files --- ## Project-Specific Conventions ### Legacy Field Naming Database uses PascalCase for column names (legacy convention): - `PatientID`, `NameFirst`, `NameLast` - `Birthdate`, `CreatedAt`, `UpdatedAt` ### ValueSet System Use the `App\Libraries\Lookups` class for static dropdown values: ```php use App\Libraries\Lookups; $genders = Lookups::get('gender'); $options = Lookups::getOptions('gender'); ``` ### Models Extend `BaseModel` for automatic UTC date handling: ```php