18

I've got a very simple FormRequest class in a Laravel project. Two methods, both of which return a simple array that's partially populated by a method, but the IDE treats them differently.

<?php

namespace App\Http\Requests;

class VerifyPhoneNumber extends FormRequest {
    use Support\ValidatesPhoneNumbers;

    public function authorize(): bool {
        return true;
    }

    public function rules(): array {
        return [
            "phone_number" => $this->getPhoneNumberRules(),
            "verify_code" => ["required", "numeric", "max:999999"],
        ];
    }

    public function messages(): array {
        return [
            "phone_number.regex" => $this->getPhoneNumberMessage(),
        ];
    }
}

And the very basic trait methods:

<?php

namespace App\Http\Requests\Support;

trait ValidatesPhoneNumbers {
    protected function getPhoneNumberMessage(): string {
        return __("Some localized error message");
    }

    protected function getPhoneNumberRules(): array {
        return ["regex:/^\+?1?[2-9][0-9]{5,14}$/", "max:16"];
    }
}

The thing I'm puzzled about is that the IDE inspection complains that I should add the JetBrains\PhpStorm\Pure attribute to the rules() method, but not the messages() method.

The comments in the class definition say:

The attribute marks the function that has no impact on the program state or passed parameters used after the function execution. This means that a function call that resolves to such a function can be safely removed if the execution result is not used in code afterwards.

That doesn't really give me any clues why it treats these two methods differently. If I'm understanding the second sentence correctly, when the result of a "pure" method is unused the IDE will flag usage of the method as unused and recommend its removal.

What is the logic used to determine when this attribute is needed?

LazyOne
  • 158,824
  • 45
  • 388
  • 391
miken32
  • 42,008
  • 16
  • 111
  • 154

4 Answers4

13

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)

miken32
  • 42,008
  • 16
  • 111
  • 154
LazyOne
  • 158,824
  • 45
  • 388
  • 391
  • 2
    Not OP, but I have some troubles understanding why a changing localization message makes it not "pure". The call to `__()` also intrigued me, but since it's a read-only mechanism wouldn't that make it pure [according to the definition given by JetBrains](https://blog.jetbrains.com/phpstorm/2020/10/phpstorm-2020-3-eap-4/#pure)? – Noah Boegli Oct 06 '21 at 17:45
  • 1
    @NoahBoegli `__()` reads a file (or even a DB) to get translations. Such operations can throw exceptions if a storage (file/DB) is not readable. Sure, `__()` only reads and does not modifies file/DB .. but the IDE does not know about it. Change `__()` and declare it as Pure and IDE will offer the same for both methods from the above example. – LazyOne Oct 06 '21 at 17:50
  • 1
    Good point with the I/O or resource errors, I also wondered a bit. However I think it's more that Phpstorm can't fully complete the analysis so _only_ suggests the Pure attribute where it is 100% certainty it is pure and only pure. Phpstorm can't know all libraries. – hakre Oct 06 '21 at 17:59
  • Let's bend the edges a bit on that error case: Does it mean, as long as PHPs file-based auto-loading is in effect, methods of classes that extend, implement or use (trait) can't be pure? – hakre Oct 06 '21 at 18:02
  • That makes sense, thanks. Is anything that throws exceptions unable to be "pure" or is that not considered as a side-effect? – miken32 Oct 06 '21 at 18:06
  • 1
    `#Pure` in PhpStorm does not exactly match what "pure function" means in other languages/literature. That's why you can find a suggestion to rename it to `#[NoSideEffect` https://youtrack.jetbrains.com/issue/WI-56211 . – LazyOne Oct 06 '21 at 18:16
  • What does it actually do in code tho? anything? or is this purely for PhpStorms own indexing and suggestion purposes like the old @tags in dog blocks? – Brad May 22 '22 at 02:39
  • 1
    @Brad It's for PhpStorm's Static Code Analysis only: 1) https://blog.jetbrains.com/phpstorm/2020/10/phpstorm-2020-3-eap-4/#pure 2) https://github.com/JetBrains/phpstorm-attributes#pure – LazyOne May 22 '22 at 09:49
6

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.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • 1
    Feels like this "Pure" thing is wide open to high maintenance. As soon as a method changes, or a child method or some DI'd class which the method uses, then it may no longer be Pure. – James Dec 30 '21 at 12:32
  • It's essentially a form of documentation (it's just in a format that's intended to be used by the IDE rather than for direct human consumption). So if you change the behavior of a function you need to keep it consistent. – Barmar Dec 30 '21 at 15:20
1

Use this attribute for functions that do not produce any side effects. All such PHP internal functions are already marked in PhpStorm.

#[Pure]
function compare(Foo $a, Foo $b): int
{
    return $a->a <=> $b->b;
}

Source: https://github.com/JetBrains/phpstorm-attributes#pure

As this function only does something within its own it can be called a #[Pure] function and so you know that via PhpStorm.

hakre
  • 193,403
  • 52
  • 435
  • 836
Caleb
  • 33
  • 12
0

Forget about side effects for a second.

A pure function is one that is guaranteed to always give the same result if called with the same parameters.

If your application calls $verifyPhoneNumberRequest->rules() it will get a certain result. Then some other code could change the application locale and the next time you call $verifyPhoneNumberRequest->rules() in exactly the same way (i.e. without parameters), it may return a different result because the __() or trans() function checks some global state (the locale) to decide which string to return.

Pure functions only rely on their direct input parameters, and not some outside state that may or may not have been modified elsewhere.

Another criterium for pure functions is that they do not modify non-local state. It's totally possible to drop your entire database within a getUsername() method (or cause less devastating yet painful to debug modifications). However if the method is marked and approved as pure you are guaranteed that this will not happen.