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
?>

▶ Example: A Practical Trait Collection

Output:

TEXT 📖 Display only
value<br>
value
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']));
?>

Output:

TEXT 📖 Display only
{"name":"Mechanical Keyboard","price":299,"description":""}
Array
(
)

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

▶ Example: Late Static Binding in Practice — A Simple ORM

Output:

TEXT 📖 Display only
Output from echo/print statements
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
?>

Output:

TEXT 📖 Display only
Output displayed

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.

▶ Example: Static Factory Methods for Flexible Object Creation

Output:

TEXT 📖 Display only
{1->name}: {1->role}
{value->name}: {value->role}
PHP
<?php
class User {
    private function __construct(
        public string $name,
        public string $role
    ) {}

    public static function createAdmin(string $name): self {
        return new self($name, 'admin');
    }

    public static function createMember(string $name): self {
        return new self($name, 'member');
    }
}

$admin = User::createAdmin("John");
$member = User::createMember("Jane");
echo "{$admin->name}: {$admin->role}";   // John: admin
echo "{$member->name}: {$member->role}"; // Jane: member
?>

Output:

TEXT 📖 Display only
Output displayed

❓ FAQ

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.

📖 Summary

📝 Exercises

  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 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%

🙏 帮我们做得更好

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

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