6
\$\begingroup\$

I recently discovered the file_get_contents function and wanted to put it to some use, alongside preg_match_all, off the information in this Stack Overflow thread.

<?php
    //use a site with lots of email address on.
    $string = file_get_contents("https://stackoverflow.com/questions/3901070/in-php-how-do-i-extract-multiple-e-mail-addresses-from-a-block-of-text-and-put");

    //initialise an empty array.
    $matches = array();

    //regular expression that matches most email addresses, courtesy of @Eric-Karl.
    $pattern = '/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}/';

    //perform global regular expression match, ie search the entire web page for a particular thing, and store it in the previously initialised array.
    preg_match_all($pattern, $string, $matches);

    //output array of values; remove duplicate email addresses, but maintain incremental key count.
    var_dump(array_values(array_unique($matches[0])));

    //store above in array for upcoming bit.
    $neaterArray = array_values(array_unique($matches[0]));

    //count the amount of variables in the array.
    $count = count($neaterArray);

    //implode array values to string.
    $emailsAsString = implode(", ", $neaterArray);

    //present the string, prepended with the count.
    echo "<h3>$count email addresses in total:</h3> $emailsAsString";

?>

I trust my comments are enough to justify the choices I made. I used the stackoverflow thread I mentioned as a test case, because it had many test/fake emails, but all in an appropriate format for the regex.

Here's the output for this case:

array (size=19)
  0 => string 'apple-touch-icon@2.png' (length=22)
  1 => string 'example@slu.edu' (length=15)
  2 => string 'a+b@google.com.sg' (length=17)
  3 => string 'test1+2@gmail.com' (length=17)
  4 => string 'test-2@yahoo.co.jp' (length=18)
  5 => string 'test@test.com' (length=13)
  6 => string 'test@test.co.uk' (length=15)
  7 => string 'test@google.com.sg' (length=18)
  8 => string 'email@domain.info' (length=17)
  9 => string 'email@domain.inf' (length=16)
  10 => string 'first.lastname@domain.be' (length=24)
  11 => string 'lastname@domain.be' (length=18)
  12 => string 'HIDDENFORLOGICALREASONS@cameranh.rs.gov.br' (length=42)
  13 => string 'HIDDENFORLOGICALREASONS@cameranh.rs.go' (length=38)
  14 => string 'myemail@office21.company.com' (length=28)
  15 => string 'mymail@yahoo.com' (length=16)
  16 => string 'my-e.mail@yahoo.com' (length=19)
  17 => string 'joe@mysite.com' (length=14)
  18 => string 'name@example.com.sv' (length=19)
19 email addresses in total:

apple-touch-icon@2.png, example@slu.edu, a+b@google.com.sg, test1+2@gmail.com, test-2@yahoo.co.jp, test@test.com, test@test.co.uk, test@google.com.sg, email@domain.info, email@domain.inf, first.lastname@domain.be, lastname@domain.be, HIDDENFORLOGICALREASONS@cameranh.rs.gov.br, HIDDENFORLOGICALREASONS@cameranh.rs.go, myemail@office21.company.com, mymail@yahoo.com, my-e.mail@yahoo.com, joe@mysite.com, name@example.com.sv

I realise the string at the end isn't particularly necessary, I just wanted to make use of implode.

Some questions:

  • Is this a good way to go about solving the problem? Are there parts that could be optimised with more suitable functions?

  • Is initialising the array imperative for this program? I tried it without the original declaration (in the preg_match_all line) and it still functioned.

  • Is there a part/are there parts of the code that comes across as poorly written?

\$\endgroup\$
5
\$\begingroup\$

First, your code isn't exactly substantial, so it's hard to write a good review.

Up first, file_get_contents() works, but curl is faster.

It's a little longer, but try this:

function file_get_contents_curl($url) {
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_AUTOREFERER, TRUE);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);       

    $data = curl_exec($ch);
    curl_close($ch);

    return $data;
}

Moving on to your regex:

/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}/

Instead of A-Za-z, you can end with a /i, and make your search case insensitive:

/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}/i

Depending on how complex you'll allow emails to be, you can add (or remove), some valid characters.

Next, you perform a array_values(array_unique($matches[0])) twice.

var_dump(array_values(array_unique($matches[0])));

$neaterArray = array_values(array_unique($matches[0]));

Into:

$neaterArray = (array_values(array_unique($matches[0])));
var_dump($neaterArray);

Your variable naming follows a camelCase standard all-throughout, so that's alright.

Your comments are generally good, but //store above in array for upcoming bit is incorrect as I pointed out above.

Is this a good way to go about solving the problem? Are there parts that could be optimised with more suitable functions?

Swapping file_get_contents() with curl will improve the speed of page download.

Is initialising the array imperative for this program? I tried it without the original declaration (in the preg_match_all line) and it still functioned.

You don't need to initialise the variable first, you really only would if it where in a for loop, in this case you can just output to a new variable.

Is there a part/are there parts of the code that comes across as poorly written?

Not really, variable naming could be expressed better by replacing $string with $input and $neaterArray with $duplicateRemovedArray, but other than that, the code is good.

\$\endgroup\$
  • \$\begingroup\$ Can you show some examples of sites that make use of curl to obtain information from another site? \$\endgroup\$ – alanbuchanan May 31 '15 at 20:51
  • \$\begingroup\$ @alanbuchanan If you don't need to re-index the keys after filtering out the duplicates because you are just going to implode them, you can increase efficiency with array_flip(array_flip()).stackoverflow.com/questions/8321620/array-unique-vs-array-flip \$\endgroup\$ – mickmackusa Jun 26 '17 at 6:35
2
\$\begingroup\$

I certainly endorse the use of @Quill's file_get_contents_curl() and using the i pattern modifier to enable case-insensitive searching.

These are my additional suggestions...

[A-Za-z0-9_] is equal to \w

There are many pattern refinements that could be implemented, but improving the validation will results in diminished readability and maintainability. If you need to tighten the validation, it might be wiser to perform a "loose" sweep of the document, then call non-regex functions to validate the email ...but even php's native checks aren't completely accurate.

If you want to accommodate "international emails" that may contain unicode/multibyte characters, you can add the unicode pattern modifier and expand your pattern further, but I won't go down that rabbit hole. You might have a look here: https://stackoverflow.com/a/37320735/2943403

Basic pattern = /[\w.%+-]+@(?:[a-z\d-]+\.)+[a-z]{2,4}/iu

  • preg_match_all() should be written as a conditional check so that your script can be halted when there is a failure or there are no matches. In doing so, there is no need to declare an empty matches() array.

  • Unfortunately array_unique() is slower than array_keys(array_flip()) and array_flip(array_flip(). If you cared about re-indexing the array of emails, you could is the array_keys() technique, but because you are just imploding the array to a string the keys are irrelevant so the "double flip" is the most efficient technique.

Suggested snippet:

$pattern = '/[\w.%+-]+@(?:[a-z\d-]+\.)+[a-z]{2,4}/iu';
if (!preg_match_all($pattern, $contents, $emails)) {
    echo '<h3>No emails found</h3>';
} else {
    $unique_emails = array_flip(array_flip($emails[0]));
    $count         = sizeof($unique_emails);
    $email_string  = implode(', ', $unique_emails);
    echo "<h3>$count email addresses in total:</h3> $email_string";
}

Generally I recommend not declaring single-use variables, but if it improves readability or you happen to prefer shorter versus longer lines of code then there is no harm.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.