DEV Community

Cover image for Lazy and Unorthodox PHP Coding
Code Boxx
Code Boxx

Posted on • Updated on

Lazy and Unorthodox PHP Coding

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 { ... }
Enter fullscreen mode Exit fullscreen mode
// ANOTHER.PHP
require "SOME.PHP";
$OBJECT = new SOMETHING\MYLIB();
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode
// LIB-DB.PHP
class DB {}
Enter fullscreen mode Exit fullscreen mode

"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.

(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]; }
  }
}
Enter fullscreen mode Exit fullscreen mode
  • 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]; }
  }
}
Enter fullscreen mode Exit fullscreen mode
// LIB-DB.PHP
class DB extends Core {}
Enter fullscreen mode Exit fullscreen mode
  • 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].

(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");
  }
}
Enter fullscreen mode Exit fullscreen mode
// LIB-MAIL.PHP
class Mail extends Core {
  function send ($to, $title, $message) {
    return @mail($to, $title, $message);
  }
}
Enter fullscreen mode Exit fullscreen mode

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().
  • 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; }
  }
}
Enter fullscreen mode Exit fullscreen mode

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(...) */ }
  }
}
Enter fullscreen mode Exit fullscreen mode
// 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"]);
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode
  • 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)

Collapse
 
evergreen1907 profile image
Allester Corton

So good but I lost...

Collapse
 
codeboxx profile image
Code Boxx

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.