# AGENTS.md - QC Application Development Guide This document provides guidelines for agentic coding agents working on this PHP QC (Quality Control) application built with CodeIgniter 4. ## Project Overview This is a CodeIgniter 4 PHP application using SQL Server database for quality control data management. The app handles control tests, daily/monthly entries, and reporting. Uses Tailwind CSS and Alpine.js for UI. ## Build/Lint/Test Commands ```bash # PHP syntax check single file php -l app/Controllers/Dashboard.php # PHP syntax check all files recursively find . -name "*.php" -exec php -l {} \; 2>&1 | grep -v "No syntax errors" # Run all PHPUnit tests ./vendor/bin/phpunit # Run single test class ./vendor/bin/phpunit tests/unit/HealthTest.php # Run single test method ./vendor/bin/phpunit tests/unit/HealthTest.php --filter=testIsDefinedAppPath # Run with coverage report ./vendor/bin/phpunit --coverage-html coverage/ # Start development server php spark serve ``` ## Code Style Guidelines ### General Principles - Follow CodeIgniter 4 MVC patterns - Maintain consistency with surrounding code - Keep files focused (<200 lines preferred) - Use clear, descriptive names ### PHP Style - Use `dictTestModel = new DictTestModel(); } public function index(): string { $data = [ 'title' => 'Test Dictionary', 'tests' => $this->dictTestModel->findAll(), ]; return view('layout', [ 'content' => view('test/index', $data), 'page_title' => 'Test Dictionary', 'active_menu' => 'test' ]); } } ``` **Models** extend `CodeIgniter\Model`: ```php namespace App\Models; use CodeIgniter\Model; class TestModel extends Model { protected $table = 'dict_test'; protected $primaryKey = 'id'; protected $returnType = 'array'; protected $useSoftDeletes = false; protected $allowedFields = ['deptid', 'name', 'unit', 'method']; } ``` **Views** use PHP short tags `` for output: ```php

``` ### Database Operations - Configure connections in `app/Config/Database.php` - Use CodeIgniter's Query Builder for queries - Use parameterized queries to prevent SQL injection: ```php $builder = $this->db->table('results'); $builder->select('*'); $builder->where('control_ref_id', $controlId); $results = $builder->get()->getResultArray(); ``` - For SQL Server, set `DBDriver` to `'SQLSRV'` in config ### Error Handling - Use CodeIgniter's exception handling: `throw new \CodeIgniter\Exceptions\PageNotFoundException('Not found')` - Return JSON responses for AJAX endpoints: ```php return $this->response->setJSON(['success' => true, 'data' => $results]); ``` - Use `log_message('error', $message)` for logging - Validate input with `$this->validate()` in controllers ### Frontend (Tailwind CSS + Alpine.js) The layout already includes Tailwind via CDN and Alpine.js: ```html ``` **Common JavaScript (`public/js/app.js`):** - Contains global `App` object with shared utilities - Handles sidebar toggle functionality - Provides `App.showToast()`, `App.confirmSave()`, `App.closeAllModals()` - Initialize with `App.init()` on DOMContentLoaded **Page-Specific Alpine.js:** - Complex Alpine.js components should be defined inline in the view PHP file - Use `x-data` with an object containing all properties and methods - Example: ```html
``` ### File Organization - Controllers: `app/Controllers/` - Models: `app/Models/` - Views: `app/Views/` (subfolders for modules: `entry/`, `test/`, `control/`, `report/`, `dept/`) - Config: `app/Config/` - Routes: `app/Config/Routes.php` ### Modal-based CRUD Pattern All CRUD operations use a single modal dialog file (`dialog.php`) that handles both add and edit modes. **File Naming:** - Single dialog: `dialog.php` (no `_add` or `_edit` suffix) **Controller Pattern:** ```php class Dept extends BaseController { protected $dictDeptModel; public function __construct() { $this->dictDeptModel = new DictDeptModel(); } public function index(): string { $data = [ 'title' => 'Department Dictionary', 'depts' => $this->dictDeptModel->findAll(), ]; return view('layout', [ 'content' => view('dept/index', $data), 'page_title' => 'Department Dictionary', 'active_menu' => 'dept' ]); } public function save(): \CodeIgniter\HTTP\RedirectResponse { $data = ['name' => $this->request->getPost('name')]; $this->dictDeptModel->insert($data); return redirect()->to('/dept'); } public function update($id): \CodeIgniter\HTTP\RedirectResponse { $data = ['name' => $this->request->getPost('name')]; $this->dictDeptModel->update($id, $data); return redirect()->to('/dept'); } public function delete($id): \CodeIgniter\HTTP\RedirectResponse { $this->dictDeptModel->delete($id); return redirect()->to('/dept'); } } ``` **Dialog View Pattern (`dialog.php`):** ```php
``` **Index View Pattern (`index.php`):** ```php
``` **Searchable Multi-Select:** - Use Select2 for searchable dropdowns (already included in layout.php) - Initialize with: `$('#selectId').select2({ placeholder: 'Search...', allowClear: true })` ### Security Considerations - Use CodeIgniter's built-in CSRF protection (`$this->validate('csrf')`) - Escape all output in views with `` (auto-escaped) - Use `$this->request->getPost()` instead of `$_POST` - Never commit `.env` files with credentials ## Common Operations **Add a new CRUD resource:** 1. Create model in `app/Models/` 2. Create controller in `app/Controllers/` 3. Add routes in `app/Config/Routes.php` 4. Create views in `app/Views/[module]/` **Add a menu item:** - Add to sidebar in `app/Views/layout.php` - Add route in `app/Config/Routes.php` - Create controller method - Set `active_menu` parameter in view() call