First try had a Dependency Injection framework, with automatic creation of instances at the time the request was routed to a dispatcher class, because it was the default "best practice" of the framework I was using.
Soon realized that I was relying on magic object creation which was too magic for my taste, and also only happened at one preset point in the request cycle, which was not flexible enough, and would not let a subcomponent have its dependencies managed without involving its callers all the way up to the request dispatcher.
Second try toned down the magic by using a Service locator and passing it all around. Quickly realized that all the code was burdened by passing an extra param for absolutely no benefit. If whole chains of functions are passing the same param without making any changes to it, and it only ever takes a single value in the whole program, you may have reinvented global variables, badly.
Third try decided to bite the bullet and embrace carefully managed global state. The running environment is global. So the SL might as well be a single global. This stage did not make it into code because...
Why would you want to create a single monster class called SL or "Context" or some such, artificially bringing together all sorts of unrelated things like your DB hostname, your AWS credentials and your password hashing secrets?
If you follow object orientation as a dogma, then sure, you have a problem rooting the whole system of creating objects. Just like in religious dogma, if every existent has to be created by a previous existent, your logic inevitably leads to ask for a first existent with God-like powers. So the Service Locator becomes the demiurge or creator God of object orientation...
In the end I decided OO is not my religion. I write modular code, but whether a module is implemented as a class, or a folder with a few closely interrelated classes, a bag of functions, a whole bunch of closures, or something else, is an implementation detail. What I know for sure, from John Ousterhout's holy teachings, is that I want my modules to be the right size, not just minimal, and certainly not "single function", so they encapsulate as much complexity as needed while presenting a small and clear interface to the rest of the word.
Which means, in the case of my project, that "get me a DB handler" becomes a static method of a DB management class, which knows all about DB management: where the credentials are found, what things should be done differently in production or development, how to reuse DB connections, etc. Over time this class can grow to accommodate and encapsulate more DB-handler-management logic, such as getting connections for writing vs connections to a replica for reading, or returning mock DB connections for integration testing, and whatnot, without overextending its scope.
And whatever code needs a DB connection, just calls DB::get().