Skip to content

Conversation

@shaedrich
Copy link
Contributor

@shaedrich shaedrich commented Sep 29, 2024

As laid out in #47044 (comment), the Str class has become the jack of all trades device (in other words "bloated" and "haphazard"), having 1994(!) lines of code, which will be reduced to 939 with this PR, giving the class a slimmed down look. This PR groups the methods more logically into:

New classes, their meaning and methods
  • Casing (Change the case of a string)
    • convertCase
    • upper
    • lower
    • camel
    • snake
    • kebab
    • title
    • headline
    • lcfirst
    • ucfirst
    • studly
  • Generator (Generate new strings)
    • random (including createRandomStringsUsingSequence(), createRandomStringsUsingSequence(), and createRandomStringsNormally())
    • uuid (including uuid7(), orderedUuid(), createUuidsUsing(), createUuidsUsingSequence(), freezeUuids(), and createUuidsNormally())
    • ulid (including orderedUlid(), createUlidsUsing(), createUlidsUsingSequence(), freezeUlids(), and createUlidsNormally())
    • password
  • PatternMatcher (Similar to str_match()/preg_match())
    • is
    • isAscii
    • isUrl
    • isUuid
    • match
    • isMatch
    • matchAll
  • Replacer (Similar to str_replace()/preg_replace())
    • numbers
    • replaceArray
    • replace
    • replaceFirst
    • replaceStart
    • replaceLast
    • replaceEnd
    • replaceMatches
    • substrReplace
  • Sanitzer (Removes specific characters from a string so that it can be safely used in subsequent operations)
    • remove
    • trim
    • ltrim
    • rtrim
    • squish
  • StrGrammar (Does grammatical changes to the string)
    • plural
    • pluralStudly
    • singular

What if someone doesn't like the change? No problem, just add the following to the service provider of your liking and the Str class will work as before:

Str::mixin(new Casing());
Str::mixin(new Generator());
Str::mixin(new PatternMatcher());
Str::mixin(new Replacer());
Str::mixin(new Sanitzer());
Str::mixin(new StrGrammar());

Alternatives

  • The new class names are just ideas—feel free to suggest better ones (like @flavio-schoute did)
  • The grouping of methods are just ideas—feel free to suggest other groupings that might make more sense
  • I put the new classes also in ./src/Illuminate/Support—maybe, they would fit better into ./src/Illuminate/Support/Str 🤔

@github-actions
Copy link

Thanks for submitting a PR!

Note that draft PR's are not reviewed. If you would like a review, please mark your pull request as ready for review in the GitHub user interface.

Pull requests that are abandoned in draft may be closed due to inactivity.

@shaedrich shaedrich changed the title Split str class into logically grouped separate classes [12.x] Split str class into logically grouped separate classes Sep 29, 2024
use Ramsey\Uuid\UuidFactory;
use Symfony\Component\Uid\Ulid;

class Generator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe another name for example StringGenerator

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not bad, let's see what the others say 👍🏻

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of \Illuminate\Support\Str\Generator?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhmm that could be a solution but then the other classes also needs to be added to the folder Str

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that would be the idea then 👍🏻

@jmarcher
Copy link
Contributor

This seems like a pain to use, I need to remember where all the functions are in order to use them.

As a Developer you use the class and I do not care about how long that class is.

The problem you present can be solved with splitting the methods in different Traits an use them directly in the Str class.

@shaedrich
Copy link
Contributor Author

shaedrich commented Sep 30, 2024

@jmarcher The whole framework consists of more than this single class—how do you remember these?

While traits might be part of a solution, this isn't taking it far enough in my opinion. It's not "just too long", it's muddles the domain of the class into a meaningless class, that does something. Other classes in Laravel do have meaning, a domain, and a scope. This class, however, is treated like a dumpster, everyone just throws their stuff in mind- and carelessly, because it's there conveniently.

@flavio-schoute
Copy link
Contributor

flavio-schoute commented Sep 30, 2024

This seems like a pain to use, I need to remember where all the functions are in order to use them.

As a Developer you use the class and I do not care about how long that class is.

The problem you present can be solved with splitting the methods in different Traits an use them directly in the Str class.

I think this can be resolved by settings/ using these as the standard so developers kan still use Str:: because they are naturally used to it

Str::mixin(new Casing());
Str::mixin(new Generator());
Str::mixin(new PatternMatcher());
Str::mixin(new Replacer());
Str::mixin(new Sanitzer());
Str::mixin(new StrGrammar());

Like these in the example and I think this is a really good solution/ idea to split the code up. Like @shaedrich mentiod

@shaedrich
Copy link
Contributor Author

Is it possible, that the Str::upper() call, most of the tests are complaining about, comes from orcestra/testbench and not laravel/framework? That would mean, it is out of scope for this PR, right?

@shaedrich shaedrich marked this pull request as ready for review September 30, 2024 18:08
@jmarcher
Copy link
Contributor

jmarcher commented Sep 30, 2024

@jmarcher The whole framework consists of more than this single class—how do you remember these?

While traits might be part of a solution, this isn't taking it far enough in my opinion. It's not "just too long", it's muddles the domain of the class into a meaningless class, that does something. Other classes in Laravel do have meaning, a domain, and a scope. This class, however, is treated like a dumpster, everyone just throws their stuff in mind- and carelessly, because it's there conveniently.

Whenever I want to use a Str method I go to the class and read the methods included (with traits) whenever I want to use the response class I do the same...

Really I don't get exactly what this is trying to solve is it because in your opinion the class is too long? Then propose separation into traits

@shaedrich
Copy link
Contributor Author

shaedrich commented Sep 30, 2024

Woah, you really are hung up on on it being too long, despite me having explained already that it is more than just "too long". Please (re)read my previous comment concerning that very question.

@jmarcher
Copy link
Contributor

jmarcher commented Oct 1, 2024

Woah, you really are hung up on on it being too long, despite me having explained already that it is more than just "too long". Please (re)read my previous comment concerning that very question.

This is because this PR changes 160 files, and almost 3k lines of code and also big breaking change for all the existing Laravel apps, and the gain from it is none, just more things to remember.

You have to deal with it in open-source people have different opinions, I mean this may get merged and I will need to use it, and then deal with this change, this is one core idea of open-source, meaning to gather different opinions and build the best solution out of it.

@func0der
Copy link

func0der commented Oct 1, 2024

I can see why this would be useful.

Regarding the breaking change: Instead of throwing this out the window at once, it could be slowely faced out (deprecated), if we would put the mixin part in a file that comes with Laravel for version 11/12 and a deprecation notice.
In 12/13 the file could be removed.
It should not be that hard to provide for example a Rector rule for the migration, so the friction would be less, when it is removed.
IDEs will help to find methods either way, not matter which class they are in, especially if they have the namespace Str :)

It is probably a good idea to have the namespace Str for all those classes, since they are logically bound.

This change could also be a good orientation for some other groupings of present/future code, if needed.

@func0der
Copy link

func0der commented Oct 1, 2024

@taylorotwell
Can you please elaborate why you closed this? It comes across kind of rude to just swoop in, no words, no reasoning and shut a thing down, don't you think?

@flavio-schoute
Copy link
Contributor

Maybe make a new PR with just the changes because changes this at once is alot of review work

@crynobone
Copy link
Member

crynobone commented Oct 2, 2024

Maybe make a new PR with just the changes because changes this at once is alot of review work

I wouldn't recommend that as this PR creates a major breaking change to Laravel Framework and any PHP library relying on illuminate/support or Illuminate\Support\Str specifically. Even with macro, this reduces IDE autocompletion support (adds additional maintenance with little benefits).

@shaedrich
Copy link
Contributor Author

shaedrich commented Oct 2, 2024

I wouldn't recommend that as this PR creates a major breaking change to Laravel Framework and any PHP library relying on illuminate/support or Illuminate\Support\Str specifically. Even with macro, this reduces IDE autocompletion support (adds additional maintenance with little benefits).

With barryvdh/laravel-ide-helper, IDE support of mixins should not be a problem.

Yes, it is a breaking change (hence the 12.x tagging) and a lot of files were (briefly) touched, but as both @func0der and @flavio-schoute both have pointed out, it is extremely(!) easy to opt out of this change individually, even after a potential phase out in 13.x, as @func0der suggested.

Maybe make a new PR with just the changes because changes this at once is alot of review work

The changed files won't become fewer and the majority has already vehemently positioned itself against this change, so I don't see this going through either way 🤷🏻 Thank you and @func0der nonetheless for your suggestions :)

@crynobone
Copy link
Member

IDE support of mixins should not be a problem

That's only for Laravel application, what about 32k packages relying on illuminate/support: https://packagist.org/packages/illuminate/support/dependents?order_by=downloads

@Sophist-UK
Copy link

@shaedrich You can still have a partial win if you submit a PR which splits it using traits.

But something which creates backwards compatibility issues is starting from some major cons and needs especially strong functional pros to outweigh these.

@shaedrich
Copy link
Contributor Author

shaedrich commented Nov 23, 2024

@Sophist-UK As I explained earlier, this is not a viable solution as it doesn't change the root cause of the problem. Maybe, we have maneuvered ourselves in a corner there where there is just no way out 🤷

Furthermore, it's always "just yet another try" and then, the PR gets rejected again for whatever reason, Taylor again doesn't disclose and gets everyone guessing, yet nobody knowing for sure except Taylor.

And, what I hate the most: Everyone has expressed their opinion as to why this won't work, yet nobody acknowledged the current state being problematic. I can see why it is not easy to change it now, but that doesn't mean, it wouldn't be right to do so if we could.

@Sophist-UK
Copy link

Sophist-UK commented Nov 24, 2024

@shaedrich in your description above you said:

As laid out in #47044 (comment), the Str class has become the jack of all trades device (in other words "bloated" and "haphazard"), having 1994(!) lines of code, which will be reduced to 939 with this PR, giving the class a slimmed down look. This PR groups the methods more logically into [new classes].

So this actually is trying to achieve two things:

  1. Split the Str class into multiple classes, so that by itself Str is smaller and uses less resources and people can opt-in to adding in one or more or all of the extensions by specifying a mixin; and
  2. Make the code more maintainable by splitting the code into a larger number of smaller more self-contained files.

The problem with 1. is backwards compatibility, and IMO that means it needs to be opt-out rather than opt-in. And given that macros or mixins are done at a class-level, this can be VERY difficult.

The other issues with opt-ins or opt-outs for the main application is that we need to be VERY careful not to create backwards incompatibility with other packages that happen to use the full set of Str methods.

So, here is a suggested approach that you might be able to use to achieve your goals.

Do the same separation as you have done above, but separate the core Str functions also into a separate StrBase (abstract?) class, and create a replacement Str definition which looks something like:

namespace Illuminate\Support;

class Str extends StrBase
{
    use StrCasing;
    use StrGenerators;
    use StrPatterns;
    use StrReplacers;
    use StrSanitzers;
    use StrPlurals;
}

What a user who doesn't want the bloat can then do is easily to create and use their own version of the Str class which doesn't contain all the "bloat" i.e.

namespace MyApp;

class MyStr extends StrBase
{
    use StrSanitzers;
}

A couple of potential gotchas...

Type matching

Because you are not extending the Str class, any methods in packages that require Str as a parameter will fail if you pass in a MyStr. You may need to create some way of doing this cast (ideally automatically (using Reflection??) but if not explicitly) - and this may not be that easy to do in a generic way that you can include in StrBase.

Because packages may return Str objects, any methods you write will need to have parameters stated as StrBase or Str | MyStr

Over time, as the use of StrBase becomes known, people can change their packages to use StrBase as parameter / return types where they don't need the non-base methods. If they need non-base methods, then I guess they stick with Str.

Aside: This is where purists will say that Laravel should separate out Objects and Interfaces - and you should use Interfaces as parameter and return types, and use Objects that implement those interfaces as data. I have no idea why Laravel doesn't do this, but I think it likely that Taylor has a reason.

That said, once this PR is accepted, there is nothing to stop you submitting a further PR to define interfaces for StrBase and the other Traits so that the interface names can be used for parameter and return types.

Naming of Abstract Classes and Interfaces

If you are going to use Abstract Classes or Interfaces then you probably need to follow https://www.php-fig.org/bylaws/psr-naming-conventions/ i.e.

namespace Illuminate\Support;

class Str extends AbstractStrBase implements StrBaseInterface, StrCasingInterface, StrGeneratorsInterface, StrPatternsInterface, StrReplacersInterface, StrSanitizersInterface, StrPluralsInterface 
{
    use StrCasingTrait;
    use StrGeneratorsTrait;
    use StrPatternsTrait;
    use StrReplacersTrait;
    use StrSanitzersTrait;
    use StrPluralsTrait;
}

However, Laravel does not use this naming convention e.g. for Traits, and I think that you can see why Laravel doesn't widely use Interfaces in this way also. So I suggest that you don't use these in the next PR.

I hope this helps you to find a way to achieve your objectives.

@shaedrich
Copy link
Contributor Author

shaedrich commented Nov 24, 2024

I think it likely that Taylor has a reason.

This describes perfectly how all this works: Taylor has plenty of reasons for plenty of things. None of these, he discloses. So everyone thinks, they know what these reasons are, so they keep telling everyone their interpretations and tell them to open PRs based on these interpretations. And then—surprise!—Taylor closes the PR, because they are not his reasons, but doesn't bother to share his reasons. Who doesn't like guessing games?

Over time […] people can change their packages to use StrBase as parameter / return types where they don't need the non-base methods.

What I explained above leads to people having confidence in one solution and one solution only: Their own, based on their interpretation of the words Taylor didn't bother to put into a reply. I'd love to believe you and share your dream, but even if I don't like it, I tend to subscribe to the stark contrast of the visions of @jmarcher and @crynobone. They tell the same story but with the polar opposite as outcome. They can't both become reality.

And that's the problem: Taylor keeps us guessing and we all think, we know best, yet Taylor is the person who approves or rejects PRs, so us guessing is like playing the lottery.

And getting one PR approved doesn't say, the next one does as well. If Taylor would agree to a roadmap, that would convey security for building a solution that is actually wanted at all, not solely at the mercy Taylor's current mood. I don't like being convinced to do a solution, that might change things for the better, if it was part of multiple changes, but in the end, the subsequent changes never get approved and therefore it changes nothing because this was just meant to satisfy me short term, so that when I make demands, I can be told that I already got my changes approved. But without clear communication from Taylor, this just isn't possible.

All criticism aside: I do like your suggested solution as outlined in your last comment. And if the whole process wasn't the way it is and wouldn't only lead to frustration, I might actually try it. Thanks for your dedication to lay it all out.

@Sophist-UK
Copy link

Sophist-UK commented Nov 24, 2024

Well, I have also submitted a PR and had it closed without any explanation. And the scuttlebutt view of Taylor playing favourites with a selected few is worse than what you have suggested above. But whilst both of us might prefer a greater degree of explanation and transparency, 1) IMO it is better to have a decisive leader who takes definite decisions in a reasonable time-scale over a wishy-washy one who lets PRs languish for years; 2) In the end it is Taylor's baby, so he can adopt whatever leadership style he likes; and 3) We always have the option of forking Laravel and creating our own version which is identical except in one small way - and then have the onus of maintaining and supporting it as more and more commits need to be tweaked to make them merge; and 4) This is the reality, so we either deal with it or go away and use a different framework.

Even if Taylor didn't say it explicitly, in this case there seems to be a consensus that your PR was not a good one because of the backwards compatibility issues, and (whilst there may be other reasons) this is a pretty good one to be going on with. That said, even with my approach which has backwards compatibility, I am not sure that Taylor would see that the benefits of a slightly smaller object would outweigh the additional complexity and the confusion of having yet another set of choices and decisions, a range of new errors for those that do create a cut-down MyStr and then have difficulties, and additional support and maintenance workload that will result from such situations.

So - it is clearly up to you whether you want to make the effort to submit a new PR which doesn't have backwards compatibility issues - and which will therefore have a better chance of success or not bother. All of us who have been here understand the frustrations, and will not think bad of you if you decide not to take the risk of throwing good time after bad.

All that said, I don't believe that splitting the code out in the way I suggested will take more than 20 minutes of your time. There might be a few tweaks to tests to make them work with the internals - but test failures should IMO be seen as a failure of backwards compatibility.

Then you have to see what happens if you call a Str method on MyStr or have a Str parameter and feed it a MyStr object or vice versa, and write some code to give a better exception messages, to coerce data from Str to MyStr and vice versa, and (if possible) to help php to use the coercion methods automatically when needed - and to write additional tests all this new code.

So, its probably a days work all in to create a new PR and see what happens.

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.

7 participants