8

I am logging in users via windows authentication and then storing that user's rights in a session variable. I use a delimited method of rights storage in a database i.e:

$rights //retrieved from database 
= 'read,edit,delete,admin'

so my question is should I;

//generate variable
$_SESSION['userrights'] = $rights ($rights is retrieved from database)

//use strpos to find if right is allowed
if (strpos($_SESSION['userrights'],"admin") !== false) { // do the function }

OR

//make array of rights
$_SESSION['userrights'] = explode(',',$rights)

//use in_array to find if right is allowed
if (in_array("admin",$_SESSION['userrights'])) { // do the function }

Bit of a OCD question as I presume the difference will be pretty much negligible for what I am doing but which would be the faster (use less resources) method?

Any answers appreciated except ones that insult my method of rights storage!

DJB
  • 257
  • 2
  • 11
  • 3
    Use the second method, for sure. Checking for a specific value in an array is a much better method compared to looking for a value inside a string. Note that `in_array` argument order is `needle,haystack`, not `haystack,needle` as you have there. – jszobody Jan 12 '14 at 02:47
  • 2:50AM lazy copy and pasting! appreciate the answer! – DJB Jan 12 '14 at 02:51
  • 2
    Why don't you store them as the keys in an associative array? That should be faster than either. – Barmar Jan 12 '14 at 03:08
  • You mean so I could use isset($_SESSION['admin']) instead? – DJB Jan 12 '14 at 03:16
  • 2
    Yeah, or `isset($_SESSION['userrights']['admin'])` – Barmar Jan 12 '14 at 03:18
  • That's a pretty brilliant solution! explode to $arr, and then `foreach($arr as $str) { $_SESSION['userrights'][$str] = 1 }` I presume would be the best method? – DJB Jan 12 '14 at 03:22
  • @DJB I like [this idea](http://discdev.com/2010/07/16/using-binary-numbers-for-permissions/) of [permissions](http://stackoverflow.com/a/9811127/3110638). Nice [comparable](http://stackoverflow.com/a/10504868/3110638) with [bitwise operators](http://www.php.net/manual/en/language.operators.bitwise.php). – Jonny 5 Jan 12 '14 at 04:14

4 Answers4

17

As I often work with large datasets, I'd go with isset or !empty on an associative array and check for the key, like @Barmar suggests. Here is a quick 1M benchmark on an Intel® Core™ i3-540 (3.06 GHz)

$test = array("read", "edit", "delete", "admin");

echo "<pre>";

// --- strpos($rights,$test[$i%4]) ---

$rights = 'read,edit,delete,admin';
$mctime = microtime(true);
for($i=0; $i<=1000000; $i++) { if (strpos($rights,$test[$i%4]) !== false) { }}
echo '  strpos(... '.round(microtime(true)-$mctime,3)."s\n";

// --- in_array($test[$i%4],$rights) ---

$rights = array("read", "edit", "delete", "admin");
$mctime = microtime(true);
for($i=0; $i<=1000000; $i++) { if (in_array($test[$i%4],$rights)) { }}
echo 'in_array(... '.round(microtime(true)-$mctime,3)."s\n";

// --- !empty($rights[$test[$i%4]]) ---

$rights = array('read' => 1, 'edit' => 1, 'delete' => 1, 'admin' => 1);
$mctime = microtime(true);
for($i=0; $i<=1000000; $i++) { if (!empty($rights[$test[$i%4]])) { }}
echo '  !empty(... '.round(microtime(true)-$mctime,3)."s\n";

// --- isset($rights[$test[$i%4]]) ---

$rights = array('read' => 1, 'edit' => 1, 'delete' => 1, 'admin' => 1);
$mctime = microtime(true);
for($i=0; $i<=1000000; $i++) { if (isset($rights[$test[$i%4]])) { }}
echo '   isset(... '.round(microtime(true)-$mctime,3)."s\n\n";

echo "</pre>";

The winner is isset:

  strpos(... 0.393s
in_array(... 0.519s
  !empty(... 0.232s
   isset(... 0.209s
Jonny 5
  • 12,171
  • 2
  • 25
  • 42
  • 1
    The performance characteristics come from using an associative array instead of a list. Although your benchmark is slightly flawed, you're not including any balance, and only testing worst case scenario of the `strpos` and `in_array` (last entry in the array/string). To make it fairer, you should use a modulo 4 on $i and fetch that from an array. However, associative arrays scale better and false results is also faster with them. – Aidiakapi Jan 12 '14 at 15:00
  • @Aidiakapi Thanks for your comment! Modified it a bit. – Jonny 5 Jan 12 '14 at 15:17
  • 2
    @Jonny5 Thank you for going to this amount of effort to appease my ocd! I am using isset due to yours and Barmar 's answers.. much appreciated – DJB Jan 13 '14 at 00:08
  • 1
    I might be missing something but does this test take into account the time it would take to convert from the csv in the database string to the array keys? or is that considered negligible? – Mike May 01 '17 at 22:37
  • I'd like to know the answer to what Mike has asked above also! If you have a text blob, would it be faster to explode it into an array and then use isset() over just using strpos() straight up on the text? – Andrew Schultz Feb 23 '18 at 12:30
7

strpos is the fastest way to search a text needle, per the php.net documentation for strstr():

If you only want to determine if a particular needle occurs within haystack, use the faster and less memory intensive function strpos() instead.1

Sᴀᴍ Onᴇᴌᴀ
  • 8,218
  • 8
  • 36
  • 58
1

A benchmark that proofs that strpos is not the fastest method, but faster than in_array:

<?php

echo phpversion() . PHP_EOL;

// build random array
$array = array_fill(0, 10000, 16);
$array = array_map('openssl_random_pseudo_bytes', $array);
$array = array_map('bin2hex', $array);
$array_flipped = array_flip($array);
$string = implode($array);
$random_keys = array_rand($array_flipped, 10);

$loops = 10000;

$start = microtime(true);
for ($i = 0; $i < $loops; $i++) {
    strpos($string, $random_keys[ rand(0, 9) ]);
}
echo __LINE__ . ': ' . round(microtime(true) - $start, 5) . PHP_EOL;

$start = microtime(true);
for ($i = 0; $i < $loops; $i++) {
    in_array($random_keys[ rand(0, 9) ], $array);
}
echo __LINE__ . ': ' . round(microtime(true) - $start, 5) . PHP_EOL;

$start = microtime(true);
for ($i = 0; $i < $loops; $i++) {
    isset($array_flipped[ $random_keys[ rand(0, 9) ] ]);
}
echo __LINE__ . ': ' . round(microtime(true) - $start, 5) . PHP_EOL;

$start = microtime(true);
for ($i = 0; $i < $loops; $i++) {
    $array_flipped = array_flip($array);
    isset($array_flipped[ $random_keys[ rand(0, 9) ] ]);
}
echo __LINE__ . ': ' . round(microtime(true) - $start, 5) . PHP_EOL;

?>

Result:

5.6.31
19: 1.11484
25: 1.3109
31: 0.00237
38: 13.64204

As you can see it is important not to flip an array on-the-fly to benefit from isset.

mgutt
  • 5,867
  • 2
  • 50
  • 77
-1

Have you considered the possibility of doing a query on the database if $_SESSION['userrights'] exists in the database? You're already doing a query to get the list of rights in your example, why not do a query for a specific $_SESSION['userrights'] and check if there are any rows returned?

Hayden
  • 2,082
  • 1
  • 14
  • 18
  • I was under the impression that querying would be far more intensive that either strpos or in_array.. I already query the database and store the value in a session var so I have the values to hand. Unless I mis-understand, you are suggesting I query every time I want to check if user has a certain level of rights? (I have multiple things displaying based on whether user has one right or another) – DJB Jan 12 '14 at 03:12
  • How many times are you doing a user rights look-up on a page? – Hayden Jan 12 '14 at 03:13
  • The application isn't finished yet, but I'm guessing the maximum right now would be 2/3 – DJB Jan 12 '14 at 03:23