Program Structure
Overview
Understanding how to organize PHP programs is essential for writing maintainable and scalable applications. This chapter covers file organization, namespaces, autoloading, and architectural patterns that help you structure your PHP projects effectively.
File Organization
Basic File Structure
my-php-project/
├── public/ # Web-accessible files
│ ├── index.php # Entry point
│ ├── css/
│ ├── js/
│ └── images/
├── src/ # Application source code
│ ├── Models/
│ ├── Controllers/
│ └── Views/
├── config/ # Configuration files
├── tests/ # Test files
├── vendor/ # Composer dependencies
├── composer.json # Dependency management
└── .htaccess # Apache configurationSingle File Program
php
<?php
// simple_calculator.php
// Configuration
error_reporting(E_ALL);
ini_set('display_errors', 1);
// Functions
function add($a, $b) {
return $a + $b;
}
function subtract($a, $b) {
return $a - $b;
}
function multiply($a, $b) {
return $a * $b;
}
function divide($a, $b) {
if ($b == 0) {
throw new InvalidArgumentException("Division by zero");
}
return $a / $b;
}
// Main program logic
try {
$num1 = 10;
$num2 = 5;
echo "Addition: " . add($num1, $num2) . "\n";
echo "Subtraction: " . subtract($num1, $num2) . "\n";
echo "Multiplication: " . multiply($num1, $num2) . "\n";
echo "Division: " . divide($num1, $num2) . "\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
?>Multi-File Program
config/database.php
php
<?php
return [
'host' => 'localhost',
'database' => 'myapp',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4'
];
?>src/Database.php
php
<?php
class Database {
private $connection;
public function __construct($config) {
$dsn = "mysql:host={$config['host']};dbname={$config['database']};charset={$config['charset']}";
$this->connection = new PDO($dsn, $config['username'], $config['password']);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function getConnection() {
return $this->connection;
}
}
?>src/User.php
php
<?php
class User {
private $db;
public function __construct(Database $database) {
$this->db = $database->getConnection();
}
public function create($name, $email) {
$stmt = $this->db->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
return $stmt->execute([$name, $email]);
}
public function findById($id) {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
}
?>public/index.php
php
<?php
// Include required files
require_once '../src/Database.php';
require_once '../src/User.php';
// Load configuration
$config = require '../config/database.php';
// Initialize application
try {
$database = new Database($config);
$userModel = new User($database);
// Application logic
$user = $userModel->findById(1);
if ($user) {
echo "Welcome, " . htmlspecialchars($user['name']);
} else {
echo "User not found";
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
?>Include and Require
Include vs Require
php
<?php
// include - continues execution if file not found (warning)
include 'optional_file.php';
// require - stops execution if file not found (fatal error)
require 'essential_file.php';
// include_once - includes file only once
include_once 'header.php';
// require_once - requires file only once (recommended for classes)
require_once 'User.php';
?>Best Practices for Includes
php
<?php
// Use absolute paths to avoid issues
require_once __DIR__ . '/config/database.php';
// Define constants for common paths
define('ROOT_PATH', __DIR__);
define('SRC_PATH', ROOT_PATH . '/src');
define('CONFIG_PATH', ROOT_PATH . '/config');
require_once SRC_PATH . '/User.php';
require_once CONFIG_PATH . '/database.php';
?>Namespaces
Basic Namespace Usage
php
<?php
// src/Models/User.php
namespace App\Models;
class User {
public function getName() {
return "User from Models namespace";
}
}
?>php
<?php
// src/Controllers/User.php
namespace App\Controllers;
class User {
public function index() {
return "User Controller";
}
}
?>php
<?php
// Using namespaced classes
require_once 'src/Models/User.php';
require_once 'src/Controllers/User.php';
// Fully qualified names
$userModel = new \App\Models\User();
$userController = new \App\Controllers\User();
// Using use statements
use App\Models\User as UserModel;
use App\Controllers\User as UserController;
$model = new UserModel();
$controller = new UserController();
?>Namespace Aliases
php
<?php
namespace App\Services;
// Import classes with aliases
use App\Models\User as UserModel;
use App\Repositories\User as UserRepository;
use DateTime;
class UserService {
private $userModel;
private $userRepository;
public function __construct() {
$this->userModel = new UserModel();
$this->userRepository = new UserRepository();
}
public function createUser($data) {
$data['created_at'] = new DateTime();
return $this->userRepository->save($data);
}
}
?>Global Namespace
php
<?php
namespace App\Models;
// Access global classes with leading backslash
$date = new \DateTime();
$pdo = new \PDO($dsn, $user, $pass);
// Or import them
use DateTime;
use PDO;
$date = new DateTime();
$pdo = new PDO($dsn, $user, $pass);
?>Autoloading
Manual Autoloading
php
<?php
// autoload.php
spl_autoload_register(function ($className) {
// Convert namespace to file path
$file = str_replace('\\', DIRECTORY_SEPARATOR, $className) . '.php';
$fullPath = __DIR__ . '/src/' . $file;
if (file_exists($fullPath)) {
require_once $fullPath;
}
});
// Usage
require_once 'autoload.php';
// These classes will be autoloaded
$user = new App\Models\User();
$controller = new App\Controllers\UserController();
?>PSR-4 Autoloading
php
<?php
// composer.json
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
?>php
<?php
// After running: composer dump-autoload
require_once 'vendor/autoload.php';
// Classes are autoloaded based on namespace
$user = new App\Models\User(); // Loads src/Models/User.php
$service = new App\Services\UserService(); // Loads src/Services/UserService.php
?>Object-Oriented Structure
Class Organization
php
<?php
namespace App\Models;
use DateTime;
use InvalidArgumentException;
/**
* User Model Class
*/
class User {
// Constants
const STATUS_ACTIVE = 1;
const STATUS_INACTIVE = 0;
// Properties
private $id;
private $name;
private $email;
private $status;
private $createdAt;
// Constructor
public function __construct($name, $email) {
$this->setName($name);
$this->setEmail($email);
$this->status = self::STATUS_ACTIVE;
$this->createdAt = new DateTime();
}
// Getters
public function getId() {
return $this->id;
}
public function getName() {
return $this->name;
}
public function getEmail() {
return $this->email;
}
// Setters with validation
public function setName($name) {
if (empty($name)) {
throw new InvalidArgumentException("Name cannot be empty");
}
$this->name = trim($name);
}
public function setEmail($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Invalid email format");
}
$this->email = $email;
}
// Business logic methods
public function activate() {
$this->status = self::STATUS_ACTIVE;
}
public function deactivate() {
$this->status = self::STATUS_INACTIVE;
}
public function isActive() {
return $this->status === self::STATUS_ACTIVE;
}
// Utility methods
public function toArray() {
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'status' => $this->status,
'created_at' => $this->createdAt->format('Y-m-d H:i:s')
];
}
}
?>Interface and Abstract Classes
php
<?php
namespace App\Contracts;
interface UserRepositoryInterface {
public function find($id);
public function save(User $user);
public function delete($id);
}
?>php
<?php
namespace App\Repositories;
use App\Contracts\UserRepositoryInterface;
use App\Models\User;
abstract class BaseRepository {
protected $connection;
public function __construct($connection) {
$this->connection = $connection;
}
abstract protected function getTableName();
}
class UserRepository extends BaseRepository implements UserRepositoryInterface {
protected function getTableName() {
return 'users';
}
public function find($id) {
$stmt = $this->connection->prepare("SELECT * FROM {$this->getTableName()} WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
public function save(User $user) {
// Implementation
}
public function delete($id) {
// Implementation
}
}
?>MVC Architecture Pattern
Model-View-Controller Structure
src/
├── Models/
│ ├── User.php
│ └── Product.php
├── Views/
│ ├── users/
│ │ ├── index.php
│ │ └── show.php
│ └── layouts/
│ └── main.php
├── Controllers/
│ ├── BaseController.php
│ └── UserController.php
└── Core/
├── Router.php
└── Application.phpBase Controller
php
<?php
namespace App\Controllers;
abstract class BaseController {
protected function render($view, $data = []) {
extract($data);
ob_start();
include __DIR__ . "/../Views/{$view}.php";
$content = ob_get_clean();
include __DIR__ . "/../Views/layouts/main.php";
}
protected function redirect($url) {
header("Location: {$url}");
exit;
}
protected function json($data) {
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
}
?>User Controller
php
<?php
namespace App\Controllers;
use App\Models\User;
use App\Repositories\UserRepository;
class UserController extends BaseController {
private $userRepository;
public function __construct(UserRepository $userRepository) {
$this->userRepository = $userRepository;
}
public function index() {
$users = $this->userRepository->findAll();
$this->render('users/index', ['users' => $users]);
}
public function show($id) {
$user = $this->userRepository->find($id);
if (!$user) {
$this->redirect('/users');
}
$this->render('users/show', ['user' => $user]);
}
public function create() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$user = new User($_POST['name'], $_POST['email']);
$this->userRepository->save($user);
$this->redirect('/users');
} catch (Exception $e) {
$this->render('users/create', ['error' => $e->getMessage()]);
}
} else {
$this->render('users/create');
}
}
}
?>Configuration Management
Environment-Based Configuration
php
<?php
// config/app.php
return [
'name' => $_ENV['APP_NAME'] ?? 'My Application',
'debug' => $_ENV['APP_DEBUG'] ?? false,
'url' => $_ENV['APP_URL'] ?? 'http://localhost',
'database' => [
'host' => $_ENV['DB_HOST'] ?? 'localhost',
'name' => $_ENV['DB_NAME'] ?? 'myapp',
'user' => $_ENV['DB_USER'] ?? 'root',
'pass' => $_ENV['DB_PASS'] ?? '',
],
'mail' => [
'driver' => $_ENV['MAIL_DRIVER'] ?? 'smtp',
'host' => $_ENV['MAIL_HOST'] ?? 'localhost',
'port' => $_ENV['MAIL_PORT'] ?? 587,
]
];
?>Configuration Class
php
<?php
namespace App\Core;
class Config {
private static $config = [];
public static function load($file) {
$path = __DIR__ . "/../../config/{$file}.php";
if (file_exists($path)) {
self::$config[$file] = require $path;
}
}
public static function get($key, $default = null) {
$keys = explode('.', $key);
$value = self::$config;
foreach ($keys as $k) {
if (!isset($value[$k])) {
return $default;
}
$value = $value[$k];
}
return $value;
}
}
// Usage
Config::load('app');
Config::load('database');
$appName = Config::get('app.name');
$dbHost = Config::get('app.database.host');
?>Error Handling Structure
Custom Exception Classes
php
<?php
namespace App\Exceptions;
class ValidationException extends \Exception {
private $errors;
public function __construct($errors, $message = "Validation failed") {
parent::__construct($message);
$this->errors = $errors;
}
public function getErrors() {
return $this->errors;
}
}
class DatabaseException extends \Exception {
// Custom database exception handling
}
?>Global Error Handler
php
<?php
namespace App\Core;
use App\Exceptions\ValidationException;
class ErrorHandler {
public static function register() {
set_error_handler([self::class, 'handleError']);
set_exception_handler([self::class, 'handleException']);
register_shutdown_function([self::class, 'handleShutdown']);
}
public static function handleError($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
return false;
}
throw new \ErrorException($message, 0, $severity, $file, $line);
}
public static function handleException($exception) {
if ($exception instanceof ValidationException) {
self::renderValidationError($exception);
} else {
self::renderGenericError($exception);
}
}
public static function handleShutdown() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR])) {
self::handleException(new \ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']));
}
}
private static function renderValidationError(ValidationException $e) {
// Render validation error page
}
private static function renderGenericError(\Exception $e) {
// Render generic error page
}
}
?>Application Bootstrap
Bootstrap File
php
<?php
// bootstrap.php
// Define constants
define('ROOT_PATH', __DIR__);
define('APP_PATH', ROOT_PATH . '/src');
define('CONFIG_PATH', ROOT_PATH . '/config');
// Load environment variables
if (file_exists(ROOT_PATH . '/.env')) {
$lines = file(ROOT_PATH . '/.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (strpos($line, '=') !== false && strpos($line, '#') !== 0) {
list($key, $value) = explode('=', $line, 2);
$_ENV[trim($key)] = trim($value);
}
}
}
// Autoloader
require_once ROOT_PATH . '/vendor/autoload.php';
// Error handling
use App\Core\ErrorHandler;
ErrorHandler::register();
// Load configuration
use App\Core\Config;
Config::load('app');
Config::load('database');
// Set error reporting based on environment
if (Config::get('app.debug')) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
} else {
error_reporting(0);
ini_set('display_errors', 0);
}
?>Next Steps
Now that you understand how to structure PHP programs, let's explore PHP's data types in detail in Data Types.
Practice Exercises
- Create a multi-file project with proper namespace organization
- Implement a simple MVC structure for a blog application
- Set up autoloading for your custom classes
- Create a configuration system that supports different environments
- Build a basic error handling system with custom exceptions
Understanding program structure is essential for building maintainable PHP applications!