Public static functions are global functions, in OOP, global anything is bad other than the application singleton. It would create multiple entry points to your code, expanding the global interface, which makes it hard to manage.
This is one of those cases where the framing of the argument matters. A non-complex static function cannot operate in isolations and only functions in the confides it was intended to be used, thus limiting the scope of the "global" to be no more than specific designated use cases.
In a decoupled architecture, a global function that reaches across bundles adds an additional dependency. In OOP, and Symfony specifically, we can use DependencyInjection to handle the loose coupling of our dependencies. Public static functions ignore all of that.
What about static calls inside the dependency management itself? Using public static helper functions to set correct dependencies saves duplicate code. It can't manage itself.
Dependency management should not blindly pull in dependency logic that is not under its own jurisdiction. It would lead to your own bundle having unpredictable dependencies.
If the helper method is tightly coupled to the dependency logic being used and package management tools are used to insure that the external dependency that's already being referenced is present, then this is a not a real concern.
Some duplicate code/logic is ok in a fully decoupled system. It's not the business logic, it's the dependency logic about how your business logic connects to other business logic.
Fear of duplicate code is often driven by academic knowledge, not real-world experience. In large complex applications, some small amount of duplication for the sake of code soverienty should be acceptable.
It is very true that academia leads to an idealistic perspective of how production code should be structured and written, but the same is true for thought processes around unit testing.
Overly dogmatic approaches to 100% test coverage and testing all executed lines of code can lead to brittle tests that don't inform you of anything valuable outside of the fact that your test itself is broken. This can lead to developers not taking tests seriously.
If the "global function" is defined in a specific bundle and only relates to the operations of that bundle, which is versioned separately as a dependency then there is no violation of the loose coupling of dependencies, since the DI to be used wouldn't be present otherwise.
Dependency configuration structure may require knowledge that a loose coupling is not fully aware of. This means that we spend more engineering cycles maintaining standard structures that could just as easily be maintained centrally and merely utilized by the loosely coupled bundles.
Calling static functions means you need to test the static function, for every place it is called/used (duplicate testing). There is no test/mock isolation.
The most common unit tests only test the code executed within their scope. When testing code used in other class methods you typically mock the object and its responses to simulate the results and reduce external variables.
Having multiple unit tests that verify the same code can result in multiple tests failing for the same issue which can erode an engineer's confidence in the unit tests.
You are not testing what arguments are sent to it - what if our code is sending the wrong ones? ie the wrong string. The only way to test that, is to test the outputs and actions it has on your own function.
When you are testing a system that does not return a result, then you cannot test if your arguments are "correct" without knowledge of the system you are calling yourself.
The only cases in which the test will fail are when the outside code is updated and the expectations in the test were not. This results in test failures that don't actually indicate that anything is wrong and wastes engineering time to update and maintain.
This is a flawed reasoning because it encourages testing the code as it was written and not what it does. It doesn't follow TDD (Test Driven Development) principles.
You should be free to refactor the implementation of your class or method and as long as the contract of inputs to outputs is maintained, then the tests should still pass.
Failure to follow this principle leads to wasted time refactoring brittle tests and reduced trust that the failure should be investigated as an actual issue in the code instead of an issue in the unit test itself.