MVC - Part II

Dieser erweiterte MVC soll zeigen, wie man mittels einer Request-Klasse und einem FactoryMuster mehrere Controller, Models und Views per Request ansprechen kann. Natürlich sind auch diese Scripte von einer "vollkommenen" Lösung weit entfernt - beispielsweise fehlen Exceptions zum Abfangen von Fehlern u.ä.

Das Script soll nur verdeutlichen, wie man einen Minimal-MVC weiter aufbohren und so die Grundlage für ein simples Framework schaffen kann.

./index.php
<?php
define ('BASEPATH', realpath(dirname(__FILE__)));
include(BASEPATH.DIRECTORY_SEPARATOR.'classes' .DIRECTORY_SEPARATOR.'autoload.class.php');


$controller = Factory::getController($request = new Request());
echo $controller->display();
?>
./classes/autoload.class.php
<?php
function __autoload ($classe) {

	$class = strtolower($classe);
	$class = $class.'.class.php';
	
	$directory = array('classes', 'controller', 'model');

	foreach ($directory as $value) {
		if (file_exists(BASEPATH.DIRECTORY_SEPARATOR.$value.DIRECTORY_SEPARATOR.$class)) {
			include(BASEPATH.DIRECTORY_SEPARATOR.$value.DIRECTORY_SEPARATOR.$class);
		}
	}
}
?>
./classes/factory.class.php
<?php
class Factory {
	
	public static function getController($request) {
		
		$page = $request->getVar('page');
		$controller = $page.'Controller';
		
		if (empty($page)) {
			$controller =  new DefaultController();
			return $controller;
		}
		
		if (class_exists($controller)) {
			$controller =  new $controller();
		} else {
			$controller =  new ErrorController();
		}		
		return $controller;
	}
}
?>
./classes/request.class.php
class Request {
	
	protected $_request = array (
			'METHOD' => array (),
			'GET' => array (),
			'POST' => array (),
			'COOKIE' => array (),
			'FILES' => array (),
			'SERVER' => array (),
			'ENV' => array () 
	);
	protected $_params = array ();
	protected $_requestUri;
	protected $_baseUrl;
	protected $_pathInfo;
	protected $_method;
	
	
	public function __construct() {
		
		$this->_request ['GET'] 	= & $_GET;
		$this->_request ['POST'] 	= & $_POST;
		$this->_request ['COOKIE'] 	= & $_COOKIE;
		$this->_request ['FILES'] 	= & $_FILES;
		$this->_request ['SERVER'] 	= & $_SERVER;
		$this->_request ['ENV'] 	= & $_ENV;
		
		$this->_method = strtoupper ( $this->_request ['SERVER'] ['REQUEST_METHOD'] );
		
		switch ($this->_method) {
			case 'POST' :
				$this->_request ['METHOD'] = & $this->_request ['POST'];
				break;
			case 'COOKIE' :
				$this->_request ['METHOD'] = & $this->_request ['COOKIE'];
				break;
			case 'GET' :
			default :
				$this->_request ['METHOD'] = & $this->_request ['GET'];
		}
		
		$this->setVar ( 'REQUEST_URI', $this->getRequestUri (), 'SERVER' );
		$this->setVar ( 'PATH_INFO', $this->getPathInfo (), 'SERVER' );
	}
	
	
	public function __get($name) {
		
		if (isset ( $this->_request ['METHOD'] [$name] )) {
			return $this->_request ['METHOD'] [$name];
		}
		return null;
	}
	
	
	public function __set($name, $value) {
		$this->_request ['METHOD'] [$name] = $value;
	}
	
	
	public function __isset($name) {
		return isset ( $this->_request ['METHOD'] [$name] );
	}
	
	
	public function has($name) {
		return $this->__isset ( $name );
	}
	
	
	public function get($name, $default = null) {
		
		if ($this->__isset ( $name )) {
			return $this->__get ( $name );
		}
		return $default;
	}
	
	
	public function set($name, $value) {
		$this->__set ( $name, $value );
	}
	
	
	public function getVar($name, $filter = 'striped', $default = null, $method = 'METHOD') {
		
		$method = strtoupper ( $method );
		
		if ($method === 'METHOD') {
			$method = $this->getMethod ();
		}
		
		if (isset ( $this->_request [$method] [$name] )) {
			$var = $this->_request [$method] [$name];
			
			if ($method != 'FILES' && $var != $default) {
				if (get_magic_quotes_gpc ()) {
					$var = $this->_stripSlashesRecursive ( $var );
				}
			}
		} else {
			$var = $default;
		}
		return $var;
	}
	
	
	public function setVar($name, $value = null, $method = 'METHOD', $overwrite = true) {
		
		$method = strtoupper ( $method );
		
		if ($method === 'METHOD') {
			$method = $this->getMethod ();
		}
		
		if ($overwrite != true && array_key_exists ( $name, $this->_request [$method] [$name] )) {
			return $this->_request [$method] [$name];
		}
		
		$previous = array_key_exists ( $name, $this->_request [$method] ) ? $this->_request [$method] [$name] : null;
		
		if (isset ( $this->_request [$method] )) {
			$this->_request [$method] [$name] = $value;
		}
		return $previous;
	}
	
	
	public function getParam($name, $default = null) {
		
		if (isset ( $this->_params [$name] )) {
			return $this->_params [$name];
		}
		return $default;
	}
	
	
	public function getParams() {
		return $this->_params;
	}
	
	
	public function setParam($name, $value) {
		$this->_params [$name] = $value;
	}
	
	
	public function setParams(array $params) {
		
		foreach ( $params as $key => $value ) {
			$this->setParam ( $key, $value );
		}
	}
	
	
	public function isPost() {
		return $this->_method == 'POST';
	}
	
	
	public function getMethod() {
		return $this->_method;
	}
	
	
	public function setRequestUri($requestUri = null) {
		
		if ($requestUri === null) {
			if (isset ( $this->_request ['SERVER'] ['REQUEST_URI'] )) {
				$requestUri = $this->_request ['SERVER'] ['REQUEST_URI'];
			} elseif (isset ( $this->_request ['SERVER'] ['HTTP_X_REWRITE_URL'] )) {
				$requestUri = $this->_request ['SERVER'] ['HTTP_X_REWRITE_URL'];
			} else {
				return false;
			}
		}
	}
	
	
	public function getRequestUri() {
		
		if ($this->_requestUri === null) {
			$this->setRequestUri ();
		}
		return $this->_requestUri;
	}
	
	
	public function setBaseUrl($baseUrl = null) {
		
		if ($baseUrl === null) {
			$filename = basename ( $this->_request ['SERVER'] ['SCRIPT_NAME'] );
			
			if (basename ( $this->_request ['SERVER'] ['SCRIPT_NAME'] ) === $filename) {
				$baseUrl = $this->_request ['SERVER'] ['SCRIPT_NAME'];
			} elseif (basename ( $this->_request ['SERVER'] ['PHP_SELF'] ) === $filename) {
				$baseUrl = $this->_request ['SERVER'] ['PHP_SELF'];
			} elseif (isset ( $this->_request ['SERVER'] ['ORIG_SCRIPT_NAME'] ) && basename ( $this->_request ['SERVER'] ['ORIG_SCRIPT_NAME'] ) === $filename) {
				$baseUrl = $this->_request ['SERVER'] ['ORIG_SCRIPT_NAME'];
			} else {
				return false;
			}
			
			if (($requestUri = $this->getRequestUri ()) === null) {
				return false;
			}
			
			if (($pos = strpos ( $requestUri, $baseUrl )) === false || $pos !== 0) {
				$baseUrl = dirname ( $baseUrl );
			}
		}
		
		$this->_baseUrl = rtrim ( $baseUrl, '/' );
	}
	
	
	public function getBaseUrl() {
		
		if ($this->_baseUrl === null) {
			$this->setBaseUrl ();
		}
		
		return $this->_baseUrl;
	}
	
	
	public function setPathInfo($pathInfo = null) {
		
		if ($pathInfo === null) {
			if (($baseUrl = $this->getBaseUrl ()) === null) {
				return false;
			}
			
			if (($requestUri = $this->getRequestUri ()) === null) {
				return false;
			}
			
			if ($pos = strpos ( $requestUri, '?' )) {
				$requestUri = substr ( $requestUri, 0, $pos );
			}
			
			if (($pathInfo = substr ( $requestUri, strlen ( $baseUrl ) )) === false) {
				$pathInfo = '';
			}
		}
		$this->_pathInfo = $pathInfo;
	}
	
	
	public function getPathInfo() {
		
		if ($this->_pathInfo === null) {
			$this->setPathInfo ();
		}
		return $this->_pathInfo;
	}
	
	
	protected function _stripSlashesRecursive($value) {
		
		if (is_array ( $value )) {
			$value = array_map ( array (
					$this,
					'_stripSlashesRecursive' 
			), $value );
		} else {
			$value = stripslashes ( $value );
		}
		return $value;
	}
	
}
./classes/defaultcontroller.class.php
<?php
class DefaultController {

	public function __construct() {
		
		$this->request 		= new Request();	
		$this->view 		= new View($this->request->getVar('template'));
	}

	public function display() {
		
		$this->view->setContent("title"		, "OOP mit PHP - Willkommen");
		$this->view->setContent("navigation", DefaultModel::getNavigation());
		$this->view->setContent("content"	, DefaultModel::getContent());
		$this->view->setContent("footer"	, "&copy 2013 by mausernetwork");

		return $this->view->parseTemplate();
	}
}
?>
./classes/defaultmodel.class.php
<?php
class DefaultModel {

	public function __construct() {
		// ich hole hier spaeter die Datenbankverbindung :)
	}

	public static function getNavigation() {
		
		$text = "<ul id='navigation'>";
		$text .=  "<li><a href='index.php?page=first'>Seite 1</a></li>";
		$text .= "<li><a href='index.php?page=second'>Seite 2</a></li>";
		$text .= "<li><a href='index.php?page=third'>Seite 3</a></li>";
		$text .= "</ul>";
		
		return $text;
	}
	
	public static function getContent() {
	
		$text = "Herzlich Willkommen auf unserer Seite. Mit dem linken Men&uuml; k&ouml;nnen Sie zwischen den Inhalten bl&auml;ttern.";
	
		return $text;
	}
}
?>
./classes/view.class.php
class View {

	private $path 		= 'templates';
	private $content 	= array();
	private $template;
	
	public function __construct($template) {
		
		// Hier wird jetzt ein anderes Template geladen...
		// ... oder, wenn nicht vorhanden, wird das default Template geladen
		
		if (file_exists($this->path . DIRECTORY_SEPARATOR . $template . '.tpl.php')) {
			$this->template = $this->path . DIRECTORY_SEPARATOR . $template. '.tpl.php';
		} else {
			$this->template = $this->path . DIRECTORY_SEPARATOR  . 'default.tpl.php';
		}
		
		return $this->template;
	}

	public function setContent($key, $value){
		$this->content[$key] = $value;
	}

	public function parseTemplate() {

		if (file_exists($this->template)) {

			ob_start();
			include $this->template;
			$output = ob_get_contents();
			ob_end_clean();

			return $output;
		}
		return "Kann das Template ".$this->template." nicht finden";
	}
}
./controller/firstcontroller.class.php
<?php
class FirstController extends DefaultController {

	public function display() {
		parent::display();
		
		$this->view->setContent("title"		,"OOP mit PHP - Seite1");
		$this->view->setContent("content"	, FirstModel::getContent());

		return $this->view->parseTemplate();
	}
}
?>
./controller/secondcontroller.class.php
<?php
class SecondController extends DefaultController {

	public function display() {
		parent::display();
		
		$this->view->setContent("title"		,"OOP mit PHP - Seite2");
		$this->view->setContent("content"	, SecondModel::getContent());

		return $this->view->parseTemplate();
	}
}
?>
./controller/errorcontroller.class.php
<?php
class ErrorController {

	private $view;

	public function __construct() {
		$this->view 		= new View('error');
	}

	public function display() {
		
		$this->view->setContent("title"		,"OOP mit PHP - Error 404");
		$this->view->setContent("navigation", ErrorModel::getNavigation());
		$this->view->setContent("content"	, ErrorModel::getContent());
		$this->view->setContent("footer"	,"Hotline bei Problemen: 1234-567-890");

		return $this->view->parseTemplate();
	}
}
?>
./model/firstmodel.class.php
<?php
class FirstModel {
	
	public static function getContent() {
	
		$text = "Das ist der Inhalt von Seite 1";
	
		return $text;
	}
}
?>
./model/secondmodel.class.php
<?php
class SecondModel {
	
	public static function getContent() {
	
		$text = "Das ist der Inhalt von Seite 2";
	
		return $text;
	}
}
?>
./model/errormodel.class.php
<?php
class ErrorModel {

	public static function getNavigation() {
		
		$text = "<ul id='navigation'>";
		$text .=  "<li><a href='index.php?page=first'>Seite 1</a></li>";
		$text .= "<li><a href='index.php?page=second'>Seite 2</a></li>";
		$text .= "</ul>";

		return $text;
	}
	
	public static function getContent() {
	
		$text = "404 - Diese Seite existiert leider nicht.<br /> Bitte w&auml;hlen Sie eine Seite aus der linken Navigation.";
	
		return $text;
	}
}
?>
./templates/default.tpl.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
<head>
<title><?php echo $this->content['title']; ?></title>
<style type="text/css">
ul {
	margin: 0px;
	padding: 0px;
	list-style-type: none;
}
ul li {
	background-color: #c0c0c0;
	width: 90px;
	margin-bottom: 5px;
	padding: 5px;
}
a {
	color: #FFFFFF;
	text-decoration: none;
}
.content {
	width: 600px;
	padding-top: 10px;
	margin:auto;
	border: 1px solid grey;
}

.left {
	width: 100px;
	float: left;
	margin-right: 20px;
	margin-bottom: 10px;
}
.right {
	width: 470px;
	height: 200px;
	display: inline-block;
}
.footer {
	clear: left;
	background-color: #c0c0c0;
	padding: 5px;
}
</style>
</head>
<body>
<div class="content">
	<div class="left"><?php echo $this->content['navigation']; ?></div>
	<div class="right"><?php echo $this->content['content']; ?></div>
	<div class="footer"><?php echo $this->content['footer']; ?></div>
</div>
</body>
./templates/error.tpl.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
<head>
<title><?php echo $this->content['title']; ?></title>
<style type="text/css">
ul {
	margin: 0px;
	padding: 0px;
	list-style-type: none;
}
ul li {
	background-color: #c0c0c0;
	width: 90px;
	margin-bottom: 5px;
	padding: 5px;
}
a {
	color: #FFFFFF;
	text-decoration: none;
}
.content {
	width: 600px;
	padding-top: 10px;
	margin:auto;
	border: 1px solid grey;
}

.left {
	width: 100px;
	float: left;
	margin-right: 20px;
	margin-bottom: 10px;
}
.right {
	width: 470px;
	height: 200px;
	display: inline-block;
	color: red;
}
.footer {
	clear: left;
	background-color: #c0c0c0;
	padding: 5px;
}
</style>
</head>
<body>
<div class="content">
	<div class="left"><?php echo $this->content['navigation']; ?></div>
	<div class="right"><?php echo $this->content['content']; ?></div>
	<div class="footer"><?php echo $this->content['footer']; ?></div>
</div>
</body>

Video zum MVC - Part II

Das folgende Video soll den Ablauf der oben dargestellten Scripte zeigen. Da das mein erster Screencast ist bitte ich, von undeutlicher Aussprache oder kleineren Fehlern in den Beschreibungen abzusehen Zwinkernd Nachdem der Player gestartet wurde, kann man in der unteren Leiste die Lautstärke anpassen und das Video auf Vollbildmodus einstellen.

Download des MVC - Part II

Download Download (11,9 kB)

Kommentare

Kommentar von Nik | 16.09.2015

Moin! Ich finde das Tutorial sehr aufschlussreich und es hat mich motiviert mein eigenes CMS auf der Basis vom MVC Pattern zu gestalten.

Allerdings habe ich folgendes Verständnisproblem:

Die Daten meines Firstcontrollers sollen in eine andere view geladen werden als default, diese view soll aber im Content bereich des default tpl geladen werden.

Wie stelle ich das am vernünftigsten an ?