123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\base;
  8. use Yii;
  9. use ReflectionClass;
  10. /**
  11. * Widget is the base class for widgets.
  12. *
  13. * @property string $id ID of the widget.
  14. * @property \yii\web\View $view The view object that can be used to render views or view files. Note that the
  15. * type of this property differs in getter and setter. See [[getView()]] and [[setView()]] for details.
  16. * @property string $viewPath The directory containing the view files for this widget. This property is
  17. * read-only.
  18. *
  19. * @author Qiang Xue <qiang.xue@gmail.com>
  20. * @since 2.0
  21. */
  22. class Widget extends Component implements ViewContextInterface
  23. {
  24. /**
  25. * @var integer a counter used to generate [[id]] for widgets.
  26. * @internal
  27. */
  28. public static $counter = 0;
  29. /**
  30. * @var string the prefix to the automatically generated widget IDs.
  31. * @see getId()
  32. */
  33. public static $autoIdPrefix = 'w';
  34. /**
  35. * @var Widget[] the widgets that are currently being rendered (not ended). This property
  36. * is maintained by [[begin()]] and [[end()]] methods.
  37. * @internal
  38. */
  39. public static $stack = [];
  40. /**
  41. * Begins a widget.
  42. * This method creates an instance of the calling class. It will apply the configuration
  43. * to the created instance. A matching [[end()]] call should be called later.
  44. * As some widgets may use output buffering, the [[end()]] call should be made in the same view
  45. * to avoid breaking the nesting of output buffers.
  46. * @param array $config name-value pairs that will be used to initialize the object properties
  47. * @return static the newly created widget instance
  48. * @see end()
  49. */
  50. public static function begin($config = [])
  51. {
  52. $config['class'] = get_called_class();
  53. /* @var $widget Widget */
  54. $widget = Yii::createObject($config);
  55. static::$stack[] = $widget;
  56. return $widget;
  57. }
  58. /**
  59. * Ends a widget.
  60. * Note that the rendering result of the widget is directly echoed out.
  61. * @return static the widget instance that is ended.
  62. * @throws InvalidCallException if [[begin()]] and [[end()]] calls are not properly nested
  63. * @see begin()
  64. */
  65. public static function end()
  66. {
  67. if (!empty(static::$stack)) {
  68. $widget = array_pop(static::$stack);
  69. if (get_class($widget) === get_called_class()) {
  70. echo $widget->run();
  71. return $widget;
  72. } else {
  73. throw new InvalidCallException('Expecting end() of ' . get_class($widget) . ', found ' . get_called_class());
  74. }
  75. } else {
  76. throw new InvalidCallException('Unexpected ' . get_called_class() . '::end() call. A matching begin() is not found.');
  77. }
  78. }
  79. /**
  80. * Creates a widget instance and runs it.
  81. * The widget rendering result is returned by this method.
  82. * @param array $config name-value pairs that will be used to initialize the object properties
  83. * @return string the rendering result of the widget.
  84. * @throws \Exception
  85. */
  86. public static function widget($config = [])
  87. {
  88. ob_start();
  89. ob_implicit_flush(false);
  90. try {
  91. /* @var $widget Widget */
  92. $config['class'] = get_called_class();
  93. $widget = Yii::createObject($config);
  94. $out = $widget->run();
  95. } catch (\Exception $e) {
  96. // close the output buffer opened above if it has not been closed already
  97. if (ob_get_level() > 0) {
  98. ob_end_clean();
  99. }
  100. throw $e;
  101. }
  102. return ob_get_clean() . $out;
  103. }
  104. private $_id;
  105. /**
  106. * Returns the ID of the widget.
  107. * @param boolean $autoGenerate whether to generate an ID if it is not set previously
  108. * @return string ID of the widget.
  109. */
  110. public function getId($autoGenerate = true)
  111. {
  112. if ($autoGenerate && $this->_id === null) {
  113. $this->_id = static::$autoIdPrefix . static::$counter++;
  114. }
  115. return $this->_id;
  116. }
  117. /**
  118. * Sets the ID of the widget.
  119. * @param string $value id of the widget.
  120. */
  121. public function setId($value)
  122. {
  123. $this->_id = $value;
  124. }
  125. private $_view;
  126. /**
  127. * Returns the view object that can be used to render views or view files.
  128. * The [[render()]] and [[renderFile()]] methods will use
  129. * this view object to implement the actual view rendering.
  130. * If not set, it will default to the "view" application component.
  131. * @return \yii\web\View the view object that can be used to render views or view files.
  132. */
  133. public function getView()
  134. {
  135. if ($this->_view === null) {
  136. $this->_view = Yii::$app->getView();
  137. }
  138. return $this->_view;
  139. }
  140. /**
  141. * Sets the view object to be used by this widget.
  142. * @param View $view the view object that can be used to render views or view files.
  143. */
  144. public function setView($view)
  145. {
  146. $this->_view = $view;
  147. }
  148. /**
  149. * Executes the widget.
  150. * @return string the result of widget execution to be outputted.
  151. */
  152. public function run()
  153. {
  154. }
  155. /**
  156. * Renders a view.
  157. * The view to be rendered can be specified in one of the following formats:
  158. *
  159. * - path alias (e.g. "@app/views/site/index");
  160. * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
  161. * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
  162. * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
  163. * The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
  164. * active module.
  165. * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
  166. *
  167. * If the view name does not contain a file extension, it will use the default one `.php`.
  168. *
  169. * @param string $view the view name.
  170. * @param array $params the parameters (name-value pairs) that should be made available in the view.
  171. * @return string the rendering result.
  172. * @throws InvalidParamException if the view file does not exist.
  173. */
  174. public function render($view, $params = [])
  175. {
  176. return $this->getView()->render($view, $params, $this);
  177. }
  178. /**
  179. * Renders a view file.
  180. * @param string $file the view file to be rendered. This can be either a file path or a path alias.
  181. * @param array $params the parameters (name-value pairs) that should be made available in the view.
  182. * @return string the rendering result.
  183. * @throws InvalidParamException if the view file does not exist.
  184. */
  185. public function renderFile($file, $params = [])
  186. {
  187. return $this->getView()->renderFile($file, $params, $this);
  188. }
  189. /**
  190. * Returns the directory containing the view files for this widget.
  191. * The default implementation returns the 'views' subdirectory under the directory containing the widget class file.
  192. * @return string the directory containing the view files for this widget.
  193. */
  194. public function getViewPath()
  195. {
  196. $class = new ReflectionClass($this);
  197. return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
  198. }
  199. }