CI3设置子目录控制器为默认控制器的解决办法

codeigniter

[Meting autoplay=“true”]

[Music server=“xiami” id=“82474” type=“song”/]

[/Meting]

    在框架中配置文件多目录前后台应该是个很常见的事情。像一般的php框架(CITp等)采用都是[单一入口][2]模式,或许有人会直接在框架根目录新建文件admin.php,然后改变框架app结构,以达到访问不同入口文件名获得不同资源的效果。那么在CI中一样可以这样做,不过个人觉得这种方法太浪费资源(占用了几十k的资源吧)。于是在‘求学问道’的途中,终于得到了比较完美的解决方法。

业务需求

环境:[codeigniter 3][3]

需求:在CI3中实现前后台的效果。例:

地址栏输入xxx.com默认访问前台主页,输入xxx.com/admin访问后台

所遇问题

依照惯例,我们会在框架中的config/route.php路由配置文件中配置我们的前后台访问路径:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12

// path => application/config/route.php



$route['admin'] = 'admin/admin'; //后台路径

$route['default_controller'] = 'home/home'; // 默认前台路径

$route['404_override'] = '';

$route['translate_uri_dashes'] = FALSE;

一般来说,我们这样配置是没问题的,但是有一个条件就是在CI3以下的版本中是没任何问题。但是目前的框架版本CI3,所以就会出现找不到资源文件的情况,空口无凭不算,下面是两张CI2CI3的route的配置图,和浏览器效果图:

CI2和CI3相同路由配置对比图:

CI2和CI3路由配置对

CI2和CI3相同路由配置运行网页对比图:

CI2和CI3网页展示对比

由上述两图可以看到,相同的路由配置下,但是结果却是不一样。因为CI3已经不支持设置子目录下的控制器为默认控制器的功能。但是要完成需求描述,这样的效果该如何实现呢?接下来看我们追踪CI3源码;

源码追踪core/Route.php

通过上面的结论,我们应该可以联想到出现404这样的报错,应该是解析default_controller的时候出现的问题,于是我在sublime中利用全文检索查询哪里有用到default_controller,搜索的范围可以假定为在CI核心目录中,因为路由的解析一般是由核心目录里的路由类完成的,于是查询范围锁定在system目录,得出下面的结果:

查询结果

锁定_set_default_controller方法

Route类中_set_default_controller方法

于是我一步一步排查,最终发现是这一段代码的问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22

// Is the method being specified?

if (sscanf($this->default_controller, '%[^/]/%s', $class, $method) !== 2)

{

	$method = 'index';

}



if ( ! file_exists(APPPATH.'controllers/'.$this->directory.ucfirst($class).'.php'))

{

	// This will trigger 404 later

	return;

}

上面的代码第一段if里面拆分我们在config/route.php里配置好的default_controller值得到控制器名并赋值给变量class方法名并赋值给method,如果method为空则默认为index,很显然这与我们的初衷不相符,因为我们的计划是default_controller里的值home/home,第一个home是目录名(floder_name),第二个home才是控制器名字(controller_name)

而第二段if的意思是判断控制器文件是否存在,排查也发现控制器名竟然不存在,打印

APPPATH . ‘controllers/’ . $this->directory . ucfirst($class) . ‘.php’

得到:E:\WWW\ci3\application\controllers/Home.php,这显然与我们的实际目录不相符,我们的实际目录应该是E:\WWW\ci3\application\controllers/home/Home.php,锁定这两个问题之后,就可以思考如何修正这里了,刚开始对这个地方的改动想法是这样的:

  1. 假定设置默认的控制器值为Home/home/index(目录名/控制器名/方法名)的形式

  2. 修改core/Route.php源码中的_set_default_controller方法,截取default_controller的值进行处理

修改_set_default_controller方法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

protected function _set_default_controller()

{

	if (empty($this->default_controller))

	{

		show_error('Unable to determine what should be displayed. A default route has not been specified in the routing file.');

	}



	/**

	 * if里为自己修改的部分

	 * 1.截取default_controller为数组

	 * 2.如果default_controller_arr大于3 表示是默认控制器过来的

	 * 3.赋值相应的变量

	 */

	$default_controller_arr = explode('/', $this->default_controller);

	if(count($default_controller_arr) == 3) {

		// 赋值控制器所在目录

		$this->directory = trim($default_controller_arr[0], '/') . '/';

		// 赋值控制器名

		$class = $default_controller_arr[1];

		// 因为这里计划约定默认控制器输入完整uri 即目录名/控制器名/方法名的形式

		// 所以方法名这里一定不为空

		$method = $default_controller_arr[2];



	}else {

		// Is the method being specified?

		if (sscanf($this->default_controller, '%[^/]/%s', $class, $method) !== 2)

		{

			$method = 'index';

		}

	}

	if ( ! file_exists(APPPATH.'controllers/'.$this->directory.ucfirst($class).'.php'))

	{

		// This will trigger 404 later

		return;

	}



	$this->set_class($class);

	$this->set_method($method);



	// Assign routed segments, index starting from 1

	$this->uri->rsegments = array(

		1 => $class,

		2 => $method

	);



	log_message('debug', 'No URI present. Default controller set.');

}

虽说这样修改测试成功了,但是觉得并不是最好的解决办法(修改源码一般是最后的解决手段),于是求助codeigniter中国的官方微信群的小伙伴,在群里和Hex(手动@Hex)老大讨论了一下这个功能的解决方案,最终在他的帮助下得到了比较完美的解决方法,就是要在application/core里新建一个自己的扩展路由类MY_Router.php,然后定义自己的_set_default_controller方法,代码如下,顺便贴上自己上面设想的解决方法:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220

<?php

defined('BASEPATH') OR exit('No direct script access allowed');



class MY_Router extends CI_Router {



	/**

	 * 个人认为比较完美解决问题的方法

	 */

    protected function _set_default_controller() {

         

        if (empty($this->default_controller)) {

            

            show_error('Unable to determine what should be displayed. A default route has not been specified in the routing file.');

        }



        if (sscanf($this->default_controller, '%[^/]/%s', $class, $method) !== 2) {

            $method = 'index';

        }



        /**

         * 1.判断目录是否存在

         * 2.如果存在 调用设置控制器目录方法 详细参考system/core/Route.php set_directory方法

         * 3.接着再把method拆分 赋值给$class $method $method为空则设置为index

         */

        if( is_dir(APPPATH.'controllers/'.$class) ) {

             

            // Set the class as the directory

            $this->set_directory($class);

            

            // $method is the class

            $class = $method;

            

            // Re check for slash if method has been set

            if (sscanf($method, '%[^/]/%s', $class, $method) !== 2) {

                $method = 'index';

            }

        }



        if ( ! file_exists(APPPATH.'controllers/'.$this->directory.ucfirst($class).'.php')) {

            // This will trigger 404 later

            return;

        }



        $this->set_class($class);

        $this->set_method($method);



        // Assign routed segments, index starting from 1

        $this->uri->rsegments = array(

            1 => $class,

            2 => $method

        );

        log_message('debug', 'No URI present. Default controller set.');

    }



    /**

     * @author 命中水、 

     * @date(2017-8-7)

     * 

     * 使用这个方法时 把这个方法名和上面的方法名调换一下 

     * application/config/route.php default_controller的值写uri全称(目录名/控制器名/方法名) 即可

     * 因为最终Route.php路由类库调用的还是_set_default_controller方法

     */

    protected function _set_default_controller_me() {

         

        if (empty($this->default_controller))

		{

			show_error('Unable to determine what should be displayed. A default route has not been specified in the routing file.');

		}



		/**

		 * if里为自己修改的部分

		 * 1.截取default_controller为数组

		 * 2.如果default_controller_arr大于3 表示是默认控制器过来的

		 * 3.赋值相应的变量

		 */

		$default_controller_arr = explode('/', $this->default_controller);

		if(count($default_controller_arr) == 3) {

			// 赋值控制器目录

			$this->directory = trim($default_controller_arr[0], '/') . '/';

			// 赋值控制器名

			$class  = $default_controller_arr[1];

			// 因为这里计划约定默认控制器输入完整uri 即目录名/控制器名/方法名的形式

			// 所以方法名这里一定不为空

			$method = $default_controller_arr[2];



		}else {

			if (sscanf($this->default_controller, '%[^/]/%s', $class, $method) !== 2)

			{

				$method = 'index';

			}

		}

		if ( ! file_exists(APPPATH.'controllers/'.$this->directory.ucfirst($class).'.php'))

		{

			// This will trigger 404 later

			return;

		}



		$this->set_class($class);

		$this->set_method($method);



		// Assign routed segments, index starting from 1

		$this->uri->rsegments = array(

			1 => $class,

			2 => $method

		);



		log_message('debug', 'No URI present. Default controller set.');

    }



}

以上代码比较完美的那个,亲测有效!!!(自己的这个简单测试了一下,也可以使用)

资源

参考文章

  1. [How to select default controller in subfolder?][8]

  2. [How to use a sub folder in default controller route in CodeIgniter 3][9]

资源

  1. [MY_Route.php][10]

  2. ,[CodeIgniter中国微信群][11]