异常处理

异常处理其实是我一直很少接触的,之前是因为异常处理相比较于php的基础知识更高一层,大学时就有个习惯,越到后面的内容看的越少,比如c语言的指针,每学期的专业课,都是只能讲完前面的语法部分,运算符?if?else?for循环?后面就没了。

但当我学习tp5的时候,发现tp5的所有错误都会以异常的形式抛出,然后在7月老师的指导下,认识到了AOP思想,了解了统一异常处理类的时候,我才逐渐重视了这个东西。

新项目中:因为我希望把每个函数写的尽量短些,虽然以后追踪问题的时候可能会跳来跳去,但如果单单只靠函数的名称就能了解到函数的作用,这将是多么美好的一件事,于是产生了一个问题:一般只有最外层的函数才能决定最终抛给客户端的内容,但如果我们在内部自己调用的函数中也这么写,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function father()
{
$res = $this->children();
return [
'code' => 200,
'msg' => $res
]
}

public function children()
{
return [
'code' => 500,
'msg' => '你有毒'
]
}

这个样子返回的结果肯定不是我们希望的,你可以说我可以处理啊,但其实如果层次深了之类的,处理是很麻烦的,如果我能立刻把结果给到前端,那该多棒啊,这时候就可以通过抛出异常,最外层统一对异常进行处理,比如抛出异常的时候

1
throw new ControllerException(404, 'controller not exist.');

这个ControllerException 是继承于基本的\Exception,php自带的。然后我们在最外层进行捕获

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
<?php
date_default_timezone_set('Asia/Shanghai');
include_once('vendor/autoload.php');
include_once './Libs/Eagles/Document.php';

define('SWOOLE_PORT', 56732);
define('SWOOLE_ERROR_LOG', '/var/log/swoole_error.log');

define('EAGLES_SERVER', '211.144.114.26');
define('EAGLES_PORT', '17200');
// error_reporting(0);



// http服务的配置
$http = new swoole_http_server("0.0.0.0", SWOOLE_PORT);
$http->set([
'worker_num' => 20, // cpu 6核 默认启动6个, 1-4 倍合适 占用:40M内存*20
// 'task_worker_num' => 6, //开启task功能
// 'daemonize' => true, // 守护进程
'log_file' => SWOOLE_ERROR_LOG,
'buffer_output_size' => 8 * 1024 *1024, // 8M 缓冲区。占用 8*12M,api接口可最大返回8M的数据内容
'package_max_length'=> 5 * 1024 * 1024, // 最大数据块是5M。上传可上传4M大小文件
// 配置静态文件根目录
// 'enable_static_handler' => true,
// 'document_root' => '/home/projectx/server/upload'
// 'daemonize' => true,
// 'pid_file' => __DIR__.'/server.pid'
]);

use Framework\Base\Route as Router;
use Framework\DB\ConnectionManager as ConnectionManager;
use Framework\Log\Logger as Logger;

$config = include_once('config/config.php');
Logger::logfile($config['logfile']);

ConnectionManager::db_config($config['db']);

$http->on('request', function ($request, $response) {
try{
if ($request->server['path_info'] == '/favicon.ico'
|| $request->server['request_uri'] == '/favicon.ico') {
return $response->end();
}

$param = $request->server['request_method'] == 'POST' ? @$request->post : @$request->get;
$data = \Libs\Common::getCallData(@$request->header['www-authorization'], $request->header['host'], $request->server['request_uri'], $param, $request->server['remote_addr'], $request->server['request_method']);
//\Libs\ElasticSearch::setIndexTableDoc('projectx_log', 'call_log', $data['doc_id'], $data['data']);

$allow_method = ['POST', 'GET', 'OPTIONS'];
if(!in_array($request->server['request_method'], $allow_method)){
$response->status(405);
return $response->end();
}

//如果需要设置允许所有域名发起的跨域请求,可以使用通配符 *
$response->header("Access-Control-Allow-Origin", '*');
$response->header('Access-Control-Allow-Methods', 'POST, GET');
$response->header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, www-authorization');

$uri = $request->server['request_uri'];
$uri = $uri;
$res = Router::dispatch($uri, $request, $response);

@$response->header('Content-Type', 'application/json');
@$response->end(json_encode($res));
}catch(\Framework\Exception\ControllerException $ce){
$status = $ce->getStatusCode();
$response->status($status);

$errorcode = $ce->getErrorCode();
$msg = $ce->getMsg();
$response->header("Access-Control-Allow-Origin", '*');
$response->header('Content-Type', 'application/json');
$response->end(json_encode(['code'=>$errorcode, 'msg'=>$msg]));
}catch(\Framework\Exception\RouteException $re){
$status = $re->getStatusCode();
@$response->header("Access-Control-Allow-Origin", '*');
@$response->status($status);
@$response->end();
}catch(Exception $e){
@$response->header("Access-Control-Allow-Origin", '*');
@$response->status(500);
@$response->end('exceptions:' . $e->getMessage());
}
});

echo "server start listening port:" . SWOOLE_PORT . PHP_EOL;
$http->start();

看见了嘛,最外层的几个catch就是对他的捕获,注意最大的要写在外面哦,因为前面的如果捕获了,后面的就不会处理了呢。

其实通过上面的例子我们还能了解到其实一个php项目说到底还是一个脚本,上面代码是我用swoole搭建的webserver时候的启动脚本,其实用swoole能更好的帮助phper理解web是怎么样运行的,当服务器监听到request过来的时候,我们通过这个

1
Router::dispatch($uri, $request, $response);

去执行对应的项目里面的代码,这就是我们平时经常写的工程,大部分用的是面向对象的思想,定义一个类啊,方法啊,因为这里面就像一个仓库,是我们用来取东西的,所以没有那种流程的概念,唯一的流程概念应该在我们这个启动脚本里,这也是我们把try catch 写在这里面的一个原因(这也是我找php统一异常处理的方法应该定义在哪的时候发现的),后来继续思考了下,感觉不管是我之前做的那个itbasic,还是现在这个bbs,其实都是脚本+类库合在一起的,因为很多时候我们往往都是在写类库,而忽略了脚本的存在(一般都是写好的,很少需要改),甚至把一个项目就当做一个面向对象,而忘记了面向过程的存在,之前群里面有个朋友不知道在哪定义通过哪个搜素引擎查找到自己网站的网上百度的方法,根源也是因为不理解一个项目的脚本到底存在于什么地方。

其实类比自己接触的两个框架,tp和laravel,为嘛他的入口文件index.php和路由文件分开了呢,:sweat: 其实本该分开,路由是个类库,入口文件是个脚本呢,之所以产生这种坑爹的想法是itbasic的route.php即是入口文件,也是路由类定义的地方。

观察server.php, 观察config目录,发现set.php 是定义的常量,这样在使用的时候可以直接使用,但是config.php 是返回的数组,项目里面想用这里面的值是不可以直接使用的,那数据库是怎么连接的呢,发现

1
ConnectionManager::db_config($config['db']);

这个类和普通的类不一样,这个直接在入口文件中就有加载,这个是把config里面的值保存到他的类变量中了,所以在项目里面可以直接使用。

还有上次那个exception,其实子类继承了父类,父类中变量和子类中变量重名,但他们是各自的,子类继承了父类,用了父类获取自己errorcode的方法,那个其实是获取父类的,而并非是获取子类的,这点要注意。

子类继承父类的初始化函数的时候不要忘记传参数了哦。

wait:

1.异常处理的细节

2.还记得之前在学习aop思想的时候,觉得既然在最外层套一个比如index.php文件那块 try catch 就能捕获所有的异常(异常在throw 的时候如果没有捕获就会继续往上抛出,可以理解成类似冒泡那样,层层函数传递出来),那为啥看见别人在代码中还是要写 try catch呢,其实是为了立刻捕获异常,让程序还能往下执行,如果我们throw了一个异常,没有捕获,而是在最外层的index.php捕获,那么代码会立刻在throw的地方终止,这可能是我们不想的结果,比如在一个foreach循环处理数据的过程中,其中一条数据处理有问题,我们只需要把对应结果置为空或者记录下来后面再分析就好了,如果停掉程序真的没必要,也不太合适。这时候我们就可以 try catch 代码块,catch中处理异常,千万注意不要 return~也不要再次throw,这两个都会让代码停止执行

notice: 所以有时候我们看到代码 try catch ,catch块是空的时候不要觉得没意义(之前表哥diss过),哪怕是空的不写,也能让程序运行下去

1
2
3
4
5
6
7
8
9
10
11
12
foreach ([1,2,4] as $value) {

try {
throw new \Exception('11');

} catch(Exception $e) {
var_dump('handle');
}
var_dump('haha');
}

exit;

3.自定义异常处理

php手册中有对异常处理重写的详细讲解,重写的exception 都必须继承到php原本自带的exception上面,所以我们在用扩展包的时候,如果偷懒的话只需要捕获php最现实的exception就好了,但是为啥看到别人的代码块中经常套了好几层catch 呢,这是因为exception 的 code, 和 errorMsg 都是 protected 的,也就是子类不能重写,然后getCode 和 getMessage都是final,也不能重写,所以我们虽然能捕获exception,但是不能控制输出的code 和message 内容(我们在调用任何包的时候,本质上还是调用php代码,比如guzzle 封装的http,可能他本质上就是调用的curl库或者file_get_contents库,对于400的http返回,原生的exception 直接把 状态码这些全都封装在了 errorMessage 中,这是我们不想要的,所以我们才会在原生exception 的上层补上自定义的exception,我们可以在自定义的exception ,自己封装想要的errMsg,比如上面的那个例子中去掉400状态码,然后捕获这个异常,再输出这个errMsg)

4.另一类的异常(itbasic的数据库查询出错,不抛出异常,而是以errorMsg这个私有变量来提示,参考go的异常)

坚持原创技术分享,您的支持将鼓励我继续创作!
-------------本文结束感谢您的阅读-------------