Ini adalah website personal Selamat M. Harjono
Rabu, 17 Desember 2025 | oleh Selamat Muliyadi Harjono | Materi
Durasi: 3 jam (Teori: 1.5 jam, Praktik: 1.5 jam)
A. Opening & Ice Breaking (15 menit)
B. Tujuan Bootcamp & Output Akhir (20 menit)
C. Website Statis vs Dinamis (25 menit)
D. Backend, Database, dan CMS (30 menit)
Frontend (Tampilan) ββ Backend (Logika) ββ Database (Penyimpanan)
E. Contoh Arsitektur Blog Modern (20 menit)
F. Setup Environment (60 menit - PRAKTIKUM)
Langkah-langkah:
# Download dari herd.laravel.com # Install dengan default settings
# Buka terminal php --version composer --version
<?php // buat file test.php di herd/www phpinfo(); ?>
G. PHP Overview (30 menit)
<?php echo "Hello Bootcamp!"; ?>
<!DOCTYPE html> <html> <body> <h1><?php echo "Judul Dinamis"; ?></h1> </body> </html>
Durasi: 3 jam (Teori: 1 jam, Praktik: 2 jam)
A. Review & Q&A (15 menit)
B. Variabel & Tipe Data (30 menit)
php
<?php // Deklarasi variabel $nama = "Budi"; // String $umur = 25; // Integer $tinggi = 175.5; // Float $is_mahasiswa = true; // Boolean $hobi = ["Membaca", "Coding", "Gaming"]; // Array // Output echo "Nama: $nama"; // String interpolation echo 'Umur: ' . $umur; // Concatenation ?>
C. Kontrol Alur (45 menit)
Percabangan:
php
<?php
$nilai = 85;
// If-else
if ($nilai >= 90) {
$grade = "A";
} elseif ($nilai >= 80) {
$grade = "B";
} else {
$grade = "C";
}
// Switch-case
switch($grade) {
case "A":
$keterangan = "Luar Biasa!";
break;
case "B":
$keterangan = "Baik";
break;
default:
$keterangan = "Perbaikan";
}
?>Perulangan:
php
<?php
// For loop
for($i = 1; $i <= 5; $i++) {
echo "Artikel ke-$i<br>";
}
// Foreach
$kategori = ["Teknologi", "Desain", "Bisnis"];
foreach($kategori as $kategori) {
echo "- $kategori<br>";
}
// While
$counter = 1;
while($counter <= 3) {
echo "Loading... ($counter/3)<br>";
$counter++;
}
?>
D. Function & Array (45 menit)
Function:
php
<?php
// Function dasar
function hitungLuas($panjang, $lebar) {
return $panjang * $lebar;
}
// Function dengan default parameter
function sapaUser($nama, $waktu = "pagi") {
return "Selamat $waktu, $nama!";
}
// Pemanggilan
echo hitungLuas(10, 5); // Output: 50
echo sapaUser("Ani"); // Output: Selamat pagi, Ani!
?>Array Multidimensi:
php
<?php
// Array artikel
$artikel = [
[
"judul" => "Belajar PHP",
"penulis" => "Budi",
"views" => 1000
],
[
"judul" => "Database Design",
"penulis" => "Sari",
"views" => 2500
]
];
// Akses data
echo $artikel[0]["judul"]; // Output: Belajar PHP
?>
E. Form Handling (45 menit - PRAKTIKUM)
File: form_artikel.php:
php
<!DOCTYPE html>
<html>
<head>
<title>Input Artikel</title>
</head>
<body>
<h2>Form Input Artikel</h2>
<form method="POST" action="proses_artikel.php">
<label>Judul:</label><br>
<input type="text" name="judul" required><br><br>
<label>Konten:</label><br>
<textarea name="konten" rows="5" required></textarea><br><br>
<label>Kategori:</label><br>
<select name="kategori">
<option value="teknologi">Teknologi</option>
<option value="desain">Desain</option>
<option value="bisnis">Bisnis</option>
</select><br><br>
<label>Tags (pisahkan koma):</label><br>
<input type="text" name="tags" placeholder="PHP, Database, Web"><br><br>
<input type="submit" value="Simpan Artikel">
</form>
</body>
</html>File: proses_artikel.php:
php
<?php
// Tangkap data dari form
$judul = $_POST['judul'];
$konten = $_POST['konten'];
$kategori = $_POST['kategori'];
$tags = explode(',', $_POST['tags']); // Convert ke array
// Simpan ke session (sementara)
session_start();
$_SESSION['artikel'] = [
'judul' => $judul,
'konten' => $konten,
'kategori' => $kategori,
'tags' => $tags,
'tanggal' => date('Y-m-d H:i:s')
];
// Redirect ke halaman tampil
header('Location: tampil_artikel.php');
?>
F. Session Management (30 menit)
php
<?php
// File: tampil_artikel.php
session_start();
// Cek apakah ada data session
if(!isset($_SESSION['artikel'])) {
die("Tidak ada artikel untuk ditampilkan");
}
$artikel = $_SESSION['artikel'];
?>
<!DOCTYPE html>
<html>
<body>
<h1><?php echo htmlspecialchars($artikel['judul']); ?></h1>
<p><strong>Kategori:</strong> <?php echo $artikel['kategori']; ?></p>
<p><strong>Tanggal:</strong> <?php echo $artikel['tanggal']; ?></p>
<hr>
<p><?php echo nl2br(htmlspecialchars($artikel['konten'])); ?></p>
<h3>Tags:</h3>
<ul>
<?php foreach($artikel['tags'] as $tag): ?>
<li><?php echo trim($tag); ?></li>
<?php endforeach; ?>
</ul>
<a href="form_artikel.php">Tambah Artikel Baru</a>
</body>
</html>G. Studi Kasus: Sistem Artikel Sederhana (60 menit - PRAKTIKUM)
Tugas Kelompok:
Durasi: 3 jam (Teori: 1.5 jam, Praktik: 1.5 jam)
A. Review & Problem Statement (30 menit)
B. Konsep Data vs Informasi (30 menit)
Data: "25", "Budi", "2024-01-15" Informasi: "Budi berusia 25 tahun, lahir tanggal 15 Januari 2024"
C. Entity, Attribute, Relationship (45 menit)
1. Entity (Entitas):
2. Attribute (Atribut):
3. Relationship (Hubungan):
D. Primary Key & Foreign Key (30 menit)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE
);CREATE TABLE posts (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
FOREIGN KEY (user_id) REFERENCES users(id)
);
E. Cardinality (30 menit)
User (1) ββββ (1) UserProfile
User (1) ββββ (N) Post
Post (N) ββββ (M) Tag
F. ERD untuk Blog System (60 menit - PRAKTIKUM)
Langkah-langkah desain:
User Post Category Tag Comment Portfolio
User: - id (PK) - username - email - password - role - created_at Post: - id (PK) - title - slug - content - excerpt - featured_image - user_id (FK) - category_id (FK) - status - published_at - created_at - updated_at
User βββ¨writesβ©ββ Post (1:N) Post βββ¨belongs toβ©ββ Category (N:1) Post βββ¨hasβ©ββ Tag (N:M) via post_tag Post βββ¨receivesβ©ββ Comment (1:N) User βββ¨hasβ©ββ Portfolio (1:N)
[USER]ββββββ
1 β
β writes
N
[POST]ββββ β
N β β
β β
[CATEGORY] β
1 β
Mβ
βN
[TAG]
G. Praktikum: Desain ERD Blog & Portfolio (60 menit)
Kelompok 3-4 orang:
Durasi: 3 jam (Teori: 1 jam, Praktik: 2 jam)
A. Review & Presentasi ERD (45 menit)
B. Anomali Data (30 menit)
Contoh tabel tidak ternormalisasi:
sql
-- TABEL TIDAK NORMAL (bermasalah)
CREATE TABLE post_data (
post_id INT,
title VARCHAR(200),
author_name VARCHAR(100),
author_email VARCHAR(100),
category_name VARCHAR(50),
tag1 VARCHAR(30),
tag2 VARCHAR(30),
tag3 VARCHAR(30)
);Masalah (Anomali):
C. 1NF - First Normal Form (30 menit)
Rules:
Contoh perbaikan:
sql
-- MASALAH: repeating groups (tag1, tag2, tag3) -- SOLUSI: Buat tabel terpisah untuk tags
D. 2NF - Second Normal Form (30 menit)
Rules:
Contoh:
sql
-- MASALAH: author_name tergantung author_id, bukan post_id -- author_email juga tergantung author_id -- SOLUSI: Pisahkan ke tabel authors
E. 3NF - Third Normal Form (30 menit)
Rules:
Contoh:
sql
-- MASALAH: category_description tergantung category_name -- yang tergantung post_id (transitive) -- SOLUSI: Pisahkan ke tabel categories
F. Tabel Junction (Many-to-Many) (45 menit)
Studi Kasus: Post - Tag:
sql
-- Post dan Tag: Many-to-Many relationship
-- SOLUSI: Buat junction table
-- Tabel posts
CREATE TABLE posts (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200),
content TEXT
);
-- Tabel tags
CREATE TABLE tags (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) UNIQUE
);
-- JUNCTION TABLE: post_tag
CREATE TABLE post_tag (
post_id INT,
tag_id INT,
PRIMARY KEY (post_id, tag_id),
FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);
G. Studi Kasus Lengkap: Normalisasi Sistem Blog (60 menit)
Dari ERD ke Tabel Normalized:
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
full_name VARCHAR(100),
bio TEXT,
avatar_url VARCHAR(255),
role ENUM('admin', 'author', 'guest') DEFAULT 'guest',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);CREATE TABLE categories (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) UNIQUE NOT NULL,
slug VARCHAR(60) UNIQUE NOT NULL,
description TEXT,
parent_id INT NULL,
FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);CREATE TABLE posts (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200) NOT NULL,
slug VARCHAR(220) UNIQUE NOT NULL,
excerpt TEXT,
content LONGTEXT NOT NULL,
featured_image VARCHAR(255),
status ENUM('draft', 'published', 'archived') DEFAULT 'draft',
published_at TIMESTAMP NULL,
user_id INT NOT NULL,
category_id INT,
view_count INT DEFAULT 0,
meta_title VARCHAR(200),
meta_description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
);CREATE TABLE tags (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) UNIQUE NOT NULL,
slug VARCHAR(60) UNIQUE NOT NULL
);
CREATE TABLE post_tag (
post_id INT NOT NULL,
tag_id INT NOT NULL,
PRIMARY KEY (post_id, tag_id),
FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);CREATE TABLE comments (
id INT PRIMARY KEY AUTO_INCREMENT,
content TEXT NOT NULL,
status ENUM('pending', 'approved', 'spam') DEFAULT 'pending',
user_id INT NULL,
post_id INT NOT NULL,
parent_id INT NULL,
author_name VARCHAR(100),
author_email VARCHAR(100),
author_ip VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
FOREIGN KEY (parent_id) REFERENCES comments(id) ON DELETE CASCADE
);CREATE TABLE portfolios (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200) NOT NULL,
slug VARCHAR(220) UNIQUE NOT NULL,
description TEXT,
project_url VARCHAR(255),
client_name VARCHAR(100),
project_date DATE,
featured_image VARCHAR(255),
gallery_images JSON,
user_id INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
H. Praktikum: Normalisasi ERD ke Skema SQL (60 menit)
Tugas Individu:
Durasi: 3 jam (Teori: 1 jam, Praktik: 2 jam)
A. Review & Q&A (30 menit)
B. DDL vs DML (30 menit)
DDL (Data Definition Language):
sql
-- CREATE CREATE DATABASE blog_db; CREATE TABLE users (...); -- ALTER ALTER TABLE posts ADD COLUMN reading_time INT; -- DROP DROP TABLE temp_data; -- TRUNCATE (hapus semua data, reset auto_increment) TRUNCATE TABLE logs;
DML (Data Manipulation Language):
sql
-- INSERT
INSERT INTO users (username, email)
VALUES ('budi', 'budi@email.com');
-- UPDATE
UPDATE posts SET view_count = view_count + 1
WHERE id = 5;
-- DELETE
DELETE FROM comments
WHERE status = 'spam' AND created_at < '2024-01-01';
-- SELECT
SELECT * FROM posts WHERE status = 'published';C. Indexes (30 menit)
Untuk optimasi query:
sql
-- Single column index
CREATE INDEX idx_posts_status ON posts(status);
CREATE INDEX idx_posts_user ON posts(user_id);
-- Composite index (untuk query multi-kolom)
CREATE INDEX idx_posts_user_status ON posts(user_id, status);
-- Unique index
CREATE UNIQUE INDEX idx_users_username ON users(username);
-- Full-text index (untuk search)
ALTER TABLE posts
ADD FULLTEXT idx_fulltext_search (title, content);
-- Query dengan full-text
SELECT * FROM posts
WHERE MATCH(title, content) AGAINST('laravel tutorial' IN NATURAL LANGUAGE MODE);D. Views (30 menit)
Virtual tables untuk simplify query:
sql
-- View untuk post yang published
CREATE VIEW v_published_posts AS
SELECT
p.id,
p.title,
p.slug,
p.excerpt,
p.featured_image,
p.published_at,
u.username as author_name,
c.name as category_name
FROM posts p
JOIN users u ON p.user_id = u.id
LEFT JOIN categories c ON p.category_id = c.id
WHERE p.status = 'published'
ORDER BY p.published_at DESC;
-- Penggunaan view
SELECT * FROM v_published_posts
WHERE category_name = 'Teknologi';
-- View untuk statistic
CREATE VIEW v_blog_statistics AS
SELECT
(SELECT COUNT(*) FROM posts WHERE status = 'published') as total_posts,
(SELECT COUNT(*) FROM users) as total_users,
(SELECT COUNT(*) FROM comments WHERE status = 'approved') as total_comments,
(SELECT SUM(view_count) FROM posts) as total_views;E. Triggers (60 menit)
1. Auto-generate slug:
sql
DELIMITER $$
CREATE TRIGGER tr_posts_before_insert
BEFORE INSERT ON posts
FOR EACH ROW
BEGIN
IF NEW.slug IS NULL OR NEW.slug = '' THEN
-- Convert title to slug
SET NEW.slug = LOWER(REPLACE(NEW.title, ' ', '-'));
SET NEW.slug = REGEXP_REPLACE(NEW.slug, '[^a-z0-9-]', '');
-- Ensure uniqueness
SET @counter = 1;
SET @base_slug = NEW.slug;
WHILE EXISTS (SELECT 1 FROM posts WHERE slug = NEW.slug) DO
SET NEW.slug = CONCAT(@base_slug, '-', @counter);
SET @counter = @counter + 1;
END WHILE;
END IF;
END$$
DELIMITER ;2. Activity log trigger:
sql
CREATE TABLE activity_logs (
id INT PRIMARY KEY AUTO_INCREMENT,
table_name VARCHAR(50) NOT NULL,
record_id INT NOT NULL,
action ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL,
old_data JSON,
new_data JSON,
user_id INT,
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
DELIMITER $$
CREATE TRIGGER tr_posts_after_update
AFTER UPDATE ON posts
FOR EACH ROW
BEGIN
INSERT INTO activity_logs (
table_name,
record_id,
action,
old_data,
new_data,
user_id
) VALUES (
'posts',
OLD.id,
'UPDATE',
JSON_OBJECT(
'title', OLD.title,
'status', OLD.status,
'content', LEFT(OLD.content, 500)
),
JSON_OBJECT(
'title', NEW.title,
'status', NEW.status,
'content', LEFT(NEW.content, 500)
),
NEW.user_id
);
END$$
DELIMITER ;3. Update timestamp trigger:
sql
DELIMITER $$
CREATE TRIGGER tr_posts_update_timestamp
BEFORE UPDATE ON posts
FOR EACH ROW
BEGIN
SET NEW.updated_at = CURRENT_TIMESTAMP;
END$$
DELIMITER ;F. Constraints & Cascading (30 menit)
sql
-- Foreign Key dengan berbagai cascade options
CREATE TABLE posts (
-- ...
user_id INT NOT NULL,
category_id INT,
-- CASCADE: hapus user, hapus semua postnya
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE CASCADE,
-- SET NULL: hapus category, set category_id jadi NULL
FOREIGN KEY (category_id)
REFERENCES categories(id)
ON DELETE SET NULL,
-- NO ACTION: default, gagal jika ada reference
FOREIGN KEY (category_id)
REFERENCES categories(id)
ON DELETE NO ACTION,
-- RESTRICT: sama seperti NO ACTION
FOREIGN KEY (category_id)
REFERENCES categories(id)
ON DELETE RESTRICT
);
-- Check constraint (MySQL 8.0+)
ALTER TABLE posts
ADD CONSTRAINT chk_post_status
CHECK (status IN ('draft', 'published', 'archived'));
ALTER TABLE users
ADD CONSTRAINT chk_user_email
CHECK (email LIKE '%@%.%');G. Praktikum Langsung di MySQL (90 menit)
Skenario: Implementasi Database Blog:
CREATE DATABASE blog_portfolio; USE blog_portfolio;
-- Index untuk query yang sering digunakan CREATE INDEX idx_posts_published ON posts(published_at, status); CREATE INDEX idx_comments_post ON comments(post_id, status); CREATE INDEX idx_portfolios_user ON portfolios(user_id);
-- View untuk dashboard admin
CREATE VIEW v_admin_dashboard AS
SELECT
(SELECT COUNT(*) FROM posts) as total_posts,
(SELECT COUNT(*) FROM posts WHERE status = 'published') as published_posts,
(SELECT COUNT(*) FROM users) as total_users,
(SELECT COUNT(*) FROM comments) as total_comments,
(SELECT COUNT(*) FROM portfolios) as total_portfolios;
-- View untuk recent activity
CREATE VIEW v_recent_activity AS
SELECT 'post' as type, title as description, created_at
FROM posts
UNION ALL
SELECT 'comment' as type, CONCAT('Comment on post #', post_id) as description, created_at
FROM comments
ORDER BY created_at DESC
LIMIT 20;-- Trigger untuk auto-slug categories
DELIMITER $$
CREATE TRIGGER tr_categories_before_insert
BEFORE INSERT ON categories
FOR EACH ROW
BEGIN
IF NEW.slug IS NULL THEN
SET NEW.slug = LOWER(REPLACE(NEW.name, ' ', '-'));
END IF;
END$$
DELIMITER ;
-- Trigger untuk update view count
DELIMITER $$
CREATE TRIGGER tr_posts_after_select
AFTER SELECT ON posts
FOR EACH ROW
BEGIN
-- Ini contoh konsep, implementasi sebenarnya butuh application logic
-- atau stored procedure
END$$
DELIMITER ;-- Insert users
INSERT INTO users (username, email, password_hash, role) VALUES
('admin', 'admin@blog.com', 'hashed_password', 'admin'),
('writer1', 'writer1@blog.com', 'hashed_password', 'author'),
('guest1', 'guest1@blog.com', 'hashed_password', 'guest');
-- Insert categories
INSERT INTO categories (name, slug) VALUES
('Teknologi', 'teknologi'),
('Desain', 'desain'),
('Bisnis', 'bisnis');
-- Insert posts
INSERT INTO posts (title, content, user_id, category_id, status, published_at) VALUES
('Belajar Database', 'Content...', 2, 1, 'published', NOW()),
('Tips Desain UI', 'Content...', 2, 2, 'published', NOW());
-- Insert tags
INSERT INTO tags (name, slug) VALUES
('PHP', 'php'),
('MySQL', 'mysql'),
('Laravel', 'laravel');
-- Insert post_tag relationships
INSERT INTO post_tag (post_id, tag_id) VALUES
(1, 1), (1, 2), -- Post 1 punya tag PHP dan MySQL
(2, 3); -- Post 2 punya tag Laravel-- Query: Dapatkan post dengan detail author, category, dan tags
SELECT
p.title,
p.published_at,
u.username as author,
c.name as category,
GROUP_CONCAT(t.name SEPARATOR ', ') as tags
FROM posts p
JOIN users u ON p.user_id = u.id
LEFT JOIN categories c ON p.category_id = c.id
LEFT JOIN post_tag pt ON p.id = pt.post_id
LEFT JOIN tags t ON pt.tag_id = t.id
WHERE p.status = 'published'
GROUP BY p.id
ORDER BY p.published_at DESC;
-- Query: Statistik post per kategori
SELECT
c.name as category,
COUNT(p.id) as post_count,
SUM(p.view_count) as total_views
FROM categories c
LEFT JOIN posts p ON c.id = p.category_id AND p.status = 'published'
GROUP BY c.id
ORDER BY post_count DESC;-- Test auto-slug trigger
INSERT INTO posts (title, content, user_id, slug)
VALUES ('Judul Test', 'Content...', 2, '');
-- Cek: slug akan otomatis terisi 'judul-test'
-- Test activity log
UPDATE posts SET title = 'Judul Updated' WHERE id = 1;
-- Cek tabel activity_logs
SELECT * FROM activity_logs;Buat database lengkap untuk sistem Blog & Portfolio:
Delivery: