PageRenderTime 64ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/LiquidContext.class.php

http://github.com/harrydeluxe/php-liquid
PHP | 351 lines | 186 code | 57 blank | 108 comment | 35 complexity | 857e19ab4d0f64548384fc951bbf42b8 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /**
  3. * Context keeps the variable stack and resolves variables, as well as keywords
  4. *
  5. * @package Liquid
  6. * @copyright Copyright (c) 2011-2012 Harald Hanek,
  7. * fork of php-liquid (c) 2006 Mateo Murphy,
  8. * based on Liquid for Ruby (c) 2006 Tobias Luetke
  9. * @license http://harrydeluxe.mit-license.org
  10. */
  11. class LiquidContext
  12. {
  13. /**
  14. * Local scopes
  15. *
  16. * @var array
  17. */
  18. private $_assigns;
  19. /**
  20. * Registers for non-variable state data
  21. *
  22. * @var array
  23. */
  24. public $registers;
  25. /**
  26. * The filterbank holds all the filters
  27. *
  28. * @var LiquidFilterbank
  29. */
  30. private $_filterbank;
  31. /**
  32. * Global scopes
  33. *
  34. * @var array
  35. */
  36. public $environments = array();
  37. /**
  38. * Constructor
  39. *
  40. * @param array $assigns
  41. * @param array $registers
  42. */
  43. public function __construct($assigns = null, $registers = array())
  44. {
  45. $this->_assigns = (isset($assigns)) ? array(
  46. $assigns
  47. ) : array(
  48. array()
  49. );
  50. $this->registers = $registers;
  51. $this->_filterbank = new LiquidFilterbank($this);
  52. }
  53. /**
  54. * Add a filter to the context
  55. *
  56. * @param mixed $filter
  57. */
  58. public function addFilters($filter)
  59. {
  60. $this->_filterbank->addFilter($filter);
  61. }
  62. /**
  63. * Invoke the filter that matches given name
  64. *
  65. * @param string $name The name of the filter
  66. * @param mixed $value The value to filter
  67. * @param array $args Additional arguments for the filter
  68. * @return string
  69. */
  70. public function invoke($name, $value, $args = null)
  71. {
  72. return $this->_filterbank->invoke($name, $value, $args);
  73. }
  74. /**
  75. * Merges the given assigns into the current assigns
  76. *
  77. * @param array $newAssigns
  78. */
  79. public function merge($newAssigns)
  80. {
  81. $this->_assigns[0] = array_merge($this->_assigns[0], $newAssigns);
  82. }
  83. /**
  84. * Push new local scope on the stack.
  85. *
  86. * @return bool
  87. */
  88. public function push()
  89. {
  90. array_unshift($this->_assigns, array());
  91. return true;
  92. }
  93. /**
  94. * Pops the current scope from the stack.
  95. *
  96. * @return bool
  97. */
  98. public function pop()
  99. {
  100. if (count($this->_assigns) == 1)
  101. {
  102. throw new LiquidException('No elements to pop');
  103. }
  104. array_shift($this->_assigns);
  105. }
  106. /**
  107. * Replaces []
  108. *
  109. * @param string
  110. * @return mixed
  111. */
  112. public function get($key)
  113. {
  114. return $this->resolve($key);
  115. }
  116. /**
  117. * Replaces []=
  118. *
  119. * @param string $key
  120. * @param mixed $value
  121. */
  122. public function set($key, $value)
  123. {
  124. $this->_assigns[0][$key] = $value;
  125. }
  126. /**
  127. * Returns true if the given key will properly resolve
  128. *
  129. * @param string $key
  130. * @return bool
  131. */
  132. public function hasKey($key)
  133. {
  134. return (!is_null($this->resolve($key)));
  135. }
  136. /**
  137. * Resolve a key by either returning the appropriate literal or by looking up the appropriate variable
  138. *
  139. * Test for empty has been moved to interpret condition, in LiquidDecisionBlock
  140. *
  141. * @param string $key
  142. * @return mixed
  143. */
  144. public function resolve($key)
  145. {
  146. // this shouldn't happen
  147. if (is_array($key))
  148. {
  149. throw new LiquidException("Cannot resolve arrays as key");
  150. }
  151. if (is_null($key) || $key == 'null')
  152. {
  153. return null;
  154. }
  155. if ($key == 'true')
  156. {
  157. return true;
  158. }
  159. if ($key == 'false')
  160. {
  161. return false;
  162. }
  163. if (preg_match('/^\'(.*)\'$/', $key, $matches))
  164. {
  165. return $matches[1];
  166. }
  167. if (preg_match('/^"(.*)"$/', $key, $matches))
  168. {
  169. return $matches[1];
  170. }
  171. if (preg_match('/^(\d+)$/', $key, $matches))
  172. {
  173. return $matches[1];
  174. }
  175. if (preg_match('/^(\d[\d\.]+)$/', $key, $matches))
  176. {
  177. return $matches[1];
  178. }
  179. return $this->variable($key);
  180. }
  181. /**
  182. * Fetches the current key in all the scopes
  183. *
  184. * @param string $key
  185. * @return mixed
  186. */
  187. public function fetch($key)
  188. {
  189. foreach($this->environments as $environment)
  190. {
  191. if (array_key_exists($key, $environment))
  192. {
  193. return $environment[$key];
  194. }
  195. }
  196. foreach($this->_assigns as $scope)
  197. {
  198. if (array_key_exists($key, $scope))
  199. {
  200. $obj = $scope[$key];
  201. if ($obj instanceof LiquidDrop)
  202. $obj->setContext($this);
  203. return $obj;
  204. }
  205. }
  206. }
  207. /**
  208. * Resolved the namespaced queries gracefully.
  209. *
  210. * @param string $key
  211. * @return mixed
  212. */
  213. public function variable($key)
  214. {
  215. /* Support [0] style array indicies */
  216. if (preg_match("|\[[0-9]+\]|", $key))
  217. {
  218. $key = preg_replace("|\[([0-9]+)\]|", ".$1", $key);
  219. }
  220. $parts = explode(LIQUID_VARIABLE_ATTRIBUTE_SEPARATOR, $key);
  221. $object = $this->fetch(array_shift($parts));
  222. if (is_object($object))
  223. {
  224. if (!method_exists($object, 'toLiquid'))
  225. throw new LiquidException("Method 'toLiquid' not exists!");
  226. $object = $object->toLiquid();
  227. }
  228. if (!is_null($object))
  229. {
  230. while(count($parts) > 0)
  231. {
  232. if ($object instanceof LiquidDrop)
  233. $object->setContext($this);
  234. $nextPartName = array_shift($parts);
  235. if (is_array($object))
  236. {
  237. // if the last part of the context variable is .size we just return the count
  238. if ($nextPartName == 'size' && count($parts) == 0 && !array_key_exists('size', $object))
  239. {
  240. return count($object);
  241. }
  242. if (array_key_exists($nextPartName, $object))
  243. {
  244. $object = $object[$nextPartName];
  245. }
  246. else
  247. {
  248. return null;
  249. }
  250. }
  251. elseif (is_object($object))
  252. {
  253. if ($object instanceof LiquidDrop)
  254. {
  255. // if the object is a drop, make sure it supports the given method
  256. if (!$object->hasKey($nextPartName))
  257. {
  258. return null;
  259. }
  260. // php4 doesn't support array access, so we have
  261. // to use the invoke method instead
  262. $object = $object->invokeDrop($nextPartName);
  263. }
  264. elseif (method_exists($object, LIQUID_HAS_PROPERTY_METHOD))
  265. {
  266. if (!call_user_method(LIQUID_HAS_PROPERTY_METHOD, $object, $nextPartName))
  267. {
  268. return null;
  269. }
  270. $object = call_user_method(LIQUID_GET_PROPERTY_METHOD, $object, $nextPartName);
  271. }
  272. else
  273. {
  274. // if it's just a regular object, attempt to access a property
  275. if (!property_exists($object, $nextPartName))
  276. {
  277. return null;
  278. }
  279. $object = $object->$nextPartName;
  280. }
  281. }
  282. if (is_object($object) && method_exists($object, 'toLiquid'))
  283. {
  284. $object = $object->toLiquid();
  285. }
  286. }
  287. return $object;
  288. }
  289. else
  290. {
  291. return null;
  292. }
  293. }
  294. }