├── .gitignore ├── tests ├── _config.example.php ├── APITest.php └── AuthenticationTest.php ├── composer.json ├── src ├── Authentication.php └── API.php ├── README.md └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | AuthenticationObjStore 2 | vendor/ 3 | tests/_config.php 4 | .DS_Store -------------------------------------------------------------------------------- /tests/_config.example.php: -------------------------------------------------------------------------------- 1 | fail('_config.php must contain valid Car-Net credentials.'); 32 | } 33 | 34 | $this->Authentication = new Authentication(); 35 | $this->Authentication->authenticate(KnownValidCarNetEmailAddress, KnownValidCarNetPassword); 36 | $this->AuthenticationFromFile = false; 37 | 38 | $this->API = new API($this->Authentication, KnownValidCarNetPIN); 39 | } 40 | 41 | 42 | public function testGetVehicleId(): void 43 | { 44 | $vehicleId = $this->API->getVehicleId(); 45 | 46 | $regexPattern = '/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/m'; 47 | 48 | $this->assertMatchesRegularExpression($regexPattern, $vehicleId); 49 | } 50 | 51 | 52 | // TODO Write more test coverage for API object. 53 | 54 | public function testHelperMethods(): void 55 | { 56 | $kilometersToTest = 122; 57 | $milesResult = 76; 58 | $kilometersToMiles = $this->API::kilometersToMiles($kilometersToTest); 59 | $this->assertEquals($kilometersToMiles, $milesResult); 60 | 61 | $fahrenheitToTest = 78; 62 | $celsiusResult = 26; 63 | $fahrenheitToCelsius = $this->API::fahrenheitToCelsius($fahrenheitToTest); 64 | $this->assertEquals($fahrenheitToCelsius, $celsiusResult); 65 | 66 | $camelCaseStringToTest = "thisIsACamelCaseString"; 67 | $ccConvertResult = $this->API::codeCaseToWords($camelCaseStringToTest); 68 | $this->assertEquals($ccConvertResult, "This is a camel case string"); 69 | 70 | $snakeCaseStringToTest = "this_is_a_snake_case_string"; 71 | $scConvertResult = $this->API::codeCaseToWords($snakeCaseStringToTest); 72 | $this->assertEquals($scConvertResult, "This is a snake case string"); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /tests/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | fail('tests/_config.php must contain valid Car-Net credentials.'); 29 | } 30 | 31 | $this->Authentication = new Authentication(); 32 | $this->AuthenticationFromFile = false; 33 | 34 | } 35 | 36 | 37 | public function testGetEmailAddressBeforeSet() 38 | { 39 | $this->expectException(Exception::class); 40 | $this->Authentication->getEmailAddress(); 41 | } 42 | 43 | 44 | public function testGetAccessTokenBeforeSet() 45 | { 46 | $this->expectException(Exception::class); 47 | $this->Authentication->getAccessToken(); 48 | } 49 | 50 | 51 | public function testGetIdTokenBeforeSet() 52 | { 53 | $this->expectException(Exception::class); 54 | $this->Authentication->getIdToken(); 55 | } 56 | 57 | 58 | public function testEmptyStringAuthenticateAttempt() 59 | { 60 | $this->expectException(Exception::class); 61 | 62 | $this->Authentication->authenticate('', ''); 63 | } 64 | 65 | 66 | public function testInvalidEmailAddressAuthenticateAttempt() 67 | { 68 | $this->expectException(Exception::class); 69 | 70 | $this->Authentication->authenticate('invalid111111@emailaddress.com', 'invalidpassword'); 71 | } 72 | 73 | 74 | public function testKnownInvalidPasswordAddressAuthenticateAttempt() 75 | { 76 | $this->expectException(Exception::class); 77 | 78 | $this->Authentication->authenticate(KnownValidCarNetEmailAddress, 'invalidpassword'); 79 | } 80 | 81 | 82 | public function testKnownValidAuthenticateAttempt() 83 | { 84 | try { 85 | $this->Authentication->authenticate(KnownValidCarNetEmailAddress, KnownValidCarNetPassword); 86 | } catch (\Exception $e) { 87 | $this->fail('An unexpected exception was thrown during known valid authentication attempt test: ' . $e->getMessage()); 88 | } 89 | 90 | $authenticationPathStrings = $this->Authentication->getAllAuthenticationTokens(); 91 | 92 | $this->assertIsArray($authenticationPathStrings); 93 | $this->assertIsString($authenticationPathStrings['accessToken']); 94 | $this->assertIsInt($authenticationPathStrings['accessTokenExpires']); 95 | $this->assertIsString($authenticationPathStrings['idToken']); 96 | $this->assertIsInt($authenticationPathStrings['idTokenExpires']); 97 | $this->assertIsString($authenticationPathStrings['refreshToken']); 98 | $this->assertIsInt($authenticationPathStrings['refreshTokenExpires']); 99 | $this->assertIsString($authenticationPathStrings['codeVerifier']); 100 | $this->assertIsString($authenticationPathStrings['emailAddress']); 101 | 102 | try { 103 | $accessToken = $this->Authentication->getAccessToken(); 104 | } catch (\Exception $e) { 105 | $this->fail('An unexpected exception was thrown during test of getAccessToken(): ' . $e->getMessage()); 106 | } 107 | 108 | $this->assertIsString($accessToken); 109 | 110 | try { 111 | $idToken = $this->Authentication->getIdToken(); 112 | } catch (\Exception $e) { 113 | $this->fail('An unexpected exception was thrown during test of getIdToken(): ' . $e->getMessage()); 114 | } 115 | 116 | $this->assertIsString($idToken); 117 | 118 | $this->assertEquals($authenticationPathStrings['accessToken'], $accessToken); 119 | $this->assertEquals($authenticationPathStrings['idToken'], $idToken); 120 | } 121 | 122 | 123 | public function testGenerateMockUuid() 124 | { 125 | $regexPattern = '/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/m'; 126 | 127 | $this->assertMatchesRegularExpression($regexPattern, $this->Authentication::generateMockUuid()); 128 | } 129 | } -------------------------------------------------------------------------------- /src/Authentication.php: -------------------------------------------------------------------------------- 1 | client = new GuzzleHttp\Client(); 47 | $this->clientCookieJar = new GuzzleHttp\Cookie\CookieJar(); 48 | } 49 | 50 | 51 | public function __sleep() 52 | { 53 | return [ 54 | 'accessToken', 55 | 'accessTokenExpires', 56 | 'idToken', 57 | 'idTokenExpires', 58 | 'refreshToken', 59 | 'refreshTokenExpires', 60 | 'codeVerifier', 61 | 'emailAddress' 62 | ]; 63 | } 64 | 65 | 66 | public function __wakeup() 67 | { 68 | $this->client = new GuzzleHttp\Client(); 69 | $this->clientCookieJar = new GuzzleHttp\Cookie\CookieJar(); 70 | } 71 | 72 | 73 | public function authenticate(string $emailAddress, string $password): void 74 | { 75 | $this->emailAddress = $emailAddress; 76 | $this->password = $password; 77 | 78 | if (!$this->emailAddress || !$this->password) 79 | throw new \Exception("No email or password set"); 80 | 81 | // Execute each step, in sequence 82 | $this->fetchLogInForm(); 83 | $this->submitEmailAddressForm(); 84 | $this->submitPasswordForm(); 85 | $this->fetchInitialAccessTokens(); 86 | } 87 | 88 | 89 | public function setAuthenticationTokens(array $set): void 90 | { 91 | if (!isset($set['accessToken']) || !isset($set['accessTokenExpires']) || 92 | !isset($set['idToken']) || !isset($set['idTokenExpires']) || 93 | !isset($set['refreshToken']) || !isset($set['refreshTokenExpires']) || 94 | !isset($set['codeVerifier']) || !isset($set['emailAddress'])) 95 | throw new \Exception('setAuthenticationTokens() method requires an associative array that includes keys: accessToken, accessTokenExpires, idToken, idTokenExpires, refreshToken, refreshTokenExpires, codeVerifier, emailAddress'); 96 | 97 | $this->accessToken = $set['accessToken']; 98 | $this->accessTokenExpires = $set['accessTokenExpires']; 99 | $this->idToken = $set['idToken']; 100 | $this->idTokenExpires = $set['idTokenExpires']; 101 | $this->refreshToken = $set['refreshToken']; 102 | $this->refreshTokenExpires = $set['refreshTokenExpires']; 103 | $this->codeVerifier = $set['codeVerifier']; 104 | $this->emailAddress = $set['emailAddress']; 105 | } 106 | 107 | 108 | public function getEmailAddress(): string 109 | { 110 | if (!$this->emailAddress) 111 | throw new \Exception("There is no email address set."); 112 | 113 | return $this->emailAddress; 114 | } 115 | 116 | 117 | public function getAccessToken(): string 118 | { 119 | if (!$this->accessToken) 120 | throw new \Exception("There is no accessToken set yet."); 121 | 122 | if (time() >= $this->accessTokenExpires) 123 | $this->fetchRefreshedAccessTokens(); 124 | 125 | return $this->accessToken; 126 | } 127 | 128 | 129 | public function getIdToken(): string 130 | { 131 | if (!$this->idToken) 132 | throw new \Exception("There is no idToken set yet."); 133 | 134 | if (time() >= $this->idTokenExpires) 135 | $this->fetchRefreshedAccessTokens(); 136 | 137 | return $this->idToken; 138 | } 139 | 140 | 141 | public function getAllAuthenticationTokens(): array 142 | { 143 | return [ 144 | 'accessToken' => $this->accessToken, 145 | 'accessTokenExpires' => $this->accessTokenExpires, 146 | 'idToken' => $this->idToken, 147 | 'idTokenExpires' => $this->idTokenExpires, 148 | 'refreshToken' => $this->refreshToken, 149 | 'refreshTokenExpires' => $this->refreshTokenExpires, 150 | 'codeVerifier' => $this->codeVerifier, 151 | 'emailAddress' => $this->emailAddress, 152 | ]; 153 | } 154 | 155 | 156 | public function setSaveCallback(callable $function): void 157 | { 158 | $this->saveCallback = $function; 159 | } 160 | 161 | 162 | 163 | // Static Public Helper Methods 164 | 165 | static public function generateMockUuid(): string 166 | { 167 | // This is derived from https://www.php.net/manual/en/function.uniqid.php#94959 168 | 169 | // This method doesn't create unique values or cryptographically secure values. 170 | // It simply creates mocks to satisfy the Car-Net APIs expectations. 171 | 172 | return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', 173 | mt_rand(0, 0xffff), mt_rand(0, 0xffff), 174 | mt_rand(0, 0xffff), 175 | mt_rand(0, 0x0fff) | 0x4000, 176 | mt_rand(0, 0x3fff) | 0x8000, 177 | mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) 178 | ); 179 | } 180 | 181 | 182 | 183 | // Private Methods /////////////////// 184 | 185 | private function fetchLogInForm(): void 186 | { 187 | $this->state = self::generateMockUuid(); 188 | 189 | $PKCEPair = $this->generatePKCEPair(); 190 | 191 | $this->codeChallenge = $PKCEPair['codeChallenge']; 192 | $this->codeVerifier = $PKCEPair['codeVerifier']; 193 | 194 | $url = self::API_HOST . '/oidc/v1/authorize?redirect_uri=car-net%3A%2F%2F%2Foauth-callback&scope=openid&prompt=login&code_challenge='.$this->codeChallenge.'&state=' . $this->state . '&response_type=code&client_id=' . self::APP_CLIENT_ID_IOS; 195 | 196 | $res = $this->client->request('GET', $url, ['cookies' => $this->clientCookieJar]); 197 | 198 | // Scrape some values from the log in page for use in subsequent requests 199 | $xml = new SimpleXMLElement(strval($res->getBody())); 200 | 201 | $csrfQuery = $xml->xpath("//*[@name='_csrf']/@value"); 202 | $relayStateQuery = $xml->xpath("//*[@name='relayState']/@value"); 203 | $hmacQuery = $xml->xpath("//*[@name='hmac']/@value"); 204 | $formActionQuery = $xml->xpath("//*[@name='emailPasswordForm']/@action"); 205 | 206 | if (!$csrfQuery || !$relayStateQuery || !$hmacQuery || !$formActionQuery) 207 | throw new \Exception('Could not find the required values in HTML of first step of log-in process.'); 208 | 209 | $this->csrf = strval($csrfQuery[0][0][0]); 210 | $this->relayState = strval($relayStateQuery[0][0][0]); 211 | $this->hmac = strval($hmacQuery[0][0][0]); 212 | $this->nextFormAction = strval($formActionQuery[0][0][0]); 213 | } 214 | 215 | 216 | private function submitEmailAddressForm(): void 217 | { 218 | $url = self::AUTH_HOST . $this->nextFormAction; 219 | 220 | $res = $this->client->request('POST', $url, 221 | [ 222 | 'cookies' => $this->clientCookieJar, 223 | 'headers' => [ 224 | 'user-agent' => self::AUTH_USER_AGENT_SPOOF, 225 | 'content-type' => 'application/x-www-form-urlencoded', 226 | 'accept-language' => 'en-us', 227 | 'accept' => '*/*' 228 | ], 229 | 'form_params' => [ 230 | '_csrf' => $this->csrf, 231 | 'relayState' => $this->relayState, 232 | 'hmac' => $this->hmac, 233 | 'email' => $this->emailAddress 234 | ] 235 | ] 236 | ); 237 | 238 | $returnedHtml = strval($res->getBody()); 239 | 240 | // Before we scrape, we have to remove a bit of problematic html 241 | // from this this response, because it was breaking the SimpleXMLElement parser... 242 | $re = '/onclick="(.*)"/m'; 243 | $repairedHtml = preg_replace($re, '', $returnedHtml); 244 | 245 | // Scrape some more values for the next requests... 246 | $xml = new SimpleXMLElement($repairedHtml); 247 | 248 | $hmacQuery = $xml->xpath("//*[@name='hmac']/@value"); 249 | $formActionQuery = $xml->xpath("//*[@name='credentialsForm']/@action"); 250 | 251 | if (!$hmacQuery || !$formActionQuery) 252 | throw new \Exception('Could not scrape required values in the response of the second step of the log in process. This is likely due to an incorrect email address.'); 253 | 254 | $this->hmac = strval($hmacQuery[0][0][0]); 255 | $this->nextFormAction = strval($formActionQuery[0][0][0]); 256 | } 257 | 258 | 259 | private function submitPasswordForm(): void 260 | { 261 | $url = self::AUTH_HOST . $this->nextFormAction; 262 | 263 | try { 264 | $res = $this->client->request('POST', $url, 265 | [ 266 | 'cookies' => $this->clientCookieJar, 267 | 'headers' => [ 268 | 'user-agent' => self::AUTH_USER_AGENT_SPOOF, 269 | 'content-type' => 'application/x-www-form-urlencoded', 270 | 'accept-language' => 'en-us', 271 | 'accept' => '*/*' 272 | ], 273 | 'form_params' => [ 274 | '_csrf' => $this->csrf, 275 | 'relayState' => $this->relayState, 276 | 'hmac' => $this->hmac, 277 | 'email' => $this->emailAddress, 278 | 'password' => $this->password 279 | ] 280 | ] 281 | ); 282 | } catch (\InvalidArgumentException $e) { 283 | // We are expecting this to throw an exception even when succesful, 284 | // because guzzle seems to have no way of gracefully handling redirects that 285 | // attempt to redirect to a custom uri scheme (i.e. car-net:/// ) 286 | 287 | // Luckily guzzle returns the failed uri in the exception message, so we 288 | // can just grab the value we need from that and move on... 289 | 290 | $code = trim(explode("&code=", $e->getMessage())[1]); 291 | } 292 | 293 | if (!isset($code) || preg_match('/^[a-f0-9]{96}$/', $code) === false) 294 | throw new \Exception('No "code" value was returned from the API after the final step of the log in process. This is most likely due to an incorrect email address or password.'); 295 | 296 | $this->code = $code; 297 | } 298 | 299 | 300 | private function fetchInitialAccessTokens() 301 | { 302 | if (!$this->code || !$this->codeVerifier) 303 | throw new \Exception("Can not request access tokens without valid 'code' and 'codeVerifier' values."); 304 | 305 | $url = self::API_HOST . '/oidc/v1/token'; 306 | 307 | $res = $this->client->request('POST', $url, 308 | [ 309 | 'headers' => [ 310 | 'user-agent' => self::APP_USER_AGENT_SPOOF, 311 | 'content-type' => 'application/x-www-form-urlencoded', 312 | 'accept-language' => 'en-us', 313 | 'accept' => '*/*', 314 | 'accept-encoding' => 'gzip, deflate, br' 315 | ], 316 | 'form_params' => [ 317 | 'grant_type' => 'authorization_code', 318 | 'code' => $this->code, 319 | 'client_id' => self::APP_CLIENT_ID_IOS, 320 | 'redirect_uri' => 'car-net:///oauth-callback', 321 | 'code_verifier' => $this->codeVerifier 322 | ] 323 | ] 324 | ); 325 | 326 | $responseJson = json_decode(strval($res->getBody()), true); 327 | 328 | if (!$responseJson['access_token'] || !$responseJson['id_token'] || !$responseJson['refresh_token']) 329 | throw new \Exception('Invalid response from initial token request'); 330 | 331 | $this->accessToken = $responseJson['access_token']; 332 | $this->accessTokenExpires = time() + $responseJson['expires_in']; 333 | $this->idToken = $responseJson['id_token']; 334 | $this->idTokenExpires = time() + $responseJson['id_expires_in']; 335 | $this->refreshToken = $responseJson['refresh_token']; 336 | $this->refreshTokenExpires = time() + $responseJson['refresh_expires_in']; 337 | 338 | if (is_callable($this->saveCallback)) 339 | call_user_func($this->saveCallback, $this); 340 | } 341 | 342 | 343 | private function fetchRefreshedAccessTokens(): void 344 | { 345 | if (!$this->refreshToken) 346 | throw new \Exception("Can not refresh access tokens without a valid 'refresh token' value."); 347 | 348 | if (time() >= $this->refreshTokenExpires) 349 | throw new \Exception("Your saved refresh token has expired. Please re-authenticate."); 350 | 351 | $url = self::API_HOST . '/oidc/v1/token'; 352 | 353 | $res = $this->client->request('POST', $url, 354 | [ 355 | 'headers' => [ 356 | 'user-agent' => self::APP_USER_AGENT_SPOOF, 357 | 'content-type' => 'application/x-www-form-urlencoded', 358 | 'accept-language' => 'en-us', 359 | 'accept' => '*/*', 360 | 'accept-encoding' => 'gzip, deflate, br' 361 | ], 362 | 'form_params' => [ 363 | 'grant_type' => 'refresh_token', 364 | 'refresh_token' => $this->refreshToken, 365 | 'client_id' => self::APP_CLIENT_ID_IOS, 366 | 'code_verifier' => $this->codeVerifier 367 | ] 368 | ] 369 | ); 370 | 371 | $responseJson = json_decode(strval($res->getBody()), true); 372 | 373 | if (!$responseJson['access_token'] || !$responseJson['id_token'] || !$responseJson['refresh_token']) 374 | throw new \Exception('Invalid response from refresh token request'); 375 | 376 | $this->accessToken = $responseJson['access_token']; 377 | $this->accessTokenExpires = time() + $responseJson['expires_in']; 378 | $this->idToken = $responseJson['id_token']; 379 | $this->idTokenExpires = time() + $responseJson['id_expires_in']; 380 | $this->refreshToken = $responseJson['refresh_token']; 381 | $this->refreshTokenExpires = time() + $responseJson['refresh_expires_in']; 382 | 383 | if (is_callable($this->saveCallback)) 384 | call_user_func($this->saveCallback, $this); 385 | } 386 | 387 | 388 | private function generatePKCEPair(): array 389 | { 390 | $bytes = random_bytes(64 / 2); 391 | $codeVerifier = bin2hex($bytes); 392 | 393 | $hashOfVerifier = hash('sha256', $codeVerifier, true); 394 | $codeChallenge = strtr(base64_encode($hashOfVerifier), '+/', '-_'); 395 | 396 | return [ 397 | 'codeVerifier' => $codeVerifier, 398 | 'codeChallenge' => $codeChallenge 399 | ]; 400 | } 401 | } -------------------------------------------------------------------------------- /src/API.php: -------------------------------------------------------------------------------- 1 | Authentication = $Authentication; 36 | $this->pin = $pin; 37 | 38 | // Set up a new Guzzle client with some defaults 39 | $this->client = new GuzzleHttp\Client( 40 | [ 41 | 'base_uri' => self::API_HOST, 42 | 'headers' => [ 43 | 'content-type' => 'application/json;charset=UTF-8', 44 | 'x-user-id' => $this->userId, 45 | 'user-agent' => self::APP_USER_AGENT_SPOOF, 46 | 'x-user-agent' => self::APP_USER_AGENT_SPOOF, 47 | 'x-app-uuid' => Authentication::generateMockUuid(), 48 | 'accept' => '*/*' 49 | ] 50 | ]); 51 | } 52 | 53 | 54 | public function getVehiclesAndEnrollmentStatus(): array 55 | { 56 | if ($this->vehiclesAndEnrollmentStatus) 57 | return $this->vehiclesAndEnrollmentStatus; 58 | 59 | $this->fetchVehiclesAndEnrollmentStatus(); 60 | return $this->vehiclesAndEnrollmentStatus; 61 | } 62 | 63 | 64 | public function getCurrentlySelectedVehicle(): array 65 | { 66 | // If there isn't one explicitly set, default to first vehicle in their list. 67 | if (!$this->currentlySelectedVehicle) 68 | $this->currentlySelectedVehicle = $this->getVehiclesAndEnrollmentStatus()['vehicleEnrollmentStatus'][0]; 69 | 70 | return $this->currentlySelectedVehicle; 71 | } 72 | 73 | 74 | public function setCurrentlySelectedVehicle(string $vehicleId): void 75 | { 76 | $count = 0; 77 | 78 | foreach ($this->getVehiclesAndEnrollmentStatus()['vehicleEnrollmentStatus'] as $vehicle) { 79 | if ($vehicleId == $vehicle['vehicleId']) { 80 | // Found it, break out of this loop 81 | $index = $count; 82 | break; 83 | } 84 | 85 | $count++; 86 | } 87 | 88 | if (isset($index)) { 89 | $this->currentlySelectedVehicle = $this->getVehiclesAndEnrollmentStatus()['vehicleEnrollmentStatus'][$index]; 90 | 91 | // Unset any values that might have been set while working with the previous vehicle 92 | $this->currentlySelectedVehicleStatus = []; 93 | $this->currentlySelectedVehicleClimateStatus = []; 94 | $this->currentlySelectedVehicleHealthReport = []; 95 | } 96 | 97 | throw new \Exception('That vehicle id was not found in your vehicles list.'); 98 | } 99 | 100 | 101 | public function getVehicleStatus($forceRefetch = false): array 102 | { 103 | if ($this->currentlySelectedVehicleStatus && !$forceRefetch) 104 | return $this->currentlySelectedVehicleStatus; 105 | 106 | $this->fetchVehicleStatus(); 107 | return $this->currentlySelectedVehicleStatus; 108 | } 109 | 110 | 111 | public function getVehicleHealthReport($forceRefetch = false): array 112 | { 113 | if ($this->currentlySelectedVehicleHealthReport && !$forceRefetch) 114 | return $this->currentlySelectedVehicleHealthReport; 115 | 116 | $this->fetchVehicleHealthReport(); 117 | return $this->currentlySelectedVehicleHealthReport; 118 | } 119 | 120 | 121 | public function getAllVehicles(): array 122 | { 123 | return $this->getVehiclesAndEnrollmentStatus()['vehicleEnrollmentStatus']; 124 | } 125 | 126 | 127 | public function getVehicleId(): string 128 | { 129 | return $this->getCurrentlySelectedVehicle()['vehicleId']; 130 | } 131 | 132 | 133 | public function getBatteryStatus(): array 134 | { 135 | if ($this->getVehicleStatus()['powerStatus']['battery']) 136 | return $this->getVehicleStatus()['powerStatus']['battery']; 137 | 138 | throw new \Exception('No battery status information in the vehicle status.'); 139 | } 140 | 141 | 142 | public function getClimateStatus(): array 143 | { 144 | if (!$this->currentlySelectedVehicleClimateStatus) 145 | $this->fetchCurrentlySelectedVehicleClimateStatus(); 146 | 147 | return $this->currentlySelectedVehicleClimateStatus; 148 | } 149 | 150 | 151 | public function getPowerStatus(): array 152 | { 153 | if ($this->getVehicleStatus()['powerStatus']) 154 | return $this->getVehicleStatus()['powerStatus']; 155 | 156 | throw new \Exception('No power status information in the vehicle status.'); 157 | } 158 | 159 | 160 | public function requestRepollOfVehicleStatus(): void 161 | { 162 | $url = '/mps/v1/vehicles/' . $this->getAccountNumber() . '/status/fresh'; 163 | 164 | $res = $this->client->request('PUT', $url, 165 | [ 166 | 'headers' => [ 167 | 'authorization' => 'Bearer ' . $this->Authentication->getAccessToken() 168 | ], 169 | 'json' => [ 170 | 'tsp_token' => $this->getTspToken(), 171 | 'email' => $this->Authentication->getEmailAddress(), 172 | 'vw_id' => $this->vwId 173 | ] 174 | ] 175 | ); 176 | 177 | // Un set the vehicle status so that that the next call to 178 | // getVehicleStatus() forces a re-fetch 179 | $this->currentlySelectedVehicleStatus = []; 180 | } 181 | 182 | 183 | public function requestRepollOfVehicleHealthReport(): void 184 | { 185 | $url = '/mps/v1/vehicles/' . $this->getAccountNumber() . '/health/fresh'; 186 | 187 | $res = $this->client->request('POST', $url, 188 | [ 189 | 'headers' => [ 190 | 'authorization' => 'Bearer ' . $this->Authentication->getAccessToken() 191 | ], 192 | 'json' => [ 193 | 'tsp_token' => $this->getTspToken(), 194 | 'email' => $this->Authentication->getEmailAddress(), 195 | 'vw_id' => $this->vwId 196 | ] 197 | ] 198 | ); 199 | 200 | // Un set the vehicle health report property so that that the next call to 201 | // getVehicleHealthReport() forces a re-fetch 202 | $this->currentlySelectedVehicleHealthReport = []; 203 | } 204 | 205 | 206 | public function setClimateControl(bool $active, int $targetTemperature = 75): void 207 | { 208 | $url = '/mps/v1/vehicles/' . $this->getAccountNumber() . '/status/climate'; 209 | 210 | $res = $this->client->request('PUT', $url, 211 | [ 212 | 'headers' => [ 213 | 'authorization' => 'Bearer ' . $this->Authentication->getAccessToken() 214 | ], 215 | 'json' => [ 216 | 'active' => $active, 217 | 'target_temperature' => $targetTemperature, 218 | 'tsp_token' => $this->getTspToken(), 219 | 'email' => $this->Authentication->getEmailAddress(), 220 | 'vw_id' => $this->vwId 221 | ] 222 | ] 223 | ); 224 | } 225 | 226 | 227 | public function setDefroster(bool $on): void 228 | { 229 | $url = '/mps/v1/vehicles/' . $this->getAccountNumber() . '/status/defrost'; 230 | 231 | $res = $this->client->request('PUT', $url, 232 | [ 233 | 'headers' => [ 234 | 'authorization' => 'Bearer ' . $this->Authentication->getAccessToken() 235 | ], 236 | 'json' => [ 237 | 'active' => $on, 238 | 'tsp_token' => $this->getTspToken(), 239 | 'email' => $this->Authentication->getEmailAddress(), 240 | 'vw_id' => $this->vwId 241 | ] 242 | ] 243 | ); 244 | } 245 | 246 | 247 | public function setCharge(bool $charge): void 248 | { 249 | $url = '/mps/v1/vehicles/' . $this->getAccountNumber() . '/status/charging'; 250 | 251 | $res = $this->client->request('PATCH', $url, 252 | [ 253 | 'headers' => [ 254 | 'authorization' => 'Bearer ' . $this->Authentication->getAccessToken() 255 | ], 256 | 'json' => [ 257 | 'active' => $charge, 258 | 'tsp_token' => $this->getTspToken(), 259 | 'email' => $this->Authentication->getEmailAddress(), 260 | 'vw_id' => $this->vwId 261 | ] 262 | ] 263 | ); 264 | } 265 | 266 | 267 | public function setLock(bool $lock): void 268 | { 269 | $url = '/mps/v1/vehicles/' . $this->getAccountNumber() . '/status/exterior/doors'; 270 | 271 | $res = $this->client->request('PUT', $url, 272 | [ 273 | 'headers' => [ 274 | 'authorization' => 'Bearer ' . $this->Authentication->getAccessToken() 275 | ], 276 | 'json' => [ 277 | 'lock' => $lock, 278 | 'tsp_token' => $this->getTspToken(), 279 | 'email' => $this->Authentication->getEmailAddress(), 280 | 'vw_id' => $this->vwId 281 | ] 282 | ] 283 | ); 284 | } 285 | 286 | 287 | public function setUnpluggedClimateControl(bool $enabled): void 288 | { 289 | $url = '/mps/v1/vehicles/' . $this->getAccountNumber() . '/settings/unplugged_climate_control'; 290 | 291 | $res = $this->client->request('PUT', $url, 292 | [ 293 | 'headers' => [ 294 | 'authorization' => 'Bearer ' . $this->Authentication->getAccessToken() 295 | ], 296 | 'json' => [ 297 | 'enabled' => $enabled, 298 | 'tsp_token' => $this->getTspToken(), 299 | 'email' => $this->Authentication->getEmailAddress(), 300 | 'vw_id' => $this->vwId 301 | ] 302 | ] 303 | ); 304 | } 305 | 306 | 307 | 308 | // Static Public Methods //////////////////////////////// 309 | 310 | static public function kilometersToMiles(int $km, int $precision = 0): int 311 | { 312 | return intval(ceil($km / 1.609344)); 313 | } 314 | 315 | 316 | static public function fahrenheitToCelsius(int $f, int $precision = 0): int 317 | { 318 | return intval(ceil(($f - 32) * 5/9)); 319 | } 320 | 321 | 322 | static public function codeCaseToWords(string $string): string 323 | { 324 | if (strpos($string, '_') === false) { 325 | // if there's no underscores, assume input is camel case 326 | $arr = preg_split('/(?=[A-Z])/', $string); 327 | $spaced = trim(implode(' ', $arr)); 328 | } else { 329 | // other process as if input is snake case 330 | $spaced = str_replace('_', ' ', $string); 331 | } 332 | 333 | return ucfirst(strtolower($spaced)); 334 | } 335 | 336 | 337 | 338 | // Private Methods //////////////////// 339 | 340 | private function getUserId(): string 341 | { 342 | if ($this->userId) 343 | return $this->userId; 344 | 345 | $this->fetchVehiclesAndEnrollmentStatus(); 346 | return $this->userId; 347 | } 348 | 349 | 350 | private function getAccountNumber(): string 351 | { 352 | return $this->getCurrentlySelectedVehicle()['rolesAndRights']['tspAccountNum']; 353 | } 354 | 355 | 356 | private function getTspToken(): string 357 | { 358 | // If the tsp token is not yet set, or has expired, refetch it before returning... 359 | if (!$this->tspToken || time() >= $this->tspTokenExpires) 360 | $this->fetchTspToken(); 361 | 362 | return $this->tspToken; 363 | } 364 | 365 | 366 | private function fetchTspToken(): void 367 | { 368 | $url = '/ss/v1/user/' . $this->getUserId() . '/vehicle/' . $this->getCurrentlySelectedVehicle()['vehicleId'] . '/session'; 369 | 370 | try { 371 | $res = $this->client->request('POST', $url, 372 | [ 373 | 'headers' => [ 374 | 'authorization' => 'Bearer ' . $this->Authentication->getAccessToken() 375 | ], 376 | 'json' => [ 377 | 'accountNumber' => $this->getCurrentlySelectedVehicle()['rolesAndRights']['tspAccountNum'], 378 | 'idToken' => $this->Authentication->getIdToken(), 379 | 'tspPin' => $this->pin, 380 | 'tsp' => $this->getCurrentlySelectedVehicle()['vehicle']['tspProvider'] 381 | ] 382 | ] 383 | ); 384 | } catch (\GuzzleHttp\Exception\ClientException $e) { 385 | throw new \Exception('Invalid response from tsp token request. This is likely due to an incorrect PIN. ' . $e->getMessage()); 386 | } 387 | 388 | $responseJson = json_decode(strval($res->getBody()), true); 389 | 390 | if (!$responseJson['data']['tspToken']) 391 | throw new \Exception('Invalid response from tsp token request.'); 392 | 393 | $this->tspToken = $responseJson['data']['tspToken']; 394 | 395 | // The API doesn't give expiration information for this token, 396 | // so we'll set an expiry ourselves of 30 minutes from now. 397 | $this->tspTokenExpires = time() + 3600; 398 | } 399 | 400 | 401 | private function fetchVehiclesAndEnrollmentStatus(): void 402 | { 403 | $url = '/account/v1/enrollment/status?idToken=' . $this->Authentication->getIdToken(); 404 | 405 | $res = $this->client->request('GET', $url, [ 406 | 'headers' => [ 407 | 'authorization' => 'Bearer ' . $this->Authentication->getAccessToken() 408 | ] 409 | ] 410 | ); 411 | 412 | $responseJson = json_decode(strval($res->getBody()), true); 413 | 414 | $this->vehiclesAndEnrollmentStatus = $responseJson['data']; 415 | $this->userId = $this->vehiclesAndEnrollmentStatus['customer']['userId']; 416 | $this->vwId = $this->vehiclesAndEnrollmentStatus['customer']['vwId']; 417 | } 418 | 419 | 420 | private function fetchVehicleStatus(): void 421 | { 422 | $url = '/rvs/v1/vehicle/' . $this->getVehicleId(); 423 | 424 | $res = $this->client->request('GET', $url, [ 425 | 'headers' => [ 426 | 'authorization' => 'Bearer ' . $this->Authentication->getAccessToken() 427 | ] 428 | ] 429 | ); 430 | 431 | $responseJson = json_decode(strval($res->getBody()), true); 432 | 433 | // unset any 'UNAVAILABLE' or 'UNSUPPORTED' values from the exterior statuses summary 434 | foreach ($responseJson['data']['exteriorStatus']['doorStatus'] as $k => $v) { 435 | if ($v == "NOTAVAILABLE" || $v == "UNSUPPORTED") 436 | unset($responseJson['data']['exteriorStatus']['doorStatus'][$k]); 437 | } 438 | 439 | foreach ($responseJson['data']['exteriorStatus']['doorLockStatus'] as $k => $v) { 440 | if ($v == "NOTAVAILABLE" || $v == "UNSUPPORTED") 441 | unset($responseJson['data']['exteriorStatus']['doorLockStatus'][$k]); 442 | } 443 | 444 | foreach ($responseJson['data']['exteriorStatus']['windowStatus'] as $k => $v) { 445 | if ($v == "NOTAVAILABLE" || $v == "UNSUPPORTED") 446 | unset($responseJson['data']['exteriorStatus']['windowStatus'][$k]); 447 | } 448 | 449 | foreach ($responseJson['data']['exteriorStatus']['lightStatus'] as $k => $v) { 450 | if ($v == "NOTAVAILABLE" || $v == "UNSUPPORTED") 451 | unset($responseJson['data']['exteriorStatus']['lightStatus'][$k]); 452 | } 453 | 454 | $this->currentlySelectedVehicleStatus = $responseJson['data']; 455 | } 456 | 457 | 458 | private function fetchVehicleHealthReport(): void 459 | { 460 | $url = '/vhs/v2/vehicle/' . $this->getVehicleId(); 461 | 462 | $res = $this->client->request('GET', $url, [ 463 | 'headers' => [ 464 | 'authorization' => 'Bearer ' . $this->Authentication->getAccessToken() 465 | ] 466 | ] 467 | ); 468 | 469 | $responseJson = json_decode(strval($res->getBody()), true); 470 | 471 | $this->currentlySelectedVehicleHealthReport = $responseJson['data']; 472 | } 473 | 474 | 475 | private function fetchCurrentlySelectedVehicleClimateStatus(): void 476 | { 477 | $url = '/mps/v1/vehicles/' . $this->getAccountNumber() . '/status/climate/details'; 478 | 479 | $res = $this->client->request('PUT', $url, 480 | [ 481 | 'headers' => [ 482 | 'authorization' => 'Bearer ' . $this->Authentication->getAccessToken() 483 | ], 484 | 'json' => [ 485 | 'tsp_token' => $this->getTspToken(), 486 | 'email' => $this->Authentication->getEmailAddress(), 487 | 'vw_id' => $this->vwId 488 | ] 489 | ] 490 | ); 491 | 492 | $responseJson = json_decode(strval($res->getBody()), true); 493 | 494 | $this->currentlySelectedVehicleClimateStatus = $responseJson['data']; 495 | } 496 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚨 Abandon Alert! 🚨 2 | I sold my Volkswagen and of course am no longer a car-net customer, so I can no longer maintain this repo. It will be left here for reference. See "Contact" at the bottom of this document if you have any questions with your own project that you think I can help answer. 3 | 4 | # An unofficial PHP Wrapper for the VW Car-Net API 5 | 6 | [![Latest Stable Version](https://poser.pugx.org/thomasesmith/php-vw-car-net/v)](//packagist.org/packages/thomasesmith/php-vw-car-net) [![Total Downloads](https://poser.pugx.org/thomasesmith/php-vw-car-net/downloads)](//packagist.org/packages/thomasesmith/php-vw-car-net) [![Latest Unstable Version](https://poser.pugx.org/thomasesmith/php-vw-car-net/v/unstable)](//packagist.org/packages/thomasesmith/php-vw-car-net) [![License](https://poser.pugx.org/thomasesmith/php-vw-car-net/license)](//packagist.org/packages/thomasesmith/php-vw-car-net) 7 | 8 | This package attempts to follow the advice and guidelines outlined [in this document](https://github.com/thomasesmith/vw-car-net-api) detailing the workings of the VW Car-Net API. This package and its code will try to immediately reflect in its functionality any changes made to that document. 9 | 10 | ## Installing 11 | It is recommended that you install this with [Composer](https://getcomposer.org/). 12 | ```bash 13 | composer require thomasesmith/php-vw-car-net 14 | ``` 15 | ## Regional Support 16 | So far, this package has only been officially tested with a ***U.S.A. VW Car-Net account***. It is unknown how this will work with Car-Net accounts outside of the U.S., and it is unknown how it will work with VW WeConnect accounts. If you want to help test this, [please do](https://github.com/thomasesmith/php-vw-car-net/issues). 17 | 18 | ## Quick Start 19 | All you need now is a VW Car-Net account in good standing... 20 | ```php 21 | use thomasesmith\VWCarNet; 22 | 23 | $Auth = new VWCarNet\Authentication(); 24 | $Auth->authenticate("YOUR CAR NET EMAIL ADDRESS", "YOUR CAR NET PASSWORD"); 25 | $CN = new VWCarNet\API($Auth, "YOUR CAR NET PIN"); 26 | 27 | var_dump($CN->getVehicleStatus()); 28 | ``` 29 | #### ...will return... 30 | 31 | ```php 32 | array(10) { 33 | ["currentMileage"]=> 34 | int(55788) 35 | ["timestamp"]=> 36 | int(1597167320003) 37 | ["exteriorStatus"]=> 38 | array(5) { 39 | ["secure"]=> 40 | string(6) "SECURE" 41 | ["doorStatus"]=> 42 | array(8) { 43 | ["frontLeft"]=> 44 | string(6) "CLOSED" 45 | ["frontRight"]=> 46 | string(6) "CLOSED" 47 | ["rearLeft"]=> 48 | string(6) "CLOSED" 49 | ["rearRight"]=> 50 | string(6) "CLOSED" 51 | ["trunk"]=> 52 | string(6) "CLOSED" 53 | ["hood"]=> 54 | string(6) "CLOSED" 55 | } 56 | ["doorLockStatus"]=> 57 | array(4) { 58 | ["frontLeft"]=> 59 | string(6) "LOCKED" 60 | ["frontRight"]=> 61 | string(6) "LOCKED" 62 | ["rearLeft"]=> 63 | string(6) "LOCKED" 64 | ["rearRight"]=> 65 | string(6) "LOCKED" 66 | } 67 | ["windowStatus"]=> 68 | array(7) { 69 | ["frontLeft"]=> 70 | string(6) "CLOSED" 71 | ["frontRight"]=> 72 | string(6) "CLOSED" 73 | ["rearLeft"]=> 74 | string(6) "CLOSED" 75 | ["rearRight"]=> 76 | string(6) "CLOSED" 77 | } 78 | ["lightStatus"]=> 79 | array(5) { 80 | ["left"]=> 81 | string(3) "OFF" 82 | ["right"]=> 83 | string(3) "OFF" 84 | } 85 | } 86 | ["powerStatus"]=> 87 | array(5) { 88 | ["cruiseRange"]=> 89 | int(131) 90 | ["fuelPercentRemaining"]=> 91 | int(0) 92 | ["cruiseRangeUnits"]=> 93 | string(2) "KM" 94 | ["cruiseRangeFirst"]=> 95 | int(131) 96 | ["battery"]=> 97 | array(4) { 98 | ["chargePercentRemaining"]=> 99 | int(100) 100 | ["minutesUntilFullCharge"]=> 101 | int(15) 102 | ["chargePlug"]=> 103 | string(9) "PLUGGEDIN" 104 | ["triggeredByTimer"]=> 105 | string(5) "false" 106 | } 107 | } 108 | ["location"]=> 109 | array(2) { 110 | ["latitude"]=> 111 | float(35.123456) 112 | ["longitude"]=> 113 | float(-120.123456) 114 | } 115 | ["lastParkedLocation"]=> 116 | array(2) { 117 | ["latitude"]=> 118 | float(35.123456) 119 | ["longitude"]=> 120 | float(-120.123456) 121 | } 122 | ["lockStatus"]=> 123 | string(6) "LOCKED" 124 | } 125 | ``` 126 | ## All Methods Available in the `API` Object 127 | 128 | #### `getVehiclesAndEnrollmentStatus()`: `array` 129 | This will return an associative array of information about your Car-Net account, but most importantly will contain a `vehicleEnrollmentStatus` array, where in each of the vehicles associated with your account will have their own array of details, including their `vehicleId` values. 130 | 131 | #### `getAllVehicles()`: `array` 132 | This is just a shortcut to the `vehicleEnrollmentStatus` array thats inside of the response above. In case you don't want or need the rest of that response. 133 | 134 | #### `setCurrentlySelectedVehicle(string $vehicleId)`: `array` 135 | This method takes a `vehicleId` as its one parameter and sets it as your "current vehicle," that is, the vehicle you will be commanding/querying with the subsequent method calls you make. It will use this vehicle, until of course it is set to a different value. 136 | 137 | > If you only have one vehicle associated with your Car-Net account, you don't have to set this at all, because your currently selected vehicle will default to the first one listed in the `getVehiclesAndEnrollmentStatus()` vehicles list. 138 | 139 | #### `getVehicleStatus([boolean $refetch])`: `array` 140 | This returns an associative array containing all the current details of the currently selected vehicle and its various statuses, such as door lock status, battery status, window states, cruise range, odometer mileage, etc. 141 | 142 | This method will actually only fetch the vehicle status the first time you call it. After that, it will return back the saved results of that first fetch. If you want the status to be re-fetched, you will have to pass in this methods one parameter, an optional boolean. Passing in a value of `true` there will force it to re-fetch the status. It will wait until that fetch is complete, then return the response just the same as without the parameter. 143 | 144 | > "Re-fetching" the status should not be confused with "re-polling" the status. These are two different functions. Forcing a re-fetch is a "cheaper" thing to execute than a re-poll, and is sometimes all you need. 145 | 146 | #### `requestRepollOfVehicleStatus()`: `void` 147 | Sometimes the information returned by the car can get a little stale, call this method to force the Car-Net API to re-poll the car for an updated status of your currently selected vehicle. ***Subject to request rate limit*** 148 | 149 | > Calling this method will automatically force the next call to `getVehicleStatus()` to perform a re-fetch of the status. 150 | 151 | > The status takes about 25 seconds to actually update after making this method call. So wait about that long before calling `getVehicleStatus()` to give the vehicle time to comply with your command. 152 | 153 | #### `getVehicleId()`: `string` 154 | This returns a string containing the vehicle id value of the currently selected vehicle. 155 | 156 | #### `getBatteryStatus()`: `array` 157 | ***EV ONLY*** This is just a shortcut that returns the `powerStatus` array that `getVehicleStatus()` includes as part of its output. 158 | 159 | #### `getClimateStatus()`: `array` 160 | This returns an associative array containing all the details of the currently selected vehicle's climate system: the outdoor temperature, whether or not the climate control is running, where or not your defroster is running, and whether or not these conditions were triggered by a departure timer. ***Subject to request rate limit*** 161 | 162 | #### `setUnpluggedClimateControl(bool $enabled)`: `void` 163 | ***EV ONLY*** Passing in a boolean `false` will disable your currently selected vehicle from turning its climate system on when it is not plugged in. A boolean `true` will set it to allow the car to use the climate system when unplugged. This method returns nothing. ***Subject to request rate limit*** 164 | > This setting stays persistent in the vehicle, you don't have to set it every time you execute your code. 165 | 166 | #### `setClimateControl(bool $enabled [, int $temperatureDegrees])`: `void` 167 | Passing in a boolean `false` as the first parameter will turn off the climate system in the currently selected vehicle. A boolean `true` will turn it on. An additional ***EV ONLY*** feature: use the optional second parameter to set the target temperature you would like the vehicle to try to get to. If no int is passed in, the cars default is used. This method returns nothing.***Subject to request rate limit*** 168 | 169 | > If the currently selected vehicle is an EV, it must either be plugged in, or you have to make sure `setUnpluggedClimateControl()` is set to `true`. 170 | 171 | #### `setDefroster(bool $enabled)`: `void` 172 | Passing in a boolean `true` will start your currently selected vehicles defroster. A boolean `false` will stop it. This method returns nothing. ***Subject to request rate limit*** 173 | 174 | > If the currently selected vehicle is an EV, your car must either be plugged in or you have to make sure `setUnpluggedClimateControl()` is set to `true`. 175 | 176 | #### `setCharge(bool $enabled)`: `void` 177 | ***EV ONLY*** Passing in a boolean `true` starts your currently selected vehicles battery charger. A boolean `false` will stop it. This method returns nothing. ***Subject to request rate limit*** 178 | 179 | #### `setLock(bool $enabled)`: `void` 180 | Passing in a boolean `true` will lock the doors of your currently selected vehicle. A boolean `false` will unlock them. This method returns nothing. ***Subject to request rate limit*** 181 | 182 | #### `getVehicleHealthReport([boolean $refetch])`: `array` 183 | This returns an associative array containing your currently selected vehicles health report. ***Subject to request rate limit*** 184 | 185 | This method will actually only fetch the vehicles health report the first time you call it. After that, it will return back the saved results of that first fetch. If you want the status to be actually re-fetched, you will have to pass in this methods one parameter, an optional boolean. Passing in a value of `true` there will force it to re-fetch the health report. It will wait until that fetch is complete, then return the response just the same as without the parameter. 186 | 187 | > "Re-fetching" the health report should not be confused with "re-polling" the health report. These are two different functions. 188 | 189 | #### `requestRepollOfVehicleHealthReport()`: `void` 190 | Like the vehicle status can, the health report can get a little stale, so call this method to force the Car-Net API to re-poll the car for an updated health report of the currently selected vehicle. This method returns nothing. ***Subject to request rate limit*** 191 | 192 | > Calling this method will automatically force the next call of `getVehicleHealthReport()` to perform a re-fetch of the status. 193 | 194 | > The health report takes about 25 seconds to actually update after making this method call. So don't call `getVehicleHealthReport()` immediately after calling this method, without first waiting a bit. 195 | 196 | *** 197 | 198 | ## What is the "request rate limit?" 199 | 200 | Many of the `API` object methods make requests on which the Car-Net API imposes a strict rate limit. These methods are denoted in this documentation with the phrase "subject to request rate limit." These methods will begin throwing exceptions with a message like, "429 Too Many Requests" if your account makes too many requests over a certain length of time. 201 | 202 | How many requests are allowed, over how much time, before your requests begin to be 429'd is unknown. How long you must wait before you can expect your requests to be fulfilled again is also unknown. 203 | 204 | Be careful with these for now. 205 | 206 | *** 207 | 208 | ## It's Important to Store Your Tokens! 209 | 210 | The Authentication object will do all the work to getting and holding on to the access tokens it will need to use the API–and will refresh them for you automatically if they expire–but it will only hold on to them for the duration of your scripts execution unless you store them somewhere persistently between those executions. Not doing so will force your app to re-authenticate each time it runs, and that will make your app very slow. 211 | 212 | The Authentication class has three methods that can help with this: `setSaveCallback()` `getAllAuthenticationTokens()` and `setAuthenticationTokens()` 213 | 214 | #### Flat File Method 215 | A cheap and easy way to save your access tokens is to just serialize and save to a flat file your Authentication instance. If your application is simple enough and you think that might work for you, that would look something like this: 216 | 217 | ```php 218 | use thomasesmith\VWCarNet; 219 | 220 | // ... 221 | 222 | $authObjectFilename = __DIR__ . '/AuthenticationObjectStore'; 223 | 224 | $tokenChangeCallback = function($Auth) use ($authObjectFilename) { 225 | file_put_contents($authObjectFilename, serialize($Auth)); 226 | }; 227 | 228 | 229 | if (file_exists($authObjectFilename)) { 230 | 231 | $Auth = unserialize(file_get_contents($authObjectFilename)); 232 | 233 | // Be sure to just make sure this instance's save callback function is set. 234 | $Auth->setSaveCallback($tokenChangeCallback); 235 | 236 | } else { 237 | 238 | // No flat file found, so create one. 239 | try { 240 | $Auth = new VWCarNet\Authentication(); 241 | 242 | $Auth->setSaveCallback($tokenChangeCallback); 243 | /* 244 | The order here is important! This instance's setSaveCallback() method 245 | must be called before authenticate() is, to make sure it has something 246 | to perform at the very end of the authenticate() method. 247 | */ 248 | 249 | // Finally, perform the actual authentication process. 250 | $Auth->authenticate("YOUR CAR NET EMAIL ADDRESS", "YOUR CAR NET PASSWORD"); 251 | 252 | } catch (Exception $e) { 253 | // Any issue that arises while logging in will throw an exception here. 254 | print $e->getMessage(); 255 | } 256 | } 257 | 258 | 259 | // Now create an instance of the API object and pass in the Auth instance 260 | // as the first parameter, and your Car-Net PIN as the second, and you're set 261 | $CN = new VWCarNet\API($Auth, "YOUR CAR NET PIN"); 262 | 263 | // ... 264 | ``` 265 | 266 | #### Or, Save Them However You Wish 267 | If you want to store tokens persistently in some other manner, that might look more like this pseudo-code: 268 | 269 | ```php 270 | use thomasesmith\VWCarNet; 271 | 272 | // ... 273 | 274 | $tokenChangeCallback = function($Auth) { 275 | 276 | $tokensArray = $Auth->getAllAuthenticationTokens(); 277 | 278 | // ... 279 | // Here you'd put code to persistently store the $tokensArray 280 | // in whatever manner you with 281 | // ... 282 | }; 283 | 284 | // ... 285 | // Code to load the tokens array from your persistent store 286 | $loadedTokensArray = []; 287 | // ... 288 | 289 | if ($loadedTokensArray) { 290 | 291 | // If you have 'em, use 'em. 292 | $Auth = new VWCarNet\Authentication(); 293 | $Auth->setAuthenticationTokens($loadedTokensArray); 294 | $Auth->setSaveCallback($tokenChangeCallback); 295 | 296 | } else { 297 | 298 | // If you don't, get new ones and be sure to use setSaveCallback() so store them. 299 | try { 300 | 301 | $Auth = new VWCarNet\Authentication(); 302 | $Auth->setSaveCallback($tokenChangeCallback); // Must be called BEFORE authenticate() is 303 | $Auth->authenticate("YOUR CAR NET EMAIL ADDRESS", "YOUR CAR NET PASSWORD"); 304 | 305 | } catch (Exception $e) { 306 | // Any issue that arises while logging in will throw an exception here. 307 | print $e->getMessage(); 308 | } 309 | } 310 | 311 | 312 | // Now create an instance of the API object and pass in the Auth instance 313 | // as the first parameter, and your Car-Net PIN as the second, and you're set 314 | $CN = new VWCarNet\API($Auth, "YOUR CAR NET PIN"); 315 | 316 | // ... 317 | ``` 318 | 319 | *** 320 | 321 | ## Helper Methods 322 | Included in the `API` object are some static helper methods that might come in handy while using this package. 323 | 324 | #### `API::kilometersToMiles(int $kilometers)`: `int` 325 | The Car-Net API seems to default to using kilometers as distance units, so if you prefer imperial units you'll have to convert them. This method takes an int representing kilometers, and returns an int representing its approximate equivalent miles. 326 | ```php 327 | $kilometersCruiseRange = $CN->getVehicleStatus()['powerStatus']['cruiseRange']; // 122 328 | echo VWCarNet\API::kilometersToMiles($kilometersCruiseRange) . " miles"; // echoes: 76 miles 329 | ``` 330 | 331 | #### `API::fahrenheitToCelsius(int $celsius)`: `int` 332 | The Car-Net API seems to default to using fahrenheit as temperature units, so if you prefer metric units you'll have to convert them. This method takes an int representing fahrenheit degrees, and returns an int representing its approximate equivalent celsius degrees. 333 | ```php 334 | $outdoorTempF = $CN->getClimateStatus()['outdoor_temperature']; // 78 335 | echo VWCarNet\API::fahrenheitToCelsius($outdoorTempF) . " celsius"; // echoes: 26 celsius; 336 | ``` 337 | 338 | #### `API::codeCaseToWords(string $weirdCaseString)`: `string` 339 | The API is inconsistent in that it names some of its attribute keys with camel case (i.e. "frontLeft", "sunRoof") and others in snake case (i.e. "outdoor_temperature"). If you want to use those names in your app, but you want to turn them in to plain english, this method takes either a camel case or snake case string and tries its best to return a string containing the plain english words, separated by space characters. 340 | ```php 341 | foreach ($CN->getVehicleStatus()['exteriorStatus']['doorLockStatus'] as $position => $status) { 342 | // Here, $position can equal "frontLeft", "frontRight", "rearLeft", etc. 343 | echo VWCarNet\API::codeCaseToWords($position) . " door is " . strtolower($status) . "." . PHP_EOL; 344 | } 345 | 346 | /* 347 | 348 | The above will print: 349 | 350 | Front left door is unlocked. 351 | Front right door is locked. 352 | Rear left door is locked. 353 | Rear right door is locked. 354 | 355 | */ 356 | ``` 357 | 358 | *** 359 | ## Disclaimer 360 | No affiliation or sponsorship is to be implied between the developers of this package and Volkswagen AG. Any trademarks, service marks, brand names or logos are the properties of their respective owners and are used on this site for educational purposes only. -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "d07b08d9ec4c4873de92bacf83f2a05f", 8 | "packages": [ 9 | { 10 | "name": "guzzlehttp/guzzle", 11 | "version": "7.0.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/guzzle/guzzle.git", 15 | "reference": "2d9d3c186a6637a43193e66b097c50e4451eaab2" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/2d9d3c186a6637a43193e66b097c50e4451eaab2", 20 | "reference": "2d9d3c186a6637a43193e66b097c50e4451eaab2", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-json": "*", 25 | "guzzlehttp/promises": "^1.0", 26 | "guzzlehttp/psr7": "^1.6.1", 27 | "php": "^7.2.5", 28 | "psr/http-client": "^1.0" 29 | }, 30 | "provide": { 31 | "psr/http-client-implementation": "1.0" 32 | }, 33 | "require-dev": { 34 | "ergebnis/composer-normalize": "^2.0", 35 | "ext-curl": "*", 36 | "php-http/client-integration-tests": "dev-phpunit8", 37 | "phpunit/phpunit": "^8.5.5", 38 | "psr/log": "^1.1" 39 | }, 40 | "suggest": { 41 | "ext-curl": "Required for CURL handler support", 42 | "ext-intl": "Required for Internationalized Domain Name (IDN) support", 43 | "psr/log": "Required for using the Log middleware" 44 | }, 45 | "type": "library", 46 | "extra": { 47 | "branch-alias": { 48 | "dev-master": "7.0-dev" 49 | } 50 | }, 51 | "autoload": { 52 | "psr-4": { 53 | "GuzzleHttp\\": "src/" 54 | }, 55 | "files": [ 56 | "src/functions_include.php" 57 | ] 58 | }, 59 | "notification-url": "https://packagist.org/downloads/", 60 | "license": [ 61 | "MIT" 62 | ], 63 | "authors": [ 64 | { 65 | "name": "Michael Dowling", 66 | "email": "mtdowling@gmail.com", 67 | "homepage": "https://github.com/mtdowling" 68 | }, 69 | { 70 | "name": "Márk Sági-Kazár", 71 | "email": "mark.sagikazar@gmail.com", 72 | "homepage": "https://sagikazarmark.hu" 73 | } 74 | ], 75 | "description": "Guzzle is a PHP HTTP client library", 76 | "homepage": "http://guzzlephp.org/", 77 | "keywords": [ 78 | "client", 79 | "curl", 80 | "framework", 81 | "http", 82 | "http client", 83 | "psr-18", 84 | "psr-7", 85 | "rest", 86 | "web service" 87 | ], 88 | "time": "2020-06-27T10:33:25+00:00" 89 | }, 90 | { 91 | "name": "guzzlehttp/promises", 92 | "version": "v1.3.1", 93 | "source": { 94 | "type": "git", 95 | "url": "https://github.com/guzzle/promises.git", 96 | "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" 97 | }, 98 | "dist": { 99 | "type": "zip", 100 | "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", 101 | "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", 102 | "shasum": "" 103 | }, 104 | "require": { 105 | "php": ">=5.5.0" 106 | }, 107 | "require-dev": { 108 | "phpunit/phpunit": "^4.0" 109 | }, 110 | "type": "library", 111 | "extra": { 112 | "branch-alias": { 113 | "dev-master": "1.4-dev" 114 | } 115 | }, 116 | "autoload": { 117 | "psr-4": { 118 | "GuzzleHttp\\Promise\\": "src/" 119 | }, 120 | "files": [ 121 | "src/functions_include.php" 122 | ] 123 | }, 124 | "notification-url": "https://packagist.org/downloads/", 125 | "license": [ 126 | "MIT" 127 | ], 128 | "authors": [ 129 | { 130 | "name": "Michael Dowling", 131 | "email": "mtdowling@gmail.com", 132 | "homepage": "https://github.com/mtdowling" 133 | } 134 | ], 135 | "description": "Guzzle promises library", 136 | "keywords": [ 137 | "promise" 138 | ], 139 | "time": "2016-12-20T10:07:11+00:00" 140 | }, 141 | { 142 | "name": "guzzlehttp/psr7", 143 | "version": "1.6.1", 144 | "source": { 145 | "type": "git", 146 | "url": "https://github.com/guzzle/psr7.git", 147 | "reference": "239400de7a173fe9901b9ac7c06497751f00727a" 148 | }, 149 | "dist": { 150 | "type": "zip", 151 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", 152 | "reference": "239400de7a173fe9901b9ac7c06497751f00727a", 153 | "shasum": "" 154 | }, 155 | "require": { 156 | "php": ">=5.4.0", 157 | "psr/http-message": "~1.0", 158 | "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" 159 | }, 160 | "provide": { 161 | "psr/http-message-implementation": "1.0" 162 | }, 163 | "require-dev": { 164 | "ext-zlib": "*", 165 | "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" 166 | }, 167 | "suggest": { 168 | "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" 169 | }, 170 | "type": "library", 171 | "extra": { 172 | "branch-alias": { 173 | "dev-master": "1.6-dev" 174 | } 175 | }, 176 | "autoload": { 177 | "psr-4": { 178 | "GuzzleHttp\\Psr7\\": "src/" 179 | }, 180 | "files": [ 181 | "src/functions_include.php" 182 | ] 183 | }, 184 | "notification-url": "https://packagist.org/downloads/", 185 | "license": [ 186 | "MIT" 187 | ], 188 | "authors": [ 189 | { 190 | "name": "Michael Dowling", 191 | "email": "mtdowling@gmail.com", 192 | "homepage": "https://github.com/mtdowling" 193 | }, 194 | { 195 | "name": "Tobias Schultze", 196 | "homepage": "https://github.com/Tobion" 197 | } 198 | ], 199 | "description": "PSR-7 message implementation that also provides common utility methods", 200 | "keywords": [ 201 | "http", 202 | "message", 203 | "psr-7", 204 | "request", 205 | "response", 206 | "stream", 207 | "uri", 208 | "url" 209 | ], 210 | "time": "2019-07-01T23:21:34+00:00" 211 | }, 212 | { 213 | "name": "psr/http-client", 214 | "version": "1.0.1", 215 | "source": { 216 | "type": "git", 217 | "url": "https://github.com/php-fig/http-client.git", 218 | "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" 219 | }, 220 | "dist": { 221 | "type": "zip", 222 | "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", 223 | "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", 224 | "shasum": "" 225 | }, 226 | "require": { 227 | "php": "^7.0 || ^8.0", 228 | "psr/http-message": "^1.0" 229 | }, 230 | "type": "library", 231 | "extra": { 232 | "branch-alias": { 233 | "dev-master": "1.0.x-dev" 234 | } 235 | }, 236 | "autoload": { 237 | "psr-4": { 238 | "Psr\\Http\\Client\\": "src/" 239 | } 240 | }, 241 | "notification-url": "https://packagist.org/downloads/", 242 | "license": [ 243 | "MIT" 244 | ], 245 | "authors": [ 246 | { 247 | "name": "PHP-FIG", 248 | "homepage": "http://www.php-fig.org/" 249 | } 250 | ], 251 | "description": "Common interface for HTTP clients", 252 | "homepage": "https://github.com/php-fig/http-client", 253 | "keywords": [ 254 | "http", 255 | "http-client", 256 | "psr", 257 | "psr-18" 258 | ], 259 | "time": "2020-06-29T06:28:15+00:00" 260 | }, 261 | { 262 | "name": "psr/http-message", 263 | "version": "1.0.1", 264 | "source": { 265 | "type": "git", 266 | "url": "https://github.com/php-fig/http-message.git", 267 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" 268 | }, 269 | "dist": { 270 | "type": "zip", 271 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", 272 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", 273 | "shasum": "" 274 | }, 275 | "require": { 276 | "php": ">=5.3.0" 277 | }, 278 | "type": "library", 279 | "extra": { 280 | "branch-alias": { 281 | "dev-master": "1.0.x-dev" 282 | } 283 | }, 284 | "autoload": { 285 | "psr-4": { 286 | "Psr\\Http\\Message\\": "src/" 287 | } 288 | }, 289 | "notification-url": "https://packagist.org/downloads/", 290 | "license": [ 291 | "MIT" 292 | ], 293 | "authors": [ 294 | { 295 | "name": "PHP-FIG", 296 | "homepage": "http://www.php-fig.org/" 297 | } 298 | ], 299 | "description": "Common interface for HTTP messages", 300 | "homepage": "https://github.com/php-fig/http-message", 301 | "keywords": [ 302 | "http", 303 | "http-message", 304 | "psr", 305 | "psr-7", 306 | "request", 307 | "response" 308 | ], 309 | "time": "2016-08-06T14:39:51+00:00" 310 | }, 311 | { 312 | "name": "ralouphie/getallheaders", 313 | "version": "3.0.3", 314 | "source": { 315 | "type": "git", 316 | "url": "https://github.com/ralouphie/getallheaders.git", 317 | "reference": "120b605dfeb996808c31b6477290a714d356e822" 318 | }, 319 | "dist": { 320 | "type": "zip", 321 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", 322 | "reference": "120b605dfeb996808c31b6477290a714d356e822", 323 | "shasum": "" 324 | }, 325 | "require": { 326 | "php": ">=5.6" 327 | }, 328 | "require-dev": { 329 | "php-coveralls/php-coveralls": "^2.1", 330 | "phpunit/phpunit": "^5 || ^6.5" 331 | }, 332 | "type": "library", 333 | "autoload": { 334 | "files": [ 335 | "src/getallheaders.php" 336 | ] 337 | }, 338 | "notification-url": "https://packagist.org/downloads/", 339 | "license": [ 340 | "MIT" 341 | ], 342 | "authors": [ 343 | { 344 | "name": "Ralph Khattar", 345 | "email": "ralph.khattar@gmail.com" 346 | } 347 | ], 348 | "description": "A polyfill for getallheaders.", 349 | "time": "2019-03-08T08:55:37+00:00" 350 | } 351 | ], 352 | "packages-dev": [ 353 | { 354 | "name": "doctrine/instantiator", 355 | "version": "1.3.1", 356 | "source": { 357 | "type": "git", 358 | "url": "https://github.com/doctrine/instantiator.git", 359 | "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" 360 | }, 361 | "dist": { 362 | "type": "zip", 363 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", 364 | "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", 365 | "shasum": "" 366 | }, 367 | "require": { 368 | "php": "^7.1 || ^8.0" 369 | }, 370 | "require-dev": { 371 | "doctrine/coding-standard": "^6.0", 372 | "ext-pdo": "*", 373 | "ext-phar": "*", 374 | "phpbench/phpbench": "^0.13", 375 | "phpstan/phpstan-phpunit": "^0.11", 376 | "phpstan/phpstan-shim": "^0.11", 377 | "phpunit/phpunit": "^7.0" 378 | }, 379 | "type": "library", 380 | "extra": { 381 | "branch-alias": { 382 | "dev-master": "1.2.x-dev" 383 | } 384 | }, 385 | "autoload": { 386 | "psr-4": { 387 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 388 | } 389 | }, 390 | "notification-url": "https://packagist.org/downloads/", 391 | "license": [ 392 | "MIT" 393 | ], 394 | "authors": [ 395 | { 396 | "name": "Marco Pivetta", 397 | "email": "ocramius@gmail.com", 398 | "homepage": "http://ocramius.github.com/" 399 | } 400 | ], 401 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 402 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 403 | "keywords": [ 404 | "constructor", 405 | "instantiate" 406 | ], 407 | "time": "2020-05-29T17:27:14+00:00" 408 | }, 409 | { 410 | "name": "myclabs/deep-copy", 411 | "version": "1.10.1", 412 | "source": { 413 | "type": "git", 414 | "url": "https://github.com/myclabs/DeepCopy.git", 415 | "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" 416 | }, 417 | "dist": { 418 | "type": "zip", 419 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", 420 | "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", 421 | "shasum": "" 422 | }, 423 | "require": { 424 | "php": "^7.1 || ^8.0" 425 | }, 426 | "replace": { 427 | "myclabs/deep-copy": "self.version" 428 | }, 429 | "require-dev": { 430 | "doctrine/collections": "^1.0", 431 | "doctrine/common": "^2.6", 432 | "phpunit/phpunit": "^7.1" 433 | }, 434 | "type": "library", 435 | "autoload": { 436 | "psr-4": { 437 | "DeepCopy\\": "src/DeepCopy/" 438 | }, 439 | "files": [ 440 | "src/DeepCopy/deep_copy.php" 441 | ] 442 | }, 443 | "notification-url": "https://packagist.org/downloads/", 444 | "license": [ 445 | "MIT" 446 | ], 447 | "description": "Create deep copies (clones) of your objects", 448 | "keywords": [ 449 | "clone", 450 | "copy", 451 | "duplicate", 452 | "object", 453 | "object graph" 454 | ], 455 | "time": "2020-06-29T13:22:24+00:00" 456 | }, 457 | { 458 | "name": "nikic/php-parser", 459 | "version": "v4.8.0", 460 | "source": { 461 | "type": "git", 462 | "url": "https://github.com/nikic/PHP-Parser.git", 463 | "reference": "8c58eb4cd4f3883f82611abeac2efbc3dbed787e" 464 | }, 465 | "dist": { 466 | "type": "zip", 467 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8c58eb4cd4f3883f82611abeac2efbc3dbed787e", 468 | "reference": "8c58eb4cd4f3883f82611abeac2efbc3dbed787e", 469 | "shasum": "" 470 | }, 471 | "require": { 472 | "ext-tokenizer": "*", 473 | "php": ">=7.0" 474 | }, 475 | "require-dev": { 476 | "ircmaxell/php-yacc": "^0.0.6", 477 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" 478 | }, 479 | "bin": [ 480 | "bin/php-parse" 481 | ], 482 | "type": "library", 483 | "extra": { 484 | "branch-alias": { 485 | "dev-master": "4.8-dev" 486 | } 487 | }, 488 | "autoload": { 489 | "psr-4": { 490 | "PhpParser\\": "lib/PhpParser" 491 | } 492 | }, 493 | "notification-url": "https://packagist.org/downloads/", 494 | "license": [ 495 | "BSD-3-Clause" 496 | ], 497 | "authors": [ 498 | { 499 | "name": "Nikita Popov" 500 | } 501 | ], 502 | "description": "A PHP parser written in PHP", 503 | "keywords": [ 504 | "parser", 505 | "php" 506 | ], 507 | "time": "2020-08-09T10:23:20+00:00" 508 | }, 509 | { 510 | "name": "phar-io/manifest", 511 | "version": "2.0.1", 512 | "source": { 513 | "type": "git", 514 | "url": "https://github.com/phar-io/manifest.git", 515 | "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" 516 | }, 517 | "dist": { 518 | "type": "zip", 519 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", 520 | "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", 521 | "shasum": "" 522 | }, 523 | "require": { 524 | "ext-dom": "*", 525 | "ext-phar": "*", 526 | "ext-xmlwriter": "*", 527 | "phar-io/version": "^3.0.1", 528 | "php": "^7.2 || ^8.0" 529 | }, 530 | "type": "library", 531 | "extra": { 532 | "branch-alias": { 533 | "dev-master": "2.0.x-dev" 534 | } 535 | }, 536 | "autoload": { 537 | "classmap": [ 538 | "src/" 539 | ] 540 | }, 541 | "notification-url": "https://packagist.org/downloads/", 542 | "license": [ 543 | "BSD-3-Clause" 544 | ], 545 | "authors": [ 546 | { 547 | "name": "Arne Blankerts", 548 | "email": "arne@blankerts.de", 549 | "role": "Developer" 550 | }, 551 | { 552 | "name": "Sebastian Heuer", 553 | "email": "sebastian@phpeople.de", 554 | "role": "Developer" 555 | }, 556 | { 557 | "name": "Sebastian Bergmann", 558 | "email": "sebastian@phpunit.de", 559 | "role": "Developer" 560 | } 561 | ], 562 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 563 | "time": "2020-06-27T14:33:11+00:00" 564 | }, 565 | { 566 | "name": "phar-io/version", 567 | "version": "3.0.2", 568 | "source": { 569 | "type": "git", 570 | "url": "https://github.com/phar-io/version.git", 571 | "reference": "c6bb6825def89e0a32220f88337f8ceaf1975fa0" 572 | }, 573 | "dist": { 574 | "type": "zip", 575 | "url": "https://api.github.com/repos/phar-io/version/zipball/c6bb6825def89e0a32220f88337f8ceaf1975fa0", 576 | "reference": "c6bb6825def89e0a32220f88337f8ceaf1975fa0", 577 | "shasum": "" 578 | }, 579 | "require": { 580 | "php": "^7.2 || ^8.0" 581 | }, 582 | "type": "library", 583 | "autoload": { 584 | "classmap": [ 585 | "src/" 586 | ] 587 | }, 588 | "notification-url": "https://packagist.org/downloads/", 589 | "license": [ 590 | "BSD-3-Clause" 591 | ], 592 | "authors": [ 593 | { 594 | "name": "Arne Blankerts", 595 | "email": "arne@blankerts.de", 596 | "role": "Developer" 597 | }, 598 | { 599 | "name": "Sebastian Heuer", 600 | "email": "sebastian@phpeople.de", 601 | "role": "Developer" 602 | }, 603 | { 604 | "name": "Sebastian Bergmann", 605 | "email": "sebastian@phpunit.de", 606 | "role": "Developer" 607 | } 608 | ], 609 | "description": "Library for handling version information and constraints", 610 | "time": "2020-06-27T14:39:04+00:00" 611 | }, 612 | { 613 | "name": "phpdocumentor/reflection-common", 614 | "version": "2.2.0", 615 | "source": { 616 | "type": "git", 617 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 618 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" 619 | }, 620 | "dist": { 621 | "type": "zip", 622 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", 623 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", 624 | "shasum": "" 625 | }, 626 | "require": { 627 | "php": "^7.2 || ^8.0" 628 | }, 629 | "type": "library", 630 | "extra": { 631 | "branch-alias": { 632 | "dev-2.x": "2.x-dev" 633 | } 634 | }, 635 | "autoload": { 636 | "psr-4": { 637 | "phpDocumentor\\Reflection\\": "src/" 638 | } 639 | }, 640 | "notification-url": "https://packagist.org/downloads/", 641 | "license": [ 642 | "MIT" 643 | ], 644 | "authors": [ 645 | { 646 | "name": "Jaap van Otterdijk", 647 | "email": "opensource@ijaap.nl" 648 | } 649 | ], 650 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 651 | "homepage": "http://www.phpdoc.org", 652 | "keywords": [ 653 | "FQSEN", 654 | "phpDocumentor", 655 | "phpdoc", 656 | "reflection", 657 | "static analysis" 658 | ], 659 | "time": "2020-06-27T09:03:43+00:00" 660 | }, 661 | { 662 | "name": "phpdocumentor/reflection-docblock", 663 | "version": "5.2.0", 664 | "source": { 665 | "type": "git", 666 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 667 | "reference": "3170448f5769fe19f456173d833734e0ff1b84df" 668 | }, 669 | "dist": { 670 | "type": "zip", 671 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/3170448f5769fe19f456173d833734e0ff1b84df", 672 | "reference": "3170448f5769fe19f456173d833734e0ff1b84df", 673 | "shasum": "" 674 | }, 675 | "require": { 676 | "ext-filter": "*", 677 | "php": "^7.2 || ^8.0", 678 | "phpdocumentor/reflection-common": "^2.2", 679 | "phpdocumentor/type-resolver": "^1.3", 680 | "webmozart/assert": "^1.9.1" 681 | }, 682 | "require-dev": { 683 | "mockery/mockery": "~1.3.2" 684 | }, 685 | "type": "library", 686 | "extra": { 687 | "branch-alias": { 688 | "dev-master": "5.x-dev" 689 | } 690 | }, 691 | "autoload": { 692 | "psr-4": { 693 | "phpDocumentor\\Reflection\\": "src" 694 | } 695 | }, 696 | "notification-url": "https://packagist.org/downloads/", 697 | "license": [ 698 | "MIT" 699 | ], 700 | "authors": [ 701 | { 702 | "name": "Mike van Riel", 703 | "email": "me@mikevanriel.com" 704 | }, 705 | { 706 | "name": "Jaap van Otterdijk", 707 | "email": "account@ijaap.nl" 708 | } 709 | ], 710 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 711 | "time": "2020-07-20T20:05:34+00:00" 712 | }, 713 | { 714 | "name": "phpdocumentor/type-resolver", 715 | "version": "1.3.0", 716 | "source": { 717 | "type": "git", 718 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 719 | "reference": "e878a14a65245fbe78f8080eba03b47c3b705651" 720 | }, 721 | "dist": { 722 | "type": "zip", 723 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e878a14a65245fbe78f8080eba03b47c3b705651", 724 | "reference": "e878a14a65245fbe78f8080eba03b47c3b705651", 725 | "shasum": "" 726 | }, 727 | "require": { 728 | "php": "^7.2 || ^8.0", 729 | "phpdocumentor/reflection-common": "^2.0" 730 | }, 731 | "require-dev": { 732 | "ext-tokenizer": "*" 733 | }, 734 | "type": "library", 735 | "extra": { 736 | "branch-alias": { 737 | "dev-1.x": "1.x-dev" 738 | } 739 | }, 740 | "autoload": { 741 | "psr-4": { 742 | "phpDocumentor\\Reflection\\": "src" 743 | } 744 | }, 745 | "notification-url": "https://packagist.org/downloads/", 746 | "license": [ 747 | "MIT" 748 | ], 749 | "authors": [ 750 | { 751 | "name": "Mike van Riel", 752 | "email": "me@mikevanriel.com" 753 | } 754 | ], 755 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 756 | "time": "2020-06-27T10:12:23+00:00" 757 | }, 758 | { 759 | "name": "phpspec/prophecy", 760 | "version": "1.11.1", 761 | "source": { 762 | "type": "git", 763 | "url": "https://github.com/phpspec/prophecy.git", 764 | "reference": "b20034be5efcdab4fb60ca3a29cba2949aead160" 765 | }, 766 | "dist": { 767 | "type": "zip", 768 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b20034be5efcdab4fb60ca3a29cba2949aead160", 769 | "reference": "b20034be5efcdab4fb60ca3a29cba2949aead160", 770 | "shasum": "" 771 | }, 772 | "require": { 773 | "doctrine/instantiator": "^1.2", 774 | "php": "^7.2", 775 | "phpdocumentor/reflection-docblock": "^5.0", 776 | "sebastian/comparator": "^3.0 || ^4.0", 777 | "sebastian/recursion-context": "^3.0 || ^4.0" 778 | }, 779 | "require-dev": { 780 | "phpspec/phpspec": "^6.0", 781 | "phpunit/phpunit": "^8.0" 782 | }, 783 | "type": "library", 784 | "extra": { 785 | "branch-alias": { 786 | "dev-master": "1.11.x-dev" 787 | } 788 | }, 789 | "autoload": { 790 | "psr-4": { 791 | "Prophecy\\": "src/Prophecy" 792 | } 793 | }, 794 | "notification-url": "https://packagist.org/downloads/", 795 | "license": [ 796 | "MIT" 797 | ], 798 | "authors": [ 799 | { 800 | "name": "Konstantin Kudryashov", 801 | "email": "ever.zet@gmail.com", 802 | "homepage": "http://everzet.com" 803 | }, 804 | { 805 | "name": "Marcello Duarte", 806 | "email": "marcello.duarte@gmail.com" 807 | } 808 | ], 809 | "description": "Highly opinionated mocking framework for PHP 5.3+", 810 | "homepage": "https://github.com/phpspec/prophecy", 811 | "keywords": [ 812 | "Double", 813 | "Dummy", 814 | "fake", 815 | "mock", 816 | "spy", 817 | "stub" 818 | ], 819 | "time": "2020-07-08T12:44:21+00:00" 820 | }, 821 | { 822 | "name": "phpunit/php-code-coverage", 823 | "version": "9.1.3", 824 | "source": { 825 | "type": "git", 826 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 827 | "reference": "4abbce3b0ad05f2e7143ea5f775d5513cb5261e4" 828 | }, 829 | "dist": { 830 | "type": "zip", 831 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4abbce3b0ad05f2e7143ea5f775d5513cb5261e4", 832 | "reference": "4abbce3b0ad05f2e7143ea5f775d5513cb5261e4", 833 | "shasum": "" 834 | }, 835 | "require": { 836 | "ext-dom": "*", 837 | "ext-libxml": "*", 838 | "ext-xmlwriter": "*", 839 | "nikic/php-parser": "^4.8", 840 | "php": "^7.3 || ^8.0", 841 | "phpunit/php-file-iterator": "^3.0.3", 842 | "phpunit/php-text-template": "^2.0.2", 843 | "sebastian/code-unit-reverse-lookup": "^2.0.2", 844 | "sebastian/complexity": "^2.0", 845 | "sebastian/environment": "^5.1.2", 846 | "sebastian/lines-of-code": "^1.0", 847 | "sebastian/version": "^3.0.1", 848 | "theseer/tokenizer": "^1.2.0" 849 | }, 850 | "require-dev": { 851 | "phpunit/phpunit": "^9.3" 852 | }, 853 | "suggest": { 854 | "ext-pcov": "*", 855 | "ext-xdebug": "*" 856 | }, 857 | "type": "library", 858 | "extra": { 859 | "branch-alias": { 860 | "dev-master": "9.1-dev" 861 | } 862 | }, 863 | "autoload": { 864 | "classmap": [ 865 | "src/" 866 | ] 867 | }, 868 | "notification-url": "https://packagist.org/downloads/", 869 | "license": [ 870 | "BSD-3-Clause" 871 | ], 872 | "authors": [ 873 | { 874 | "name": "Sebastian Bergmann", 875 | "email": "sebastian@phpunit.de", 876 | "role": "lead" 877 | } 878 | ], 879 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 880 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 881 | "keywords": [ 882 | "coverage", 883 | "testing", 884 | "xunit" 885 | ], 886 | "time": "2020-08-10T17:45:51+00:00" 887 | }, 888 | { 889 | "name": "phpunit/php-file-iterator", 890 | "version": "3.0.4", 891 | "source": { 892 | "type": "git", 893 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 894 | "reference": "25fefc5b19835ca653877fe081644a3f8c1d915e" 895 | }, 896 | "dist": { 897 | "type": "zip", 898 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/25fefc5b19835ca653877fe081644a3f8c1d915e", 899 | "reference": "25fefc5b19835ca653877fe081644a3f8c1d915e", 900 | "shasum": "" 901 | }, 902 | "require": { 903 | "php": "^7.3 || ^8.0" 904 | }, 905 | "require-dev": { 906 | "phpunit/phpunit": "^9.0" 907 | }, 908 | "type": "library", 909 | "extra": { 910 | "branch-alias": { 911 | "dev-master": "3.0-dev" 912 | } 913 | }, 914 | "autoload": { 915 | "classmap": [ 916 | "src/" 917 | ] 918 | }, 919 | "notification-url": "https://packagist.org/downloads/", 920 | "license": [ 921 | "BSD-3-Clause" 922 | ], 923 | "authors": [ 924 | { 925 | "name": "Sebastian Bergmann", 926 | "email": "sebastian@phpunit.de", 927 | "role": "lead" 928 | } 929 | ], 930 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 931 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 932 | "keywords": [ 933 | "filesystem", 934 | "iterator" 935 | ], 936 | "time": "2020-07-11T05:18:21+00:00" 937 | }, 938 | { 939 | "name": "phpunit/php-invoker", 940 | "version": "3.1.0", 941 | "source": { 942 | "type": "git", 943 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 944 | "reference": "7a85b66acc48cacffdf87dadd3694e7123674298" 945 | }, 946 | "dist": { 947 | "type": "zip", 948 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/7a85b66acc48cacffdf87dadd3694e7123674298", 949 | "reference": "7a85b66acc48cacffdf87dadd3694e7123674298", 950 | "shasum": "" 951 | }, 952 | "require": { 953 | "php": "^7.3 || ^8.0" 954 | }, 955 | "require-dev": { 956 | "ext-pcntl": "*", 957 | "phpunit/phpunit": "^9.0" 958 | }, 959 | "suggest": { 960 | "ext-pcntl": "*" 961 | }, 962 | "type": "library", 963 | "extra": { 964 | "branch-alias": { 965 | "dev-master": "3.1-dev" 966 | } 967 | }, 968 | "autoload": { 969 | "classmap": [ 970 | "src/" 971 | ] 972 | }, 973 | "notification-url": "https://packagist.org/downloads/", 974 | "license": [ 975 | "BSD-3-Clause" 976 | ], 977 | "authors": [ 978 | { 979 | "name": "Sebastian Bergmann", 980 | "email": "sebastian@phpunit.de", 981 | "role": "lead" 982 | } 983 | ], 984 | "description": "Invoke callables with a timeout", 985 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 986 | "keywords": [ 987 | "process" 988 | ], 989 | "time": "2020-08-06T07:04:15+00:00" 990 | }, 991 | { 992 | "name": "phpunit/php-text-template", 993 | "version": "2.0.2", 994 | "source": { 995 | "type": "git", 996 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 997 | "reference": "6ff9c8ea4d3212b88fcf74e25e516e2c51c99324" 998 | }, 999 | "dist": { 1000 | "type": "zip", 1001 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/6ff9c8ea4d3212b88fcf74e25e516e2c51c99324", 1002 | "reference": "6ff9c8ea4d3212b88fcf74e25e516e2c51c99324", 1003 | "shasum": "" 1004 | }, 1005 | "require": { 1006 | "php": "^7.3 || ^8.0" 1007 | }, 1008 | "require-dev": { 1009 | "phpunit/phpunit": "^9.0" 1010 | }, 1011 | "type": "library", 1012 | "extra": { 1013 | "branch-alias": { 1014 | "dev-master": "2.0-dev" 1015 | } 1016 | }, 1017 | "autoload": { 1018 | "classmap": [ 1019 | "src/" 1020 | ] 1021 | }, 1022 | "notification-url": "https://packagist.org/downloads/", 1023 | "license": [ 1024 | "BSD-3-Clause" 1025 | ], 1026 | "authors": [ 1027 | { 1028 | "name": "Sebastian Bergmann", 1029 | "email": "sebastian@phpunit.de", 1030 | "role": "lead" 1031 | } 1032 | ], 1033 | "description": "Simple template engine.", 1034 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 1035 | "keywords": [ 1036 | "template" 1037 | ], 1038 | "time": "2020-06-26T11:55:37+00:00" 1039 | }, 1040 | { 1041 | "name": "phpunit/php-timer", 1042 | "version": "5.0.1", 1043 | "source": { 1044 | "type": "git", 1045 | "url": "https://github.com/sebastianbergmann/php-timer.git", 1046 | "reference": "cc49734779cbb302bf51a44297dab8c4bbf941e7" 1047 | }, 1048 | "dist": { 1049 | "type": "zip", 1050 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/cc49734779cbb302bf51a44297dab8c4bbf941e7", 1051 | "reference": "cc49734779cbb302bf51a44297dab8c4bbf941e7", 1052 | "shasum": "" 1053 | }, 1054 | "require": { 1055 | "php": "^7.3 || ^8.0" 1056 | }, 1057 | "require-dev": { 1058 | "phpunit/phpunit": "^9.2" 1059 | }, 1060 | "type": "library", 1061 | "extra": { 1062 | "branch-alias": { 1063 | "dev-master": "5.0-dev" 1064 | } 1065 | }, 1066 | "autoload": { 1067 | "classmap": [ 1068 | "src/" 1069 | ] 1070 | }, 1071 | "notification-url": "https://packagist.org/downloads/", 1072 | "license": [ 1073 | "BSD-3-Clause" 1074 | ], 1075 | "authors": [ 1076 | { 1077 | "name": "Sebastian Bergmann", 1078 | "email": "sebastian@phpunit.de", 1079 | "role": "lead" 1080 | } 1081 | ], 1082 | "description": "Utility class for timing", 1083 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 1084 | "keywords": [ 1085 | "timer" 1086 | ], 1087 | "time": "2020-06-26T11:58:13+00:00" 1088 | }, 1089 | { 1090 | "name": "phpunit/phpunit", 1091 | "version": "9.3.7", 1092 | "source": { 1093 | "type": "git", 1094 | "url": "https://github.com/sebastianbergmann/phpunit.git", 1095 | "reference": "c638a0cac77347980352485912de48c99b42ad00" 1096 | }, 1097 | "dist": { 1098 | "type": "zip", 1099 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c638a0cac77347980352485912de48c99b42ad00", 1100 | "reference": "c638a0cac77347980352485912de48c99b42ad00", 1101 | "shasum": "" 1102 | }, 1103 | "require": { 1104 | "doctrine/instantiator": "^1.3.1", 1105 | "ext-dom": "*", 1106 | "ext-json": "*", 1107 | "ext-libxml": "*", 1108 | "ext-mbstring": "*", 1109 | "ext-xml": "*", 1110 | "ext-xmlwriter": "*", 1111 | "myclabs/deep-copy": "^1.10.1", 1112 | "phar-io/manifest": "^2.0.1", 1113 | "phar-io/version": "^3.0.2", 1114 | "php": "^7.3 || ^8.0", 1115 | "phpspec/prophecy": "^1.11.1", 1116 | "phpunit/php-code-coverage": "^9.1.1", 1117 | "phpunit/php-file-iterator": "^3.0.4", 1118 | "phpunit/php-invoker": "^3.1", 1119 | "phpunit/php-text-template": "^2.0.2", 1120 | "phpunit/php-timer": "^5.0.1", 1121 | "sebastian/code-unit": "^1.0.5", 1122 | "sebastian/comparator": "^4.0.3", 1123 | "sebastian/diff": "^4.0.2", 1124 | "sebastian/environment": "^5.1.2", 1125 | "sebastian/exporter": "^4.0.2", 1126 | "sebastian/global-state": "^5.0", 1127 | "sebastian/object-enumerator": "^4.0.2", 1128 | "sebastian/resource-operations": "^3.0.2", 1129 | "sebastian/type": "^2.2.1", 1130 | "sebastian/version": "^3.0.1" 1131 | }, 1132 | "require-dev": { 1133 | "ext-pdo": "*", 1134 | "phpspec/prophecy-phpunit": "^2.0.1" 1135 | }, 1136 | "suggest": { 1137 | "ext-soap": "*", 1138 | "ext-xdebug": "*" 1139 | }, 1140 | "bin": [ 1141 | "phpunit" 1142 | ], 1143 | "type": "library", 1144 | "extra": { 1145 | "branch-alias": { 1146 | "dev-master": "9.3-dev" 1147 | } 1148 | }, 1149 | "autoload": { 1150 | "classmap": [ 1151 | "src/" 1152 | ], 1153 | "files": [ 1154 | "src/Framework/Assert/Functions.php" 1155 | ] 1156 | }, 1157 | "notification-url": "https://packagist.org/downloads/", 1158 | "license": [ 1159 | "BSD-3-Clause" 1160 | ], 1161 | "authors": [ 1162 | { 1163 | "name": "Sebastian Bergmann", 1164 | "email": "sebastian@phpunit.de", 1165 | "role": "lead" 1166 | } 1167 | ], 1168 | "description": "The PHP Unit Testing framework.", 1169 | "homepage": "https://phpunit.de/", 1170 | "keywords": [ 1171 | "phpunit", 1172 | "testing", 1173 | "xunit" 1174 | ], 1175 | "time": "2020-08-11T15:36:12+00:00" 1176 | }, 1177 | { 1178 | "name": "sebastian/code-unit", 1179 | "version": "1.0.5", 1180 | "source": { 1181 | "type": "git", 1182 | "url": "https://github.com/sebastianbergmann/code-unit.git", 1183 | "reference": "c1e2df332c905079980b119c4db103117e5e5c90" 1184 | }, 1185 | "dist": { 1186 | "type": "zip", 1187 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/c1e2df332c905079980b119c4db103117e5e5c90", 1188 | "reference": "c1e2df332c905079980b119c4db103117e5e5c90", 1189 | "shasum": "" 1190 | }, 1191 | "require": { 1192 | "php": "^7.3 || ^8.0" 1193 | }, 1194 | "require-dev": { 1195 | "phpunit/phpunit": "^9.0" 1196 | }, 1197 | "type": "library", 1198 | "extra": { 1199 | "branch-alias": { 1200 | "dev-master": "1.0-dev" 1201 | } 1202 | }, 1203 | "autoload": { 1204 | "classmap": [ 1205 | "src/" 1206 | ] 1207 | }, 1208 | "notification-url": "https://packagist.org/downloads/", 1209 | "license": [ 1210 | "BSD-3-Clause" 1211 | ], 1212 | "authors": [ 1213 | { 1214 | "name": "Sebastian Bergmann", 1215 | "email": "sebastian@phpunit.de", 1216 | "role": "lead" 1217 | } 1218 | ], 1219 | "description": "Collection of value objects that represent the PHP code units", 1220 | "homepage": "https://github.com/sebastianbergmann/code-unit", 1221 | "time": "2020-06-26T12:50:45+00:00" 1222 | }, 1223 | { 1224 | "name": "sebastian/code-unit-reverse-lookup", 1225 | "version": "2.0.2", 1226 | "source": { 1227 | "type": "git", 1228 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 1229 | "reference": "ee51f9bb0c6d8a43337055db3120829fa14da819" 1230 | }, 1231 | "dist": { 1232 | "type": "zip", 1233 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ee51f9bb0c6d8a43337055db3120829fa14da819", 1234 | "reference": "ee51f9bb0c6d8a43337055db3120829fa14da819", 1235 | "shasum": "" 1236 | }, 1237 | "require": { 1238 | "php": "^7.3 || ^8.0" 1239 | }, 1240 | "require-dev": { 1241 | "phpunit/phpunit": "^9.0" 1242 | }, 1243 | "type": "library", 1244 | "extra": { 1245 | "branch-alias": { 1246 | "dev-master": "2.0-dev" 1247 | } 1248 | }, 1249 | "autoload": { 1250 | "classmap": [ 1251 | "src/" 1252 | ] 1253 | }, 1254 | "notification-url": "https://packagist.org/downloads/", 1255 | "license": [ 1256 | "BSD-3-Clause" 1257 | ], 1258 | "authors": [ 1259 | { 1260 | "name": "Sebastian Bergmann", 1261 | "email": "sebastian@phpunit.de" 1262 | } 1263 | ], 1264 | "description": "Looks up which function or method a line of code belongs to", 1265 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1266 | "time": "2020-06-26T12:04:00+00:00" 1267 | }, 1268 | { 1269 | "name": "sebastian/comparator", 1270 | "version": "4.0.3", 1271 | "source": { 1272 | "type": "git", 1273 | "url": "https://github.com/sebastianbergmann/comparator.git", 1274 | "reference": "dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f" 1275 | }, 1276 | "dist": { 1277 | "type": "zip", 1278 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f", 1279 | "reference": "dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f", 1280 | "shasum": "" 1281 | }, 1282 | "require": { 1283 | "php": "^7.3 || ^8.0", 1284 | "sebastian/diff": "^4.0", 1285 | "sebastian/exporter": "^4.0" 1286 | }, 1287 | "require-dev": { 1288 | "phpunit/phpunit": "^9.0" 1289 | }, 1290 | "type": "library", 1291 | "extra": { 1292 | "branch-alias": { 1293 | "dev-master": "4.0-dev" 1294 | } 1295 | }, 1296 | "autoload": { 1297 | "classmap": [ 1298 | "src/" 1299 | ] 1300 | }, 1301 | "notification-url": "https://packagist.org/downloads/", 1302 | "license": [ 1303 | "BSD-3-Clause" 1304 | ], 1305 | "authors": [ 1306 | { 1307 | "name": "Sebastian Bergmann", 1308 | "email": "sebastian@phpunit.de" 1309 | }, 1310 | { 1311 | "name": "Jeff Welch", 1312 | "email": "whatthejeff@gmail.com" 1313 | }, 1314 | { 1315 | "name": "Volker Dusch", 1316 | "email": "github@wallbash.com" 1317 | }, 1318 | { 1319 | "name": "Bernhard Schussek", 1320 | "email": "bschussek@2bepublished.at" 1321 | } 1322 | ], 1323 | "description": "Provides the functionality to compare PHP values for equality", 1324 | "homepage": "https://github.com/sebastianbergmann/comparator", 1325 | "keywords": [ 1326 | "comparator", 1327 | "compare", 1328 | "equality" 1329 | ], 1330 | "time": "2020-06-26T12:05:46+00:00" 1331 | }, 1332 | { 1333 | "name": "sebastian/complexity", 1334 | "version": "2.0.0", 1335 | "source": { 1336 | "type": "git", 1337 | "url": "https://github.com/sebastianbergmann/complexity.git", 1338 | "reference": "33fcd6a26656c6546f70871244ecba4b4dced097" 1339 | }, 1340 | "dist": { 1341 | "type": "zip", 1342 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/33fcd6a26656c6546f70871244ecba4b4dced097", 1343 | "reference": "33fcd6a26656c6546f70871244ecba4b4dced097", 1344 | "shasum": "" 1345 | }, 1346 | "require": { 1347 | "nikic/php-parser": "^4.7", 1348 | "php": "^7.3 || ^8.0" 1349 | }, 1350 | "require-dev": { 1351 | "phpunit/phpunit": "^9.2" 1352 | }, 1353 | "type": "library", 1354 | "extra": { 1355 | "branch-alias": { 1356 | "dev-master": "2.0-dev" 1357 | } 1358 | }, 1359 | "autoload": { 1360 | "classmap": [ 1361 | "src/" 1362 | ] 1363 | }, 1364 | "notification-url": "https://packagist.org/downloads/", 1365 | "license": [ 1366 | "BSD-3-Clause" 1367 | ], 1368 | "authors": [ 1369 | { 1370 | "name": "Sebastian Bergmann", 1371 | "email": "sebastian@phpunit.de", 1372 | "role": "lead" 1373 | } 1374 | ], 1375 | "description": "Library for calculating the complexity of PHP code units", 1376 | "homepage": "https://github.com/sebastianbergmann/complexity", 1377 | "time": "2020-07-25T14:01:34+00:00" 1378 | }, 1379 | { 1380 | "name": "sebastian/diff", 1381 | "version": "4.0.2", 1382 | "source": { 1383 | "type": "git", 1384 | "url": "https://github.com/sebastianbergmann/diff.git", 1385 | "reference": "1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113" 1386 | }, 1387 | "dist": { 1388 | "type": "zip", 1389 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113", 1390 | "reference": "1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113", 1391 | "shasum": "" 1392 | }, 1393 | "require": { 1394 | "php": "^7.3 || ^8.0" 1395 | }, 1396 | "require-dev": { 1397 | "phpunit/phpunit": "^9.0", 1398 | "symfony/process": "^4.2 || ^5" 1399 | }, 1400 | "type": "library", 1401 | "extra": { 1402 | "branch-alias": { 1403 | "dev-master": "4.0-dev" 1404 | } 1405 | }, 1406 | "autoload": { 1407 | "classmap": [ 1408 | "src/" 1409 | ] 1410 | }, 1411 | "notification-url": "https://packagist.org/downloads/", 1412 | "license": [ 1413 | "BSD-3-Clause" 1414 | ], 1415 | "authors": [ 1416 | { 1417 | "name": "Sebastian Bergmann", 1418 | "email": "sebastian@phpunit.de" 1419 | }, 1420 | { 1421 | "name": "Kore Nordmann", 1422 | "email": "mail@kore-nordmann.de" 1423 | } 1424 | ], 1425 | "description": "Diff implementation", 1426 | "homepage": "https://github.com/sebastianbergmann/diff", 1427 | "keywords": [ 1428 | "diff", 1429 | "udiff", 1430 | "unidiff", 1431 | "unified diff" 1432 | ], 1433 | "time": "2020-06-30T04:46:02+00:00" 1434 | }, 1435 | { 1436 | "name": "sebastian/environment", 1437 | "version": "5.1.2", 1438 | "source": { 1439 | "type": "git", 1440 | "url": "https://github.com/sebastianbergmann/environment.git", 1441 | "reference": "0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2" 1442 | }, 1443 | "dist": { 1444 | "type": "zip", 1445 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2", 1446 | "reference": "0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2", 1447 | "shasum": "" 1448 | }, 1449 | "require": { 1450 | "php": "^7.3 || ^8.0" 1451 | }, 1452 | "require-dev": { 1453 | "phpunit/phpunit": "^9.0" 1454 | }, 1455 | "suggest": { 1456 | "ext-posix": "*" 1457 | }, 1458 | "type": "library", 1459 | "extra": { 1460 | "branch-alias": { 1461 | "dev-master": "5.0-dev" 1462 | } 1463 | }, 1464 | "autoload": { 1465 | "classmap": [ 1466 | "src/" 1467 | ] 1468 | }, 1469 | "notification-url": "https://packagist.org/downloads/", 1470 | "license": [ 1471 | "BSD-3-Clause" 1472 | ], 1473 | "authors": [ 1474 | { 1475 | "name": "Sebastian Bergmann", 1476 | "email": "sebastian@phpunit.de" 1477 | } 1478 | ], 1479 | "description": "Provides functionality to handle HHVM/PHP environments", 1480 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1481 | "keywords": [ 1482 | "Xdebug", 1483 | "environment", 1484 | "hhvm" 1485 | ], 1486 | "time": "2020-06-26T12:07:24+00:00" 1487 | }, 1488 | { 1489 | "name": "sebastian/exporter", 1490 | "version": "4.0.2", 1491 | "source": { 1492 | "type": "git", 1493 | "url": "https://github.com/sebastianbergmann/exporter.git", 1494 | "reference": "571d721db4aec847a0e59690b954af33ebf9f023" 1495 | }, 1496 | "dist": { 1497 | "type": "zip", 1498 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/571d721db4aec847a0e59690b954af33ebf9f023", 1499 | "reference": "571d721db4aec847a0e59690b954af33ebf9f023", 1500 | "shasum": "" 1501 | }, 1502 | "require": { 1503 | "php": "^7.3 || ^8.0", 1504 | "sebastian/recursion-context": "^4.0" 1505 | }, 1506 | "require-dev": { 1507 | "ext-mbstring": "*", 1508 | "phpunit/phpunit": "^9.2" 1509 | }, 1510 | "type": "library", 1511 | "extra": { 1512 | "branch-alias": { 1513 | "dev-master": "4.0-dev" 1514 | } 1515 | }, 1516 | "autoload": { 1517 | "classmap": [ 1518 | "src/" 1519 | ] 1520 | }, 1521 | "notification-url": "https://packagist.org/downloads/", 1522 | "license": [ 1523 | "BSD-3-Clause" 1524 | ], 1525 | "authors": [ 1526 | { 1527 | "name": "Sebastian Bergmann", 1528 | "email": "sebastian@phpunit.de" 1529 | }, 1530 | { 1531 | "name": "Jeff Welch", 1532 | "email": "whatthejeff@gmail.com" 1533 | }, 1534 | { 1535 | "name": "Volker Dusch", 1536 | "email": "github@wallbash.com" 1537 | }, 1538 | { 1539 | "name": "Adam Harvey", 1540 | "email": "aharvey@php.net" 1541 | }, 1542 | { 1543 | "name": "Bernhard Schussek", 1544 | "email": "bschussek@gmail.com" 1545 | } 1546 | ], 1547 | "description": "Provides the functionality to export PHP variables for visualization", 1548 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1549 | "keywords": [ 1550 | "export", 1551 | "exporter" 1552 | ], 1553 | "time": "2020-06-26T12:08:55+00:00" 1554 | }, 1555 | { 1556 | "name": "sebastian/global-state", 1557 | "version": "5.0.0", 1558 | "source": { 1559 | "type": "git", 1560 | "url": "https://github.com/sebastianbergmann/global-state.git", 1561 | "reference": "22ae663c951bdc39da96603edc3239ed3a299097" 1562 | }, 1563 | "dist": { 1564 | "type": "zip", 1565 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/22ae663c951bdc39da96603edc3239ed3a299097", 1566 | "reference": "22ae663c951bdc39da96603edc3239ed3a299097", 1567 | "shasum": "" 1568 | }, 1569 | "require": { 1570 | "php": "^7.3 || ^8.0", 1571 | "sebastian/object-reflector": "^2.0", 1572 | "sebastian/recursion-context": "^4.0" 1573 | }, 1574 | "require-dev": { 1575 | "ext-dom": "*", 1576 | "phpunit/phpunit": "^9.3" 1577 | }, 1578 | "suggest": { 1579 | "ext-uopz": "*" 1580 | }, 1581 | "type": "library", 1582 | "extra": { 1583 | "branch-alias": { 1584 | "dev-master": "5.0-dev" 1585 | } 1586 | }, 1587 | "autoload": { 1588 | "classmap": [ 1589 | "src/" 1590 | ] 1591 | }, 1592 | "notification-url": "https://packagist.org/downloads/", 1593 | "license": [ 1594 | "BSD-3-Clause" 1595 | ], 1596 | "authors": [ 1597 | { 1598 | "name": "Sebastian Bergmann", 1599 | "email": "sebastian@phpunit.de" 1600 | } 1601 | ], 1602 | "description": "Snapshotting of global state", 1603 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1604 | "keywords": [ 1605 | "global state" 1606 | ], 1607 | "time": "2020-08-07T04:09:03+00:00" 1608 | }, 1609 | { 1610 | "name": "sebastian/lines-of-code", 1611 | "version": "1.0.0", 1612 | "source": { 1613 | "type": "git", 1614 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 1615 | "reference": "e02bf626f404b5daec382a7b8a6a4456e49017e5" 1616 | }, 1617 | "dist": { 1618 | "type": "zip", 1619 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e02bf626f404b5daec382a7b8a6a4456e49017e5", 1620 | "reference": "e02bf626f404b5daec382a7b8a6a4456e49017e5", 1621 | "shasum": "" 1622 | }, 1623 | "require": { 1624 | "nikic/php-parser": "^4.6", 1625 | "php": "^7.3 || ^8.0" 1626 | }, 1627 | "require-dev": { 1628 | "phpunit/phpunit": "^9.2" 1629 | }, 1630 | "type": "library", 1631 | "extra": { 1632 | "branch-alias": { 1633 | "dev-master": "1.0-dev" 1634 | } 1635 | }, 1636 | "autoload": { 1637 | "classmap": [ 1638 | "src/" 1639 | ] 1640 | }, 1641 | "notification-url": "https://packagist.org/downloads/", 1642 | "license": [ 1643 | "BSD-3-Clause" 1644 | ], 1645 | "authors": [ 1646 | { 1647 | "name": "Sebastian Bergmann", 1648 | "email": "sebastian@phpunit.de", 1649 | "role": "lead" 1650 | } 1651 | ], 1652 | "description": "Library for counting the lines of code in PHP source code", 1653 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 1654 | "time": "2020-07-22T18:33:42+00:00" 1655 | }, 1656 | { 1657 | "name": "sebastian/object-enumerator", 1658 | "version": "4.0.2", 1659 | "source": { 1660 | "type": "git", 1661 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1662 | "reference": "074fed2d0a6d08e1677dd8ce9d32aecb384917b8" 1663 | }, 1664 | "dist": { 1665 | "type": "zip", 1666 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/074fed2d0a6d08e1677dd8ce9d32aecb384917b8", 1667 | "reference": "074fed2d0a6d08e1677dd8ce9d32aecb384917b8", 1668 | "shasum": "" 1669 | }, 1670 | "require": { 1671 | "php": "^7.3 || ^8.0", 1672 | "sebastian/object-reflector": "^2.0", 1673 | "sebastian/recursion-context": "^4.0" 1674 | }, 1675 | "require-dev": { 1676 | "phpunit/phpunit": "^9.0" 1677 | }, 1678 | "type": "library", 1679 | "extra": { 1680 | "branch-alias": { 1681 | "dev-master": "4.0-dev" 1682 | } 1683 | }, 1684 | "autoload": { 1685 | "classmap": [ 1686 | "src/" 1687 | ] 1688 | }, 1689 | "notification-url": "https://packagist.org/downloads/", 1690 | "license": [ 1691 | "BSD-3-Clause" 1692 | ], 1693 | "authors": [ 1694 | { 1695 | "name": "Sebastian Bergmann", 1696 | "email": "sebastian@phpunit.de" 1697 | } 1698 | ], 1699 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1700 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1701 | "time": "2020-06-26T12:11:32+00:00" 1702 | }, 1703 | { 1704 | "name": "sebastian/object-reflector", 1705 | "version": "2.0.2", 1706 | "source": { 1707 | "type": "git", 1708 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1709 | "reference": "127a46f6b057441b201253526f81d5406d6c7840" 1710 | }, 1711 | "dist": { 1712 | "type": "zip", 1713 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/127a46f6b057441b201253526f81d5406d6c7840", 1714 | "reference": "127a46f6b057441b201253526f81d5406d6c7840", 1715 | "shasum": "" 1716 | }, 1717 | "require": { 1718 | "php": "^7.3 || ^8.0" 1719 | }, 1720 | "require-dev": { 1721 | "phpunit/phpunit": "^9.0" 1722 | }, 1723 | "type": "library", 1724 | "extra": { 1725 | "branch-alias": { 1726 | "dev-master": "2.0-dev" 1727 | } 1728 | }, 1729 | "autoload": { 1730 | "classmap": [ 1731 | "src/" 1732 | ] 1733 | }, 1734 | "notification-url": "https://packagist.org/downloads/", 1735 | "license": [ 1736 | "BSD-3-Clause" 1737 | ], 1738 | "authors": [ 1739 | { 1740 | "name": "Sebastian Bergmann", 1741 | "email": "sebastian@phpunit.de" 1742 | } 1743 | ], 1744 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1745 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1746 | "time": "2020-06-26T12:12:55+00:00" 1747 | }, 1748 | { 1749 | "name": "sebastian/recursion-context", 1750 | "version": "4.0.2", 1751 | "source": { 1752 | "type": "git", 1753 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1754 | "reference": "062231bf61d2b9448c4fa5a7643b5e1829c11d63" 1755 | }, 1756 | "dist": { 1757 | "type": "zip", 1758 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/062231bf61d2b9448c4fa5a7643b5e1829c11d63", 1759 | "reference": "062231bf61d2b9448c4fa5a7643b5e1829c11d63", 1760 | "shasum": "" 1761 | }, 1762 | "require": { 1763 | "php": "^7.3 || ^8.0" 1764 | }, 1765 | "require-dev": { 1766 | "phpunit/phpunit": "^9.0" 1767 | }, 1768 | "type": "library", 1769 | "extra": { 1770 | "branch-alias": { 1771 | "dev-master": "4.0-dev" 1772 | } 1773 | }, 1774 | "autoload": { 1775 | "classmap": [ 1776 | "src/" 1777 | ] 1778 | }, 1779 | "notification-url": "https://packagist.org/downloads/", 1780 | "license": [ 1781 | "BSD-3-Clause" 1782 | ], 1783 | "authors": [ 1784 | { 1785 | "name": "Sebastian Bergmann", 1786 | "email": "sebastian@phpunit.de" 1787 | }, 1788 | { 1789 | "name": "Jeff Welch", 1790 | "email": "whatthejeff@gmail.com" 1791 | }, 1792 | { 1793 | "name": "Adam Harvey", 1794 | "email": "aharvey@php.net" 1795 | } 1796 | ], 1797 | "description": "Provides functionality to recursively process PHP variables", 1798 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1799 | "time": "2020-06-26T12:14:17+00:00" 1800 | }, 1801 | { 1802 | "name": "sebastian/resource-operations", 1803 | "version": "3.0.2", 1804 | "source": { 1805 | "type": "git", 1806 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1807 | "reference": "0653718a5a629b065e91f774595267f8dc32e213" 1808 | }, 1809 | "dist": { 1810 | "type": "zip", 1811 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0653718a5a629b065e91f774595267f8dc32e213", 1812 | "reference": "0653718a5a629b065e91f774595267f8dc32e213", 1813 | "shasum": "" 1814 | }, 1815 | "require": { 1816 | "php": "^7.3 || ^8.0" 1817 | }, 1818 | "require-dev": { 1819 | "phpunit/phpunit": "^9.0" 1820 | }, 1821 | "type": "library", 1822 | "extra": { 1823 | "branch-alias": { 1824 | "dev-master": "3.0-dev" 1825 | } 1826 | }, 1827 | "autoload": { 1828 | "classmap": [ 1829 | "src/" 1830 | ] 1831 | }, 1832 | "notification-url": "https://packagist.org/downloads/", 1833 | "license": [ 1834 | "BSD-3-Clause" 1835 | ], 1836 | "authors": [ 1837 | { 1838 | "name": "Sebastian Bergmann", 1839 | "email": "sebastian@phpunit.de" 1840 | } 1841 | ], 1842 | "description": "Provides a list of PHP built-in functions that operate on resources", 1843 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1844 | "time": "2020-06-26T12:16:22+00:00" 1845 | }, 1846 | { 1847 | "name": "sebastian/type", 1848 | "version": "2.2.1", 1849 | "source": { 1850 | "type": "git", 1851 | "url": "https://github.com/sebastianbergmann/type.git", 1852 | "reference": "86991e2b33446cd96e648c18bcdb1e95afb2c05a" 1853 | }, 1854 | "dist": { 1855 | "type": "zip", 1856 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/86991e2b33446cd96e648c18bcdb1e95afb2c05a", 1857 | "reference": "86991e2b33446cd96e648c18bcdb1e95afb2c05a", 1858 | "shasum": "" 1859 | }, 1860 | "require": { 1861 | "php": "^7.3 || ^8.0" 1862 | }, 1863 | "require-dev": { 1864 | "phpunit/phpunit": "^9.2" 1865 | }, 1866 | "type": "library", 1867 | "extra": { 1868 | "branch-alias": { 1869 | "dev-master": "2.2-dev" 1870 | } 1871 | }, 1872 | "autoload": { 1873 | "classmap": [ 1874 | "src/" 1875 | ] 1876 | }, 1877 | "notification-url": "https://packagist.org/downloads/", 1878 | "license": [ 1879 | "BSD-3-Clause" 1880 | ], 1881 | "authors": [ 1882 | { 1883 | "name": "Sebastian Bergmann", 1884 | "email": "sebastian@phpunit.de", 1885 | "role": "lead" 1886 | } 1887 | ], 1888 | "description": "Collection of value objects that represent the types of the PHP type system", 1889 | "homepage": "https://github.com/sebastianbergmann/type", 1890 | "time": "2020-07-05T08:31:53+00:00" 1891 | }, 1892 | { 1893 | "name": "sebastian/version", 1894 | "version": "3.0.1", 1895 | "source": { 1896 | "type": "git", 1897 | "url": "https://github.com/sebastianbergmann/version.git", 1898 | "reference": "626586115d0ed31cb71483be55beb759b5af5a3c" 1899 | }, 1900 | "dist": { 1901 | "type": "zip", 1902 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/626586115d0ed31cb71483be55beb759b5af5a3c", 1903 | "reference": "626586115d0ed31cb71483be55beb759b5af5a3c", 1904 | "shasum": "" 1905 | }, 1906 | "require": { 1907 | "php": "^7.3 || ^8.0" 1908 | }, 1909 | "type": "library", 1910 | "extra": { 1911 | "branch-alias": { 1912 | "dev-master": "3.0-dev" 1913 | } 1914 | }, 1915 | "autoload": { 1916 | "classmap": [ 1917 | "src/" 1918 | ] 1919 | }, 1920 | "notification-url": "https://packagist.org/downloads/", 1921 | "license": [ 1922 | "BSD-3-Clause" 1923 | ], 1924 | "authors": [ 1925 | { 1926 | "name": "Sebastian Bergmann", 1927 | "email": "sebastian@phpunit.de", 1928 | "role": "lead" 1929 | } 1930 | ], 1931 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1932 | "homepage": "https://github.com/sebastianbergmann/version", 1933 | "time": "2020-06-26T12:18:43+00:00" 1934 | }, 1935 | { 1936 | "name": "symfony/polyfill-ctype", 1937 | "version": "v1.18.1", 1938 | "source": { 1939 | "type": "git", 1940 | "url": "https://github.com/symfony/polyfill-ctype.git", 1941 | "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" 1942 | }, 1943 | "dist": { 1944 | "type": "zip", 1945 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", 1946 | "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", 1947 | "shasum": "" 1948 | }, 1949 | "require": { 1950 | "php": ">=5.3.3" 1951 | }, 1952 | "suggest": { 1953 | "ext-ctype": "For best performance" 1954 | }, 1955 | "type": "library", 1956 | "extra": { 1957 | "branch-alias": { 1958 | "dev-master": "1.18-dev" 1959 | }, 1960 | "thanks": { 1961 | "name": "symfony/polyfill", 1962 | "url": "https://github.com/symfony/polyfill" 1963 | } 1964 | }, 1965 | "autoload": { 1966 | "psr-4": { 1967 | "Symfony\\Polyfill\\Ctype\\": "" 1968 | }, 1969 | "files": [ 1970 | "bootstrap.php" 1971 | ] 1972 | }, 1973 | "notification-url": "https://packagist.org/downloads/", 1974 | "license": [ 1975 | "MIT" 1976 | ], 1977 | "authors": [ 1978 | { 1979 | "name": "Gert de Pagter", 1980 | "email": "BackEndTea@gmail.com" 1981 | }, 1982 | { 1983 | "name": "Symfony Community", 1984 | "homepage": "https://symfony.com/contributors" 1985 | } 1986 | ], 1987 | "description": "Symfony polyfill for ctype functions", 1988 | "homepage": "https://symfony.com", 1989 | "keywords": [ 1990 | "compatibility", 1991 | "ctype", 1992 | "polyfill", 1993 | "portable" 1994 | ], 1995 | "time": "2020-07-14T12:35:20+00:00" 1996 | }, 1997 | { 1998 | "name": "theseer/tokenizer", 1999 | "version": "1.2.0", 2000 | "source": { 2001 | "type": "git", 2002 | "url": "https://github.com/theseer/tokenizer.git", 2003 | "reference": "75a63c33a8577608444246075ea0af0d052e452a" 2004 | }, 2005 | "dist": { 2006 | "type": "zip", 2007 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", 2008 | "reference": "75a63c33a8577608444246075ea0af0d052e452a", 2009 | "shasum": "" 2010 | }, 2011 | "require": { 2012 | "ext-dom": "*", 2013 | "ext-tokenizer": "*", 2014 | "ext-xmlwriter": "*", 2015 | "php": "^7.2 || ^8.0" 2016 | }, 2017 | "type": "library", 2018 | "autoload": { 2019 | "classmap": [ 2020 | "src/" 2021 | ] 2022 | }, 2023 | "notification-url": "https://packagist.org/downloads/", 2024 | "license": [ 2025 | "BSD-3-Clause" 2026 | ], 2027 | "authors": [ 2028 | { 2029 | "name": "Arne Blankerts", 2030 | "email": "arne@blankerts.de", 2031 | "role": "Developer" 2032 | } 2033 | ], 2034 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 2035 | "time": "2020-07-12T23:59:07+00:00" 2036 | }, 2037 | { 2038 | "name": "webmozart/assert", 2039 | "version": "1.9.1", 2040 | "source": { 2041 | "type": "git", 2042 | "url": "https://github.com/webmozart/assert.git", 2043 | "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" 2044 | }, 2045 | "dist": { 2046 | "type": "zip", 2047 | "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", 2048 | "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", 2049 | "shasum": "" 2050 | }, 2051 | "require": { 2052 | "php": "^5.3.3 || ^7.0 || ^8.0", 2053 | "symfony/polyfill-ctype": "^1.8" 2054 | }, 2055 | "conflict": { 2056 | "phpstan/phpstan": "<0.12.20", 2057 | "vimeo/psalm": "<3.9.1" 2058 | }, 2059 | "require-dev": { 2060 | "phpunit/phpunit": "^4.8.36 || ^7.5.13" 2061 | }, 2062 | "type": "library", 2063 | "autoload": { 2064 | "psr-4": { 2065 | "Webmozart\\Assert\\": "src/" 2066 | } 2067 | }, 2068 | "notification-url": "https://packagist.org/downloads/", 2069 | "license": [ 2070 | "MIT" 2071 | ], 2072 | "authors": [ 2073 | { 2074 | "name": "Bernhard Schussek", 2075 | "email": "bschussek@gmail.com" 2076 | } 2077 | ], 2078 | "description": "Assertions to validate method input/output with nice error messages.", 2079 | "keywords": [ 2080 | "assert", 2081 | "check", 2082 | "validate" 2083 | ], 2084 | "time": "2020-07-08T17:02:28+00:00" 2085 | } 2086 | ], 2087 | "aliases": [], 2088 | "minimum-stability": "stable", 2089 | "stability-flags": [], 2090 | "prefer-stable": false, 2091 | "prefer-lowest": false, 2092 | "platform": { 2093 | "php": "^7.4", 2094 | "ext-simplexml": "^7.4" 2095 | }, 2096 | "platform-dev": [] 2097 | } 2098 | --------------------------------------------------------------------------------