Без DI контейнера ты создаёшь какую-то конкретную реализацию подключения к БД и каждый раз к ней обращаешься, без возможности изменить или создать новую. То есть экземпляр объекта класса PDO создаёшь внутри.
При Dependency Injection, в сухом остатке, тебе не нужно привязываться к реализации, достаточно создать объект класса pdo снаружи и передать его.
DI-контейнер позволяет автоматизировать создание объектов и внедрение зависимостей. Это означает, что нам нужно иметь класс, который хранит все варианты созданных подключений к БД.
То есть, в любой момент мы можем положить в контейнер нужное подключение, а потом просто обращаться к уже готовому подключению. Но в отличие от Singleton, таких подключений может быть несколько и в коде можно выбирать разные, там где это нужно.
В твоём случае это будет что-то типа такого:
$container = new Container();
$container->set('primary_db', function () {
return new PDO(
'mysql:host=localhost;dbname= primary_db',
'db_user',
'password'
);
});
$db = $container->get('primary_db');
А в той экземпляр Model нужно передавать в конструктор уже существующее подключение. Например, так:
class User {
private $db;
public function __construct(PDO $db) {
$this->db = $db;
}
...
}
$db = $container->get('primary_db');
$user = new User($db);
или через биндинг.