手把手编写自己的PHP MVC框架实例教程

1 什么是MVC

MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式

MVC把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。

PHP中MVC模式也称Web MVC,从上世纪70年代进化而来。

MVC的目的是实现一种动态的程序设计,便于后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。

除此之外,此模式通过对复杂度的简化,使程序结构更加直观。

MVC各部分的职能:

  • 模型Model – 管理大部分的业务逻辑所有的数据库逻辑。模型提供了连接和操作数据库的抽象层。
  • 控制器Controller - 负责响应用户请求准备数据,以及决定如何展示数据。
  • 视图View – 负责渲染数据,通过HTML方式呈现给用户。

MVC流程图

一个典型的Web MVC流程:

  1. Controller截获用户发出的请求;
  2. Controller调用Model完成状态的读写操作;
  3. Controller把数据传递给View;
  4. View渲染最终结果并呈献给用户。

2 为什么要自己开发MVC框架

网络上有大量优秀的MVC框架可供使用,本教程并不是为了开发一个全面的、终极的MVC框架解决方案。

我们将它看作是一个很好的从内部学习PHP的机会。

在此过程中,你将学习面向对象编程MVC设计模式,并学习到开发中的一些注意事项。

更重要的是,通过自制MVC框架,每个人都可以完全控制自己的框架,将你的想法融入到你开发的框架中。

虽然不一定是最好的,但是你可以按照自己的方式开发各种功能。

3 开始开发自己的MVC框架

3.1 目录准备

在开始开发前,让我们先来把项目建立好。

假设我们建立的项目为 project,MVC的框架命名为 fastphp,那么接下来,第一步要把目录结构设置好。

project  WEB部署目录
├─application           应用目录
│  ├─controllers        控制器目录
│  ├─models             模块目录
│  ├─views              视图目录
├─config                配置文件目录
├─fastphp               框架核心目录
├─static                静态文件目录
├─index.php             入口文件

然后把Nginx或者Apache的站点根目录配置到project目录。

3.2 代码规范

在目录设置好以后,我们接下来规定代码的规范:

  1. MySQL的表名需小写小写加下划线,如:itemcar_orders
  2. 模块名(Models)需用大驼峰命名法,即首字母大写,并在名称后添加Model,如:ItemModelCarModel
  3. 控制器(Controllers)需用大驼峰命名法,即首字母大写,并在名称后添加Controller,如:ItemControllerCarController
  4. 方法名(Action)需用小驼峰命名法,即首字母小写,如:indexindexPost
  5. 视图(Views)部署结构为控制器名/行为名,如:item/view.phpcar/buy.php

上述规则是为了程序能更好地相互调用。

接下来就开始真正的PHP MVC编程了。

3.3 重定向

重定向的目的有两个:设置根目录为project所在位置,以及将所有请求都发送给 index.php 文件。

如果是Apache服务器,在 project 目录下新建一个 .htaccess 文件,内容为:

<IfModule mod_rewrite.c>
    # 打开Rerite功能
    RewriteEngine On

    # 如果请求的是真实存在的文件或目录,直接访问
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d

    # 如果访问的文件或目录不是真事存在,分发请求至 index.php
    RewriteRule . index.php
</IfModule>
如果是Nginx服务器,修改配置文件,在server块中加入如下的重定向:
location / {
    # 重新向所有非真是存在的请求到index.php
    try_files $uri $uri/ /index.php$args;
}
这样做的主要原因是:

(1)静态文件能直接访问。

如果文件或者目录真实存在,则直接访问存在的文件/目录。

比如,静态文件static/css/main.css真实存在,就可以直接访问它。

(2)程序有单一的入口

这种情况是请求地址不是真实存在的文件或目录,这样请求就会传到 index.php 上。

例如,访问地址:localhost/item/view/1,在文件系统中并不存在这样的文件或目录。

那么,Apache或Nginx服务器会把请求发给index.php,并且把域名之后的字符串赋值给REQUEST_URI变量。

这样在PHP中用$_SERVER['REQUEST_URI']就能拿到/item/view/1

(3)可以用来生成美化的URL,利于SEO

3.4 入口文件

接下来,在 project 目录下新建 index.php 入口文件,文件内容为:

<?php
// 应用目录为当前目录
define('APP_PATH', __DIR__ . '/');

// 开启调试模式
define('APP_DEBUG', true);

// 加载框架文件
require(APP_PATH . 'fastphp/Fastphp.php');

// 加载配置文件
$config = require(APP_PATH . 'config/config.php');

// 实例化框架类
(new Fastphp($config))->run();
注意,上面的PHP代码中,并没有添加PHP结束符号?>
这么做的主要原因是:
对于只有 PHP 代码的文件,最好没有结束标志?>
PHP自身并不需要结束符号,不加结束符让程序更加安全,很大程度防止了末尾被注入额外的内容。

3.5 配置文件

在入口文件中,我们加载了config.php文件的内容,那它有何作用呢?

从名称不难看出,它的作用是保存一些常用配置

config.php 文件内容如下,作用是定义数据库连接参数参数,以及配置默认控制器名和操作名:

<?php

// 数据库配置
$config['db']['host'] = 'localhost';
$config['db']['username'] = 'root';
$config['db']['password'] = '123456';
$config['db']['dbname'] = 'project';

// 默认控制器和操作名
$config['defaultController'] = 'Item';
$config['defaultAction'] = 'index';

return $config;

入口中的$config变量接收到配置参数后,再传给框架的核心类,也就是Fastphp类。

3.6 框架核心类

入口文件对框架类做了两步操作:实例化调用run()方法
实例化操作接受$config参数配置,并保存到类属性中。
run()方法则调用用类自身方法,完成:自动加载类文件、监测开发环境、过滤敏感字符、移除全局变量的老用法、和处理路由
<?php
/**
 * fastphp框架核心
 */
class Fastphp
{
    protected $_config = [];

    public function __construct($config)
    {
        $this->_config = $config;
    }

    // 运行程序
    public function run()
    {
        spl_autoload_register(array($this, 'loadClass'));
        $this->setReporting();
        $this->removeMagicQuotes();
        $this->unregisterGlobals();
        $this->setDbConfig();
        $this->route();
    }

    // 路由处理
    public function route()
    {
        $controllerName = $this->_config['defaultController'];
        $actionName = $this->_config['defaultAction'];
        $param = array();

        $url = $_SERVER['REQUEST_URI'];
        // 清除?之后的内容
        $position = strpos($url, '?');
        $url = $position === false ? $url : substr($url, 0, $position);
        // 删除前后的“/”
        $url = trim($url, '/');

        if ($url) {
            // 使用“/”分割字符串,并保存在数组中
            $urlArray = explode('/', $url);
            // 删除空的数组元素
            $urlArray = array_filter($urlArray);
            
            // 获取控制器名
            $controllerName = ucfirst($urlArray[0]);
            
            // 获取动作名
            array_shift($urlArray);
            $actionName = $urlArray ? $urlArray[0] : $actionName;
            
            // 获取URL参数
            array_shift($urlArray);
            $param = $urlArray ? $urlArray : array();
        }

        // 判断控制器和操作是否存在
        $controller = $controllerName . 'Controller';
        if (!class_exists($controller)) {
            exit($controller . '控制器不存在');
        }
        if (!method_exists($controller, $actionName)) {
            exit($actionName . '方法不存在');
        }

        // 如果控制器和操作名存在,则实例化控制器,因为控制器对象里面
        // 还会用到控制器名和操作名,所以实例化的时候把他们俩的名称也
        // 传进去。结合Controller基类一起看
        $dispatch = new $controller($controllerName, $actionName);

        // $dispatch保存控制器实例化后的对象,我们就可以调用它的方法,
        // 也可以像方法中传入参数,以下等同于:$dispatch->$actionName($param)
        call_user_func_array(array($dispatch, $actionName), $param);
    }

    // 检测开发环境
    public function setReporting()
    {
        if (APP_DEBUG === 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');
        }
    }

    // 删除敏感字符
    public function stripSlashesDeep($value)
    {
        $value = is_array($value) ? array_map(array($this, 'stripSlashesDeep'), $value) : stripslashes($value);
        return $value;
    }

    // 检测敏感字符并删除
    public function removeMagicQuotes()
    {
        if (get_magic_quotes_gpc()) {
            $_GET = isset($_GET) ? $this->stripSlashesDeep($_GET ) : '';
            $_POST = isset($_POST) ? $this->stripSlashesDeep($_POST ) : '';
            $_COOKIE = isset($_COOKIE) ? $this->stripSlashesDeep($_COOKIE) : '';
            $_SESSION = isset($_SESSION) ? $this->stripSlashesDeep($_SESSION) : '';
        }
    }

    // 检测自定义全局变量并移除。因为 register_globals 已经弃用,如果
    // 已经弃用的 register_globals 指令被设置为 on,那么局部变量也将
    // 在脚本的全局作用域中可用。 例如, $_POST['foo'] 也将以 $foo 的
    // 形式存在,这样写是不好的实现,会影响代码中的其他变量。 相关信息,
    // 参考: http://php.net/manual/zh/faq.using.php#faq.register-globals
    public function unregisterGlobals()
    {
        if (ini_get('register_globals')) {
            $array = array('_SESSION', '_POST', '_GET', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');
            foreach ($array as $value) {
                foreach ($GLOBALS[$value] as $key => $var) {
                    if ($var === $GLOBALS[$key]) {
                        unset($GLOBALS[$key]);
                    }
                }
            }
        }
    }

    // 配置数据库信息
    public function setDbConfig()
    {
        if ($this->_config['db']) {
            Model::$dbConfig = $this->_config['db'];
        }
    }

    // 自动加载控制器和模型类 
    public static function loadClass($class)
    {
        $frameworks = __DIR__ . '/' . $class . '.php';
        $controllers = APP_PATH . 'application/controllers/' . $class . '.php';
        $models = APP_PATH . 'application/models/' . $class . '.php';

        if (file_exists($frameworks)) {
            // 加载框架核心类
            include $frameworks;
        } elseif (file_exists($controllers)) {
            // 加载应用控制器类
            include $controllers;
        } elseif (file_exists($models)) {
            //加载应用模型类
            include $models;
        } else {
            // 错误代码
        }
    }
}

下面重点讲解主请求方法 route(),它也称路由方法,作用是:截取URL,并解析出控制器名、方法名和URL参数

假设我们的 URL 是这样:

yoursite.com/controllerName/actionName/queryString

当浏览器访问上面的URL,route()从全局变量 $_SERVER['REQUEST_URI']中获取到字符串/controllerName/actionName/queryString

然后,会将这个字符串分割成三部分:controlleractionqueryString

例如,URL链接为:yoursite.com/item/view/1/hello,那么route()分割之后,

  • Controller名就是:item
  • action名就是:view
  • URL参数就是:array(1, hello)

分割完成后,再实例化控制器:itemController,并调用其中的view方法 。

3.7 Controller基类

接下来,就是在 fastphp 中创建MVC基类,包括控制器模型视图三个基类。

新建控制器基类,文件名 Controller.class.php,功能就是总调度,内容如下:

<?php 
/**
 * 控制器基类
 */
class Controller
{
    protected $_controller;
    protected $_action;
    protected $_view;
 
    // 构造函数,初始化属性,并实例化对应模型
    public function __construct($controller, $action)
    {
        $this->_controller = $controller;
        $this->_action = $action;
        $this->_view = new View($controller, $action);
    }

    // 分配变量
    public function assign($name, $value)
    {
        $this->_view->assign($name, $value);
    }

    // 渲染视图
    public function render()
    {
        $this->_view->render();
    }
}

Controller 类用assign()方法实现把变量保存到View对象中

这样,在调用$this-> render() 后视图文件就能显示这些变量。

3.8 Model基类

新建模型基类,继承自数据库操作类Sql类(因为数据库操作比较复杂)。

模型基类文件名为 Model.class.php,代码如下:

<?php

class Model extends Sql
{
    protected $_model;
    protected $_table;
    public static $dbConfig = [];

    public function __construct()
    {
        // 连接数据库
        $this->connect(self::$dbConfig['host'], self::$dbConfig['username'], self::$dbConfig['password'],
            self::$dbConfig['dbname']);

        // 获取数据库表名
        if (!$this->_table) {
            // 获取模型类名称
            $this->_model = get_class($this);
            // 删除类名最后的 Model 字符
            $this->_model = substr($this->_model, 0, -5);

            // 数据库表名与类名一致
            $this->_table = strtolower($this->_model);
        }
    }
}

建立一个数据库基类 Sql.class.php,代码如下:

<?php 

class Sql
{
    protected $_dbHandle;
    protected $_result;
    private $filter = '';

    // 连接数据库
    public function connect($host, $username, $password, $dbname)
    {
        try {
            $dsn = sprintf("mysql:host=%s;dbname=%s;charset=utf8", $host, $dbname);
            $option = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);
            $this->_dbHandle = new PDO($dsn, $username, $password, $option);
        } catch (PDOException $e) {
            exit('错误: ' . $e->getMessage());
        }
    }

    // 查询条件
    public function where($where = array())
    {
        if (isset($where)) {
            $this->filter .= ' WHERE ';
            $this->filter .= implode(' ', $where);
        }

        return $this;
    }

    // 排序条件
    public function order($order = array())
    {
        if(isset($order)) {
            $this->filter .= ' ORDER BY ';
            $this->filter .= implode(',', $order);
        }

        return $this;
    }

    // 查询所有
    public function selectAll()
    {
        $sql = sprintf("select * from `%s` %s", $this->_table, $this->filter);
        $sth = $this->_dbHandle->prepare($sql);
        $sth->execute();

        return $sth->fetchAll();
    }

    // 根据条件 (id) 查询
    public function select($id)
    {
        $sql = sprintf("select * from `%s` where `id` = '%s'", $this->_table, $id);
        $sth = $this->_dbHandle->prepare($sql);
        $sth->execute();

        return $sth->fetch();
    }

    // 根据条件 (id) 删除
    public function delete($id)
    {
        $sql = sprintf("delete from `%s` where `id` = '%s'", $this->_table, $id);
        $sth = $this->_dbHandle->prepare($sql);
        $sth->execute();

        return $sth->rowCount();
    }

    // 自定义SQL查询,返回影响的行数
    public function query($sql)
    {
        $sth = $this->_dbHandle->prepare($sql);
        $sth->execute();

        return $sth->rowCount();
    }

    // 新增数据
    public function add($data)
    {
        $sql = sprintf("insert into `%s` %s", $this->_table, $this->formatInsert($data));

        return $this->query($sql);
    }

    // 修改数据
    public function update($id, $data)
    {
        $sql = sprintf("update `%s` set %s where `id` = '%s'", $this->_table, $this->formatUpdate($data), $id);

        return $this->query($sql);
    }

    // 将数组转换成插入格式的sql语句
    private function formatInsert($data)
    {
        $fields = array();
        $values = array();
        foreach ($data as $key => $value) {
            $fields[] = sprintf("`%s`", $key);
            $values[] = sprintf("'%s'", $value);
        }

        $field = implode(',', $fields);
        $value = implode(',', $values);

        return sprintf("(%s) values (%s)", $field, $value);
    }

    // 将数组转换成更新格式的sql语句
    private function formatUpdate($data)
    {
        $fields = array();
        foreach ($data as $key => $value) {
            $fields[] = sprintf("`%s` = '%s'", $key, $value);
        }

        return implode(',', $fields);
    }
}

应该说,Sql.class.php 是框架的核心部分。为什么?

因为通过它,我们创建了一个 SQL 抽象层,可以大大减少了数据库的编程工作。

虽然 PDO 接口本来已经很简洁,但是抽象之后框架的可灵活性更高。

这里的数据库句柄$this->_dbHandle还能用单例模式返回,让数据读写更高效,这部分可自行实现。

3.9 View基类

视图基类 View.class.php 内容如下:

<?php
/**
 * 视图基类
 */
class View
{
    protected $variables = array();
    protected $_controller;
    protected $_action;

    function __construct($controller, $action)
    {
        $this->_controller = strtolower($controller);
        $this->_action = strtolower($action);
    }
 
    // 分配变量
    public function assign($name, $value)
    {
        $this->variables[$name] = $value;
    }
 
    // 渲染显示
    public function render()
    {
        extract($this->variables);
        $defaultHeader = APP_PATH . 'application/views/header.php';
        $defaultFooter = APP_PATH . 'application/views/footer.php';

        $controllerHeader = APP_PATH . 'application/views/' . $this->_controller . '/header.php';
        $controllerFooter = APP_PATH . 'application/views/' . $this->_controller . '/footer.php';
        $controllerLayout = APP_PATH . 'application/views/' . $this->_controller . '/' . $this->_action . '.php';

        // 页头文件
        if (file_exists($controllerHeader)) {
            include ($controllerHeader);
        } else {
            include ($defaultHeader);
        }

        include ($controllerLayout);
        
        // 页脚文件
        if (file_exists($controllerFooter)) {
            include ($controllerFooter);
        } else {
            include ($defaultFooter);
        }
    }
}

这样,核心的PHP MVC框架核心就完成了。

下面我们编写应用来测试框架功能。

4 应用

4.1 数据库部署

在 SQL 中新建一个 project 数据库,增加一个item 表、并插入两条记录,命令如下:

CREATE DATABASE `project` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `project`;

CREATE TABLE `item` (
    `id` int(11) NOT NULL auto_increment,
    `item_name` varchar(255) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
 
INSERT INTO `item` VALUES(1, 'Hello World.');
INSERT INTO `item` VALUES(2, 'Lets go!');

4.2 部署模型

然后,我们还需要在 models 目录中创建一个 ItemModel.php 模型,内容如下:

<?php

/**
 * 用户Model
 */
class ItemModel extends Model
{
    /**
     * 自定义当前模型操作的数据库表名称,
     * 如果不指定默认为类名称的小写字符串,
     * 这里就是 item 表
     * @var string
     */
    public $_table = 'item';

    /**
     * 搜索功能,因为Sql父类里面没有现成的like搜索,
     * 所以需要自己写SQL语句,对数据库的操作应该都放
     * 在Model里面,然后提供给Controller直接调用
     * @param $title string 查询的关键词
     * @return array 返回的数据
     */
    public function search($keyword)
    {
        $sql = "select * from `$this->_table` where `item_name` like '%$keyword%'";
        $sth = $this->_dbHandle->prepare($sql);
        $sth->execute();

        return $sth->fetchAll();
    }
}

因为 Item 模型继承了 Model基类,所以它拥有 Model 类的所有功能。

4.3 部署控制器

controllers 目录下创建一个 ItemController.php 控制器,内容如下:

<?php
 
class ItemController extends Controller
{
    // 首页方法,测试框架自定义DB查询
    public function index()
    {
        $keyword = isset($_GET['keyword']) ? $_GET['keyword'] : '';

        if ($keyword) {
            $items = (new ItemModel())->search($keyword);
        } else {
            $items = (new ItemModel)->selectAll();
        }

        $this->assign('title', '全部条目');
        $this->assign('keyword', $keyword);
        $this->assign('items', $items);
        $this->render();
    }
    
    // 添加记录,测试框架DB记录创建(Create)
    public function add()
    {
        $data['item_name'] = $_POST['value'];
        $count = (new ItemModel)->add($data);

        $this->assign('title', '添加成功');
        $this->assign('count', $count);
        $this->render();
    }
    
    // 操作管理
    public function manage($id = 0)
    {
        $item = array();
        $postUrl = '/item/add';
        if ($id) {
            $item = (new ItemModel)->select($id);
            $postUrl = '/item/update';
        }

        $this->assign('title', '管理条目');
        $this->assign('item', $item);
        $this->assign('postUrl', $postUrl);
        $this->render();
    }
    
    // 更新记录,测试框架DB记录更新(Update)
    public function update()
    {
        $data = array('id' => $_POST['id'], 'item_name' => $_POST['value']);
        $count = (new ItemModel)->update($data['id'], $data);

        $this->assign('title', '修改成功');
        $this->assign('count', $count);
        $this->render();
    }
    
    // 删除记录,测试框架DB记录删除(Delete)
    public function delete($id = null)
    {
        $count = (new ItemModel)->delete($id);

        $this->assign('title', '删除成功');
        $this->assign('count', $count);
        $this->render();
    }
}

4.4 部署视图

views 目录下新建 header.php 和 footer.php 两个页头页脚模板,如下。

header.php 内容:

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title><?php echo $title ?></title>
    <link rel="stylesheet" href="/static/css/main.css" type="text/css" />
</head>
<body>
    <h1><?php echo $title ?></h1>

footer.php 内容:

</body>
</html>

页头文件用到了main.css样式文件,内容:

html, body {
    margin: 0;
    padding: 10px;
    font-size: 20px;
}

input {
    font-family:georgia,times;
    font-size:24px;
    line-height:1.2em;
}

a {
    color:blue;
    font-family:georgia,times;
    line-height:1.2em;
    text-decoration:none;
}

a:hover {
    text-decoration:underline;
}

h1 {
    color:#000000;
    font-size:41px;
    border-bottom:1px dotted #cccccc;
}

td {padding: 1px 30px 1px 0;}

然后,在 views/item 创建以下几个视图文件。

index.php,浏览数据库内 item 表的所有记录,内容:

<form action="" method="get">
    <input type="text" value="<?php echo $keyword ?>" name="keyword">
    <input type="submit" value="搜索">
</form>

<p><a href="/item/manage">新建</a></p>

<table>
    <tr>
        <th>ID</th>
        <th>内容</th>
        <th>操作</th>
    </tr>
    <?php foreach ($items as $item): ?>
        <tr>
            <td><?php echo $item['id'] ?></td>
            <td><?php echo $item['item_name'] ?></td>
            <td>
                <a href="/item/manage/<?php echo $item['id'] ?>">编辑</a>
                <a href="/item/delete/<?php echo $item['id'] ?>">删除</a>
            </td>
        </tr>
    <?php endforeach ?>
</table>

add.php,添加记录后的提示,内容:

<a class="big" href="/item/index">成功添加<?php echo $count ?>条记录,点击返回</a>

manage.php,管理记录,内容:

<form action="<?php echo $postUrl; ?>" method="post">
    <?php if (isset($item['id'])): ?>
        <input type="hidden" name="id" value="<?php echo $item['id'] ?>">
    <?php endif; ?>
    <input type="text" name="value" value="<?php echo isset($item['item_name']) ? $item['item_name'] : '' ?>">
    <input type="submit" value="提交">
</form>

<a class="big" href="/item/index">返回</a>

update.php,更改记录后的提示,内容:

<a class="big" href="/item/index">成功修改<?php echo $count ?>项,点击返回</a>

delete.php,删除记录后的提示,内容:

<a href="/item/index">成功删除<?php echo $count ?>项,点击返回</a>

4.5 应用测试

这样,在浏览器中访问 project程序:http://localhost/item/index/,就可以看到效果了。

result

以上代码已经全部发布到 github 上,关键部分加了注释:

要设计更好的MVC,或使用得更加规范,请看《MVC架构的职责划分原则》。

 

内容不错?

我要小额赞助,鼓励作者

 

参考资料:

183条评论

D.Deng says: 回复

运营报错了。
Warning: mysql_num_fields() expects parameter 1 to be resource, boolean given in D:\phpStudy\WWW\fast\fastphp\Sql.class.php on line 60

Warning: mysql_fetch_row() expects parameter 1 to be resource, boolean given in D:\phpStudy\WWW\fast\fastphp\Sql.class.php on line 66

Warning: mysql_result() expects at least 2 parameters, 1 given in D:\phpStudy\WWW\fast\fastphp\Sql.class.php on line 72

歪麦 says: 回复

应该是数据库连接不上导致的,把 $this->_dbHandle = @mysql_connect($address, $account, $pwd); 这一句中的 @ 去掉,可以显示具体错误。

刘sir says: 回复

怒赞

fatrbaby says: 回复

不错,很原始的设计。

Awezome says: 回复

为啥不用composer

歪麦 says: 回复

目前只仅为了说明原理,用composer就复杂了。以后完善了再composer

steve says: 回复

你好 教程写的很好,是我看过最良心的MVC框架介绍了,请问是否有兴趣继续把这个框架写下去,建议把mysql用mysqli,然后加一下路由,可以一步步慢慢扩展开来。还有文章里有个问题就是,你建的table有错误,itmes该为item就可以正常使用了,上面那位兄弟就是这个原因。

歪麦 says: 回复

非常感谢你的建议,我们会每隔一段时间优化这个框架,谢谢!

kimen says: 回复

model 中怎么调用操作sql的方法 😀

歪麦 says: 回复

因为model继承了Sql类,所以直接用$this调用,例如:

$this->selectAll();
我是东方的旭日 says: 回复

写的很不错

Jacky says: 回复

我看了你的框架
很适合用在中小型 的 网站
上手快速
只是里面我有更改了一些东西
除了 MVC 之外 我有加入一个Helper 的基类 然后延伸Pagination / Upload / Validation 那些逻辑
这个类 主旨是能够重用
还有加入了 不同用户的权限 限制
例如User 只能用到 User的 controller
我对你的框架很有兴趣 如果可以的话 请email我 讨论一起发展下去?

gsl says: 回复

之前的 $_GET =stripSlashesDeep($_GET );我改成了 $_GET =$this->stripSlashesDeep($_GET );不然会报错,是不是我php版本造成的,我是一个新手。不太懂这个,然后访问根目录有报如下错误。
Fatal error: Class 'IndexController' not found in E:\apmserv\APMServ-v5.2.6\APMServ5.2.6\www\htdocs\fastphp\fastphp\Core.php on line 47
请问这个是什么原因造成的?

歪麦 says: 回复

如果用 PHP5.2.6,就会有这个问题的,请使用 PHP5.4.0+ 。如果测试环境是Windows,可使用 phpStudy 来搭建环境,能切换多个版本的PHP。

ckc says: 回复

PHP 7.0.6 也出現這個 是一個原因嗎?

歪麦 says: 回复

请问具体的提示是怎样的呢?

mvcer says: 回复

使用php5.4,测试成功。很需要这种简单的原生的mvc,用于简化学习。非常感谢!

美丽心情 says: 回复

你好,最近在写基于MVC的php框架的论文,看了你写的对我有很大的帮助,在此表示感谢。如果加上一些思想的讲解,比如模型层引用的PDO等等。

乌班图 says: 回复

谢谢 不错的教程.易懂适合我这样的菜鸟学习。 😆

凯旋 says: 回复

首先谢谢博主的分享,让我学到了很多东西、我现在不解的是 ,(URL链接为:project.com/item/view/1/first-item $controller 就是:item $action 就是:view)是在哪个部分执行到了 index.php 为什么不用写project.com/index.php/item/view/1/first-item 就能引用到框架类库

歪麦 says: 回复

在.htaccess文件中有这么一行:

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

作用是重定向请求到:index.php?url=
例如:当我们请求<域名>item/index时,实际上是请求<域名>index.php?url=item/index。在PHP中用 GET['url'] 就能拿到字符串item/index,请看Core.php中的Route()方法。

凯旋 says: 回复

明白啦 谢谢博主 😈

歪麦 says: 回复

😀

zhou says: 回复

博主你好!这个$_GET['url']我查了好多资料,还是没搞清楚是什么意思。这个url参数是php本身就有的,还是您在哪里定义的?

歪麦 says: 回复

文件.htaccess中有这样一句:

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

例如,当访问了:www.awaimai.com/a/b,传到PHP获得的地址就是:www.awaimai.com/index.php?url=a/b,用$_GET['url']就能得到字符串a/b

zhou says:

明白了 谢谢博主!

says: 回复

博主你好 我是初学者 我看了你的框架 感觉帮助很大 谢谢

歪麦 says: 回复

非常荣幸能够帮上忙 ,欢迎多多光顾:)

speclalized-呆萌 says: 回复

我的数据库是MSSQL,配置文件要怎么改

歪麦 says: 回复

MSSSQL需要修改Sql.class.php文件中的PDO连接,如下一行:

$dsn = sprintf("mysql:host=%s;dbname=%s;charset=utf8", $host, $dbname);

改成MSSQL连接方式。

specialized-呆萌 says: 回复

Notice: Undefined variable: items in E:\Apache\htdocs\project\application\views\item\index.php on line 9
Warning: Invalid argument supplied for foreach() in E:\Apache\htdocs\project\application\views\item\index.php on line 9

报错额,

歪麦 says: 回复

说明没读出数据库里面的数据,需要调整Sql.class.php中点fetchaAll()等方法。
建议先用MySQL调通之后再换MSSQL。

specialized-呆萌 says: 回复

我换成mysql,还是这错误

zd says: 回复

我在用你框架的时候出现了控制器找不到的错误,怎么解决呢?

歪麦 says: 回复

用的github的代码么?可否把错误贴出来呢?

zd says: 回复

Fatal error: Class 'ItemController' not found in E:\web\WWW\mvc\fastphp\Core.php on line 45
是这样的,我把你的代码从网站上复制下去,打开就是这样,能够告诉我是哪里出问题了吗?

歪麦 says: 回复

建议直接从github上clone一份

AMOR says: 回复

可能是大小写的问题

ant says: 回复

少了个路由,希望这个能加上去,还有少了个加载自定义类库的方法.

歪麦 says: 回复

嗯,感谢提议!目前在Core.php文件的route()方法中,有做简单点路由处理呢 🙂

新手 says: 回复

我找不到Core.php文件……

歪麦 says: 回复

新版已经改为Fastphp.php文件了。

AMOR says: 回复

博主可以问一下吗?ItemController里面没有调用方法给 content赋值,为什么可以显示view 里面相应的content内容呢?比如 views/item/index.php 、add.php

歪麦 says: 回复

在View基类中,有两个方法:assign()render()
assign()将变量保存到variables数组中,render()输出变量,在之后用include包含方式显示。

一世宁安 says: 回复

你好,可以问个问题么。$dispatch = new $controller($controllerName, $action);里面的$controllerName, $action是怎么传过来的

歪麦 says: 回复

通过URL传递过来的。

一世宁安 says: 回复

为什么的的评论一直显示排队审核中。。。

victcity says: 回复

楼主楼主,这个情况该怎么解决, /home/u980175229/public_html/ 是我的php空间的目录, php版本 5.5
Fatal error: Class 'IndexController' not found in /home/u980175229/public_html/fastphp/Core.php on line 45

歪麦 says: 回复

应该是复制代码有缺失导致的,建议直接通过github克隆代码。

agoni says: 回复

连接oracle 所有sql语句都要改

歪麦 says: 回复

只需要改Sql类就可以了。

noob says: 回复

博主你好,我是一个初入门的PHP新手,之前学习了PHP的基础知识,然后又学习了一些网站项目的功能实现,都比较简单.这是第一次接触到MVC这个概念,按照您的教程做,发现绝大多数代码都不懂其含义,也出现了很多即便是查资料也一知半解的函数.我本来想针对于几个特别难懂的点问您,后来发现不懂的太多了,没可能一个一个问,所以想请问您,有什么更好的方式能使我这种新手更有助于理解的方法吗? 一些其他的相关知识? 书籍? 我想也许是我的基础知识底蕴还不够,可是我不知道涉及到这篇文章需要了解哪些知识,总之,希望能获得您的帮助,感激不尽!

歪麦 says: 回复

嗯,可能还是需要扎实一下基础,一些PHP语法用法的数据都看一下吧。
书籍推荐可以参看这里:https://github.com/yeszao/phproad

marshal says: 回复

请问一下:
public function add()
{
$val = !empty($_POST["value"]) ? $_POST["value"] : "没有获取到文本值";
$data['item_name'] =$val;
为什么$_POST["value"] 获取不到值呢?

歪麦 says: 回复

检查一下PHP环境。

marshal says: 回复

// 添加记录,测试框架DB记录创建(Create)
public function add()
{
var_dump($_POST);die;
$val = !empty($_POST["value"]) ? $_POST["value"] : "没有获取到文本值";

是个空数组,这是什么问题呢?

歪麦 says: 回复

检查一下PHP环境,代码测试过OK。

Apollo says: 回复

博主你好,非常感谢你的分享,我这里有个问题,你在Core.php文件中的route路由处理方法中,有那么以下两句:
// 实例化控制器
$controller = $controllerName . 'Controller';
$dispatch = new $controller($controllerName, $action);
我想问一下,$dispatch = new $controller($controllerName, $action);这一句代码,我看你好像也没有引进什么文件啊,这样实例化类会报错吧,而且我运行的时候也报错了:Fatal error: Class 'ItemController' not found in F:\wwwroot\myframe\fastphp\Core.php on line 44

歪麦 says: 回复

不需要手动include的,用spl_autoload_register(array($this, 'loadClass'));就可以实现通过loadClass()方法自动加载类文件。
如果报错,请确认PHP为5.4+版本,建议从github下载最新的代码。

Apollo says: 回复

嗯嗯,刚刚走了一遍这个代码,基本上可以理解了。非常感谢

Apollo says: 回复

我知道了,那个控制器的文件名应该为ItemController.class.php,而不是ItemController.php

kimwang says: 回复

您好!歪麦,我是从phpchina获得此教程,然后过来学习的,近期有意向自己写程序,搞个轻论坛,但鉴于现时的轻论坛项目过于同质化,某些RUBY些的,或者基于laravel框架的一些程序,对我来说又难以部署和维护,所以想零基础学习PHP+MYSQL,结合一些前端框架,比如BOOTSTRAPE,编写一套功能相对简单,但完全符合自己期望的轻论坛,现在经您介绍,对于MVC架构的开发工作有了较详细的了解,但对于字符的入库和出库处理,还是比较朦胧,不知您是否有空闲可以略作介绍?如果可以,真是非常感激。再次感谢你的教程,这对初学者帮助非常大!

歪麦 says: 回复

字符的入库和出库是否指的是数据库的增查改删(CRUD)?
这个在我们的框架中也有的哟,就是Sql类,增查改删都有的,用的是PDO类来读写数据库。
如果不理解,可以详细看看PHP网站PDO的介绍。

kimwang says: 回复

你好,可能是我表述得不够好,我想请教的可能是关于转义方面的问题?在具体一点,假如现在我要开发一个轻论坛,那么用户发帖时的内容里面,如果含有换行、空格、甚至HTML标记、代码等,我应该如何对危害数据库/程序安全的字符过滤?如何对换行、空格等正常入库,而查的时候又能正常恢复其原有行为格式?歪麦可以给点思路吗,谢谢。

歪麦 says: 回复

使用PDO的预处理语句就可以基本解决:http://php.net/manual/zh/pdo.prepared-statements.php
PHP网站也对SQL注入有介绍:http://php.net/manual/zh/security.database.sql-injection.php

kimwang says:

收到,谢谢你!

PHP程序员雷雪松 says: 回复

很不错的PHP文章,从头开始设计PHP框架。

学生 says: 回复

博主,请问fashphp目录下的那些类怎么引进去的啊?比如sql.class.php等

歪麦 says: 回复

loadClass方法实现类加载,再用PHP的spl_autoload_register方法自动加载

学生 says: 回复

请教博主, 检测自定义全局变量(register globals)并移除,这段代码的具体作用是什么,可以举个例子吗?谢啦

歪麦 says: 回复

因为 register_globals 已经弃用。
如果已经弃用的 register_globals 指令被设置为 on,那么局部变量也将在脚本的全局作用域中可用。
例如, $_POST['foo'] 也将以 $foo 的形式存在,这样写是不好的实现,会影响代码中的其他变量。

相关信息,参见 FAQ “register_globals 对我有什么影响?

1111 says: 回复

这哪里是参考!!
完全就是翻译!!!!!

啊啊啊 says: 回复

😀

ourenyou says: 回复

Controller类中初始化里有下面的一条语句
$this->_view = new View($controller, $action);
哪个地方包含了View.class.php文件?之前没有系统说过php,房主解答下。

歪麦 says: 回复

Core.php中用PHP提供的spl_autoload_register方法实现了类文件的自动加载,具体实现在:loadClass方法中。详细请看PHP官网介绍:http://php.net/manual/zh/function.spl-autoload-register.php

GD says: 回复

php 5.4+ 版本的,你的框架好像没有对特殊符号转义呢

歪麦 says: 回复

各种数据都有自己不同的用途,在入口对数据一刀切地进行转义,这种方式并不优雅,还有可能带来不必要的麻烦。
所以PHP5.4+取消了这个功能。
详情可看PHP官网解释:http://php.net/manual/en/security.magicquotes.php

aa says: 回复

缺陷是一个model不能调用其他model

歪麦 says: 回复

可以的

kavi says: 回复

请问博主nginx服务器怎么配置 .htaccess 文件呢?

kavi says: 回复

public function where($where = array())请问这个方法是用来干麻的?看不懂

歪麦 says: 回复

拼装MySQL的WHERE条件

He110 says: 回复

博主,有没有 Model 传递记录给 Control 的Demo?一次性传递多条数据的

歪麦 says: 回复

额。。。不太明白,能否详细描述详细一点呢

一颗星 says: 回复

楼主请教下, autoload 自动加载里 用 require require_once include include_once 有什么区别么?网上看大家各种写法都有。找资料找半天也没找到

歪麦 says: 回复

这几个函数是有区别的,require遇到警告时更严厉,include则比较宽松。具体请看另一篇文章:PHP函数include include_once require和require_once的区别

keone says: 回复

楼主你好,请教一下:假如我访问www.awaimai.com 让其指定默认的控制器和操作为 site/index 就是www.awaimai.com/site/index 等于www.awaimai.com

keone says: 回复

楼主你好,请教一下:假如我访问www.awaimai.com 让其指定默认的控制器和操作为 site/index 就是www.awaimai.com/site/index 等于www.awaimai.com

歪麦 says: 回复

办法是,修改Core.php文件,将route()方法中的$controllerName = 'Index';$action = 'index';的值改为需要的内容。

keone says: 回复

我的意思是能不能不加/site/index 这种,然后直接访问网址www.awaimai.com就能访问到www.awaimai.com/site/index,
我现在如果不加控制器名和方法名就会报错

keone says: 回复

懂了!谢谢

歪麦 says: 回复

好的

JUNLIANG says: 回复

兄弟 请问你接不接私活

歪麦 says: 回复

这个嘛,私聊啦 🙂

dengyun says: 回复

楼主请问一下,,我想做前后台,该怎么实现,目前我只能做一个前台,要怎样分出后台应用

歪麦 says: 回复

暂时没做分模块呢,如果要做前后台,加上模块支持就可以啦

往上 says: 回复

这是我见过的最好的PHP MVC入门教程了,感谢博主 😀
已小额赞助,多写些经典的 ❗ ❗

mrsli says: 回复

也是Fatal error: Class 'IndexController' not found in D:\phpStudy\WWW\demo\todo\fastphp\Core.php on line 44 这个错
最后发现是路径少了个杠:D:\phpStudy\WWW\demo\todo/application/controllersIndexController.class.php 😈

歪麦 says: 回复

你如何解决的呢?

mrsli says: 回复

在Core.php的loadClass方法中:
$controllers = APP_PATH.'application/controllers' . $class . '.class.php';
这句controllers后面少了个斜杠:
就是这样:$controllers = APP_PATH.'application/controllers/' . $class . '.class.php';

歪麦 says: 回复

很细节的地方 🙄

jcplus says: 回复

Fatal error: Uncaught Error: Class '/ItemController' not found in D:\projects\aureview\frame\Core.php on line 45
( ! ) Error: Class '/ItemController' not found in D:\projects\aureview\frame\Core.php on line 45

歪麦 says: 回复

有没有额外说明?

jcplus says: 回复

目录结构都和教程的一样。
fastphp -
- Controller.class.php
- Core.php
- fastphp.php
- Model.class.php
- Sql.class.php
- View.class.php

ItemController.php在application > controller目录下

歪麦 says: 回复

直接git clone代码是否有同样问题呢?

jcplus says:

我是顺着教程走下来的,和你的github的项目比较了一下发现你的教程里说的不一样,比如教程里没说views目录下还有一个layout.php。教程里说的ItemController.php其实不对,你的项目里的是ItemController.class.php。另外教程里也没提到还有一个文件叫做IndexController.class.php,我补充上文件并且改名之后就没问题了。

歪麦 says:

好的,问题已修复并优化了框架。

followyounger says:

网址上输入localhost/fastphp 之后,,,提示Controller控制器不存在,,这是什么原因呢?

歪麦 says:

把localhost的站点目录设置为project目录,然后直接访问localhost呢?

jcplus says: 回复

我如果想在数据库塞一些分类,比如按地域分,比如beijing。然后点击domain/beijing,正常会出现错误 Fatal error: BeijingController not found。 这个分类肯定不能是由一个地区就写一个controller的吧,那还不累死,而且内容都相同,肯定是调阅比如数据库跟这个地区挂钩的比如食物数据,人员数据等等。这样的话应该大概怎么写,能不能给点思路,谢谢。

Romins says: 回复

我在php7.1.2下调试提示有几处语法错误,可能是php7.1.2语法要求更规范的原因。例如在类属性成员定义出现语法错误:protected $_config = [];应修改为protected $_config = array();还有:(new Fastphp($config))->run();修改为:$runFrame = new Fastphp($config);$runFrame->run();
现在的问题是:语法修正后调试出现"Controller控制器不存在",麻烦帮忙解决下。

歪麦 says: 回复

我试了PHP7.1.4,没有出现这样的提示呢
PHP7中也是可以用[]代表数组的,提示控制器不存在可能是代码有遗漏。
请从github上克隆一份再试试。

Romins says: 回复

谢谢热心指导,我刚下载xampp7.1.4默认设置安装和从githib上重新克隆,在未编辑代码情况下,程序可以运行,但仍然把报“Contrpller控制器不存在”的提示,请问是不是我的php服务器设置不对?叧外关于语法错误的问题,我用的是dreamweaver cs6。早上提的语法错误还是有,只是不影响程序运行。打开文件有:此页可能具有仅可由服务器搜索到的动态相关文件,点击“搜索”就提示:语法错误

歪麦 says:

那是可以运行呢,还是报错呢?我在apache和nginx服务器下都试过呢,似乎都没有出现类似的问题
最好查一下apache的log日志,查找一下具体原因

歪麦 says: 回复

一般来说,这种直接通过表单提交的,用一个控制器就可以了。
比如创建一个area的控制器、一个area的model。
控制器负责接受处理表单数据,area负责读写数据库。
假设要插入beijing这个分类,就可以在表单里面用一个input/select等HTML标签定义,提交后发给控制器。
控制器再传给model写入数据库,这样就可以了。

jcplus says: 回复

关于表单那个,网上有现成的例子吗,或者说我要想搜索例子的话用什么关键词比较准确?

歪麦 says: 回复

可以看看这个是否符合:http://www.runoob.com/php/php-forms.html

jcplus says: 回复

还是跟着我之前询问的地域那个问题,我自己创建了一个region的controller和index的view,但是现在遇到了一个思路上的问题。假设路径会是region/beijing或者region/shanghai,这样的话我是不是要为每个地点都建立一个class,但是它们其实内容都差不多。有没有办法只创建一个class,然后用户访问region/beijing的时候,去数据库的region表找有没有beijing,如果有就显示beijing的内容,没有就显示没有该地名

歪麦 says: 回复

也不一定都创建控制器呀,像这种:

region/index?name=beijing
region/index?name=shanghai
...

这样不是也可以吗?

jcplus says: 回复

理解了,但是用这样的方式可以写成region/Beijing吗?

歪麦 says: 回复

region是控制器,beijing是操作名,在控制器里面增加一个操作就可以了
参数也可以做成这样:region/view/beijing,只要路由里面规定好就行

阿辉 says: 回复

歪麦,我是刚学php的新手,我有个疑问,每个控制器都要继承Controller基类,但这个Controller基类是什么时候引入进去的?

歪麦 says: 回复

Fastphp.php里面的run()方法注册了自动类加载spl_autoload_register(array($this, 'loadClass'));
因为子类继承了父类,所以PHP会去根据loadClass()方法去寻找该父类文件,并include进来。
可以研究下PHP的类自动加载。

jcplus says: 回复

我发现model里面没有任何例子,能增加一个简单的model的例子吗

歪麦 says: 回复

感谢建议,已经加上了

ethan says: 回复

Not Found

The requested URL /item/add was not found on this server.

报了这个错,是我有哪儿没有配置吗? 🙄

歪麦 says: 回复

应该是.htaccess文件没配置好,或者nginx的跳转未配置。

jcplus says: 回复

作者能简单的解释一下
spl_autoload_register( array( $this, 'loadClass' ) );
$this->setReporting();
$this->removeMagicQuotes();
$this->unregisterGlobals();
$this->route();

歪麦 says: 回复

调用本类中的方法,每个方法都有独自的注释说明的,请往下看。

taylor says: 回复

"如果文件或者目录真实存在,则直接访问存在的文件/目录。比如,静态文件static/css/main.css真实存在,就可以直接访问它。"
问题:project/application/下的MVC以及config等也是真实存在的文件和目录,也是可以直接访问的吗?如果使用rewrite规则将请求全部重定向到index.php,根据楼主Apache(Nginx)的配置,并未对静态文件和php脚本区分开来,只是用存在与否进行区分,可是如果直接访问php脚本呢

歪麦 says: 回复

如果直接访问,也可以,但是因为没有echo等输出语句,所以看到的是空白的。
如果访问的是控制器、或者model、或者其他核心类,因为是类,没有指明方法名也是没有输出的。
如果要安全一点,那就在每个头加上这样一句:

if (!defined('APP_PATH')) {
	exit('Access Denied!');
}

没有从index.php走的访问一律提示无权限。

taylor says: 回复

赞同,因为框架MVC或config一般不会直接echo输出,所有直接访问是空白。在每个头上加上这句,也是保证安全,同时需要新增一个index.html(Access Denied)文件放在每层目录中,防止文件和目录被攻击者一览无余~类似于CI。还有一种比较好的方法就是在Nginx或Apache中配置location:涉及静态文件(.css/.js/.jpg等)可直接访问,涉及到php脚本全部重定向到index.php入口文件

歪麦 says: 回复

同意!

ss says: 回复

LzController鎺у埗鍣ㄤ笉瀛樺湪,楼主,这是地址错误?

歪麦 says: 回复

乱码呢?

ss says: 回复

一直说控制器不存在是什么情况

ss says: 回复

是我访问http://localhost/lz/indexphp错了,才会一直说控制器不存在。
Not Found

The requested URL /lz/Item/index was not found on this server. 又出现新问题了

歪麦 says:

为啥中间多了个lz?

zz says: 回复

一直报控制器不存在怎么办

歪麦 says: 回复

那就是确实没有这个控制器,或者nginx配置:

        location / {
            try_files $uri $uri/ /index.php$args;
        }
请叫我雷锋 says: 回复

有个地方要更正一下,要把 application/views/ 下的 item 改成 Item(大写),否则报错:Warning: include(/home/*/*/project/application/views/Item/index.php): failed to open stream: No such file or directory in /home/*/*/project/fastphp/View.php on line 41

因为,你在用程序 将 item 的首字母大写了,如果不改 那就找到不文件夹喽! 调试了半天,才发现~
还有这个程序的效果是什么?只有 全部条目,管理条目?但是 添加了 ,也没见到添加到哪里了?

歪麦 says: 回复

嗯,如果系统是Linux,因为大小写敏感,确实会存在这个问题 。
已经修改了,views下面的目录和文件全部用小写。

另外,添加条目了之后,在“全部条目”下是可以看到新增的记录的。

请叫我雷锋 says: 回复

这个真心没有,全部条目 -》 新建 -》 管理条目 -》点击添加以后 -》成功添加0条记录 返回 -》 跳转到 全部条目 -》然而没看任何记录

歪麦 says: 回复

说明数据库没写成功

jcplus says: 回复

我在View.class.php里面修改了这行 public function render( $codec = 'html' ) 然后在我自己的MyController 里面的index()调用的时候用了$this->render( 'json' );就报错说我那个render() 有missing 1 argument

歪麦 says: 回复

Controller.php里面还有用了View对象的方法,

    // 渲染视图
    public function render()
    {
        $this->_view->render();
    }

一般像你这个情况,建议重新加一个方法,请参看面向对象设计原则的开闭原则

同舟共济 says: 回复

ProjectController.php不存在

歪麦 says: 回复

确实没有这个控制器的,要自己创建,默认有`ItemController`

11 says: 回复

你这好多坑啊!

歪麦 says: 回复

比如哪里?

11 says: 回复

解决啦,你网上的代码,和github上的有区别,问题解决!

歪麦 says: 回复

OK,可能更新的时候有遗漏,再同步下。

小可爱 says: 回复

不明白为什么model类为啥要继承SQL,为什么不通过一个SQL实例来操作数据库。

DM says: 回复

域名/item/manage/1这个编辑或者新建或者删除会报The requested URL /item/manage/1 was not found on this server.这种错误

歪麦 says: 回复

需要配置一下Nginx或apache

DM says: 回复

是配置apache的伪静态这一块吗?可是我配置的是打开的啊

歪麦 says: 回复

这里有介绍:3.3 重定向设置

DM says:

那请问如何配置这一块的Apache呢,我找了百度很多配置Apache的东西都没搞定,能和你细聊吗?qq1396930993

人人 says: 回复

缺的那个控制器直接复制 ItemController.php重名命可以吗? http://localhost/fastphp/item/index.php 打开提示“item方法不存在”。 新手第一次接触php的MVC,帮忙回答一下。

歪麦 says: 回复

如果Nginx或者apache配置好的话,直接访问localhost就可以了,建议从github上克隆最新代码。

lu says: 回复

刚开始学习写mvc框架,博主的文章太有用了。我有一个问题,我运行代码时提示:
Notice: Undefined index: db in D:\xampp\htdocs\test\fastphp\Fastphp.php on line 119
TestController控制器不存在

能帮我看一下那里写错了吗?

歪麦 says: 回复

重新配置一下文件:config/config.php,具体请看:3.5 配置文件

ktyaly says: 回复

我现在 有一个 疑问 $this->_view->render(); $this->_view->assign($name, $value); 这两个 是将什么值 存入 这里的呢

歪麦 says: 回复

render()不需要传入参数,assign()在控制器里面传入参数

cnbraid says: 回复

你好,请教一个细节问题。fastphp里有个spl_autoload_register函数,然后我发现其实在如下两行就会触发:

$controller = $controllerName . 'Controller';
if (!class_exists($controller)) {

此时$controller是ItermController的,那么是不是就无法正常加载 $models = APP_PATH . 'application/models/' . $class . '.php';Model类了,另外还有 $frameworks = __DIR__ . '/' . $class . '.php';也不清楚是干嘛用的,还请指导,谢谢!

cnbraid says: 回复

我大概理解了,需要把所有可能性考虑进去,找到了调用点了 Model:: 还有extends Controller之类的。。。谢谢!

cnbraid says: 回复

学完了,感觉还可以简单明了~
打算下午自己写一遍,加固下~

歪麦 says: 回复

嗯,加油:)

歪麦 says: 回复

就是类的自动加载,PHP提供有现成用法的。

Admin says: 回复

这么封装应该怎么实现前后台分离呢? 😕

歪麦 says: 回复

前后台分离需要路由的支持,我们这个教程主要用来学习的,所以这一块目前暂时未考虑加呢

编程爱好 says: 回复

在sql.class.php里为什么要用 return $this

歪麦 says: 回复

主要是为了链式操作。

seven says: 回复

楼主好:
php集成环境wamp,代码是博主GitHub上下载的,全部代码放在wamp/www/project文件夹内,地址栏:localhost/project/item/index 报错:projectController控制器不存在 请问在该环境下如何正常运行该项目?

seck says: 回复

看了 楼主写的 这个 mvc ,整体感觉 还是能学到一些不错的思想
但是出现的安全问题就是application 下的model 层处理search 出现了 sql注入, 没有进行占位符的处理 导致PDO 一样是拼接字符串
导致sql注入 payload => keyword=1%' and (sleep(8)) --+ 简单的方法就是使用sqlmap 跑一下就能发现是可以进行注入的

歪麦 says: 回复

嗯,一般项目中必须使用占位符预处理,不然注入风险很大。
github上是最新的代码,要不你帮忙提交个pull request,我合并到主干上?

歪麦 says: 回复

如果把代码直接放在wamp/www目录下呢?我猜应该是多了一个目录。

妮麦 says: 回复

Fatal error: Class 'Model' not found in D:\phpStudy\WWW\shiyue\wuli_s\fastphp\Fastphp.php on line 130

这个有点问题

歪麦 says: 回复

D:\phpStudy\WWW\shiyue\wuli_s\这个目录是否是站点的根目录呢?
如果不是,那需要配置一下phpStudy,把这个目录作为站点根目录。

lusty says: 回复

楼主好,
我正在学习php的mvc,楼主的代码对我帮助很大。
我现在有一个问题,如果我要使用ajax,比如校验用户名密码,在登录页直接反馈结果,请问使用您的这个框架怎么实现呢?
我之前是直接使用如下代码:
$.post("login.php",pars,loginFeedback);

function loginFeedback(data){
if(data=="success")
window.location.href="index_frame.php";
else
$("#error").html(data);
}

歪麦 says: 回复

可以的呀,把请求地址改成相应MVC中的地址就可以啦,比如:$.post("/item/manage/1", pars, loginFeedback);
这样就会提交到Item控制器的manage方法中。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

昵称 *