The Model class handles all database operations in MINI. It uses PDO for secure database access with prepared statements.Located at: application/model/model.php
In MINI, all database methods are in a single Model class for simplicity. In larger applications, you might create separate model classes for different entities.
public function getAllSongs(){ $sql = "SELECT id, artist, track, link FROM song"; $query = $this->db->prepare($sql); $query->execute(); // fetchAll() is the PDO method that gets all result rows return $query->fetchAll();}
public function getSong($song_id){ $sql = "SELECT id, artist, track, link FROM song WHERE id = :song_id LIMIT 1"; $query = $this->db->prepare($sql); $parameters = array(':song_id' => $song_id); $query->execute($parameters); // fetch() is the PDO method that gets exactly one result return $query->fetch();}
public function getAmountOfSongs(){ $sql = "SELECT COUNT(id) AS amount_of_songs FROM song"; $query = $this->db->prepare($sql); $query->execute(); // fetch() gets exactly one result return $query->fetch()->amount_of_songs;}
public function deleteSong($song_id){ $sql = "DELETE FROM song WHERE id = :song_id"; $query = $this->db->prepare($sql); $parameters = array(':song_id' => $song_id); $query->execute($parameters);}
This is a permanent deletion. In production applications, consider soft deletes or adding confirmation dialogs.
Usage:
// In controller:public function delete($song_id){ // In a real app, add authentication and authorization checks $this->model->deleteSong($song_id); header('location: ' . URL . 'songs');}
MINI includes a helper function to debug PDO queries:
$sql = "SELECT id, artist, track FROM song WHERE id = :song_id LIMIT 1";$query = $this->db->prepare($sql);$parameters = array(':song_id' => $song_id);// Debug: see the actual SQL that will be executedecho Helper::debugPDO($sql, $parameters);// Output: SELECT id, artist, track FROM song WHERE id = '5' LIMIT 1$query->execute($parameters);
Use Helper::debugPDO() during development to see the exact SQL query with parameters substituted. Remove these calls in production.
// Search songs by artistpublic function searchSongsByArtist($search_term){ $sql = "SELECT id, artist, track, link FROM song WHERE artist LIKE :search"; $query = $this->db->prepare($sql); $parameters = array(':search' => '%' . $search_term . '%'); $query->execute($parameters); return $query->fetchAll();}// Get songs with paginationpublic function getSongsPaginated($offset, $limit){ $sql = "SELECT id, artist, track, link FROM song LIMIT :offset, :limit"; $query = $this->db->prepare($sql); // Bind parameters with explicit types for LIMIT clause $query->bindValue(':offset', (int)$offset, PDO::PARAM_INT); $query->bindValue(':limit', (int)$limit, PDO::PARAM_INT); $query->execute(); return $query->fetchAll();}// Get songs ordered by datepublic function getRecentSongs($limit = 10){ $sql = "SELECT id, artist, track, link FROM song ORDER BY id DESC LIMIT :limit"; $query = $this->db->prepare($sql); $query->bindValue(':limit', (int)$limit, PDO::PARAM_INT); $query->execute(); return $query->fetchAll();}
Never concatenate user input directly into SQL queries:
// NEVER DO THIS (SQL Injection vulnerability):$sql = "SELECT * FROM song WHERE id = " . $_GET['id'];$query = $this->db->query($sql);// ALWAYS DO THIS:$sql = "SELECT * FROM song WHERE id = :id";$query = $this->db->prepare($sql);$query->execute([':id' => $_GET['id']]);
Use Named Placeholders
Named placeholders (:name) are more readable than positional placeholders (?):
PDO handles input escaping automatically. Your job is to escape output in views:
// In model: Store raw input (PDO handles escaping)$this->model->addSong($_POST['artist'], $_POST['track'], $_POST['link']);// In view: Escape output to prevent XSSecho htmlspecialchars($song->artist, ENT_QUOTES, 'UTF-8');
public function getSongsWithAlbums(){ $sql = "SELECT s.id, s.artist, s.track, a.album_name FROM song s LEFT JOIN album a ON s.album_id = a.id"; $query = $this->db->prepare($sql); $query->execute(); return $query->fetchAll();}