You are missing a lot of use-cases
rand()- database interaction
- file IO
staticvariables- calls to other functions with
global import/requirestatements inside functions- functions with internal state like
ob_get_contents() - mutating array pointers
There is probably a lot of stuff I'm not thinking of. PHP has a very stateful design.
Answer from 9072997 on Stack OverflowHow to increase the number of pure functions in your code
Pure methods - where to put 'em?
Infer function/method as `@pure` without the need to mark it explicitly
php - What is the "Add #[Pure] attribute" inspection in PhpStorm checking for? - Stack Overflow
Pure functions have lots of desirable properties, for example:
-
Composability (without side-effects, you can combine them freely)
-
Predictability (clear relation between input and output)
-
Nothing to mock when testing (all dependencies are in the input variables)
-
Nothing to mock when they are used (since mocking is mostly done to control side-effects, pure functions can often be used as-is)
Because of this, we want to increase the number of pure functions in our code-base and reduce the number of functions and classes that has side-effects. But how? Here are some ways:
-
Move side-effects up in the stack-trace to separate calculations from file IO, database access, etc (e.g. only call classes with side-effects from controller methods if using MVC)
-
Use value objects with no methods and no setters instead of traditional OOP; these objects are also immutable
-
Use explicit state instead of implicit (kind of the same as above; state is part of function input and passed around)
-
Use annotations to keep functions pure, e.g. Psalm's
@psalm-pure(https://psalm.dev/docs/annotating_code/supported_annotations/#psalm-pure) -
If a method has no
thisnorself, move it to a pure function instead
In PHP, there are also some downsides:
-
Pure functions cannot be hidden/encapsulated (no module system), so the API will be polluted. Static methods could resolve this, but it muddles the intent of the code since static methods are often used for singleton factories etc (BUT: Not if you use @psalm-pure annotations?)
-
You cannot use inheritance for code reuse (BUT: Maybe better to use composition and interfaces instead anyway?)
-
Code could look unorthodox and become harder to read and maintain (this is PHP, not Haskell)
-
PHP is a language for the web, and the web is mostly about shuffling data from here to there, that is, state-full computations
-
More...?
Some things to think about:
-
Should a project's folder structure reflect side-effect-free vs side-effect-full code?
-
How to educate colleagues about pros and cons of pure functions?
-
Real example needed of how to convert state-full code to pure
-
More...?
Do you have any thoughts or something to add?
Thanks for reading!
Edit: Some further reading:
-
https://sidburn.github.io/blog/2016/03/14/immutability-and-pure-functions
-
https://old.reddit.com/r/PHP/comments/dp5pcb/pure_methods_where_to_put_em/
Pure functions have lots of pros. They are predictable, composable, testable and you never have to mock them. Thus, we should try to increase the number of pure methods/functions in our code base, right? So how would you do that? If you have a method with both side-effects and calculations, you can sometimes life the side-effects out of the method. That is why lifting side-effects higher up in the stack trace will increase white-box testability. Taken to the extreme, you end up with a class with only properties, and a bunch of functions that operate on that class, which is close to functional programming with modules and explicit state (although you lose encapsulation).
Anyway, you have a class, you have a bunch of methods, you realize some could be made pure easily. Would you do it? In MVC, would you create a helper namespace and put your pure functions there? Or is this just an empty intellectual exercise with no real-world applicability?
The rules() method has fixed (better say "side effects free") result -- it uses fixed values only. Sure, it calls getPhoneNumberRules() from a trait but it also returns fixed array (always the same). It does not make changes anywhere else (internal state or external storage).
The messages() method calls a method from a trait that calls __() to get translated message... that can come from a different source (a DB, a file) and therefore potentially can throw exceptions if a storage (file/DB) is not readable. The IDE does not know for sure if __() makes changes to a file/DB -- it's not annotated as pure and PhpStorm cannot make such decision from the methods that it uses.
P.S. If this inspection annoys you (which I can understand) then I suggest you just ignore such inspection and lower its severity in PhpStorm Settings to "No highlighting, only fix" (Settings (Preferences on macOS) | Editor | Inspections | PHP | Attributes | '#[Pure]' attribute can be added)
If a function only depends on other pure functions, then it is also pure. Since getPhoneNumberRules() just returns a fixed array, it's pure, so rules() is also pure.
But messages() calls getPhoneNumberMessage(), which calls the __() function that can return a different localized message if the location state changes, so it's not pure.