For the last couple of Fridays I’ve been concentrating on my framework project, rewriting it wholly to take advantage of traits and working with knowledge and insights I’ve gained in the last couple years. For those not familiar, this project - which has had several names and is currently under the namespace PNL, is an ongoing pet of mine. My goal, ultimately, is to build a teaching tool.
I’ve been working on the core bootstrap class, the event dispatcher, and the router. The normal entry point of the program is a landing file and mod_rewrite is used to route all requests to that file. The end plan is that the system will write completed files to the htdocs folder when possible, caching them (or large pieces of them) to keep PHP use to a minimum. Caching being the bear that it is, other than leave the outs in place for this plan I haven’t implemented it.
The routing system looks up file in the map folder. So given “http://www.mysite.com/path/to/section” the router first looks for “/map/path/to/section/index.php” then “map/path/to/index.php” until it hits “index.php” or throws an error.
That file is the control procedure for that location. The framework gives the using programmer a few choices on how to proceed here. First, the file is wrapped in an object buffer, so they could put a bunch of mixed php / html in there. For a simple page this might be ok and I’m personally not going to pass judgement here. More commonly and more flexibly the procedure will look like this
namespace PNL;
$this->page = new Page($this, array(
'body' => 'This is a triumph'
));
The Page class
namespace PNL;
class Page extends Block {
use Responder;
protected $template = 'master';
}
Page is the template for the master layout. It uses the Responder trait which contains the methods for sending back responses to the browser.
<?php
namespace PNL;
/**
* A class that needs to send output to the browser uses the Responder trait.
* Most of the time this will be page controllers, but the front controller
* itself also employs this trait in the event it is given a string to output
* by the event.
*
*/
trait Responder {
protected $output = null;
protected $headers = null;
protected function setHeaders( array $headers = array() ) {
if (is_null($this->headers)) {
$this->headers = new Headers( $headers );
}
}
public function respond( $headers ) {
$this->setHeaders( $headers );
$this->sendHeaders();
$this->sendResponse();
}
protected function sendHeaders() {
$this->headers->send();
}
protected function sendResponse() {
if(empty($this->output)) {
if ($this instanceof Template) {
$this->parse();
} else {
throw new Exception('No output to respond with, no parse method to create it.');
}
if(empty($this->output)) {
throw new Exception('No output to respond with.');
}
}
if (class_exists('PNL\\\\Debug', false)) {
$this->output .= Debug::consoleMessage();
}
## Send Output
if( $this->headers->compress ) {
$this->sendCompressedOutput($this->output);
} else {
print( $this->output );
}
}
protected function stripWhiteSpace( $output, $glue = ' ' ) {
$lines = explode( "\
", $output );
foreach ($lines as $num => &$line) {
$line = trim($line);
if (empty($line)) {
unset($lines[$num]);
}
}
return implode($glue, $lines);
}
protected function sendCompressedOutput( $output ) {
$gzipSize = strlen( $output );
// Don't try to zip anything under 2k - isn't worth it.
if ($gzipSize > 2048) {
$gzipCrc = crc32($output);
$output = gzcompress($output, 9);
$output = substr($output, 0, strlen($output) - 4);
print("\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00".$output.pack('V', $gzipCrc).pack('V', $gzipSize));
} else {
header('Content-Encoding:');
print( $output );
}
}
}
At the moment the front controller also uses this trait when the users’s procedure creates text output instead of making a page object. Like this one, which is the javascript request script.
namespace PNL;
$this->headers = new Headers( array('Content-Type' => 'text/javascript'));
if ($route->pathAfter == '/index.js') {
// Code to deliver a compilation of all scripts.
} else if (file_exists($_SERVER['PNL_ROOT'].'/core/js'.$route->pathAfter)) {
include ($_SERVER['PNL_ROOT'].'/core/js'.$route->pathAfter);
} else {
throw new FileNotFoundException();
}
The system is becoming quite flexible and powerful. The template objects nest each other as arrays. They sometimes create new blocks inline like this
?><!DOCTYPE HTML>
<html>
<head>
<?= new HTMLHeadings($this->dispatcher) ?>
</head>
<body>
<?= $body ?>
</body>
</html>
The HTMLHeadings object does it’s parsing business when it’s parse() method is called, which is invoked by its __toString() method. So, in testing, this class can be started without violating the principle that constructors should do nothing except get the object to a ready state. Yet in practice it works fairly cleanly.
I’ll respond to the question I anticipate first - why no factory method on the new calls? I’m not opposed to using a factory pattern - the core class of the framework uses one to give the user maximum control of the elements of the core framework. That said, at some point programming must occur, and abstraction can only defer that decision, not remove it. At that point, it really doesn’t matter if the new operator is used or not. The framework doesn’t stop further abstraction from being deployed by client applications - and depending on their scale that may be justifiable. But at this point, it isn’t.
I’m still working with this, but so far, so good. I’m hoping to have something that can be shown by summer’s end.