4

This has probably been asked before, but I couldn't find the answer to my question with some preliminary searches, so here it is:

A very simple example of my current method of injecting PHP variables into HTML is as follows:

HTML (file.php):

<tag><?php echo $variable; ?></tag>

PHP:

$variable = "value";

ob_start();
include "file.php";
$results = ob_get_clean();

This achieves the correct result just fine, but it annoys me every time that I have to copy and paste those three lines to get variables injected into my HTML files. There's a chance I might want to change the details of this injection functionality at a later date, and it's currently scattered in a couple hundred locations throughout my code.

In a perfect world, I would move those three lines to a separate function that takes the file path as an input, and returns the "compiled" result. However, the function would then no longer have access to the calling scope, and I would have to pass the variables in as another input. The only thing I'm able to think of for doing that is:

function injectVariables($filePath, array $variables)
{
    //Built-in PHP function, extracts array key => value pairs into variable $key = value pairs in the current scope
    extract($variables);

    ob_start();
    include $filePath;
    return ob_get_clean();
}

That certainly gets the job done, and it will be what I implement if there aren't any better solutions, but it seems less than ideal to me. It involves creating an array every time this runs, just to loop through it and extract all the variables, which just feels a bit wrong to me.

Does anyone know of any other methods that might work?

3
  • It seems to work just fine without the ob_start(); Commented Jan 24, 2016 at 2:57
  • What do you see if you omit it? Commented Jan 24, 2016 at 2:57
  • If you directly echo your ob_get_clean call (or, equivalently, echo the results variable), you will get the same result. with or without the ob_start call, since including the file will output it directly to the user. However, there are many places in my projects that this needs to be saved into a variable and not directly output (for example, in injecting the results of one file into another). Commented Jan 24, 2016 at 3:01

3 Answers 3

1

Not really sure what you are asking, but here is my answer

I don't know the structure of your code, but I hope you are using a MVC approach (or at least something which deals with classes) so that you can do the following:

In your main controller, you create a class variable like viewData which will be an array. And you put in it everything you want

$this->viewData['username'] = $username;
$this->viewData['myArray'] = array('foo' => 'bar');
$this->viewData['menuSubview'] = 'path_to_menu_subview.php';

Then, you create a render function.

public function render()
{
    extract($this->viewData);
    ob_start();
    include("myfile.php");
    return ob_get_clean();
}

And in myfile.php (with the HTML) you can simply do what you used so far

<div id="menu"><?php include($menuSubview);?></div>
<p><?=$username;?></p>
<p><?=$myArray['foo'];?></p>

The whole code can be something like this.

class Something {

    protected $viewData;
    protected $viewFile;

    public function logic()
    {
        $this->userProfile();
        echo $this->render();
    }

    public function userProfile()
    {
        $this->viewData['username'] = 'John The Ripper';
        $this->viewFile = 'myFile.php';
    }

    public function render()
    {
        extract($this->viewData);
        ob_start();
        include($this->viewFile);
        return ob_get_clean();
    }
}
Sign up to request clarification or add additional context in comments.

9 Comments

The current scope is preserved when you include a file within an object. You can access $this->viewData from the template file without extracting it.
I am using a MVC approach (I was just trying to keep the question focused on one specific problem, so if someone in the future needs something similar the issues don't get tied up together). This solution would certainly work, but it's essentially equivalent to the tentative one I posted, so it doesn't exactly answer my question. Thanks though!
@MPLewis If you're using an MVC approach then anything you need to display in your template should be a property (or method) of your view. If you include the file from that view then you should have access to everything you need without extracting anything.
@MPLewis I think the point people are making is that your question is a bit unclear. Could you perhaps add a bit more context to how you use MVC?
@MPLewis I understand that you don't want to turn this into a discussion about MVC, but I suspect the core issue is with how you have implemented MVC. If your template file is in the same scope as your view object then you should be able to access any property of that object from within that template file by using $this as you normally would (no need for extract). If your template file is in a different scope then it is not a part of that view at all.
|
1

Here's a simplified class that stores data and allows for recursive rendering of files that all have access to the save data form the initial instance.

class View {

    // view data 
    protected $_data = [];

    /**
     * Set view data 
     */
    public function __set( $key, $value )
    {
        $this->_data[ $key ] = $value; 
    }

    /**
     * Get view data 
     */
    public function __get( $key )
    {
        if( array_key_exists( $key, $this->_data ) )
        {
            return $this->_data[ $key ];
        }
        return null;
    }

    /**
     * Render view file 
     */
    public function render( $file )
    {
        if( is_file( $file ) )
        {
            $view = $this; 
            ob_start();
            include( $file );
            return ob_get_clean();
        }
        return 'File not found: '.$file;
    }
}

Just use the variable $view inside your included files to access the data in the class, or render() another partial file that can do the same thing, and so on.

// Bootstrap a View instance and add some data 
$view = new View; 
$view->dataOne = 'valueOne'; 
$view->dataTwo = 'valueTwo'; 

// Render main template 
echo $view->render( 'template.php' );

Inside template.php

<header>
    <?= $view->render( 'header.php' )  ?> 
</header>

<h1><?= $view->dataOne ?></h1> 
<p><?= $view->dataTwo ?></p>

Comments

1

Nothing is wrong with your injectVariables() function, it is in fact a good idea.

  • You shouldn't have performance impact anyway (If this is your main concern your are doing premature optimization !)
  • Your view (HTML) shouldn't know about the internal of your application. (You split responsability - this is a huge subject that I won't talk deep) You know if something end-up into $variables it has been build/formatted or is revelent to be display for $filePath.
    In complex system you may end up with a variable with a pdf generator, why would the HTML want that? The only purpose of the HTML is to DISPLAY HTML data.

    Some variables from your logic will end-up in $variables almost every time such as session informations (Who is currently logged), for your case this is normal.

    In the perfect world if you put a class into $variables it should be designed only for purpose of your HTML even if it is almost the same object as your logic.
    As an exemple I designed a Session class in my logic. It does have method such as Logout() and EnsureIsAlive() as well as field such as FullName. FullName is going to be use by my HTML because I want to display it in the header of my page.
    I should have a Session class for the HTML that only contains a FullName field because the HTML has only one job and is it to display data. Login() and EnsureIsAlive() are specific for the logic and aren't related at all with displaying data. In reallity time is always a constraint and because you are writing all by yurself from scratch you may end-up just sending the logic Session class into $variables.

    • Design note : I'm a C# programmer and we always use class over array to pass parameters as a good pratice and it does affect the exemple about how to pass FullName to your HTML. In your case, instead of creating a class dedicated for the HTML you could also create a simple array :

      $variables['Session'] = array('FullName' => $mySession->fullName).
      

    That way you still avoid your HTML to have access to the unrelated method specific for your logic. I'm not sure if it is a good pratice in php...

  • Keep context variables clean
    If you do call injectVariables() more than once (for different $PathFile) you may not want the same variables.
    One injectionVariables() could be for a widget, another for a complete complete page.
    Why would a TimeWidget need your totalInvoice variable?

    You also have the ability to prepare multiples $variables at the same time because each injectVariables() will have his own parameters.

I'm not up-to-date but I know phpBB template system use (back near 2000) the exact same pattern as your injectVariables()).

You wrote that each of your page do call injectVariables(), you could also (but it may be complex) do it only in one file - like php frameworks.

Framework handle HTTP requests into one specific file
(eg. http://yoursite.com/hello/world or http://yoursite.com/login would call (internaly) http://yoursite.com/index.php?page=hello&subpage=world and http://yoursite.com?page=login).
Then this page (index.php in my exemple) would include a specific file (controller) according with his GET parameters ($_GET['page'] and $_GET['subpage']).
The controller job would be to fetch data, apply logic then fill $variables.
Then index.php will call injectVariables() with the releated HTML.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.