Over many years in the industry, I have developed what people call "funky programming habits". Here is a quick sharing of some of my "funky coding techniques", hopefully tickle the brains of some beginners.
(PART 0) BORING OOP
// SOME.PHP
namespace SOMETHING;
class MYLIB { ... }
// ANOTHER.PHP
require "SOME.PHP";
$OBJECT = new SOMETHING\MYLIB();
First, a quick revision of boring OOP - Define a class MYLIB
and create $OBJECT = new MYLIB()
.
(PART 1) STANDARDIZED LIBRARIES
// LIB-CORE.PHP
class CoreBoxx {
public $modules = [];
function load ($module) : void {
require "LIB-$module.php";
$this->modules[$module] = new $module($this);
}
}
$_CORE = new CoreBoxx();
$_CORE->load("DB");
// LIB-DB.PHP
class DB {}
"Traditional OOP" works, but what if we "automate" and "standardize" it further? For those who are lost:
- We have a
class Coreboxx
and$_CORE = new CoreBoxx()
. Easy. - When we call
$_CORE->load("DB")
, this will automatically:require "LIB-DB.php";
$_CORE->modules["DB"] = new DB($_CORE);
- This may seem stupid at first, but it enforces a "standard naming format".
- All library file names will have the format of
LIB-Module.php
. - All class definitions will follow the file name
class Module
. Hard to go wrong.
- All library file names will have the format of
(PART 2) MAGIC POINTERS & LINKS
(2A) MAGIC GET
// LIB-CORE.PHP
class CoreBoxx {
function __get ($name) {
if (isset($this->modules[$name])) { return $this->modules[$name]; }
}
}
- Take note that we put new objects into an array
$_CORE["modules"]["DB"] = new DB()
. - "By default", we access the Database module with
$_CORE->modules["DB"]
. - For those who are lost:
-
__get ($name)
will try to map$_CORE->$name
to$_CORE->["modules"][$name]
. - In short, we "shorten"
$_CORE->modules["DB"]
to$_CORE->DB
. As lazy as possible, get it?
-
(2B) MAGIC LINK
// LIB-CORE.PHP
class Core {
public $Core;
function __construct ($core) { $this->Core =& $core; }
function __get ($name) {
if (isset($this->Core->modules[$name])) { return $this->Core->modules[$name]; }
}
}
// LIB-DB.PHP
class DB extends Core {}
- Once again, take note that we pass
$_CORE
into new objects -$_CORE->modules["DB"] = new DB($_CORE)
. - What happens here:
- This creates a pointer
$_CORE->DB->Core =& $_CORE
, linking the database module back to the core. - The same old magic
__get($name)
trick.$_CORE->DB->$name
will attempt to refer back to$_CORE->Module[$name]
.
- This creates a pointer
(2C) SIBLING LINK
// LIB-DB.PHP
// LET'S SAY, WE ADD A DUMMY FUNCTION TO DB
class DB extends Core {
function dummy {
$this->Core->load("Mail");
$this->Mail->send("JON@DOE.COM", "TITLE", "MESSAGE");
}
}
// LIB-MAIL.PHP
class Mail extends Core {
function send ($to, $title, $message) {
return @mail($to, $title, $message);
}
}
Finally, to explain why we link all modules back to the core:
- In all functions of
$_CORE->DB
, we can access the core with$this->Core
. - Meaning, we can also access sibling modules.
- Call
$this->Core->load("Module")
to load more modules as required. - Use the sibling module
$this->Module->FUNCTION()
.
- Call
- That is, we are no longer bound by "hierarchical OOP". Develop a module once, it can be reused by all other modules.
(PART 3) CONSTRUCTORS & DESTRUCTORS
class DB {
public $pdo = null;
public $stmt = null;
function __construct ($core) {
parent::__construct($core);
$this->pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
}
function __destruct () {
if ($this->stmt!==null) { $this->stmt = null; }
if ($this->pdo!==null) { $this->pdo = null; }
}
}
Constructor and destructors are often overlooked by beginners, but they are VERY useful. Here is a quick example.
- In this database class, we automatically connect to the database on
new DB()
. - When the object is destroyed, the destructor closes the connection.
(PART 4) EVIL "PARAMETERS MAPPING"
(4A) NO!
// LIB-USER.PHP
class User extends Core {
function save ($name, $email, $password, $id=null) {
if ($id == null) { /* $THIS->DB->INSERT(...) */ }
else { /* $THIS->DB->UPDATE(...) */ }
}
}
// LET'S SAY WE SUBMIT A FORM TO SAVE A USER
$_CORE->load("User");
$result = $_CORE->User->save($_POST["name"], $_POST["email"], $_POST["password"], $_POST["id"]);
This is probably how many people work with POST and GET variables, manually map them to a function... But this gets VERY painful after a million times.
(4B) EVIL EVAL TO THE RESCUE
// LIB-CORE.PHP
class CoreBoxx {
function autoCall ($module, $function, $mode="POST") {
// (A) LOAD MODULE
$this->load($module);
// (B) GET FUNCTION PARAMETERS
if ($mode=="POST") { $target =& $_POST; } else { $target =& $_GET; }
$reflect = new ReflectionMethod($module, $function);
$params = $reflect->getParameters();
// (C) EVIL MAPPING
$evil = "\$results = \$this->$module->$function(";
if (count($params)==0) { $evil .= ");"; }
else {
foreach ($params as $p) {
// (C1) POST OR GET HAS EXACT PARAMETER MATCH
if (isset($target[$p->name])) { $evil .= "\$_". $mode ."[\"". $p->name ."\"],"; }
// (C2) USE DEFAULT VALUE
else if ($p->isDefaultValueAvailable()) {
$val = $p->getDefaultValue();
if (is_string($val)) { $evil .= "\"$val\","; }
else if (is_bool($val)) { $evil .= $val ? "true," : "false," ; }
else { $evil .= ($val===null ? "null," : "$val,"); }
}
// (C3) NULL IF ALL ELSE FAILS
else { $evil .= "null,"; }
}
$evil = substr($evil, 0, -1) . ");";
}
// (C4) EVIL RESULTS
eval($evil);
return $results;
}
}
// YES!
$_CORE->autoCall("User", "save");
For those who are lost, $_CORE->autoCall("User", "save")
will:
$_CORE->load("User")
$_CORE->User->save(AUTO MAP $_POST TO FUNCTION PARAMETERS)
You "experts" can cringe all you want. I am lazy.
(PART 5) "UNIVERSAL" ERROR HANDLER
class CoreBoxx {
function ouch ($ex) {
// SAVE $EX TO FILE? DATABASE?
// AUTO SEND EMAIL ON CRITICAL STOPS?
// SHOW YOUR OWN CUSTOM ERROR MESSAGE
}
}
function _CORERR ($ex) { global $_CORE; $_CORE->ouch($ex); }
set_exception_handler("_CORERR");
- Last bit, we can do a "global catch" in PHP (for all uncaught exceptions).
- Use this to customize your "blue screen of death".
- Keep a log of errors, or even send a warning message on critical failures.
(EXTRA) THE END
That's all for this compressed sharing session.
- Of course, there is no such thing as a "perfect system".
- Constructive critism welcomed. Feel free to disagree with my funky coding.
- Yep, Core Boxx is a "real PHP framework".
Top comments (2)
So good but I lost...
Yep, this will require a solid understanding of OOP and pointers. I have cleaned up the guide, take your time and go through section by section.