Service.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. <?php
  2. /*
  3. * FecShop file.
  4. *
  5. * @link http://www.fecshop.com/
  6. * @copyright Copyright (c) 2016 FecShop Software LLC
  7. * @license http://www.fecshop.com/license/
  8. */
  9. namespace fecshop\services;
  10. use Yii;
  11. use yii\base\InvalidCallException;
  12. use yii\base\InvalidConfigException;
  13. use yii\base\BaseObject;
  14. /**
  15. * @author Terry Zhao <2358269014@qq.com>
  16. * @since 1.0
  17. */
  18. class Service extends BaseObject
  19. {
  20. public $childService;
  21. public $enableService = true; // 该服务是否可用
  22. protected $_childService;
  23. protected $_beginCallTime;
  24. protected $_beginCallCode;
  25. protected $_callFuncLog;
  26. public function __get($attr)
  27. {
  28. return $this->getChildService($attr);
  29. }
  30. /**
  31. * 通过call函数,去调用actionXxxx方法。
  32. */
  33. public function __call($originMethod, $arguments)
  34. {
  35. if (isset($this->_callFuncLog[$originMethod])) {
  36. $method = $this->_callFuncLog[$originMethod];
  37. } else {
  38. $method = 'action'.ucfirst($originMethod);
  39. $this->_callFuncLog[$originMethod] = $method;
  40. }
  41. if (method_exists($this, $method)) {
  42. $this->beginCall($originMethod, $arguments);
  43. $return = call_user_func_array([$this, $method], $arguments);
  44. $this->endCall($originMethod, $arguments);
  45. return $return;
  46. } else {
  47. throw new InvalidCallException('fecshop service method is not exit. '.get_class($this)."::$method");
  48. }
  49. }
  50. /**
  51. * 得到services 里面配置的子服务childService的实例.
  52. */
  53. public function getChildService($childServiceName)
  54. {
  55. //var_dump($this->childService['xunSearch']);exit;
  56. if (!isset($this->_childService[$childServiceName]) || !$this->_childService[$childServiceName]) {
  57. $childService = $this->childService;
  58. if (isset($childService[$childServiceName])) {
  59. $service = $childService[$childServiceName];
  60. if (!isset($service['enableService']) || $service['enableService'] !== false) {
  61. $this->_childService[$childServiceName] = Yii::createObject($service);
  62. } else {
  63. throw new InvalidConfigException('Child Service ['.$childServiceName.'] is disable in '.get_called_class().', you must config it! ');
  64. }
  65. } else {
  66. throw new InvalidConfigException('Child Service ['.$childServiceName.'] is not find in '.get_called_class().', you must config it! ');
  67. }
  68. }
  69. return isset($this->_childService[$childServiceName]) ? $this->_childService[$childServiceName] : null;
  70. }
  71. /**
  72. * 得到所有的子服务
  73. * 如果子服务含有enableService字段,并且设置成false,则该子服务会被判定为关闭
  74. */
  75. public function getAllChildServiceName()
  76. {
  77. $childService = $this->childService;
  78. $arr = [];
  79. if (is_array($childService) && !empty($childService)) {
  80. foreach ($childService as $childName => $service) {
  81. if ($service['enableService'] !== false) {
  82. $arr[] = $childName;
  83. }
  84. }
  85. }
  86. return $arr;
  87. }
  88. /**
  89. * 如果开启service log,则记录开始的时间。
  90. */
  91. protected function beginCall($originMethod, $arguments)
  92. {
  93. if (Yii::$app->serviceLog->isServiceLogEnable()) {
  94. $this->_beginCallTime = microtime(true);
  95. }
  96. }
  97. /**
  98. * @param $originMethod and $arguments,魔术方法传递的参数
  99. * 调用service后,调用endCall,目前用来记录log信息
  100. * 1. 如果service本身的调用,则不会记录,只会记录外部函数调用service
  101. * 2. 同一次访问的service_uid 的值是一样的,这样可以把一次访问调用的serice找出来。
  102. */
  103. protected function endCall($originMethod, $arguments)
  104. {
  105. if (Yii::$app->serviceLog->isServiceLogEnable()) {
  106. list($logTrace, $isCalledByThis) = $this->debugBackTrace();
  107. // if function is called by $this ,not log it to mongodb.
  108. if ($isCalledByThis) {
  109. return;
  110. }
  111. $begin_microtime = $this->_beginCallTime;
  112. $endCallTime = microtime(true);
  113. $used_time = round(($endCallTime - $begin_microtime), 4);
  114. if (is_object($arguments)) {
  115. $arguments = 'object';
  116. } elseif (is_array($arguments)) {
  117. $arguments = 'array';
  118. } else {
  119. $arguments = 'string or int or other';
  120. }
  121. $serviceLogUid = Yii::$app->serviceLog->getLogUid();
  122. $log_info = [
  123. 'service_uid' => $serviceLogUid,
  124. 'current_url' => Yii::$service->url->getCurrentUrl(),
  125. 'home_url' => Yii::$service->url->homeUrl(),
  126. 'service_file' => get_class($this),
  127. 'service_method' => $originMethod,
  128. 'service_method_argument' => $arguments,
  129. 'begin_microtime' => $begin_microtime,
  130. 'end_microtime' => $endCallTime,
  131. 'used_time' => $used_time,
  132. 'process_date_time' => date('Y-m-d H:i:s'),
  133. 'log_trace' => $logTrace,
  134. ];
  135. Yii::$app->serviceLog->printServiceLog($log_info);
  136. }
  137. }
  138. /**
  139. * debug 追踪
  140. * 返回调用当前service的所有的文件。以及 行,类,类方法 。
  141. * 这几个方法将不会被记录:'__call','endCall','debugBackTrace','call_user_func_array'
  142. * 如果$file 不存在,也不会记录。
  143. * @return Array, $arr里面存储执行的记录,$isCalledByThis 代表是否是当前的service内部方法调用,
  144. * article 服务方法,在执行过程中,调用了一个内部的方法,追踪函数也会记录这个。
  145. */
  146. protected function debugBackTrace()
  147. {
  148. $arr = [];
  149. $isCalledByThis = false;
  150. $d = debug_backtrace();
  151. $funcNotContainArr = [
  152. '__call', 'endCall', 'debugBackTrace', 'call_user_func_array',
  153. ];
  154. $thisClass = get_class($this);
  155. //echo '**'.$thisClass.'**';
  156. $i = 0;
  157. $last_invoke_class = '';
  158. $last_sec_invoke_class = '';
  159. foreach ($d as $e) {
  160. $function = $e['function'];
  161. $class = $e['class'];
  162. //echo '**'.$class.'**';
  163. $file = $e['file'];
  164. $line = $e['line'];
  165. if ($file && !in_array($function, $funcNotContainArr)) {
  166. $arr[] = $file.'('.$line.'),'.$class.'::'.$function.'()';
  167. $i++;
  168. if ($i === 1) {
  169. $last_invoke_class = $class;
  170. } elseif ($i === 2) {
  171. $last_sec_invoke_class = $class;
  172. }
  173. }
  174. }
  175. if ($last_invoke_class === $last_sec_invoke_class) {
  176. $isCalledByThis = true;
  177. }
  178. return [$arr, $isCalledByThis];
  179. }
  180. /**
  181. * @param $object | Object , 调用该函数的对象
  182. * 注意:
  183. * 1. $object 必须存在属性storage,否则将会报错
  184. * 2. 根据该函数得到相应的Storage,该文件必须存在并设置好相应的namespace,否则将报错
  185. * 作用:
  186. * 作为services,同一个功能的实现,我们可能使用多种实现方式,譬如
  187. * search功能的实现,我们可以使用mysql,也可以使用mongodb,
  188. * 产品搜索,可以使用mongodb,也可以使用xunsearch,elasticSearch等
  189. * 因此一个功能可以有多种实现,我们通过设置$object->storage 来进行切换各种实现方式。
  190. * 譬如 searchStorage有2种,\fecshop\services\search\MongoSearch 和 \fecshop\services\search\XunSearch
  191. * 使用该函数返回相应的storage类,类似工厂的方式,易于后续的扩展。
  192. * 举例:
  193. * 在@fecshop\services\Product.php 这个类中设置类变量 $storage = 'ProductMongodb';
  194. * 那么调用该函数返回的字符串为:'\fecshop\services\product\'+$storage,
  195. * 最终函数返回值为:\fecshop\services\product\ProductMongodb
  196. * 感谢:
  197. * @dionyang 提的建议:http://www.fecshop.com/topic/281
  198. */
  199. public function getStorageService($object)
  200. {
  201. $className = get_class($object);
  202. if (!isset($object->storage) || !$object->storage) {
  203. throw new InvalidConfigException('you must config class var $storage in '.$className);
  204. return false;
  205. }
  206. if ($object->storagePath) {
  207. $storagePath = '\\'.trim($object->storagePath, '\\').'\\';
  208. } else {
  209. $storagePath = '\\'.strtolower($className).'\\';
  210. }
  211. $storageServiceClass = $storagePath.ucfirst($object->storage);
  212. if (!class_exists($storageServiceClass)) {
  213. throw new InvalidCallException('class ['.$storageServiceClass.'] is not exist , you must add the class before you use it');
  214. return false;
  215. }
  216. return $storageServiceClass;
  217. }
  218. }