I agree that its a better pattern to wrap your view logic in a class.
Here is some code I whipped together tonight - It represents the simplest class I could devise that contains the minimum logic that I require in a view:
Includes - Ability to include other views
Captures - Ability to easily capture content within your view
Layouts - Ability to inject data into a re-usable layout template
Fetching - Ability to fetch view output instead of sending it to output buffer
data - Ability to access the resulting data once the view is finished
[edit] Refactored per conversation in comments regarding passing $data by reference.
View.php
<?php
/**
* Simple view class that supports includes, capturing, and layouts, as well
* as retrieving rendered view content and resulting data.
*
* *NOTE* When a view uses a layout, the output of the view is ignored, as
* as the view is expected to use capture() to send data to the layout.
*
* @author David Farrell <[email protected]>
*/
class View implements ArrayAccess
{
/**
* View file to include
* @var string
*/
private $file;
/**
* View data
* @var array
*/
private $data;
/**
* Layout to include (optional)
* @var string
*/
private $layout;
/**
* Constructor
*
* @param string $file file to include
*/
public function __construct($file)
{
$this->file = $file;
}
/**
* render Renders the view using the given data
*
* @param array $data
* @return void
*/
public function render($data)
{
$this->data = $data;
$this->layout = null;
ob_start();
include ($this->file);
// If we did not set a layout
if (null === $this->layout)
{
// flush view output
ob_end_flush();
}
// We set a layout
else
{
// Ignore view output
ob_end_clean();
// Include the layout
$this->include_file($this->layout);
}
}
/**
* fetch Fetches the view result intead of sending it to the output buffer
*
* @param array $data
* @return string The rendered view content
*/
public function fetch($data)
{
ob_start();
$this->render($data);
return ob_get_clean();
}
/**
* get_data Returns the view data
*
* @return array
*/
public function get_data()
{
return $this->data;
}
/**
* include_file Used by view to include sub-views
*
* @param string $file
* @return void
*/
protected function include_file($file)
{
$v = new View($file);
$v->render($this->data);
$this->data = $v->get_data();
}
/**
* set_layout Used by view to indicate the use of a layout.
*
* If a layout is selected, the normal output of the view wil be
* discarded. The only way to send data to the layout is via
* capture()
*
* @param string $file
* @return void
*/
protected function set_layout($file)
{
$this->layout = $file;
}
/**
* capture Used by view to capture output.
*
* When a view is using a layout (via set_layout()), the only way to pass
* data to the layout is via capture(), but the view can use capture()
* to capture text any time, for any reason, even if the view is not using
* a layout
*
* @return void
*/
protected function capture()
{
ob_start();
}
/**
* end_capture Used by view to signal end of a capture().
*
* The content of the capture is stored under $name
*
* @param string $name
* @return void
*/
protected function end_capture($name)
{
$this->data[$name] = ob_get_clean();
}
/* ArrayAccess methods */
public function offsetExists($offset) { return isset($this->data[$offset]); }
public function offsetGet($offset) { return $this->data[$offset]; }
public function offsetSet($offset, $value) { $this->data[$offset] = $value; }
public function offsetUnset($offset) { unset($this->data[$offset]); }
}
run.php
<?php
require "View.php";
$v = new View('view_main_simple.php');
$fetch = $v->fetch(array('message' => 'Hello, world'));
print("Fetch result: {$fetch}\n");
$v = new View('view_main_complex.php');
$v->render(array('one' => 1, 'two' => 2, 'rows' => array('a','b','c')));
$data = $v->get_data();
print("\n");
var_export($data);
view_main_simple.php
The message is: <?php echo $this['message'] ?><br/>
view_main_complex.php
<?php $this->set_layout('view_layout.php') ?>
<?php $this->capture() ?>
one=<?php echo $this['one'] ?><br/>
<?php $this->include_file('view_include.php') ?>
three=<?php echo $this['three'] ?><br/>
<?php $this->end_capture('body') ?>
view_include.php
two=<?php echo $this['two'] ?><br/>
<?php $this['three'] = 3 ?>
<ul>
<?php foreach($this['rows'] as $row) { ?>
<li><?php echo $row ?></li>
<?php } ?>
</ul>
view_layout.php
<html>
<body>
<pre>
<?php echo $this['body'] ?>
</pre>
</body>
</html>
Program Output
Fetch result: The message is: Hello, world<br/>
<html>
<body>
<pre>
one=1<br/>
two=2<br/>
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
three=3<br/>
</pre>
</body>
</html>
array (
'one' => 1,
'two' => 2,
'rows' =>
array (
0 => 'a',
1 => 'b',
2 => 'c',
),
'three' => 3,
'body' => ' one=1<br/>
two=2<br/>
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
three=3<br/>
',
)