PHP: OOP Practice Project

You've studied 5 lessons of object-oriented programming. Now let's weld all that knowledge together—two projects: a full user system covering inheritance/enums/traits/interfaces, and a simple framework that gives you a first taste of MVC architecture.

1. Project One: User Role Management System

Combine classes, inheritance, enums, traits, and interfaces to build a complete user role management framework.

▶ Example: Complete OOP User System

Output:

TEXT 📖 Display only
<h4>{root->getLogIdentifier()}</h4>
Permissions: " . implode(', ', root->getPermissions()) . "<br>
root<br>
PHP
<?php
// === Enums ===
enum UserRole: string {
    case ADMIN  = 'admin';
    case EDITOR = 'editor';
    case MEMBER = 'member';
    case GUEST  = 'guest';
}

enum UserStatus: string {
    case ACTIVE   = 'active';
    case INACTIVE = 'inactive';
    case BANNED   = 'banned';
}

// === Interfaces ===
interface Loggable {
    public function getLogIdentifier(): string;
}

interface Manageable {
    public function canAccess(string $resource): bool;
    public function getPermissions(): array;
}

// === Traits ===
trait HasTimestamps {
    public readonly string $createdAt;
    public readonly string $updatedAt;

    public function touch(): void {
        $now = date("Y-m-d H:i:s");
        if (!isset($this->createdAt)) {
            $this->createdAt = $now;
        }
        $this->updatedAt = $now;
    }
}

trait ArraySerializable {
    public function toArray(): array {
        return get_object_vars($this);
    }
}

// === Base Class ===
abstract class User implements Loggable, Manageable {
    use HasTimestamps, ArraySerializable;

    public function __construct(
        public readonly string $id,
        public string $name,
        public string $email,
        public UserRole $role = UserRole::MEMBER,
        public UserStatus $status = UserStatus::ACTIVE,
    ) {
        $this->touch();
    }

    public function getLogIdentifier(): string {
        return "User#{$this->id} ({$this->name})";
    }

    abstract public function canAccess(string $resource): bool;
    abstract public function getPermissions(): array;
    abstract public function getRoleLabel(): string;
}

// === Concrete Role Classes ===
class Admin extends User {
    public function __construct(
        string $id, string $name, string $email
    ) {
        parent::__construct($id, $name, $email, UserRole::ADMIN);
    }

    public function canAccess(string $resource): bool {
        return true;  // Admin can access everything
    }

    public function getPermissions(): array {
        return ['create', 'read', 'update', 'delete', 'manage_users', 'system_config'];
    }

    public function getRoleLabel(): string {
        return '🔧 Admin';
    }
}

class Editor extends User {
    public function __construct(
        string $id, string $name, string $email
    ) {
        parent::__construct($id, $name, $email, UserRole::EDITOR);
    }

    public function canAccess(string $resource): bool {
        return in_array($resource, ['articles', 'media', 'comments']);
    }

    public function getPermissions(): array {
        return ['create', 'read', 'update', 'manage_comments'];
    }

    public function getRoleLabel(): string {
        return '✏️ Editor';
    }
}

class Member extends User {
    public function __construct(
        string $id, string $name, string $email
    ) {
        parent::__construct($id, $name, $email, UserRole::MEMBER);
    }

    public function canAccess(string $resource): bool {
        return in_array($resource, ['articles', 'comments', 'profile']);
    }

    public function getPermissions(): array {
        return ['read', 'comment'];
    }

    public function getRoleLabel(): string {
        return '👤 Member';
    }
}

// === Access Manager ===
class AccessManager {
    public function checkAccess(User $user, string $resource): string {
        if ($user->status === UserStatus::BANNED) {
            return "{$user->getRoleLabel()} {$user->name}: Banned, access denied for {$resource}";
        }
        
        $allowed = $user->canAccess($resource)
            ? "✅ Access Granted" : "❌ Access Denied";
        
        return "{$user->getRoleLabel()} {$user->name}: {$allowed} {$resource}";
    }
    
    public function listAllAccess(array $users, array $resources): void {
        foreach ($users as $user) {
            echo "<h4>{$user->getLogIdentifier()}</h4>";
            echo "Permissions: " . implode(', ', $user->getPermissions()) . "<br>";
            foreach ($resources as $res) {
                echo $this->checkAccess($user, $res) . "<br>";
            }
        }
    }
}

// === Test the System ===
$users = [
    new Admin("u1", "John", "admin@example.com"),
    new Editor("u2", "Jane", "editor@example.com"),
    new Member("u3", "Bob", "member@example.com"),
];

$resources = ['articles', 'media', 'comments', 'system_config', 'manage_users'];

$manager = new AccessManager();
$manager->listAllAccess($users, $resources);

// Test enum + serialization
echo "<br>Admin data: " . json_encode($users[0]->toArray(), JSON_UNESCAPED_UNICODE);
?>

Output:

TEXT 📖 Display only
User#u1 (John)
Permissions: create, read, update, delete, manage_users, system_config
Access Granted: articles
Access Granted: media

2. Project Two: Simple MVC Framework

Understand the MVC concept of Router, Controller, and View—this is the core idea behind frameworks like Laravel.

▶ Example: Mini MVC

Output:

TEXT 📖 Display only
value
<h2>404 — Page Not Found</h2>
{"name":"Alice","age":25}
PHP
<?php
// === Router ===
class Router {
    private array $routes = [];

    public function get(string $path, callable $handler): void {
        $this->routes['GET'][$path] = $handler;
    }

    public function post(string $path, callable $handler): void {
        $this->routes['POST'][$path] = $handler;
    }

    public function dispatch(string $method, string $uri): void {
        $path = parse_url($uri, PHP_URL_PATH);
        $handler = $this->routes[$method][$path] ?? null;

        if ($handler) {
            echo $handler($_GET);
        } else {
            http_response_code(404);
            echo "<h2>404 — Page Not Found</h2>";
        }
    }
}

// === View Base ===
class View {
    public static function render(string $template, array $data = []): string {
        extract($data);
        ob_start();
        include __DIR__ . "/{$template}.php";
        return ob_get_clean();
    }
}

// === Controller Base ===
abstract class Controller {
    protected function json(array $data, int $code = 200): void {
        http_response_code($code);
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode($data, JSON_UNESCAPED_UNICODE);
    }
}

// === Concrete Controller ===
class HomeController extends Controller {
    public function index(): void {
        echo View::render('home', [
            'title'   => 'Home',
            'message' => 'Welcome to my blog!',
        ]);
    }

    public function about(): void {
        echo View::render('about', [
            'title'   => 'About Us',
            'team'    => ['John', 'Jane', 'Bob'],
            'version' => '1.0.0',
        ]);
    }

    public function api(): void {
        $this->json([
            'status' => 'ok',
            'data'   => ['version' => '1.0.0', 'uptime' => time()],
        ]);
    }
}

// === Set Up Routes ===
$router = new Router();
$home = new HomeController();

$router->get('/', fn() => $home->index());
$router->get('/about', fn() => $home->about());
$router->get('/api/status', fn() => $home->api());

// === Launch the App ===
$router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
?>

Output:

TEXT 📖 Display only
<h1>Welcome to MyBlog</h1>  (or 404 Page Not Found for unknown routes)

The accompanying view file home.php:

PHP
<!DOCTYPE html>
<html lang="en">
<head><title><?= $title ?></title></head>
<body>
    <h1><?= $message ?></h1>
    <p>This page is built using MVC principles.</p>
</body>
</html>

3. The Mental Shift: From Procedural to OOP

Procedural Thinking OOP Thinking
"I need to read user data" → write a getUser() function "What is a user? What are its properties and behaviors?" → design a User class
Data and functions scattered across files Data + behavior encapsulated in a class
Code is duplicated → copy-paste or extract a function Code is duplicated → use inheritance or Traits
Pass 'admin' strings for permissions Pass UserRole::ADMIN enum, type-safe
💡 Tip: OOP is a means, not an end. Procedural is perfectly fine for small scripts. Shift to OOP when you see these signs: (1) the same parameters keep getting passed between functions; (2) global variables are piling up; (3) conditionals are based on "type strings" (if ($role === 'admin')).

▶ Example: Simple Dependency Injection Container

Output:

TEXT 📖 Display only
(no visible output - class defined)
PHP
<?php
class Container {
    private array $bindings = [];

    public function bind(string $abstract, callable $concrete): void {
        $this->bindings[$abstract] = $concrete;
    }

    public function resolve(string $abstract): object {
        if (!isset($this->bindings[$abstract])) {
            throw new Exception("No binding for {$abstract}");
        }
        return ($this->bindings[$abstract])($this);
    }
}

$container = new Container();
$container->bind(PDO::class, fn() => new PDO('sqlite::memory:'));
$container->bind(UserRepository::class, fn($c) => new UserRepository($c->resolve(PDO::class)));

$repo = $container->resolve(UserRepository::class);
?>

Output:

TEXT 📖 Display only
Output displayed

❓ FAQ

Q What does a Controller actually do in MVC?
A The Controller is the "traffic director"—it receives the request, calls the Model to get data, and chooses the View for rendering. It doesn't handle business logic itself (that's the Model's job) and doesn't write HTML (that's the View's job).
Q Should I build my own framework or use an existing one?
A Build a mini-framework during the learning phase to understand the principles (just like this lesson). Use Laravel/Symfony for production. After building your own Router/Controller/View once, using Laravel makes you say, "Ah, so that's what's happening behind the scenes."

❓ FAQ

Q How do I test the MVC mini-framework I built?
A Start Apache or the built-in PHP server (php -S localhost:8000) and navigate to http://localhost:8000 in your browser. The dispatcher routes to the appropriate controller based on the URL. Use var_dump() or error_log() to debug step by step.

📖 Summary

📝 Exercises

  1. Based on the user system, add a Guest role (can only access articles, no editing permissions at all) and add it to the test suite.
  2. Add an addRoute('GET/POST', $path, $handler) method to the router so a single call can register both methods.
  3. Add a UserController to the MVC framework that implements a user list (read from an array) and a user detail page (query by ID).
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏