1. php
  2. /object oriented
  3. /interfaces-traits

PHP Interfaces and Traits

Introduction to Interfaces and Traits

PHP provides two powerful mechanisms for code organization and reuse beyond traditional inheritance: Interfaces and Traits. Interfaces define contracts that classes must follow, while traits provide horizontal code reuse, solving the limitations of single inheritance.

Both features enable more flexible and maintainable object-oriented designs by promoting composition over inheritance and establishing clear contracts between components.

PHP Interfaces

Interfaces define method signatures that implementing classes must provide. They establish contracts without providing implementation details.

Basic Interface Implementation

<?php
interface Shape {
    public function calculateArea();
    public function calculatePerimeter();
    public function getType();
}

interface Drawable {
    public function draw();
    public function setColor($color);
}

class Circle implements Shape, Drawable {
    private $radius;
    private $color = 'black';
    
    public function __construct($radius) {
        $this->radius = $radius;
    }
    
    public function calculateArea() {
        return pi() * pow($this->radius, 2);
    }
    
    public function calculatePerimeter() {
        return 2 * pi() * $this->radius;
    }
    
    public function getType() {
        return 'Circle';
    }
    
    public function draw() {
        return "Drawing a {$this->color} circle with radius {$this->radius}";
    }
    
    public function setColor($color) {
        $this->color = $color;
    }
}

class Rectangle implements Shape, Drawable {
    private $width;
    private $height;
    private $color = 'black';
    
    public function __construct($width, $height) {
        $this->width = $width;
        $this->height = $height;
    }
    
    public function calculateArea() {
        return $this->width * $this->height;
    }
    
    public function calculatePerimeter() {
        return 2 * ($this->width + $this->height);
    }
    
    public function getType() {
        return 'Rectangle';
    }
    
    public function draw() {
        return "Drawing a {$this->color} rectangle {$this->width}x{$this->height}";
    }
    
    public function setColor($color) {
        $this->color = $color;
    }
}

// Usage with polymorphism
function processShape(Shape $shape) {
    echo "Shape: " . $shape->getType() . "\n";
    echo "Area: " . $shape->calculateArea() . "\n";
    echo "Perimeter: " . $shape->calculatePerimeter() . "\n";
}

$circle = new Circle(5);
$rectangle = new Rectangle(4, 6);

processShape($circle);    // Works with any Shape implementation
processShape($rectangle); // Works with any Shape implementation
?>

Real-World Interface Example

<?php
interface PaymentProcessorInterface {
    public function processPayment($amount, $currency = 'USD');
    public function refundPayment($transactionId, $amount);
    public function getTransactionStatus($transactionId);
    public function validatePaymentData($paymentData);
}

interface LoggerInterface {
    public function log($message, $level = 'info');
    public function error($message);
    public function warning($message);
    public function info($message);
}

class StripePaymentProcessor implements PaymentProcessorInterface {
    private $apiKey;
    private $logger;
    
    public function __construct($apiKey, LoggerInterface $logger) {
        $this->apiKey = $apiKey;
        $this->logger = $logger;
    }
    
    public function processPayment($amount, $currency = 'USD') {
        $this->logger->info("Processing Stripe payment: {$amount} {$currency}");
        
        // Stripe-specific payment processing logic
        $transactionId = 'stripe_' . uniqid();
        
        // Simulate API call
        if ($this->validateAmount($amount)) {
            $this->logger->info("Stripe payment successful: {$transactionId}");
            return [
                'success' => true,
                'transaction_id' => $transactionId,
                'amount' => $amount,
                'currency' => $currency
            ];
        }
        
        $this->logger->error("Stripe payment failed");
        return ['success' => false, 'error' => 'Payment failed'];
    }
    
    public function refundPayment($transactionId, $amount) {
        $this->logger->info("Processing Stripe refund: {$transactionId} for {$amount}");
        // Stripe refund logic
        return ['success' => true, 'refund_id' => 'ref_' . uniqid()];
    }
    
    public function getTransactionStatus($transactionId) {
        // Stripe transaction status check
        return 'completed';
    }
    
    public function validatePaymentData($paymentData) {
        return isset($paymentData['card_number']) && 
               isset($paymentData['expiry_date']) && 
               isset($paymentData['cvv']);
    }
    
    private function validateAmount($amount) {
        return is_numeric($amount) && $amount > 0;
    }
}

class PayPalPaymentProcessor implements PaymentProcessorInterface {
    private $clientId;
    private $clientSecret;
    private $logger;
    
    public function __construct($clientId, $clientSecret, LoggerInterface $logger) {
        $this->clientId = $clientId;
        $this->clientSecret = $clientSecret;
        $this->logger = $logger;
    }
    
    public function processPayment($amount, $currency = 'USD') {
        $this->logger->info("Processing PayPal payment: {$amount} {$currency}");
        
        // PayPal-specific payment processing logic
        $transactionId = 'pp_' . uniqid();
        
        $this->logger->info("PayPal payment successful: {$transactionId}");
        return [
            'success' => true,
            'transaction_id' => $transactionId,
            'amount' => $amount,
            'currency' => $currency
        ];
    }
    
    public function refundPayment($transactionId, $amount) {
        $this->logger->info("Processing PayPal refund: {$transactionId} for {$amount}");
        // PayPal refund logic
        return ['success' => true, 'refund_id' => 'pp_ref_' . uniqid()];
    }
    
    public function getTransactionStatus($transactionId) {
        // PayPal transaction status check
        return 'completed';
    }
    
    public function validatePaymentData($paymentData) {
        return isset($paymentData['email']) && 
               filter_var($paymentData['email'], FILTER_VALIDATE_EMAIL);
    }
}

class FileLogger implements LoggerInterface {
    private $logFile;
    
    public function __construct($logFile = 'application.log') {
        $this->logFile = $logFile;
    }
    
    public function log($message, $level = 'info') {
        $timestamp = date('Y-m-d H:i:s');
        $logEntry = "[{$timestamp}] [{$level}] {$message}\n";
        file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
    }
    
    public function error($message) {
        $this->log($message, 'ERROR');
    }
    
    public function warning($message) {
        $this->log($message, 'WARNING');
    }
    
    public function info($message) {
        $this->log($message, 'INFO');
    }
}

// Usage demonstrating polymorphism through interfaces
class PaymentService {
    private $processor;
    
    public function __construct(PaymentProcessorInterface $processor) {
        $this->processor = $processor;
    }
    
    public function chargeCustomer($amount, $paymentData) {
        if (!$this->processor->validatePaymentData($paymentData)) {
            return ['success' => false, 'error' => 'Invalid payment data'];
        }
        
        return $this->processor->processPayment($amount);
    }
    
    public function refundCustomer($transactionId, $amount) {
        return $this->processor->refundPayment($transactionId, $amount);
    }
}

// Can switch payment processors easily
$logger = new FileLogger();
$stripeProcessor = new StripePaymentProcessor('sk_test_123', $logger);
$paypalProcessor = new PayPalPaymentProcessor('client_123', 'secret_456', $logger);

$paymentService = new PaymentService($stripeProcessor);
$result = $paymentService->chargeCustomer(100, ['card_number' => '4242424242424242']);

// Easy to switch to different processor
$paymentService = new PaymentService($paypalProcessor);
$result = $paymentService->chargeCustomer(100, ['email' => '[email protected]']);
?>

PHP Traits

Traits enable horizontal code reuse by allowing methods to be shared across multiple classes without inheritance.

Basic Trait Usage

<?php
trait TimestampableTrait {
    private $createdAt;
    private $updatedAt;
    
    public function setCreatedAt($date = null) {
        $this->createdAt = $date ?: new DateTime();
    }
    
    public function setUpdatedAt($date = null) {
        $this->updatedAt = $date ?: new DateTime();
    }
    
    public function getCreatedAt() {
        return $this->createdAt;
    }
    
    public function getUpdatedAt() {
        return $this->updatedAt;
    }
    
    public function touch() {
        $this->setUpdatedAt();
    }
    
    public function getAge() {
        if (!$this->createdAt) {
            return null;
        }
        
        return $this->createdAt->diff(new DateTime())->format('%d days');
    }
}

trait ValidatableTrait {
    private $validationErrors = [];
    
    abstract protected function getValidationRules();
    
    public function validate() {
        $this->validationErrors = [];
        $rules = $this->getValidationRules();
        
        foreach ($rules as $field => $fieldRules) {
            $value = $this->getFieldValue($field);
            
            foreach ($fieldRules as $rule => $parameter) {
                if (!$this->applyValidationRule($field, $value, $rule, $parameter)) {
                    break; // Stop validation on first error for this field
                }
            }
        }
        
        return empty($this->validationErrors);
    }
    
    public function getValidationErrors() {
        return $this->validationErrors;
    }
    
    public function isValid() {
        return $this->validate();
    }
    
    private function applyValidationRule($field, $value, $rule, $parameter) {
        switch ($rule) {
            case 'required':
                if ($parameter && empty($value)) {
                    $this->validationErrors[$field] = ucfirst($field) . " is required";
                    return false;
                }
                break;
                
            case 'min_length':
                if (!empty($value) && strlen($value) < $parameter) {
                    $this->validationErrors[$field] = ucfirst($field) . " must be at least {$parameter} characters";
                    return false;
                }
                break;
                
            case 'max_length':
                if (!empty($value) && strlen($value) > $parameter) {
                    $this->validationErrors[$field] = ucfirst($field) . " must not exceed {$parameter} characters";
                    return false;
                }
                break;
                
            case 'email':
                if (!empty($value) && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
                    $this->validationErrors[$field] = ucfirst($field) . " must be a valid email";
                    return false;
                }
                break;
        }
        
        return true;
    }
    
    private function getFieldValue($field) {
        if (property_exists($this, $field)) {
            return $this->$field;
        }
        
        $getter = 'get' . ucfirst($field);
        if (method_exists($this, $getter)) {
            return $this->$getter();
        }
        
        return null;
    }
}

class User {
    use TimestampableTrait, ValidatableTrait;
    
    private $name;
    private $email;
    private $password;
    
    public function __construct($name, $email, $password) {
        $this->name = $name;
        $this->email = $email;
        $this->password = $password;
        $this->setCreatedAt();
    }
    
    protected function getValidationRules() {
        return [
            'name' => ['required' => true, 'min_length' => 2, 'max_length' => 50],
            'email' => ['required' => true, 'email' => true],
            'password' => ['required' => true, 'min_length' => 8]
        ];
    }
    
    public function getName() {
        return $this->name;
    }
    
    public function getEmail() {
        return $this->email;
    }
    
    public function setEmail($email) {
        $this->email = $email;
        $this->touch();
    }
    
    public function updateProfile($name, $email) {
        $this->name = $name;
        $this->email = $email;
        $this->touch();
    }
}

class Article {
    use TimestampableTrait, ValidatableTrait;
    
    private $title;
    private $content;
    private $authorId;
    
    public function __construct($title, $content, $authorId) {
        $this->title = $title;
        $this->content = $content;
        $this->authorId = $authorId;
        $this->setCreatedAt();
    }
    
    protected function getValidationRules() {
        return [
            'title' => ['required' => true, 'min_length' => 5, 'max_length' => 200],
            'content' => ['required' => true, 'min_length' => 50],
            'authorId' => ['required' => true]
        ];
    }
    
    public function getTitle() {
        return $this->title;
    }
    
    public function getContent() {
        return $this->content;
    }
    
    public function getAuthorId() {
        return $this->authorId;
    }
    
    public function updateContent($content) {
        $this->content = $content;
        $this->touch();
    }
}

// Usage
$user = new User("John Doe", "[email protected]", "secretpassword");

if ($user->isValid()) {
    echo "User created: " . $user->getName() . "\n";
    echo "Created: " . $user->getCreatedAt()->format('Y-m-d H:i:s') . "\n";
    echo "Age: " . $user->getAge() . "\n";
} else {
    print_r($user->getValidationErrors());
}

$article = new Article("My First Article", "This is the content of my first article...", 1);
if ($article->isValid()) {
    echo "Article created: " . $article->getTitle() . "\n";
}
?>

Advanced Trait Features

Trait Conflict Resolution

<?php
trait DatabaseLogger {
    public function log($message) {
        // Log to database
        echo "DB Log: {$message}\n";
    }
    
    public function logError($error) {
        $this->log("ERROR: {$error}");
    }
}

trait FileLogger {
    public function log($message) {
        // Log to file
        echo "File Log: {$message}\n";
    }
    
    public function logWarning($warning) {
        $this->log("WARNING: {$warning}");
    }
}

trait EmailLogger {
    public function log($message) {
        // Log via email
        echo "Email Log: {$message}\n";
    }
    
    public function logCritical($critical) {
        $this->log("CRITICAL: {$critical}");
    }
}

class Logger {
    use DatabaseLogger, FileLogger, EmailLogger {
        DatabaseLogger::log insteadof FileLogger, EmailLogger;
        FileLogger::log as fileLog;
        EmailLogger::log as emailLog;
        DatabaseLogger::logError as logDbError;
    }
    
    public function logToAll($message) {
        $this->log($message);        // Uses DatabaseLogger::log
        $this->fileLog($message);    // Uses FileLogger::log
        $this->emailLog($message);   // Uses EmailLogger::log
    }
    
    public function logErrorToDatabase($error) {
        $this->logDbError($error);   // Uses DatabaseLogger::logError
    }
}

$logger = new Logger();
$logger->logToAll("Application started");
$logger->logErrorToDatabase("Database connection failed");
?>

Trait Composition and Requirements

<?php
trait CacheableTrait {
    private $cache = [];
    private $cacheExpiry = 3600; // 1 hour
    
    // Require implementing class to provide these methods
    abstract protected function getCacheKey($key);
    abstract protected function shouldCache();
    
    protected function getCached($key) {
        if (!$this->shouldCache()) {
            return null;
        }
        
        $cacheKey = $this->getCacheKey($key);
        
        if (isset($this->cache[$cacheKey])) {
            $cached = $this->cache[$cacheKey];
            
            if (time() - $cached['timestamp'] < $this->cacheExpiry) {
                return $cached['data'];
            } else {
                unset($this->cache[$cacheKey]);
            }
        }
        
        return null;
    }
    
    protected function setCache($key, $data) {
        if (!$this->shouldCache()) {
            return;
        }
        
        $cacheKey = $this->getCacheKey($key);
        $this->cache[$cacheKey] = [
            'data' => $data,
            'timestamp' => time()
        ];
    }
    
    protected function clearCache($pattern = null) {
        if ($pattern) {
            foreach ($this->cache as $key => $value) {
                if (strpos($key, $pattern) !== false) {
                    unset($this->cache[$key]);
                }
            }
        } else {
            $this->cache = [];
        }
    }
    
    protected function setCacheExpiry($seconds) {
        $this->cacheExpiry = $seconds;
    }
}

trait ApiClientTrait {
    private $baseUrl;
    private $headers = [];
    
    protected function setBaseUrl($url) {
        $this->baseUrl = rtrim($url, '/');
    }
    
    protected function setHeader($key, $value) {
        $this->headers[$key] = $value;
    }
    
    protected function makeRequest($endpoint, $method = 'GET', $data = null) {
        $url = $this->baseUrl . '/' . ltrim($endpoint, '/');
        
        // Simulate HTTP request
        $response = [
            'status' => 200,
            'data' => [
                'method' => $method,
                'url' => $url,
                'data' => $data,
                'headers' => $this->headers
            ]
        ];
        
        return $response;
    }
}

class UserRepository {
    use CacheableTrait, ApiClientTrait;
    
    public function __construct() {
        $this->setBaseUrl('https://api.example.com');
        $this->setHeader('Content-Type', 'application/json');
        $this->setCacheExpiry(1800); // 30 minutes
    }
    
    public function findUser($id) {
        // Check cache first
        $cached = $this->getCached("user_{$id}");
        if ($cached !== null) {
            return $cached;
        }
        
        // Make API request
        $response = $this->makeRequest("/users/{$id}");
        
        if ($response['status'] === 200) {
            $userData = $response['data'];
            $this->setCache("user_{$id}", $userData);
            return $userData;
        }
        
        return null;
    }
    
    public function findUsersByRole($role) {
        $cached = $this->getCached("users_role_{$role}");
        if ($cached !== null) {
            return $cached;
        }
        
        $response = $this->makeRequest("/users?role={$role}");
        
        if ($response['status'] === 200) {
            $users = $response['data'];
            $this->setCache("users_role_{$role}", $users);
            return $users;
        }
        
        return [];
    }
    
    // Required by CacheableTrait
    protected function getCacheKey($key) {
        return 'user_repo_' . $key;
    }
    
    protected function shouldCache() {
        return true; // Always cache in this implementation
    }
}

$userRepo = new UserRepository();
$user = $userRepo->findUser(123);
$admins = $userRepo->findUsersByRole('admin');
?>

Combining Interfaces and Traits

<?php
interface NotificationInterface {
    public function send($recipient, $message);
    public function setTemplate($template);
    public function getDeliveryStatus($id);
}

trait NotificationFormatterTrait {
    private $templates = [];
    
    protected function formatMessage($message, $variables = []) {
        foreach ($variables as $key => $value) {
            $message = str_replace("{{$key}}", $value, $message);
        }
        return $message;
    }
    
    protected function setTemplate($name, $template) {
        $this->templates[$name] = $template;
    }
    
    protected function getTemplate($name) {
        return $this->templates[$name] ?? null;
    }
    
    protected function loadTemplatesFromConfig($config) {
        $this->templates = $config['templates'] ?? [];
    }
}

trait NotificationLoggingTrait {
    private $notifications = [];
    
    protected function logNotification($type, $recipient, $message, $status) {
        $this->notifications[] = [
            'type' => $type,
            'recipient' => $recipient,
            'message' => $message,
            'status' => $status,
            'timestamp' => new DateTime()
        ];
    }
    
    public function getNotificationHistory() {
        return $this->notifications;
    }
    
    public function getFailedNotifications() {
        return array_filter($this->notifications, function($notification) {
            return $notification['status'] === 'failed';
        });
    }
}

class EmailNotification implements NotificationInterface {
    use NotificationFormatterTrait, NotificationLoggingTrait;
    
    private $smtpConfig;
    private $currentTemplate;
    
    public function __construct($smtpConfig) {
        $this->smtpConfig = $smtpConfig;
        $this->loadTemplatesFromConfig([
            'templates' => [
                'welcome' => 'Welcome {{name}}! Thank you for joining us.',
                'reset_password' => 'Click here to reset your password: {{link}}'
            ]
        ]);
    }
    
    public function send($recipient, $message) {
        $formattedMessage = $this->formatMessage($message);
        
        // Simulate email sending
        $success = $this->sendEmail($recipient, $formattedMessage);
        
        $status = $success ? 'sent' : 'failed';
        $this->logNotification('email', $recipient, $formattedMessage, $status);
        
        return $success ? uniqid('email_') : false;
    }
    
    public function setTemplate($template) {
        $this->currentTemplate = $template;
    }
    
    public function sendWithTemplate($recipient, $templateName, $variables = []) {
        $template = $this->getTemplate($templateName);
        if (!$template) {
            throw new InvalidArgumentException("Template {$templateName} not found");
        }
        
        $message = $this->formatMessage($template, $variables);
        return $this->send($recipient, $message);
    }
    
    public function getDeliveryStatus($id) {
        // Simulate checking email delivery status
        return 'delivered';
    }
    
    private function sendEmail($recipient, $message) {
        // Simulate SMTP sending logic
        return filter_var($recipient, FILTER_VALIDATE_EMAIL) !== false;
    }
}

class SmsNotification implements NotificationInterface {
    use NotificationFormatterTrait, NotificationLoggingTrait;
    
    private $apiKey;
    private $currentTemplate;
    
    public function __construct($apiKey) {
        $this->apiKey = $apiKey;
        $this->loadTemplatesFromConfig([
            'templates' => [
                'verification' => 'Your verification code is: {{code}}',
                'alert' => 'ALERT: {{message}}'
            ]
        ]);
    }
    
    public function send($recipient, $message) {
        $formattedMessage = $this->formatMessage($message);
        
        // Simulate SMS sending
        $success = $this->sendSms($recipient, $formattedMessage);
        
        $status = $success ? 'sent' : 'failed';
        $this->logNotification('sms', $recipient, $formattedMessage, $status);
        
        return $success ? uniqid('sms_') : false;
    }
    
    public function setTemplate($template) {
        $this->currentTemplate = $template;
    }
    
    public function sendWithTemplate($recipient, $templateName, $variables = []) {
        $template = $this->getTemplate($templateName);
        if (!$template) {
            throw new InvalidArgumentException("Template {$templateName} not found");
        }
        
        $message = $this->formatMessage($template, $variables);
        return $this->send($recipient, $message);
    }
    
    public function getDeliveryStatus($id) {
        // Simulate checking SMS delivery status
        return 'delivered';
    }
    
    private function sendSms($recipient, $message) {
        // Simulate SMS API logic
        return preg_match('/^\+?[1-9]\d{1,14}$/', $recipient);
    }
}

// Usage
class NotificationService {
    private $notifiers = [];
    
    public function addNotifier(NotificationInterface $notifier) {
        $this->notifiers[] = $notifier;
    }
    
    public function sendToAll($recipient, $message) {
        $results = [];
        
        foreach ($this->notifiers as $notifier) {
            $results[] = $notifier->send($recipient, $message);
        }
        
        return $results;
    }
}

$emailNotifier = new EmailNotification(['host' => 'smtp.example.com']);
$smsNotifier = new SmsNotification('api_key_123');

$service = new NotificationService();
$service->addNotifier($emailNotifier);
$service->addNotifier($smsNotifier);

// Send welcome notification via both email and SMS
$emailNotifier->sendWithTemplate('[email protected]', 'welcome', ['name' => 'John']);
$smsNotifier->sendWithTemplate('+1234567890', 'verification', ['code' => '123456']);

// View notification history
print_r($emailNotifier->getNotificationHistory());
?>

Best Practices

Interface Design

<?php
// Good: Focused, cohesive interface
interface UserRepositoryInterface {
    public function find($id);
    public function save(User $user);
    public function delete($id);
    public function findByEmail($email);
}

// Bad: Interface with too many responsibilities
interface BadUserInterface {
    public function find($id);
    public function save(User $user);
    public function sendEmail($email);  // Email sending is not repository responsibility
    public function logActivity($action); // Logging is not repository responsibility
    public function validateUser($user); // Validation might belong elsewhere
}

// Better: Separate concerns
interface UserRepositoryInterface {
    public function find($id);
    public function save(User $user);
    public function delete($id);
}

interface UserValidatorInterface {
    public function validate(User $user);
}

interface UserEmailServiceInterface {
    public function sendWelcomeEmail(User $user);
    public function sendPasswordResetEmail(User $user);
}
?>

Trait Design

<?php
// Good: Focused trait with single responsibility
trait AuditableTrait {
    private $auditLog = [];
    
    protected function recordAuditEvent($action, $details = []) {
        $this->auditLog[] = [
            'action' => $action,
            'details' => $details,
            'timestamp' => new DateTime(),
            'user' => $this->getCurrentUser()
        ];
    }
    
    public function getAuditLog() {
        return $this->auditLog;
    }
    
    abstract protected function getCurrentUser();
}

// Bad: Trait with mixed responsibilities
trait BadUtilityTrait {
    public function formatDate($date) {} // Date formatting
    public function sendEmail($to, $subject) {} // Email sending
    public function logMessage($message) {} // Logging
    public function validateInput($input) {} // Validation
    // Too many unrelated responsibilities!
}
?>

Explore related object-oriented concepts:

Summary

Interfaces and traits provide powerful mechanisms for creating flexible, maintainable PHP applications:

Interfaces:

  • Define contracts that classes must implement
  • Enable polymorphism and dependency injection
  • Promote loose coupling between components
  • Support multiple interface implementation

Traits:

  • Provide horizontal code reuse
  • Solve single inheritance limitations
  • Allow method sharing across unrelated classes
  • Support conflict resolution and method aliasing

Best Practices:

  • Keep interfaces focused and cohesive
  • Use traits for cross-cutting concerns
  • Resolve naming conflicts explicitly
  • Combine interfaces and traits for powerful designs
  • Follow SOLID principles

Together, interfaces and traits enable more flexible architectures than inheritance alone, promoting code reuse and maintainability while establishing clear contracts between application components.