手把手编写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 准备工作

3.1 环境准备

这里我们需要最基本的PHP环境:

  • Nginx或Apache
  • PHP5.4+
  • MySQL

推荐使用phpStudydocker一键安装这样的LNMP环境。

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 目录准备

在开始开发前,我们给这个框架先起个名字吧,就叫:Fastphp框架

然后根据需要来把项目的目录创建。

假设我们建立的项目为 project,目录结构就这样:

project                 WEB部署根目录
├─app                   应用目录
│  ├─controllers        控制器目录
│  ├─models             模块目录
│  ├─views              视图目录
├─config                配置文件目录
├─fastphp               框架核心目录
│ ├─base                MVC基类目录
│ ├─db                  数据库操作类目录
│ ├─Fastphp.php         内核文件  
├─static                静态文件目录
├─index.php             入口文件

然后按照下一步,把Nginx或者Apache的站点根目录配置到project目录,。

3.4 重定向

重定向的目的有两个:设置根目录为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/detail/1,在文件系统中并不存在这样的文件或目录。

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

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

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

4 PHP MVC核心文件

4.1 入口文件

接下来,在 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\Fastphp($config))->run();
注意,上面的PHP代码中,并没有添加PHP结束符号?>
这么做的主要原因是:
对于只有 PHP 代码的文件,最好没有结束标志?>
PHP自身并不需要结束符号,不加结束符让程序更加安全,很大程度防止了末尾被注入额外的内容。

4.2 配置文件

在入口文件中,我们加载了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类。

4.3 框架核心类

入口文件对框架类做了两步操作:实例化调用run()方法
实例化操作接受$config参数配置,并保存到对象属性中。
run()方法则调用用类自身方法,完成下面几个操作:
  1. 类自动加载
  2. 环境检查
  3. 过滤敏感字符
  4. 移除全局变量的老用法
  5. 路由处理
fastphp目录下新建核心类文件,名称Fastphp.php,代码:
<?php
namespace fastphp;

// 框架根目录
defined('CORE_PATH') or define('CORE_PATH', __DIR__);

/**
 * 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 = 'app\\controllers\\'. $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']) {
            define('DB_HOST', $this->config['db']['host']);
            define('DB_NAME', $this->config['db']['dbname']);
            define('DB_USER', $this->config['db']['username']);
            define('DB_PASS', $this->config['db']['password']);
        }
    }

    // 自动加载类
    public function loadClass($className)
    {
        $classMap = $this->classMap();

        if (isset($classMap[$className])) {
            // 包含内核文件
            $file = $classMap[$className];
        } elseif (strpos($className, '\\') !== false) {
            // 包含应用(application目录)文件
            $file = APP_PATH . str_replace('\\', '/', $className) . '.php';
            if (!is_file($file)) {
                return;
            }
        } else {
            return;
        }

        include $file;

        // 这里可以加入判断,如果名为$className的类、接口或者性状不存在,则在调试模式下抛出错误
    }

    // 内核文件命名空间映射关系
    protected function classMap()
    {
        return [
            'fastphp\base\Controller' => CORE_PATH . '/base/Controller.php',
            'fastphp\base\Model' => CORE_PATH . '/base/Model.php',
            'fastphp\base\View' => CORE_PATH . '/base/View.php',
            'fastphp\db\Db' => CORE_PATH . '/db/Db.php',
            'fastphp\db\Sql' => CORE_PATH . '/db/Sql.php',
        ];
    }
}

下面重点讲解主请求方法 route(),它也称路由方法

路由方法的主要作用是:截取URL,并解析出控制器名、方法名和URL参数

假设我们的 URL 是这样:

yoursite.com/controllerName/actionName/queryString

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

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

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

  • ControllerName名就是:item
  • actionName名就是:detail
  • URL参数就是:array(1, hello)

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

4.4 Controller基类

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

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

<?php
namespace fastphp\base;

/**
 * 控制器基类
 */
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() 后视图文件就能显示这些变量。

4.5 Model基类

新建模型基类,继承自数据库操作类Sql类。

因为数据库操作比较复杂,所以SQL操作我们单独创建一个类。

Model基类涉及到3个类:Model基类本身,它的父类SQL,以及提供数据库连接句柄的Db类。

fastphp/base/目录下新建模型基类文件,名为 Model.php,代码如下:

<?php
namespace fastphp\base;

use fastphp\db\Sql;

class Model extends Sql
{
    protected $model;

    public function __construct()
    {
        // 获取数据库表名
        if (!$this->table) {

            // 获取模型类名称
            $this->model = get_class($this);

            // 删除类名最后的 Model 字符
            $this->model = substr($this->model, 0, -5);

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

fastphp/db/目录下建立一个数据库基类 Sql.php,代码如下:

<?php
namespace fastphp\db;

use \PDOStatement;

class Sql
{
    // 数据库表名
    protected $table;

    // 数据库主键
    protected $primary = 'id';

    // WHERE和ORDER拼装后的条件
    private $filter = '';

    // Pdo bindParam()绑定的参数集合
    private $param = array();

    /**
     * 查询条件拼接,使用方式:
     *
     * $this->where(['id = 1','and title="Web"', ...])->fetch();
     * 为防止注入,建议通过$param方式传入参数:
     * $this->where(['id = :id'], [':id' => $id])->fetch();
     *
     * @param array $where 条件
     * @return $this 当前对象
     */
    public function where($where = array(), $param = array())
    {
        if ($where) {
            $this->filter .= ' WHERE ';
            $this->filter .= implode(' ', $where);

            $this->param = $param;
        }

        return $this;
    }

    /**
     * 拼装排序条件,使用方式:
     *
     * $this->order(['id DESC', 'title ASC', ...])->fetch();
     *
     * @param array $order 排序条件
     * @return $this
     */
    public function order($order = array())
    {
        if($order) {
            $this->filter .= ' ORDER BY ';
            $this->filter .= implode(',', $order);
        }

        return $this;
    }

    // 查询所有
    public function fetchAll()
    {
        $sql = sprintf("select * from `%s` %s", $this->table, $this->filter);
        $sth = Db::pdo()->prepare($sql);
        $sth = $this->formatParam($sth, $this->param);
        $sth->execute();

        return $sth->fetchAll();
    }

    // 查询一条
    public function fetch()
    {
        $sql = sprintf("select * from `%s` %s", $this->table, $this->filter);
        $sth = Db::pdo()->prepare($sql);
        $sth = $this->formatParam($sth, $this->param);
        $sth->execute();

        return $sth->fetch();
    }

    // 根据条件 (id) 删除
    public function delete($id)
    {
        $sql = sprintf("delete from `%s` where `%s` = :%s", $this->table, $this->primary, $this->primary);
        $sth = Db::pdo()->prepare($sql);
        $sth = $this->formatParam($sth, [$this->primary => $id]);
        $sth->execute();

        return $sth->rowCount();
    }

    // 新增数据
    public function add($data)
    {
        $sql = sprintf("insert into `%s` %s", $this->table, $this->formatInsert($data));
        $sth = Db::pdo()->prepare($sql);
        $sth = $this->formatParam($sth, $data);
        $sth = $this->formatParam($sth, $this->param);
        $sth->execute();

        return $sth->rowCount();
    }

    // 修改数据
    public function update($data)
    {
        $sql = sprintf("update `%s` set %s %s", $this->table, $this->formatUpdate($data), $this->filter);
        $sth = Db::pdo()->prepare($sql);
        $sth = $this->formatParam($sth, $data);
        $sth = $this->formatParam($sth, $this->param);
        $sth->execute();

        return $sth->rowCount();
    }

    /**
     * 占位符绑定具体的变量值
     * @param PDOStatement $sth 要绑定的PDOStatement对象
     * @param array $params 参数,有三种类型:
     * 1)如果SQL语句用问号?占位符,那么$params应该为
     *    [$a, $b, $c]
     * 2)如果SQL语句用冒号:占位符,那么$params应该为
     *    ['a' => $a, 'b' => $b, 'c' => $c]
     *    或者
     *    [':a' => $a, ':b' => $b, ':c' => $c]
     *
     * @return PDOStatement
     */
    public function formatParam(PDOStatement $sth, $params = array())
    {
        foreach ($params as $param => &$value) {
            $param = is_int($param) ? $param + 1 : ':' . ltrim($param, ':');
            $sth->bindParam($param, $value);
        }

        return $sth;
    }

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

        $field = implode(',', $fields);
        $name = implode(',', $names);

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

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

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

应该说,Sql基类是框架的核心部分。为什么?

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

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

Sql类里面有用到Db:pdo()方法,这是我们创建的Db类,它提供一个PDO单例。

fastphp/db/目录下创建Db.php文件,内容:

<?php
namespace fastphp\db;

use PDO;
use PDOException;

/**
 * 数据库操作类。
 * 其$pdo属性为静态属性,所以在页面执行周期内,
 * 只要一次赋值,以后的获取还是首次赋值的内容。
 * 这里就是PDO对象,这样可以确保运行期间只有一个
 * 数据库连接对象,这是一种简单的单例模式
 * Class Db
 */
class Db
{
    private static $pdo = null;

    public static function pdo()
    {
        if (self::$pdo !== null) {
            return self::$pdo;
        }

        try {
            $dsn    = sprintf('mysql:host=%s;dbname=%s;charset=utf8', DB_HOST, DB_NAME);
            $option = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);

            return self::$pdo = new PDO($dsn, DB_USER, DB_PASS, $option);
        } catch (PDOException $e) {
            exit($e->getMessage());
        }
    }
}

4.6 View基类

fastphp/base/目录下新建视图基类 View.php 内容如下:

<?php
namespace fastphp\base;

/**
 * 视图基类
 */
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 . 'app/views/header.php';
        $defaultFooter = APP_PATH . 'app/views/footer.php';

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

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

        //判断视图文件是否存在
        if (is_file($controllerLayout)) {
            include ($controllerLayout);
        } else {
            echo "<h1>无法找到视图文件</h1>";
        }
        
        // 页脚文件
        if (is_file($controllerFooter)) {
            include ($controllerFooter);
        } else {
            include ($defaultFooter);
        }
    }
}

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

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

5 应用

5.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!');

5.2 部署模型

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

<?php
namespace app\models;

use fastphp\base\Model;
use fastphp\db\Db;

/**
 * 用户Model
 */
class ItemModel extends Model
{
    /**
     * 自定义当前模型操作的数据库表名称,
     * 如果不指定,默认为类名称的小写字符串,
     * 这里就是 item 表
     * @var string
     */
    protected $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 = Db::pdo()->prepare($sql);
        $sth = $this->formatParam($sth, [':keyword' => "%$keyword%"]);
        $sth->execute();

        return $sth->fetchAll();
    }
}

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

5.3 部署控制器

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

<?php
namespace app\controllers;

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

        if ($keyword) {
            $items = (new ItemModel())->search($keyword);
        } else {
            // 查询所有内容,并按倒序排列输出
            // where()方法可不传入参数,或者省略
            $items = (new ItemModel)->where()->order(['id DESC'])->fetchAll();
        }

        $this->assign('title', '全部条目');
        $this->assign('keyword', $keyword);
        $this->assign('items', $items);
        $this->render();
    }

    // 查看单条记录详情
    public function detail($id)
    {
        // 通过?占位符传入$id参数
        $item = (new ItemModel())->where(["id = ?"], [$id])->fetch();

        $this->assign('title', '条目详情');
        $this->assign('item', $item);
        $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();
        if ($id) {
            // 通过名称占位符传入参数
            $item = (new ItemModel())->where(["id = :id"], [':id' => $id])->fetch();
        }

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

5.4 部署视图

app/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;}

然后,在 app/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>

6 应用测试

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

result

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

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

参考资料:

320条评论

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我 讨论一起发展下去?

提交人 says:

优秀的Jacky

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?

芸宝儿 says:

最好配个域名,访问就很顺利了。。LZ 应该是他自己的项目名字 相当于博主的project

thirteen says:

相同的错误,连乱码都一摸一样
http://localhost/fastphp-master/

报错:app\controllers\Fastphp-masterController鎺у埗鍣ㄤ笉瀛樺湪

是在您的github拷贝的源代码

歪麦 says:

要配置源码为网站根目录

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:

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

cph4【伙食团】 says:

前后分离,你该说,返回数据即可(比如json),而不是返回网页。。。

cph4【伙食团】 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方法中。

Brian says: 回复

出現了一個問題
Parse error: syntax error, unexpected 'count' (T_STRING) in /home/coolca11/public_html/application/controllers/ItemController.php on line 29

歪麦 says:

变量名写错了吧

青涟 says: 回复

我似乎看到了一个喜欢拍黄片的大佬博客,

歪麦 says:

/捂脸

现在网 says: 回复

你好,假设我访问的是:`http://xxxx.com/api/news/list/55`,我要在 `application\views\api\news.php` 获取 `list/55` ,需要怎么写呢?反复看了 `fastphp\Fastphp.php` 路由那块的注释,依旧没搞懂,望指点下。

歪麦 says:

看Fastphp.php里面的route()路由方法,参数list/55会传到控制器的参数。

wozien says: 回复

怎么自动加载基类的class.php文件的,我看自动加载函数中没有加入进去

歪麦 says:

在Fastphp.php框架核心的loadClass()方法里面,使用了PHP的类自动加载功能。

是是是是 says: 回复

Parse error: syntax error, unexpected T_OBJECT_OPERATOR in C:\AppServ\www\project\index.php on line 15
怎么一访问就出现这个问题,文件我都没有动过,数据库都搞好的了……

歪麦 says:

要用PHP5.4+

9527 says: 回复

博主,首先谢谢分享啊。
我一直提示控制器不存在,我是WampServer64装的环境,php5.5.12,apache2.4.9,还需要怎么设置吗?我别的项目是可以正常运行的。看到给回复一下哦,万分感谢。

歪麦 says:

自己写的控制器吗?请确保配置apache的站点目录根目录为index.php所在目录。

5927 says:

apache的站点根目录为index.php所在目录,照样提示控制器不存在。我下了你的git的代码,除了改数据库连接,其它没改,不知是环境还需要怎么配置?

歪麦 says:

是的,改数据库配置,同时设置apache(或nginx)就可以了的。

兰特tt says: 回复

博主,我也是个PHP新手,看了基础知识,就上手你这个了,发现按照你网页的打代码无法运行,又从你的GitHub下载了代码,运行发现还是不行,主要应该是Fastphp.php这些有问题的。访问http://localhost/project/application/views/item/index.php,报出http://localhost/project/application/views/item/index.php。同时view.php并没有组装好html,所以出现乱码等等。。。所以想请教一下,有没有debug模式,我好一步一步自己看,我是用phpstudy搭建环境的。。

歪麦 says:

。。。需要创建一个站点,根目录为project所在的目录。

Mike says: 回复

版主您好

首先

謝謝 教導 此篇對我學MVC真的很有幫助

以下為問題點所在

網址上輸入localhost / fastphp之後,,,提示Controller不存在,

是否能請教一下

我是用wamp 開啟 www底下想要分資料夾

我已看過您的解釋,因為我不想設置根目錄 有辦法解決嗎?

我試過更改您的路徑無法解決 所以才想問看看您的想法

我是新手 所以對MVC有不懂的地方 請多多包涵

歪麦 says:

你好,如果不设置根目录,那就需要调整代码了呢
因为在route()方法里面我们是根据“/”来读取参数的
所以不设置根目录可以通过修改route()方法来适配

frank says: 回复

你好,请问class ItemController extends Controller 是如何定位到Controller的?

歪麦 says:

类自动加载呀

am says:

class ItemController extends Controller这个怎么实现类自动加载呀,具体的代码是哪些?

歪麦 says:

在Fastphp基类里面,详细请看上面几楼的回复。

张楠 says: 回复

博主,首先谢谢分享,让我复习了一遍MVC框架。
我想要说的是:能简单介绍自动类加载,也就是这个是干嘛用的,我不是想了解这个的具体实现,或许我连我自己想了解什么都不知道

frank says: 回复

从index.php进入,如何能进到view 下的index.html页面啊!

歪麦 says:

流程大概是:index.php → Fastphp.php → ... → XXXController → view/XXX.html
跟踪代码更清晰些

白天的狗 says: 回复

博主写的确实不错,可惜代码没有namespace部分,我现在需要学习带有namespace的自编框架,仍然感谢

歪麦 says:

嗯,现在namespace确实是趋势,有空了整理下
如果你有修改,提交个pull request到github上一起看看

wingking says: 回复

博主人好热情!哈哈~~
感谢博主给出这么好的MVC教程!
我的也报错,说Model类没有找到,尴尬
Fatal error: Class 'Model' not found in D:\3W\project\fastphp\Fastphp.php on line 136
看了楼上,博主的回答,是不是必须要把站点的根目录设置为项目的根目录呢?
我还是菜鸟,勿怪 ~

歪麦 says:

嗯,是必须的。

wingking says: 回复

还有啊,博主能给个联系方式吗?
昨天看到另外一篇MVC模式介绍的时候碰到的问题,百度了一下午,没找到解决办法,想谷歌去,还无法翻墙。

SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: YES)

好难受哇 ~

歪麦 says:

数据库无法连接,估计是配置或者权限问题。

歪麦 says:

联系方式在“关于”页面

hello says: 回复

db类是如何引入的,我没看到继承进来。$sth = Db::pdo()->prepare($sql); 这里没继承是如何调用的pdo()方法的?

歪麦 says:

Db::pdo()是静态方法,静态方法可以直接使用的,不需要继承,也不需要实例化。
引入的话,是通过类自动加载引入的。

三哥来了 says: 回复

您这个代码应该不完整吧。
views/item/index.php中跳转到detail.php时候,变量是传不过去的
detail.php中下面这两行代码运行是错误的:

ID:
Name:

歪麦 says:

是可以的哟,或者直接从github上下载代码试试

三哥来了 says:

有QQ吗,加你QQ聊。 我这个地方不是很明白,我的代码运行不了。

歪麦 says:

QQ在关于页面有哦

素材火 says: 回复

手把手的话,我选择女老师

listen says: 回复

很好,已经讨论到12月了,首先非常感谢博主的无私贡献,才有了我们方便的基础入手,现在问题来了,楼上有人jacky提到过加入了:Pagination / Upload / Validation 这几个基类,楼主能不能完善下去?这个很急需哦,又或者弄个开放平台,可以一起搞大这个框架。谢谢,各位辛苦了。

歪麦 says:

代码全部放在github上了呢,大家都可以修改提交的哦~~

用心 says: 回复

请问nginx配置中try_files $uri $uri/ /index.php$args; $args不是url传的参数么,前面只加一个index没有吧$url带上是不是路由就拿不到了,还是路由拿信息是通过$_SERVER全局变量,所以就不需要/index.php后面带上路径信息了?

阿狸 says: 回复

出现这种错误是哪里出错了?
Notice: Undefined index: id in /data/home/qxu1192210031/htdocs/app/views/item/index.php on line 16

阿狸 says:

并且只能新建两条数据,新建第三条的时候就显示已新建0条数据

休止千鹤 says: 回复

这个框架教学太棒了,刚刚入门,看完这个以后整个框架结构思路就出来了。立即自己写了一个,收获很大。
然后试着读了别的MVC框架,貌似瞬间清晰了很多,简直打开了新世界的大门。
收藏了√
PHP MVC框架入门经典
希望有空能继续维护。博主啊您这个博客很有价值,抵得上几本书!

歪麦 says:

哈哈,过奖了,感谢支持~

Json says: 回复

在manage.php中表单的提交地址是$postUrl的值:
<form action="" method="post">

但是我找遍全篇也没找到给$postUrl赋值的语句,这就导致了那个提交的按钮怎么按也没有用。。。
个人认为应该在ItemController.php的manage function中添加如下的赋值语句:
if ($id == 0) // create new item
{
echo "item id is ".$id;
$this->assign('postUrl', 'add');
}
else // update existing item
{
echo "item id is ".$id;
$this->assign('postUrl', '../update');
}
这样提交按钮就有效了。。

歪麦 says:

以github代码为主哟,都有详细注释的

Json says: 回复

博主你好,对于php编程我还是个新手。spl_autoload_register('auto_load_function') 这种syntax我能够理解,但是spl_autoload_register(array($this, 'loadClass'))这个就不容易理解了,这里的 array($this, 'loadClass') 是创建了一个array吗?含有$this 和 'loadClass' 两个元素的array?这说不通啊?在网上找了很久也没有答案,希望博主或其他朋友不吝赐教。

歪麦 says:

如果有个全局函数auto_load_function,那么我们可以用:spl_autoload_register('auto_load_function')
但是如果这个变量是在本类里面,就得用spl_autoload_register(array($this, 'loadClass'))这种方式了,告诉spl方法我用的是本类的方法,而不是全局函数。
因为我们总不能这样传入:spl_autoload_register('$this->loadClass'),或者:spl_autoload_register([$this=>loadClass]),
前者会被当成一个完整的字符串,去找名为'$this->loadClass'的全局函数,后者语法错误。
换个角度来理解的话,就是,spl_autoload_register支持传入字符串和数组,如果是字符串,那直接调用这个方法,
如果是数组(假设为:$a),就以对象方式调用,即:$a[0]->$a[1],这里就是$this->loadClass。

歪麦 says:

PHP官网有个Note有提到:http://php.net/manual/en/function.spl-autoload-register.php#102180

Json says:

博主解释的好详细,再次感谢!我在根据您MVC框架的启发搭建符合我自己项目需求的框架。现正在研究.htaccess的语法中。祝博主和各位朋友圣诞及新年快乐!

歪麦 says:

祝你元旦快乐啦,哈哈

侠斯沃勒 says: 回复

歪麦你好,nginx环境中当域名是www.mvc.com/index/demo 可以重定向到入口文件那
当域名是www.mvc.com/index/demo?id=1 就是file not found
问题是:这一块重定向该怎么写? 当时你的是什么环境?

歪麦 says:

可以直接用www.mvc.com/index/demo/1这种方式。

laolang2016 says: 回复

你好,Controller 为什么不用单例呢?

歪麦 says:

因为一次请求只会实例化一个Controller,而Model层可能多次被Controller调用。

非常棒 says: 回复

谢谢分享,学到很多东西哦

歪麦 says:

谢谢光顾!

歪麦 says:

感谢光顾~

春风 says: 回复

博主你好 我想问一下 view视图中的 header和footer 可以不应用吗

歪麦 says:

可以,不调用:$this->render();,就不会调用渲染,这样的话需要自己手动echo() 或者exit()

春风 says:

好的 谢谢 echo() exit() 是什么呢 定义好的方法?

歪麦 says:

PHP的内置函数

春风 says:

比方说我现在需要从login方法跳转到login页面 不用render 怎么去写

歪麦 says:

这个用header()函数来实现。

春风 says:

你好 歪麦 我怎么可以得到我URL传递的参数呢 $_GET['name']?

阿陆 says: 回复

歪麦您好,我刚刚开始学习这个mvc框架,在自动加载类上有一些问题。请问loadClass($className) 这个函数中,参数是如何传入进去的?同时自动加载类是怎么确定类名的变化,又是如何寻找子类的父类并且引入的?反复看了多遍loadClass($className)函数还是没有看明白,也没有看到调用loadClass时传入的参数

歪麦 says:

这个是PHP实现的,为什么叫“自动”加载,就是参数会自动传递到方法里面。
比如我们调用一个方法:
Math::add($a, $b);
用了自动加载的话,PHP会自动把类名传给loadClass,这里就是Math

阿陆 says:

那php的这个自动加载机制,我想了解它的实现远离,就是spl_autoload_register这个方法实现的吗?谢谢博主!

ticky says: 回复

根据楼主的代码我这里总是报 控制器不存在

歪麦 says:

克隆一份github的代码

ticky says: 回复

/**
* 用户Model
*/
ItemModel -> search($keyword)

这个出现页面空白

baimayou says: 回复

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

$field = implode(',', $fields);
$name = implode(',', $names);

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

baimayou says: 回复

博主你好,对于路由分发,我有些问题想要请教
在我的项目中我没有引入头尾文件,而是采用在iframe中跳转,所以出现了一个情况:
相同的代码,如href(‘class/index’)
在iframe外(入口页)跳转为...../class/index;是正常的。
但在iframe中(如class控制器下的页面中)跳转为....../class/class/index;
怎么忽然间多了一个class,让我不明觉厉

php老油条 says:

这么神奇

baimayou says:

是啊,我很焦灼

baimayou says:

在跳转时使用 /index/index 形式。。就好了

baimayou says:

可以再index.php里加载一个加密解密函数,用来加密传递参数,就更好了

薛不会 says: 回复

本人不会php,但是感觉博主描述的很清晰,看着很舒服,一点都不繁琐,关键点又没拉下,博主牛逼!谢谢;-)

歪麦 says:

感谢支持:)

亮子 says: 回复

楼主你好,你的框架很好,给你个赞。我不知道你用过谷歌浏览器打开过没,我用时,打开出现您要访问的网站包含有害程序
test1.com 上的攻击者可能会试图骗您安装有损浏览体验的程序(例如更改您的主页或在您访问的网站上显示额外的广告)。不能访问。其它360和ie正常.我是新手,这个问题是什么原因,怎么解决

亮子 says: 回复

歪麦您好,model 层怎么用多个数据库表,是不是每个表要建个model,我怎么又建个model,把model中$items变成$dns,在$items控制中,加了一行,$this->assign('dhs', $dhs);到views表中和您的一样,建个
<a href= >  

怎么显示Undefined variable: dns in F:\web\kuangj\app\views\item\index.php on line 6
还有就是model是不是每个都要如ItemModml或IdhModml。后面的modml,不能去掉吗

亮子 says: 回复

歪麦您好,最近没来吗?model怎么设置查询多个数据库

BB CHAN says: 回复

楼主你好,我想请教一下
按您说的配置完成后,首页可以正常显示,但是点击页面中热河一个按钮,都会提示

Not Found
The requested URL /item/manage/2 was not found on this server.

请问是哪里出了问题呢?

还有一点,之前配置的时候出现控制器不存在的情况,我将
// 获取控制器名
//$controllerName = ucfirst($urlArray[0]);

// 获取动作名
//array_shift($urlArray);
//$actionName = $urlArray ? $urlArray[0] : $actionName;
这些代码注释掉就可以正常通过,是不是这里出了问题导致了所有的跳转都报错呢?

亮子 says: 回复

楼主你好,如果头部和尾部模版,用是公用的,如果里边要调用数据库,怎么给那一个公用的调用数据,不用都要在每个控制器都做一遍

歪麦 says:

暂时没做这么复杂呢,你可以实现下:)

PHP新手 says: 回复

框架怎么弄个php生成的验证码并验证

歪麦 says:

可以使用composer,拉别人现成的项目来实现

蓝黛 says: 回复

博主,我下载的代码之后就出现这样额错误app\controllers\Shixun-1Controller鎺у埗鍣ㄤ笉瀛樺湪 这是什么错误呢,帮忙解一下

歪麦 says:

是不是文件的编码不是utf-8?或者html模板不是或没指定编码格式?

地方还是个 says:

在 index.php 首行加上 header("Content-type:text/html;charset=utf-8");

says: 回复

博主你好,为什么我访问的时候会出现app\controllers\ProjectController控制器不存在啊?

QQ邮箱12600954 says: 回复

你好 博主请问 直接运行 index.php就可以运行整个项目吗

歪麦 says:

index.php是入口文件,所有的请求都会经过index.php的~

李峰 says: 回复

帮助很大

歪麦 says:

感谢支持。

Chao says: 回复

謝謝博主分享
學習了

alex says: 回复

不是说controller thin,而model fat吗?这方面应该还可以改进

歪麦 says:

是的,现在的程序只是演示,实际使用时controller应尽量“瘦”

says: 回复

有木有人实现的哇。。。不管怎么改都是控制器不存在,求帮助啊。
看评论 也木有正确解释
去github下载下来 还是提示这个。。。求解呀
app\controllers\ItemController控制器不存在

alex says:

如果是本地的话,可能你需要把你项目名去掉

//本地
$pattern = '/fastphp-master/';
if (preg_match($pattern, $url))
{
$url = preg_replace($pattern, '', $url);
define('PROJECT_NAME', trim($pattern, '/'));
}else
{
define('PROJECT_NAME','');
}

says:

是添加在route里面 $url = $_SERVER['REQUEST_URI'] 这个的后面吗?没有用哇- - 还是提示一模一样的

歪麦 says:

Nginx配置是否正确?

liquid says: 回复

成功运行,给楼主个赞

颖颖 says: 回复

不是说好了手把手教学吗? 你人在哪呢?

歪麦 says:

你住哪?我这就过去 /斜眼笑

颖颖 says:

我在海淀区上地七街等你QuQ

歪麦 says:

好,马上来,嘿嘿

颖颖 says:

我是个菜鸟QuQ,defined('CORE_PATH') or define('CORE_PATH', __DIR__);这个什么意思没太懂

歪麦 says:

这里有两个点,一个是defined函数(注意,不是define),另一个是or的熔断机制,把两个串起来就容易理解了。

颖颖 says:

为什么要这样写啊

php小白 says: 回复

在fastphp/Fastphp.php中有一行代码会带来错误:

// 获取控制器名
$controllerName = ucfirst($urlArray[0]);

如果地址没有输入控制器名如http://localhost/index.php,则会报错,
"app\controllers\Index.phpController控制器不存在"
可以这样
if (count($urlArray) == 2){
// 获取控制器名
$controllerName = ucfirst($urlArray[0]);
}

php小白 says:

大神,我的php可能有点问题,要不然也不会出现这样问题,就是打开目录,出现index.php不会自动执行该文件,这是为什么?

歪麦 says:

配置Apache或者nginx,设置index(首页)为index.php

php小白 says:

对的,还没配置首页

西行寺幽幽子 says: 回复

dalao问个问题 我按照dalao您的做法写了一个MVC框架 可是部署完后发现报错:
PHP Fatal error: Uncaught Error: Class 'application\controllers\NoExistController' not found in 路径:73
Stack trace: #0 路径: HksMVC\HksMVC->Route() #1 D:\web site\MVCdemo\index.php(16): HksMVC\HksMVC->Run() #2 {main} thrown in D:\web site\MVCdemo\HksMVC\HksMvc.php on line 73

歪麦 says:

自己写的控制器吗?得按照规范来写哟,要是自己实现的话,可以调试到路由方法里面看看具体的调用过程。

萌新 says: 回复

app\controllers\FastphpController控制器不存在

西行寺幽幽子 says:

emm 他老是提示找不到class 然后我干脆把那个exit给注释掉了 然后emmm 我感觉要include控制器才行呐

歪麦 says:

内核类是自动加载的,不需要手动include的

西行寺幽幽子 says:

嘿嘿.. 我自己改装了一下 我就狂include了 然后我在实例化控制器的时候也会提前引入控制器

小鱼 says: 回复

我也成功了,提示控制器不存在的,关键还是,要建个虚拟主机,比如我在localhost下建个project文件夹,然后将东西都导入进来,按照要求进行修改后(就是数据库设置这些基本的)。新建一个虚拟主机,根文件就设在project下。这样你访问的时候,直接访问比如:project.com(就是localhost/project/)这样你访问,就不会提示控制器不存在了。。。反正我是放弃了两个礼拜 重写试后可以的。还是真心感谢作者, 很有用

西行寺幽幽子 says:

我花了4days 深思熟虑去思考这个问题 然后自己深度重写了(这个框架 然后就ok了

window says: 回复

大佬,除了增删改查之外,考虑出登录注册的教程吗?我是个前端,最近向学习后端,非常期待和饥渴啊

西行寺幽幽子 says:

注册: 连接数据库->判断用户名是否重复->其他条件->数据库插入
登录: 连接数据库->查询->验证

window says:

主要是想学习登录相关的session这块 要怎么使用 要怎么封装

西行寺幽幽子 says:

session_start();
$_session['xxx'];

话说为什么我有头像了233

yeye says: 回复

想请教一下,在使用这个框架时发现一个问题,Sql.php 中的 formatParam 绑定多个字段时,$sth->fetch执行返回false,而我把其中的$params中的数值echo出来,发现没问题,最后执行fetch 却是返回false

发表评论

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

昵称 *