一个简单的留言本


创建APP和入口文件

一. 创建app目录

进入app目录, 创建文件夹guestbook, 并在guestbook文件夹下创建controllers, templates, views目录, 分别用于存放控制器, 模板, 和视图控制器, 创建好以后的结构如下:

├─admin
├─api
├─cli
├─web
└─guestbook
    ├─controllers
    ├─templates
    └─views

二. 创建app配置文件

直接拷贝app/web/init.php文件到app/guestbook目录, 作为app配置文件

三. 创建入口文件

htdocs下创建guestbook文件夹, 并创建存放静态资源的static文件夹

├─admin
├─api
├─guestbook
│  └─static
└─web

然后在guestbook目录下新建入口文件index.php

<?php
require __DIR__ . '/../../crossboot.php';
Cross\Core\Delegate::loadApp('guestbook')->run();

创建好入口文件后, 通过浏览器访问http://domain/htdocs/guestbook/, 这时会显示CP的异常页面, 提示我们Main控制器不存在, 默认控制器可以通过app配置文件指定

创建控制器

guestbook/controllers目录下新建控制器文件Main.php, 输入以下内容:

<?php	
namespace app\guestbook\controllers;

use Cross\MVC\Controller;

class Main extends Controller
{	
    function index()
    {
        echo 'im guestbook';
    }	
}

在控制器的第一行我们指定控制器的命名空间. cp会根据命名空间自动加载类文件, 具体请查看文档模块及命名空间

然后刷新网页http://domain/htdocs/guestbook/, 如果网页显示im guestbook, 那就证明创建控制器成功了.

使用视图控制器及布局文件

guestbook/views目录下创建文件MainView.php, 输入以下内容

<?php
namespace app\guestbook\views;

use Cross\MVC\View;

class MainView extends View
{
    function index($data = array())
    {
        echo $data['title'];
    }
}

然后改写控制器, 通过$data数组把数据传给视图控制器, 把输出内容的工作交给视图控制器来处理

<?php
namespace app\guestbook\controllers;

use Cross\MVC\Controller;

class Main extends Controller
{
    function index()
    {
        $data['title'] = 'im guestbook';
        $this->display($data);
    }
}

控制器中调用$this->display()来使用视图控制器. 完成后刷新页面http://domain/htdocs/guestbook/, 会提示我们默认的布局文件不存在

app\guestbook\templates\default\default.layer.php layer Not found!

根据提示我们在app\guestbook\templates目录下新建文件夹default, 并在default中新建布局文件default.layer.php

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title><?php echo isset($title)?$title:'guestbook' ?></title>
</head>
<body>
    <?php echo $content; ?>
</body>
</html>

在布局文件中, 通过视图控制器处理过的内容都会被赋值给$content变量

我们在布局中只要在适当的位置输出$content变量就OK了, 再此刷新网页, 就可以看到我们通过视图控制器输出的内容了, 查看网页源码可以看到视图控制器生成的内容放到了default.layer.php中指定的位置, 并且输出了默认的标题guestbook, 关于布局文件的详细介绍请查看文档使用布局

创建留言表单模板

app/guestbook/templates/default下创建文件夹main, 并在文件夹下创建模板文件guest.tpl.php, 我们创建一个表单用于发布留言

<div class="col-md-12">
    <form class="form-horizontal" action="" method="post">
        <div class="form-group">
            <label for="name" class="col-sm-2 control-label">昵称</label>
            <div class="col-sm-3">
                <input type="text" class="form-control" name="name" id="name" placeholder="昵称">
            </div>
        </div>
        <div class="form-group">
            <label for="url" class="col-sm-2 control-label">个人主页</label>
            <div class="col-sm-5">
                <input type="text" class="form-control" name="url" id="url" placeholder="个人主页">
            </div>
        </div>
        <div class="form-group">
            <label for="content" class="col-sm-2 control-label"></label>
            <div class="col-sm-8">
                <textarea name="content" class="form-control" id="content" cols="30" rows="10"></textarea>
            </div>
        </div>
        <div class="form-group">
            <div class="col-sm-offset-2 col-sm-10">
                <button type="submit" class="btn btn-default">提交</button>
            </div>
        </div>
    </form>
</div>

我们在模板中使用了bootstrap, 所以我们还需要在布局文件default.layer.php中引入bootstrap前端框架所需的css和js.

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title><?php echo isset($title)?$title:'guestbook' ?></title>
    <link href="http://cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
    <link href="http://cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" rel="stylesheet">
    <style>
        #headerwrap {
            background-color: #FFFAF0;
            margin-top: -20px;
            padding-top:50px;
            background-attachment: relative;
            background-position: center center;
            min-height: 150px;
            width: 100%;

            -webkit-background-size: 100%;
            -moz-background-size: 100%;
            -o-background-size: 100%;
            background-size: 100%;

            -webkit-background-size: cover;
            -moz-background-size: cover;
            -o-background-size: cover;
            background-size: cover;
            text-align: center;
        }

        #headerwrap img{
            width:120px;
            padding:30px;
        }
    </style>
</head>
<body>

    <div id="headerwrap">
        <img src="http://www.crossphp.com/static/images/logo.png" alt="cp logo">
    </div>

    <div class="container" style="padding-top: 30px;">
        <?php echo $content; ?>
    </div>
    <script src="http://cdn.bootcss.com/jquery/1.12.1/jquery.min.js"></script>
    <script src="http://cdn.bootcss.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</body>
</html>

这里直接使用第三方CDN提供的在线版本, 当然你可以下载到本地, 放到入口的static目录, 在相应位置使用$this->res('css/bootstrap.css')来获取资源文件的绝对路径, 为了让教程更加简明, 我们直接把自定义的css写在了布局文件中. 最后修改视图控制器代码来加载模板.

<?php
namespace app\guestbook\views;

use Cross\MVC\View;

class MainView extends View
{
    function index($data = array())
    {
        $this->renderTpl('main/guest', $data);
    }
}

在视图控制器中调用模板所在文件夹和模板名称(不带后缀), 就可以了, 这时刷新浏览器, 访问http://domain/htdocs/guestbook/就可以看到带表单的留言发布页面了.

在控制器中接收表单数据

创建好表单后, 我们就可以在控制器中接收表单数据了

<?php
namespace app\guestbook\controllers;

use Cross\MVC\Controller;

class Main extends Controller
{
    function index()
    {
        if ($this->is_post()) {
            print_r($_POST);
        }

        $data['title'] = 'im guestbook';
        $this->display($data);
    }
}

我们在index方法中加一个判断, 如果是POST请求, 那么就打印$_POST数组中的值.

在使用Module之前我们先修改数据库配置, 并创建相应的表, 用来保存留言本数据. 打开config/db.config.php修改数据库配置

/**
 * mysql
 */
$mysql_link = array(
    'host' => '127.0.0.1',
    'port' => '3306',
    'user' => 'root',
    'pass' => '123456',
    'prefix' => '',
    'charset' => 'utf8',
);

#默认数据库配置
$db = $mysql_link;
$db['name'] = 'test';

return array(
    'mysql' => array(
        'db' => $db,
    )
);

不要忘记修改$db['name']来指定数据库名称, 然后创建存储留言的表cp_guestbook, 结构如下:

CREATE TABLE `cp_guestbook` (
	`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	`url` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '访客主页地址',
	`name` VARCHAR(255) NOT NULL COMMENT '昵称',
	`ip` INT(10) UNSIGNED NOT NULL COMMENT '访客IP地址',
	`content` TEXT NOT NULL COMMENT '留言内容',
	`t` INT(11) NOT NULL COMMENT '留言时间',
	PRIMARY KEY (`id`)
)
ENGINE=InnoDB;

创建Module

创建好表结构以后, 我们在modules目录下新建文件夹guestbook, 并在之下创建GuestBookModule.php文件, 用于处理留言数据. 当然你可以把GuestBookModule创建在app/guestbook中, 只要遵循命名规则约定就可以了

<?php
namespace modules\guestbook;

use Cross\Module\SQLModule;

class GuestBookModule extends SQLModule
{
    protected $t = 'cp_guestbook';
}

GuestBookModuleSQLModule继承, 这样我们可以直接使用SQLModule中提供的方法, 来进行简单的数据操作.

使用Module

创建好Module以后, 我们在控制器中调用GuestBookModule来处理数据

<?php
namespace app\guestbook\controllers;

use Cross\Core\Helper;
use Cross\MVC\Controller;
use modules\guestbook\GuestBookModule;

class Main extends Controller
{
    function index()
    {
        $GuestBook = new GuestBookModule();
        if ($this->is_post()) {

            $data = array();
            //简单过滤, 在项目中请自定义一个类来处理这类问题
            $data['name'] = htmlentities(strip_tags($_POST['name']));
            $data['url'] = htmlentities($_POST['url']);
            $data['content'] = htmlentities($_POST['content']);

            //调用Helper来获取客户端IP
            $data['ip'] = Helper::getLongIp();
            $data['t'] = TIME;

            if (empty($data['name']) || empty($data['content'])) {
                die('昵称或内容不能为空');
            }

            //data中的key和数据库中的字段名对应, 直接调用add方法来保存留言数据
            $ret = $GuestBook->add($data);
            if ($ret) {
                //发表留言失败重定向到首页
                $this->to();
                return;
            } else {
                die('发表留言失败, 请联系管理员');
            }
        }

        $this->display($data);
    }
}

查看数据表, 如果数据库中有数据, 那么证明我们发表留言成功了, 下一步我们从数据库中读取数据. 依然修改控制器代码:

<?php
namespace app\guestbook\controllers;

use Cross\Core\Helper;
use Cross\MVC\Controller;
use modules\guestbook\GuestBookModule;

class Main extends Controller
{
    /**
     * @cp_params p=1
     */
    function index()
    {
        $GuestBook = new GuestBookModule();

        //如果是POST请求, 那么保存留言
        if ($this->is_post()) {

            $data = array();
            //简单过滤, 在项目中请自定义一个类来专门处理这类
            $data['name'] = htmlentities(strip_tags($_POST['name']));
            $data['url'] = htmlentities($_POST['url']);
            $data['content'] = htmlentities($_POST['content']);

            //调用Helper来获取客户端IP
            $data['ip'] = Helper::getLongIp();
            $data['t'] = TIME;

            if (empty($data['name']) || empty($data['content'])) {
                die('昵称或内容不能为空');
            }

            //data中的key和数据库中的字段名对应, 直接调用add方法来保存留言数据
            $ret = $GuestBook->add($data);
            if ($ret) {
                //发表留言失败重定向到首页
                $this->to();
                return;
            } else {
                die('发表留言失败, 请联系管理员');
            }

        //其他请求(一般是GET)获取留言数据
        } else {

            //分页数据
            //$page变量是以引用的形式传入find()
            //find()方法执行后会添加总条数到$page变量, 返回到控制器
            $page = array(
                'p' => (int)$this->params['p'], //当前页数 我们用@cp_param来还原参数的key
                'limit' => 10, //每页取多少条
                'link' => array('main:index', array()) //分页参数链接配置 第一个为控制器:方法, 第二个参数为附加参数
            );

            //按id倒排
            $order = 'id DESC';
            //默认查询所有数据, 条件为空
            $condition = array();
            $guest = $GuestBook->find($condition, $page, $order);

            $data['page'] = $page;
            $data['guest'] = $guest;
        }

        $this->display($data);
    }
}

我们把查询结果和分页数据分别放到$data中, 然后一起传给视图控制器来处理.

输出留言列表

从控制器中获取到数据后, 再修改视图控制器输出留言列表, 并扩展视图控制器, 增加一个方法输出分页标签.

<?php
namespace app\guestbook\views;

use Cross\MVC\View;

class MainView extends View
{
    function index($data = array())
    {
        //判断是否有guest数据, 有的话载入main/guest_list模板
        if (!empty($data['guest'])) {
            $this->renderTpl('main/guest_list', $data);
        }

        $this->renderTpl('main/guest', $data);
    }

    //分页方法
    function page($data = array())
    {
        list($controller, $params) = $data['link'];

        //上一页
        $pre_params = $params;
        $pre_params['p'] = max(1, $data['p'] - 1);

        //下一页
        $next_params = $params;
        $next_params['p'] = min($data['p'] + 1, $data['total_page']);
        ?>
        <nav>
            <ul class="pagination">
                <li>
                    <a href="<?php echo $this->url($controller, $pre_params) ?>" aria-label="Previous">
                        <span aria-hidden="true">&laquo;</span>
                    </a>
                </li>

                <?php
                for($i=1; $i<=$data['total_page']; $i++) {
                    $params['p'] = $i;
                    if ($data['p'] == $i) {
                        //当前页
                        printf('<li class="active"><a href="%s">%d</a></li>', $this->url($controller, $params), $i);
                    } else {
                        printf('<li><a href="%s">%d</a></li>', $this->url($controller, $params), $i);
                    }
                }
                ?>
                <li>
                    <a href="<?php echo $this->url($controller, $next_params) ?>" aria-label="Next">
                        <span aria-hidden="true">&raquo;</span>
                    </a>
                </li>
            </ul>
        </nav>
        <?php
    }
}

最后创建main/guest_list模板

<div class="col-md-12">
    <div class="panel">
        <div class="panel-heading">
            <h3>
                留言列表
            </h3>
        </div>

        <div class="panel-body">
            <?php foreach($data['guest'] as $d) : ?>
                <div class="media">
                    <div class="media-left">
                        <a href="<?php echo $this->e($d, 'url', 'http://www.crossphp.com') ?>">
                            <?php echo $d['name'] ?>
                        </a>
                    </div>
                    <div class="media-body">
                        <h4 class="media-heading">
                            <?php echo $d['content'] ?>
                        </h4>
                        <?php printf('留言时间: %s', date('Y-m-d H:i:s', $d['t'])) ?>
                    </div>
                </div>
            <?php endforeach ?>
        </div>

        <div class="panel-footer">
            <?php $this->page($data['page']) ?>
        </div>
    </div>
</div>

总结

到此我们的留言本例子完成了. 我们一共创建了八个文件

  1. 控制器

    app/guestbook/controllers/Main.php
    

    在控制器中处理url参数, 从Module中获取数据, 最后把数据交给视图控制器来处理.

  2. 视图控制器及模板

    app/guestbook/views/MainView.php
    

    视图控制器和控制器默认是一一对应的, 专职处理数据输出, 根据配置输出不同格式的数据给浏览器

    app/guestbook/templates/default/main/guest.tpl.php
    app/guestbook/templates/default/main/guest_list.tpl.php
    
    

    两个模板, 分别用于留言发布和列表, 发布和列表分开的原因是发布留言的模板, 可以重用于留言编辑, 只是我们在demo中没实现 :)

    app/guestbook/templates/default/default.layer.php		
    

    布局文件用来整体控制页面结构, 管理css, js等. 通过布局文件, 可以很方便的做skin. 视图控制器方法的输出最后会赋值给布局中一个叫$content的变量, 灵活的使用布局, 我们可以处理项目中各种复杂的页面结构.

  3. 模块

    modules/guestbook/GuestBookModule.php
    

    模块是项目的数据中心, 数据在模块中处理, 在控制器中调用传递给视图控制器. 模块使数据来源统一, 便于各app重用.

  4. 一个app配置文件

    app/guestbook/init.php
    
  5. 一个入口文件

    app/htdocs/guest/index.php
    

以上.

这个例子实现了一个简单的留言本的发布和查看功能, 也大致梳理了基于cp的项目结构. 由于目的只是讲解cp的使用, 所以功能并不完善, 编辑, 删除功能都大同小异, 有兴趣的同学可以自己完善, 如有问题或建议请加入我们的QQ群讨论~

最后附上截图