Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a fluent Stringable Str::of($string)->if() method to allow longer chaining of a Stringable, based on the Stringable itself #40038

Closed
wants to merge 4 commits into from

Conversation

ralphjsmit
Copy link
Contributor

@ralphjsmit ralphjsmit commented Dec 14, 2021

This Pull Request adds the ability to use an if() method on an instance of Stringable. This would allow developers to chain more Stringables and keep the code cleaner.

What & why

In certain situations, developers need to modify a fluent Stringable based on the value of the Stringable itself. The when() methods do not allow this, because they accept a boolean, which is based on a condition outside of the Stringable, and not a condition on the contents of the Stringable itself. I think that an example will make this more clear.

Consider the following scenario, where I want to append a value to a string if the string is numeric. In the current situation, we have to do something like this:

$stringable = Str::of('200');

$isNumeric = is_numeric((string) $stringable);

$stringable->when($isNumeric, function ($stringable) {
    return $stringable->append('is numeric');
});
// '200 is numeric'
// Not possible (the first condition will always evaluate to true):
Str::of('200')->when(function ($stringable) {
    return is_numeric((string) $stringable);
}, function ($stringable) {
    return $stringable->append(' is numeric');
});

As you see, we need to extract the Stringable to a separate before before we can make assertions on it. This causes our code code to become more unclear. In particular in cases where there is no need to a separate $stringable, this is needless.

With the new if() method, we could refactor the above example to:

Str::of('200')->if(function ($stringable) {
    return is_numeric((string) $stringable);
}, function ($stringable) {
    return $stringable->append(' is numeric');
});

// '200 is numeric'

Or, even shorter with PHP 7.4 short function calls:

Str::of('200')->if(
    fn ($stringable) => is_numeric((string) $stringable),
    fn ($stringable) => $stringable->append(' is numeric')
    fn ($stringable) => $stringable->append(' is not numeric')
);
// '200 is numeric'

If you only want to modify the string in one of the two cases, you can just pass null instead of a callback. The third parameter can be omitted entirely.

The whole if() method returns a new instance of Stringable, so everything is chainable. That means that you could chain multiple if()s and easily create complex strings.

Example 2

Imagine the following long and boring procedural code:

$stringable = Str::of('200')->append('00');

$isNumeric = is_numeric((string) $stringable);
// true

$stringable = $stringable->when(true, function ($stringable) {
    return $stringable->append(' is a numeric string');
});

$containsLetterA = $stringable->contains('a');
// true

$stringable = $stringable->when($containsLetterA, function ($stringable) {
    return $stringable->append(' and now contains the letter \'a\'');
});

With the if() helper, we can replace it with the following:

Str::of('200')->append('00')
    ->if(fn($stringable) => is_numeric((string) $stringable), fn($stringable) => $stringable->append(' is a numeric string'))
    ->if(fn($stringable) => $stringable->contains('a'), fn($stringable) => $stringable->append(' and now contains the letter \'a\''));

Both will output:

"20000 is a numeric string and now contains the letter 'a'"

It would also allow to perform an action directly inside an array, whereas we otherwise would have to inline the variable and perform the conditions before constructing the array.

$array = [
    'key' => Str::of('200')->append('00')
                ->if(fn($stringable) => is_numeric((string) $stringable), fn($stringable) => $stringable->append(' is a numeric string'))
                ->if(fn($stringable) => $stringable->contains('a'), fn($stringable) => $stringable->append(' and now contains the letter \'a\''));,
];

@foremtehan
Copy link
Contributor

foremtehan commented Dec 14, 2021

In this example you mentioned:

// Not possible (the first condition will always evaluate to true):
Str::of('200')->when(function ($stringable) {
    return is_numeric((string) $stringable);
}, function ($stringable) {
    return $stringable->append(' is numeric');
});

Why did you pass closure for the first argument here? It will never be called if you look at the when implementation.

What is the problem with this? :

Str::of('something')
    ->when(is_numeric($input), fn($str) => $str->append(' another'))
    ->when(is_int($input), fn($str) => $str->append(' thing'));

@ralphjsmit
Copy link
Contributor Author

ralphjsmit commented Dec 14, 2021

Why did you pass closure for the first argument here? It will never be called if you look at the when implementation.

It is true that this will never be called and that's also the point of the example. The point was that I'd like like to do something with the string, based on the value of the stringable itself. That's not possible with when(), because you only pass truthy/falsy values and cannot do a condition based on the contents of the string.

What is the problem with this? :

Str::of('something')
    ->when(true, fn($str) => $str->append(' another'))
    ->when(true, fn($str) => $str->append(' thing'));

The problem is that I want to do a condition on the contents of the Stringable class, which is not possible if you are chaining things ever since you did Str::of().

I added an updated example, which makes it hopefully more clear!

@foremtehan
Copy link
Contributor

foremtehan commented Dec 14, 2021

The problem is that I want to do a condition on the contents of the Stringable class, which is not possible if you are chaining things ever since you did Str::of().

What about using pipe ?

Str::of('something')->pipe(function (Stringable $str) {
    return $str
        ->when($condition, fn($str) => $str->append(' another'))
        ->when($condition, fn($str) => $str->append(' thing'));
});

// you can now do a condition on $str itself in the closure 

@ralphjsmit
Copy link
Contributor Author

What about using pipe ?

That would indeed be an option, though you need to repeat the condition there.

@taylorotwell
Copy link
Member

I'm going to not merge this because the when method is actually updated in the upcoming Laravel 9 release to have special handling for when it is passed a Closure as the first argument. The closure will be evaluated first in Laravel 9, so the solution in this PR will be achievable using the normal when method...

CleanShot 2021-12-15 at 08 04 31@2x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants