Вопрос сложный.
Правильный ответ: передавать в конструктор класса.
Всякие "хранилища" - это костыли, не сильно лучше жлобалсов.
Если все объекты в приложении создаются и вызываются вручную в коде, то руками и передавать.
Если объекты вызываются автоматом, например в роутере, то надо как-то делать конфигурацию, какой обхект какие зависимости требует. И потом создавать эти зависимости по мере надобности и класть их в особый контейнер. Из которого доставать, если зависимость снова понадобилась.
Тут фокус в том, что мы не создаем объекты руками через new.
И мыслим не отдельными объектами, а конкретным функционалом.
В А мы передаем тот функционал, который нам создаст экземпляр объекта Бэ
Самый тупой пример: контроллер принципиально не работает с БД.
С БД работает модель. Но модель мы вызываем из контроллера. Как быть? Вот он твой случай.
И тут нам DI, при создании экземпляра контроллера, и передаст в него нужную модель.
У которой внутри уже будет подключение к БД.
И которая нам вернет какой-нибудь DTO, создав его сама.
То есть в ООП мы на самом деле мыслим не отдельными классами, а функционалом. Не "я хочу такой объект создать в А", а "я хочу такой-то функционал получить в А"
А этот функционал уже сам говорит DI, какой функйионал, какие зависимости ему в свою очередь нужны для работы. DI их прилежно создает. И так по цепочке до самых первичных объектов типа соединения с БД, логгера и пр.