Skip to content

Conversation

Rahul-Mac
Copy link
Contributor

@Rahul-Mac Rahul-Mac commented Sep 13, 2025

This PR introduces a new method to determine if a value is a valid email. Currently, the only way to check this is using the Validator. This change transfers the logic of ValidatesAttributes::validateEmail() into Str::isEmail()

Signature

public static function isEmail(
        \Stringable|string $value,
        bool $rfc = false,
        bool $strict = false,
        bool $dns = false,
        bool $spoof = false,
        bool $filter = false,
        bool $filterUnicode = false,
        array $customValidations = [],
    ): bool {}

Usage

Basic

Str::isEmail('[email protected]'); // true

Str::isEmail('invalid-email'); // false

Validation Mode

Str::isEmail('[email protected]', dns: true); // true

Str::isEmail('test👨‍💻@domain.com', rfc: true); // false

Note

RFC validation will be used by default if all the flags are set to false and there aren't any custom validations added.

Stringable class

(new Stringable('[email protected]'))->isEmail(); // true

@Rahul-Mac Rahul-Mac marked this pull request as draft September 13, 2025 06:22
@Rahul-Mac Rahul-Mac changed the title feat: add isEmail() for Str and Stringable feat: add isEmail() for Str and Stringable Sep 13, 2025
@Rahul-Mac Rahul-Mac changed the title feat: add isEmail() for Str and Stringable [12.x] Add isEmail() for Str and Stringable Sep 13, 2025
@Rahul-Mac Rahul-Mac marked this pull request as ready for review September 13, 2025 06:42
@rodrigopedra
Copy link
Contributor

Attempts just from this year:

Good luck.

@Rahul-Mac
Copy link
Contributor Author

@rodrigopedra So this is the 5th one. Yikes! I wanted this method to exist and use it. Seems like there is a demand for this, although the odds of getting this merged are slim. Anyway, I'll let it stay.

return false;
}

$validations = (new Collection($parameters))
Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't use parameters here like we do in validation. You can use named arguments for better readability.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@taylorotwell Should I go for something like this?

public static function isEmail(
    $value,
    bool $strict = false,
    bool $dns = false,
    bool $spoof = false,
    bool $filter = false,
    bool $filterUnicode = false,
    array $customValidations = [],
) {}

@taylorotwell taylorotwell marked this pull request as draft September 13, 2025 12:27
*/
public static function isEmail($value, $parameters = [])
{
if (! is_string($value) && ! (is_object($value) && method_exists($value, '__toString'))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't testing if the object's class implements Stringable be better?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@shaedrich That sounds better. I'll fix it.

* Determine if the given value is a valid email.
*
* @param mixed $value
* @param array<int, int|string> $parameters
Copy link
Contributor

Choose a reason for hiding this comment

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

You could narrow this further down:

Suggested change
* @param array<int, int|string> $parameters
* @param array<int, int|'strict'|'dns'|'spoof'|'filter'|'filter_unicode'|class-string> $parameters

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@shaedrich Sure, but @taylorotwell said:

I wouldn't use parameters here like we do in validation. You can use named arguments for better readability.

Should I go for this instead?

public static function isEmail(
    $value,
    bool $strict = false,
    bool $dns = false,
    bool $spoof = false,
    bool $filter = false,
    bool $filterUnicode = false,
    array $customValidations = [],
) {}

Or I'd love a better suggestion than my proposal.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, Taylor's feedback and your solution look good 👍🏻

You can incorporate my suggestion into this without a problem:

/**
 * @param  class-string[]  $customValidations
 */
public static function isEmail(
    $value,
    bool $strict = false,
    bool $dns = false,
    bool $spoof = false,
    bool $filter = false,
    bool $filterUnicode = false,
    array $customValidations = [],
) {}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@shaedrich Great! I'll implement all the changes. BTW, may I add type declarations?

public static function isEmail(
    \Stringable|string $value,
    // other arguments...

Instead of the if check?

if (! is_string($value) && ! (is_object($value) && $value instanceof \Stringable)) {
    return false;
}

Copy link
Contributor

@shaedrich shaedrich Sep 13, 2025

Choose a reason for hiding this comment

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

We tend to use native types over PHPDoc ones these days, so that would make sense 👍🏻

@Rahul-Mac Rahul-Mac marked this pull request as ready for review September 14, 2025 03:01
Copy link
Contributor

@shaedrich shaedrich left a comment

Choose a reason for hiding this comment

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

@Rahul-Mac I can't approve or reject since I'm not a reviewer, but Taylor will

@rodrigopedra
Copy link
Contributor

@Rahul-Mac

You need to move the email validator dependency from the illuminate/validator's composer.json file to the illuminate/support's composer.json file.

For those who use these components outside a Laravel app.

"egulias/email-validator": "^3.2.5|^4.0",

@Rahul-Mac Rahul-Mac marked this pull request as draft September 15, 2025 01:00
@Rahul-Mac Rahul-Mac marked this pull request as ready for review September 15, 2025 01:21
@NickSdot
Copy link
Contributor

Perhaps it should be considered to make it easier to validate a single value in a fluent way in the validator and return the string helper?

After all, this is a string helper, not a string validator. Validator already depends on Support, so there wouldn't be any dependency changes.

@Rahul-Mac
Copy link
Contributor Author

After all, this is a string helper, not a string validator.

@NickSdot Str contains validation methods like isUrl, isJson, etc., and there could be a need to validate an email in a service (I had this problem), so I decided to migrate the logic.

@NickSdot
Copy link
Contributor

After all, this is a string helper, not a string validator.

@NickSdot Str contains validation methods like isUrl, isJson, etc., and there could be a need to validate an email in a service (I had this problem), so I decided to migrate the logic.

I know. But they shouldn't be there (at least they don't have dependencies, though). Where does it end? Do we soon have the full validator in the string helper?

@shaedrich
Copy link
Contributor

shaedrich commented Sep 16, 2025

After all, this is a string helper, not a string validator.

I was advocating for reducing the bloat of the string helper, yet to no avail (#52979), so I have to assume, this is by design for a lack of effort to change that.

@NickSdot
Copy link
Contributor

After all, this is a string helper, not a string validator.

I was advocating for reducing the bloat of the string helper, yet to no avail (#52979), so I have to assume, this is by design for a lack of effort to change that.

I remember that PR.

Here since Laravel 4. Things got way too overwhelming ever since. In average we added ~42,000 new lines per year since L5.4. And that's just laravel/framework. Str has > 100 methods. PHPdoc doesn't include examples. Navigating in the IDE makes your head want to explode--not only in the string helper, thanks to inheritance.

Now we start to basically "proxy" the validator into the string helper? After this is merged, the rest will follow? How aboutStr::isDate() next? And then range validation for it?

We have so much things in here that people send PRs for methods that already exist. Because no one knows everything anymore. Not even the OGs and Taylor himself (as seen on Twitter).

My personal feeling is that something has to happen, somehow. Maybe a feature freeze year under the theme "let's clean up and reorganise things" would do good. Modernise, type, reduce inheritance, reduce duplication, reduce call stacks, separate concerns, make global helpers namespaced, make IDEs understand Laravel without plugins that make it sweat, etc...

Open a new namespace as a sibling of Illuminate or whatever; then people can opt-in in their own pace. Introduce official rector rules.

This is not me bashing. It's subjective and constructive feedback.


@taylorotwell, you mentioned on Twitter that you are very interested in any feedback. Here is some. 👆

image

@Rahul-Mac
Copy link
Contributor Author

@NickSdot Your argument is compelling. There are tons of classes that appear bloated but the thing is (correct me if I'm wrong here) that Laravel is more of a "making devs happy & rapidly building code" framework more than a "best practices" framework like Symfony.

Let's face it: Laravel violates a few principles. $request->user() violates SOLID. Ideally, we should inject Auth/Factory Contract to get the user. Almost every framework ends up having a few such issues unless they're truly focused on abiding by the principles.

Personally, if I wanted to follow "best practices", I'd code using Symfony. But Laravel helps me get my work done and provides a certain ease although I do prefer Contracts over Facades.

This helper is something that I felt was needed. Let's see what happens. But I'll keep in mind not to come up with another such helper unless it truly solves a problem.

@NickSdot
Copy link
Contributor

NickSdot commented Sep 16, 2025

@NickSdot Your argument is compelling. There are tons of classes that appear bloated but the thing is (correct me if I'm wrong here) that Laravel is more of a "making devs happy & rapidly building code" framework more than a "best practices" framework like Symfony.

Let's face it: Laravel violates a few principles. $request->user() violates SOLID. Ideally, we should inject Auth/Factory Contract to get the user. Almost every framework ends up having a few such issues unless they're truly focused on abiding by the principles.

Personally, if I wanted to follow "best practices", I'd code using Symfony. But Laravel helps me get my work done and provides a certain ease although I do prefer Contracts over Facades.

This helper is something that I felt was needed. Let's see what happens. But I'll keep in mind not to come up with another such helper unless it truly solves a problem.

Screenshot above shows that devs aren't as happy anymore. My point exactly. I don't even talk about principles here. It's about usability. The Laravel approach worked great for a long time. But now Laravel is a massive framework. It has many many more tools compared to L4. Throwing everything just somewhere like it's a kitchen sink will not age well.

@shaedrich
Copy link
Contributor

My personal feeling is that something has to happen, somehow. Maybe a feature freeze year under the theme "let's clean up and reorganise things" would do good. Modernise, type, reduce inheritance, reduce duplication, reduce call stacks, separate concerns, make global helpers namespaced, make IDEs understand Laravel without plugins that make it sweat, etc...

Open a new namespace as a sibling of Illuminate or whatever; then people can opt-in in their own pace. Introduce official rector rules.

This is not me bashing. It's subjective and constructive feedback.

It's great feedback, thanks! 👍🏻 Yeah, that'd be great.

Throwing everything just somewhere like it's a kitchen sink will not age well.

Wholeheartedly agree! 👍🏻

"ext-ctype": "*",
"ext-filter": "*",
"ext-mbstring": "*",
"egulias/email-validator": "^3.2.5|^4.0",
Copy link
Member

Choose a reason for hiding this comment

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

Should just be a suggest:

"suggest": {
"illuminate/filesystem": "Required to use the Composer class (^12.0).",
"laravel/serializable-closure": "Required to use the once function (^1.3|^2.0).",
"league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.7).",
"league/uri": "Required to use the Uri class (^7.5.1).",
"ramsey/uuid": "Required to use Str::uuid() (^4.7).",
"symfony/process": "Required to use the Composer class (^7.2).",
"symfony/uid": "Required to use Str::ulid() (^7.2).",
"symfony/var-dumper": "Required to use the dd function (^7.2).",
"vlucas/phpdotenv": "Required to use the Env class and env helper (^5.6.1)."
},

Copy link
Contributor

Choose a reason for hiding this comment

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

Then Validation/composer.json should keep it as required.

@taylorotwell
Copy link
Member

Thanks for your pull request to Laravel!

Unfortunately, I'm going to delay merging this code for now. To preserve our ability to adequately maintain the framework, we need to be very careful regarding the amount of code we include.

If applicable, please consider releasing your code as a package so that the community can still take advantage of your contributions!

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.

6 participants