Factory.php 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. <?php
  2. namespace Illuminate\View;
  3. use Illuminate\Support\Arr;
  4. use Illuminate\Support\Str;
  5. use InvalidArgumentException;
  6. use Illuminate\Contracts\Events\Dispatcher;
  7. use Illuminate\Contracts\Support\Arrayable;
  8. use Illuminate\View\Engines\EngineResolver;
  9. use Illuminate\Contracts\Container\Container;
  10. use Illuminate\Contracts\View\Factory as FactoryContract;
  11. class Factory implements FactoryContract
  12. {
  13. use Concerns\ManagesComponents,
  14. Concerns\ManagesEvents,
  15. Concerns\ManagesLayouts,
  16. Concerns\ManagesLoops,
  17. Concerns\ManagesStacks,
  18. Concerns\ManagesTranslations;
  19. /**
  20. * The engine implementation.
  21. *
  22. * @var \Illuminate\View\Engines\EngineResolver
  23. */
  24. protected $engines;
  25. /**
  26. * The view finder implementation.
  27. *
  28. * @var \Illuminate\View\ViewFinderInterface
  29. */
  30. protected $finder;
  31. /**
  32. * The event dispatcher instance.
  33. *
  34. * @var \Illuminate\Contracts\Events\Dispatcher
  35. */
  36. protected $events;
  37. /**
  38. * The IoC container instance.
  39. *
  40. * @var \Illuminate\Contracts\Container\Container
  41. */
  42. protected $container;
  43. /**
  44. * Data that should be available to all templates.
  45. *
  46. * @var array
  47. */
  48. protected $shared = [];
  49. /**
  50. * The extension to engine bindings.
  51. *
  52. * @var array
  53. */
  54. protected $extensions = [
  55. 'blade.php' => 'blade',
  56. 'php' => 'php',
  57. 'css' => 'file',
  58. ];
  59. /**
  60. * The view composer events.
  61. *
  62. * @var array
  63. */
  64. protected $composers = [];
  65. /**
  66. * The number of active rendering operations.
  67. *
  68. * @var int
  69. */
  70. protected $renderCount = 0;
  71. /**
  72. * Create a new view factory instance.
  73. *
  74. * @param \Illuminate\View\Engines\EngineResolver $engines
  75. * @param \Illuminate\View\ViewFinderInterface $finder
  76. * @param \Illuminate\Contracts\Events\Dispatcher $events
  77. * @return void
  78. */
  79. public function __construct(EngineResolver $engines, ViewFinderInterface $finder, Dispatcher $events)
  80. {
  81. $this->finder = $finder;
  82. $this->events = $events;
  83. $this->engines = $engines;
  84. $this->share('__env', $this);
  85. }
  86. /**
  87. * Get the evaluated view contents for the given view.
  88. *
  89. * @param string $path
  90. * @param array $data
  91. * @param array $mergeData
  92. * @return \Illuminate\Contracts\View\View
  93. */
  94. public function file($path, $data = [], $mergeData = [])
  95. {
  96. $data = array_merge($mergeData, $this->parseData($data));
  97. return tap($this->viewInstance($path, $path, $data), function ($view) {
  98. $this->callCreator($view);
  99. });
  100. }
  101. /**
  102. * Get the evaluated view contents for the given view.
  103. *
  104. * @param string $view
  105. * @param array $data
  106. * @param array $mergeData
  107. * @return \Illuminate\Contracts\View\View
  108. */
  109. public function make($view, $data = [], $mergeData = [])
  110. {
  111. $path = $this->finder->find(
  112. $view = $this->normalizeName($view)
  113. );
  114. // Next, we will create the view instance and call the view creator for the view
  115. // which can set any data, etc. Then we will return the view instance back to
  116. // the caller for rendering or performing other view manipulations on this.
  117. $data = array_merge($mergeData, $this->parseData($data));
  118. return tap($this->viewInstance($view, $path, $data), function ($view) {
  119. $this->callCreator($view);
  120. });
  121. }
  122. /**
  123. * Get the first view that actually exists from the given list.
  124. *
  125. * @param array $views
  126. * @param array $data
  127. * @param array $mergeData
  128. * @return \Illuminate\Contracts\View\View
  129. */
  130. public function first(array $views, $data = [], $mergeData = [])
  131. {
  132. $view = Arr::first($views, function ($view) {
  133. return $this->exists($view);
  134. });
  135. if (! $view) {
  136. throw new InvalidArgumentException('None of the views in the given array exist.');
  137. }
  138. return $this->make($view, $data, $mergeData);
  139. }
  140. /**
  141. * Get the rendered content of the view based on a given condition.
  142. *
  143. * @param bool $condition
  144. * @param string $view
  145. * @param array $data
  146. * @param array $mergeData
  147. * @return string
  148. */
  149. public function renderWhen($condition, $view, $data = [], $mergeData = [])
  150. {
  151. if (! $condition) {
  152. return '';
  153. }
  154. return $this->make($view, $this->parseData($data), $mergeData)->render();
  155. }
  156. /**
  157. * Get the rendered contents of a partial from a loop.
  158. *
  159. * @param string $view
  160. * @param array $data
  161. * @param string $iterator
  162. * @param string $empty
  163. * @return string
  164. */
  165. public function renderEach($view, $data, $iterator, $empty = 'raw|')
  166. {
  167. $result = '';
  168. // If is actually data in the array, we will loop through the data and append
  169. // an instance of the partial view to the final result HTML passing in the
  170. // iterated value of this data array, allowing the views to access them.
  171. if (count($data) > 0) {
  172. foreach ($data as $key => $value) {
  173. $result .= $this->make(
  174. $view, ['key' => $key, $iterator => $value]
  175. )->render();
  176. }
  177. }
  178. // If there is no data in the array, we will render the contents of the empty
  179. // view. Alternatively, the "empty view" could be a raw string that begins
  180. // with "raw|" for convenience and to let this know that it is a string.
  181. else {
  182. $result = Str::startsWith($empty, 'raw|')
  183. ? substr($empty, 4)
  184. : $this->make($empty)->render();
  185. }
  186. return $result;
  187. }
  188. /**
  189. * Normalize a view name.
  190. *
  191. * @param string $name
  192. * @return string
  193. */
  194. protected function normalizeName($name)
  195. {
  196. return ViewName::normalize($name);
  197. }
  198. /**
  199. * Parse the given data into a raw array.
  200. *
  201. * @param mixed $data
  202. * @return array
  203. */
  204. protected function parseData($data)
  205. {
  206. return $data instanceof Arrayable ? $data->toArray() : $data;
  207. }
  208. /**
  209. * Create a new view instance from the given arguments.
  210. *
  211. * @param string $view
  212. * @param string $path
  213. * @param array $data
  214. * @return \Illuminate\Contracts\View\View
  215. */
  216. protected function viewInstance($view, $path, $data)
  217. {
  218. return new View($this, $this->getEngineFromPath($path), $view, $path, $data);
  219. }
  220. /**
  221. * Determine if a given view exists.
  222. *
  223. * @param string $view
  224. * @return bool
  225. */
  226. public function exists($view)
  227. {
  228. try {
  229. $this->finder->find($view);
  230. } catch (InvalidArgumentException $e) {
  231. return false;
  232. }
  233. return true;
  234. }
  235. /**
  236. * Get the appropriate view engine for the given path.
  237. *
  238. * @param string $path
  239. * @return \Illuminate\Contracts\View\Engine
  240. *
  241. * @throws \InvalidArgumentException
  242. */
  243. public function getEngineFromPath($path)
  244. {
  245. if (! $extension = $this->getExtension($path)) {
  246. throw new InvalidArgumentException("Unrecognized extension in file: {$path}");
  247. }
  248. $engine = $this->extensions[$extension];
  249. return $this->engines->resolve($engine);
  250. }
  251. /**
  252. * Get the extension used by the view file.
  253. *
  254. * @param string $path
  255. * @return string
  256. */
  257. protected function getExtension($path)
  258. {
  259. $extensions = array_keys($this->extensions);
  260. return Arr::first($extensions, function ($value) use ($path) {
  261. return Str::endsWith($path, '.'.$value);
  262. });
  263. }
  264. /**
  265. * Add a piece of shared data to the environment.
  266. *
  267. * @param array|string $key
  268. * @param mixed $value
  269. * @return mixed
  270. */
  271. public function share($key, $value = null)
  272. {
  273. $keys = is_array($key) ? $key : [$key => $value];
  274. foreach ($keys as $key => $value) {
  275. $this->shared[$key] = $value;
  276. }
  277. return $value;
  278. }
  279. /**
  280. * Increment the rendering counter.
  281. *
  282. * @return void
  283. */
  284. public function incrementRender()
  285. {
  286. $this->renderCount++;
  287. }
  288. /**
  289. * Decrement the rendering counter.
  290. *
  291. * @return void
  292. */
  293. public function decrementRender()
  294. {
  295. $this->renderCount--;
  296. }
  297. /**
  298. * Check if there are no active render operations.
  299. *
  300. * @return bool
  301. */
  302. public function doneRendering()
  303. {
  304. return $this->renderCount == 0;
  305. }
  306. /**
  307. * Add a location to the array of view locations.
  308. *
  309. * @param string $location
  310. * @return void
  311. */
  312. public function addLocation($location)
  313. {
  314. $this->finder->addLocation($location);
  315. }
  316. /**
  317. * Add a new namespace to the loader.
  318. *
  319. * @param string $namespace
  320. * @param string|array $hints
  321. * @return $this
  322. */
  323. public function addNamespace($namespace, $hints)
  324. {
  325. $this->finder->addNamespace($namespace, $hints);
  326. return $this;
  327. }
  328. /**
  329. * Prepend a new namespace to the loader.
  330. *
  331. * @param string $namespace
  332. * @param string|array $hints
  333. * @return $this
  334. */
  335. public function prependNamespace($namespace, $hints)
  336. {
  337. $this->finder->prependNamespace($namespace, $hints);
  338. return $this;
  339. }
  340. /**
  341. * Replace the namespace hints for the given namespace.
  342. *
  343. * @param string $namespace
  344. * @param string|array $hints
  345. * @return $this
  346. */
  347. public function replaceNamespace($namespace, $hints)
  348. {
  349. $this->finder->replaceNamespace($namespace, $hints);
  350. return $this;
  351. }
  352. /**
  353. * Register a valid view extension and its engine.
  354. *
  355. * @param string $extension
  356. * @param string $engine
  357. * @param \Closure $resolver
  358. * @return void
  359. */
  360. public function addExtension($extension, $engine, $resolver = null)
  361. {
  362. $this->finder->addExtension($extension);
  363. if (isset($resolver)) {
  364. $this->engines->register($engine, $resolver);
  365. }
  366. unset($this->extensions[$extension]);
  367. $this->extensions = array_merge([$extension => $engine], $this->extensions);
  368. }
  369. /**
  370. * Flush all of the factory state like sections and stacks.
  371. *
  372. * @return void
  373. */
  374. public function flushState()
  375. {
  376. $this->renderCount = 0;
  377. $this->flushSections();
  378. $this->flushStacks();
  379. }
  380. /**
  381. * Flush all of the section contents if done rendering.
  382. *
  383. * @return void
  384. */
  385. public function flushStateIfDoneRendering()
  386. {
  387. if ($this->doneRendering()) {
  388. $this->flushState();
  389. }
  390. }
  391. /**
  392. * Get the extension to engine bindings.
  393. *
  394. * @return array
  395. */
  396. public function getExtensions()
  397. {
  398. return $this->extensions;
  399. }
  400. /**
  401. * Get the engine resolver instance.
  402. *
  403. * @return \Illuminate\View\Engines\EngineResolver
  404. */
  405. public function getEngineResolver()
  406. {
  407. return $this->engines;
  408. }
  409. /**
  410. * Get the view finder instance.
  411. *
  412. * @return \Illuminate\View\ViewFinderInterface
  413. */
  414. public function getFinder()
  415. {
  416. return $this->finder;
  417. }
  418. /**
  419. * Set the view finder instance.
  420. *
  421. * @param \Illuminate\View\ViewFinderInterface $finder
  422. * @return void
  423. */
  424. public function setFinder(ViewFinderInterface $finder)
  425. {
  426. $this->finder = $finder;
  427. }
  428. /**
  429. * Flush the cache of views located by the finder.
  430. *
  431. * @return void
  432. */
  433. public function flushFinderCache()
  434. {
  435. $this->getFinder()->flush();
  436. }
  437. /**
  438. * Get the event dispatcher instance.
  439. *
  440. * @return \Illuminate\Contracts\Events\Dispatcher
  441. */
  442. public function getDispatcher()
  443. {
  444. return $this->events;
  445. }
  446. /**
  447. * Set the event dispatcher instance.
  448. *
  449. * @param \Illuminate\Contracts\Events\Dispatcher $events
  450. * @return void
  451. */
  452. public function setDispatcher(Dispatcher $events)
  453. {
  454. $this->events = $events;
  455. }
  456. /**
  457. * Get the IoC container instance.
  458. *
  459. * @return \Illuminate\Contracts\Container\Container
  460. */
  461. public function getContainer()
  462. {
  463. return $this->container;
  464. }
  465. /**
  466. * Set the IoC container instance.
  467. *
  468. * @param \Illuminate\Contracts\Container\Container $container
  469. * @return void
  470. */
  471. public function setContainer(Container $container)
  472. {
  473. $this->container = $container;
  474. }
  475. /**
  476. * Get an item from the shared data.
  477. *
  478. * @param string $key
  479. * @param mixed $default
  480. * @return mixed
  481. */
  482. public function shared($key, $default = null)
  483. {
  484. return Arr::get($this->shared, $key, $default);
  485. }
  486. /**
  487. * Get all of the shared data for the environment.
  488. *
  489. * @return array
  490. */
  491. public function getShared()
  492. {
  493. return $this->shared;
  494. }
  495. }