You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

同一类中使用多类型数据库时的PHP依赖注入方案问询

How to Handle Multiple Database Dependencies in a Modernizing Legacy PHP App

Great question—this is exactly the kind of scenario where thoughtful dependency injection (DI) practices turn messy legacy code into something maintainable and scalable. Let’s walk through your options and land on the best approach for your use case, including future support for Redis or other databases.

First: Separate Concerns (Don’t Let User Class Talk Directly to Databases)

Before jumping into injection methods, let’s fix a common legacy code smell: your User class shouldn’t be handling database operations directly. Instead, split responsibilities:

  • The User class is an entity: it only holds data (name, email, id, user_type) and basic business logic (if any).
  • A UserRepository class handles all data access: it talks to MySQL, MongoDB, and any future databases to fetch/save user data.

This separation makes your code far easier to test, extend, and debug.

Since your UserRepository must have both MySQL and MongoDB to function properly (it needs MySQL for core user attributes, MongoDB for user_type), constructor injection is the right call. This makes dependencies explicit—anyone using the repository knows exactly what it needs to work, and you avoid runtime errors from missing dependencies.

Example Implementation

First, define your database connection classes (wrap existing clients like PDO or MongoDB’s PHP driver):

// MySQL connection wrapper
class MySqlUserStore {
    private PDO $pdo;

    public function __construct(string $dsn, string $username, string $password) {
        $this->pdo = new PDO($dsn, $username, $password);
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }

    // Methods for core user operations
    public function getCoreUserAttributes(int $userId): array {
        $stmt = $this->pdo->prepare("SELECT id, name, email FROM users WHERE id = ?");
        $stmt->execute([$userId]);
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
    }

    public function updateCoreUserAttributes(User $user): void {
        $stmt = $this->pdo->prepare("UPDATE users SET name = ?, email = ? WHERE id = ?");
        $stmt->execute([$user->getName(), $user->getEmail(), $user->getId()]);
    }
}

// MongoDB connection wrapper
class MongoUserTypeStore {
    private MongoDB\Collection $collection;

    public function __construct(string $mongoUri, string $dbName) {
        $client = new MongoDB\Client($mongoUri);
        $this->collection = $client->selectDatabase($dbName)->selectCollection('user_types');
    }

    // Methods for user_type operations
    public function getUserType(int $userId): ?string {
        $doc = $this->collection->findOne(['user_id' => $userId]);
        return $doc?->user_type;
    }

    public function updateUserType(int $userId, string $userType): void {
        $this->collection->updateOne(
            ['user_id' => $userId],
            ['$set' => ['user_type' => $userType]],
            ['upsert' => true]
        );
    }
}

Then the User entity (clean, no database dependencies):

class User {
    private int $id;
    private string $name;
    private string $email;
    private ?string $userType;

    public function __construct(int $id, string $name, string $email, ?string $userType = null) {
        $this->id = $id;
        $this->name = $name;
        $this->email = $email;
        $this->userType = $userType;
    }

    // Getters and setters for properties
    public function getId(): int { return $this->id; }
    public function getName(): string { return $this->name; }
    public function getEmail(): string { return $this->email; }
    public function getUserType(): ?string { return $this->userType; }
    public function setUserType(string $userType): void { $this->userType = $userType; }
}

Finally, the UserRepository with constructor injection:

class UserRepository {
    private MySqlUserStore $mysqlStore;
    private MongoUserTypeStore $mongoStore;
    // Optional: Add Redis later with setter injection (see below)
    // private ?RedisCache $redisCache = null;

    public function __construct(MySqlUserStore $mysqlStore, MongoUserTypeStore $mongoStore) {
        $this->mysqlStore = $mysqlStore;
        $this->mongoStore = $mongoStore;
    }

    // Optional setter for non-required dependencies like Redis
    // public function setRedisCache(RedisCache $redisCache): void {
    //     $this->redisCache = $redisCache;
    // }

    public function findById(int $userId): ?User {
        // Fetch core attributes from MySQL
        $coreData = $this->mysqlStore->getCoreUserAttributes($userId);
        if (empty($coreData)) {
            return null;
        }

        // Fetch user_type from MongoDB
        $userType = $this->mongoStore->getUserType($userId);

        // Optional: Check Redis cache first if enabled
        // if ($this->redisCache) {
        //     $cachedUser = $this->redisCache->get("user:$userId");
        //     if ($cachedUser) {
        //         return unserialize($cachedUser);
        //     }
        // }

        $user = new User(
            $coreData['id'],
            $coreData['name'],
            $coreData['email'],
            $userType
        );

        // Optional: Cache the user in Redis
        // if ($this->redisCache) {
        //     $this->redisCache->set("user:$userId", serialize($user), 3600);
        // }

        return $user;
    }

    public function save(User $user): void {
        // Update MySQL
        $this->mysqlStore->updateCoreUserAttributes($user);
        // Update MongoDB
        $this->mongoStore->updateUserType($user->getId(), $user->getUserType());
        // Optional: Invalidate Redis cache
        // if ($this->redisCache) {
        //     $this->redisCache->delete("user:{$user->getId()}");
        // }
    }
}

Option 2: Setter Injection (Only for Optional Dependencies)

Avoid using setter injection for required dependencies like your MySQL/MongoDB stores. Setters work best for optional add-ons (like Redis caching) where the repository can function without them.

For example, if you add Redis later, you’d add a setRedisCache() method to the repository, as shown in the comments above. This keeps your constructor focused on mandatory dependencies while allowing flexibility for optional features.

Why This Works for Future Scalability

  • Adding Redis: Just create a RedisCache class, add a setter to the repository, and update methods to use it—no changes to existing database classes or the User entity.
  • New Databases: If you need to add another database (e.g., PostgreSQL for audit logs), create a new store class and inject it into the repository (either via constructor if required, or setter if optional).
  • Testability: You can easily mock any store class (e.g., MockMySqlUserStore) for unit tests, without connecting to real databases.

Key Takeaways

  • Don’t let entities talk to databases: Keep User clean, let a repository handle data access.
  • Constructor injection for required dependencies: Ensures your repository has everything it needs to work from the start.
  • Setter injection for optional dependencies: Keeps your code flexible for future additions like Redis.

This approach aligns with the principles in Modernizing Legacy Applications In PHP and sets you up for a maintainable, scalable codebase.

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:00:52