Is Conditional Header Rendering in PHP a Good Approach?

When building a web application in vanilla PHP, creating a reusable header.php and footer.php is a classic, time-tested approach to keep your code DRY (Don't Repeat Yourself). Dynamically changing the navigation layout based on the logged-in user's role (e.g., Guest vs. Standard User vs. Admin) using session variables is highly effective.

Your proposed solution works and is widely used in smaller PHP projects. However, to make it production-ready, secure, and easily maintainable, there are a few critical improvements and security considerations you should implement.

1. The Critical Missing Piece: Session Initialization

For your conditional checks like $_SESSION['user_id'] to work, you must initialize the session engine. If you don't call session_start() before accessing the $_SESSION superglobal, PHP will not load the user session data, and your navigation bar will always treat users as guests.

Add this at the very top of your header.php (or in a global bootstrap/init file that runs before any HTML is output):

<?php
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}
?>

2. Security Warning: UI Hiding is Not Security (Authorization)

Hiding the "Admin" link in your navigation bar prevents regular users from seeing the link, but it does not stop them from accessing the admin page directly. If a malicious user types index.php?view=admin into their browser address bar, they can bypass your UI restriction.

You must enforce authorization checks on the target controller or page level. For example, at the top of your admin view controller, you should have a backend check:

<?php
// admin_controller.php or index.php (where view == admin)
if (empty($_SESSION['user_id']) || $_SESSION['role'] !== 1) {
    // Redirect unauthorized users to login or home page
    header('Location: index.php?view=login');
    exit;
}
?>

3. Refactoring Code for Better Readability

Using "magic numbers" like $_SESSION['role'] == 1 can make your code harder to read as your application grows. Consider defining constants or assigning roles to descriptive variables. Here is an optimized version of your header.php:

<?php
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

// Define role constants for readability
define('ROLE_ADMIN', 1);
define('ROLE_USER', 2);

$isLoggedIn = !empty($_SESSION['user_id']);
$isAdmin = $isLoggedIn && isset($_SESSION['role']) && $_SESSION['role'] === ROLE_ADMIN;
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Application</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
  <div class="container-fluid">
    <a class="navbar-brand" href="index.php?view=mainpage">My App</a>
    <button class="navbar-collapse-toggle navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav me-auto">
        <li class="nav-item"><a class="nav-link" href="index.php?view=mainpage">Home</a></li>

        <?php if ($isLoggedIn): ?>
            <!-- Shared logged-in links -->
            <li class="nav-item"><a class="nav-link" href="index.php?view=loggedin">Dashboard</a></li>

            <?php if ($isAdmin): ?>
                <!-- Admin Only -->
                <li class="nav-item"><a class="nav-link text-warning" href="index.php?view=admin">Admin Panel</a></li>
            <?php endif; ?>

            <li class="nav-item"><a class="nav-link" href="index.php?view=logout">Logout</a></li>
        <?php else: ?>
            <!-- Guest Only -->
            <li class="nav-item"><a class="nav-link" href="index.php?view=login">Login</a></li>
            <li class="nav-item"><a class="nav-link" href="index.php?view=register">Register</a></li>
        <?php endif; ?>
      </ul>
    </div>
  </div>
</nav>

<div class="container">

4. A Better Architectural Pattern: Layout Wrappers

While splitting files into header.php and footer.php works, it has a drawback: you are splitting HTML tags across files (e.g., opening <div class="container"> in the header and closing it in the footer). This can easily lead to layout breakage if you forget to include a footer or mismatch div tags.

A cleaner modern approach in PHP without using full-blown frameworks is to use a Layout Wrapper function. You define your layout in a single file and pass the page content to it:

<?php
// layout.php
function render_layout($content_callback) {
    include 'header.php'; // Contains only <head>, dynamic <nav>, etc.
    echo '<div class="container">';
    $content_callback();
    echo '</div>';
    include 'footer.php';
}
?>

Then, your individual pages look clean and self-contained:

<?php
require_once 'layout.php';

render_layout(function() {
    ?>
    <h1>Welcome to the Dashboard</h1>
    <p>This is your main content area.</p>
    <?php
});
?>

Summary

  • Yes, your logic of conditional rendering via $_SESSION is correct and standard.
  • Always call session_start() at the entry point of your script.
  • Never rely on UI elements for security; validate user roles on the server side on every restricted page request.
  • Use descriptive variables or constants instead of magic numbers (like 1 or 2) for user roles.