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.

▶ サンプル: Complete OOP User System

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);
?>
論理コード 130 行(40 行制限超過、参照専用)

2. Project Two: Simple MVC Framework

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

▶ サンプル: Mini MVC

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']);
?>
論理コード 63 行(40 行制限超過、参照専用)

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')).

❓ よくある質問

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."

❓ よくある質問

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.

📖 まとめ

📝 練習問題

  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 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%