Standar Coding PHP CodeIgniter 4
Standar Coding PHP CodeIgniter 4
Daftar Isi
1. Standar PHP Umum
2. Standar Khusus CodeIgniter 4
3. Organisasi File
4. Konvensi Penamaan
5. Struktur Kode
6. Standar Database
7. Standar Keamanan
8. Pedoman Performa
9. Standar Dokumentasi
Format Kode
// Konstanta: UPPER_SNAKE_CASE
define('MAX_LOGIN_ATTEMPTS', 3);
const API_VERSION = '2.1';
Struktur Kontrol
// Selalu gunakan kurung kurawal, bahkan untuk statement tunggal
if ($condition) {
doSomething();
}
1
processItem($item);
}
namespace App\Controllers;
use App\Models\UserModel;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\RESTful\ResourceController;
Standar Controller
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
2
'title' => 'Daftar Pengguna'
];
Standar Model
<?php
namespace App\Models;
use CodeIgniter\Model;
// Tanggal
protected $useTimestamps = true;
protected $dateFormat = 'datetime';
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
protected $deletedField = 'deleted_at';
// Validasi
protected $validationRules = [
'email' => 'required|valid_email|is_unique[users.email]',
'username' => 'required|alpha_numeric_space|min_length[3]|
max_length[20]'
];
protected $validationMessages = [
'email' => [
'is_unique' => 'Email sudah digunakan'
]
];
Organisasi File
3
Struktur Direktori
app/
├── Config/
├── Controllers/
│ ├── Admin/
│ └── Api/
├── Database/
│ ├── Migrations/
│ └── Seeds/
├── Filters/
├── Helpers/
├── Libraries/
├── Models/
├── Views/
│ ├── layouts/
│ ├── partials/
│ └── errors/
└── Language/
Penamaan File
Konvensi Penamaan
Kelas dan Method
// Kelas: PascalCase
class UserProfileController extends BaseController
{
// Method public: camelCase
public function getUserProfile(): array
{
return $this->userModel->getProfile();
}
Konvensi Database
// Nama tabel: snake_case, jamak
'user_profiles', 'order_items', 'product_categories'
4
// Nama kolom: snake_case
'user_id', 'created_at', 'is_active'
Struktur Kode
Organisasi Method
class ExampleController extends BaseController
{
// 1. Properties dahulu
protected $userModel;
private $validationRules = [];
// 2. Constructor/Inisialisasi
public function initController(RequestInterface $request,
ResponseInterface $response, LoggerInterface $logger)
{
parent::initController($request, $response, $logger);
}
Penanganan Error
public function createUser()
{
try {
$data = $this->request->getPost();
if (!$this->userModel->insert($data)) {
throw new \RuntimeException('Gagal membuat pengguna');
}
5
Standar Database
Penggunaan Query Builder
// Direkomendasikan: Gunakan Query Builder
$users = $this->db->table('users')
->select('id, username, email')
->where('status', 'active')
->orderBy('created_at', 'DESC')
->limit(10)
->get()
->getResultArray();
Migrasi
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
6
$this->forge->addKey('id', true);
$this->forge->addKey('email');
$this->forge->createTable('users');
}
Standar Keamanan
Validasi dan Sanitasi Input
// Selalu validasi input
$validation = \Config\Services::validation();
$validation->setRules([
'email' => 'required|valid_email',
'password' => 'required|min_length[8]'
]);
if (!$validation->withRequest($this->request)->run()) {
return redirect()->back()->withInput()->with('errors', $validation-
>getErrors());
}
Perlindungan CSRF
// Di form
<?= csrf_field() ?>
// Verifikasi manual
if (!$this->request->isCLI() && !$this->validate([], [], $this->request-
>getPost())) {
throw new \CodeIgniter\Security\Exceptions\
SecurityException('Verifikasi CSRF gagal');
}
// BUKAN ini
$user = $this->db->query("SELECT * FROM users WHERE email = '$email'")-
>getRow();
Pedoman Performa
7
Optimasi Database
// Gunakan select spesifik daripada SELECT *
$users = $this->userModel
->select('id, username, email')
->findAll();
Caching
// Cache operasi yang mahal
$cache = \Config\Services::cache();
$cacheKey = 'user_stats_' . $userId;
if (!$stats = $cache->get($cacheKey)) {
$stats = $this->calculateUserStats($userId);
$cache->save($cacheKey, $stats, 3600); // Cache selama 1 jam
}
Standar Dokumentasi
Komentar PHPDoc
/**
* Mengambil informasi profil pengguna
*
* @param int $userId ID pengguna
* @param string $format Format output (json|array)
* @return array|string Data profil pengguna
* @throws \InvalidArgumentException Ketika ID pengguna tidak valid
*/
public function getUserProfile(int $userId, string $format = 'array')
{
if ($userId <= 0) {
throw new \InvalidArgumentException('ID pengguna harus positif');
}
// Implementasi di sini
}
8
Checklist Code Review
Sebelum Commit
Dokumen ini harus direview dan diperbarui secara berkala seiring berkembangnya proyek
dan munculnya best practice baru.
9
<?php
// =============================================================================
// 1. DATABASE MIGRATION
// File: app/Database/Migrations/2025_01_01_000001_CreateAkunTable.php
// =============================================================================
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
$this->forge->addField([
'sbb' => [
],
'rekening' => [
],
'tipe' => [
],
'keterangan' => [
10
'constraint' => 255,
],
'created_at' => [
],
'updated_at' => [
],
]);
$this->forge->addKey('sbb', true);
$this->forge->createTable('akun');
$this->forge->dropTable('akun');
// =============================================================================
// 2. MODEL
// File: app/Models/AkunModel.php
// =============================================================================
<?php
namespace App\Models;
11
use CodeIgniter\Model;
// Dates
// Validation
protected $validationRules = [
];
protected $validationMessages = [
'sbb' => [
12
'max_length' => 'Kode SBB maksimal 10 karakter'
],
'rekening' => [
],
'tipe' => [
];
/**
*/
return [
];
/**
*/
13
public function searchAkun(string $keyword = '', int $perPage = 10)
if (!empty($keyword)) {
$this->groupStart()
->like('sbb', $keyword)
->orLike('rekening', $keyword)
->orLike('tipe', $keyword)
->orLike('keterangan', $keyword)
->groupEnd();
// =============================================================================
// 3. CONTROLLER
// File: app/Controllers/AkunController.php
// =============================================================================
<?php
namespace App\Controllers;
use App\Models\AkunModel;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\ResponseInterface;
protected $akunModel;
14
protected $validation;
$this->validation = \Config\Services::validation();
/**
*/
$perPage = 10;
$data = [
];
/**
*/
15
{
if (!$this->request->isAJAX()) {
return $this->response->setStatusCode(403);
$akun = $this->akunModel->find($sbb);
if (!$akun) {
return $this->response->setJSON([
]);
return $this->response->setJSON([
]);
/**
*/
if (!$this->request->isAJAX()) {
return $this->response->setStatusCode(403);
$data = [
16
'rekening' => $this->request->getPost('rekening'),
];
if (!$this->akunModel->insert($data)) {
return $this->response->setJSON([
]);
return $this->response->setJSON([
]);
/**
*/
if (!$this->request->isAJAX()) {
return $this->response->setStatusCode(403);
$akun = $this->akunModel->find($sbb);
if (!$akun) {
return $this->response->setJSON([
17
'success' => false,
]);
$data = [
];
if (!$this->akunModel->update($sbb, $data)) {
return $this->response->setJSON([
]);
return $this->response->setJSON([
]);
/**
*/
if (!$this->request->isAJAX()) {
18
return $this->response->setStatusCode(403);
$akun = $this->akunModel->find($sbb);
if (!$akun) {
return $this->response->setJSON([
]);
if (!$this->akunModel->delete($sbb)) {
return $this->response->setJSON([
]);
return $this->response->setJSON([
]);
// =============================================================================
// 4. ROUTES
// =============================================================================
$routes->group('akun', function($routes) {
19
$routes->get('/', 'AkunController::index');
$routes->get('show/(:segment)', 'AkunController::show/$1');
$routes->post('store', 'AkunController::store');
$routes->post('update/(:segment)', 'AkunController::update/$1');
$routes->delete('delete/(:segment)', 'AkunController::delete/$1');
});
// =============================================================================
// 5. VIEW - LAYOUT
// File: app/Views/layouts/main.php
// =============================================================================
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/4.6.2/css/bootstrap.min.css"
rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"
rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/11.7.32/
sweetalert2.min.css" rel="stylesheet">
<style>
.table th {
20
background-color: #f8f9fa;
border-top: none;
.btn-action {
font-size: 0.875rem;
.modal-header {
background-color: #007bff;
color: white;
</style>
</head>
<body>
<div class="container">
</a>
</div>
</nav>
</div>
21
<!-- jQuery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/4.6.2/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/11.7.32/
sweetalert2.min.js"></script>
</body>
</html>
<?php
// =============================================================================
// File: app/Views/akun/index.php
// =============================================================================
?>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<div class="col-md-6">
</h5>
22
</div>
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="col-md-6">
<div class="input-group">
<div class="input-group-append">
</button>
</a>
</div>
</div>
</form>
23
</div>
</div>
<div class="table-responsive">
<thead>
<tr>
<th width="10%">SBB</th>
<th width="35%">Rekening</th>
<th width="15%">Tipe</th>
<th width="25%">Keterangan</th>
</tr>
</thead>
<tbody>
<tr>
</td>
</tr>
<tr>
<td>
24
</span>
</td>
<td class="text-center">
title="Edit">
</button>
title="Hapus">
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-6">
<p class="text-muted">
25
dari <?= $pager->getTotal() ?> data
</p>
</div>
<div class="col-md-6">
<div class="float-right">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-content">
<div class="modal-header">
</h5>
<span>×</span>
</button>
</div>
<form id="akunForm">
<div class="modal-body">
<div class="form-group">
26
<input type="text" class="form-control" id="sbb" name="sbb" maxlength="10"
required>
<div class="invalid-feedback"></div>
</div>
<div class="form-group">
<div class="invalid-feedback"></div>
</div>
<div class="form-group">
</select>
<div class="invalid-feedback"></div>
</div>
<div class="form-group">
<label for="keterangan">Keterangan</label>
rows="3" maxlength="255"></textarea>
<div class="invalid-feedback"></div>
</div>
</div>
<div class="modal-footer">
27
<i class="fas fa-times"></i> Batal
</button>
</button>
</div>
</form>
</div>
</div>
</div>
<script>
modalMode = mode;
currentSbb = sbb;
// Reset form
$('#akunForm')[0].reset();
$('.form-control').removeClass('is-invalid');
$('.invalid-feedback').text('');
$('#sbb').prop('readonly', false);
28
} else {
$('#sbb').prop('readonly', true);
loadAkunData(sbb);
function loadAkunData(sbb) {
$.ajax({
method: 'GET',
dataType: 'json',
success: function(response) {
if (response.success) {
$('#sbb').val(data.sbb);
$('#rekening').val(data.rekening);
$('#tipe').val(data.tipe);
$('#keterangan').val(data.keterangan);
} else {
},
error: function() {
});
29
}
// Submit form
$('#akunForm').on('submit', function(e) {
e.preventDefault();
// Show loading
$.ajax({
url: url,
method: 'POST',
data: formData,
processData: false,
contentType: false,
dataType: 'json',
success: function(response) {
if (response.success) {
$('#akunModal').modal('hide');
Swal.fire({
icon: 'success',
title: 'Berhasil!',
text: response.message,
showConfirmButton: false,
timer: 1500
}).then(() => {
30
window.location.reload();
});
} else {
$('.form-control').removeClass('is-invalid');
$('.invalid-feedback').text('');
if (response.errors) {
Object.keys(response.errors).forEach(field => {
$(`#${field}`).addClass('is-invalid');
$(`#${field}`).siblings('.invalid-feedback').text(response.errors[field]);
});
},
error: function() {
},
complete: function() {
$('#btnSubmit').prop('disabled', false);
$('#btnSubmit').html(btnText);
});
});
// Delete akun
31
function deleteAkun(sbb, rekening) {
Swal.fire({
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#dc3545',
cancelButtonColor: '#6c757d',
}).then((result) => {
if (result.isConfirmed) {
$.ajax({
method: 'DELETE',
dataType: 'json',
success: function(response) {
if (response.success) {
Swal.fire({
icon: 'success',
title: 'Berhasil!',
text: response.message,
showConfirmButton: false,
timer: 1500
}).then(() => {
window.location.reload();
});
} else {
},
32
error: function() {
});
});
$('#sbb').on('input', function() {
this.value = this.value.toUpperCase();
});
</script>
<?php
// =============================================================================
// File: app/Views/pagers/bootstrap_pagination.php
// =============================================================================
?>
<li class="page-item">
</a>
33
</li>
<li class="page-item">
</a>
</li>
</a>
</li>
<li class="page-item">
</a>
</li>
<li class="page-item">
</a>
</li>
</ul>
</nav>
34