I'm currently doing a beginner course in coding, mainly focusing on PHP and in one exercise we're changing our code from including a template through normal namespacing to instead including it via a function so that we can use output buffering. To make it work we are using extract() and although I understand how extract works, I struggle to see why we need to use it to make include work. Before running it via the function, we didn't need to send in or extract new variables. Is someone able to explain the reasons behind this?
Here's what the function looks like:
const TEMPLATE_EXTENSION = '.phtml';
const TEMPLATE_FOLDER = 'templates/';
const TEMPLATE_PREFIX = 'cart_view_';
function display($template, $variables, $extension = TEMPLATE_EXTENSION) {
extract($variables);
ob_start();
include TEMPLATE_FOLDER . TEMPLATE_PREFIX . $template . $extension;
return ob_get_clean();
}
Here's how we call it:
<?php echo display('user', ['users' => $users, 'cart' => $cart]); ?>
<?php echo display('item', ['new_item' => $new_item]); ?>
<?php echo display('items', ['cart' => $cart]); ?>
And here's what's in the templates we're including:
<h2>New Item</h2>
<p><?php printf($new_item['name']);?> is $<?php printf($new_item['price']);?></p>
<h2>User: <?php printf($cart['user']); ?></h2>
<p>ID: <?php printf($users[$cart['user']]['id']); ?></p>
<p>Email: <?php printf($users[$cart['user']]['email']); ?></p>
<h2>Cart</h2>
<?php foreach ($cart['items'] as $item) {
printf("<p>%s is $%d</p>\n", $item['name'], $item['price']);
} ?>
The variables are definied in another file which is already included in the index. Previously before using the function to buffer, all we needed was this:
<?php include 'templates/cart_view_user.phtml'; ?>
<?php include 'templates/cart_view_item.phtml'; ?>
<?php include 'templates/cart_view_items.phtml'; ?>
Functions do have their own variable scope. So when you created the display()
function, it doesn't see what variables are available outside of it. See more on Variable Scopes in PHP. You are using extract()
in order to convert an array into variables in the scope where you are calling it from eg: in the function.
Your first solution was probably something like this (I'm making up the variables):
$users = [];
$cart = [];
// you are including the template in the same scope as the variables are defined, aka it will "see"/have access to those variables
include 'templates/cart_view_user.phtml';
Now you have refactored your code and moved the including logic in a function. This function has its own local scope.
$users = [];
$cart = [];
function display($template, $variables, $extension = TEMPLATE_EXTENSION) {
// you don't have access to $users and $cart here as those are defined in the global scope
extract($variables); // <-- after this call you will have new variables created in the local function scope based on your $variables array
ob_start();
include TEMPLATE_FOLDER . TEMPLATE_PREFIX . $template . $extension;
return ob_get_clean();
}
So now you would have two options if you know what variables you want to make available inside the function you could use the global keyword, but I would discourage using it, it could lead to weird bugs when you don't understand why your variables being changed (ps later you will move onto using classes and you won't have the headache of global variables).
// you can drop $variables from the function signature
function display($template, $extension = TEMPLATE_EXTENSION) {
global $users, $cart, $new_item;
// no extract needed, but please try not using global, it can lead to weird bugs
ob_start();
include TEMPLATE_FOLDER . TEMPLATE_PREFIX . $template . $extension;
return ob_get_clean();
}
Or you can use the extract
to create variables in the function's scope so when you include the files they will have access to those variables. On thing to note here that extract will use the array keys as the variable names it is creating. That's why you are passing in display('user', ['users' => $users, 'cart' => $cart]);
after this call, extract
will create a $users
and a $cart
variable inside the function call. If you would call it with different array keys, like: display('user', ['u' => $users, 'c' => $cart]);
the included file would complain that it can't find the variables $users
and $cart
.
I hope this helped, feel free to ask more if I wasn't clear anywhere.