PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / trunk
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution vtrunk
2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 1.7.2 All 33 releases
fluent-booking / vendor / wpfluent / framework / src / WPFluent / Randomizer / Randomizer.php

Randomizer.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution trunk, at vendor/wpfluent/framework/src/WPFluent/Randomizer/Randomizer.php

229 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\Framework\Randomizer;
4
5 use Exception;
6 use FluentBooking\Framework\Support\InvalidArgumentException;
7
8 /**
9 * An implementation of the Randomizer class.
10 * @see https://www.php.net/manual/en/class.random-randomizer.php
11 */
12 if (class_exists('Random\Randomizer')) {
13 /**
14 * Wrapper around the native PHP 8.2+ Random\Randomizer.
15 *
16 * @method string getBytes(int $length)
17 * @method int getInt(int $min, int $max)
18 * @method float getFloat(float $min, float $max)
19 * @method int nextInt()
20 * @method float nextFloat()
21 * @method array pickArrayKeys(array $array, int $num)
22 * @method array shuffleArray(array $array)
23 * @method string shuffleBytes(string $string)
24 */
25 final class Randomizer
26 {
27 use GetStringTrait;
28
29 private $randomizer;
30
31 public function __construct()
32 {
33 // @phpstan-ignore-next-line
34 $this->randomizer = new \Random\Randomizer();
35 }
36
37 public function getBytesFromString(string $string, int $length): string
38 {
39 return $this->randomizer->getBytesFromString($string, $length);
40 }
41
42 public function __call($method, $args = [])
43 {
44 return $this->randomizer->{$method}(...$args);
45 }
46 }
47 } else {
48 final class Randomizer
49 {
50 use GetStringTrait;
51
52 private $engine;
53
54 public function __construct()
55 {
56 $this->engine = $this->makeEngine();
57 }
58
59 public function getBytes(int $length)
60 {
61 $result = '';
62
63 while (strlen($result) < $length) {
64 $result .= $this->engine->generate();
65 }
66
67 return substr($result, 0, $length);
68 }
69
70 public function getBytesFromString(string $string, int $length)
71 {
72 if ($length === 0) {
73 throw new InvalidArgumentException('Length cannot be zero.');
74 }
75
76 $sourceLength = strlen($string);
77
78 if ($sourceLength === 0) {
79 throw new InvalidArgumentException(
80 'Source string cannot be empty.'
81 );
82 }
83
84 $result = [];
85
86 for ($i = 0; $i < $length; $i++) {
87 $index = $this->getInt(0, $sourceLength - 1);
88 $result[] = $string[$index];
89 }
90
91 return implode('', $result);
92 }
93
94 public function getFloat(float $min, float $max)
95 {
96 // Match native Random\Randomizer::getFloat — allow $min == $max
97 // (returns $min); only reject $min > $max.
98 if ($min > $max) {
99 $this->throwInvalidRange(
100 'Argument #1 ($min) must be less than or equal to argument #2 ($max).'
101 );
102 }
103
104 // When $min == $max, the formula naturally returns $min
105 // (anything * 0 == 0), so no special case needed.
106 return $min + ($this->nextFloat() * ($max - $min));
107 }
108
109 public function getInt(int $min, int $max)
110 {
111 // Match native Random\Randomizer::getInt — throw on $min > $max
112 // and delegate to random_int (which already handles the range
113 // correctly, rejection-sampled, no manual offset arithmetic).
114 if ($min > $max) {
115 $this->throwInvalidRange(
116 'Argument #1 ($min) must be less than or equal to argument #2 ($max).'
117 );
118 }
119
120 return random_int($min, $max);
121 }
122
123 /**
124 * Throw the same exception type the native Random\Randomizer would
125 * throw on bad range input. \ValueError landed in PHP 8.0; on older
126 * runtimes fall back to InvalidArgumentException so we always raise
127 * something meaningful.
128 */
129 private function throwInvalidRange(string $message)
130 {
131 if (class_exists('ValueError', false)) {
132 throw new \ValueError($message);
133 }
134
135 throw new InvalidArgumentException($message);
136 }
137
138 public function nextFloat()
139 {
140 return random_int(0, PHP_INT_MAX) / (PHP_INT_MAX + 1);
141 }
142
143 public function nextInt()
144 {
145 return random_int(0, PHP_INT_MAX);
146 }
147
148 public function pickArrayKeys(array $array, int $num)
149 {
150 $count = count($array);
151
152 if ($num < 1 || $num > $count) {
153 throw new InvalidArgumentException(
154 'Argument #2 ($num) must be between 1 and the number of elements in argument #1 ($array).'
155 );
156 }
157
158 // Match native Random\Randomizer::pickArrayKeys — picked keys
159 // are returned in their ORIGINAL position order in the array,
160 // not in pick order. Strategy: build an index list, partial
161 // Fisher-Yates to select $num indices, then sort those indices
162 // and map back to keys. Runs in O(n) time.
163 $keys = array_keys($array);
164 $indices = range(0, $count - 1);
165
166 // Partial Fisher-Yates: only shuffle the first $num positions.
167 for ($i = 0; $i < $num; $i++) {
168 $j = random_int($i, $count - 1);
169 [$indices[$i], $indices[$j]] = [$indices[$j], $indices[$i]];
170 }
171
172 // Take the picked indices, sort to restore original order.
173 $picked = array_slice($indices, 0, $num);
174 sort($picked);
175
176 return array_map(static function ($i) use ($keys) {
177 return $keys[$i];
178 }, $picked);
179 }
180
181 public function shuffleArray(array $array)
182 {
183 $count = count($array);
184
185 if ($count < 2) {
186 return $array;
187 }
188
189 // Match native Random\Randomizer::shuffleArray — a single
190 // unbiased Fisher-Yates pass. Do NOT loop until the result
191 // differs from the input; that biases the distribution and
192 // can infinite-loop on tiny arrays. A fair shuffle MUST be
193 // allowed to occasionally produce the original order.
194 for ($i = $count - 1; $i > 0; $i--) {
195 $j = random_int(0, $i);
196 [$array[$i], $array[$j]] = [$array[$j], $array[$i]];
197 }
198
199 return $array;
200 }
201
202 public function shuffleBytes(string $bytes)
203 {
204 $array = str_split($bytes);
205 $shuffled = $this->shuffleArray($array);
206 return implode('', $shuffled);
207 }
208
209 private function makeEngine()
210 {
211 return new class {
212 public function generate() {
213 $length = 32;
214
215 if (function_exists('random_bytes')) {
216 return random_bytes($length);
217 }
218
219 if (function_exists('openssl_random_pseudo_bytes')) {
220 return openssl_random_pseudo_bytes($length);
221 }
222
223 throw new Exception('No secure random source available.');
224 }
225 };
226 }
227 }
228 }
229