Core code updates under way: Pardon any rough edges.

Introduction

We were at the JavaScript meetup the other day and mentioned “includes” in context of how we like to teach early web dev stuff here at PE with PHP. But they didn’t like includes. They made faces! And exclaimed something funny about how terrible includes are.

They are right. Includes have some issues… but we like to help people find that out for themselves. So, what’s the problem?

Include

header.php
<title><?=$pageTitle?></title>

Lets say you have a page title in your header file.

index.php
<?php $pageTitle = "Home"; ?>

<?php include('header.php'); ?>

But you’re just counting on that data being available upstream somewhere.

about.php
<?php include('header.php'); ?>

And then on some other page, you include the header – but it breaks. No error. Just a blank title. Or a warning buried somewhere. You stare at about.php and see nothing wrong.

Of course that’s a really simple example and on a small personal site there’s not so much surface area that you’re going to get too lost.

Global mystery variables

menu.php
<?php foreach ($navItems as $item): ?>
    <a href="<?=$item['url']?>">
		<?=$item['name']?>
	</a>
<?php endforeach; ?>
header.php
<?php include('menu.php'); ?>
page.php
<?php include('header.php'); ?>

<?php
	$item = "something important";

	// ... 40 lines later ...
	echo $item; // why is this a nav item array??
?>

Ghosts!

monster-card.php
<?php
	// we'd be assuming/hoping a $monster would be available -

	$name = $monster['name'] ?? "Unknown Monster";
	$favoriteColor = $monster['favoriteColor'] ?? "Red";
	$image = $monster['image'] ?? 'default.png';
?>

<article class="monster-card">
	<picture>
  		<img src="<?=$image?>">
  	</picture>

    <h1><?=$name?></h1>

    <p>Favorite color: <?=$favoriteColor?></p>
</article>
monsters.php
<?php
	$name = "Monster Gallery"; // for the page title

	$monsters = [
		[
			'name' => "Munchy", 
			'favoriteColor' => "Green"
		],
		['name' => 'Crunchy', 'favoriteColor' => 'Purple', 'image' => 'crunchy.png'],
		['name' => 'Chunky', 'favoriteColor' => 'Orange'],
	];
?>

<h1><?=$name?></h1>

<section class="monster-list">
	<?php foreach ($monsters as $monster): ?>
		<?php include('monster-card.php'); ?>
	<?php endforeach; ?>
</section>

<p>Welcome to <?=$name?></p>  <!-- "Chunky" — oops -->

Ooops!

monster-card.php
<?php
	// we'd be assuming/hoping a $monster would be available -

	$name = $monster['name'] ?? "Unknown Monster";
	$favoriteColor = $monster['favoriteColor'] ?? "Red";
	$image = $monster['image'] ?? 'default.png';
?>

<article class="monster-card">
	<picture>
  		<img src="<?=$image?>">
  	</picture>

    <h1><?=$name?></h1>

    <p>Favorite color: <?=$favoriteColor?></p>
</article>

<?php unset($name, $favoriteColor, $image); ?>

Sometimes a student will come up with this trick to unset the variable after each use. Pretty clever!

monsters.php
<?php
	// ...
?>

<h1><?=$name?></h1>

<section class="monster-list">
	<?php foreach ($monsters as $monster): ?>
		<?php include 'monster-card.php'; ?>
	<?php endforeach; ?>
</section>

<p>Welcome to <?=$name?></p>  <!-- undefined — not even "Chunky" anymore -->

<p>Last monster: <?=$monster['name']?></p>  <!-- "Chunky" — still here! so, $monster just sticks around forever! -->

Expecting a variable like $name (or anything) to just exist – is fine if you know the codebase really well and it’s simple. We could argue that too much abstraction is going to make everything more confusing. But we can organize thing differently and have both.

Function scope

header.php
<?php
	$name = "Monster Gallery"; // for the page title
?>
monsters.php
<?php
	function monsterCard($monster) {
		$name = $monster['name'] ?? "Unknown Monster";
		$favoriteColor = $monster['favoriteColor'] ?? "Red";
		$image = $monster['image'] ?? 'default.png';

		return "
			<article class='monster-card'>
				<picture>
					<img src='{$image}'>
				</picture>

				<h1>{$name}</h1>

				<p>Favorite color: {$favoriteColor}</p>
			</article>
		";
	}
?>

<h1><?=$name?></h1>

<section class="monster-list">
	<?php foreach ($monsters as $monster): ?>
		<?=monsterCard($monster)?>
	<?php endforeach; ?>
</section>

<p>Welcome to <?=$name?></p>  <!-- "Monster Gallery" — still intact -->

The entire program – is scoped, right? So – it’s almost like one big function being run when the server requests something.

A function is a smaller version of that. So, it’ll hold it’s scope, right? So, you might try something like this.

But that’s very specific to this one card. It’s also a bit messy and likely has less than great syntax highlighting due to how the template is now working.

Can you figure out a better solution? Something with a clearer separation of logic and template? And something that could work for ANY component?

You owe it to yourself to try… before moving on…

A universal component function

functions.php
<?php

function component($template, $props = []) {
	include("components/{$template}.php");
}

OK. So, just thinking about the big picture… we want a “component” to appear… and we’ll need to decide which one… and we could use it’s file name (assuming we can’t count on that location)… and that template is going to expect some data probably – and we don’t really know what data. So – this could be our ideal design.

header.php
<?php

require('functions.php');

This general “functions” or “utilities” or whatever you want to have — would be globally required/included (on purpose). (you have to decide where you want to draw the line)

components/monster-card.php
<?php
	$name = $props['name'] ?? "Unknown Monster";
	$favoriteColor = $props['favoriteColor'] ?? "Red";
	$image = $props['image'] ?? 'default.png';
?>

<article class="monster-card">
	... same as before
</article>

RE: ?? (we often keep backup data for the style-guide, but you don’t have to do that)

monsters.php
<section class="monster-list">
	<?php foreach ($monsters as $monster): ?>
		<?=component('monster-card', $monster)?>
	<?php endforeach; ?>
</section>

But somethings goes weird…

Cards render above everything. The function outputs immediately and returns nothing.

Pausing output

functions.php
<?php

function component($template, $props = []) {
	ob_start();
	include "components/{$template}.php";
	return ob_get_clean();
}

ob_start() is “output buffering.” Normally when PHP hits HTML or an echo, it sends it straight to the browser. Output buffering says “hold on, don’t send anything yet—collect it in memory.” Then ob_get_clean() grabs everything that was collected and returns it as a string.
We need it because include doesn’t return anything. It just outputs. So to capture what a template produces (so we can return it from a function) we have to intercept that output.

monster-card.php
<?php
	$name = $props['name'] ?? "Unknown Monster";
	$favoriteColor = $props['favoriteColor'] ?? "Red";
	$image = $props['image'] ?? 'default.png';
?>

<article class="monster-card">
	<picture>
		<img src="<?=$image?>">
	</picture>

	<h1><?=$name?></h1>

	<p>Favorite color: <?=$favoriteColor?></p>
</article>
wherever.php
<?=component('monster-card', $monster)?>

functions.php
<?php

function component($template, $props = []) {
	extract($props);
	ob_start();
	include "components/{$template}.php";
	return ob_get_clean();
}

And if you want to clean up all that prop array stuff in the component’s template, you can use extract().

monster-card.php
<?php
	$name = $name ?? "Unknown Monster";
	$favoriteColor = $favoriteColor ?? "Red";
	$image = $image ?? 'default.png';
?>

<article class="monster-card">
	<picture>
		<img src="<?=$image?>">
	</picture>

	<h1><?=$name?></h1>

	<p>Favorite color: <?=$favoriteColor?></p>
</article>
wherever.php
<?=component('monster-card', $monster)?>

Applying the concept other places

controllers/monsters.php
<?php

function monstersPage() {
	$monsters = getMonsters(); // from a database, API, wherever

	return view('monsters', [
		'title' => "Monster Gallery",
		'monsters' => $monsters,
	]);
}

You might have a controller that handles whatever logic – and then returns a page-level view() with the same strategy as component(). (view is a common convention for page-level component/module etc)

models/monster.php
<?php

function getMonsters() {
	// could be a database query
	// could be an API call
	// could be a JSON file
	// the controller doesn't care
}

And you don’t want $monsters just floating around in the global scope – so, that could be wrapped in a function too.

Models? Views? Controllers?

Maybe you’re heard of the MVC pattern. Each part knows nothing about the others’ insides. You can swap out the database for an API and the controller and view don’t change. You can redesign the template and the model doesn’t care.

In Node-land, template engines like EJS or Handlebars already return strings – they don’t blast output to the browser immediately like PHP’s include. So there’s no ob_start equivalent because there’s no problem to solve. But the shape is the same: pass a template name, pass some data, get HTML back. Laravel’s view(), Express’s res.render(), Rails’ render -same idea, different syntax.

Let's be friends