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

CodeIgniter控制器之业务逻辑

[复制链接]
发表于 2013-12-6 22:18:36 | 显示全部楼层 |阅读模式
前面对公用控制器按模块分发,方便对特定模块的控制,而具体的实现类则是放在library中。那放在library中是否合适呢?以及控制器中更多的业务逻辑该放在哪里?
先说下对CI中几个文件夹的理解
helpers、libraries: 存放一系列辅助函数、辅助类,用来辅助控制器、业务逻辑实现功能。他们中的方法应当尽量避免与CI依赖,依赖越紧越难以复用。以邮件发送为例,发送邮件时很多参数是不变的,如编码、协议、端口等,我们可能会在config下进行配置这些参数,然后library封装一个邮件发送的类,并在其中获取CI实例后读取这些参数。此时就出现了与CI实例的依赖,该类就只能在CI框架中使用,其他系统要用到,就只能重写了,没达到复用的目的。如果发送的类只是接收参数,并封装发送方法呢?所以说,尽可能的让helpers、libraries变的简单,职责变得单一。
controllers: 控制器目录。控制器主要用来接管程序,起到连接的作用。通常情况下,我们会把业务逻辑写在action中。但随着业务变得复杂,action代码将越来越臃肿,难以维护。
models: 模型目录。CI的模型的主要职责就是和数据库打交道,获取数据。很多时候也会把业务逻辑放在模型中,但业务逻辑与模型实际上是两种东西了。模型只是获取数据,业务逻辑可能是把这些数据根据业务需要进行组合,组合方式可能有很多种,放在模型中会让模型难以维护且不利于复用。说个碰到的例子,对数据按一定条件做缓存,获取数据和缓存结果两个流程写在同一个方法中,但同样的数据需要做另一种形式的缓存时发现,获取数据的方法就没法重用了。
third_party:第三方类库目录。拿到一个类库后不要直接使用, 可以在library中进行一次封装,让其更适应于系统,其他人使用起来难度也会降低。
可以发现,每个文件夹都有自己的职责,每个模块都有自己的家,都有自己的职能。那业务逻辑该怎么办?
既然这样, 我们也应该给业务逻辑安个家,建立一个唯一的目录用来存放业务逻辑,暂且命名为service。控制器主要负责接收参数并调用service,service来调用模型,各层各尽其责。
下面看看怎么实现:
我们可以重写MY_Load,增加service方法,直接通过$this->load->service('user_service');来调用。
但业务逻辑很多都需要获取CI实例,这里可以参考模型的方法,core建立一个MY_Service,其他service均继承该类,这样子service里用法就跟控制器里一样了。
PHP复制代码
 
class MY_Service
{
    public function __construct()
    {
        log_message('debug', "Service Class Initialized");
    }
 
    function __get($key)
    {
        $CI = & get_instance();
        return $CI->$key;
    }
}
 
复制代码

其实主要思路还是需要有一层用来处理业务逻辑,java中都有这一层。随着对CI的不断熟悉,发觉这里需要这一层,达到解放控制器和模型的目的。和这种类似的做法还有很多,如果系统中有很多地方需要用到web service 或者说cache之类的,其实也可以按照上面的思路单独放在一个文件夹中处理,方便管理。
发表于 2014-5-3 23:36:25 | 显示全部楼层
我说呢。虽然我是新手,我就觉得MVC应该加这一层,要不然没法理解
发表于 2014-5-9 00:30:08 | 显示全部楼层
为什么一定要修改CI核心的东西,不能搞个扩展类吗
发表于 2013-12-15 21:54:21 | 显示全部楼层
大侠,根据你的思路,我对MY_Loader编写如下,请您指教是否妥当? 谢谢。
PHP复制代码
 
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 
/**
 * My Loader Class
 *
 * @package             MyCore
 * @subpackage          Libraries
 * @category            Libraries
 * @author              zhengmz
 * @link                http://codeigniter.com/user_guide/libraries/config.html
 */

class MY_Loader extends CI_Loader {
 
        /**
         * List of paths to load services from
         *
         * @var array
         * @access protected
         */

        protected $_ci_service_paths = array();
        /**
         * List of loaded services
         *
         * @var array
         * @access protected
         */

        protected $_ci_services = array();
 
 
        /**
         * Constructor
         *
         * @access public
         */

        public function __construct()
        {
                parent::__construct();
                $this->_ci_service_paths = array(APPPATH);
                $this->_ci_services = array();
 
                log_message('debug', "MY_Loader Class Initialized");
        }
 
 
        /**
         * Service Loader
         *
         * This function lets users load and instantiate services.
         *
         * @param       string  the name of the service
         * @param       mixed   the optional parameters
         * @param       string  an optional object name
         * @return      void
         */

        public function service($service = '', $object_name = NULL)
        {
                if (is_array($service))
                {
                        foreach ($service as $class)
                        {
                                $this->service($class);
                        }
 
                        return;
                }
 
 
                if ($service == '')
                {
                        return;
                }
 
                $subpath = '';
 
                // Is the service in a sub-folder? If so, parse out the filename and path.
                if (($last_slash = strrpos($service, '/')) !== FALSE)
                {
                        // The path is in front of the last slash
                        $subpath = substr($service, 0, $last_slash + 1);
 
                        // And the service name behind it
                        $service = substr($service, $last_slash + 1);
                }
 
                if ($object_name == '')
                {
                        $object_name = $service;
                }
 
                if (in_array($object_name, $this->_ci_services, TRUE))
                {
                        return;
                }
 
                $CI =& get_instance();
                if (isset($CI->$object_name))
                {
                        show_error('The service name you are loading is the name of a resource that is already being used: '.$object_name);
                }
 
                $service = strtolower($service);
                foreach ($this->_ci_service_paths as $mod_path)
                {
                        $service_full_path = $mod_path.'services/'.$subpath.$service.'.php';
 
                        if ( ! file_exists($service_full_path))
                        {
                                continue;
                        }
 
                        if ( ! class_exists('CI_Service'))
                        {
                                load_class('Service', 'core');
                        }
 
                        require_once($service_full_path);
 
                        $service = ucfirst($service);
 
                        $CI->$object_name = new $service();
 
                        $this->_ci_services[] = $object_name;
                        return;
                }
 
                // couldn't find the service
                show_error('Unable to locate the service you have specified: '.$service);
        }
 
}
 
/* End of file MY_Loader.php */
/* Location: ./application/core/MY_Loader.php */
 
复制代码

发表于 2013-12-15 21:58:44 | 显示全部楼层
同时,我对例子中的upload修改如下,请一并帮忙指教,主要我对J2EE不熟,不知道service这一层具体如何使用,不过我觉得你说的很有道理,所以我采用你的思路。
upload的controller代码如下:
PHP复制代码
 
<?php
 
class Upload extends CI_Controller {
 
        function __construct()
        {
                parent::__construct();
                $this->load->helper(array('form', 'url'));
        }
 
        function index()
        {
                $this->load->view('upload_form', array('error' => ' ' ));
        }
 
        function do_upload()
        {
                $this->load->service('upload_serv');
                $this->upload_serv->do_upload();
        }
}
 
/* End of file upload.php */
/* Location: ./application/controllers/upload.php */
 
 
复制代码


upload的service代码如下:
PHP复制代码
 
<?php
 
class Upload_serv extends MY_Service
{
        function do_upload()
        {
                $config['upload_path'] = 'uploads/';
                $config['allowed_types'] = 'gif|jpg|png';
                $config['max_size'] = '100';
                $config['max_width']  = '1024';
                $config['max_height']  = '768';
 
                $this->load->library('upload', $config);
 
                if ( ! $this->upload->do_upload())
                {
                        $error = array('error' => $this->upload->display_errors());
                        $this->load->view('upload_form', $error);
                }
                else
                {
                        $data = array('upload_data' => $this->upload->data());
                        $this->load->view('upload_success', $data);
                }
        }
}
 
/* End of file upload_serv.php */
/* Location: ./application/services/upload_serv.php */
 
 
复制代码
发表于 2013-12-15 21:59:36 | 显示全部楼层
我以前是用C/C++,现在要做WEB开发了,所以很菜,还有多多请教。
 楼主| 发表于 2013-12-15 23:38:23 | 显示全部楼层
zhengmz 发表于 2013-12-15 21:54
大侠,根据你的思路,我对MY_Loader编写如下,请您指教是否妥当? 谢谢。

...

您写的很好,思路都清楚了。我这边是把创建的方法在提取了一遍,方便以后load cache 或者其他。service我的理解还是希望能更公用, load view的过程可以在控制器中来做。

我这边的service参考,_ci_load_user_class为自定义方法,也就是和您service里面的实现差不多。

PHP复制代码
 
    protected $_ci_service_paths;
 
    public function __construct()
    {
        parent::__construct();
        $this->_ci_service_paths = array(APPPATH.'/service');
    }
 
    public function service($service_name = '', $params = NULL, $object_name = NULL)
    {
        load_class('Service', 'core');
        if(is_array($service_name)) {
            foreach($service_name as $class) {
                $this->service($class, $params);
            }
            return;
        }
        if($service_name == '' or isset($this->_base_classes[$service_name])) {
            return FALSE;
        }
 
        if(! is_null($params) && ! is_array($params)) {
            $params = NULL;
        }
 
        $this->_ci_load_user_class($service_name, $params, $object_name, $this->_ci_service_paths);
    }
 
复制代码

发表于 2013-12-16 14:22:05 | 显示全部楼层
Bobby 发表于 2013-12-15 23:38
您写的很好,思路都清楚了。我这边是把创建的方法在提取了一遍,方便以后load cache 或者其他。service我 ...

感谢你及时的反馈。
你在实现loader的service方法,是借鉴了libarariy,可以实现参数传递,这一点我可以借鉴一下,谢谢了。
对于service,我也简单了解了一下yii的框架,在yii框架中业务处理层,每一个功能对应一个文件,不知理解是否正确?另外,你能否将发一份service的代码给我参考,谢谢。
 楼主| 发表于 2013-12-16 21:42:43 | 显示全部楼层
本帖最后由 Bobby 于 2013-12-16 21:44 编辑
zhengmz 发表于 2013-12-16 14:22
感谢你及时的反馈。
你在实现loader的service方法,是借鉴了libarariy,可以实现参数传递,这一点我可以 ...

YII是提取公用方法的时候有参考它的函数和方法名, YII的业务逻辑也是写在控制器或模型中,AR的模型也是一个对一个。Service里都是业务逻辑了,好像也没什么,看个用户的。
PHP复制代码
 
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 
/**
 * User Service
 *
 */

class User_service extends Common_service
{
    public function __construct()
    {
        parent::__construct();
        $this->load->model('admin_user_model');
    }
 
    /**
     * Check User Login
     *
     * @param string $username
     * @param string $password
     * @param string $after_handle Whether Update Last Logined Ts OR Not
     * @return boolean|unknown
     */

    public function login($username, $password, $after_handle = true)
    {
        $admin = $this->admin_user_model->findByAttributes(array('username' => $username));
        if(empty($admin) || $admin['password'] != generate_passwrod($password, $admin['salt'])) {
            return false;
        }
        if($after_handle) {
            $this->admin_user_model->updateByPk($admin['user_id'], array(
                'last_logined_ts' => date("Y-m-d H:i:s")
            ));
        }
        return $admin;
    }
}
 
/* End of file user_service.php */
/* Location: ./application/service/user_service.php */
 
复制代码

发表于 2013-12-16 22:19:34 | 显示全部楼层
Bobby 发表于 2013-12-16 21:42
YII是提取公用方法的时候有参考它的函数和方法名, YII的业务逻辑也是写在控制器或模型中,AR的模型也是一 ...

好的,多谢了。{:soso_e183:}
发表于 2014-3-22 21:23:26 | 显示全部楼层
没有看明白

本版积分规则