Page 1 of 1

How to handle file includes properly

Posted: Tue Jan 08, 2008 12:26 am
by Mr Tech
I'm developing a script that uses modules for different sections of the website... basically the PHP code is laid out like this:

Code: Select all

include("header.php");

include("includes/".$_GET[module]."/index.php");

include("footer.php");
The problem with this method (i guess) is that it will cause "warning: headers already sent" messages" because the included module file uses the header() function a lot. There may be more reasons this is not a good idea but I have no idea what.

Is there a better approach to this? What do you do when you include files in this manner?

Posted: Tue Jan 08, 2008 12:42 am
by Christopher
Take a look at the output buffering functions.

Posted: Tue Jan 08, 2008 2:18 am
by Kieran Huggins
sadly, output buffering doesn't buffer headers.

Posted: Tue Jan 08, 2008 2:27 am
by alex.barylski
Good point.

In my opinion, when you start having issues with headers already sent...thats a good time to invest some time in a simple template engine.

Posted: Tue Jan 08, 2008 2:52 am
by Chris Corbyn
Kieran Huggins wrote:sadly, output buffering doesn't buffer headers.
I'm not sure it needs to. I think the issue Mr Tech was referring to was that of including the header before including the bulk of the page which includes such controller logic (sessions, redirects etc). Output buffering will help in this case.

Though Hockey is on track too, but it's not really about template engines, it's about code organization... effectively you want to run all the code which processes user input, handles sessions/redirects etc (the controller) *before* including header & footer. Obviously you need to split your "index.php" file into two for that. You're slowly refactoring towards something like MVC this way.

Code: Select all

include('controllers/' . $_GET['page'] . '.php');

include('templates/header.php');
include('templates/' . $_GET['page'] . '.php');
include('templates/footer.php');
I'd be a bit wary about directly using the GET variable in the include() however since you can pass "../../../some/secret/folder" in input like that ;)

If your main include previously had something like the following:

Code: Select all

<table>
  <tr>
    <th>Name</th>
    <th>Email</th>
  </tr>
<?php

$query = "select name, email from users";
$result = mysql_query($query);
while ($row = mysql_fetch_assoc($result))
{
  echo '<tr><td>';
  echo $row['name'];
  echo '</td><td>';
  echo $row['email'];
  echo '</td></tr>';
}

?>
</table>
Then you'd split it something like this:

controllers/pageName.php

Code: Select all

<?php

$users = array();

$query = "select name, email from users";
$result = mysql_query($query);
while ($row = mysql_fetch_assoc($result))
{
  $users[] = $row;
}

?>
templates/pageName.php

Code: Select all

<table>
  <tr>
    <th>Name</th>
    <th>Email</th>
  </tr>
  <?php foreach ($users as $user): ?>
  <tr>
    <td><?php echo $user['name']; ?></td>
    <td><?php echo $user['email']; ?></td>
  </tr>
  <?php endforeach; ?>
</table>
All I've done is separated my business logic from my presentation logic.

For the sake of brevity I've not escaped data I'm echo'ing into the HTML which is *bad* -- use htmlspecialchars() or such like in real code.

In this case too you really only got two layers: View and Controller-Model.

Posted: Tue Jan 08, 2008 3:30 am
by Christopher
Kieran Huggins wrote:sadly, output buffering doesn't buffer headers.
That is the whole point. Headers are sent and output is held in a string to be echoed later. Doing that is just making PHP templates function the same as templates where parsing/replacement is done. They all share the same idea -- gather output in strings and sent them last.

I think a Response object is the real solution, then you "buffer" headers as well as control doctype, etc.

Posted: Tue Jan 08, 2008 4:49 am
by Kieran Huggins
it just occurred to me that it doesn't matter that headers aren't buffered. The problem lies in the fact that there's output BEFORE some of the headers he wants to send. Ergo, start output buffering before the includes and flush at the end of the script; all headers (including his) will be sent before the output. Bingo!

Posted: Tue Jan 08, 2008 4:59 am
by Chris Corbyn
Kieran Huggins wrote:it just occurred to me that it doesn't matter that headers aren't buffered. The problem lies in the fact that there's output BEFORE some of the headers he wants to send. Ergo, start output buffering before the includes and flush at the end of the script; all headers (including his) will be sent before the output. Bingo!
Did someone say coffee? :wink: