Coding a PHP 7 Framework #7 - Code Workshop

In part 7 of Coding a PHP Framework, we look at instantiating our first object using the /library/Lectric/lecPDO.class.php class, building the final component to our library of classes, the /library/Lectric/view.class.php.



Coding a PHP 7 Framework #7 - Code Workshop

6th October 2018 in   PHP Tutorials by Elliott Barratt

You can view the finished framework at any time by visiting the GitHub repo at https://github.com/erbarratt/lectric.

If you need more space to see the code, click "Hide Sidebar >" above the Article Categories box to the right ->

The /library/Lectric/view.class.php's job is to set up some view specific properties, then render out something based on those properties ($_pageURL, $_urlDirectory):

<?php
namespace Lectric;

/**
* View class, URL parse and view loader
*
* The controller determines whether do and action or vieww something
*
* @package    Lectric Framework
* @author     Elliott Barratt
* @copyright  Elliott Barratt, all rights reserved.
*
*/ 
class view extends lecPDO {
	
	private $page = null;
	
	//callable members for use in template
	private $_URLdirectory = '';
	private $_directory = '';
	private $_pageUrl = '';
	
	private $_imgLocalDir = '';
	private $_cssLocalDir = '';
	private $_jsLocalDir = '';
	
	//framework scpecific
	private $_cssLibDir = '/library/css';
	private $_jsLibDir = '/library/js';
	
	/**
     * construct to parse URL for your viewing pleasure
     *
     * @param object $DBH DBH handler for SQLQueryPDO
	 *
     */
		function __construct(&$DBH)
		{
			
			/*
			* seeing as we're using the construct for other stuff, we need to pass the DBH through as normal as in parent
			* 
			*/
				parent::__construct($DBH);
			 
			/*
			* parse URL into file directories, url directories and page urls
			* 
			*/
			
				$urlNodes = URL_NODES; 										//needed for use of end()
				
				if (!isset($urlNodes[0])){									//i.e.empty, homepage.
					$this->_URLdirectory = 'root';
					$this->_fileDirectory = DEFAULT_DIRECTORY;
					$this->_pageUrl = 'index';	
				} else {
					
					if (count($urlNodes) === 1){
						
						/*
						* first find physical actual derectories - they will always be preffered over default directory.
						* e.g. if you have an "admin" interface seperate to front end, mount in a directory called /view/admin/ 
						*/
							if (is_dir(DOC_ROOT.'/view/'.$urlNodes[0])){
								
								$this->_URLdirectory = 'root';				// database directory
								$this->_fileDirectory = $urlNodes[0];		// as phsical folder
								$this->_pageUrl = 'index';					// database url
								
							} else {
								$this->_URLdirectory = 'root';				// database directory
								$this->_fileDirectory = DEFAULT_DIRECTORY;	// default directory instead?
								$this->_pageUrl = $urlNodes[0];				// ie /webpage
							}
						
					} else {
						
						/*
						* Is the first node a phyiscal view directory? if not, then treat as database directory
						*/
							if (is_dir(DOC_ROOT.'/view/'.$urlNodes[0])){
								
								if (count($urlNodes) === 2){
									$this->_URLdirectory = 'root';				// database directory
									$this->_fileDirectory = $urlNodes[0];		// as physical folder
									$this->_pageUrl = end($urlNodes);			// end() to allow many sub directories in link
								} else {
									$this->_URLdirectory = $urlNodes[1];		// database directory (after physical directory in URL)
									$this->_fileDirectory = $urlNodes[0];		// as physical folder
									$this->_pageUrl = end($urlNodes);			// end() to allow many sub directories in link
								}
								
							} else {
							
								$this->_URLdirectory = $urlNodes[0];			// database directory
								$this->_fileDirectory = DEFAULT_DIRECTORY;		// default directory instead?
								$this->_pageUrl = end($urlNodes);				// end() to allow many sub directories in link
							}
						
					}			
					
				}
				
			/*
			* set up some helper properties
			* 
			*/
				$this->_imgLocalDir = '/view/'.$this->_fileDirectory.'/img';
				$this->_cssLocalDir = '/view/'.$this->_fileDirectory.'/css';
				$this->_jsLocalDir = '/view/'.$this->_fileDirectory.'/js';
				
			$this->render();
			
		}
	
    /**
     * main render call
     * 
     * @return void
     */
		public function render(): void
		{
			
			/*
			* first find physical actual directories - they will always be preferred over default directory.
			* e.g. if you have an "admin" interface separate to front end, mount in a directory called /view/admin/ 
			*/
				if (is_dir(DOC_ROOT.'/view/'.$this->_fileDirectory)){

					if (file_exists(DOC_ROOT.'/view/'.$this->_fileDirectory.'/render.php')) {
						require(DOC_ROOT.'/view/'.$this->_fileDirectory.'/render.php');
					} else {
						echo 'render file not in view directory: '.$this->_fileDirectory;
						exit;
					}		
				} 
			
			/*
			* if a physical directory does not exist, attempt to rout to DEFAULT_DIRECTORY instead
			*/
				else {
					
					if (file_exists(DOC_ROOT.'/view/'.DEFAULT_DIRECTORY.'/render.php')) {
						require(DOC_ROOT.'/view/'.DEFAULT_DIRECTORY.'/render.php');
					} else {
						echo 'render file not in view directory: '.DEFAULT_DIRECTORY;
						exit;
					}
					
				}
			
			return;

		}
	
    /**
     * Load up the webpage row from database
     * 
     * @return array
     */
		public function loadPage(): ?array
		{
			
			try{
				
				//strict to catch a directory that doesn't exist.
				$this->setWhereFields(['name' => $this->_URLdirectory, 'live' => 1]);
				$this->setWhereOps('==');
				$result_dir = $this->selStrict($this->_fileDirectory.'_directories', \Lectric\lecPDO::SINGLE, \Lectric\lecPDO::STRICT);
				
				//get webpage via directory id
				$this->setWhereFields(['directory' => $result_dir['id'],'url' => $this->_pageUrl, 'live' => 1]);
				$this->setWhereOps('===');
				$result = $this->selStrict($this->_fileDirectory.'_views', \Lectric\lecPDO::SINGLE, \Lectric\lecPDO::STRICT);
				
			} catch (\Exception $e){
				
				//is what they've requested a /do/ request?
				if($this->_URLdirectory === 'do'){
					header("HTTP/1.0 400 Bad Request");
				} else {
					header("HTTP/1.0 404 Not Found");
				}
				
				$this->setWhereFields(['url' => 'error']);
				$this->setWhereOps('=');
				$result = $this->selStrict($this->_fileDirectory.'_views', \Lectric\lecPDO::SINGLE);

			}
			
			return $result;
			
		}
	
    /**
     * Load up the webpage row from database, by selection
     * 
     * @return array
     */
		public function loadPageSelection(string $directory, string $url): ?array
		{
			
			try{
				
				//strict to catch a directory that doesn't exist.
				$this->setWhereFields(['name' => $directory, 'live' => 1]);
				$this->setWhereOps('==');
				$result_dir = $this->selStrict($this->_fileDirectory.'_directories', \Lectric\lecPDO::SINGLE, \Lectric\lecPDO::STRICT);
				
				//get webpage via directory id
				$this->setWhereFields(['directory' => $result_dir['id'],'url' => $url, 'live' => 1]);
				$this->setWhereOps('===');
				$result = $this->selStrict($this->_fileDirectory.'_views', \Lectric\lecPDO::SINGLE, \Lectric\lecPDO::STRICT);
				
			} catch (\Exception $e){
				
				//is what they've requested a /do/ request?
				if($directory === 'do'){
					header("HTTP/1.0 400 Bad Request");
				} else {
					header("HTTP/1.0 404 Not Found");
				}
				
				$this->setWhereFields(['url' => 'error']);
				$this->setWhereOps('=');
				$result = $this->selStrict($this->_fileDirectory.'_views', \Lectric\lecPDO::SINGLE);
				
			}
			
			return $result;
			
		}
	
}

Construct and Definition

This class extends the /library/Lectric/lecPDO.class.php class, which means it get's all the functionality of the parent class, and can define / override any of the classes already defined. To that end, a construct already exists in lecPDO, and since we want to define a construct here to, we must call parent::__construct() to also execute the parent construct function (which in this case is assigning the database handle $DBH to a private property).

Next, we need to decide how to deal with the URL to generate a view. The Lectric framework makes an assumption about how the /view/ folder is to be organised:

$urlNodes = URL_NODES; 										//needed for use of end()
				
				if (!isset($urlNodes[0])){									//i.e.empty, homepage.
					$this->_URLdirectory = 'root';
					$this->_fileDirectory = DEFAULT_DIRECTORY;
					$this->_pageUrl = 'index';	
				} else {
					
					if (count($urlNodes) === 1){
						
						/*
						* first find physical actual derectories - they will always be preffered over default directory.
						* e.g. if you have an "admin" interface seperate to front end, mount in a directory called /view/admin/ 
						*/
							if (is_dir(DOC_ROOT.'/view/'.$urlNodes[0])){
								
								$this->_URLdirectory = 'root';				// database directory
								$this->_fileDirectory = $urlNodes[0];		// as phsical folder
								$this->_pageUrl = 'index';					// database url
								
							} else {
								$this->_URLdirectory = 'root';				// database directory
								$this->_fileDirectory = DEFAULT_DIRECTORY;	// default directory instead?
								$this->_pageUrl = $urlNodes[0];				// ie /webpage
							}
						
					} else {
						
						/*
						* Is the first node a phyiscal view directory? if not, then treat as database directory
						*/
							if (is_dir(DOC_ROOT.'/view/'.$urlNodes[0])){
								
								if (count($urlNodes) === 2){
									$this->_URLdirectory = 'root';				// database directory
									$this->_fileDirectory = $urlNodes[0];		// as physical folder
									$this->_pageUrl = end($urlNodes);			// end() to allow many sub directories in link
								} else {
									$this->_URLdirectory = $urlNodes[1];		// database directory (after physical directory in URL)
									$this->_fileDirectory = $urlNodes[0];		// as physical folder
									$this->_pageUrl = end($urlNodes);			// end() to allow many sub directories in link
								}
								
							} else {
							
								$this->_URLdirectory = $urlNodes[0];			// database directory
								$this->_fileDirectory = DEFAULT_DIRECTORY;		// default directory instead?
								$this->_pageUrl = end($urlNodes);				// end() to allow many sub directories in link
							}
						
					}			
					
				}

First we take a working copy of the URL_NODES constant, as we may need to use the end() function on it. Having made this copy, we test to see if the request is in fact the homepage of the website, by the existence of the element $urlNodes[0] (i.e. the array is empty). 

If the $urlNodes array only contains 1 element, we might be at the index page of a physical directory within the /view/ folder, or it might simply be a webpage. Testing for the existence of this directory tells us which.

However, if the $urlNodes array contains more than 1 element, then we can determine - again based on the existence of phyical folders - how to assign those properties.

If this doesn't make much sense, it will become clearer when we actually come to build a simple application using the above logic. Briefly, it allows us to group distinct 'areas' or 'sections' of the website into physical subfolders within the /view/ folder. For example, you might want /view/public/ to contain the template for the public front-end of a website, but /view/admin/ to contain the template for a protected administration panel for the website. You could also have /view/user/ for a user only area.

Why do this? Well, remember this framework is supposed to reflect common web applications and their functionality. For a public facing website, the template may need to be Search Engine Optomised, having unified CSS, JS, meta tags, Google Analytics codes etc. On the other hand, an Administration interface will need none of those things, as behind a username and password, a search engine index means nothing and the admin area may need vastly different JS plugins and CSS.

As you'll see below in the page loading functions, this also allows use to split out those /view/ subfolder database entries onto seperate tables.

Render

There isn't much limitation to the /view/ subdirectory folders other than one stipulation: all /view/ subfolders must contain a /view/subfolder/render.php file:

     /**
     * main render call
     * 
     * @return void
     */
		public function render(): void
		{
			
			/*
			* first find physical actual directories - they will always be preferred over default directory.
			* e.g. if you have an "admin" interface separate to front end, mount in a directory called /view/admin/ 
			*/
				if (is_dir(DOC_ROOT.'/view/'.$this->_fileDirectory)){

					if (file_exists(DOC_ROOT.'/view/'.$this->_fileDirectory.'/render.php')) {
						require(DOC_ROOT.'/view/'.$this->_fileDirectory.'/render.php');
					} else {
						echo 'render file not in view directory: '.$this->_fileDirectory;
						exit;
					}		
				} 
			
			/*
			* if a physical directory does not exist, attempt to rout to DEFAULT_DIRECTORY instead
			*/
				else {
					
					if (file_exists(DOC_ROOT.'/view/'.DEFAULT_DIRECTORY.'/render.php')) {
						require(DOC_ROOT.'/view/'.DEFAULT_DIRECTORY.'/render.php');
					} else {
						echo 'render file not in view directory: '.DEFAULT_DIRECTORY;
						exit;
					}
					
				}
			
			return;

		}

The render file is intended to be a definition of how the template is assembled, plus some other rules that may redirect a request given specific cretieria. We'll get into that when we make our simple application.

Loading a Page

The loadPage() function now takes the private properties we assigned above, and attempts to retrieve it from the database. Having this functionality outside of the __construct() means in any given /view/subfolder/render.php file, we don't actually need to use the database for our views, we can simply define inclusion files instead. The likelihood is however, that even a small application will use a database of some kind, and so this function hooks into that.

    /**
     * Load up the webpage row from database
     * 
     * @return array
     */
		public function loadPage(): ?array
		{
			
			try{
				
				//strict to catch a directory that doesn't exist.
				$this->setWhereFields(['name' => $this->_URLdirectory, 'live' => 1]);
				$this->setWhereOps('==');
				$result_dir = $this->selStrict($this->_fileDirectory.'_directories', \Lectric\lecPDO::SINGLE, \Lectric\lecPDO::STRICT);
				
				//get webpage via directory id
				$this->setWhereFields(['directory' => $result_dir['id'],'url' => $this->_pageUrl, 'live' => 1]);
				$this->setWhereOps('===');
				$result = $this->selStrict($this->_fileDirectory.'_views', \Lectric\lecPDO::SINGLE, \Lectric\lecPDO::STRICT);
				
			} catch (\Exception $e){
				
				//is what they've requested a /do/ request?
				if($this->_URLdirectory === 'do'){
					header("HTTP/1.0 400 Bad Request");
				} else {
					header("HTTP/1.0 404 Not Found");
				}
				
				$this->setWhereFields(['url' => 'error']);
				$this->setWhereOps('=');
				$result = $this->selStrict($this->_fileDirectory.'_views', \Lectric\lecPDO::SINGLE);

			}
			
			return $result;
			
		}

This is the first example we've seen of a real use of the selStrict() function. We can use $this as the view class extends lecPDO, we don't need to instantiate another lecPDO object to make database calls.

Surrounding the calls in a try / catch enables us to use the \Lectric\lecPDO::STRICT option. In this case that means that if either the requested directory or webpage is not present in the database, we can assume the requested page has not been found and so return a 400/404 error depending on whether or not the URL request was a /do/ request or normal view request.

This also assumes that the database always contains an error page entry - a good practice in any circumstance.

Conclusion

With the last library class complete, we can now move on to making a simple application!

Continue to Part 8 by clicking here.




Hide Sidebar >



Archive



Search


Hide Sidebar >


This website uses cookies. Privacy Policy