SitePoint Sponsor

User Tag List

Results 1 to 4 of 4
  1. #1
    I solve practical problems. bronze trophy
    Michael Morris's Avatar
    Join Date
    Jan 2008
    Location
    Knoxville TN
    Posts
    2,053
    Mentioned
    66 Post(s)
    Tagged
    0 Thread(s)

    PHP 5.4: Templates and Front controllers.

    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

    Code php:
    namespace PNL;
     
    $this->page = new Page($this, array(
    	'body' =>  'This is a triumph'
    ));

    The Page class

    Code php:
    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.

    Code php:
    <?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( "\n", $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.

    Code php:
    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

    Code php:
     
    ?><!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.

  2. #2
    Foozle Reducer ServerStorm's Avatar
    Join Date
    Feb 2005
    Location
    Burlington, Canada
    Posts
    2,699
    Mentioned
    89 Post(s)
    Tagged
    6 Thread(s)
    Hi Michael,

    Nice work so far. Please know that my questions are not intended to attack or infer you have done anything wrong; just after thinking about your design I have these questions:


    1. Your current take on PNL is interesting to see. I can't pretend to fully understand the best way to use traits, but I am wondering one thing in their regard. Traits are designed to be mix-in components (essentially copy & paste functionality into classes and to allow a type of multi-inheritance/or composition. Your Responder trait seems to be very specific to a FULL page; I wouldn't mix-in the Responder trait into many situations than a page, so what primary benefit do you get by defining this as a trait?
    2. How does the instance of Template get satisfied?
    3. It can be argued that Apache/IIS are already a front-controller, so your JavaScript example of a front-end controller, is it not an Filter that may be strung together with like objects in a Filter Chain?
    4. How do you plan to allow authentication and permissions in this framework, are you designing some way for people to implement their own, or will you design this into the framework?


    I really like the clean and 'trimmed-of-fat' your classes have.

    Thanks for sharing!

    Regards,
    Steve
    ictus==""

  3. #3
    I solve practical problems. bronze trophy
    Michael Morris's Avatar
    Join Date
    Jan 2008
    Location
    Knoxville TN
    Posts
    2,053
    Mentioned
    66 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by ServerStorm View Post
    Hi Michael,

    Nice work so far. Please know that my questions are not intended to attack or infer you have done anything wrong; just after thinking about your design I have these questions:
    Thanks, I'll try to clarify as best I can.

    1. Traits are designed to be mix-in components (essentially copy & paste functionality into classes and to allow a type of multi-inheritance/or composition. Your Responder trait seems to be very specific to a FULL page; I wouldn't mix-in the Responder trait into many situations than a page, so what primary benefit do you get by defining this as a trait?
    You're right in that it won't be mixed in often, but it is mixed in more than once - so the principle of DRY demands the behavior be defined only once. Two classes currently use it.

    EventDispatcher, because if the result of the parse method is a text string it needs to encapsulate and cache that response.
    Page, for obvious reasons, is the lion's share client of this behavior.

    While at the moment the Javascript response is just a procedure, if I transformed it into a class it would need to use responder as well.

    2. How does the instance of Template get satisfied?
    It's simply higher up in the ancestry than the example shows. Block extends from Template. Template extends from ArrayObject.

    Objects which contain manipulable data all extend from ArrayObject in this framework. So array access is used to touch the primary data of object and the data that may need iteration in the object. Meanwhile, the object may have other settings. The template object and its descendants have a template property, which points to the phtml template used for final binding of their data.

    3. It can be argued that Apache/IIS are already a front-controller, so your JavaScript example of a front-end controller, is it not an Filter that may be strung together with like objects in a Filter Chain?
    Exactly the plan. What I'm working on now is still fairly low level aimed at bridging the problems and confusion mod_rewrite has brought without removing PHP's inherent mix in flexability. The final form of the pieces of PNL I'm working on is as a C++ mod_pnl object, which derives from mod_php. Once that occurs then, like in python, we'd point the directory at mod_pnl and there would be no __landing__.php file at all.

    4. How do you plan to allow authentication and permissions in this framework, are you designing some way for people to implement their own, or will you design this into the framework?
    This section of the framework isn't worried with authentication, nor for that matter the database. Both of those are business logic, and therefore model concerns. I have a plan for it for my own system but it's rather radical - rather than have PHP use a god account to reach the database, PHP's authentication to the database is the same as the user's. Hence the database handles authentication, which in turn changes the available view plans.

    Speaking of authentication and security, there's a dangerous security whole in that javascript script I posted above. Anyone spot it?

  4. #4
    Foozle Reducer ServerStorm's Avatar
    Join Date
    Feb 2005
    Location
    Burlington, Canada
    Posts
    2,699
    Mentioned
    89 Post(s)
    Tagged
    6 Thread(s)
    Quote Originally Posted by Michael Morris View Post
    You're right in that it won't be mixed in often, but it is mixed in more than once - so the principle of DRY demands the behavior be defined only once. Two classes currently use it.

    EventDispatcher, because if the result of the parse method is a text string it needs to encapsulate and cache that response.
    Page, for obvious reasons, is the lion's share client of this behavior.

    While at the moment the Javascript response is just a procedure, if I transformed it into a class it would need to use responder as well.
    Got it, I forgot that you mentioned the cache or no cache, but I understand. On my home PHP stack I've been playing with traits but have been using them in a much smaller atomic way, so it was interesting seeing you using largely like extending regular classes.

    It's simply higher up in the ancestry than the example shows. Block extends from Template. Template extends from ArrayObject.

    Objects which contain manipulable data all extend from ArrayObject in this framework. So array access is used to touch the primary data of object and the data that may need iteration in the object. Meanwhile, the object may have other settings. The template object and its descendants have a template property, which points to the phtml template used for final binding of their data.
    Interesting, I can't wait until I can see more

    Exactly the plan. What I'm working on now is still fairly low level aimed at bridging the problems and confusion mod_rewrite has brought without removing PHP's inherent mix in flexability. The final form of the pieces of PNL I'm working on is as a C++ mod_pnl object, which derives from mod_php. Once that occurs then, like in python, we'd point the directory at mod_pnl and there would be no __landing__.php file at all.
    Your undertaking here is not for the faint hearted, but given your pushing for python like functionality, if you pull it off, it should work well.

    This section of the framework isn't worried with authentication, nor for that matter the database. Both of those are business logic, and therefore model concerns. I have a plan for it for my own system but it's rather radical - rather than have PHP use a god account to reach the database, PHP's authentication to the database is the same as the user's. Hence the database handles authentication, which in turn changes the available view plans.
    Interesting take, although I don't like this idea from the standpoint when you do have your role or view based permissions then you need to hit the database every time you need to validate a users access; doing this with a session hash and validation offloads this from the database. Although you are saying you will use it for you own project so you should know if this will or will not become a bottleneck.

    Thanks

    Steve
    ictus==""


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •