# 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
= $title ?>
= $test['name'] ?>
```
### 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
= $title ?>
```
**Index View Pattern (`index.php`):**
```php
= $record['name'] ?>
= view('module/dialog') ?>
```
**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