forked from fecshop/yii2_fecshop
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathService.php
248 lines (218 loc) · 9.23 KB
/
Service.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
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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
<?php
/*
* FecShop file.
*
* @link http://www.fecshop.com/
* @copyright Copyright (c) 2016 FecShop Software LLC
* @license http://www.fecshop.com/license/
*/
namespace fecshop\services;
use Yii;
use yii\base\InvalidCallException;
use yii\base\InvalidConfigException;
use yii\base\BaseObject;
/**
* @author Terry Zhao <[email protected]>
* @since 1.0
*/
class Service extends BaseObject
{
public $childService;
public $enableService = true; // 该服务是否可用
protected $_childService;
protected $_beginCallTime;
protected $_beginCallCode;
protected $_callFuncLog;
public function __get($attr)
{
return $this->getChildService($attr);
}
/**
* 通过call函数,去调用actionXxxx方法。
*/
public function __call($originMethod, $arguments)
{
if (isset($this->_callFuncLog[$originMethod])) {
$method = $this->_callFuncLog[$originMethod];
} else {
$method = 'action'.ucfirst($originMethod);
$this->_callFuncLog[$originMethod] = $method;
}
if (method_exists($this, $method)) {
$this->beginCall($originMethod, $arguments);
$return = call_user_func_array([$this, $method], $arguments);
$this->endCall($originMethod, $arguments);
return $return;
} else {
throw new InvalidCallException('fecshop service method is not exit. '.get_class($this)."::$method");
}
}
/**
* 得到services 里面配置的子服务childService的实例.
*/
public function getChildService($childServiceName)
{
//var_dump($this->childService['xunSearch']);exit;
if (!isset($this->_childService[$childServiceName]) || !$this->_childService[$childServiceName]) {
$childService = $this->childService;
if (isset($childService[$childServiceName])) {
$service = $childService[$childServiceName];
if (!isset($service['enableService']) || $service['enableService'] !== false) {
$this->_childService[$childServiceName] = Yii::createObject($service);
} else {
throw new InvalidConfigException('Child Service ['.$childServiceName.'] is disable in '.get_called_class().', you must config it! ');
}
} else {
throw new InvalidConfigException('Child Service ['.$childServiceName.'] is not find in '.get_called_class().', you must config it! ');
}
}
return isset($this->_childService[$childServiceName]) ? $this->_childService[$childServiceName] : null;
}
/**
* 得到所有的子服务
* 如果子服务含有enableService字段,并且设置成false,则该子服务会被判定为关闭
*/
public function getAllChildServiceName()
{
$childService = $this->childService;
$arr = [];
if (is_array($childService) && !empty($childService)) {
foreach ($childService as $childName => $service) {
if ($service['enableService'] !== false) {
$arr[] = $childName;
}
}
}
return $arr;
}
/**
* 如果开启service log,则记录开始的时间。
*/
protected function beginCall($originMethod, $arguments)
{
if (Yii::$app->serviceLog->isServiceLogEnable()) {
$this->_beginCallTime = microtime(true);
}
}
/**
* @param $originMethod and $arguments,魔术方法传递的参数
* 调用service后,调用endCall,目前用来记录log信息
* 1. 如果service本身的调用,则不会记录,只会记录外部函数调用service
* 2. 同一次访问的service_uid 的值是一样的,这样可以把一次访问调用的serice找出来。
*/
protected function endCall($originMethod, $arguments)
{
if (Yii::$app->serviceLog->isServiceLogEnable()) {
list($logTrace, $isCalledByThis) = $this->debugBackTrace();
// if function is called by $this ,not log it to mongodb.
if ($isCalledByThis) {
return;
}
$begin_microtime = $this->_beginCallTime;
$endCallTime = microtime(true);
$used_time = round(($endCallTime - $begin_microtime), 4);
if (is_object($arguments)) {
$arguments = 'object';
} elseif (is_array($arguments)) {
$arguments = 'array';
} else {
$arguments = 'string or int or other';
}
$serviceLogUid = Yii::$app->serviceLog->getLogUid();
$log_info = [
'service_uid' => $serviceLogUid,
'current_url' => Yii::$service->url->getCurrentUrl(),
'home_url' => Yii::$service->url->homeUrl(),
'service_file' => get_class($this),
'service_method' => $originMethod,
'service_method_argument' => $arguments,
'begin_microtime' => $begin_microtime,
'end_microtime' => $endCallTime,
'used_time' => $used_time,
'process_date_time' => date('Y-m-d H:i:s'),
'log_trace' => $logTrace,
];
Yii::$app->serviceLog->printServiceLog($log_info);
}
}
/**
* debug 追踪
* 返回调用当前service的所有的文件。以及 行,类,类方法 。
* 这几个方法将不会被记录:'__call','endCall','debugBackTrace','call_user_func_array'
* 如果$file 不存在,也不会记录。
* @return Array, $arr里面存储执行的记录,$isCalledByThis 代表是否是当前的service内部方法调用,
* article 服务方法,在执行过程中,调用了一个内部的方法,追踪函数也会记录这个。
*/
protected function debugBackTrace()
{
$arr = [];
$isCalledByThis = false;
$d = debug_backtrace();
$funcNotContainArr = [
'__call', 'endCall', 'debugBackTrace', 'call_user_func_array',
];
$thisClass = get_class($this);
//echo '**'.$thisClass.'**';
$i = 0;
$last_invoke_class = '';
$last_sec_invoke_class = '';
foreach ($d as $e) {
$function = $e['function'];
$class = $e['class'];
//echo '**'.$class.'**';
$file = $e['file'];
$line = $e['line'];
if ($file && !in_array($function, $funcNotContainArr)) {
$arr[] = $file.'('.$line.'),'.$class.'::'.$function.'()';
$i++;
if ($i === 1) {
$last_invoke_class = $class;
} elseif ($i === 2) {
$last_sec_invoke_class = $class;
}
}
}
if ($last_invoke_class === $last_sec_invoke_class) {
$isCalledByThis = true;
}
return [$arr, $isCalledByThis];
}
/**
* @param $object | Object , 调用该函数的对象
* 注意:
* 1. $object 必须存在属性storage,否则将会报错
* 2. 根据该函数得到相应的Storage,该文件必须存在并设置好相应的namespace,否则将报错
* 作用:
* 作为services,同一个功能的实现,我们可能使用多种实现方式,譬如
* search功能的实现,我们可以使用mysql,也可以使用mongodb,
* 产品搜索,可以使用mongodb,也可以使用xunsearch,elasticSearch等
* 因此一个功能可以有多种实现,我们通过设置$object->storage 来进行切换各种实现方式。
* 譬如 searchStorage有2种,\fecshop\services\search\MongoSearch 和 \fecshop\services\search\XunSearch
* 使用该函数返回相应的storage类,类似工厂的方式,易于后续的扩展。
* 举例:
* 在@fecshop\services\Product.php 这个类中设置类变量 $storage = 'ProductMongodb';
* 那么调用该函数返回的字符串为:'\fecshop\services\product\'+$storage,
* 最终函数返回值为:\fecshop\services\product\ProductMongodb
* 感谢:
* @dionyang 提的建议:http://www.fecshop.com/topic/281
*/
public function getStorageService($object)
{
$className = get_class($object);
if (!isset($object->storage) || !$object->storage) {
throw new InvalidConfigException('you must config class var $storage in '.$className);
return false;
}
if ($object->storagePath) {
$storagePath = '\\'.trim($object->storagePath, '\\').'\\';
} else {
$storagePath = '\\'.strtolower($className).'\\';
}
$storageServiceClass = $storagePath.ucfirst($object->storage);
if (!class_exists($storageServiceClass)) {
throw new InvalidCallException('class ['.$storageServiceClass.'] is not exist , you must add the class before you use it');
return false;
}
return $storageServiceClass;
}
}