* @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; } }