PHP: Traits and Static Members

PHP has two features that most other mainstream languages don't have (or don't design the same way): Traits and late static binding. This lesson gives you both of PHP's unique power tools to write more elegant code.

1. Traits — PHP's Code Reuse Magic

PHP has single inheritance—a class can only have one parent. But sometimes unrelated classes need to share the same piece of code (like "record timestamps" or "generate unique IDs"). Traits were born for this.

PHP
<?php
// Define a Trait
trait HasTimestamp {
    private string $createdAt;
    private string $updatedAt;
    
    public function touch(): void {
        $now = date("Y-m-d H:i:s");
        if (!isset($this->createdAt)) {
            $this->createdAt = $now;
        }
        $this->updatedAt = $now;
    }
    
    public function getCreatedAt(): string {
        return $this->createdAt ?? 'Unknown';
    }
    
    public function getUpdatedAt(): string {
        return $this->updatedAt ?? 'Unknown';
    }
}

trait HasUuid {
    public function generateUuid(): string {
        return uniqid() . bin2hex(random_bytes(8));
    }
}

// Use Traits in completely different classes
class User {
    use HasTimestamp, HasUuid;  // Use multiple Traits
    
    public function __construct(
        public string $name
    ) {
        $this->touch();
    }
}

class Article {
    use HasTimestamp;
    
    public function __construct(
        public string $title,
        public string $content
    ) {
        $this->touch();
    }
}

$user = new User("John");
echo $user->name . " created at " . $user->getCreatedAt() . "<br>";
echo "UUID: " . $user->generateUuid() . "<br>";

$article = new Article("PHP Traits Tutorial", "Main content...");
echo $article->title . " last updated at " . $article->getUpdatedAt() . "<br>";
?>
💡 Tip: Trait vs. Inheritance: use Traits when multiple unrelated classes (User ≠ Article) need to share the same logic. If child classes have an is-a relationship (Dog is-a Animal), use inheritance.


2. Trait Conflict Resolution

When multiple traits have methods with the same name:

PHP
<?php
trait Logger {
    public function log(string $msg): void {
        echo "[LOG] {$msg}<br>";
    }
}

trait FileLogger {
    public function log(string $msg): void {
        echo "[FILE] {$msg}<br>";
    }
}

class App {
    use Logger, FileLogger {
        // Replace Logger's log with FileLogger's log
        FileLogger::log insteadof Logger;
        
        // Give the replaced method an alias so it's still callable
        Logger::log as logSimple;
    }
}

$app = new App();
$app->log("App started");      // [FILE] App started
$app->logSimple("App started"); // [LOG] App started
?>

▶ サンプル: A Practical Trait Collection

PHP
<?php
trait Arrayable {
    public function toArray(): array {
        return get_object_vars($this);
    }
    
    public function toJson(): string {
        return json_encode($this->toArray(), JSON_UNESCAPED_UNICODE);
    }
}

trait Validatable {
    public function validate(array $rules): array {
        $errors = [];
        foreach ($rules as $field => $rule) {
            $value = $this->$field ?? null;
            if (str_contains($rule, 'required') && empty($value)) {
                $errors[$field] = "{$field} is required";
            }
        }
        return $errors;
    }
}

class Product {
    use Arrayable, Validatable;
    
    public function __construct(
        public string $name,
        public float $price,
        public string $description = '',
    ) {}
}

$p = new Product("Mechanical Keyboard", 299);
echo $p->toJson() . "<br>";
print_r($p->validate(['name' => 'required', 'price' => 'required']));
?>
▶ 試してみよう

3. Static Properties and Methods

Static members belong to the class itself, not to instances. All objects share the same static property:

PHP
<?php
class Counter {
    private static int $count = 0;
    
    public function __construct() {
        self::$count++;  // self:: accesses static members
    }
    
    public static function getCount(): int {
        return self::$count;
    }
}

echo Counter::getCount();  // 0 (call with ClassName::method)
$a = new Counter();
$b = new Counter();
$c = new Counter();
echo Counter::getCount();  // 3 (all instances share a single $count)
?>
Instance Property $this-> Static Property self::$
Belongs to Each object The class itself
Memory One per object One global copy
Access $obj->prop Class::$prop

4. self:: vs static:: (Late Static Binding)

This is one of PHP's most subtle—and most commonly confused—concepts:

PHP
<?php
class ParentClass {
    public static function who(): string {
        return 'Parent';
    }
    
    public static function test(): string {
        return self::who();   // self:: always points to the defining class (compile-time binding)
    }
    
    public static function testLate(): string {
        return static::who(); // static:: points to the actual calling class (runtime binding)
    }
}

class ChildClass extends ParentClass {
    public static function who(): string {
        return 'Child';
    }
}

echo ChildClass::test();     // "Parent"  ← self:: bound to ParentClass
echo ChildClass::testLate(); // "Child"   ← static:: bound to ChildClass
?>
self:: static::
Binding time Compile time Runtime
Points to The class where the method is defined The actual class that calls the method
Best for Utility methods, constants Static methods that subclasses should override

▶ サンプル: Late Static Binding in Practice — A Simple ORM

PHP
<?php
abstract class Model {
    // static:: lets subclasses return their own table name
    public static function table(): string {
        // Default: class name → snake_case → plural
        return strtolower(static::class) . 's';
    }
    
    public static function find(int $id): string {
        return "SELECT * FROM " . static::table() . " WHERE id = {$id}";
    }
}

class User extends Model {}
class Product extends Model {
    public static function table(): string {
        return 'products';  // Custom table name
    }
}

echo User::find(1);     // SELECT * FROM users WHERE id = 1
echo Product::find(5);  // SELECT * FROM products WHERE id = 5
// static::table() returns each subclass's own table name
?>
▶ 試してみよう

5. Class Constants (const)

Class constants don't change between objects—they're perfect for configuration values and conventions:

PHP
<?php
class HttpStatus {
    public const OK = 200;
    public const NOT_FOUND = 404;
    public const INTERNAL_ERROR = 500;
    
    public static function getMessage(int $code): string {
        return match($code) {
            self::OK            => 'OK',
            self::NOT_FOUND     => 'Not Found',
            self::INTERNAL_ERROR => 'Internal Server Error',
            default             => 'Unknown',
        };
    }
}

echo HttpStatus::OK;            // 200
echo HttpStatus::NOT_FOUND;     // 404
echo HttpStatus::getMessage(404); // Not Found

// PHP 8.1+ final const
class Config {
    final public const APP_NAME = 'MyBlog';
    // Subclasses can't override final constants
}
?>
💡 Tip: Class constants are better than define()—they have namespacing, autocomplete, and don't pollute the global scope. If a group of values belongs to the same concept (HTTP status codes, user roles), organize them with class constants.

❓ よくある質問

Q How do I choose between Trait and abstract class?
A Unrelated classes sharing code → Trait. Is-a relationship + shared properties → abstract class. A class can use multiple traits but can only extend one abstract class.
Q When does the self:: vs static:: distinction actually matter?
A When you write static methods that will be inherited. For example, a framework's Model base class needs to know "which specific subclass is calling this static method." For utility classes (static methods that are never inherited), self:: is enough.
Q Can traits define properties? Constants?
A Traits can define properties and methods but not constants (PHP doesn't support this yet). Multiple traits can't define properties with the same name—that's a fatal error.

📖 まとめ

📝 練習問題

  1. Write a Loggable Trait with a log($message) method (auto-prefixes a timestamp). Make two unrelated classes, User and Product, use it.
  2. Write a parent Database class, use static:: to implement a table() method, with subclasses User/Order returning users/orders respectively. Write a static all() method that returns SELECT * FROM [table].
  3. Create an AppConfig class, use class constants to define APP_NAME, MAX_UPLOAD_SIZE, and DEFAULT_LANGUAGE. Write a static display() method that outputs all configuration values.
Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%