I'm working on a framework written in PHP and trying to keep the code simple by omitting the functional and object oriented approaches as much as possible. What I'm having in mind might be best defined as loose modular programming. It might lead nowhere good but that's why this is an experiment.
Background
Before I started teaching myself programming from books and videos I had only been familiar with structured programming in the logical sense. Gamedev tools like RPG Maker and the Warcraft III map editor had taught me about variables and the basic control structures (conditional branches, switches and loops). So when I worked on my first dynamic website in PHP I mostly built up on that knowdledge and focused on the stuff that was important to the functionality of the program. Writing my own functions didn't seem very important to me back then (obviously I had not understood the concept of reusing code) and I'm not sure the book I was using for reference even covered classes and objects at all. Years later I came to understand functions better, and managed to somewhat understand OOP a little as well. Yet my first C game was still a mess with dozens of global variables, ridiculously long functions that made use of jumps to navigate back and forth. Eventually I learned to logically divide my code into pseudo modules. For the first time I felt like I'd be able to understand my code if I were to come back to it years later. The natural next step would have been to switch to C++, but when I took a closer look at it none of the features it offered really sold me on it. Starting over and abandoning the plain C code I had written for my game engine didn't seem worth it for what little additional convenience object oriented programming techniques seemed to offer a solo dev. Today I still feel like even pure modular programming is overkill for small projects that can be handled by one or two persons. I like code that is straight to the point and not too abstract. Sometimes it's more elegant to have a couple of globals and to avoid constantly passing variables around. Obviously depending on the nature and size of a project trade offs require to be made however, and that's when evolved modern concepts such as functional and object oriented programming have to come into play. At least that's how it looks to me.
I guess I'm just curious to see how far I can push the lone hacker approach to programming, the one that doesn't have to worry about the next junior dev potentially running a large corporate project into the ground. Honestly I could write an entire post about this topic and might really do so someday.
My first experiments with modular PHP
Let me state for context that I originally started to write this project in OOP style. So I made some decisions early on based on what my code had looked like when it wasn't intended to omit the OOP aspect. As one might expect it didn't turn out very well. I already decided that I'm going to be making major changes to my first draft so I'm posting this here for posterity.
001 class main {
002 use as_module;
003
004 public function run() {
005
006 config::init();
007 db::init();
008 store::init();
009 //var_dump(db::uses());
010 request::init();
011 }
012
013 }
My first consideration had been to figure out how to write PHP code in the way you lay out a modular C program. I thought that classes only holding static methods and properties came pretty close to the concept and thus wrote the trait as_module
. Since all my classes so far had been singletons this trait was sort of supposed to be a replacement for my previously written Singleton trait. I chose using a trait in case I'd want to make use of inheritance on my classes later on.
001 trait as_module {
002
003 static $prop = array();
004
005 private function __construct() {
006 $trace = debug_backtrace();
007 trigger_error(
008 'Attempting to create object of module class: in '.
009 $trace[0]['file'].' on line '.$trace[0]['line'], E_USER_ERROR);
010 }
011
012 public static function __callstatic($name, $args) {
013 if (count($args) > 0) {
014 if (strpos($args[0], ':') !== false) {
015 $a = explode(':', $args[0]);
016 $a2 = self::$prop[$name];
017 foreach ($a as $k) {
018 $a2 = $a2[$k];
019 }
020 return $a2;
021 }
022 return self::$prop[$name][$args[0]];
023 }
024 return self::$prop[$name];
025 }
026
027 }
The property array probably was never necessary. I'm already thinking of replacing it with static properties written straight into the classes. If you can call them that. Can't call them modules either because I plan on having dependencies between each other where needed, just to keep things simple and avoid passing variables around too much. At the very least I don't plan on developing the next facebook with this framework so it should be fine.
It seemed straightforward to lock the constructor away. Do we need objects if we want to avoid writing object oriented code? I didn't think so. Now I'm not so sure anymore. I'll get back to that later.
Lastly I wrote a function to access the property array from outside a class, turning them into namespaced globals using neither namespaces nor globals variables per sē. Admittedly I just really hate the way static properties are accessed in PHP. module::$property
just looks plain ugly. Besides a magic function was necessary to at least access the properties since the static array was meant to be private. Thought it can't hurt to prevent unnecessary write access to the variables. Encapsulation is actually a neat feature even for projects that haven't hit corporate scale yet.
Anyways I felt smart using a string parameter to simplify nested array access, so you could access write module::prop('a:b:c');
instead of module::prop()['a']['b']['c']
But it also feels like a waste of processing time (not to mention that part should be offloaded to an array helper) compared to accessing multidimensional array elements directly.
And honestly I don't even know why the property array isn't declared private. I seem to remember it having caused errors, but some quick tests I just did seem to prove that wrong. Anyways...
001 class config {
002 use as_module;
003
004 // Properties are simply the config array keys
005
006 public static function init() {
007 self::read_base_config();
008 self::read_db_config();
009 }
010
011 private static function read_base_config() {
012 $config = array();
013 $config['store'] = array();
014 $config['store']['db'] = array();
015 foreach (new FilesystemIterator(realpath(path::CONFIG)) as $i) {
016 if (is_file($i)) { include($i->getPathName()); }
017 }
018 self::$prop = array_merge(self::$prop, $config);
019 }
020
021 private function read_db_config() {
022 foreach (new FilesystemIterator(realpath(path::DB_CONFIG)) as $i) {
023 if (is_file($i)) {
024 $config = array();
025 $config['connections'] = array();
026 include ($i->getPathName());
027 foreach ($config['connections'] as $conn) {
028 $config['connections'][$conn['key']]['db'] =
029 $config['key'];
030 }
031 self::$prop['store']['db'][$config['key']] = $config;
032 }
033 }
034 }
035
036
037
038 }
A simple module to load config files and merge them into a monolithic array. Normally you'd use config data to initialize objects. But I felt like copying data out of the config array would be a waste since it was already in memory to begin with. A terrible mistake because I ended up having to bake an additional value into the connection arrays later on, and that's just ugly. Not to mention trying to work with the data without introducing new objects (types) wasn't comfortable and caused me a lot of headaches.
001 class db {
002 use as_module;
003
004 // Properties
005 // $uses; The currently used database and connection
006 // $conns; Array of array
007 //
008
009 public static function init() {
010 self::$prop['conns'] = array();
011 }
012
013 public static function load($db) {
014 $a = (isset($db['default']) && $db['default']) ? $db['key'] : null;
015 $b = null;
016 foreach ($db['connections'] as $conn) {
017 array_push(self::$prop['conns'], array(
018 'db' => $db['key'],
019 'conn' => $conn['key'],
020 'state' => false
021 ));
022 $b = (isset($conn['default']) && $conn['default']) ?
023 $conn['key'] : null;
024 // if ($b) { break; }
025 }
026 self::$prop['uses'] = array($a, $b);
027 }
028
029 public static function use($db, $conn) {
030 self::$prop['uses'] = array($db, $conn);
031 }
032
033 public static function checkconn($conn) {
034 foreach (self::$prop['conns'] as $conn2) {
035 if (($conn2['db'] == $conn['db']) &&
036 ($conn2['conn'] == $conn['key']))
037 {
038 return $conn2['state'];
039 }
040 }
041 return self::$prop['conns'][$conn['key']];
042 }
043
044 public static function set_connstate($conn, $bool) {
045 $i = -1;
046 $j = 0;
047 foreach (self::$prop['conns'] as $conn2) {
048 if (($conn2['db'] == $conn['db']) &&
049 ($conn2['conn'] == $conn['key']))
050 {
051 self::$prop['conns'][$j]['state'] = $bool;
052 return true;
053 } else {
054 $j++;
055 }
056 }
057 return false;
058 }
059
060 }
So just writing a couple of simple routines for initial database handling turned into a log of pain. This would have taken much less time to write in pure object oriented code. Also I found need to put a flag indicating whether a connection had been set up into each connection array. Putting this piece straight into the config array didn't sit well with me since I meant for the config array data to be immutable, so I tried to keep copies of the little data I needed for the system to work instead. It was really awkward to say the least.
Conclusion
So far this has been a terrible mess that's just hard to read and not nearly as elegant as I had imagined it to be. I'm going to need either real classes or to use creator functions (that return StdClass objects) to mimic C structs. Juggling arrays and strings like that is a pain to work with and awfuly confusing. Maybe forcing type safety might be a good idea too.
The way I'm heading I feel like carefuly writing simple OOP code might be the way to go in general. I'm already looking forward to refactor the pieces of code I have written so far for this project.