[
  {
    "path": ".gitignore",
    "content": ".idea\r\nvendor\r\n"
  },
  {
    "path": ".htaccess",
    "content": "<IfModule mod_rewrite.c>\r\n    # 打开Rerite功能\r\n    RewriteEngine On\r\n\r\n    # 如果请求的是真实存在的文件或目录，直接访问\r\n    RewriteCond %{REQUEST_FILENAME} !-f\r\n    RewriteCond %{REQUEST_FILENAME} !-d\r\n\r\n    # 如果访问的文件或目录不是真事存在，分发请求至 index.php\r\n    RewriteRule . index.php\r\n</IfModule>"
  },
  {
    "path": ".travis.yml",
    "content": "language: php\r\nphp:\r\n  - 5.4\r\n\r\nbefore_script:\r\n  - composer install\r\n\r\nscript:\r\n  - phpunit -c phpunit.xml"
  },
  {
    "path": "README.md",
    "content": "# FastPHP\n\n[![Build Status](https://travis-ci.org/yeszao/fastphp.svg?branch=master)](https://travis-ci.org/yeszao/fastphp)\n[![Latest Stable Version](https://poser.pugx.org/yeszao/fastphp/v/stable)](https://packagist.org/packages/yeszao/fastphp)\n[![Total Downloads](https://poser.pugx.org/yeszao/fastphp/downloads)](https://packagist.org/packages/yeszao/fastphp)\n[![Latest Unstable Version](https://poser.pugx.org/yeszao/fastphp/v/unstable)](https://packagist.org/packages/yeszao/fastphp)\n[![License](https://poser.pugx.org/yeszao/fastphp/license)](https://packagist.org/packages/yeszao/fastphp)\n\n## 简述\n\n**fastphp**是一款简单的PHP MVC框架，目的是方便学习《手把手编写自己的PHP MVC框架》教程的同学下载源代码，详细介绍请参考网站：http://www.awaimai.com/128.html 。\n\n要求：\n\n* PHP 5.4.0+\n\n## 目录说明\n\n```\nproject                 根目录\n├─app                   应用目录\n│  ├─controllers        控制器目录\n│  ├─models             模块目录\n│  ├─views              视图目录\n├─config                配置文件目录\n├─fastphp               框架核心目录\n├─static                静态文件目录\n├─index.php             入口文件\n```\n\n## 使用\n\n### 1.安装\n主要介绍通过composer和git两种安装方法，选择其一即可。\n\n**方法1**：Composer安装（推荐）\n```\ncomposer create-project yeszao/fastphp project --no-dev\n```\n其中，`--no-dev`表示不安装-dev依赖包（PHPUnit）。\n\n**方法2**：Github安装：\n```\ngit clone https://github.com/yeszao/fastphp.git project\n```\n> 说明：这两个命令都会创建并将代码安装到`project`目录。\n\n### 2. 创建数据库\n\n在数据库中创建名为 project 的数据库，并插入两条记录，命令：\n\n```\nCREATE DATABASE `project` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;\nUSE `project`;\n\nCREATE TABLE `item` (\n    `id` int(11) NOT NULL auto_increment,\n    `item_name` varchar(255) NOT NULL,\n    PRIMARY KEY (`id`)\n) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;\n \nINSERT INTO `item` VALUES(1, 'Hello World.');\nINSERT INTO `item` VALUES(2, 'Lets go!');\n```\n\n### 3.修改数据库配置文件\n\n打开配置文件 config/config.php ，使之与自己的数据库匹配\n\n```\n$config['db']['host'] = 'localhost';\n$config['db']['username'] = 'root';\n$config['db']['password'] = '123456';\n$config['db']['dbname'] = 'project';\n```\n\n### 4.配置Nginx或Apache\n在Apache或Nginx中创建一个站点，把 project 设置为站点根目录（入口文件 index.php 所在的目录）。\n\n然后设置单一入口， Apache服务器配置：\n```\n<IfModule mod_rewrite.c>\n    # 打开Rerite功能\n    RewriteEngine On\n\n    # 如果请求的是真实存在的文件或目录，直接访问\n    RewriteCond %{REQUEST_FILENAME} !-f\n    RewriteCond %{REQUEST_FILENAME} !-d\n\n    # 如果访问的文件或目录不是真事存在，分发请求至 index.php\n    RewriteRule . index.php\n</IfModule>\n```\nNginx服务器配置：\n```\nlocation / {\n    # 重新向所有非真实存在的请求到index.php\n    try_files $uri $uri/ /index.php$args;\n}\n```\n\n### 5.测试访问\n\n然后访问站点域名：http://localhost/ 就可以了。\n"
  },
  {
    "path": "app/controllers/ItemController.php",
    "content": "<?php\r\nnamespace app\\controllers;\r\n\r\nuse fastphp\\base\\Controller;\r\nuse app\\models\\Item;\r\n \r\nclass ItemController extends Controller\r\n{\r\n    // 首页方法，测试框架自定义DB查询\r\n    public function index()\r\n    {\r\n        $keyword = isset($_GET['keyword']) ? $_GET['keyword'] : '';\r\n\r\n        if ($keyword) {\r\n            $items = (new Item())->search($keyword);\r\n        } else {\r\n            // 查询所有内容，并按倒序排列输出\r\n            // where()方法可不传入参数，或者省略\r\n            $items = (new Item)->where()->order(['id DESC'])->fetchAll();\r\n        }\r\n\r\n        $this->assign('title', '全部条目');\r\n        $this->assign('keyword', $keyword);\r\n        $this->assign('items', $items);\r\n        $this->render();\r\n    }\r\n\r\n    // 查看单条记录详情\r\n    public function detail($id)\r\n    {\r\n        // 通过?占位符传入$id参数\r\n        $item = (new Item())->where([\"id = ?\"], [$id])->fetch();\r\n\r\n        $this->assign('title', '条目详情');\r\n        $this->assign('item', $item);\r\n        $this->render();\r\n    }\r\n    \r\n    // 添加记录，测试框架DB记录创建（Create）\r\n    public function add()\r\n    {\r\n        $data['item_name'] = $_POST['value'];\r\n        $count = (new Item)->add($data);\r\n\r\n        $this->assign('title', '添加成功');\r\n        $this->assign('count', $count);\r\n        $this->render();\r\n    }\r\n    \r\n    // 操作管理\r\n    public function manage($id = 0)\r\n    {\r\n        $item = array();\r\n        if ($id) {\r\n            // 通过名称占位符传入参数\r\n            $item = (new Item())->where([\"id = :id\"], [':id' => $id])->fetch();\r\n        }\r\n\r\n        $this->assign('title', '管理条目');\r\n        $this->assign('item', $item);\r\n        $this->render();\r\n    }\r\n    \r\n    // 更新记录，测试框架DB记录更新（Update）\r\n    public function update()\r\n    {\r\n        $data = array('id' => $_POST['id'], 'item_name' => $_POST['value']);\r\n        $count = (new Item)->where(['id = :id'], [':id' => $data['id']])->update($data);\r\n\r\n        $this->assign('title', '修改成功');\r\n        $this->assign('count', $count);\r\n        $this->render();\r\n    }\r\n    \r\n    // 删除记录，测试框架DB记录删除（Delete）\r\n    public function delete($id = null)\r\n    {\r\n        $count = (new Item)->delete($id);\r\n\r\n        $this->assign('title', '删除成功');\r\n        $this->assign('count', $count);\r\n        $this->render();\r\n    }\r\n}"
  },
  {
    "path": "app/models/Item.php",
    "content": "<?php\r\nnamespace app\\models;\r\n\r\nuse fastphp\\base\\Model;\r\nuse fastphp\\db\\Db;\r\n\r\n/**\r\n * 用户Model\r\n */\r\nclass Item extends Model\r\n{\r\n    /**\r\n     * 自定义当前模型操作的数据库表名称，\r\n     * 如果不指定，默认为类名称的小写字符串，\r\n     * 这里就是 item 表\r\n     * @var string\r\n     */\r\n    protected $table = 'item';\r\n\r\n    /**\r\n     * 搜索功能，因为Sql父类里面没有现成的like搜索，\r\n     * 所以需要自己写SQL语句，对数据库的操作应该都放\r\n     * 在Model里面，然后提供给Controller直接调用\r\n     * @param $title string 查询的关键词\r\n     * @return array 返回的数据\r\n     */\r\n    public function search($keyword)\r\n    {\r\n        $sql = \"select * from `$this->table` where `item_name` like :keyword\";\r\n        $sth = Db::pdo()->prepare($sql);\r\n        $sth = $this->formatParam($sth, [':keyword' => \"%$keyword%\"]);\r\n        $sth->execute();\r\n\r\n        return $sth->fetchAll();\r\n    }\r\n}"
  },
  {
    "path": "app/views/footer.php",
    "content": "</body>\r\n</html>"
  },
  {
    "path": "app/views/header.php",
    "content": "<html>\r\n<head>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\r\n    <title><?php echo $title ?></title>\r\n    <link rel=\"stylesheet\" href=\"/static/css/main.css\" type=\"text/css\" />\r\n</head>\r\n<body>\r\n    <h1><?php echo $title ?></h1>"
  },
  {
    "path": "app/views/item/add.php",
    "content": "<a class=\"big\" href=\"/item/index\">成功添加<?php echo $count ?>条记录，点击返回</a>"
  },
  {
    "path": "app/views/item/delete.php",
    "content": "<a href=\"/item/index\">成功删除<?php echo $count ?>项，点击返回</a>"
  },
  {
    "path": "app/views/item/detail.php",
    "content": "ID：<?php echo $item['id'] ?><br />\r\nName：<?php echo isset($item['item_name']) ? $item['item_name'] : '' ?>\r\n\r\n<br />\r\n<br />\r\n<a class=\"big\" href=\"/item/index\">返回</a>"
  },
  {
    "path": "app/views/item/index.php",
    "content": "<form action=\"\" method=\"get\">\r\n    <input type=\"text\" value=\"<?php echo $keyword ?>\" name=\"keyword\">\r\n    <input type=\"submit\" value=\"搜索\">\r\n</form>\r\n\r\n<p><a href=\"/item/manage\">新建</a></p>\r\n\r\n<table>\r\n    <tr>\r\n        <th>ID</th>\r\n        <th>内容</th>\r\n        <th>操作</th>\r\n    </tr>\r\n    <?php foreach ($items as $item): ?>\r\n        <tr>\r\n            <td><?php echo $item['id'] ?></td>\r\n            <td>\r\n                <a href=\"/item/detail/<?php echo $item['id'] ?>\" title=\"查看详情\">\r\n                    <?php echo $item['item_name'] ?>\r\n                </a>\r\n            </td>\r\n            <td>\r\n                <a href=\"/item/manage/<?php echo $item['id'] ?>\">编辑</a>\r\n                <a href=\"/item/delete/<?php echo $item['id'] ?>\">删除</a>\r\n            </td>\r\n        </tr>\r\n    <?php endforeach ?>\r\n</table>\r\n"
  },
  {
    "path": "app/views/item/manage.php",
    "content": "<form  <?php if (isset($item['id'])) { ?>\r\n            action=\"/item/update/<?php echo $item['id'] ?>\"\r\n        <?php } else { ?>\r\n            action=\"/item/add\"\r\n        <?php } ?>\r\n      method=\"post\">\r\n\r\n    <?php if (isset($item['id'])): ?>\r\n        <input type=\"hidden\" name=\"id\" value=\"<?php echo $item['id'] ?>\">\r\n    <?php endif; ?>\r\n    <input type=\"text\" name=\"value\" value=\"<?php echo isset($item['item_name']) ? $item['item_name'] : '' ?>\">\r\n    <input type=\"submit\" value=\"提交\">\r\n</form>\r\n\r\n<a class=\"big\" href=\"/item/index\">返回</a>"
  },
  {
    "path": "app/views/item/update.php",
    "content": "<a class=\"big\" href=\"/item/index\">成功修改<?php echo $count ?>项，点击返回</a>"
  },
  {
    "path": "composer.json",
    "content": "{\r\n  \"name\": \"yeszao/fastphp\",\r\n  \"description\": \"Simple and lightweight PHP framework\",\r\n  \"type\": \"project\",\r\n  \"keywords\": [\r\n    \"framework\",\r\n    \"fastphp\",\r\n    \"mvc\"\r\n  ],\r\n  \"homepage\": \"http://www.awaimai.com\",\r\n  \"license\": \"MIT\",\r\n  \"authors\": [\r\n    {\r\n      \"name\": \"gary\",\r\n      \"email\": \"galley.meng@gmail.com\"\r\n    }\r\n  ],\r\n  \"require\": {\r\n    \"php\": \">=5.4.0\"\r\n  },\r\n  \"require-dev\": {\r\n    \"phpunit/phpunit\": \"~4.8.0\"\r\n  },\r\n  \"config\": {\r\n    \"preferred-install\": \"dist\",\r\n    \"platform\": {\r\n      \"php\": \"5.4\"\r\n    }\r\n  },\r\n  \"autoload\": {\r\n    \"psr-4\": {\r\n      \"app\\\\\": \"app\"\r\n    }\r\n  }\r\n}"
  },
  {
    "path": "config/config.php",
    "content": "<?php\n\n// 数据库配置\n$config['db']['host'] = 'localhost';\n$config['db']['username'] = 'root';\n$config['db']['password'] = '123456';\n$config['db']['dbname'] = 'project';\n\n// 默认控制器和操作名\n$config['defaultController'] = 'Item';\n$config['defaultAction'] = 'index';\n\nreturn $config;\n"
  },
  {
    "path": "fastphp/Fastphp.php",
    "content": "<?php\r\n\r\nnamespace fastphp;\r\n\r\n// 框架根目录\r\ndefined('CORE_PATH') or define('CORE_PATH', __DIR__);\r\n\r\n/**\r\n * fastphp框架核心\r\n */\r\nclass Fastphp\r\n{\r\n    // 配置内容\r\n    protected $config = [];\r\n\r\n    public function __construct($config)\r\n    {\r\n        $this->config = $config;\r\n    }\r\n\r\n    // 运行程序\r\n    public function run()\r\n    {\r\n        spl_autoload_register(array($this, 'loadClass'));\r\n        $this->setReporting();\r\n        $this->removeMagicQuotes();\r\n        $this->unregisterGlobals();\r\n        $this->setDbConfig();\r\n        $this->route();\r\n    }\r\n\r\n    // 路由处理\r\n    public function route()\r\n    {\r\n        $controllerName = $this->config['defaultController'];\r\n        $actionName = $this->config['defaultAction'];\r\n        $param = array();\r\n\r\n        $url = $_SERVER['REQUEST_URI'];\r\n        // 清除?之后的内容\r\n        $position = strpos($url, '?');\r\n        $url = $position === false ? $url : substr($url, 0, $position);\r\n\r\n        // 使得可以这样访问 index.php/{controller}/{action}\r\n        $position = strpos($url, 'index.php');\r\n        if ($position !== false) {\r\n            $url = substr($url, $position + strlen('index.php'));\r\n        }\r\n\r\n        // 删除前后的“/”\r\n        $url = trim($url, '/');\r\n\r\n        if ($url) {\r\n            // 使用“/”分割字符串，并保存在数组中\r\n            $urlArray = explode('/', $url);\r\n            // 删除空的数组元素\r\n            $urlArray = array_filter($urlArray);\r\n            \r\n            // 获取控制器名\r\n            $controllerName = ucfirst($urlArray[0]);\r\n            \r\n            // 获取动作名\r\n            array_shift($urlArray);\r\n            $actionName = $urlArray ? $urlArray[0] : $actionName;\r\n            \r\n            // 获取URL参数\r\n            array_shift($urlArray);\r\n            $param = $urlArray ? $urlArray : array();\r\n        }\r\n\r\n        // 判断控制器和操作是否存在\r\n        $controller = 'app\\\\controllers\\\\'. $controllerName . 'Controller';\r\n        if (!class_exists($controller)) {\r\n            exit($controller . '控制器不存在');\r\n        }\r\n        if (!method_exists($controller, $actionName)) {\r\n            exit($actionName . '方法不存在');\r\n        }\r\n\r\n        // 如果控制器和操作名存在，则实例化控制器，因为控制器对象里面\r\n        // 还会用到控制器名和操作名，所以实例化的时候把他们俩的名称也\r\n        // 传进去。结合Controller基类一起看\r\n        $dispatch = new $controller($controllerName, $actionName);\r\n\r\n        // $dispatch保存控制器实例化后的对象，我们就可以调用它的方法，\r\n        // 也可以像方法中传入参数，以下等同于：$dispatch->$actionName($param)\r\n        call_user_func_array(array($dispatch, $actionName), $param);\r\n    }\r\n\r\n    // 检测开发环境\r\n    public function setReporting()\r\n    {\r\n        if (APP_DEBUG === true) {\r\n            error_reporting(E_ALL);\r\n            ini_set('display_errors','On');\r\n        } else {\r\n            error_reporting(E_ALL);\r\n            ini_set('display_errors','Off');\r\n            ini_set('log_errors', 'On');\r\n        }\r\n    }\r\n\r\n    // 删除敏感字符\r\n    public function stripSlashesDeep($value)\r\n    {\r\n        $value = is_array($value) ? array_map(array($this, 'stripSlashesDeep'), $value) : stripslashes($value);\r\n        return $value;\r\n    }\r\n\r\n    // 检测敏感字符并删除\r\n    public function removeMagicQuotes()\r\n    {\r\n        if (get_magic_quotes_gpc()) {\r\n            $_GET = isset($_GET) ? $this->stripSlashesDeep($_GET ) : '';\r\n            $_POST = isset($_POST) ? $this->stripSlashesDeep($_POST ) : '';\r\n            $_COOKIE = isset($_COOKIE) ? $this->stripSlashesDeep($_COOKIE) : '';\r\n            $_SESSION = isset($_SESSION) ? $this->stripSlashesDeep($_SESSION) : '';\r\n        }\r\n    }\r\n\r\n    // 检测自定义全局变量并移除。因为 register_globals 已经弃用，如果\r\n    // 已经弃用的 register_globals 指令被设置为 on，那么局部变量也将\r\n    // 在脚本的全局作用域中可用。 例如， $_POST['foo'] 也将以 $foo 的\r\n    // 形式存在，这样写是不好的实现，会影响代码中的其他变量。 相关信息，\r\n    // 参考: http://php.net/manual/zh/faq.using.php#faq.register-globals\r\n    public function unregisterGlobals()\r\n    {\r\n        if (ini_get('register_globals')) {\r\n            $array = array('_SESSION', '_POST', '_GET', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');\r\n            foreach ($array as $value) {\r\n                foreach ($GLOBALS[$value] as $key => $var) {\r\n                    if ($var === $GLOBALS[$key]) {\r\n                        unset($GLOBALS[$key]);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    // 配置数据库信息\r\n    public function setDbConfig()\r\n    {\r\n        if ($this->config['db']) {\r\n            define('DB_HOST', $this->config['db']['host']);\r\n            define('DB_NAME', $this->config['db']['dbname']);\r\n            define('DB_USER', $this->config['db']['username']);\r\n            define('DB_PASS', $this->config['db']['password']);\r\n        }\r\n    }\r\n\r\n    // 自动加载类\r\n    public function loadClass($className)\r\n    {\r\n        $classMap = $this->classMap();\r\n\r\n        if (isset($classMap[$className])) {\r\n            // 包含内核文件\r\n            $file = $classMap[$className];\r\n        } elseif (strpos($className, '\\\\') !== false) {\r\n            // 包含应用（application目录）文件\r\n            $file = APP_PATH . str_replace('\\\\', '/', $className) . '.php';\r\n            if (!is_file($file)) {\r\n                return;\r\n            }\r\n        } else {\r\n            return;\r\n        }\r\n\r\n        include $file;\r\n\r\n        // 这里可以加入判断，如果名为$className的类、接口或者性状不存在，则在调试模式下抛出错误\r\n    }\r\n\r\n    // 内核文件命名空间映射关系\r\n    protected function classMap()\r\n    {\r\n        return [\r\n            'fastphp\\base\\Controller' => CORE_PATH . '/base/Controller.php',\r\n            'fastphp\\base\\Model' => CORE_PATH . '/base/Model.php',\r\n            'fastphp\\base\\View' => CORE_PATH . '/base/View.php',\r\n            'fastphp\\db\\Db' => CORE_PATH . '/db/Db.php',\r\n            'fastphp\\db\\Sql' => CORE_PATH . '/db/Sql.php',\r\n        ];\r\n    }\r\n}"
  },
  {
    "path": "fastphp/base/Controller.php",
    "content": "<?php\r\nnamespace fastphp\\base;\r\n\r\n/**\r\n * 控制器基类\r\n */\r\nclass Controller\r\n{\r\n    protected $_controller;\r\n    protected $_action;\r\n    protected $_view;\r\n\r\n    // 构造函数，初始化属性，并实例化对应模型\r\n    public function __construct($controller, $action)\r\n    {\r\n        $this->_controller = $controller;\r\n        $this->_action = $action;\r\n        $this->_view = new View($controller, $action);\r\n    }\r\n\r\n    // 分配变量\r\n    public function assign($name, $value)\r\n    {\r\n        $this->_view->assign($name, $value);\r\n    }\r\n\r\n    // 渲染视图\r\n    public function render()\r\n    {\r\n        $this->_view->render();\r\n    }\r\n}"
  },
  {
    "path": "fastphp/base/Model.php",
    "content": "<?php\r\nnamespace fastphp\\base;\r\n\r\nuse fastphp\\db\\Sql;\r\n\r\nclass Model extends Sql\r\n{\r\n    protected $model;\r\n\r\n    public function __construct()\r\n    {\r\n        // 获取数据库表名\r\n        if (!$this->table) {\r\n\r\n            // 获取模型类名称\r\n            $this->model = get_class($this);\r\n\r\n            // 删除类名最后的 Model 字符\r\n            $this->model = substr($this->model, 0, -5);\r\n\r\n            // 数据库表名与类名一致\r\n            $this->table = strtolower($this->model);\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "fastphp/base/View.php",
    "content": "<?php\r\nnamespace fastphp\\base;\r\n\r\n/**\r\n * 视图基类\r\n */\r\nclass View\r\n{\r\n    protected $variables = array();\r\n    protected $_controller;\r\n    protected $_action;\r\n\r\n    function __construct($controller, $action)\r\n    {\r\n        $this->_controller = strtolower($controller);\r\n        $this->_action = strtolower($action);\r\n    }\r\n \r\n    // 分配变量\r\n    public function assign($name, $value)\r\n    {\r\n        $this->variables[$name] = $value;\r\n    }\r\n \r\n    // 渲染显示\r\n    public function render()\r\n    {\r\n        extract($this->variables);\r\n        $defaultHeader = APP_PATH . 'app/views/header.php';\r\n        $defaultFooter = APP_PATH . 'app/views/footer.php';\r\n\r\n        $controllerHeader = APP_PATH . 'app/views/' . $this->_controller . '/header.php';\r\n        $controllerFooter = APP_PATH . 'app/views/' . $this->_controller . '/footer.php';\r\n        $controllerLayout = APP_PATH . 'app/views/' . $this->_controller . '/' . $this->_action . '.php';\r\n\r\n        // 页头文件\r\n        if (is_file($controllerHeader)) {\r\n            include ($controllerHeader);\r\n        } else {\r\n            include ($defaultHeader);\r\n        }\r\n\r\n        //判断视图文件是否存在\r\n        if (is_file($controllerLayout)) {\r\n            include ($controllerLayout);\r\n        } else {\r\n            echo \"<h1>无法找到视图文件</h1>\";\r\n        }\r\n        \r\n        // 页脚文件\r\n        if (is_file($controllerFooter)) {\r\n            include ($controllerFooter);\r\n        } else {\r\n            include ($defaultFooter);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "fastphp/db/Db.php",
    "content": "<?php\r\nnamespace fastphp\\db;\r\n\r\nuse PDO;\r\nuse PDOException;\r\n\r\n/**\r\n * 数据库操作类。\r\n * 其$pdo属性为静态属性，所以在页面执行周期内，\r\n * 只要一次赋值，以后的获取还是首次赋值的内容。\r\n * 这里就是PDO对象，这样可以确保运行期间只有一个\r\n * 数据库连接对象，这是一种简单的单例模式\r\n * Class Db\r\n */\r\nclass Db\r\n{\r\n    private static $pdo = null;\r\n\r\n    public static function pdo()\r\n    {\r\n        if (self::$pdo !== null) {\r\n            return self::$pdo;\r\n        }\r\n\r\n        try {\r\n            $dsn    = sprintf('mysql:host=%s;dbname=%s;charset=utf8', DB_HOST, DB_NAME);\r\n            $option = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);\r\n\r\n            return self::$pdo = new PDO($dsn, DB_USER, DB_PASS, $option);\r\n        } catch (PDOException $e) {\r\n            exit($e->getMessage());\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "fastphp/db/Sql.php",
    "content": "<?php\r\nnamespace fastphp\\db;\r\n\r\nuse \\PDOStatement;\r\n\r\nclass Sql\r\n{\r\n    // 数据库表名\r\n    protected $table;\r\n\r\n    // 数据库主键\r\n    protected $primary = 'id';\r\n\r\n    // WHERE和ORDER拼装后的条件\r\n    private $filter = '';\r\n\r\n    // Pdo bindParam()绑定的参数集合\r\n    private $param = array();\r\n\r\n    /**\r\n     * 查询条件拼接，使用方式：\r\n     *\r\n     * $this->where(['id = 1','and title=\"Web\"', ...])->fetch();\r\n     * 为防止注入，建议通过$param方式传入参数：\r\n     * $this->where(['id = :id'], [':id' => $id])->fetch();\r\n     *\r\n     * @param array $where 条件\r\n     * @return $this 当前对象\r\n     */\r\n    public function where($where = array(), $param = array())\r\n    {\r\n        if ($where) {\r\n            $this->filter .= ' WHERE ';\r\n            $this->filter .= implode(' ', $where);\r\n\r\n            $this->param = $param;\r\n        }\r\n\r\n        return $this;\r\n    }\r\n\r\n    /**\r\n     * 拼装排序条件，使用方式：\r\n     *\r\n     * $this->order(['id DESC', 'title ASC', ...])->fetch();\r\n     *\r\n     * @param array $order 排序条件\r\n     * @return $this\r\n     */\r\n    public function order($order = array())\r\n    {\r\n        if($order) {\r\n            $this->filter .= ' ORDER BY ';\r\n            $this->filter .= implode(',', $order);\r\n        }\r\n\r\n        return $this;\r\n    }\r\n\r\n    // 查询所有\r\n    public function fetchAll()\r\n    {\r\n        $sql = sprintf(\"select * from `%s` %s\", $this->table, $this->filter);\r\n        $sth = Db::pdo()->prepare($sql);\r\n        $sth = $this->formatParam($sth, $this->param);\r\n        $sth->execute();\r\n\r\n        return $sth->fetchAll();\r\n    }\r\n\r\n    // 查询一条\r\n    public function fetch()\r\n    {\r\n        $sql = sprintf(\"select * from `%s` %s\", $this->table, $this->filter);\r\n        $sth = Db::pdo()->prepare($sql);\r\n        $sth = $this->formatParam($sth, $this->param);\r\n        $sth->execute();\r\n\r\n        return $sth->fetch();\r\n    }\r\n\r\n    // 根据条件 (id) 删除\r\n    public function delete($id)\r\n    {\r\n        $sql = sprintf(\"delete from `%s` where `%s` = :%s\", $this->table, $this->primary, $this->primary);\r\n        $sth = Db::pdo()->prepare($sql);\r\n        $sth = $this->formatParam($sth, [$this->primary => $id]);\r\n        $sth->execute();\r\n\r\n        return $sth->rowCount();\r\n    }\r\n\r\n    // 新增数据\r\n    public function add($data)\r\n    {\r\n        $sql = sprintf(\"insert into `%s` %s\", $this->table, $this->formatInsert($data));\r\n        $sth = Db::pdo()->prepare($sql);\r\n        $sth = $this->formatParam($sth, $data);\r\n        $sth = $this->formatParam($sth, $this->param);\r\n        $sth->execute();\r\n\r\n        return $sth->rowCount();\r\n    }\r\n\r\n    // 修改数据\r\n    public function update($data)\r\n    {\r\n        $sql = sprintf(\"update `%s` set %s %s\", $this->table, $this->formatUpdate($data), $this->filter);\r\n        $sth = Db::pdo()->prepare($sql);\r\n        $sth = $this->formatParam($sth, $data);\r\n        $sth = $this->formatParam($sth, $this->param);\r\n        $sth->execute();\r\n\r\n        return $sth->rowCount();\r\n    }\r\n\r\n    /**\r\n     * 占位符绑定具体的变量值\r\n     * @param PDOStatement $sth 要绑定的PDOStatement对象\r\n     * @param array $params 参数，有三种类型：\r\n     * 1）如果SQL语句用问号?占位符，那么$params应该为\r\n     *    [$a, $b, $c]\r\n     * 2）如果SQL语句用冒号:占位符，那么$params应该为\r\n     *    ['a' => $a, 'b' => $b, 'c' => $c]\r\n     *    或者\r\n     *    [':a' => $a, ':b' => $b, ':c' => $c]\r\n     *\r\n     * @return PDOStatement\r\n     */\r\n    public function formatParam(PDOStatement $sth, $params = array())\r\n    {\r\n        foreach ($params as $param => &$value) {\r\n            $param = is_int($param) ? $param + 1 : ':' . ltrim($param, ':');\r\n            $sth->bindParam($param, $value);\r\n        }\r\n\r\n        return $sth;\r\n    }\r\n\r\n    // 将数组转换成插入格式的sql语句\r\n    private function formatInsert($data)\r\n    {\r\n        $fields = array();\r\n        $names = array();\r\n        foreach ($data as $key => $value) {\r\n            $fields[] = sprintf(\"`%s`\", $key);\r\n            $names[] = sprintf(\":%s\", $key);\r\n        }\r\n\r\n        $field = implode(',', $fields);\r\n        $name = implode(',', $names);\r\n\r\n        return sprintf(\"(%s) values (%s)\", $field, $name);\r\n    }\r\n\r\n    // 将数组转换成更新格式的sql语句\r\n    private function formatUpdate($data)\r\n    {\r\n        $fields = array();\r\n        foreach ($data as $key => $value) {\r\n            $fields[] = sprintf(\"`%s` = :%s\", $key, $key);\r\n        }\r\n\r\n        return implode(',', $fields);\r\n    }\r\n}"
  },
  {
    "path": "index.php",
    "content": "<?php\n// 应用目录为当前目录\ndefine('APP_PATH', __DIR__ . '/');\n\n// 开启调试模式\ndefine('APP_DEBUG', true);\n\n// 加载框架文件\nrequire(APP_PATH . 'fastphp/Fastphp.php');\n\n// 加载配置文件\n$config = require(APP_PATH . 'config/config.php');\n\n// 实例化框架类\n(new fastphp\\Fastphp($config))->run();\n"
  },
  {
    "path": "phpunit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<phpunit backupGlobals=\"false\"\r\n         bootstrap=\"tests/autoload.php\"\r\n\t\t colors=\"true\">\r\n    <testsuites>\r\n        <testsuite name=\"Application Test Suite\">\r\n            <directory>./tests/</directory>\r\n        </testsuite>\r\n    </testsuites>\r\n</phpunit>"
  },
  {
    "path": "static/css/main.css",
    "content": "html, body {\r\n    margin: 0;\r\n    padding: 10px;\r\n    font-size: 20px;\r\n}\r\n\r\ninput {\r\n    font-family:georgia,times;\r\n    font-size:24px;\r\n    line-height:1.2em;\r\n}\r\n\r\na {\r\n    color:blue;\r\n    font-family:georgia,times;\r\n    line-height:1.2em;\r\n    text-decoration:none;\r\n}\r\n\r\na:hover {\r\n    text-decoration:underline;\r\n}\r\n\r\nh1 {\r\n    color:#000000;\r\n    font-size:41px;\r\n    border-bottom:1px dotted #cccccc;\r\n}\r\n\r\ntd {padding: 1px 30px 1px 0;}"
  },
  {
    "path": "tests/autoload.php",
    "content": "<?php\r\ninclude __DIR__ . '/../vendor/autoload.php';\r\n"
  }
]