tags:

views:

80

answers:

4

I'm trying to whip up a skeleton View system in PHP, but I can't figure out how to get embedded views to receive their parent's variables. For example:

View Class

class View
{
    private $_vars=array();
    private $_file;

    public function __construct($file)
    {
        $this->_file='views/'.$file.'.php';
    }

    public function set($var, $value=null)
    {
        if (is_array($var))
        {
            $this->_vars=array_merge($var, $this->_vars);
        }
        else
            $this->_vars[$var]=$value;

        return $this;
    }

    public function output()
    {
        if (count($this->_vars))
            extract($this->_vars,  EXTR_REFS);
        require($this->_file);
        exit;
    }

    public static function factory($file)
    {
        return new self($file);
    }
}

test.php (top level view)

<html>
    <body>
        Hey <?=$name?>! This is <?=$adj?>!
        <?=View::factory('embed')->output()?>
    </body>
</html>

embed.php (embedded in test.php

<html>
    <body>
        Hey <?=$name?>! This is an embedded view file!!
    </body>
</html>

Code:

$vars=array(
    'name' => 'ryan',
    'adj' => 'cool'
);
View::factory('test')->set($vars)->output();

Output:

Hey ryan! This is cool! Hey [error for $name not being defined] 
this is an embedded view file!!

The problem is the variables I set in the top level view do not get passed to the embedded view. How could I make that happen?

+1  A: 

So, I'm not exactly answering your question, but here's my super-simple hand-grown template system. It supports what you're trying to do, although the interface is different.

// Usage
$main = new SimpleTemplate("templating/html.php");
$main->extract($someObject);
$main->extract($someArray);
$main->name = "my name";
$subTemplate = new SimpleTemplate("templating/another.php");
$subTemplate->parent($main);
$main->placeholderForAnotherTemplate = $subTemplate->run();
echo $main; // or $main->run(); 

// html.php
<html><body><h1>Title <?= $name ?></h1><p><?= $placeHolderForAnotherTemplate ?></p></body></html>

    <?php
// SimpleTemplate.php
function object_to_array($object)
{
 $array = array();
 foreach($object as $property => $value)
 {
  $array[$property] = $value;
 }

 return $array;
}

class SimpleTemplate
{
 public $source;
 public $path;
 public $result;
 public $parent;

 public function SimpleTemplate($path=false, $source=false)
 {
  $this->source = array();
  $this->extract($source);
  $this->path($path);
 }

 public function __toString()
 {
  return $this->run();
 }

 public function extract($source)
 {
  if ($source)
  {
   foreach ($source as $property => $value)
   {
    $this->source[$property] = $value;
   }
  }
 }

 public function parent($parent)
 {
  $this->parent = $parent;
 }

 public function path($path)
 {
  $this->path = $path;
 }

 public function __set($name, $value)
 {
  $this->source[$name] = $value;
 }

 public function __get($name)
 {
  return isset($this->source[$name]) ? $this->source[$name] : "";
 }

 public function mergeSource()
 {
  if (isset($this->parent))
   return array_merge($this->parent->mergeSource(), $this->source);
  else
   return $this->source;
 }

 public function run()
 {
  ob_start();
  extract ($this->mergeSource());
        include $this->path;
        $this->result = ob_get_contents();
        ob_end_clean();
        return $this->result;
 }
}
Sean Clark Hess
Thanks. This made me realize that even in big frameworks, you're supposed to render the view in your controller and then just pass the rendered view in a variable to the top level view. I was trying to render the view inside another view.
ryeguy
A: 

well, you create a new instance of the class, so there are no variables defined in the embedded template. you should try to copy the object, rather than creating a new one.

edit: I'm talking about the factory method

smoove666
A: 

The main issue is that your views have no direct knowledge of each other. By calling this:

<?=View::factory('embed')->output()?>

in your "parent" view, you create and output a template that has no knowledge of the fact that it is inside another template.

There are two approaches I could recommend here.

#1 - Associate your templates.

By making your embedded templates "children" of a parent template, you could allow them to have access to the parent's variables at output() time. I utilize this approach in a View system I built. It goes something like this:

$pView = new View_Parent_Class();
$cView = new View_Child_Class();
$pView->addView($cView);

At $pview->render() time, the child view is easily given access to the parent's variables.

This method might require a lot of refactoring for you, so I'll leave out the dirty details, and go into the second approach.

#2 - Pass the parent variables

This would probably be the easiest method to implement given the approach you've taken so far. Add an optional parameter to your output method, and rewrite it slightly, like this:

public function output($extra_vars = null)
{
    if (count($this->_vars))
        extract($this->_vars,  EXTR_REFS);
    if (is_array($extra_vars)) extract($extra_vars, EXTR_REFS);
    require($this->_file);
    exit;
}

If you add a simple getter method as well:

public function get_vars()
{
    return $this->_vars;
}

Then you can embed your files with what is effectively read-access to the parent's variables:

<?=View::factory('embed')->output($this->get_vars())?>

$this will be a reference to the current template, ie. the parent. Note that you can have variable name collisions via this method because of the two extract calls.

zombat
A: 

You could make your $_vars property static, not particularly elegant, but would work for what you are trying to achieve.

On a side note... your array_merge() in the set() function is wrong, swap your 2 variables around.

Mario
How could it be wrong? It merges 2 arrays together. That's like saying 2+3 != 3+2
ryeguy
Shouldn't the vars passed to the set() function overwrite the old ones? At the moment, if you call $view->set(array('title' => 'Title1'))->set(array('title' => 'Title2'))->output(); // Title will be Title1
Mario