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.
Related Articles
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 | Step 2 | Step 3 |
Extra | Extra | Closing |
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:
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
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
$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
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.
Function | Description |
__construct | The constructor. Will connect to the database when the object is being created. |
__destruct | The destructor. Will close the database connection when the object is being destroyed. |
exec | Run an insert, replace, update, or delete query. |
fetchAll | Run a select query – When you expect multiple rows of results. |
fetch | Run a select query – When you expect a single row of result. |
start | Auto-commit off, used in conjunction with end, for a series of queries. |
end | Commit or rollback? Used in conjunction with start. |
CORE STARTER
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
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.
Field | Description |
user_id | The user ID. Primary key, auto-increment. |
user_name | The user’s name. |
user_email | The 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:
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:
extend("User");
$users = $_BASE->User->getAll();
// THE HTML ?>
Users List Demo Name
Email
$u) {
printf("%s %s ",
$u['user_name'], $u['user_email']
);
}
?>
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.
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.
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.
Request | Description |
get | Get specified user. Parameters –
|
add | Create a new user. Parameters –
|
ADD USER PAGE – HTML & JAVASCRIPT
Finally, we only have to create our add user form in HTML and fire an AJAX request from Javascript.
Add User AJAX Demo
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.