9 - oop interface best and pdo practices

Preview
1 min
12 min read


Quick summary: This lesson explains why interfaces are essential in OOP design, shows how to use the `LanguageInterface` example from your repository, and walks through robust PDO patterns for secure, maintainable and testable database access — with real business examples such as multilingual e-commerce product listings and transactional inventory updates.

Why Interfaces Matter in Real Projects

Interfaces define contracts: they tell different parts of your system what behaviour to expect without locking you into a single implementation. For businesses this means: swappable implementations, easier testing, clearer responsibilities, and cleaner code for teams. Use interfaces for services like localization, payment gateways, email providers, repositories and cache adapters.

  • Swappable implementations — switch from a file-based translator to a DB-backed translator without changing callers.
  • Testability — mock interfaces in unit-tests instead of touching the database or file system.
  • Clear boundaries — each class has well-defined responsibilities.

Practical example: `LanguageInterface` (from your diff)

Below is a simplified and improved pattern for the `LanguageInterface` and a concrete Language class implementation. This demonstrates how an interface helps keep localization logic consistent and testable.

<?php
interface LanguageInterface {
    /** Return associative array of keys => translations */
    public function strings(): array;

    /** Return raw localization data or metadata if needed */
    public function localization(): array;
}

class Language implements LanguageInterface {
    protected string $lngPath = '';
    protected string $path = '';

    public function __construct(string $lngPath = '', string $path = '')
    {
        $this->lngPath = $lngPath;
        $this->path = $path;
    }

    public function localization(): array
    {
        // example: load file or fallback
        if (is_file($this->lngPath . '.php')) {
            return require $this->lngPath . '.php';
        }
        return [];
    }

    public function strings(): array
    {
        // merge or transform localization data
        $localization = $this->localization();
        // e.g. randomize variants for marketing texts (if present)
        if (isset($localization['__shop_now__']) && is_array($localization['__shop_now__'])) {
            $localization['__shop_now__'] = $localization['__shop_now__'][array_rand($localization['__shop_now__'])];
        }
        return $localization;
    }

    // convenience helper for templates
    public static function transe(string $key): ?string
    {
        $lang = new self(/* path params as needed */);
        $strings = $lang->strings();
        return $strings[$key] ?? null;
    }
}
?>

Best practices for this example:

  1. Keep the interface small and focused (only the methods consumers need).
  2. Use dependency injection to pass language implementations into controllers/templates instead of creating them directly inside functions.
  3. Avoid side effects in `strings()` — keep it deterministic for easier testing (randomization should be explicit).

PDO Best Practices — Secure and Maintainable Database Access

PDO is the recommended way in PHP to access relational databases safely. Below are patterns and code examples used in production-grade systems.

1. Use exceptions and set error mode

<?php
$dsn = "mysql:host=127.0.0.1;dbname=app_db;charset=utf8mb4";
$options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // exceptions for errors
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false, // use native prepared statements
];
$pdo = new PDO($dsn, $user, $pass, $options);
?>

Why: exceptions make failures explicit and easier to handle/log.

2. Always use prepared statements with bound parameters

<?php
$stmt = $pdo->prepare('SELECT * FROM products WHERE price > :price AND status = :status');
$stmt->execute(['price' => 10, 'status' => 1]);
$products = $stmt->fetchAll();
?>

Why: prevents SQL injection and eliminates manual escaping errors.

3. Encapsulate DB access — Repository / Data Access Object (DAO)

Put SQL in classes that represent a single responsibility. This helps unit testing and switching storage later.

<?php
class ProductRepository {
    private PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    public function findExpensiveProducts(float $minPrice): array {
        $stmt = $this->pdo->prepare('SELECT * FROM products WHERE product_price > :p AND product_status = 1');
        $stmt->execute(['p' => $minPrice]);
        return $stmt->fetchAll();
    }
}
?>

4. Use transactions for multi-step changes

Example: decreasing inventory and creating an order should be atomic.

<?php
$pdo->beginTransaction();
try {
    // deduct inventory
    $stmt = $pdo->prepare('UPDATE inventory SET qty = qty - :qty WHERE product_id = :id AND qty >= :qty');
    $stmt->execute(['qty' => $qty, 'id' => $productId]);

    // create order
    $stmt = $pdo->prepare('INSERT INTO orders (user_id, product_id, qty) VALUES (:u, :p, :q)');
    $stmt->execute(['u' => $userId, 'p' => $productId, 'q' => $qty]);

    $pdo->commit();
} catch (Exception $e) {
    $pdo->rollBack();
    // log and rethrow or return user-friendly error
    throw $e;
}
?>

5. Use parameterized LIMIT/OFFSET carefully

Some drivers require casting integers to avoid injection-like bugs. Always validate or cast user-provided paging values.

6. Avoid dynamic table/column names in SQL or whitelist them explicitly

If you must allow sorting by column names, use a whitelist map to convert user-supplied values to real column names.

7. Use fetch modes properly

Use PDO::FETCH_ASSOC to reduce memory and avoid duplicated indexing, or PDO::FETCH_CLASS to map results into DTOs.

8. Connection management

Prefer passing a PDO instance around (DI) rather than creating new connections everywhere. Consider a connection factory for configurable connections (e.g., read/write replicas).

Design Patterns & Architecture Tips

Dependency Injection (DI)

Inject repositories or services (implementing interfaces) into controllers/services. Example:

<?php
class ProductController {
    private ProductRepositoryInterface $repo;
    public function __construct(ProductRepositoryInterface $repo) {
        $this->repo = $repo;
    }

    public function listAction() {
        $products = $this->repo->findExpensiveProducts(10.0);
        // render view
    }
}
?>

Repository + Interface

Create an interface for your repository so you can mock it in tests:

<?php
interface ProductRepositoryInterface {
    public function findExpensiveProducts(float $minPrice): array;
}
?>

Factory for Localization

Use a factory to produce language providers based on user preference or domain.

Real-life Business Examples

1. Multilingual e-commerce (connecting to your provided code)

Use the `LanguageInterface` for product page labels (e.g., "Shop Now", "Add to Cart") and DI to inject the right language provider into templates. This allows A/B testing of copy variants, and easy fallback if translation is missing.

2. Inventory + Order Processing

When a customer buys an item, use transactions to atomically decrease inventory, create an order record and log analytics events. Interfaces keep the payment provider swap-friendly, and repositories isolate SQL.

3. Analytics & Marketing texts

If you randomize marketing copy (as in your `__shop_now__` example), make that an explicit marketing service implementing an interface. Keep translation strings deterministic for analytics and testing.

Security, Performance & Testing

Security

  • Use least-privileged DB user for your application (no DROP or admin privileges).
  • Never concatenate user input into SQL — always use prepared statements.
  • Escape outputs to HTML when rendering translations or DB values (to avoid XSS).
  • Use parameterized queries for LIMIT/OFFSET by casting values to integers.

Performance

  • Profile queries and add indexes for common filters and joins.
  • Use caching (Redis, Memcached) for rarely changing localization arrays or product metadata; implement a cache adapter interface.
  • Use pagination rather than loading large result sets into memory.

Testing

  1. Unit test interface implementations by mocking dependencies.
  2. For repositories, write integration tests against a dedicated test database or use in-memory DB for CI.
  3. Simulate transaction failures to validate rollbacks.

Common Pitfalls & How to Avoid Them

  • Creating dependencies inline: new PDO() or new Language() inside methods makes testing hard — inject them.
  • Overly large interfaces: interface with many unrelated methods violates Interface Segregation Principle — split them.
  • Silent failures: swallowing PDO exceptions hides issues — log and surface errors appropriately in non-production environments.
  • Randomization in localization: if you randomize strings for UX, ensure reproducibility for A/B review and analytics.

Quick Cheat-sheet

  • Interface: keep small & focused.
  • PDO: use ERRMODE_EXCEPTION, EMULATE_PREPARES=false, prepared statements.
  • Wrap multi-step DB changes in transactions.
  • Encapsulate SQL in repository classes and expose interfaces for DI & testing.
  • Whitelist dynamic identifiers (columns/tables) if absolutely necessary.
  • Use caching for rarely changing resources (translations, product lists).

FAQ

Q: Should I use PDO or MySQLi?
A: Use PDO for portability and easy prepared statements. Choose MySQLi only if you need MySQL-specific features and are okay with less portability.
Q: Are interfaces overkill for small projects?
A: Start simple, but adopt interfaces for parts of your app that are likely to change (payments, translations, repositories). They pay off as projects grow.
Q: How to handle missing translations?
A: Provide a fallback display language, log missing keys for translators, and consider a dashboard to add translations.

Resources & Next Steps

Watch the lesson video: YouTube — OOP Interface & PDO Practices

Open lesson page: Lesson on AiwaSoft

Suggested exercises:

  1. Refactor your current localization system to implement LanguageInterface and write unit tests for it.
  2. Create a ProductRepository that returns paginated products and write an integration test against a test DB.
  3. Implement a transaction that moves inventory and creates an order; simulate failure and validate rollback.


This article includes code snippets for education. Adapt DB credentials and error-handling to your environment before using in production.

PHP & OOP & MySQLi & PDO

PHP & OOP & MySQLi & PDO

php website
softwarePHPWeb Development Basics
View course

Course Lessons