Widget.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  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\page;
  10. use fec\helpers\CCache;
  11. use fecshop\interfaces\block\BlockCache;
  12. use fecshop\services\Service;
  13. use Yii;
  14. use yii\base\InvalidConfigException;
  15. use yii\base\InvalidValueException;
  16. /**
  17. * Page widget services.
  18. * @author Terry Zhao <2358269014@qq.com>
  19. * @since 1.0
  20. */
  21. class Widget extends Service
  22. {
  23. public $defaultObMethod = 'getLastData';
  24. public $widgetConfig;
  25. /**
  26. * @param configKey String or Array
  27. * 如果传递的是一个配置数组,内容格式如下:
  28. * [
  29. * # class 选填
  30. * 'class' => 'fec\block\TestMenu',
  31. * # view 为 必填 , view可以用两种方式
  32. * # view 1 使用绝对地址的方式
  33. * 'view' => '@fec/views/testmenu/index.php',
  34. * OR
  35. * # view 2 使用相对地址,通过当前模板进行查找
  36. * 'view' => 'cms/home/index.php',
  37. *
  38. * # 下面为选填
  39. * 'method'=> 'getLastData',
  40. * 'terry1'=> 'My1',
  41. * 'terry2'=> 'My2',
  42. * ]
  43. * 如果传递的是字符串,那么会去配置($widgetConfig)中查找,譬如 category/filter_price ,对应 $widgetConfig['category']['filter_price'] 的配置
  44. * 最后找到后,通过renderContent函数,得到html
  45. * 该功能大致为通过一个动态数据提供者block,和内容显示部分view,view里面需要使用的动态变量
  46. * 由block提供,最终生成一个html区块,返回。
  47. * @param $parentThis 外部传递的数组变量,作为变量,可以在view中直接使用
  48. */
  49. protected function actionRender($configKey, $parentThis = '')
  50. {
  51. $config = '';
  52. if (is_array($configKey)) {
  53. $config = $configKey;
  54. $configKey = '';
  55. } else if ($configKey){
  56. $configArr = explode('/', $configKey);
  57. if (count($configArr) < 2 ) {
  58. throw new InvalidValueException(" config key: '$configKey', format: `xxxx/xxxx`, you must config it with correct format");
  59. }
  60. if (isset($this->widgetConfig[$configArr[0]][$configArr[1]])) {
  61. $config = $this->widgetConfig[$configArr[0]][$configArr[1]];
  62. } else {
  63. throw new InvalidValueException(" config key: '$configKey', can not find in ".'Yii::$service->page->widget->widgetConfig'.', you must config it before use it.');
  64. }
  65. }
  66. return $this->renderContent($configKey, $config, $parentThis);
  67. }
  68. /**
  69. * @param configKey String or Array
  70. * 如果传递的是一个配置数组,内容格式如下:
  71. * [
  72. * # class 选填
  73. * 'class' => 'fec\block\TestMenu',
  74. * # view 为 必填 , view可以用两种方式
  75. * # view 1 使用绝对地址的方式
  76. * 'view' => '@fec/views/testmenu/index.php',
  77. * OR
  78. * # view 2 使用相对地址,通过当前模板进行查找
  79. * 'view' => 'cms/home/index.php',
  80. *
  81. * # 下面为选填
  82. * 'method'=> 'getLastData',
  83. * 'terry1'=> 'My1',
  84. * 'terry2'=> 'My2',
  85. * ]
  86. * 如果传递的是字符串,那么会去配置($widgetConfig)中查找,譬如 category/filter_price ,对应 $widgetConfig['category']['filter_price'] 的配置
  87. * 最后找到后,通过renderContent函数,得到html
  88. * 该功能大致为通过一个动态数据提供者block,和内容显示部分view,view里面需要使用的动态变量
  89. * 由block提供,最终生成一个html区块,返回。
  90. * @param $diConfig | array 数组变量,会注入$configKey中class对应的类变量
  91. */
  92. protected function actionDiRender($configKey, $diConfig = [])
  93. {
  94. if (!is_array($diConfig)) {
  95. throw new InvalidValueException(" configParent: '$diConfig' must be array");
  96. }
  97. //
  98. $config = '';
  99. if (is_array($configKey)) {
  100. $config = $configKey;
  101. $configKey = '';
  102. } else if ($configKey){
  103. $configArr = explode('/', $configKey);
  104. if (count($configArr) < 2 ) {
  105. throw new InvalidValueException(" config key: '$configKey', format: `xxxx/xxxx`, you must config it with correct format");
  106. }
  107. if (isset($this->widgetConfig[$configArr[0]][$configArr[1]])) {
  108. $config = $this->widgetConfig[$configArr[0]][$configArr[1]];
  109. } else {
  110. throw new InvalidValueException(" config key: '$configKey', can not find in ".'Yii::$service->page->widget->widgetConfig'.', you must config it before use it.');
  111. }
  112. }
  113. if (!isset($config['class']) || !$config['class']) {
  114. throw new InvalidConfigException('in widget ['.$configKey.'],you enable cache ,you must config widget class .');
  115. }
  116. foreach ($diConfig as $k=>$v) {
  117. $config [$k] = $v;
  118. }
  119. return $this->renderContent($configKey, $config);
  120. }
  121. /**
  122. * @param $configKey | string ,使用配置中的widget,该参数对应相应的数组key
  123. * @param $config,就是上面actionRender()方法中的参数,格式一样。
  124. * @param $parentThis | array or '' , 调用层传递的参数数组,可以在view中调用。
  125. *
  126. */
  127. protected function actionRenderContentHtml($configKey, $config, $parentThis = '')
  128. {
  129. if (!isset($config['view']) || empty($config['view'])
  130. ) {
  131. throw new InvalidConfigException('view and class must exist in array config!');
  132. }
  133. $params = [];
  134. $view = $config['view'];
  135. unset($config['view']);
  136. $viewFile = $this->getViewFile($view);
  137. if (!isset($config['class']) || empty($config['class'])) {
  138. if ($parentThis) {
  139. $params['parentThis'] = $parentThis;
  140. }
  141. return Yii::$app->view->renderFile($viewFile, $params);
  142. }
  143. if (isset($config['method']) && !empty($config['method'])) {
  144. $method = $config['method'];
  145. unset($config['method']);
  146. } else {
  147. $method = $this->defaultObMethod;
  148. }
  149. $ob = Yii::createObject($config);
  150. $params = $ob->$method();
  151. if ($parentThis) {
  152. $params['parentThis'] = $parentThis;
  153. }
  154. return Yii::$app->view->renderFile($viewFile, $params);
  155. }
  156. public $_cache_arr = [
  157. 'head' => 'headBlockCache',
  158. 'header' => 'headerBlockCache',
  159. 'menu' => 'menuBlockCache',
  160. 'footer' => 'footerBlockCache',
  161. ];
  162. /**
  163. * @param $configKey | string , 标记,以及报错排查时使用的key。
  164. * @param $config,就是上面actionRender()方法中的参数,格式一样。
  165. * @param $parentThis | array or '' , 调用层传递的参数数组,可以在view中调用。
  166. *
  167. */
  168. protected function actionRenderContent($configKey, $config, $parentThis = '')
  169. {
  170. // 从配置中读取cache的enable状态
  171. $cacheEnable = false;
  172. $cacheConfigKey = $this->_cache_arr[$configKey];
  173. $appName = Yii::$service->helper->getAppName();
  174. $cacheConfig = Yii::$app->store->get($appName.'_cache');
  175. if ($cacheConfigKey && isset($cacheConfig[$cacheConfigKey]) && $cacheConfig[$cacheConfigKey] == Yii::$app->store->enable) {
  176. $cacheEnable = true;
  177. }
  178. if ($cacheEnable) {
  179. if (!isset($config['class']) || !$config['class']) {
  180. throw new InvalidConfigException('in widget ['.$configKey.'],you enable cache ,you must config widget class .');
  181. } elseif ($ob = new $config['class']()) {
  182. if ($ob instanceof BlockCache) {
  183. $cacheKey = $ob->getCacheKey();
  184. if (!($content = Yii::$app->cache->get($cacheKey))) {
  185. $cache = $config['cache'];
  186. $timeout = isset($cache['timeout']) ? $cache['timeout'] : 0;
  187. unset($config['cache']);
  188. $content = $this->renderContentHtml($configKey, $config, $parentThis);
  189. Yii::$app->cache->set($cacheKey, $content, $timeout);
  190. }
  191. return $content;
  192. } else {
  193. throw new InvalidConfigException($config['class'].' must implete fecshop\interfaces\block\BlockCache when you use block cache .');
  194. }
  195. }
  196. }
  197. // 查看 $config['class'] 是否在YiiRewriteMap重写中存在配置,如果存在,则替换
  198. !isset($config['class']) || $config['class'] = Yii::mapGetClassName($config['class']);
  199. $content = $this->renderContentHtml($configKey, $config, $parentThis);
  200. return $content;
  201. }
  202. /**
  203. * find theme file by mutil theme ,if not find view file and $throwError=true, it will throw InvalidValueException.
  204. */
  205. protected function getViewFile($view, $throwError = true)
  206. {
  207. $view = trim($view);
  208. if (substr($view, 0, 1) == '@') {
  209. return Yii::getAlias($view);
  210. }
  211. $absoluteDir = Yii::$service->page->theme->getThemeDirArr();
  212. foreach ($absoluteDir as $dir) {
  213. if ($dir) {
  214. $file = $dir.'/'.$view;
  215. //echo $file."<br/>";
  216. if (file_exists($file)) {
  217. return $file;
  218. }
  219. }
  220. }
  221. // not find view file
  222. if ($throwError) {
  223. $notExistFile = [];
  224. foreach ($absoluteDir as $dir) {
  225. if ($dir) {
  226. $file = $dir.'/'.$view;
  227. $notExistFile[] = $file;
  228. }
  229. }
  230. throw new InvalidValueException('view file is not exist in'.implode(',', $notExistFile));
  231. } else {
  232. return false;
  233. }
  234. }
  235. }