/lib/LiquidContext.class.php

https://github.com/garyc40/php-liquid · PHP · 341 lines · 174 code · 58 blank · 109 comment · 35 complexity · a1cfce0ac35889ecb42cc46066262c9e MD5 · raw file

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