├── .gitignore ├── README.md ├── composer.json ├── phpunit.xml ├── src └── Pixeloution │ └── Random │ └── Randomizer.php └── tests ├── TrueRandomNetworkTest.php └── TrueRandomNoNetworkTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | composer.lock 3 | vendor/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TrulyRandom 2 | =========== 3 | Composer-compatible library to interact with random.org's API in order to generate truly random 4 | lists of integers, sequences of integers, and random alpha-numeric strings. 5 | 6 | Random.org does limit the amount of random numbers/strings you can generate in a day, and 7 | this program will check your remaining quota before sending requests. If you need more than 8 | the free allowance, there are instructions on random.org for purchasing additional. 9 | 10 | I have nothing to do with random.org other then thinking its a cool service. 11 | 12 | ## Installation 13 | Install via Packagist 14 | 15 | "require" : 16 | { 17 | "pixeloution/true-random" : "*" 18 | }, 19 | 20 | in your composer.json file 21 | 22 | ## Set-Up 23 | use Pixeloution\Random\Randomizer; 24 | 25 | # takes a partial User Agent as an argument; random.org requests you use your 26 | # email address in case of issues 27 | $generator = new Randomizer( 'name@example.com' ); 28 | 29 | ## Generate Lists of Integers 30 | Returns an array of non-unique integers between min, max 31 | 32 | $generator->integers( $minimum_value, $maximum_value, $quantity ); 33 | 34 | ## Generate A Sequence of Integers 35 | Returns an array of a integers from $start to $end, each integer appearing once. 36 | 37 | $generator->sequence( $start, $end ); 38 | 39 | ## Generate a list of random strings 40 | Returns an array of strings $length characters long, made up of character types 41 | specified via bitwise options. The default value is `ALL ^ UNIQUE` 42 | 43 | 44 | Options are: 45 | * Randomizer::DIGITS 46 | * Randomizer::UPPERCASE 47 | * Randomizer::LOWERCASE 48 | * Randomizer::UNIQUE 49 | * Randomizer::ALL 50 | 51 | Some examples: 52 | 53 | # returns all strings containing uppercase and lowercase only 54 | $generator->strings( $len, $qty, Randomizer::UPPERCASE | Randomizer::LOWERCASE ); 55 | 56 | # returns lowercase strings, no repeated letters 57 | $generator->strings( $len, $qty, Randomizer::LOWERCASE | Randomizer::UNIQUE ); 58 | 59 | # returns uppercase, lowercase, numeric with non-unique charaters. this is the default 60 | $generator->strings( $len, $qty, Randomizer::ALL ^ Randomizer::UNIQUE ); 61 | 62 | 63 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "pixeloution/true-random", 3 | "minimum-stability" : "dev", 4 | "description" : "Interface for getting random datra from random.org", 5 | "require" : 6 | { 7 | "guzzle/guzzle" : "3.5.*" , 8 | "php" : ">=5.3.2" 9 | }, 10 | "require-dev" : 11 | { 12 | "phpunit/phpunit" : "3.7.*", 13 | "mockery/mockery" : "dev-master@dev" 14 | }, 15 | "autoload" : 16 | { 17 | "psr-0" : { "Pixeloution" : "src/" } 18 | } 19 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Pixeloution/Random/Randomizer.php: -------------------------------------------------------------------------------- 1 | browser = $browser ?: new \Guzzle\Http\Client(); 88 | $this->browser->setUserAgent( 'RandomizerLib/' . $UA ); 89 | $this->browser->setBaseUrl ( $this->service ); 90 | } 91 | 92 | /** 93 | * returns a single integer between min/max 94 | * @param [type] $min [description] 95 | * @param [type] $max [description] 96 | * @return [type] [description] 97 | */ 98 | public function integer($min, $max) 99 | { 100 | if($min >= $max) 101 | throw new InvalidArgumentException('For arguments ($min,$max) min must be less than max'); 102 | 103 | $data = $this->integers($min, $max, 1); 104 | return $data[0]; 105 | } 106 | 107 | /** 108 | * generate a list of integers 109 | * 110 | * @param integer $min 111 | * @param integer $max 112 | * @param integer $quantity 113 | * 114 | * @return array 115 | */ 116 | public function integers($min, $max, $quantity = 1) 117 | { 118 | if($min >= $max) 119 | throw new InvalidArgumentException('For arguments ($min,$max) min must be less than max'); 120 | 121 | return $this->fetchData(sprintf($this->integers, $quantity, $min, $max)); 122 | } 123 | 124 | /** 125 | * randomizes the numbers from $low to $high and returns an array in random order. 126 | * asking for a sequence between 1, 5 returns an array with the values 1, 2, 3, 4, 5 127 | * in a randomized order 128 | * 129 | * @param integer $low 130 | * @param integer $high 131 | * 132 | * @return array 133 | */ 134 | public function sequence($low, $high) 135 | { 136 | if($low >= $high) 137 | throw new InvalidArgumentException('For arguments ($low,$high) low must be less than high'); 138 | 139 | return $this->fetchData(sprintf($this->sequence, $low, $high)); 140 | } 141 | 142 | /** 143 | * creates random strings up to 20 characters in length, with options based on 144 | * a bitmask for allowing digits, uppercase, lowercase, and unique/not characters 145 | * 146 | * @param integer $length 147 | * @param integer $quantity 148 | * @param integer $opts see class constants 149 | * 150 | * @return array 151 | */ 152 | public function strings($length, $quantity = 1, $opts = null) 153 | { 154 | if($length > self::MAX_STRING || $length < self::MIN_STRING) 155 | throw new \InvalidArgumentException('value must be between 1 and 20'); 156 | 157 | if($opts === null) 158 | $opts = Randomizer::ALL ^ Randomizer::UNIQUE; 159 | 160 | # determine valid character sets for random string generated 161 | $digits = ($opts & Randomizer::DIGITS) ? 'on' : 'off'; 162 | $upper = ($opts & Randomizer::UPPERCASE) ? 'on' : 'off'; 163 | $lower = ($opts & Randomizer::LOWERCASE) ? 'on' : 'off'; 164 | $unique = ($opts & Randomizer::UNIQUE) ? 'on' : 'off'; 165 | 166 | return $this->fetchData( sprintf($this->strings, $quantity, $length, $digits, $upper, $lower, $unique) ); 167 | } 168 | 169 | 170 | /** 171 | * returns remaining bits left from random.org for free usage 172 | * 173 | * @throws QuotaExceededException 174 | * @return int 175 | */ 176 | public function checkQuota() 177 | { 178 | $request = $this->browser->get($this->quota); 179 | $response = $request->send(); 180 | 181 | if($response->getStatusCode() !== 200) 182 | throw new ConnectivityException( 'unable to fetch data from random.org' ); 183 | 184 | $remaining = trim( $response->getBody() ); 185 | 186 | // should this really be an exception? 187 | if($remaining < self::MINIMUM_BITS_REMAINING) 188 | throw new QuotaExceededException('You have exceeded your quota. Visit Random.org to learn more'); 189 | 190 | return $remaining; 191 | } 192 | 193 | /** 194 | * setting this value to true causes the object to output used bits and remaining 195 | * bits during each request. should be left false for production 196 | * 197 | * @param bool $setting 198 | */ 199 | public function setReportQuota($setting) 200 | { 201 | $this->reportQuota = $setting; 202 | } 203 | 204 | 205 | protected function fetchData($uri) 206 | { 207 | $start = $this->checkQuota(); 208 | 209 | $request = $this->browser->get($uri); 210 | 211 | // timeout length recommended by random.org 212 | $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT, 30); 213 | 214 | $response = $request->send(); 215 | 216 | if( $this->reportQuota ) 217 | { 218 | $end = $this->checkQuota(); 219 | $spent = $start - $end; 220 | 221 | echo "spent: $spent\n"; 222 | echo "remaining: $end\n"; 223 | } 224 | 225 | return $this->parse($response); 226 | } 227 | 228 | protected function parse($response) 229 | { 230 | if( $response->getStatusCode() !== 200 ) 231 | throw new ConnectivityException( 'unable to fetch data from random.org' ); 232 | 233 | $results = $response->getBody(); 234 | 235 | return explode("\n", trim($results)); 236 | } 237 | } 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /tests/TrueRandomNetworkTest.php: -------------------------------------------------------------------------------- 1 | generator = new Randomizer( 10 | 'test@example.com' 11 | ); 12 | } 13 | 14 | /** 15 | * generate a single integer 16 | * 17 | * @test 18 | */ 19 | public function generateInteger() 20 | { 21 | 22 | $response = $this->generator->integer(35,36); 23 | 24 | $this->assertTrue(is_numeric($response)); 25 | $this->assertGreaterThan(34, $response); 26 | $this->assertLessThan(37, $response); 27 | } 28 | 29 | /** 30 | * generate a sequence of integers allowing repeats 31 | * 32 | * @test 33 | */ 34 | public function generateIntegers() 35 | { 36 | $response = $this->generator->integers(35,36,10); 37 | 38 | $this->assertCount(10, $response); 39 | $this->assertTrue( 2 == count(array_unique($response))); 40 | } 41 | 42 | /** 43 | * should create a sequence of numbers 44 | * 45 | * @test 46 | */ 47 | public function generateSequence() 48 | { 49 | $response = $this->generator->sequence(5,10); 50 | sort($response); 51 | 52 | $this->assertEquals(array(5,6,7,8,9,10), $response); 53 | } 54 | 55 | /** 56 | * @test 57 | */ 58 | public function generateStringDigitsUppercase() 59 | { 60 | $response = $this->generator->strings(10, 1, Randomizer::DIGITS | Randomizer::UPPERCASE); 61 | echo "\nTesting response string {$response[0]}\n"; 62 | $this->assertRegExp('/[\dA-Z]+/', $response[0]); 63 | } 64 | 65 | /** 66 | * @test 67 | */ 68 | public function generateStringWithNoDigits() 69 | { 70 | $response = $this->generator->strings(20, 1, Randomizer::ALL ^ Randomizer::DIGITS); 71 | echo "\nTesting response string {$response[0]}\n"; 72 | $this->assertRegExp('/[\D]+/', $response[0]); 73 | } 74 | } -------------------------------------------------------------------------------- /tests/TrueRandomNoNetworkTest.php: -------------------------------------------------------------------------------- 1 | client = Mockery::mock( 12 | '\Guzzle\Http\ClientInterface' 13 | ); 14 | 15 | // these are calls the client will get in the constructor 16 | $this->client->shouldReceive('setUserAgent', 'setBaseUrl')->times(1)->andReturn(null); 17 | 18 | // sets up quota check responses 19 | $this->addClientResponse('/quota/?format=plain', 100000); 20 | 21 | $this->generator = new Randomizer( 22 | 'test@example.com' 23 | , $this->client 24 | ); 25 | } 26 | 27 | /** 28 | * all methods that take a min/max should throw exceptions when the min value 29 | * is equal to or greater then the max value 30 | * 31 | * @test 32 | */ 33 | public function invalidArgumentsToSequence() 34 | { 35 | $exceptions = $this->sendInvalidArgsTo('sequence'); 36 | 37 | if($exceptions !== 2) 38 | $this->fail('Sequence did not throw an exception for invalid min/max'); 39 | } 40 | 41 | /** 42 | * all methods that take a min/max should throw exceptions when the min value 43 | * is equal to or greater then the max value 44 | * 45 | * @test 46 | */ 47 | public function invalidArgumentsToInteger() 48 | { 49 | $exceptions = $this->sendInvalidArgsTo('integer'); 50 | 51 | if($exceptions !== 2) 52 | $this->fail('Integer did not throw an exception for invalid min/max'); 53 | } 54 | 55 | /** 56 | * all methods that take a min/max should throw exceptions when the min value 57 | * is equal to or greater then the max value 58 | * 59 | * @test 60 | */ 61 | public function invalidArgumentsToIntegers() 62 | { 63 | $exceptions = $this->sendInvalidArgsTo('integer'); 64 | 65 | if($exceptions !== 2) 66 | $this->fail('Integers did not throw an exception for invalid min/max'); 67 | } 68 | 69 | 70 | /** 71 | * should get an exception if user tries to specify a string length not within 72 | * the allowed bounds 73 | * 74 | * @test 75 | * @expectedException InvalidArgumentException 76 | */ 77 | public function invalidArgumentsToStrings() 78 | { 79 | $this->generator->strings(Randomizer::MAX_STRING + 1); 80 | } 81 | 82 | /** 83 | * generate a sequence of integers allowing repeats 84 | * 85 | * @test 86 | */ 87 | public function generateIntegers() 88 | { 89 | $this->addClientResponse( 90 | '/integers/?num=5&min=1&max=50&col=1&base=10&format=plain&rnd=new' 91 | , "7\n22\n50\n12\n7\n" 92 | ); 93 | 94 | $this->assertEquals(array(7,22,50,12,7), $this->generator->integers(1,50,5)); 95 | } 96 | 97 | /** 98 | * generate a single integer 99 | * 100 | * @test 101 | */ 102 | public function generateInteger() 103 | { 104 | $this->addClientResponse( 105 | '/integers/?num=1&min=1&max=50&col=1&base=10&format=plain&rnd=new' 106 | , "15\n" 107 | ); 108 | 109 | $this->assertEquals(15, $this->generator->integer(1,50)); 110 | } 111 | 112 | /** 113 | * should create a sequence of numbers 114 | * 115 | * @test 116 | */ 117 | public function generateSequence() 118 | { 119 | $this->addClientResponse( 120 | '/sequences/?min=1&max=5&col=1&format=plain&rnd=new' 121 | , "5\n2\n1\n4\n3\n" 122 | ); 123 | 124 | $this->assertEquals(array(5,2,1,4,3), $this->generator->sequence(1,5)); 125 | } 126 | 127 | 128 | public function tearDown() 129 | { 130 | Mockery::close(); 131 | } 132 | 133 | 134 | /** 135 | * for the methods that take min/max arguments, makes sure an exception is 136 | * thrown if they are bookends or matching 137 | * 138 | * @param [type] $method [description] 139 | * @return [type] [description] 140 | */ 141 | protected function sendInvalidArgsTo($method) 142 | { 143 | $exceptions = 0; 144 | 145 | try { 146 | $this->generator->$method(10,10); 147 | } catch(InvalidArgumentException $e) { 148 | $exceptions++; 149 | } 150 | 151 | try { 152 | $this->generator->$method(10,9); 153 | } catch(InvalidArgumentException $e) { 154 | $exceptions++; 155 | } 156 | 157 | return $exceptions; 158 | } 159 | 160 | 161 | protected function addClientResponse($uri, $responseBody) 162 | { 163 | // handle quota checks 164 | $response = $this->defaultResponseMock($responseBody); 165 | $request = $this->defaultRequestMock($response); 166 | 167 | $this->client->shouldReceive('get')->zeroOrMoreTimes()->with($uri)->andReturn($request); 168 | } 169 | 170 | protected function defaultResponseMock($data) 171 | { 172 | $response = Mockery::Mock('response'); 173 | $response->shouldReceive('getStatusCode')->zeroOrMoreTimes()->andReturn(200); 174 | $response->shouldReceive('getBody')->zeroOrMoreTimes()->andReturn($data); 175 | 176 | return $response; 177 | } 178 | 179 | 180 | protected function defaultRequestMock($response) 181 | { 182 | $request = Mockery::Mock('request'); 183 | $request->shouldReceive('send')->zeroOrMoreTimes()->andReturn($response); 184 | $request->shouldReceive('getCurlOptions->set')->zeroOrMoreTimes()->andReturn(null); 185 | 186 | return $request; 187 | } 188 | 189 | 190 | 191 | ////// TRASH 192 | protected function addQuotaExpectations($client) 193 | { 194 | $quotaResponse = Mockery::Mock('response'); 195 | $quotaResponse->shouldReceive('getStatusCode')->zeroOrMoreTimes()->andReturn(200); 196 | 197 | $quotaRequest = Mockery::Mock('request'); 198 | $quotaRequest->shouldReceive('send')->zeroOrMoreTimes()->andReturn($quotaRequest); 199 | 200 | $client->shouldReceive('get')->zeroOrMoreTimes()->with('/quota/?format=plain')->andReturn($quotaRequest); 201 | 202 | 203 | /* 204 | $x = Mockery::mock('class_x'); 205 | $x->shouldReceive('foo')->once()->andReturn('woot'); 206 | $y = Mockery::mock('class_y'); 207 | $y->shouldReceive('bar')->once()->andReturn($x); 208 | 209 | echo $y->bar()->foo(); 210 | */ 211 | 212 | return; 213 | 214 | $client = Mockery::mock( 215 | '\Guzzle\Http\ClientInterface' 216 | ); 217 | 218 | $client->shouldReceive('setUserAgent')->zeroOrMoreTimes()->andReturn(null); 219 | $client->shouldReceive('setBaseUrl')->zeroOrMoreTimes()->andReturn(null); 220 | 221 | // this is not going to be pretty 222 | #$response = Mockery::Mock('response'); 223 | #$response->shouldReceive('getStatusCode')->andReturn(200); 224 | #$response->shouldReceive('getBody')->andReturn("5\n55\n22"); 225 | 226 | #$request = Mockery::Mock('request'); 227 | #$request->shouldReceive('send')->andReturn($response); 228 | 229 | $quotaResponse = Mockery::Mock('response')->shouldReceive('getStatusCode')->twice()->andReturn(200); 230 | $quotaResponse->shouldReceive('getBody')->zeroOrMoreTimes()->andReturn(100000); 231 | $quotaRequest = Mockery::Mock('request')->shouldReceive('send')->zeroOrMoreTimes()->andReturn($quotaResponse); 232 | 233 | $client->shouldReceive('get')->once()->with('/quota/?format=plain')->andReturn($quotaRequest); 234 | 235 | #$client->shouldReceive('get')->zeroOrMoreTimes()->with('/sequences/?min=%d&max=%d&col=1&format=plain&rnd=new')->andReturn($request); 236 | 237 | 238 | 239 | 240 | return $client; 241 | } 242 | 243 | } --------------------------------------------------------------------------------