用户
 找回密码
 入住 CI 中国社区
搜索
查看: 18768|回复: 28
收起左侧

使用 Codeigniter 框架与 Datamapper ORM 创建简单的内容管理示例

  [复制链接]
发表于 2012-1-14 17:55:12 | 显示全部楼层 |阅读模式
前言:使用 Codeigniter 编写一个简单的内容管理应用相当容易,甚至还可以加上相关文章的评论,只不过这些都需要预先写好模型来处理所有的 CRUD(Create Read Update Delete) 操作,如果数据表之间的关系更复杂的时候,往往模型不会完全满足你的要求,这时你可能需要继续为模型添加方法来处理需求。当引入了 Datamapper ORM 以后,只要设计好了数据表,制定了表之间的关系以后,建立相应的 Datamapper ORM 模型,那么在写控制器的时候只要使用这些模型映射数据表得到的对象即可对数据的所有 CRUD 进行操作,甚至可以多人协同工作,比如:模型与表的建立,控制器的编写,前端设计。

注意:所有提到的代码都将在且仅在 Codeigniter 2.1.0,Datamapper ORM 1.8.2 和 Twig 1.5.1 以及 xampp 1.7.7 的环境中测试,对于其他的版本或环境使用提供的代码无法获得预期效果的,恕无法提供测试支持,请了解。

第一天:把 Datamapper ORM 集成到 Codeigniter 中

Datamapper ORM 手册中提到了如何整合其到 Codeigniter 中,依照手册步骤很容易做到。点击查看汉化版安装指导,或者查看其官方手册

首先把下载到的 Codeigniter 2.1.0 包解压到 xampp 的 htdocs 目录下,比如:cdt 目录中;从 Datamapper ORM 官方网站下载 Datamapper ORM 1.8.2 版的包(其中提供了一个 Lite Version,是没有包含其手册和示例应用的简化包,对于简化包和完全包,需要使用的都只是其 application 目录下的文件,所以请自由选择),解压其中的 application 目录到 cdt 目录中。完成这一步以后即可按照安装指导从第七步开始:

7. 使用文本编辑器打开你的 CodeIgniter 中的文件 application/config/autoload.php 并添加 database 与 datamapper 库到 autoload 自动加载库数组,像这样:
PHP复制代码
$autoload['libraries'] = array('database', 'datamapper');
复制代码

同时,确认你清除了 models 数组,因为 Datamapper 会自动加载这些模型类,像这样:
PHP复制代码
$autoload['models'] = array();
复制代码


重要:9. 一定要修改 Codeigniter 的入口文件 index.php,加上 Datamapper ORM 的引导程序。
PHP复制代码
 
/* --------------------------------------------------------------------
 * LOAD THE DATAMAPPER BOOTSTRAP FILE
 * --------------------------------------------------------------------
 */

require_once APPPATH.'third_party/datamapper/bootstrap.php';
 
/*
 * --------------------------------------------------------------------
 * LOAD THE BOOTSTRAP FILE
 * --------------------------------------------------------------------
 *
 * And away we go...
 *
 */

require_once BASEPATH.'core/CodeIgniter.php';
 
复制代码


现在 Datamapper ORM 就已经集成在 Codeigniter 中了(加入了 Datamapper ORM 后程序运行使用的内存比相同情况下不使用 Datamapper ORM 大了 1m 多),在浏览器中打开 http://localhost/cdt/ 并没有发现有什么不同,在 welcome controller 中写上:
PHP复制代码
 
public function index()
{
    $this->output->enable_profiler(TRUE);
    $this->load->view('welcome_message');
}
 
复制代码

再次运行,点开 CONFIG VARIABLES 可以在最后看到有 datamapper 的信息,集成成功。
点此下载示例代码
发表于 2015-12-16 15:53:25 | 显示全部楼层
datamapper 是不是只适合 CI2.x  不适合3.x ?  
 楼主| 发表于 2012-1-14 17:56:10 | 显示全部楼层
本帖最后由 huboo82 于 2012-1-14 19:41 编辑

第二天:建立数据表以及 Datamapper ORM 模型

首先修改 application/config/autoload.php 和 application/config/config.php 文件:
PHP复制代码
 
/*
 * 修改 autoload.php,为 libraries 增加 session 库
 */

$autoload['libraries'] = array('database', 'session', 'datamapper');
 
/*
 * 修改 config.php,要使用 session 必须得设置一个 encryption_key,这个 key 可以是任意非空字符串
 */

$config['encryption_key'] = 'z6gx2fpuk0xfjq2dcrpd';
 
复制代码


现在可以正式开始设计模型和数据表了。我们需要一个内容表来保存以及显示内容,需要一个用户表来允许授权的用户添加/修改/删除内容,可能还需要一个评论表来保存任何人发表的评论,于是我们的 Datamapper ORM 的模型将是:
PHP复制代码
 
class Content extends DataMapper {
     
}
 
复制代码
PHP复制代码
 
class User extends DataMapper {
     
}
 
复制代码
PHP复制代码
 
class Comment extends DataMapper {
     
}
 
复制代码


一条内容有一个用户与之关联,所以内容模型与用户模型之间是一对一的关系,反过来,一个用户可能创建了很多条内容,那么用户对内容(相对评论也同样是一对多关系)就是一对多的关系:
PHP复制代码
 
class Content extends DataMapper {
     public $has_one = array('user');
}
 
复制代码
PHP复制代码
 
class User extends DataMapper {
     public $has_many = array('content', 'comment');
}
 
复制代码
PHP复制代码
 
class Comment extends DataMapper {
     public $has_one = array('user');
}
 
复制代码


对于评论模型,也许我们只需要一个用户就够了,比如:是哪位用户留下的评论,但是对于内容模型来说,我们除了有一个创建内容的用户以外,可能还需要一个编辑内容的用户,那么就需要用到 Datamapper ORM 的高级关系的内容:
PHP复制代码
 
class Content extends DataMapper {
     
    public $has_one = array(
        'creator' => array(
            'class' => 'user',
            'other_field' => 'created_post'
        ),
        'editor' => array(
            'class' => 'user',
            'other_field' => 'edited_post'
        )
    );
     
}
 
复制代码
PHP复制代码
 
class User extends DataMapper {
     
    public $has_many = array(
        'created_post' => array(
            'class' => 'content',
            'other_field' => 'creator'
        ),
        'edited_post' => array(
            'class' => 'content',
            'other_field' => 'editor'
        ),
        'comment'
    );
     
}
 
复制代码


这样我们通过一个 User 的 Datamapper ORM 对象就可以访问到其修改的或创建的内容:$user->created_post->get()。通过上面的过程,我们需要用的的模型已经建立好了,可以根据模型来设计数据库表了。表的字段可以按自己的需要来定制,只是特殊的地方就在于每个表要有一个 ‘id’ 字段以及表关系。我们使用表内外键来关联一对多的关系,所以 contents 表需要有一个 'creator_id' 和 ‘editor_id’ 来关联 users 表。下面是表结构的 sql 脚本:
SQL复制代码
 
--
-- 数据库: `demo`
--
 
-- --------------------------------------------------------
 
--
-- 表的结构 `comments`
--
 
CREATE TABLE IF NOT EXISTS `comments` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `author` VARCHAR(100) DEFAULT NULL,
  `user_id` INT(10) UNSIGNED DEFAULT NULL,
  `text` text NOT NULL,
  `created` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
 
-- --------------------------------------------------------
 
--
-- 表的结构 `contents`
--
 
CREATE TABLE IF NOT EXISTS `contents` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `title` VARCHAR(100) NOT NULL,
  `text` text NOT NULL,
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  `creator_id` INT(10) UNSIGNED DEFAULT NULL,
  `editor_id` INT(10) UNSIGNED DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
 
-- --------------------------------------------------------
 
--
-- 表的结构 `users`
--
 
CREATE TABLE IF NOT EXISTS `users` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(100) NOT NULL,
  `password` VARCHAR(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
 
复制代码


现在开始在 welcome controller 中测试一下是否运行正常吧。

新建一个用户:
PHP复制代码
 
// 实例化一个 User Datamapper ORM 模型,并设置用户的用户名和密码,然后保存它
$user = new User();
$user->username = 'huboo';
$user->password = '123456';
 
// 保存这个用户,如果出错打印出错信息,否则打印保存的用户信息
if($user->save())
{
    echo $user->id . '<br />';
    echo $user->username . '<br />';
    echo $user->password;
}
else
{
    echo $user->error->string;
}
 
复制代码


新建一条内容并与刚刚创建的用户关联
PHP复制代码
 
// 如果以上没有出错就可以进行下面的测试
// 新建一条内容,并与该用户关联,使该用户为这条内容的创建者
$post = new Content();
$post->title = 'First post';
$post->text = 'First post content. blablabla...';
 
// 保存用户的关联,之后查看一下数据库的 Contents 表,应该多了一条记录,并且在 created 字段里自动加上了时间戳
$post->save_creator($user);
 
复制代码


点此下载示例代码
 楼主| 发表于 2012-1-14 17:57:34 | 显示全部楼层
本帖最后由 huboo82 于 2012-1-14 19:45 编辑

第三天:集成 Twig 模板引擎并用模板来渲染数据

经过前面的过程,我们已经可以内置的 load->view 方式来输出视图了,这里再介绍一个专门的模板引擎用来处理显示输出。

Twig 是一个专为 PHP 编写的模板引擎,可以从其官方网站上看到它的特性,当然,是英文的。下面开始介绍如何把 Twig 集成到 CI 中。

首先,从 Twig 网站上下载到当前最新 stable 版 1.5.1,解压其中的 Twig-1.5.1.tgz/Twig-1.5.1/lib 下的 Twig 目录到前面创建的 cdt 的 application/third_party 中,把 Twig.php 文件放到 application/libraries 中,文件的集成就完成了。

文件:application/libraries/Twig.php(参考链接:https://github.com/altrano/codeigniter-twig
PHP复制代码
 
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 
require_once(APPPATH . 'third_party/Twig/Autoloader.php');
class Twig {
     
    private $_CI;
    private $_twig;
    private $template_dir = '';
    private $debug = FALSE;
     
    /**
     * construct
     *
     * @access public
     * @return void
     */

    public function __construct($params = array())
    {
        $this->_CI =& get_instance();
         
        if(count($params) > 0)
        {
            $this->initialize($params);
        }
         
        Twig_Autoloader::register();
         
        $loader = new Twig_Loader_Filesystem($this->template_dir);
        $this->_twig = new Twig_Environment($loader, array(
            'cache' => $this->template_dir . DIRECTORY_SEPARATOR . 'cache',
            'debug' => $this->debug,
        ));
 
        log_message('debug', "Twig Engine Class Initialized");
    }
     
    private function initialize($config)
    {
        foreach ($config as $key => $val)
        {
            if (isset($this->$key))
            {
                $this->$key = $val;
            }
        }
    }
     
    public function render($template, $data = array())
    {
        $template = $this->_twig->loadTemplate($template);
        return $template->render($data);
    }
 
    public function display($template, $data = array())
    {
        $template = $this->_twig->loadTemplate($template);
         
        /* elapsed_time and memory_usage */
        $memory = ( ! function_exists('memory_get_usage')) ? '0' : round(memory_get_usage()/1024/1024, 2) . 'MB';
        $data['elapsed_time'] = $this->_CI->benchmark->elapsed_time('total_execution_time_start', 'total_execution_time_end');
        $data['memory_usage'] = $memory;
         
        $template->display($data);
    }
}
 
/* End of file: Twig.php */
/* Location: ./application/libraries/Twig.php */
 
复制代码


完成上面的步骤以后,为了方便模板引擎的使用以及区分用户,我们需要扩展一下 CI_Controller:

文件:application/core/MY_Controller.php
PHP复制代码
 
<?php if( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
 * 前端使用控制器
 * 扩展控制器以具备模板引擎
 */

class MY_Controller extends CI_Controller {
     
    protected $_data = array();
 
    public function __construct()
    {
        parent::__construct();
         
        $this->_data['user'] = NULL;
        if($this->session->userdata('logged_in'))
        {
            $uid = $this->session->userdata('uid');
            $this->_data['user'] = new User($uid);
        }
         
        // 加载模板引擎库并初始化
        // 这里只要更改 template_dir 即可修改模板所在目录
        // demo 里不做这样的尝试
        $this->load->library('Twig', array('template_dir' => APPPATH . 'views', 'debug' => TRUE), 'twig');
         
        // $this->output->enable_profiler(TRUE);
    }
}
 
/**
 * 后台使用控制器
 * 扩展控制器以具备基本权限检测
 */

class MY_Auth_Controller extends CI_Controller {
     
    protected $_data = array();
     
    public function __construct()
    {
        parent::__construct();
         
        if($this->session->userdata('logged_in'))
        {
            $uid = $this->session->userdata('uid');
            $this->_data['user'] = new User($uid);
        }
        else
        {
            $this->session->sess_destroy();
            redirect('login');
        }
         
        $this->load->library('Twig', array('template_dir' => APPPATH . 'views', 'debug' => TRUE), 'twig');
         
        // $this->output->enable_profiler(TRUE);
    }
}
 
/* End of file: MY_Controller.php */
/* Location: ./application/core/MY_Controller.php */
 
复制代码


以后再写控制器时,需要用到权限的就从 MY_Auth_Controller 继承,其他就从 MY_Controller 继承就可以使用到 Twig 模板引擎了。

让我们试试看效果如何。修改 application/controllers/welcome.php
PHP复制代码
 
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 
// 从我们扩展过的控制器继承
class Welcome extends MY_Controller {
 
    public function index()
    {
        // 实例化一个 User Datamapper ORM 模型
        $user = new User(1);
         
        // 如果用户存在就赋予给 _data 变量,用于模板输出
        if($user->exists())
        {
            $this->_data['user'] = $user;
        }
         
        $this->output->enable_profiler(TRUE);
        $this->twig->display('index.html', $this->_data);
    }
}
 
/* End of file welcome.php */
/* Location: ./application/controllers/welcome.php */
 
复制代码


最终用于渲染的模板:application/views/index.html
HTML复制代码
 
<html>
<head>
    <title>Hello world</title>
</head>
<body>
 
{% if user %}
{{ user.id }}<br />
{{ user.username }}<br />
{{ user.password }}<br />
{% endif %}
 
</body>
</html>
 
复制代码


看到这个模板,用过 Django 的同学应该觉得非常熟悉,因为这个模板和 Django 的模板完全一样,理解起来也非常容易,{%...%} 用来执行语句,比如 if 判断或者 for 循环;{{...}} 用来输出变量。

运行一下,id 为 1 的用户的用户名和密码以及其 id 就被显示出来了,如果把控制器中的 $user = new User() 改成一个不存在用户的 id,比如 2,那么最终结果模板不会输入任何内容。

Twig 的集成居然耗费了这么长的篇幅,那再顺便说说下一篇会需要的内容。下一篇也就是最后一篇,我们需要四个模板来分别展示登录,内容列表,内容详细,内容添加/编辑。

点击下载测试代码
 楼主| 发表于 2012-1-14 17:58:23 | 显示全部楼层
本帖最后由 huboo82 于 2012-1-14 19:49 编辑

第四天:Make it happens

虽然是最后一篇,但是真的自己在写的时候感觉才刚刚开始,之前做的只是铺垫。于是,在使用 ORM 时,在编写模板时都要去再看它们的文档来解决遇到的问题,他们都在某些方面简化了步骤,只是因为他们又产生了其他的问题,多多的查看文档才是最好的解决问题的方法吧,当然 Google 也是不可缺少的。

到最后,虽然只是新增两个控制器和一些模板,却又把以前的模型和库都做了些许修改,希望不要困扰到看它的人,如果有任何问题也欢迎邮件或留言(还没有人给我留言过唉~留言审核后可见,只要你留言时没出错,应该就进了数据库了)。

上一篇说到需要一些模板来呈现内容,这里确定是六个模板文件,分别是基础模板:base.html,剩下的都是从基础模板扩展来的用以呈现前后台内容列表、登录、内容创建/编辑、内容详细及评论/评论展示;两个控制器,前台和后台,代码太长,不太适合往这里贴出来了,我整个打包了一份放在网盘里提供下载,代码基本都注释了,若还有无法理解的部分可以邮件或留言告知,有空我都会解答的。

最后也把用来测试的数据库一并提供(似乎数据库也有修改>.<,改地方太多,自己也不记得了)。如果你在 linux 系统下测试,记得给 application/views 写入权限

Demo 下载

贴上几张图片来看样子吧:

前台内容列表

前台内容列表

前台内容详细以及评论列表

前台内容详细以及评论列表

后台内容列表

后台内容列表
 楼主| 发表于 2012-1-14 18:00:45 | 显示全部楼层
本帖最后由 huboo82 于 2012-1-14 19:50 编辑

内容已全部搬过来了。
发表于 2012-1-15 13:48:38 | 显示全部楼层
Datamapper ORM是什么
 楼主| 发表于 2012-1-15 14:36:51 | 显示全部楼层
sdink 发表于 2012-1-15 13:48
Datamapper ORM是什么

DataMapper 是一个为 CodeIgniter 设计的 Object Relational Mapper(对象关系映射) 库,它被设计用来把数据库表映射为容易使用的对象,并能完全感知对象间的关系。

简单的说你有一个 user 模型,一个 users 表,通过 datamapper orm 可以很方便的处理这个数据库表的内容。
发表于 2012-1-15 16:25:22 | 显示全部楼层
huboo82 发表于 2012-1-15 14:36
DataMapper 是一个为 CodeIgniter 设计的 Object Relational Mapper(对象关系映射) 库,它被设计用来把 ...

Active Record 类 不就可以吗
 楼主| 发表于 2012-1-15 17:34:50 | 显示全部楼层
本帖最后由 huboo82 于 2012-1-15 17:59 编辑

It's not the same thing!
用 ORM,你的 User 模型可能就是这样
PHP复制代码
 
class User extends DataMapper {
     
}
 
复制代码

就可以处理所有CRUD的操作,但是用AR,你的模型就得把CRUD的方法都写好,才可以在控制器使用。


用 ORM:
创建用户
PHP复制代码
 
$u = new User();
$u->username = 'Username';
$u->mail = 'username@mail.com';
 
$u->save();
 
复制代码

编辑/删除用户
PHP复制代码
 
$u = new User();
$u->get_by_id($id);
 
// 编辑
$u->username = 'Username';
$u->mail = 'username@mail.com';
 
$u->save();
// 删除
$u->delete();
 
复制代码

在获取用户时还可以用更复杂的方法,比如查询email为特定值的用户
PHP复制代码
 
$u = new User();
$u->get_where(array('mail' => $email), $limit, $offset);
 
复制代码
 楼主| 发表于 2012-1-15 18:02:43 | 显示全部楼层
其实用 AR 和 ORM 都可以得到一样的效果,只是便捷程度不同。我个人之前一直都是用 AR 的,直到用过 Django 以后,后来又发现这个专为 CI 写的 ORM,我觉得很方便用,所以有了写上面那些的愿望。

选择适合自己的才是重要的,祝你好运。

本版积分规则