HiveBrain v1.2.0
Get Started
← Back to all entries
patternphpMinor

Is this a proper way of "loading" views in PHP?

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
thisphpwayviewsloadingproper

Problem

As a learning exercise, I'm developing my own PHP framework. I'm looking for a way to "load views" (kinda like CodeIgniter does it), without polluting my general scope.

I came up with the following, basic, example:


$data['test'] = 'Hello world';

$str = load_view($data, true);
echo $str;

function load_view($data, $store = false) {
    extract($data);

    ob_start();
    include('view.php');

    if($store)  return ob_get_clean();
    else        ob_end_flush();
}

// End of file


view.php




I could also use load_view($data), which would output the contents of view.php immediately.

Edit: I'm mostly worried about performance. As Peter pointed out, I'm aware that the function should be a class method that's seperate from the logic.

Solution

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


 */
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

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: 


view_main_complex.php

set_layout('view_layout.php') ?>
capture() ?>
    one=
    include_file('view_include.php') ?>
    three=
end_capture('body') ?>


view_include.php

two=

    
        
    


view_layout.php



Program Output

Fetch result: The message is: Hello, world

    one=1
    two=2

            a
            b
            c
    
    three=3

array (
  'one' => 1,
  'two' => 2,
  'rows' => 
  array (
    0 => 'a',
    1 => 'b',
    2 => 'c',
  ),
  'three' => 3,
  'body' => '   one=1
    two=2

            a
            b
            c
    
    three=3
',
)

Code Snippets

<?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 <DavidPFarrell@gmail.com>
 */
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_cap
<?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);
The message is: <?php echo $this['message'] ?><br/>
<?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') ?>
two=<?php echo $this['two'] ?><br/>
<?php $this['three'] = 3 ?>
<ul>
    <?php foreach($this['rows'] as $row) { ?>
        <li><?php echo $row ?></li>
    <?php } ?>
</ul>

Context

StackExchange Code Review Q#19460, answer score: 6

Revisions (0)

No revisions yet.