文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

MVC是什么?

From Wikipedia

模型–视图–控制器 (MVC)是软件工程中使用的一种架构模式。 成功运用该模式可以将应用程序的业务逻辑从用户界面中隔离出来,这样可以轻易的修改应用程序的可视界面或者底层业务规则,而不互相影响。在MVC中,模型表示应用程序的信息(数据);视图对应于用户界面的元素,如文本、复选框选项,等等; 控制器管理数据的通信和用于操纵模型数据的业务规则。

第 1 段(可获 1.33 积分)

简而言之-

1. 模型处理所有的数据库逻辑。使用模型,我们连接到数据库并提供一个抽象层。
2. 控制器代表我们所有的业务逻辑,也就是我们的if和else语句。
3. 视图代表我们的展现逻辑,也就是HTML/XML/JSON代码。

为什么我要写我自己的框架?

对你的框架需求,本教程决不是一个全面的/权威的解决方案。这里有很多好的PHP框架

那么你为什么要写自己的框架呢?首先,这是一个很好的学习经验。你将学习PHP、面向对象编程、设计模式和一些注意事项。

第 2 段(可获 1.38 积分)

更重要的是,你可以完全掌控自己的框架。你的想法可以被集成到你的框架中。虽然并不总是有益的,你可以编写你所喜欢的编码规范/功能/模块。

让我们开始探索

目录结构

Directory Structure

尽管本教程不会用到上面所有的目录,但为了后面拓展,我们依然需要他们。让我解释一下每个目录的用途:

application – 应用程序特定的代码
config – 数据库/服务器配置
db – 数据库备份
library – 框架代码
public – 应用程序特定的s/css/images
scripts – 命令行工具
tmp – 临时数据

第 3 段(可获 1.21 积分)

目录结构设置好后,让我们定义下编码规范。

编码规范

1. mySQL 的表名总是小写、复数的 例如items, cars
2. 模型名总是单数的并且首字母大写 例如Item, Car
3. 控制器名总是以“Controller”结尾.例如ItemsController, CarsController
4. 视图总是复数名称并放置一些以动作命名的文件。例如items/view.php, cars/buy.php

我们首先在根目录添加.htaccess文件,这将将所有调用重定向到public目录

<IfModule mod_rewrite.c>
    RewriteEngine on
    RewriteRule    ^$    public/    [L]
    RewriteRule    (.*) public/$1    [L]
 </IfModule>
第 4 段(可获 1.19 积分)

接着在public目录添加.htaccess,它将所有的调用重定向到index.php。第三和第四行确保所请求的路径不是一个文件或目录。第7行重定向所有这类路径到ndex.php?url=PATHNAME

<IfModule mod_rewrite.c>
RewriteEngine On
 
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule ^(.*)$ index.php?url=$1 [PT,L]

</IfModule>

这种重定向有许多好处-
a) 可以用它来bootstrapping 即所有的调用都要经过index.php除了for images/js/cs.
b) 可以使用漂亮的/seo友好的URLS
c) 只有一个入口点

第 5 段(可获 1.04 积分)

现在我们在 public 目录添加 index.php 文件

<?php	

define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(dirname(__FILE__)));

$url = $_GET&#91;'url'&#93;;

require_once (ROOT . DS . 'library' . DS . 'bootstrap.php');
&#91;/sourcecode&#93;

注意我故意不包含结束标签 ?>。这是为了避免在输出的时候注入任何额外的空格。此外,我建议你查看 Zend 的代码风格。 我们的 index.php 设置了 $url 变量并调用 bootstrap.php 现在来看看 bootstrap.php

<?php

require_once (ROOT . DS . 'config' . DS . 'config.php');
require_once (ROOT . DS . 'library' . DS . 'shared.php');
&#91;/sourcecode&#93;

是的,这些 requires 语句已经在 index.php 包含过。但并没有故意这样做,主要是为了将来扩展用。 现在我们来看看 shared.php ,这是真正在做一些工作的地方 🙂

&#91;sourcecode language="php"&#93;
<?php

/** Check if environment is development and display errors **/

function setReporting() {
if (DEVELOPMENT_ENVIRONMENT == true) {
	error_reporting(E_ALL);
	ini_set('display_errors','On');
} else {
	error_reporting(E_ALL);
	ini_set('display_errors','Off');
	ini_set('log_errors', 'On');
	ini_set('error_log', ROOT.DS.'tmp'.DS.'logs'.DS.'error.log');
}
}

/** Check for Magic Quotes and remove them **/

function stripSlashesDeep($value) {
	$value = is_array($value) ? array_map('stripSlashesDeep', $value) : stripslashes($value);
	return $value;
}

function removeMagicQuotes() {
if ( get_magic_quotes_gpc() ) {
	$_GET    = stripSlashesDeep($_GET   );
	$_POST   = stripSlashesDeep($_POST  );
	$_COOKIE = stripSlashesDeep($_COOKIE);
}
}

/** Check register globals and remove them **/

function unregisterGlobals() {
    if (ini_get('register_globals')) {
        $array = array('_SESSION', '_POST', '_GET', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');
        foreach ($array as $value) {
            foreach ($GLOBALS&#91;$value&#93; as $key => $var) {
                if ($var === $GLOBALS[$key]) {
                    unset($GLOBALS[$key]);
                }
            }
        }
    }
}

/** Main Call Function **/

function callHook() {
	global $url;

	$urlArray = array();
	$urlArray = explode("/",$url);

	$controller = $urlArray[0];
	array_shift($urlArray);
	$action = $urlArray[0];
	array_shift($urlArray);
	$queryString = $urlArray;

	$controllerName = $controller;
	$controller = ucwords($controller);
	$model = rtrim($controller, 's');
	$controller .= 'Controller';
	$dispatch = new $controller($model,$controllerName,$action);

	if ((int)method_exists($controller, $action)) {
		call_user_func_array(array($dispatch,$action),$queryString);
	} else {
		/* Error Generation Code Here */
	}
}

/** Autoload any classes that are required **/

function __autoload($className) {
	if (file_exists(ROOT . DS . 'library' . DS . strtolower($className) . '.class.php')) {
		require_once(ROOT . DS . 'library' . DS . strtolower($className) . '.class.php');
	} else if (file_exists(ROOT . DS . 'application' . DS . 'controllers' . DS . strtolower($className) . '.php')) {
		require_once(ROOT . DS . 'application' . DS . 'controllers' . DS . strtolower($className) . '.php');
	} else if (file_exists(ROOT . DS . 'application' . DS . 'models' . DS . strtolower($className) . '.php')) {
		require_once(ROOT . DS . 'application' . DS . 'models' . DS . strtolower($className) . '.php');
	} else {
		/* Error Generation Code Here */
	}
}

setReporting();
removeMagicQuotes();
unregisterGlobals();
callHook();
第 6 段(可获 0.11 积分)

让我简要地解释一下上面的代码。当DEVELOPMENT_ENVIRONMENT为true时,setReporting() 函数帮助我们显示错误。下一步是移除global variables 和magic quotes。另一个我们所使用的函数是__autoload,它帮助我们自动加载所需的类。最后,我们执行callHook()函数进入主处理。

首先,让我说下每个URL的格式 – yoursite.com/controllerName/actionName/queryString

因此,总的来说callHook()就是获取从index.php得来的URL并从中分离出$controller和$action,然后剩余部分作为$queryString。$model 是单数版本的 $controller。

第 7 段(可获 1.38 积分)

例如我们的URL为 todo.com/items/delete/1/first-item, 那么
控制器为items
模型为item (相应的mysql表名)
视图为delete
动作为delete
查询字符串是一个数组(1,first-item)

分离完成后,它创建一个$controller.”Controller”类的对象并调用类的$action方法。

现在让我们创建一些类,首先是Controller类,它将作为所有控制器的基类,Model类作为所有模型的基类。

首先controller.class.php

第 8 段(可获 1.28 积分)
<?php
class Controller {
	
	protected $_model;
	protected $_controller;
	protected $_action;
	protected $_template;

	function __construct($model, $controller, $action) {
		
		$this->_controller = $controller;
		$this->_action = $action;
		$this->_model = $model;

		$this->$model =&amp; new $model;
		$this->_template =&amp; new Template($controller,$action);

	}

	function set($name,$value) {
		$this->_template->set($name,$value);
	}

	function __destruct() {
			$this->_template->render();
	}
		
}

上面的类用于控制器、模型和视图(template类)之间的所有通信。 它创建一个模型类的对象和一个template类的对象。模型对象名和模型名一样,这样我们可以在控制器中像这样$this->Item->selectAll();调用它。

第 9 段(可获 0.74 积分)

当释放类后,我们可以调用 render() 函数来显示视图(模板)文件。

现在来看看 model.class.php

<?php
class Model extends SQLQuery {
	protected $_model;

	function __construct() {

		$this->connect(DB_HOST,DB_USER,DB_PASSWORD,DB_NAME);
		$this->_model = get_class($this);
		$this->_table = strtolower($this->_model)."s";
	}

	function __destruct() {
	}
}

这个 Model 类扩展了 SQLQuery 类,这是对 mySQL 连接的抽象层封装。你可以指定其他任何数据库连接类,这取决于你的需求。

第 10 段(可获 0.71 积分)

现在我们来看下SQLQuery.class.php

<?php

class SQLQuery {
    protected $_dbHandle;
    protected $_result;

    /** Connects to database **/

    function connect($address, $account, $pwd, $name) {
        $this->_dbHandle = @mysql_connect($address, $account, $pwd);
        if ($this->_dbHandle != 0) {
            if (mysql_select_db($name, $this->_dbHandle)) {
                return 1;
            }
            else {
                return 0;
            }
        }
        else {
            return 0;
        }
    }

    /** Disconnects from database **/

    function disconnect() {
        if (@mysql_close($this->_dbHandle) != 0) {
            return 1;
        }  else {
            return 0;
        }
    }
    
    function selectAll() {
    	$query = 'select * from `'.$this->_table.'`';
    	return $this->query($query);
    }
    
    function select($id) {
    	$query = 'select * from `'.$this->_table.'` where `id` = \''.mysql_real_escape_string($id).'\'';
    	return $this->query($query, 1);    
    }

	
    /** Custom SQL Query **/

	function query($query, $singleResult = 0) {

		$this->_result = mysql_query($query, $this->_dbHandle);

		if (preg_match("/select/i",$query)) {
		$result = array();
		$table = array();
		$field = array();
		$tempResults = array();
		$numOfFields = mysql_num_fields($this->_result);
		for ($i = 0; $i < $numOfFields; ++$i) {
		    array_push($table,mysql_field_table($this->_result, $i));
		    array_push($field,mysql_field_name($this->_result, $i));
		}

		
			while ($row = mysql_fetch_row($this->_result)) {
				for ($i = 0;$i < $numOfFields; ++$i) {
					$table&#91;$i&#93; = trim(ucfirst($table&#91;$i&#93;),"s");
					$tempResults&#91;$table&#91;$i&#93;&#93;&#91;$field&#91;$i&#93;&#93; = $row&#91;$i&#93;;
				}
				if ($singleResult == 1) {
		 			mysql_free_result($this->_result);
					return $tempResults;
				}
				array_push($result,$tempResults);
			}
			mysql_free_result($this->_result);
			return($result);
		}
		

	}

    /** Get number of rows **/
    function getNumRows() {
        return mysql_num_rows($this->_result);
    }

    /** Free resources allocated by a query **/

    function freeResult() {
        mysql_free_result($this->_result);
    }

    /** Get error string **/

    function getError() {
        return mysql_error($this->_dbHandle);
    }
}

第 11 段(可获 0.14 积分)

SQLQuery.class.php是我们框架的核心。为什么?因为通过它创建一个SQL抽象层,可以大大减少数据库的编程工作。在下部分中,我们将探索一个新版本的SQLQuery.class.php。现在我们先保持它的简单性。

connect()disconnect() 函数相当的通用,所以这里就不做细节的深入。 让我们来具体看下查询类。48行首先执行查询。让我们看个例子,假设下面的一个SQL查询语句:

SELECT table1.field1 , table1.field2, table2.field3, table2.field4 FROM table1,table2 WHERE ….

第 12 段(可获 1.34 积分)

我们的脚本要做的首先是找出所有的输出字段以及它们对应的表,并把这些字段存放在数组 $field 和 $table 中,存放的顺序一致。上面的例子中 $table 和 $field 如下所示:

$field = array(field1,field2,field3,field4);
$table = array(table1,table1,table2,table2);

这个脚本接下来获取所有的数据行,并将表转成 Modal 名(如去除复数以及首字母大写),然后放置到我们定义的多维数组中,然后返回结果。结果的形式为 $var[‘modelName’][‘fieldName’]。这个输出风格可以让我们很方便的在视图中包含数据库元素。

第 13 段(可获 1.41 积分)

现在让我们看一看template.class.php

<?php
class Template {
	
	protected $variables = array();
	protected $_controller;
	protected $_action;
	
	function __construct($controller,$action) {
		$this->_controller = $controller;
		$this->_action = $action;
	}

	/** Set Variables **/

	function set($name,$value) {
		$this->variables[$name] = $value;
	}

	/** Display Template **/
	
    function render() {
		extract($this->variables);
		
			if (file_exists(ROOT . DS . 'application' . DS . 'views' . DS . $this->_controller . DS . 'header.php')) {
				include (ROOT . DS . 'application' . DS . 'views' . DS . $this->_controller . DS . 'header.php');
			} else {
				include (ROOT . DS . 'application' . DS . 'views' . DS . 'header.php');
			}

        include (ROOT . DS . 'application' . DS . 'views' . DS . $this->_controller . DS . $this->_action . '.php');		 
			
			if (file_exists(ROOT . DS . 'application' . DS . 'views' . DS . $this->_controller . DS . 'footer.php')) {
				include (ROOT . DS . 'application' . DS . 'views' . DS . $this->_controller . DS . 'footer.php');
			} else {
				include (ROOT . DS . 'application' . DS . 'views' . DS . 'footer.php');
			}
    }

}
第 14 段(可获 0.13 积分)

上述代码非常的简单,只是有一点,如果在 view/controllerName 文件夹中找不到对应的页头和页尾的话,它将使用 view 目录中定义的全局页头和页尾。

现在我们所需要做的就是在 config 文件夹中添加 config.php 文件,然后开始创建我们的首个模型、视图以及控制器。

<?php

/** Configuration Variables **/

define ('DEVELOPMENT_ENVIRONMENT',true);

define('DB_NAME', 'yourdatabasename');
define('DB_USER', 'yourusername');
define('DB_PASSWORD', 'yourpassword');
define('DB_HOST', 'localhost');
&#91;/sourcecode&#93;

唷! 现在让我们来创建首个 mini-todo 应用,首先创建一个数据库 "todo" 然后执行如下 SQL 查询:

&#91;sourcecode language="sql"&#93;
CREATE TABLE `items` (
  `id` int(11) NOT NULL auto_increment,
  `item_name` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`)
);

INSERT INTO `items` VALUES(1, 'Get Milk');
INSERT INTO `items` VALUES(2, 'Buy Application');
&#91;/sourcecode&#93;

一旦完成数据库的创建,接着添加 item.php 到 model 目录,内容如下:

&#91;sourcecode language="php"&#93;
<?php

class Item extends Model {

}
&#91;/sourcecode&#93;

内容是空的,更多的内容我们将在第二部分中进行扩展。

现在创建一个名为 itemscontroller.php 的文件,存放在 controller 目录

&#91;sourcecode language="php"&#93;
<?php

class ItemsController extends Controller {

	function view($id = null,$name = null) {
	
		$this->set('title',$name.' - My Todo List App');
		$this->set('todo',$this->Item->select($id));

	}
	
	function viewall() {

		$this->set('title','All Items - My Todo List App');
		$this->set('todo',$this->Item->selectAll());
	}
	
	function add() {
		$todo = $_POST['todo'];
		$this->set('title','Success - My Todo List App');
		$this->set('todo',$this->Item->query('insert into items (item_name) values (\''.mysql_real_escape_string($todo).'\')'));	
	}
	
	function delete($id = null) {
		$this->set('title','Success - My Todo List App');
		$this->set('todo',$this->Item->query('delete from items where id = \''.mysql_real_escape_string($id).'\''));	
	}

}
第 15 段(可获 0.76 积分)

最后在views目录中创建一个items目录并在该目录中创建以下文件-

view.php

<h2><?php echo $todo&#91;'Item'&#93;&#91;'item_name'&#93;?></h2>

	<a class="big" href="../../../items/delete/<?php echo $todo&#91;'Item'&#93;&#91;'id'&#93;?>">
	<span class="item">
	Delete this item
	</span>
	</a>

viewall.php

<form action="../items/add" method="post">
<input type="text" value="I have to..." onclick="this.value=''" name="todo"> <input type="submit" value="add">
</form>
<br/><br/>
<?php $number = 0?>

<?php foreach ($todo as $todoitem):?>
	<a class="big" href="../items/view/<?php echo $todoitem&#91;'Item'&#93;&#91;'id'&#93;?>/<?php echo strtolower(str_replace(" ","-",$todoitem&#91;'Item'&#93;&#91;'item_name'&#93;))?>">
	<span class="item">
	<?php echo ++$number?>
	<?php echo $todoitem&#91;'Item'&#93;&#91;'item_name'&#93;?>
	</span>
	</a><br/>
<?php endforeach?>
第 16 段(可获 0.26 积分)

delete.php

<a class="big" href="../../items/viewall">Todo successfully deleted. Click here to go back.</a>

add.php

<a class="big" href="../items/viewall">Todo successfully added. Click here to go back.</a>

header.php

<html>
<head>
<title><?php echo $title?></title>
<style>
.item {
width:400px;

}

input {
	color:#222222;
font-family:georgia,times;
font-size:24px;
font-weight:normal;
line-height:1.2em;
	color:black;
}

 a {
	color:#222222;
font-family:georgia,times;
font-size:24px;
font-weight:normal;
line-height:1.2em;
	color:black;
	text-decoration:none;
	
}

a:hover {
	background-color:#BCFC3D;
}
h1 {
color:#000000;
font-size:41px;
letter-spacing:-2px;
line-height:1em;
font-family:helvetica,arial,sans-serif;
border-bottom:1px dotted #cccccc;
}

h2 {
color:#000000;
font-size:34px;
letter-spacing:-2px;
line-height:1em;
font-family:helvetica,arial,sans-serif;

}
</style>
</head>
<body>
<h1>My Todo-List App</h1>
第 17 段(可获 0.08 积分)

footer.php

</body>
</html>

现在假设您已将目录结构上传到todo文件夹,将浏览器指向:
http://localhost/todo/items/viewall

Todo List App

接下来要做什么?

首先,通过完成本教程,我们收获良多 – 我们已经能够将我们的展示逻辑与业务逻辑和数据库逻辑分开。我们还能够提供优美的URL和可扩展性。这只是我们框架的开始。还有很多要做的。在下一部分,我将谈论如何使我们的模型和SQLQuery类更强大。

第 18 段(可获 1.21 积分)

想偷懒? 点这里的下载链接

Download Framework Part 1 (ZIP File)

务必编辑config/config.php并添加数据库详细信息。浏览器访问localhost/FRAMEWORKDIR/items/viewall查看输出。

建议?

请告诉我你对如何改进这个代码的建议/任何你希望看到的特征/任何你希望有篇教程的话题。

完成了这部分?想阅读第2部分吗?

既然你已经完成了第1部分,再看看PHP MVC Framework第二部分吧。

 

第 19 段(可获 1.14 积分)

文章评论