Build Your Own Custom Lightweight PHP Framework

0
62

Creating our own PHP framework can be a rewarding experience, providing a deep understanding of the inner workings of web applications. In this article, we’ll dive into a simple PHP framework that we’ve built, explaining its architecture, request handling, routing, and key components such as database interaction and views. We’ll also highlight possible improvements and show how this framework can be expanded into a more sophisticated and feature-rich platform.

Framework Overview

The PHP framework follows the MVC (Model-View-Controller) architecture pattern, which is widely adopted for web applications. This separation of concerns makes the application more maintainable, scalable, and testable. Here’s a breakdown of the folder structure:

Folder Structure for PHP Framework

Folder Structure for PHP Framework

Follow this video for complete guidance :

app/

  • models/
    • UserModel.php: Contains the business logic and database queries related to users.
  • views/
    • users.php: A simple template to display the list of users.
  • controllers/
    • Home.php: The controller that manages user interactions and renders views.

config/

  • config.php: Configuration file that holds the database connection details.

public/

  • index.php: The entry point of the application where requests are handled.
  • .htaccess: This file is responsible for URL rewriting and routing requests to the correct controller and method.

Framework.php: This is the core class that drives the framework, handling routing, class autoloading, and request handling.

Request Handling and Routing

One of the critical components of a framework is how it handles incoming HTTP requests and routes them to the appropriate controller and method. The routing process is primarily controlled by two files in the framework: .htaccess and index.php.

The .htaccess File

The .htaccess file is located in the public/ folder and plays a pivotal role in managing URL routing. Here’s how it works:

# Enable URL rewriting
RewriteEngine On

# If the request is for an existing file or directory, don't rewrite
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

# Rewrite all requests to /public/index.php
RewriteRule ^(.*)$ index.php/$1 [QSA,L]

# Set index.php as the default file for the directory
DirectoryIndex index.php

The .htaccess file enables URL rewriting using Apache’s mod_rewrite module. This file:

  • Redirects all requests to index.php unless the requested resource is an actual file or directory on the server.
  • Sets index.php as the default file for the directory, so visitors don’t need to type it explicitly in the URL.

Essentially, every request is sent to public/index.php, which serves as the front controller for the application.

ALSO READ  Build a simple Calendar in Website using PHP (With Source Code)

The index.php File

The index.php file is the entry point of the application. It initializes the framework, sets up autoloading, and defines routes for incoming requests. Here’s a breakdown of its role:

<?php

// Enable error reporting (development mode)
error_reporting(E_ALL);
ini_set('display_errors', 1);

// Dynamically determine the base URL
$baseUrl = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/');

// Include the framework
require_once '../Framework.php';
require_once '../config/config.php';

// Initialize the framework
$framework = new Framework();

// Autoload classes
$framework->autoload();

// Define the routes with base URL consideration
$framework->route('GET', $baseUrl . '/', 'controllers\Home@index');  // Home route
$framework->route('GET', $baseUrl . '/users', 'controllers\Home@users');  // Users route

// Handle the request
$framework->handleRequest();
  • Error Reporting: In development mode, all errors are displayed to assist with debugging.
  • Base URL: The framework dynamically calculates the base URL of the application, which is important when routing requests.
  • Autoloading: Classes are loaded automatically using the autoload() method, which avoids the need to manually include class files.
  • Routing: Routes are defined using $framework->route(), which maps HTTP methods (like GET) to specific URI patterns and controller actions.
  • Request Handling: The handleRequest() method processes incoming requests, checks the URI against defined routes, and dispatches the appropriate controller action.

Core File : Framework.php

This is the heart of the framework. This handles everything related to registering routes and dispatching to correct controller action.

<?php

class Framework {

    private $routes = [];
    private $baseUrl = '';

    // Set the base URL
    public function setBaseUrl($baseUrl) {
        $this->baseUrl = $baseUrl;
    }

    // Get the base URL
    public function getBaseUrl() {
        return $this->baseUrl;
    }

    // Autoload all classes using a basic autoloader
    public function autoload() {
        spl_autoload_register(function ($class) {
            // Replace namespace separator with directory separator
            $classPath = str_replace('\\', DIRECTORY_SEPARATOR, $class);
            $file = __DIR__ . '/app/' . $classPath . '.php'; // Adjust path as necessary
            
            // If file exists, include it
            if (file_exists($file)) {
                require_once $file;
            }
        });
    }

    // Define routes
    public function route($method, $uri, $action) {
        $this->routes[] = ['method' => $method, 'uri' => $uri, 'action' => $action];
    }

    // Handle the request
    public function handleRequest() {
        $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        $method = $_SERVER['REQUEST_METHOD'];

        // Remove base URL from URI if present
        $uri = str_replace($this->baseUrl, '', $uri);

        foreach ($this->routes as $route) {
            if ($route['method'] === $method && $route['uri'] === $uri) {
                $this->dispatch($route['action']);
                return;
            }
        }

        // If no route is found, show a 404
        http_response_code(404);
        echo "404 Not Found";
    }

    // Dispatch the action to the correct controller
    private function dispatch($action) {
        list($controller, $method) = explode('@', $action);
        // Check if the controller exists
        if (class_exists($controller)) {
            $controllerObj = new $controller();
            // Check if the method exists
            if (method_exists($controllerObj, $method)) {
                $controllerObj->$method();
            } else {
                echo "Method $method not found in controller $controller.";
            }
        } else {
            echo "Controller $controller not found.";
        }
    }
}

app/controller/Home.php

This controller get the request and then processes it including coordinating with models and loading views.

<?php

namespace Controllers;

use Models\UserModel;

class Home {

    // Show the home page
    public function index() {
        echo "Welcome to the Home Page!";
    }

    // Show the list of users
    public function users() {
        // Get users from the database
        $userModel = new UserModel();
        $users = $userModel->getAllUsers();

        // Include the users view
        include __DIR__ . '/../views/users.php';
    }
}

Database Connection : config/config.php

The framework connects to the database using PDO (PHP Data Objects), which is a flexible, secure way of interacting with a database. The connection is initialized in config/config.php:

<?php
define('DB_HOST','HOSTNAME');
define('DB_NAME','DBNAME');
define('DB_USER','USERNAME');
define('DB_PASS','PASSWORD');

try{
  $conString = "mysql:host=".DB_HOST.";dbname=".DB_NAME;
  $pdo = new PDO($conString,DB_USER,DB_PASS);

  $createTable = "
    create table if not exists users(
      id int auto_increment primary key,
      name varchar(255) not null,
      email varchar(255) not null unique,
      password varchar(255) not null,
      created_at timestamp default CURRENT_TIMESTAMP
    );";
  $pdo->exec($createTable);
  
}catch(PDOException $e){
  die('Database connection failed : '.$e->getMessage());
}

 

ALSO READ  How to get current Git branch using PHP ?

Querying Database : models/UserModel.php

This configuration file defines the database credentials and establishes a connection. The UserModel class uses this connection to interact with the database and retrieve user data, such as in the getAllUsers() method:

<?php

namespace  Models;

class UserModel {

    // Reference the existing PDO connection
    private $pdo;

    // Constructor to initialize the database connection
    public function __construct() {
        global $pdo;  // Use the globally defined $pdo object
        $this->pdo = $pdo;  // Assign it to the model's $pdo property
    }

    // Get all users from the database
    public function getAllUsers() {
        $sql = "SELECT * FROM users"; // SQL query to get all users
        $stmt = $this->pdo->query($sql); // Use PDO to execute the query
        // Return all users as an associative array
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }
}

This model method fetches all users from the users table and returns them as an associative array.

Views and Rendering : views/users.php

The views are responsible for displaying data to the user. In this case, the users.php view lists all users fetched from the database:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Users List</title>
</head>
<body>

    <h1>User List</h1>

    <?php if (empty($users)): ?>
        <p>No users found.</p>
    <?php else: ?>
        <ul>
            <?php foreach ($users as $user): ?>
                <li><?php echo htmlspecialchars($user['name']); ?> (<?php echo htmlspecialchars($user['email']); ?>)</li>
            <?php endforeach; ?>
        </ul>
    <?php endif; ?>


    <a href="/">Back to Home</a>
</body>
</html>

The Home controller’s users() method loads this view by including it with the fetched user data.

Areas for Improvement

While this framework provides a solid foundation, there are several improvements that can enhance its capabilities and make it more robust.

  • Caching:
    • Implement caching mechanisms to reduce the load on the database and improve performance, especially for frequently accessed data like the user list.
    • You could use file-based or memory-based caching (e.g., Redis) to store the results of expensive database queries.
  • Query Builder / ORM:
    • Implement a query builder to simplify and sanitize database queries, reducing the risk of SQL injection and improving the readability of complex queries.
    • Consider implementing an Object-Relational Mapping (ORM) layer to handle database operations as objects, making it easier to manage relationships between tables and objects.
  • Session Handling:
      • Implement better session management to handle user authentication, user data persistence, and more.
      • You could introduce a session management class that handles user login states, flash messages, etc.
  • Authentication & Authorization:
    • Implement a full-fledged authentication system (login, registration, password reset) with role-based access control (RBAC) to protect routes from unauthorized users.
    • Use JWT (JSON Web Tokens) or sessions to manage authentication tokens.
  • Plugin System:
    • Build a plugin architecture that allows developers to easily add functionality without altering the core framework. Plugins could provide extra features such as integrations with third-party services, custom middleware, or advanced routing mechanisms.
  • Theme Customization:
    • Implement a templating engine (e.g., Twig or Blade) to separate the logic from the presentation layer more effectively.
    • Allow users to create custom themes for the views, making the framework flexible for both developers and designers.
  • Error Handling and Logging:
    • Improve error handling by creating custom exceptions and error pages, making debugging and user experience smoother.
    • Implement logging for tracking errors, performance issues, and other significant events in the application.
ALSO READ  Prevent Too Many Request From a Client using PHP

The framework you’ve built provides a simple yet effective starting point for developing PHP applications. It adheres to the MVC pattern, supports basic routing, handles database interactions, and renders views dynamically. By focusing on areas like caching, ORM, authentication, and plugins, this framework can be expanded to create a more feature-rich and scalable solution. Whether you’re building a small personal project or a larger enterprise application, this foundation offers the flexibility and extensibility needed to grow as the application evolves.

Comments are closed.