1 <?php
  2 
  3 if (!is_callable('random_int')) {
  4     /**
  5      * Random_* Compatibility Library
  6      * for using the new PHP 7 random_* API in PHP 5 projects
  7      *
  8      * The MIT License (MIT)
  9      *
 10      * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
 11      *
 12      * Permission is hereby granted, free of charge, to any person obtaining a copy
 13      * of this software and associated documentation files (the "Software"), to deal
 14      * in the Software without restriction, including without limitation the rights
 15      * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 16      * copies of the Software, and to permit persons to whom the Software is
 17      * furnished to do so, subject to the following conditions:
 18      *
 19      * The above copyright notice and this permission notice shall be included in
 20      * all copies or substantial portions of the Software.
 21      *
 22      * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 23      * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 24      * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 25      * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 26      * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 27      * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 28      * SOFTWARE.
 29      */
 30 
 31     /**
 32      * Fetch a random integer between $min and $max inclusive
 33      *
 34      * @param int $min
 35      * @param int $max
 36      *
 37      * @throws Exception
 38      *
 39      * @return int
 40      */
 41     function random_int($min, $max)
 42     {
 43         /**
 44          * Type and input logic checks
 45          *
 46          * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
 47          * (non-inclusive), it will sanely cast it to an int. If you it's equal to
 48          * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
 49          * lose precision, so the <= and => operators might accidentally let a float
 50          * through.
 51          */
 52 
 53         try {
 54             $min = RandomCompat_intval($min);
 55         } catch (TypeError $ex) {
 56             throw new TypeError(
 57                 'random_int(): $min must be an integer'
 58             );
 59         }
 60 
 61         try {
 62             $max = RandomCompat_intval($max);
 63         } catch (TypeError $ex) {
 64             throw new TypeError(
 65                 'random_int(): $max must be an integer'
 66             );
 67         }
 68 
 69         /**
 70          * Now that we've verified our weak typing system has given us an integer,
 71          * let's validate the logic then we can move forward with generating random
 72          * integers along a given range.
 73          */
 74         if ($min > $max) {
 75             throw new Error(
 76                 'Minimum value must be less than or equal to the maximum value'
 77             );
 78         }
 79 
 80         if ($max === $min) {
 81             return $min;
 82         }
 83 
 84         /**
 85          * Initialize variables to 0
 86          *
 87          * We want to store:
 88          * $bytes => the number of random bytes we need
 89          * $mask => an integer bitmask (for use with the &) operator
 90          *          so we can minimize the number of discards
 91          */
 92         $attempts = $bits = $bytes = $mask = $valueShift = 0;
 93 
 94         /**
 95          * At this point, $range is a positive number greater than 0. It might
 96          * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
 97          * a float and we will lose some precision.
 98          */
 99         $range = $max - $min;
100 
101         /**
102          * Test for integer overflow:
103          */
104         if (!is_int($range)) {
105 
106             /**
107              * Still safely calculate wider ranges.
108              * Provided by @CodesInChaos, @oittaa
109              *
110              * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
111              *
112              * We use ~0 as a mask in this case because it generates all 1s
113              *
114              * @ref https://eval.in/400356 (32-bit)
115              * @ref http://3v4l.org/XX9r5  (64-bit)
116              */
117             $bytes = PHP_INT_SIZE;
118             $mask = ~0;
119 
120         } else {
121 
122             /**
123              * $bits is effectively ceil(log($range, 2)) without dealing with
124              * type juggling
125              */
126             while ($range > 0) {
127                 if ($bits % 8 === 0) {
128                     ++$bytes;
129                 }
130                 ++$bits;
131                 $range >>= 1;
132                 $mask = $mask << 1 | 1;
133             }
134             $valueShift = $min;
135         }
136 
137         $val = 0;
138         /**
139          * Now that we have our parameters set up, let's begin generating
140          * random integers until one falls between $min and $max
141          */
142         do {
143             /**
144              * The rejection probability is at most 0.5, so this corresponds
145              * to a failure probability of 2^-128 for a working RNG
146              */
147             if ($attempts > 128) {
148                 throw new Exception(
149                     'random_int: RNG is broken - too many rejections'
150                 );
151             }
152 
153             /**
154              * Let's grab the necessary number of random bytes
155              */
156             $randomByteString = random_bytes($bytes);
157 
158             /**
159              * Let's turn $randomByteString into an integer
160              *
161              * This uses bitwise operators (<< and |) to build an integer
162              * out of the values extracted from ord()
163              *
164              * Example: [9F] | [6D] | [32] | [0C] =>
165              *   159 + 27904 + 3276800 + 201326592 =>
166              *   204631455
167              */
168             $val &= 0;
169             for ($i = 0; $i < $bytes; ++$i) {
170                 $val |= ord($randomByteString[$i]) << ($i * 8);
171             }
172 
173             /**
174              * Apply mask
175              */
176             $val &= $mask;
177             $val += $valueShift;
178 
179             ++$attempts;
180             /**
181              * If $val overflows to a floating point number,
182              * ... or is larger than $max,
183              * ... or smaller than $min,
184              * then try again.
185              */
186         } while (!is_int($val) || $val > $max || $val < $min);
187 
188         return (int)$val;
189     }
190 }
191