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
// 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>";
?>
2. Trait Conflict Resolution
When multiple traits have methods with the same name:
<?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
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
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
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
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
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
}
?>
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.
❓ よくある質問
use multiple traits but can only extend one abstract class.self:: vs static:: distinction actually matter?self:: is enough.📖 まとめ
- Traits solve the single-inheritance limitation, letting unrelated classes share code
use Trait1, Trait2 { ... }resolves conflicts withinsteadof+asself::$propaccesses static properties/methods (belong to the class, shared by all instances)self::binds at compile time (points to where it's defined),static::binds at runtime (points to the caller)- Class constants
public const KEY = valueare better than globaldefine()
📝 練習問題
- Write a
LoggableTrait with alog($message)method (auto-prefixes a timestamp). Make two unrelated classes,UserandProduct,useit. - Write a parent
Databaseclass, usestatic::to implement atable()method, with subclassesUser/Orderreturningusers/ordersrespectively. Write a staticall()method that returnsSELECT * FROM [table]. - Create an
AppConfigclass, use class constants to define APP_NAME, MAX_UPLOAD_SIZE, and DEFAULT_LANGUAGE. Write a staticdisplay()method that outputs all configuration values.