<?php
class ViewData implements ArrayAccess {
protected $data;
protected $filter = null;
protected $override_filter = null;
public function __construct($data) {
$this->data = $data;
}
public function setFilter(Callable $filter = null) {
$this->filter = $filter;
return $this;
}
public function escape(Callable $filter) {
$this->override_filter = $filter;
return $this;
}
public function raw() {
$this->override_filter = false;
return $this;
}
public function offsetExists($key) {
return null !== $this->getProperty($key);
}
public function offsetGet($key) {
return $this->handleValue($this->getProperty($key));
}
public function offsetSet($offset, $value) { }
public function offsetUnset($offset) { }
public function __get($key) {
return $this->offsetGet($key);
}
public function exportArray() {
$result = [];
foreach ($this->data as $key => $value) {
$result[$key] = $this->offsetGet($key);
}
return $result;
}
public function __call($method, $args) {
if (!is_object($this->data) || !method_exists($this->data, $method)) {
return null;
}
return $this->handleValue(call_user_func_array([$this->data, $method], $args));
}
public function __toString() {
return (string)$this->filter($this->data);
}
protected function handleValue($value) {
if (null === $value || is_resource($value)) {
return null;
}
return $this->constructSubview($value);
}
protected function constructSubview($value) {
return (new self($value))->setFilter($this->filter);
}
protected function getProperty($key) {
if (is_array($this->data)) {
return isset($this->data[$key]) ? $this->data[$key] : null;
} elseif (is_object($this->data)) {
return property_exists($this->data, $key) ? $this->data->$key : null;
} else {
return null;
}
}
protected function filter($value) {
if (null !== $this->override_filter) {
$value = $this->override_filter ? call_user_func($this->override_filter, $value) : $value;
$this->override_filter = null;
} elseif ($this->filter) {
$value = call_user_func($this->filter, $value);
}
return $value;
}
}
class Model {
public $foo = '<b>FOO</b>';
public function getBar() {
return (object)['value' => '<h1>BAR</h1>'];
}
}
$ViewData = new ViewData([
'title' => '<h1>Title</h1>',
'a' => ['b' => '<i>Hello</i>'],
'b' => [
'c' => 1,
'model' => new Model,
],
]);
$ViewData->setFilter('htmlspecialchars');
extract($ViewData->exportArray());
function raw($v) {
return $v->raw();
}
function json($v) {
return $v->escape('json_encode');
}
?>
<!DOCTYPE html>
<html>
<head>
<?=$title?>
</head>
<body>
<h1><?=$a['b']?></h1>
<ul>
<li>
b.c = <?=$b->c?>
</li>
<li>
b.model.foo = <?=$b->model->foo?>
</li>
<li>
b.model.getBar().value = <?=$b->model->getBar()->value?>
</li>
</ul>
Raw HTML: <xmp><?=$b->model->getBar()->value->raw()?></xmp>
Raw HTML 2: <xmp><?=raw($b->model->getBar()->value)?></xmp>
JSON: <script>var json = <?=$a->escape('json_encode')?></script>
JSON 2: <script>var json = <?=json($a)?></script>
</body>
</html>