Dokumentasi

Sakuci Framework

Panduan lengkap: CLI, Routing, Model, Controller, View, Bootstrap UI, Validation, Database, Session, Middleware, dan Multi-Role Login.

Dokumentasi Sakuci

Kerangka PHP ringan bergaya Laravel tanpa Composer. Fitur: Route, Model, View, Controller, Validation, Session, Middleware, CSRF protection, Query Builder dengan eager loading, dan Bootstrap 5.3.8 built-in.

Instalasi cepat: Clone dari GitHub atau salin folder ke web server Anda.

# Clone dari GitHub
git clone https://github.com/indrabsus/sakuci-framework.git
cd sakuci-framework

# Atau download dan ekstrak folder

# Jalankan server
php sakuci serve

# Buka di browser
http://127.0.0.1:8000

Coba akun demo: admin / rahasia123 (atau staff, atau budi)

CLI -- Perintah sakuci

Jalankan semua perintah dari folder project:

php sakuci serve                    # Jalankan server (127.0.0.1:8000)
php sakuci migrate                  # Jalankan migrasi
php sakuci migrate:fresh            # Hapus semua tabel
php sakuci db:check                 # Uji koneksi database
php sakuci route:list               # Lihat semua route
php sakuci make:model Nama          # Buat model
php sakuci make:model Nama -m       # Buat model + migrasi
php sakuci make:controller Nama     # Buat controller
php sakuci make:view nama.view      # Buat view
php sakuci make:migration nama      # Buat migrasi
php sakuci view:clear               # Bersihkan cache view

Routing

Daftarkan route di routes/web.php:

use App\Controllers\PostController;
use Sakuci\Route;

// Route dasar
Route::get('/', function () { return view('welcome'); })->name('home');
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::post('/posts', [PostController::class, 'store'])->name('posts.store');

// Parameter
Route::get('/posts/{id}', [PostController::class, 'show'])->name('posts.show');
Route::get('/posts/{id}/edit', [PostController::class, 'edit'])->name('posts.edit');

// 7 route CRUD sekaligus
Route::resource('posts', PostController::class);

// Group dengan middleware
Route::group(['prefix' => 'admin', 'middleware' => 'admin'], function () {
    Route::get('/', [AdminController::class, 'index'])->name('admin.dashboard');
});

Route dapat diakses lewat nama:

route('home')                  # /
route('posts.index')            # /posts
route('posts.show', ['id' => 1]) # /posts/1
route('admin.dashboard')        # /admin/

Model & Query Builder

Model mewakili satu tabel di database. Buat dengan:

php sakuci make:model Post -m
<?php
namespace App\Models;
use Sakuci\Database\Model;

class Post extends Model
{
    protected static ?string $table = 'posts';  // opsional, ditebak dari nama
    protected array $fillable = ['title', 'body', 'status'];
    protected array $hidden = [];               // tidak tampil saat JSON
}

Query dasar:

Post::all();                           // Semua post
Post::find(1);                         // Post id=1
Post::findOrFail(1);                   // Atau 404
Post::where('status', 'published')->get();
Post::where('status', 'published')->first();
Post::latest()->limit(5)->get();
Post::count();

// Join
Post::leftJoin('users', 'users.id', '=', 'posts.user_id')
    ->select('posts.title', 'users.nama')
    ->get();

// Eager loading (cegah N+1)
Post::with('user', 'comments')->get();

// Paging
Post::paginate(15);  // 15 per halaman

CRUD:

// Create
Post::create(['title' => 'Hello', 'body' => '...', 'status' => 'published']);

// Update
$post = Post::find(1);
$post->update(['title' => 'New title']);

// Delete
$post->delete();

Controller

Buat dengan: php sakuci make:controller PostController

<?php
namespace App\Controllers;

use App\Models\Post;
use Sakuci\Controller;
use Sakuci\Http\Request;

class PostController extends Controller
{
    // GET /posts
    public function index()
    {
        $posts = Post::latest()->paginate(10);
        return view('posts.index', ['posts' => $posts]);
    }

    // GET /posts/create
    public function create()
    {
        return view('posts.create');
    }

    // POST /posts
    public function store(Request $request)
    {
        $data = $request->validate([
            'title' => 'required|min:3|max:100',
            'body'  => 'required|min:10',
        ]);

        Post::create($data);

        return redirect(route('posts.index'))
            ->with('success', 'Post berhasil dibuat.');
    }

    // GET /posts/{id} -- Route model binding
    public function show(Post $post)
    {
        return view('posts.show', ['post' => $post]);
    }

    // GET /posts/{id}/edit
    public function edit(Post $post)
    {
        return view('posts.edit', ['post' => $post]);
    }

    // PUT /posts/{id}
    public function update(Request $request, Post $post)
    {
        $data = $request->validate([
            'title' => 'required|min:3|max:100',
            'body'  => 'required|min:10',
        ]);

        $post->update($data);

        return redirect(route('posts.index'))
            ->with('success', 'Post berhasil diubah.');
    }

    // DELETE /posts/{id}
    public function destroy(Post $post)
    {
        $post->delete();

        return redirect(route('posts.index'))
            ->with('success', 'Post berhasil dihapus.');
    }
}

Penjelasan: Parameter bertipe (Post $post) otomatis diambil dari database berdasarkan {id}. Ini namanya route model binding.

View (Template)

File view ada di resources/views/ dengan akhiran .sakuci.php. Pakai sintaks Blade-like:

@extends('layouts.app')
@section('title', 'Daftar Post')
@section('content')

    <h1>Daftar Post</h1>

    {{-- Output aman (escape) --}}
    <h2>{{ $post->title }}</h2>

    {{-- Output tanpa escape --}}
    {!! $html !!}

    {{-- Kondisi --}}
    @if ($posts->count() > 0)
        Ada {{ $posts->count() }} post
    @else
        Belum ada post
    @endif

    {{-- Loop --}}
    @foreach ($posts as $post)
        <h3>{{ $post->title }}</h3>
    @endforeach

    {{-- Loop dengan else --}}
    @forelse ($posts as $post)
        <article>{{ $post->title }}</article>
    @empty
        <p>Belum ada post</p>
    @endforelse

    {{-- Include --}}
    @include('components.post-card', ['post' => $post])

    {{-- CSRF (wajib di form) --}}
    <form method="POST" action="/posts">
        @csrf
        <input type="text" name="title">
    </form>

    {{-- Flash message --}}
    @if (session('success'))
        <div class="alert">{{ session('success') }}</div>
    @endif

    {{-- Validation error --}}
    @error('title')
        <span class="error">{{ $message }}</span>
    @enderror

    {{-- Old input --}}
    <input type="text" value="{{ old('title') }}">

@endsection

Validation

Di controller, gunakan $request->validate():

$data = $request->validate([
    'nama'   => 'required|min:3|max:100',
    'email'  => 'required|email|unique:users,email',
    'umur'   => 'nullable|integer|min:18',
    'status' => 'required|in:aktif,tidak-aktif',
]);

// Kalau validasi gagal, auto-redirect dengan error & old input
// Kalau validasi lulus, $data berisi nilai yang sudah validated

Rule tersedia: required, nullable, email, url, numeric, integer, min, max, between, in, not_in, regex, alpha, alpha_num, alpha_dash, confirmed, same, different, unique, exists, date, size, array, boolean, string

Bootstrap Components

Bootstrap 5.3.8 sudah built-in. Ganti warna khas di public/css/app.css:

:root {
    --brand: #c2410c;       /* Warna utama */
    --brand-dark: #9a3412; /* Saat hover */
}

Component Bootstrap yang sering dipakai:

Kelas Fungsi
card Kotak putih dengan bayangan
btn btn-primary Tombol biru
btn btn-brand Tombol warna khas Sakuci
form-label, form-control Label & input form
is-invalid, invalid-feedback Garis merah saat validasi gagal
alert alert-success Notifikasi hijau
table table-hover Tabel
mb-3, mt-4, p-4, gap-2 Spacing (margin/padding)
row, col-md-6 Grid (responsive)
navbar navbar-expand-lg Navigation bar

Contoh form pakai Bootstrap:

<form method="POST">
    @csrf

    <div class="mb-3">
        <label class="form-label" for="nama">Nama</label>
        <input type="text" id="nama" name="nama"
               class="form-control {{ errors()->has('nama') ? 'is-invalid' : '' }}"
               value="{{ old('nama') }}">
        @error('nama')
            <div class="invalid-feedback">{{ $message }}</div>
        @enderror
    </div>

    <button class="btn btn-brand" type="submit">Simpan</button>
</form>

Database Setup

Edit .env:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=sakuci_belajar
DB_USERNAME=root
DB_PASSWORD=

Lalu buat database (lewat phpMyAdmin atau SQL):

CREATE DATABASE sakuci_belajar CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

Uji koneksi:

php sakuci db:check

Untuk SQLite (tanpa setup), cukup ubah .env:

DB_CONNECTION=sqlite

Session & Flash Message

Di controller atau view:

use Sakuci\Session;

// Simpan
Session::put('user_id', 123);
Session::put('cart', ['item1', 'item2']);

// Baca
$id = Session::get('user_id');
$cart = Session::get('cart', []);  // Default []

// Cek ada
if (Session::has('user_id')) { ... }

// Hapus
Session::forget('user_id');

// Flash (sekali pakai, auto-hapus setelah ditampilkan)
return redirect('/')->with('success', 'Berhasil login!');

Di view:

{{ session('success') }}  {{-- atau null --}}
@if (session('error'))
    <div class="alert alert-danger">{{ session('error') }}</div>
@endif

Middleware

Middleware adalah filter yang menjalankan kode sebelum/sesudah request masuk ke controller. Daftarkan di config/app.php:

'middleware' => [
    'auth'  => App\Middleware\Authenticate::class,
    'guest' => App\Middleware\RedirectIfAuthenticated::class,
    'admin' => App\Middleware\AdminOnly::class,
],

Pakai di route:

Route::get('/dashboard', [DashboardController::class, 'index'])
    ->middleware('auth');

Route::group(['middleware' => 'admin'], function () {
    Route::get('/admin', [AdminController::class, 'index']);
});

Buat middleware:

<?php
namespace App\Middleware;

use Sakuci\Http\Request;
use Sakuci\Middleware;

class MyMiddleware extends Middleware
{
    public function handle(Request $request, \Closure $next): mixed
    {
        // Sebelum masuk controller
        if (/* kondisi gagal */) {
            abort(403, 'Tidak diizinkan');
        }

        $response = $next($request);

        // Setelah controller selesai
        return $response;
    }
}

Multi-Role Login System

Sistem login dengan 3 role: admin, staff, user. Lihat awal dokumentasi untuk detailnya.

Coba login dengan salah satu akun demo:

Username Password Akses
admin rahasia123 /admin
staff rahasia123 /staff
budi rahasia123 /dashboard

Tips & Trik

Helper global yang tersedia:

view('name', ['var' => 'value'])     // Render view
route('name', ['id' => 1])           // Generate URL
redirect('/path')                    // Redirect
redirect(route('home'))
back()                               // Kembali ke halaman sebelumnya
old('field')                         // Nilai input sebelumnya
errors()                             // Object error dari validasi
session('key')                       // Baca session
dd($var)                             // Dump & die
abort(404, 'Not found')              // Abort dengan status code

Eager loading (cegah N+1):

// Buruk: 11 query (1 posts + 10 comments)
foreach (Post::all() as $post) {
    echo $post->comments;
}

// Bagus: 2 query (1 posts + 1 comments)
foreach (Post::with('comments')->get() as $post) {
    echo $post->comments;
}

// Nested
Post::with('comments.user')->get();

Join data dari tabel lain:

User::leftJoin('profil', 'profil.user_id', '=', 'users.id')
    ->select('users.nama', 'profil.alamat')
    ->get();

Untuk pertanyaan atau kontribusi, kunjungi GitHub repository.