技术标签: php 框架搭建
现如今市面上有许多PHP框架,像 ThinkPHP、YII、Laravel,那么如何自己搭建一个PHP框架呢?这里有一篇博文写的非常好,特意转载过来,供朋友们研究,原文请戳详情。
一、什么是MVC
MVC 模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
MVC 模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。软件系统通过对自身基本部份分离的同时也赋予了各个基本部分应有的功能。专业人员可以通过自身的专长分组:
(控制器Controller)- 负责转发请求,对请求进行处理。
(视图View) – 界面设计人员进行图形界面设计。
(模型Model) – 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
模型(Model): “数据模型”(Model)用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“模型”有对数据直接访问的权力,例如对数据库的访问。“模型”不依赖“视图”和“控制器”,也就是说,模型不关心它会被如何显示或是如何被操作。但是模型中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此模型的视图必须事先在此模型上注册,从而,视图可以了解在数据模型上发生的改变。
视图(View): 视图层能够实现数据有目的的显示(理论上,这不是必需的)。在视图中一般没有程序上的逻辑。为了实现视图上的刷新功能,视图需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册。
控制器(Controller): 控制器起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据模型上的改变。
二、为什么要自己开发MVC框架
网络上有大量优秀的 MVC 框架可供使用,本教程并不是为了开发一个全面的、终极的 MVC 框架解决方案,而是将它看作是一个很好的从内部学习 PHP 的机会,在此过程中,你将学习面向对象编程和设计模式,并学习到开放中的一些注意事项。
更重要的是,你可以完全控制你的框架,并将你的想法融入到你开发的框架中。虽然不一定是做好的,但是你可以按照你的方式去开发功能和模块。
三、开始开发自己的MVC框架
在开始开发前,让我们先来把项目建立好,假设我们建立的项目为 Frame,那么接下来的第一步就是把目录结构先设置好。
虽然在这个教程中不会使用到上面的所有的目录,但是为了以后程序的可拓展性,在一开始就把程序目录设置好使非常必要的。下面就具体说说每个目录的作用:
application – 存放程序代码
config – 存放程序配置或数据库配置
db – 用来存放数据库备份内容
library – 存放框架代码
public – 存放静态文件
scripts – 存放命令行工具
tmp – 存放临时数据
在目录设置好以后,我们接下来就要来顶一下一些代码的规范:
MySQL的表名需小写并采用复数形式,如items,cars
模块名(Models)需首字母大写,并采用单数模式,如Item,Car
控制器(Controllers)需首字母大写,采用复数形式并在名称中添加“Controller”,如 ItemsController, CarsController
视图(Views)采用复数形式,并在后面添加行为作为文件,如:items/view.php, cars/buy.php
上述的一些规则是为了能在程序钟更好的进行互相的调用。接下来就开始真正的编码了。
第一步将所有的的请求都重定向到 public 目录下,解决方案是在 Frame 文件下添加一个.htaccesss文件,文件内容为:
RewriteEngine on
RewriteRule ^$ public/ [L]
RewriteRule (.*) public/$1 [L]
在我们把所有的请求都重定向到 public 目录下以后,我们就需要将所有的数据请求都再重定向到 public 下的 index.php 文件,于是就需要在public文件夹下也新建一个.htaccess文件,文件内容为:
RewriteEngine On
#如果文件存在就直接访问目录不进行RewriteRule
RewriteCond %{REQUEST_FILENAME} !-f
#如果目录存在就直接访问目录不进行RewriteRule
RewriteCond %{REQUEST_FILENAME} !-d
#将所有其他URL重写到 index.php/URL
RewriteRule ^(.*)$ index.php?url=$1 [PT,L]
这么做的主要原因有:
可以使程序有一个单一的入口,将所有除静态程序以外的程序都重定向到index.php上;
可以用来生成利于SEO的URL,想要更好的配置URL,后期可能会需要URL路由,这里先不做介绍了。
做完上面的操作,就应该知道我们需要做什么了,没错!在public目录下添加 index.php 文件,文件内容为:
// 定义分隔符常量
define('DS', DIRECTORY_SEPARATOR);
// 定义根目录常量 // D:\xampps\htdocs\web\Frame
define('ROOT', dirname(dirname(__FILE__)));
$url = $_GET['url'];
// 启动文件
require_once(ROOT.DS.'library'.DS.'bootstrap.php');
注意上面的PHP代码中,并没有添加PHP结束符号”?>”,这么做的主要原因是:对于只包含PHP代码的文件,结束标志(“?>”)最好不存在,PHP自身并不需要结束符号,不添加结束符号可以很大程度上防止末尾被添加额外的注入内容,让程序更加安全。
在index.php中,我们对 library 文件夹下的 bootstrap.php 发起了请求,那么bootstrap.php 这个启动文件中到底会包含哪些内容呢?
require_once(ROOT.DS.'config'.DS .'config.php');
require_once(ROOT.DS.'library'.DS .'shared.php');
以上文件都可以直接在index.php文件中引用,我们这么做的原因是为了在后期管理和拓展中更加的方便,所以把需要在一开始的时候就加载运行的程序统一放到一个单独的文件中引用。
先来看看config文件下的 config .php 文件,该文件的主要作用是设置一些程序的配置项及数据库连接等,主要内容为:
# 设置是否为开发状态
define('DEVELOPMENT_ENVIRONMENT',true);
# 设置数据库连接所需数据
define('DB_HOST','localhost');
define('DB_NAME','todo');
define('DB_USER','root');
define('DB_PASSWORD','root');
应该说config.php涉及到的内容并不多,不过是一些基础数据的一些设置,再来看看library下的共用文件 shared.php 应该怎么写。
/**
* 检查是否为开发环境并设置是否记录错误日志
*/
function setReporting()
{
if(DEVELOPMENT_ENVIRONMENT == true)
{
error_reporting(E_ALL); // 报告所有PHP错误
ini_set('display_errors', 'On'); // 开启php错误提示,而且是所有错误提示
}
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'); // 错误日志记录
}
}
/**
* 检测敏感字符转义(Magic Quotes)并移除他们(去除反斜杠)
*/
function stripSlashDeep($value)
{
$value = is_array($value) ? array_map('stripSlashDeep', $value) : stripslashes($value);
return $value;
}
/**
* 移除魔术引号(Magic Quotes)
*
* PHP魔术符号 get_magic_quotes_gpc() 会自动给你转义单引号(')、双引号(")、反斜线(\)与
* NUL(null字符),其实就相当于调用addslashes函数。
* 你可能会说这样不是很好嘛,安全性更高了,但是,你考虑代码移植性了吗?另外,对于上所有gpc($_GET,$_POST,$_COOKIE)的数据你
* 都进行转义是否有必要?开销有多大?所以,这里去除。
*/
function removeMagicQuotes()
{
if(get_magic_quotes_gpc())
{
$_GET = stripSlashDeep($_GET);
$_POST = stripSlashDeep($_POST);
$_COOKIE = stripSlashDeep($_COOKIE);
}
}
/**
* 检测全局变量设置(register globals)并移除他们
*
* 相关解释:http://blog.csdn.net/dotuian/article/details/9167263
* 当register_globals=Off的时候,下一个程序接收的时候应该用$_GET['user_name']和$_GET['user_pass']来接受传递过来的值。
* (注:当
的method属性为post的时候应该用$_POST['user_name']和$_POST['user_pass'])* 当register_globals=On的时候,下一个程序可以直接使用$user_name和$user_pass来接受值。
* 顾名思义,register_globals的意思就是注册为全局变量,所以当On的时候,传递过来的值会被直接的注册为全局变量直接使用,而Off的
* 时候,我们需要到特定的数组里去得到它。所以,碰到上边那些无法得到值的问题的朋友应该首先检查一下你的register_globals的设置和
* 你获取值的方法是否匹配。
*
* 那我们为什么要使用Off呢?
* 原因有2:
* 1、php以后的新版本默认都用Off,虽然你可以设置它为On,但是当你无法控制服务器的时候,你的代码的兼容性就成为一个大问题,所以
*,你最好从现在就开始用Off的风格开始编程 2、这里有两篇文章介绍为什么要Off而不用On
*/
function unregisterGlobals()
{
if(ini_get('register_globals'))
{
$array = array('_SESSION','_POST','_GET','_COOKIE','_REQUEST','_SERVER','_ENV','_FILES');
foreach ($array as $value)
{
// $GLOBALS 可以打印所有的全局变量
foreach ($GLOBALS[$value] as $key => $var)
{
if ($var === $GLOBALS[$key])
{
unset($GLOBALS[$key]);
}
}
}
}
}
/**
* 请求方法,主要目的拆分URL请求
*
* 相关学习:
* 调用回调函数,并把一个数组参数作为回调函数的参数
* call_user_func_array(array($obj, $method_name), $params);
* call_user_method_array ( string $method_name , object &$obj , array $params )
*/
function callHook()
{
global $url;
$urlArray = array();
$urlArray = explode('/', $url);
// 获取控制器 controller
$controller = $urlArray[0];
// 删除第一个元素
array_shift($urlArray);
// 获取动作 Action
$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
{
/* 生成错误代码 */
exit('该控制器不存在:'. $controller);
}
}
/**
* 自动加载控制器和模型
*/
function __autoload($className)
{
if(file_exists(ROOT . DS . 'library' . DS . strtolower($className) . '.class.php'))
{
// 加载核心库 library
require_once(ROOT . DS . 'library' . DS . strtolower($className) . '.class.php');
}
else if(file_exists(ROOT . DS . 'application' . DS . 'controllers' . DS . strtolower($className) . '.php'))
{
// 加载控制器 application 目录下
require_once(ROOT . DS . 'application' . DS . 'controllers' . DS . strtolower($className) . '.php');
}
else if(file_exists(ROOT . DS . 'application' . DS . 'models' . DS . strtolower($className) . '.php'))
{
// 加载控制器 application 目录下
require_once(ROOT . DS . 'application' . DS . 'models' . DS . strtolower($className) . '.php');
}
else
{
/* 生成错误代码 */
exit("类名不存在:" . $className);
}
}
setReporting();
removeMagicQuotes();
unregisterGlobals();
callHook();
接下来的操作就是在library中建立程序所需要的基类,包括控制器、模型和视图的基类。
新建控制器基类为controller.class.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 =& new $model; // php默认为传值传递,这里默认为地址传递,即引用同一个对象
$this->_template =& new Template($controller, $action);
}
function set($name,$value)
{
$this->_template->set($name,$value);
}
function __destruct()
{
// 放在析构函数中是为了在释放实例化的时候执行下面的方法
$this->_template->render();
}
}
新建控制器基类为 model.class.php,考虑到模型需要对数据库进行处理,所以可以新建一个数据库基类 sqlquery.class.php,模型去继承 sqlquery.class.php。
新建 sqlquery.class.php,代码如下:
class SQLQuery
{
protected $_dbHandle;
protected $_result;
/** 连接数据库 **/
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;
}
}
/** 中断数据库连接 **/
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);
}
/** 自定义SQL查询语句 **/
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[$i] = trim(ucfirst($table[$i]),"s");
$tempResults[$table[$i]][$field[$i]] = $row[$i];
}
if ($singleResult == 1) {
mysql_free_result($this->_result);
return $tempResults;
}
array_push($result,$tempResults);
}
mysql_free_result($this->_result);
return($result);
}
}
/** 返回结果集行数 **/
function getNumRows()
{
return mysql_num_rows($this->_result);
}
/** 释放结果集内存 **/
function freeResult()
{
mysql_free_result($this->_result);
}
/** 返回MySQL操作错误信息 **/
function getError()
{
return mysql_error($this->_dbHandle);
}
}
新建 model.class.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()
{
}
}
新建视图基类为template.class.php,具体代码如下:
class Template {
protected $variables = array();
protected $_controller;
protected $_action;
function __construct($controller,$action)
{
$this->_controller = $controller;
$this->_action =$action;
}
/* 设置变量 */
function set($name,$value)
{
$this->variables[$name] = $value;
}
/* 显示模板 */
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');
}
}
}
四、框架应用
做完了以上这么多操作,基本上整个MVC框架已经出来了,下面就该制作我们的站点了。我们要做的站点其实很简单,一个ToDo程序。
首先是在我们的 /application/controller/ 目录下面新建一个站点控制器类为ItemsController,命名为itemscontroller.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) {
$this->set('title','Success - My Todo List App');
$this->set('todo',$this->Item->query('delete from items where id = \''.mysql_real_escape_string($id).'\''));
}
}
接下来就是先建站点的模型,在我们的/application/model/ 目录下面先建一个站点模型类为Item,内容直接继承Model,代码如下:
class Item extends Model {
}
最后一步是设置我们站点的视图部分,我们现在/application/views/目录下新建一个items的文件夹,再在items文件夹下建立与控制器重Action相同的文件,分别为view.php,viewall.php,add.php,delete.php,考虑到这么页面中可能需要共用页首和页尾,所以再新建两个文件,命名为header.php,footer.php,每个文件的代码如下:
view.php文件:查看单条待处理事务
Delete this item
viewall.php文件:查看所有待处理事务
add.php文件:添加待处理事务
Todo successfully added. Click here to go back.
delete.php文件:删除事务
Todo successfully deleted. Click here to go back.
header.php:页首文件
<?php echo $title?>.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;}
footer.php:页尾文件
当然还有一个必不可少的操作就是在数据中中建立一张表,具体代码如下:
CREATE TABLE IF NOT EXISTS `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item_name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=17 ;
至此一个使用MVC开发的网站就开发完成了,你现在可以通过访问http://localhost/Frame/items/... 查看新建的站点。
框架核心小结:
1、单一入口原则
在项目Frame 下写.htaccess 文件指定到 pulibc 目录, 然后在 public 目录下重写入口模块 .htaccess 指定到 index.php 文件
这样只要访问该项目,就会指定到 Frame/public/index.php 目录下
2、加载初始配置文件
定义根路径,分隔符,等常量
define('DEBUG', TRUE);
3、封装核心类
sql.php
model.php (该类继承sql.php)
controller.php
4、其他的类都继承核心类
文章浏览阅读2.6k次,点赞3次,收藏18次。开发一个购物车,需要将购物车中的商品以陈列的形式展示,并且还需要对购物车的商品进行增删改查操作。要实现这些功能就需要使用ListView和SQLite数据库。接下来通过一个“商品展示”案例实现在界面上的操作数据库。具体步骤如下:1.创建程序首先创建程序,修改包名,设计用户交互界面。此程序对应的布局文件(activity_main.xml)如下所示:
文章浏览阅读392次。Leetcode python 之 《热题 HOT 100》:https://leetcode-cn.com/problemset/hot-100/15. 三数之和给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。注意:答案中不可以包含重复的三元组。例如, 给定数组 nums ..._力扣热门100 python 三数之和
文章浏览阅读447次。逻辑排序训练20题1.【川0751】(1)百年老树枝繁叶茂 (2)勘测施工线路 (3)将老树平行移动300米 (4)研究制定移动方案(5)老树萌发新芽A.5-2-3-4-1 B.1-2-4-3-5 C.1-2-3-4-5 D.2-1-4-3-5【答案】D。解析:本题描述的是因施工迁移百年老树的事件。“研究制定..._(1)百年老树枝繁叶茂 (2)勘测施工线路(3)将老树平行移动300米 (4)研究制定移 动方
文章浏览阅读854次。面试秘籍_mic面试宝典
文章浏览阅读1.4w次。ONVIF致力于通过全球性的开放接口标准来推进网络视频在安防市场的应用,这一接口标准将确保不同厂商生产的网络视频产品具有互通性。2008年11月,论坛正式发布了ONVIF第一版规范——ONVIF核心规范1.0。随着视频监控的网络化应用,产业链的分工将越来越细。有些厂商专门做摄像头,有些厂商专门做DVS,有些厂商则可能专门做平台等,然后通过集成商进行集成,提供给最终客户。这种产业合作模式,已经迫切的_onvif isapi
文章浏览阅读6.4k次,点赞7次,收藏47次。编译原理的地位是软件技术的基础是计算机专业的基础课程,是专业必修课编译原理的作用编译原理是介绍如何将高级语言程序变换成低级语言程序的方法。其理论基础坚实,其形式化系统不仅用于编译程序,还大量用于人工智能、多媒体技术、数据库等领域。程序设计语言低级程序语言特定的计算机系统所固有的语言即:机器语言、汇编语言特点:执行效率高、编制效率低高级程序语言与自然语言比较接近的语言过程式语言:C, Pascal, Fortran,ADA 对象式语言:Java, C++ 等函数式语_解释型程序的流程
文章浏览阅读786次。对于零基础学Python的朋友,网络上给出的学习方向90%以上并不合适。面对大量可选择的学习资源,依然有许多人在为如何学Python而犯愁。什么样的学习方式对于初学者最高效呢?通过调研发..._初中生python入门
文章浏览阅读226次。拉取镜像并指定版本号docker pull mysql:5.7启动mysqldocker run -p 3306:3306 --name mysql -d mysql:5.7查看启动状态docker ps -s如上则启动成功Navicat连接_3306:3306 --name mysql -v /root/mysql/conf:/etc/mysql/conf.d
文章浏览阅读5.9k次。vector容器,黑马程序员笔记_vector参数还是vector怎么赋值
文章浏览阅读376次。(10.1)cktk0for all real t. When t=0, this gives c0 0 . Differentiating (10.1) and setting t=0, we find that c1 0. Repeating the process, we find that each coefficient ck is zero.If a1,...,an are disti..._数学专业英语 2-6 汉译英
文章浏览阅读327次,点赞4次,收藏10次。> QGraphicsScene提供绘图场景,场景是不可见的,它作为抽像的管理图形项QGraphicsItems的容器,用户可以向场景添加图形项,获取场景中的图形项等。_qgraphicsscene
文章浏览阅读3.1w次,点赞5次,收藏41次。文章转载自:http://www.cnblogs.com/lcngu/p/5247179.html最近做的项目,需要将一些信息导出到word中。在网上找了好多解决方案,现在将这几天的总结分享一下。目前来看,java导出word大致有6种解决方案: 1:Jacob是Java-COM Bridge的缩写,它在Java与微软的COM组件之间构建一座桥梁。使用Jacob自带的DLL动态链_java生成的docx兼容性问题如何解决