Membuat RESTful API dengan PHP

REST API adalah cara standar untuk membuat web service yang dapat diakses oleh berbagai aplikasi. Pelajari cara membuat API yang robust dan scalable dengan PHP.

Apa itu REST API?

REST (Representational State Transfer) adalah arsitektur untuk membangun web service yang menggunakan HTTP methods untuk komunikasi antara client dan server.

Prinsip-prinsip REST

  • Stateless: Setiap request harus mengandung semua informasi yang diperlukan
  • Client-Server: Pemisahan antara client dan server yang jelas
  • Cacheable: Response dapat di-cache untuk meningkatkan performance
  • Uniform Interface: Interface yang konsisten dan standar
  • Layered System: Arsitektur berlapis
  • Code on Demand: Server dapat mengirim kode executable (opsional)

HTTP Methods

Method Purpose Example URL Description
GET Read/Retrieve GET /api/users Mendapatkan semua users
POST Create POST /api/users Membuat user baru
PUT Update/Replace PUT /api/users/1 Update user dengan ID 1
PATCH Partial Update PATCH /api/users/1 Update sebagian data user
DELETE Delete DELETE /api/users/1 Hapus user dengan ID 1

Status Codes

Success (2xx)

  • 200 OK - Request berhasil
  • 201 Created - Resource berhasil dibuat
  • 204 No Content - Berhasil tanpa response body

Error (4xx, 5xx)

  • 400 Bad Request - Request tidak valid
  • 401 Unauthorized - Butuh authentication
  • 404 Not Found - Resource tidak ditemukan
  • 500 Internal Server Error - Error server

Membuat API Dasar

Struktur Project

api/
├── index.php          # Entry point
├── config/
│   └── database.php    # Database config
├── models/
│   └── User.php        # User model
├── controllers/
│   └── UserController.php
└── .htaccess          # URL rewriting

.htaccess untuk URL Rewriting

# .htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

# Set CORS headers
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header always set Access-Control-Allow-Headers "Content-Type, Authorization"

Entry Point (index.php)

<?php
// index.php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');

// Handle preflight OPTIONS request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit;
}

// Include required files
require_once 'config/database.php';
require_once 'models/User.php';
require_once 'controllers/UserController.php';

// Parse the request
$requestMethod = $_SERVER['REQUEST_METHOD'];
$requestUri = $_SERVER['REQUEST_URI'];
$scriptName = $_SERVER['SCRIPT_NAME'];

// Remove script name from URI
$pathInfo = str_replace(dirname($scriptName), '', $requestUri);
$pathInfo = trim($pathInfo, '/');

// Split path into segments
$segments = explode('/', $pathInfo);
$resource = $segments[1] ?? '';
$id = $segments[2] ?? null;

// Simple routing
try {
    switch ($resource) {
        case 'users':
            $controller = new UserController();
            $controller->handleRequest($requestMethod, $id);
            break;
            
        default:
            http_response_code(404);
            echo json_encode(['error' => 'Endpoint not found']);
            break;
    }
} catch (Exception $e) {
    http_response_code(500);
    echo json_encode(['error' => 'Internal server error: ' . $e->getMessage()]);
}
?>

Database Configuration

<?php
// config/database.php
class Database {
    private $host = 'localhost';
    private $db_name = 'api_db';
    private $username = 'root';
    private $password = '';
    private $charset = 'utf8mb4';
    private $pdo;

    public function getConnection() {
        if ($this->pdo === null) {
            try {
                $dsn = "mysql:host={$this->host};dbname={$this->db_name};charset={$this->charset}";
                $options = [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                    PDO::ATTR_EMULATE_PREPARES => false,
                ];
                
                $this->pdo = new PDO($dsn, $this->username, $this->password, $options);
            } catch (PDOException $e) {
                throw new Exception("Database connection failed: " . $e->getMessage());
            }
        }
        
        return $this->pdo;
    }
}
?>

User Model

<?php
// models/User.php
class User {
    private $conn;
    private $table = 'users';

    public function __construct($db) {
        $this->conn = $db;
    }

    public function getAll($limit = 10, $offset = 0) {
        $stmt = $this->conn->prepare("SELECT id, name, email, created_at FROM {$this->table} LIMIT ? OFFSET ?");
        $stmt->bindParam(1, $limit, PDO::PARAM_INT);
        $stmt->bindParam(2, $offset, PDO::PARAM_INT);
        $stmt->execute();
        
        return $stmt->fetchAll();
    }

    public function getById($id) {
        $stmt = $this->conn->prepare("SELECT id, name, email, created_at FROM {$this->table} WHERE id = ?");
        $stmt->execute([$id]);
        
        return $stmt->fetch();
    }

    public function create($data) {
        $stmt = $this->conn->prepare("INSERT INTO {$this->table} (name, email, password, created_at) VALUES (?, ?, ?, NOW())");
        
        $hashedPassword = password_hash($data['password'], PASSWORD_DEFAULT);
        $stmt->execute([$data['name'], $data['email'], $hashedPassword]);
        
        return $this->conn->lastInsertId();
    }

    public function update($id, $data) {
        $fields = [];
        $params = [];
        
        foreach ($data as $field => $value) {
            if (in_array($field, ['name', 'email'])) {
                $fields[] = "$field = ?";
                $params[] = $value;
            }
        }
        
        if (empty($fields)) {
            return false;
        }
        
        $params[] = $id;
        $sql = "UPDATE {$this->table} SET " . implode(', ', $fields) . ", updated_at = NOW() WHERE id = ?";
        
        $stmt = $this->conn->prepare($sql);
        $stmt->execute($params);
        
        return $stmt->rowCount() > 0;
    }

    public function delete($id) {
        $stmt = $this->conn->prepare("DELETE FROM {$this->table} WHERE id = ?");
        $stmt->execute([$id]);
        
        return $stmt->rowCount() > 0;
    }

    public function exists($id) {
        $stmt = $this->conn->prepare("SELECT 1 FROM {$this->table} WHERE id = ?");
        $stmt->execute([$id]);
        
        return $stmt->fetch() !== false;
    }
}
?>

User Controller

<?php
// controllers/UserController.php
class UserController {
    private $user;

    public function __construct() {
        $database = new Database();
        $db = $database->getConnection();
        $this->user = new User($db);
    }

    public function handleRequest($method, $id = null) {
        switch ($method) {
            case 'GET':
                if ($id) {
                    $this->getUser($id);
                } else {
                    $this->getUsers();
                }
                break;
                
            case 'POST':
                $this->createUser();
                break;
                
            case 'PUT':
                if ($id) {
                    $this->updateUser($id);
                } else {
                    $this->sendError(400, 'User ID required for update');
                }
                break;
                
            case 'DELETE':
                if ($id) {
                    $this->deleteUser($id);
                } else {
                    $this->sendError(400, 'User ID required for delete');
                }
                break;
                
            default:
                $this->sendError(405, 'Method not allowed');
                break;
        }
    }

    private function getUsers() {
        $limit = $_GET['limit'] ?? 10;
        $offset = $_GET['offset'] ?? 0;
        
        $users = $this->user->getAll($limit, $offset);
        
        $this->sendResponse(200, [
            'success' => true,
            'data' => $users,
            'pagination' => [
                'limit' => (int)$limit,
                'offset' => (int)$offset,
                'count' => count($users)
            ]
        ]);
    }

    private function getUser($id) {
        if (!is_numeric($id)) {
            $this->sendError(400, 'Invalid user ID');
            return;
        }

        $user = $this->user->getById($id);
        
        if ($user) {
            $this->sendResponse(200, [
                'success' => true,
                'data' => $user
            ]);
        } else {
            $this->sendError(404, 'User not found');
        }
    }

    private function createUser() {
        $input = $this->getJsonInput();
        
        $errors = $this->validateUserInput($input);
        if (!empty($errors)) {
            $this->sendError(400, 'Validation failed', $errors);
            return;
        }
        
        try {
            $userId = $this->user->create($input);
            
            $this->sendResponse(201, [
                'success' => true,
                'message' => 'User created successfully',
                'data' => ['id' => $userId]
            ]);
        } catch (Exception $e) {
            $this->sendError(500, 'Failed to create user');
        }
    }

    private function updateUser($id) {
        if (!$this->user->exists($id)) {
            $this->sendError(404, 'User not found');
            return;
        }
        
        $input = $this->getJsonInput();
        
        if (empty($input)) {
            $this->sendError(400, 'No data provided');
            return;
        }
        
        try {
            $updated = $this->user->update($id, $input);
            
            if ($updated) {
                $this->sendResponse(200, [
                    'success' => true,
                    'message' => 'User updated successfully'
                ]);
            } else {
                $this->sendError(400, 'No changes made');
            }
        } catch (Exception $e) {
            $this->sendError(500, 'Failed to update user');
        }
    }

    private function deleteUser($id) {
        if (!$this->user->exists($id)) {
            $this->sendError(404, 'User not found');
            return;
        }
        
        try {
            $deleted = $this->user->delete($id);
            
            if ($deleted) {
                $this->sendResponse(200, [
                    'success' => true,
                    'message' => 'User deleted successfully'
                ]);
            } else {
                $this->sendError(500, 'Failed to delete user');
            }
        } catch (Exception $e) {
            $this->sendError(500, 'Failed to delete user');
        }
    }

    private function validateUserInput($input) {
        $errors = [];
        
        if (empty($input['name'])) {
            $errors['name'] = 'Name is required';
        }
        
        if (empty($input['email'])) {
            $errors['email'] = 'Email is required';
        } elseif (!filter_var($input['email'], FILTER_VALIDATE_EMAIL)) {
            $errors['email'] = 'Invalid email format';
        }
        
        if (empty($input['password'])) {
            $errors['password'] = 'Password is required';
        } elseif (strlen($input['password']) < 6) {
            $errors['password'] = 'Password must be at least 6 characters';
        }
        
        return $errors;
    }

    private function getJsonInput() {
        return json_decode(file_get_contents('php://input'), true) ?? [];
    }

    private function sendResponse($statusCode, $data) {
        http_response_code($statusCode);
        echo json_encode($data);
    }

    private function sendError($statusCode, $message, $details = null) {
        http_response_code($statusCode);
        $response = ['success' => false, 'error' => $message];
        
        if ($details) {
            $response['details'] = $details;
        }
        
        echo json_encode($response);
    }
}
?>

Advanced Routing

Router Class

<?php
// classes/Router.php
class Router {
    private $routes = [];
    private $middlewares = [];

    public function get($path, $handler) {
        $this->addRoute('GET', $path, $handler);
    }

    public function post($path, $handler) {
        $this->addRoute('POST', $path, $handler);
    }

    public function put($path, $handler) {
        $this->addRoute('PUT', $path, $handler);
    }

    public function delete($path, $handler) {
        $this->addRoute('DELETE', $path, $handler);
    }

    public function middleware($middleware) {
        $this->middlewares[] = $middleware;
        return $this;
    }

    private function addRoute($method, $path, $handler) {
        // Convert path parameters to regex
        $pattern = preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $path);
        $pattern = '#^' . $pattern . '$#';
        
        $this->routes[] = [
            'method' => $method,
            'pattern' => $pattern,
            'handler' => $handler,
            'middlewares' => $this->middlewares
        ];
        
        $this->middlewares = []; // Reset middlewares
    }

    public function dispatch() {
        $method = $_SERVER['REQUEST_METHOD'];
        $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        
        foreach ($this->routes as $route) {
            if ($route['method'] === $method && preg_match($route['pattern'], $path, $matches)) {
                // Extract named parameters
                $params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
                
                // Run middlewares
                foreach ($route['middlewares'] as $middleware) {
                    if (!$middleware()) {
                        return; // Middleware blocked the request
                    }
                }
                
                // Call handler
                if (is_callable($route['handler'])) {
                    call_user_func($route['handler'], $params);
                } elseif (is_string($route['handler'])) {
                    list($controller, $method) = explode('@', $route['handler']);
                    $controllerInstance = new $controller();
                    $controllerInstance->$method($params);
                }
                
                return;
            }
        }
        
        // No route found
        http_response_code(404);
        echo json_encode(['error' => 'Route not found']);
    }
}
?>

Route Definition

<?php
// routes/api.php
require_once 'classes/Router.php';
require_once 'middlewares/AuthMiddleware.php';
require_once 'controllers/UserController.php';
require_once 'controllers/ProductController.php';

$router = new Router();

// Public routes
$router->get('/api/users', 'UserController@index');
$router->get('/api/users/{id}', 'UserController@show');

// Protected routes
$router->middleware('AuthMiddleware::check')
       ->post('/api/users', 'UserController@store');

$router->middleware('AuthMiddleware::check')
       ->put('/api/users/{id}', 'UserController@update');

$router->middleware('AuthMiddleware::check')
       ->delete('/api/users/{id}', 'UserController@destroy');

// Product routes
$router->get('/api/products', 'ProductController@index');
$router->get('/api/products/{id}', 'ProductController@show');
$router->get('/api/categories/{categoryId}/products', 'ProductController@byCategory');

// Group routes with middleware
$router->middleware('AuthMiddleware::check');
$router->post('/api/products', 'ProductController@store');
$router->put('/api/products/{id}', 'ProductController@update');
$router->delete('/api/products/{id}', 'ProductController@destroy');

// Dispatch the request
$router->dispatch();
?>

Route Parameters dan Query Strings

<?php
class UserController {
    public function show($params) {
        $userId = $params['id'];
        
        // Get query parameters
        $include = $_GET['include'] ?? '';
        $fields = $_GET['fields'] ?? '';
        
        $user = $this->userModel->getById($userId);
        
        if (!$user) {
            $this->sendError(404, 'User not found');
            return;
        }
        
        // Include related data if requested
        if (strpos($include, 'posts') !== false) {
            $user['posts'] = $this->postModel->getByUserId($userId);
        }
        
        // Limit fields if specified
        if ($fields) {
            $allowedFields = explode(',', $fields);
            $user = array_intersect_key($user, array_flip($allowedFields));
        }
        
        $this->sendResponse(200, [
            'success' => true,
            'data' => $user
        ]);
    }

    public function index($params) {
        // Handle pagination
        $page = (int)($_GET['page'] ?? 1);
        $limit = (int)($_GET['limit'] ?? 10);
        $offset = ($page - 1) * $limit;
        
        // Handle filtering
        $filters = [];
        if (isset($_GET['status'])) {
            $filters['status'] = $_GET['status'];
        }
        if (isset($_GET['search'])) {
            $filters['search'] = $_GET['search'];
        }
        
        // Handle sorting
        $sortBy = $_GET['sort'] ?? 'id';
        $sortOrder = $_GET['order'] ?? 'asc';
        
        $users = $this->userModel->getAll($limit, $offset, $filters, $sortBy, $sortOrder);
        $total = $this->userModel->count($filters);
        
        $this->sendResponse(200, [
            'success' => true,
            'data' => $users,
            'meta' => [
                'current_page' => $page,
                'per_page' => $limit,
                'total' => $total,
                'last_page' => ceil($total / $limit)
            ]
        ]);
    }
}
?>

Authentication & Authorization

JWT Authentication

<?php
// First, install firebase/php-jwt via Composer
// composer require firebase/php-jwt

require_once 'vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

class JWTAuth {
    private $secretKey = 'your-secret-key-here';
    private $algorithm = 'HS256';
    private $expiration = 3600; // 1 hour

    public function generateToken($userId, $email) {
        $payload = [
            'iss' => 'your-domain.com',      // Issuer
            'aud' => 'your-domain.com',      // Audience
            'iat' => time(),                 // Issued at
            'exp' => time() + $this->expiration, // Expiration
            'user_id' => $userId,
            'email' => $email
        ];

        return JWT::encode($payload, $this->secretKey, $this->algorithm);
    }

    public function validateToken($token) {
        try {
            $decoded = JWT::decode($token, new Key($this->secretKey, $this->algorithm));
            return (array) $decoded;
        } catch (Exception $e) {
            return false;
        }
    }

    public function getTokenFromHeader() {
        $headers = getallheaders();
        
        if (isset($headers['Authorization'])) {
            $authHeader = $headers['Authorization'];
            if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
                return $matches[1];
            }
        }
        
        return null;
    }
}
?>

Authentication Controller

<?php
class AuthController {
    private $userModel;
    private $jwtAuth;

    public function __construct() {
        $database = new Database();
        $db = $database->getConnection();
        $this->userModel = new User($db);
        $this->jwtAuth = new JWTAuth();
    }

    public function login() {
        $input = json_decode(file_get_contents('php://input'), true);
        
        if (empty($input['email']) || empty($input['password'])) {
            $this->sendError(400, 'Email and password are required');
            return;
        }

        $user = $this->userModel->getByEmail($input['email']);
        
        if (!$user || !password_verify($input['password'], $user['password'])) {
            $this->sendError(401, 'Invalid credentials');
            return;
        }

        $token = $this->jwtAuth->generateToken($user['id'], $user['email']);
        
        $this->sendResponse(200, [
            'success' => true,
            'message' => 'Login successful',
            'data' => [
                'token' => $token,
                'user' => [
                    'id' => $user['id'],
                    'name' => $user['name'],
                    'email' => $user['email']
                ]
            ]
        ]);
    }

    public function register() {
        $input = json_decode(file_get_contents('php://input'), true);
        
        $errors = $this->validateRegistration($input);
        if (!empty($errors)) {
            $this->sendError(400, 'Validation failed', $errors);
            return;
        }

        // Check if email exists
        if ($this->userModel->emailExists($input['email'])) {
            $this->sendError(409, 'Email already exists');
            return;
        }

        try {
            $userId = $this->userModel->create($input);
            $token = $this->jwtAuth->generateToken($userId, $input['email']);
            
            $this->sendResponse(201, [
                'success' => true,
                'message' => 'Registration successful',
                'data' => [
                    'token' => $token,
                    'user' => [
                        'id' => $userId,
                        'name' => $input['name'],
                        'email' => $input['email']
                    ]
                ]
            ]);
        } catch (Exception $e) {
            $this->sendError(500, 'Registration failed');
        }
    }

    public function refreshToken() {
        $token = $this->jwtAuth->getTokenFromHeader();
        
        if (!$token) {
            $this->sendError(401, 'Token not provided');
            return;
        }

        $payload = $this->jwtAuth->validateToken($token);
        
        if (!$payload) {
            $this->sendError(401, 'Invalid token');
            return;
        }

        // Generate new token
        $newToken = $this->jwtAuth->generateToken($payload['user_id'], $payload['email']);
        
        $this->sendResponse(200, [
            'success' => true,
            'data' => ['token' => $newToken]
        ]);
    }

    private function validateRegistration($input) {
        $errors = [];
        
        if (empty($input['name'])) {
            $errors['name'] = 'Name is required';
        }
        
        if (empty($input['email'])) {
            $errors['email'] = 'Email is required';
        } elseif (!filter_var($input['email'], FILTER_VALIDATE_EMAIL)) {
            $errors['email'] = 'Invalid email format';
        }
        
        if (empty($input['password'])) {
            $errors['password'] = 'Password is required';
        } elseif (strlen($input['password']) < 8) {
            $errors['password'] = 'Password must be at least 8 characters';
        }
        
        return $errors;
    }

    private function sendResponse($statusCode, $data) {
        http_response_code($statusCode);
        echo json_encode($data);
    }

    private function sendError($statusCode, $message, $details = null) {
        http_response_code($statusCode);
        $response = ['success' => false, 'error' => $message];
        
        if ($details) {
            $response['details'] = $details;
        }
        
        echo json_encode($response);
    }
}
?>

Auth Middleware

<?php
// middlewares/AuthMiddleware.php
class AuthMiddleware {
    public static function check() {
        $jwtAuth = new JWTAuth();
        $token = $jwtAuth->getTokenFromHeader();
        
        if (!$token) {
            http_response_code(401);
            echo json_encode(['error' => 'Token not provided']);
            return false;
        }

        $payload = $jwtAuth->validateToken($token);
        
        if (!$payload) {
            http_response_code(401);
            echo json_encode(['error' => 'Invalid or expired token']);
            return false;
        }

        // Store user info in global variable or request object
        $_SESSION['user_id'] = $payload['user_id'];
        $_SESSION['user_email'] = $payload['email'];
        
        return true;
    }

    public static function admin() {
        if (!self::check()) {
            return false;
        }

        // Check if user is admin
        $database = new Database();
        $db = $database->getConnection();
        
        $stmt = $db->prepare("SELECT role FROM users WHERE id = ?");
        $stmt->execute([$_SESSION['user_id']]);
        $user = $stmt->fetch();
        
        if (!$user || $user['role'] !== 'admin') {
            http_response_code(403);
            echo json_encode(['error' => 'Admin access required']);
            return false;
        }
        
        return true;
    }
}
?>

Rate Limiting

<?php
class RateLimiter {
    private $redis;
    
    public function __construct() {
        // Using Redis for rate limiting
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    
    public function isAllowed($identifier, $limit = 60, $window = 3600) {
        $key = "rate_limit:" . $identifier;
        $current = $this->redis->get($key);
        
        if ($current === false) {
            $this->redis->setex($key, $window, 1);
            return true;
        }
        
        if ($current >= $limit) {
            return false;
        }
        
        $this->redis->incr($key);
        return true;
    }
    
    public function getRemainingAttempts($identifier, $limit = 60) {
        $key = "rate_limit:" . $identifier;
        $current = $this->redis->get($key);
        
        return max(0, $limit - ($current ?: 0));
    }
}

// Usage in middleware
class RateLimitMiddleware {
    public static function check($limit = 60, $window = 3600) {
        $rateLimiter = new RateLimiter();
        $identifier = $_SERVER['REMOTE_ADDR']; // Or use user ID for authenticated requests
        
        if (!$rateLimiter->isAllowed($identifier, $limit, $window)) {
            http_response_code(429);
            echo json_encode([
                'error' => 'Rate limit exceeded',
                'retry_after' => $window
            ]);
            return false;
        }
        
        return true;
    }
}
?>

Advanced API Features

API Versioning

<?php
// Version through URL
$router->get('/api/v1/users', 'V1\UserController@index');
$router->get('/api/v2/users', 'V2\UserController@index');

// Version through header
class VersionMiddleware {
    public static function check() {
        $version = $_SERVER['HTTP_API_VERSION'] ?? 'v1';
        
        if (!in_array($version, ['v1', 'v2'])) {
            http_response_code(400);
            echo json_encode(['error' => 'Unsupported API version']);
            return false;
        }
        
        define('API_VERSION', $version);
        return true;
    }
}

// Version-specific controllers
namespace V1;
class UserController {
    public function index() {
        // V1 implementation
        return ['users' => $this->userModel->getAll()];
    }
}

namespace V2;
class UserController {
    public function index() {
        // V2 implementation with additional fields
        return [
            'users' => $this->userModel->getAllWithProfile(),
            'meta' => ['version' => 'v2']
        ];
    }
}
?>

Response Transformation

<?php
class ResponseTransformer {
    public static function transform($data, $transformer = null) {
        if ($transformer && class_exists($transformer)) {
            $transformerInstance = new $transformer();
            return $transformerInstance->transform($data);
        }
        
        return $data;
    }
}

class UserTransformer {
    public function transform($user) {
        return [
            'id' => (int) $user['id'],
            'name' => $user['name'],
            'email' => $user['email'],
            'avatar' => $this->getAvatarUrl($user['email']),
            'created_at' => date('c', strtotime($user['created_at'])),
            'is_active' => (bool) $user['is_active']
        ];
    }
    
    public function transformCollection($users) {
        return array_map([$this, 'transform'], $users);
    }
    
    private function getAvatarUrl($email) {
        return 'https://www.gravatar.com/avatar/' . md5(strtolower($email));
    }
}

// Usage in controller
class UserController {
    public function index() {
        $users = $this->userModel->getAll();
        $transformer = new UserTransformer();
        
        $this->sendResponse(200, [
            'success' => true,
            'data' => $transformer->transformCollection($users)
        ]);
    }
}
?>

Caching

<?php
class CacheManager {
    private $redis;
    
    public function __construct() {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    
    public function get($key) {
        $data = $this->redis->get($key);
        return $data ? json_decode($data, true) : null;
    }
    
    public function set($key, $data, $ttl = 3600) {
        return $this->redis->setex($key, $ttl, json_encode($data));
    }
    
    public function delete($key) {
        return $this->redis->del($key);
    }
    
    public function flush($pattern = null) {
        if ($pattern) {
            $keys = $this->redis->keys($pattern);
            if (!empty($keys)) {
                return $this->redis->del($keys);
            }
        } else {
            return $this->redis->flushAll();
        }
    }
}

// Cache middleware
class CacheMiddleware {
    public static function get($ttl = 3600) {
        $cache = new CacheManager();
        $cacheKey = self::generateCacheKey();
        
        $cachedData = $cache->get($cacheKey);
        if ($cachedData) {
            header('X-Cache: HIT');
            echo json_encode($cachedData);
            return false; // Stop further execution
        }
        
        // Store cache key for later use
        $_SESSION['cache_key'] = $cacheKey;
        $_SESSION['cache_ttl'] = $ttl;
        
        return true;
    }
    
    public static function set($data) {
        if (isset($_SESSION['cache_key'])) {
            $cache = new CacheManager();
            $cache->set($_SESSION['cache_key'], $data, $_SESSION['cache_ttl']);
            header('X-Cache: MISS');
        }
    }
    
    private static function generateCacheKey() {
        $uri = $_SERVER['REQUEST_URI'];
        $method = $_SERVER['REQUEST_METHOD'];
        $user = $_SESSION['user_id'] ?? 'guest';
        
        return "api_cache:" . md5($method . $uri . $user);
    }
}
?>

Error Handling & Logging

<?php
class Logger {
    private $logFile;
    
    public function __construct($logFile = 'api.log') {
        $this->logFile = $logFile;
    }
    
    public function log($level, $message, $context = []) {
        $timestamp = date('Y-m-d H:i:s');
        $contextStr = $context ? json_encode($context) : '';
        
        $logMessage = "[{$timestamp}] {$level}: {$message} {$contextStr}" . PHP_EOL;
        
        file_put_contents($this->logFile, $logMessage, FILE_APPEND | LOCK_EX);
    }
    
    public function error($message, $context = []) {
        $this->log('ERROR', $message, $context);
    }
    
    public function info($message, $context = []) {
        $this->log('INFO', $message, $context);
    }
    
    public function warning($message, $context = []) {
        $this->log('WARNING', $message, $context);
    }
}

class ErrorHandler {
    private $logger;
    
    public function __construct() {
        $this->logger = new Logger();
        
        set_error_handler([$this, 'handleError']);
        set_exception_handler([$this, 'handleException']);
    }
    
    public function handleError($severity, $message, $file, $line) {
        $this->logger->error("PHP Error: {$message}", [
            'file' => $file,
            'line' => $line,
            'severity' => $severity
        ]);
        
        if (!(error_reporting() & $severity)) {
            return;
        }
        
        $this->sendErrorResponse(500, 'Internal server error');
    }
    
    public function handleException($exception) {
        $this->logger->error("Uncaught Exception: " . $exception->getMessage(), [
            'file' => $exception->getFile(),
            'line' => $exception->getLine(),
            'trace' => $exception->getTraceAsString()
        ]);
        
        $this->sendErrorResponse(500, 'Internal server error');
    }
    
    private function sendErrorResponse($code, $message) {
        if (!headers_sent()) {
            http_response_code($code);
            header('Content-Type: application/json');
            echo json_encode([
                'success' => false,
                'error' => $message,
                'timestamp' => date('c')
            ]);
        }
    }
}

// Initialize error handler
new ErrorHandler();
?>

API Documentation

<?php
/**
 * @api {get} /api/users Get All Users
 * @apiName GetUsers
 * @apiGroup User
 * @apiVersion 1.0.0
 * 
 * @apiParam {Number} [page=1] Page number
 * @apiParam {Number} [limit=10] Number of users per page
 * @apiParam {String} [search] Search term
 * 
 * @apiSuccess {Boolean} success Success status
 * @apiSuccess {Object[]} data Array of users
 * @apiSuccess {Number} data.id User ID
 * @apiSuccess {String} data.name User name
 * @apiSuccess {String} data.email User email
 * @apiSuccess {Object} meta Pagination metadata
 * 
 * @apiSuccessExample Success Response:
 * {
 *   "success": true,
 *   "data": [
 *     {
 *       "id": 1,
 *       "name": "John Doe",
 *       "email": "john@example.com"
 *     }
 *   ],
 *   "meta": {
 *     "current_page": 1,
 *     "per_page": 10,
 *     "total": 1
 *   }
 * }
 */
public function index() {
    // Implementation
}
?>

💡 Tips dan Best Practices

  • 🔒 Security: Selalu validasi input, gunakan HTTPS, dan implementasi rate limiting
  • 📋 Consistency: Gunakan naming convention yang konsisten untuk endpoint
  • ⚡ Performance: Implementasi caching dan pagination untuk data besar
  • 📊 Monitoring: Log semua request dan monitor performa API
  • 📚 Documentation: Dokumentasikan API dengan jelas untuk developer lain
Tutorial Saat Ini
Level: Lanjutan

Untuk yang sudah mahir PHP dan ingin mendalami

Daftar Isi
Tips Belajar