Bobby 发表于 2013-12-6 22:18:36

CodeIgniter控制器之业务逻辑

前面对公用控制器按模块分发,方便对特定模块的控制,而具体的实现类则是放在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里用法就跟控制器里一样了。
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之类的,其实也可以按照上面的思路单独放在一个文件夹中处理,方便管理。

caoniao 发表于 2014-5-3 23:36:25

我说呢。虽然我是新手,我就觉得MVC应该加这一层:Q,要不然没法理解

wwwhx110com 发表于 2014-5-9 00:30:08

为什么一定要修改CI核心的东西,不能搞个扩展类吗

zhengmz 发表于 2013-12-15 21:54:21

大侠,根据你的思路,我对MY_Loader编写如下,请您指教是否妥当? 谢谢。

<?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 */

zhengmz 发表于 2013-12-15 21:58:44

同时,我对例子中的upload修改如下,请一并帮忙指教,主要我对J2EE不熟,不知道service这一层具体如何使用,不过我觉得你说的很有道理,所以我采用你的思路。
upload的controller代码如下:

<?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

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 */

zhengmz 发表于 2013-12-15 21:59:36

我以前是用C/C++,现在要做WEB开发了,所以很菜,还有多多请教。

Bobby 发表于 2013-12-15 23:38:23

zhengmz 发表于 2013-12-15 21:54 static/image/common/back.gif
大侠,根据你的思路,我对MY_Loader编写如下,请您指教是否妥当? 谢谢。

...

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

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


    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);
    }

zhengmz 发表于 2013-12-16 14:22:05

Bobby 发表于 2013-12-15 23:38 static/image/common/back.gif
您写的很好,思路都清楚了。我这边是把创建的方法在提取了一遍,方便以后load cache 或者其他。service我 ...

感谢你及时的反馈。
你在实现loader的service方法,是借鉴了libarariy,可以实现参数传递,这一点我可以借鉴一下,谢谢了。
对于service,我也简单了解了一下yii的框架,在yii框架中业务处理层,每一个功能对应一个文件,不知理解是否正确?另外,你能否将发一份service的代码给我参考,谢谢。

Bobby 发表于 2013-12-16 21:42:43

本帖最后由 Bobby 于 2013-12-16 21:44 编辑

zhengmz 发表于 2013-12-16 14:22 static/image/common/back.gif
感谢你及时的反馈。
你在实现loader的service方法,是借鉴了libarariy,可以实现参数传递,这一点我可以 ...
YII是提取公用方法的时候有参考它的函数和方法名, YII的业务逻辑也是写在控制器或模型中,AR的模型也是一个对一个。Service里都是业务逻辑了,好像也没什么,看个用户的。

<?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 */

zhengmz 发表于 2013-12-16 22:19:34

Bobby 发表于 2013-12-16 21:42 static/image/common/back.gif
YII是提取公用方法的时候有参考它的函数和方法名, YII的业务逻辑也是写在控制器或模型中,AR的模型也是一 ...

好的,多谢了。{:soso_e183:}

wwwhx110com 发表于 2014-3-22 21:23:26

没有看明白
页: [1] 2
查看完整版本: CodeIgniter控制器之业务逻辑