This is an interesting problem and my opinion is that for a class to be cleanest, most readable and reusable these rules should be followed:
- All mandatory dependencies should be injected in the constructor.
- There should be no injected containers, factories, etc. that are used to pull dependencies from (no service locators).
- The dependencies should be the minimum of what is required, not groups containing needed and unneeded stuff. For example, when I need a user ID I require $user_id and not the whole User object. When I need the root upload directory path I require that path and not the whole Config, etc.
I've found that following these principles allows to write most readable classes without superfluous dependencies. However, this can sometimes result in too many dependencies so ideally I should be able to refactor the class into smaller classes and be happy, or if that's impossible then simply leave all those many constructor arguments since they appear to be necessary in order to have a well constructed class.
Of course, there are always cases where I feel I need to break the rules for some higher purpose, or at least I believe so. My practice is that the further down the application structure I go the stricter I am about following the proper DI principle. So this would generally be the Model (or Service) classes that perform the business part of my application - I try to make them as small as possible with few and specific dependencies, which makes them easy to read and port.
As I go higher up in the application layers I allow myself more leeway in "abusing" rules - that's because the higher layers are more framework specific and less likely in need to be reused elsewhere. An example is controllers - I still keep them as independent classes and inject needed dependencies but trying to limit them I usually require a controller utility object, which is nothing more than a collection of methods that deal with request/response/session, etc. stuff like redirecting, url creation, http exceptions, etc. by invoking framework-specific code. I would call it a simple service locator or maybe a service wrapper? In this way I limit the number of dependencies but still keep the possibility of porting my controllers to another framework, provided I port them along with the utility class. I could require the whole Application object and do anything in the controller but then I get tied to a specific framework.
But because I generally consider controllers to be less in need to be reusable I don't worry so much about a large number of dependencies. However, I still find it cleaner to limit them and while in the past I had all kinds of actions in a single controller like list, add, edit, delete, etc., now I usually have a separate controller for list and a separate one for add, edit and delete.
In short, my general belief is that if a class really needs many dependencies then it should require them in the constructor and we should live with it.
A somewhat related issue I have found is passing a database object. To me, a database object is almost like a global variable because if a class has access to a database object then it can do anything in the database because it can execute any kind of SQL and there's no way to limit that. So if an object has access to the database object then it literally has access to the whole application and it's not immediately obvious what the object is going to do to the database - it could be anything. Of course, I don't abuse my classes with improper SQL but conceptually I feel that they have access to too much stuff than they should.