-1

I'm writing now an app, which is supposed to be as simple as possible. My controllers implement the render($Layout) method which just does the following:

public function render($Layout) {
  $this->Layout = $Layout;
  include( ... .php)
}

I don't really understand the following problem: in my controller, I define a variable:

public function somethingAction() {
  $someVariable = "something";
  $this->render('someLayout');
}

in the php view file (same I have included in the render function) I try to echo the variable, but there is nothing. However, if I declare my action method like this:

public function somethingAction() {
  global $someVariable;
  $someVariable = "something";
  $this->render('someLayout');
}

and in my view like this:

global $someVariable;
echo $someVariable;

it does work. Still it is annoying to write the word global each time.

How can I do this? I'd rather not use the $_GLOBAL arrays. In Cake PHP, for example, there is a method like $this->set('varName', $value). Then I can just access the variable in the view as $varName. How is it done?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Liglo App
  • 3,719
  • 4
  • 30
  • 54

2 Answers2

3

From the PHP Manual on include:

When a file is included, the code it contains inherits the variable scope of the line on which the include occurs. Any variables available at that line in the calling file will be available within the called file, from that point forward. However, all functions and classes defined in the included file have the global scope.

When you do

public function somethingAction() {
  $someVariable = "something";
  $this->render('someLayout');
}

the $someVariable variable is limited to the somethingAction() scope. Calling your render() method will not magically make the variable available in render(), because the render() method has it's own variable scope. A possible solution would be to do

public function somethingAction() {
  $this->render(
      'someLayout', 
      array(
          'someVariable' => 'something'
      )
    );
}

and then change render() to

public function render($Layout, array $viewData) {
    $this->Layout = $Layout;
    include( ... .php)
}

You will then have access to $viewData in the included file, given that you are not trying to use it in some other function or method, e.g. if your included file looks like this:

<h1><?php echo $viewData['someVariable']; ?></h1>

it will work, but if it is looks like this:

function foo() {  
    return $viewData['someVariable']; 
}
echo foo();

it will not work, because foo() has it's own variable scope.

However, a controller's sole responsibility is to handle input. Rendering is the responsibility of the View. Thus, your controller should not have a render() method at all. Consider moving the method to your View class and then do

public function somethingAction() {
  $view = new View('someLayout');
  $view->setData('someVariable', 'something');
  $view->render();
}

The render() method of your View object could then be implemented like this:

class View
…
    $private $viewData = array();       

    public function setData($key, $value) 
    {
        $this->viewData[$key] = $data;
    }

    public function render()
    {
        extract($this->viewData, EXTR_SKIP);
        include sprintf('/path/to/layouts/%s.php', $this->layout);
    }

The extract function will import the values of an array in the current scope using their keys as the name. This will allow you to use data in the viewData as $someVariable instead of $this->viewData['someVariable']. Make sure you understand the security implications of extract before using it though.

Note that this is just one possible alternative to your current way of doing things. You could also move out the View completely from the controller.

Community
  • 1
  • 1
Gordon
  • 312,688
  • 75
  • 539
  • 559
  • Yes, your solution is very good and I have done it like this. I only improved this by overlooping **$viewData** with a foreach and doing **foreach ($viewData as $k => $v) { $$k = $v; }**. So double dollar to make the variables accessible by its name. Thank you. – Liglo App Mar 03 '13 at 11:23
  • @BarthZalewski your `foreach` does what `extract` does. – Gordon Mar 03 '13 at 11:31
  • @BarthZalewski check the doc on [extract](http://php.net/manual/en/function.extract.php), using the prefix parameter `'$'` you may not need an additional for loop. +1 for @Gordon, you win ;) – metadings Mar 03 '13 at 11:52
0

Using global you're implicitly using the $_GLOBALS array.

A not recommended example, because globals are never a good thing:

function something() {
  global $var;
  $var = 'data';
}

// now these lines are the same result:
echo $_GLOBALS['var'];
global $var; echo $var;

Why don't you use simply the $this->set function?

public function render($Layout) {
  $this->Layout = $Layout;
  include( ... .php)
}

public function somethingAction() {
  $this->set('someVariable', "something");
  $this->render('someLayout');
}

// in a framework's managed view:
echo $someVariable;

I'm sorry not to know your framework in detail, but that makes perfectly sense.

Actually how it's done: There is a native extract function, that loads an associative array into the current symbol table:

array( 'var0' => 'val0', 'var1' => 'val1')

becomes

echo $var0; // val0
echo $var1; // val1

That's most likely 'what happens'.

metadings
  • 3,798
  • 2
  • 28
  • 37