Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

How to Build a PHP Plugin Module System

INTRODUCTION
WHAT IS A PLUGIN?

Welcome to a tutorial on how to create a Php Plugin Module system. You have probably seen a ton of PHP frameworks in the wild digital world, maybe even used one of those – Symfony, CodeIgniter, Cake, Laravel, Yii, and a whole bunch more. Yes, frameworks are great and convenient, but they are not plugins. 

Let me quote Wikipedia now – A plugin is a software component that adds a specific feature to an existing computer program. So yes, when we mention building a plugin system, we will be creating a Core system that is also “extendable” at the same time. Just how do we achieve this in PHP? Well, there are endless possibilities for doing so, and everyone has a different take.

So keep in mind when going through this guide. This is ultimately not “I must do it this way”, but a sharing of how an evil genius senior web developer builds his “extendable systems”. There will be some rather brain damaging concepts in this guide, but I will also try to keep it simple and not-so-boring. Read on to find out!

I have included a zip file with all the source code at the end of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

CONFESSION
AN HONEST DISCLOSURE

Quick, hide your wallets! I am an affiliate partner of Google, eBay, Adobe, Bluehost, Clickbank, and more. There are affiliate links and advertisements throughout this website. Whenever you buy things from the evil links that I recommend, I will make a commission. Nah. These are just things to keep the blog going, and allows me to give more good stuff to you guys - for free. So thank you if you decide to pick up my recommendations!


 

NAVIGATION
TABLE OF CONTENTS

Step 1
Folder Structure

Step 2
The Core System

Step 3
Extending The System

Extra
AJAX Handling

Extra
Download & More

Closing
What’s Next?

STEP 1
FOLDER STRUCTURE

The first step of every project is not really about coding, but to get organized. Yep, it is painful to see beginners chuck random pieces of code everywhere, and not know which is which. These “Frankenstein” projects are often very difficult to maintain and troubleshoot, as we will have to trace through a hundred different fragments to find a bug.

A truly good core system has all the library functions in one folder, grouped by their purposes, and the moment something screws up – We immediately know what went wrong, and where to look.

THE RAW BASIC FOLDERS

It does not require rocket science, nor do you need to be some kind of “tidying guru” to get organized. My personal way of implementing a clean structure is to start a new project by creating 2 basic folders:

  • All the landing pages will be placed in the root of the project folder.
  • The lib folder will contain all the core PHP library files that you don’t want the public to mess with.
  • The assets folder will contain all the public CSS, Javascript, and images.

Yes, that’s all to it, but I will further recommend creating a .htaccess file in the lib folder to protect it:

lib/.htaccess
Deny from all

For those of you who do not know that this does, Apache web server will deny all direct access to the scripts within this folder. For example, if a user tries to directly access http://mysite.com/lib/lib-Base.php, that will throw an unauthorized access error. Don’t worry though, we can still include "lib/lib-Base.php" in all our scripts.

As for a final small piece of advice for the organization – Please feel free to create sub-folders within the  assets folder, for example, you might want to separate out the scripts, images, uploads, and documents.

STEP 2
THE CORE SYSTEM

Now that we have created the basic project folders, let us officially begin building the core system itself.

CONFIG FILE

lib/config.php

The first script that we have to write is a config file, just somewhere to safely store all the settings and stuff. Please remember to change the database settings to your own.

BASE LIBRARY

lib/lib-Base.php
$module)) {
      if (is_object($this->$module)) {
        return true;
      } else {
        $this->error = "'$module' is already defined!";
        return false;
      }
    }

    // Extend the base object
    $file = PATH_LIB . "lib-" . $module . ".php";
    if (file_exists($file)) {
      // Include library file and create child module
      require_once $file;
      $this->$module = new $module();

      // The evil base object pointer...
      // This will allow sibling objects to communication with each other
      // Otherwise impossible with traditional OOP
      $this->$module->base =& $this;
      $this->$module->error =& $this->error;

      // Done!
      return true;
    } else {
      $this->error = "'$file' not found!";
      return false;
    }
  }
}

This is the base class with a single function that will do all the magic. The whole idea of a plugin module system is to have a “centralized object”, then we can easily write plugin modules to extend the features. For example:

extend("User");

// WE CAN NOW USE THE USER MODULE
$users = $_BASE->User->getAll();

If you have not traced through the extend function, here is how it works in a nutshell:

  • The extend function takes in a single parameter – The module name $module.
  • It will then check if the module is already extended –  is_object($this->$module).
  • If it is not extended, it will automatically look for and include the physical file in the lib folder – require_once "lib/lib-$module.php".
  • The new module object is then extended to the base – $this->$module = new $module().
  • Take note of the evil pointer link $this->$module->base =& $this, we will go through more of this in the next step.

DATABASE LIBRARY

lib/lib-DB.php
pdo = new PDO(
        $str, DB_USER, DB_PASSWORD, [
          PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
          PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
          PDO::ATTR_EMULATE_PREPARES => false
        ]
      );
    }

    // ERROR - CRITICAL STOP - THROW ERROR MESSAGE
    catch (Exception $ex) {
      print_r($ex);
      die();
    }
  }

  function __destruct () {
  // __destruct() : close connection when done

    if ($this->stmt !== null) { $this->stmt = null; }
    if ($this->pdo !== null) { $this->pdo = null; }
  }

  function exec ($sql, $data=null) {
  // exec() : run insert, replace, update, delete query
  // PARAM $sql : SQL query
  //       $data : array of data

    try {
      $this->stmt = $this->pdo->prepare($sql);
      $this->stmt->execute($data);
      $this->lastID = $this->pdo->lastInsertId();
    } catch (Exception $ex) {
      $this->base->error = $ex;
      return false;
    }
    $this->stmt = null;
    return true;
  }

  function start () {
  // start() : auto-commit off

    $this->pdo->beginTransaction();
  }

  function end ($commit=1) {
  // end() : commit or roll back?

    if ($commit) { $this->pdo->commit(); }
    else { $this->pdo->rollBack(); }
  }

  function fetchAll ($sql, $cond=null, $key=null, $value=null) {
  // fetchAll() : perform select query (multiple rows expected)
  // PARAM $sql : SQL query
  //       $cond : array of conditions
  //       $key : sort in this $key=>data order, optional
  //       $value : $key must be provided. If string provided, sort in $key=>$value order. If function provided, will be a custom sort.

    $result = [];
    try {
      $this->stmt = $this->pdo->prepare($sql);
      $this->stmt->execute($cond);
      // Sort in given order
      if (isset($key)) {
        if (isset($value)) {
          if (is_callable($value)) {
            while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
              $result[$row[$key]] = $value($row);
            }
          } else {
            while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
              $result[$row[$key]] = $row[$value];
            }
          }
        } else {
          while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
            $result[$row[$key]] = $row;
          }
        }
      }
      // No key-value sort order
      else {
        $result = $this->stmt->fetchAll();
      }
    } catch (Exception $ex) {
      $this->error = $ex;
      return false;
    }
    // Return result
    $this->stmt = null;
    return count($result)==0 ? false : $result ;
  }

  function fetch ($sql, $cond=null, $sort=null) {
  // fetch() : perform select query (single row expected)
  // PARAM $sql : SQL query
  //       $cond : array of conditions
  //       $sort : custom sort function

    $result = [];
    try {
      $this->stmt = $this->pdo->prepare($sql);
      $this->stmt->execute($cond);
      if (is_callable($sort)) {
        while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
          $result = $sort($row);
        }
      } else {
        while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
          $result = $row;
        }
      }
    } catch (Exception $ex) {
      $this->error = $ex;
      return false;
    }
    // Return result
    $this->stmt = null;
    return count($result)==0 ? false : $result ;
  }
}

This first plugin that we shall introduce is also an essential piece of the core system – The database module.

FunctionDescription
__constructThe constructor. Will connect to the database when the object is being created.
__destructThe destructor. Will close the database connection when the object is being destroyed.
execRun an insert, replace, update, or delete query.
fetchAllRun a select query – When you expect multiple rows of results.
fetchRun a select query – When you expect a single row of result.
startAuto-commit off, used in conjunction with end, for a series of queries.
endCommit or rollback? Used in conjunction with start.

CORE STARTER

lib/core.php
extend("DB");

Now that we have created all the necessary libraries and config file, this is just a script to put all of them together. This is the “firestarter” that will start the session, create the base object, and extend the database module – We will include this core into all of our pages.

STEP 3
EXTENDING THE SYSTEM

Finally, we have a working core system. But how do all of these work, and how do we create plugins? Let us create a simple users plugin in this section as an example.

THE USER DATABASE TABLE

users.sql
CREATE TABLE `users` (
  `user_id` int(11) NOT NULL,
  `user_name` varchar(255) NOT NULL,
  `user_email` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `users` (`user_id`, `user_name`, `user_email`) VALUES
(1, 'John Doe', '[email protected]'),
(2, 'Jane Doe', '[email protected]'),
(3, 'Josh Doe', '[email protected]'),
(4, 'Joy Doe', '[email protected]'),
(5, 'Jasmine Doe', 'jasmine@doe');

ALTER TABLE `users`
  ADD PRIMARY KEY (`user_id`),
  ADD UNIQUE KEY `user_email` (`user_email`),
  ADD KEY `user_name` (`user_name`);

ALTER TABLE `users`
  MODIFY `user_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;
COMMIT;

First, let us create a simple dummy users table.

FieldDescription
user_idThe user ID. Primary key, auto-increment.
user_nameThe user’s name.
user_emailThe user’s email.

THE USER LIBRARY FILE

Remember that the base object extend function will look for lib/lib-$module.php, and then create $this->$module = new $module()? We only have to create the respective library file and class accordingly:

lib/lib-User.php
base->DB->fetchAll(
      "SELECT * FROM `users`", null, "user_id"
    );
  }
}

Yep, it is as simple and lazy as it gets. Also, remember from earlier that there is a pointer $this->$module->base =& $this? Notice how we are using that evil base object pointer to access and use the database function $this->base->DB->fetchAll() now?

With that pointer, it is possible for our modules to talk with each other. For example, if we create a report class in the future, we can easily use $this->base->User->getAll() to fetch all the users and generate a report using it. This is what makes a plugin system great, and saves us a lot of time by reusing the existing functions.

LANDING PAGE

Finally, we only have to use our base system and modules to generate the HTML page. For example:

users-show.php
extend("User");
$users = $_BASE->User->getAll();

// THE HTML ?>


  
    Users List Demo$u) {
        printf("",
          $u['user_name'], $u['user_email']
        );
      }
      ?>
    
Name Email
%s%s

This is definitely a lot better than a mess of inline functions, array rearrange, and HTML all-in-one, aye?

EXTRA
AJAX HANDLING

Of course, that is not the end of the entire system, and we are no longer living in the stone age of the Internet. What if you want some AJAX action? Find out in this section.

POWER UP USER LIBRARY

First, let us power up the user library a little more so that it can handle the creation of new users.

lib/lib-User.php
base->DB->fetchAll(
      "SELECT * FROM `users`", null, "user_id"
    );
  }

  function get ($id) {
  // get() : get user by ID or email

    $sql = is_numeric($id)
      ? "SELECT * FROM `users` WHERE `user_id`=?"
      : "SELECT * FROM `users` WHERE `user_email`=?" ;
    return $this->base->DB->fetch($sql, [$id]);
  }

  function add ($name, $email) {
  // add() : create a new user

    // Check if given email is already registered
    $check = $this->get($email);
    if (is_array($check)) {
      $this->error = "'$email' is already registered!";
      return false;
    }

    // Proceed add
    return $this->base->DB->exec(
      "INSERT INTO `users` (`user_name`, `user_email`) VALUES (?, ?)",
      [$name, $email]
    );
  }
}

AJAX HANDLER

Now that the user library has more muscles, we can create an AJAX handler script to accept and process the user requests. I personally like to create another api folder to put all these AJAX handler scripts into… Because it sounds cool, and it is easier to manage this way.

api/users.php
extend("User");

// PROCESS AJAX REQUESTS
switch ($_POST['req']) {
  // INVALID REQUEST
  default : 
    echo json_encode([
      "status" => 0,
      "msg" => "Invalid request"
    ]);
    break;

  // GET USER
  case "get":
    $user = $_BASE->User->get($_POST['id']);
    $pass = is_array($user);
    echo json_encode([
      "status" => $pass ? 1 : 0,
      "msg" => $pass ? $user : "Invalid ID"
    ]);
    break;

  // ADD USER
  case "add":
    $pass = $_BASE->User->add($_POST['name'], $_POST['email']);
    echo json_encode([
      "status" => $pass ? 1 : 0,
      "msg" => $pass ? "OK" : $_BASE->error
    ]);
    break;
}

Just like all our other “usual scripts”, we only have to include our core and use the libraries. How this AJAX handler works is simple. We post a $_POST['req'] to specify what process we want to do, followed by the required parameters.

RequestDescription
get

Get specified user. Parameters –

  • id: The user ID or email.
add

Create a new user. Parameters –

  • name: The user name
  • email: The user email

ADD USER PAGE – HTML & JAVASCRIPT

Finally, we only have to create our add user form in HTML and fire an AJAX request from Javascript.

users-add.php


  
    Add User AJAX Demo
assets/users-add.js
function add() {
// add() : process add user via AJAX

  // DATA
  var data = new FormData();
  data.append('req', 'add');
  data.append('name', document.getElementById("user_name").value);
  data.append('email', document.getElementById("user_email").value);

  // AJAX
  var xhr = new XMLHttpRequest();
  xhr.open('POST', "api/users.php", true);
  xhr.onload = function () {
    // SERVER RESPONSE
    var res = JSON.parse(this.response);
    // ERROR
    if (res.status==0) {
      alert(res.msg);
    }
    // OK - REDIRECT OR DO SOMETHING ELS
    else {
      alert("OK");
    }
  };
  xhr.send(data);
  return false;
}

EXTRA
DOWNLOAD & MORE

That’s all for the code, and here is the download link as promised – Plus a few small extras that may be useful to you.

DOCUMENT YOUR LIBRARIES!

Notice how I always write a few short lines of comments to describe the function and the input parameters? Keep this as a good habit for your own sake and others who might work on your code. This will definitely help to describe what each function does without going through every single line, and what it requires to work.

MODEL-VIEW-CONTROLLER (MVC)

The concept of keeping the libraries and HTML files separate is based on something called MVC. This is a rather common term that you will hear in the cyber world, so read my other guide if you interested to know more:

4 Steps Simple PHP MVC Example (What the Heck is MVC!?)

SOURCE CODE DOWNLOAD

Click here to download the source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
 

CLOSING
WHAT’S NEXT?

Thank you for reading, and we have come to the end of this guide. I hope that it has helped you in your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

The post How to Build a PHP Plugin Module System appeared first on Code Boxx.



This post first appeared on Xxxxxxxxx, please read the originial post: here

Share the post

How to Build a PHP Plugin Module System

×

Subscribe to Xxxxxxxxx

Get updates delivered right to your inbox!

Thank you for your subscription

×