Skip to content

Commit adae554

Browse files
committed
refactor: the construction of the output from error helper function into it's own class ValetException.
Added: - Added the new `ValetException` class which extends the PHP native `Exception`, and holds various methods to help construct the `Exception` error messages. Changed: - Moved the construction of the output from the `error` helper function into the new `ValetException` class as `getError` public method. And refactored some of the code to make it simpler. This is will be the method that `error` helper function will use to obtain the error message. - Moved `getErrorTypeName` helper function into the new `ValetException` class with `private` visibility. - Moved the `constructTrace` loop from the `error` helper function into its own private method in `ValetException` class as `constructTrace`. And refactored some of the code to make it simpler. - Changed `error` helper function to get the constructed `ValetException` error message via `ValetException::getError` method and outputs this to the console. - Moved the `error` helper function calls from `GithubPackage::handleApiRateLimitExceededError` method into the `try-catch` block of the `GithubPackage::download` method. - Moved both `handleApiRateLimitExceededError` and `calculateTimeToGithubApiLimitReset` methods from `GithubPackage` class into the new `ValetException` class. So that all error logic handling is in one class. - Changed the name of `handleApiRateLimitExceededError` to `githubApiRateLimitExceededError` in the `ValetException` class, and added a return array. - Changed `handleApiRateLimitExceededError` call in the `GithubPackage::download` method to use the new `ValetException::githubApiRateLimitExceededError` method. It uses the array destructuring assignment to get the return array values and assign them to the respective variable.
1 parent 3be844c commit adae554

File tree

4 files changed

+134
-107
lines changed

4 files changed

+134
-107
lines changed

cli/Valet/Packages/GithubPackage.php

Lines changed: 8 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,14 @@ protected function download(string $githubApiUrl, string $filename, string $file
9999
$responseMsg = $responseBody->message;
100100

101101
if (str_contains($responseMsg, 'API rate limit exceeded')) {
102-
$this->handleApiRateLimitExceededError($responseHeaders, $responseMsg);
102+
[$ip, $rateLimit, $timeLeftToReset] = \ValetException::githubApiRateLimitExceededError($responseHeaders, $responseMsg);
103+
104+
// Print the error messages.
105+
error("\n\nThe GitHub API rate limit has been exceeded for your IP address ($ip). The rate limit is $rateLimit requests per hour.\n\n");
106+
107+
info("\nThe rate limit will reset in $timeLeftToReset.");
108+
109+
error("API rate limit exceeded", true);
103110
}
104111
else {
105112
$error = "Error Code: $errorCode\n";
@@ -261,66 +268,4 @@ protected function getVersionedFilename($filename, $assetName) {
261268
return $matches[0];
262269
}
263270
}
264-
265-
// TODO: Transfer all error handling to a new separate class.
266-
/**
267-
* If the GitHub API rate limit has exceeded, we need to handle it.
268-
*
269-
* The API rate limit is 60 requests per hour for unauthenticated requests.
270-
*
271-
* @param array $headers The headers from the response.
272-
* @param string $msg The error message from the response.
273-
*/
274-
private function handleApiRateLimitExceededError($headers, $msg) {
275-
// Get the rate limit.
276-
$rateLimit = $headers["X-RateLimit-Limit"][0];
277-
// Get the reset time (UTC epoch seconds).
278-
$resetTime = $headers["X-RateLimit-Reset"][0];
279-
280-
$timeLeftToReset = $this->calculateTimeToGithubApiLimitReset($resetTime);
281-
282-
// Get the IP address from the original error response message using regex.
283-
// The regex pattern matches an IPv4 address.
284-
// The pattern looks for 1 to 3 digits followed by a dot, repeated 3 times,
285-
// and then 1 to 3 digits.
286-
preg_match('/\b(?:\d{1,3}\.){3}\d{1,3}\b/', $msg, $matches);
287-
288-
// Assign the IP address to a variable if the regex matched, otherwise set it to "unknown".
289-
$ip = $matches[0] ?: "unknown";
290-
291-
// Print the error message.
292-
error("\n\nThe GitHub API rate limit has been exceeded for your IP address ($ip). The rate limit is $rateLimit requests per hour.\n\n");
293-
info("\nThe rate limit will reset in $timeLeftToReset.");
294-
error("API rate limit exceeded", true);
295-
}
296-
297-
/**
298-
* Calculate the time left to reset the GitHub API rate limit.
299-
*
300-
* @param string $resetTime The reset time in UTC epoch seconds.
301-
*
302-
* @return string The time left to reset the rate limit in a human-readable format.
303-
*/
304-
private function calculateTimeToGithubApiLimitReset($resetTime) {
305-
// Create new DateTime objects for the reset time and the current time.
306-
$reset_time = new \DateTime("@$resetTime");
307-
$current_time = new \DateTime("now");
308-
309-
// Get the difference between the 2 times.
310-
$timeDifference = $reset_time->diff($current_time);
311-
312-
// Get the difference in minutes and seconds.
313-
// The DateInterval object has many properties, including minutes and seconds,
314-
// which we can directly access.
315-
$mins = $timeDifference->i;
316-
$secs = $timeDifference->s;
317-
318-
// Format the minutes and seconds into a human-readable string.
319-
// If the minutes or seconds equals 1, we need to use the singular form
320-
// of "minute" or "second".
321-
$minsTxt = $mins === 1 ? "$mins minute" : "$mins minutes";
322-
$secsTxt = $secs === 1 ? "$secs second" : "$secs seconds";
323-
324-
return "$minsTxt and $secsTxt";
325-
}
326271
}

cli/Valet/ValetException.php

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
namespace Valet;
4+
5+
use Exception;
6+
7+
class ValetException extends Exception {
8+
/**
9+
* Construct and return the error message.
10+
*
11+
* @return string
12+
*/
13+
public function getError() {
14+
$errorMsg = $this->getMessage();
15+
$errorTypeName = $this->getErrorTypeName($this->getCode());
16+
$constructTrace = $this->constructTrace();
17+
18+
return "$errorTypeName: $errorMsg\n\n$constructTrace";
19+
}
20+
21+
/**
22+
* Get the error type name.
23+
* Eg.: Inputs error code `0`, outputs error name `"FATAL"`
24+
*
25+
* @param mixed $code The numeric error type/code
26+
* @return string The error type name
27+
*/
28+
private function getErrorTypeName($code) {
29+
return $code == 0 ? "FATAL" : array_search($code, get_defined_constants(true)['Core']);
30+
}
31+
32+
/**
33+
* Construct a better-formatted error trace.
34+
*
35+
* @return string
36+
*/
37+
private function constructTrace() {
38+
$constructTrace = [];
39+
$count = 0;
40+
foreach ($this->getTrace() as $key => $value) {
41+
$count_num = $count++ . ") ";
42+
$class = $value["class"] ?? "";
43+
$type = $value["type"] ?? "";
44+
$func = $value["function"] ?? "";
45+
46+
$file_n_line = isset($value["file"]) ?
47+
" ------ " . $value["file"] . ":" . $value["line"] : "";
48+
49+
$constructTrace[] = $count_num . $class . $type . $func . $file_n_line;
50+
}
51+
return implode("\n", $constructTrace);
52+
}
53+
54+
/**
55+
* If the GitHub API rate limit has exceeded, we need to handle it.
56+
*
57+
* The API rate limit is 60 requests per hour for unauthenticated requests.
58+
*
59+
* @param array $headers The headers from the response.
60+
* @param string $responseMsg The error message from the response.
61+
*
62+
* @return array `[$ip, $rateLimit, $timeLeftToReset]` An array containing the IP address, rate limit, and time left to reset.
63+
*/
64+
public function githubApiRateLimitExceededError($headers, $responseMsg) {
65+
// Get the rate limit.
66+
$rateLimit = $headers["X-RateLimit-Limit"][0];
67+
// Get the reset time (UTC epoch seconds).
68+
$resetTime = $headers["X-RateLimit-Reset"][0];
69+
70+
$timeLeftToReset = $this->calculateTimeToGithubApiLimitReset($resetTime);
71+
72+
// Get the IP address from the original error response message using regex.
73+
// The regex pattern matches an IPv4 address.
74+
// The pattern looks for 1 to 3 digits followed by a dot, repeated 3 times,
75+
// and then 1 to 3 digits.
76+
preg_match('/\b(?:\d{1,3}\.){3}\d{1,3}\b/', $responseMsg, $matches);
77+
78+
// Assign the IP address to a variable if the regex matched, otherwise set it to "unknown".
79+
$ip = $matches[0] ?: "unknown";
80+
81+
return [$ip, $rateLimit, $timeLeftToReset];
82+
}
83+
84+
/**
85+
* Calculate the time left to reset the GitHub API rate limit.
86+
*
87+
* @param string $resetTime The reset time in UTC epoch seconds.
88+
*
89+
* @return string The time left to reset the rate limit in a human-readable format.
90+
*/
91+
private function calculateTimeToGithubApiLimitReset($resetTime) {
92+
// Create new DateTime objects for the reset time and the current time.
93+
$reset_time = new \DateTime("@$resetTime");
94+
$current_time = new \DateTime("now");
95+
96+
// Get the difference between the 2 times.
97+
$timeDifference = $reset_time->diff($current_time);
98+
99+
// Get the difference in minutes and seconds.
100+
// The DateInterval object has many properties, including minutes and seconds,
101+
// which we can directly access.
102+
$mins = $timeDifference->i;
103+
$secs = $timeDifference->s;
104+
105+
// Format the minutes and seconds into a human-readable string.
106+
// If the minutes or seconds equals 1, we need to use the singular form
107+
// of "minute" or "second".
108+
$minsTxt = $mins === 1 ? "$mins minute" : "$mins minutes";
109+
$secsTxt = $secs === 1 ? "$secs second" : "$secs seconds";
110+
111+
return "$minsTxt and $secsTxt";
112+
}
113+
}

cli/includes/facades.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,5 @@ class Share extends Facade {}
5555
class Site extends Facade {}
5656
class Upgrader extends Facade {}
5757
class Valet extends Facade {}
58+
class ValetException extends Facade {}
5859
class WinSW extends Facade {}

cli/includes/helpers.php

Lines changed: 12 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Valet;
44

5+
use Valet\ValetException;
6+
57
use Exception;
68
use Illuminate\Container\Container;
79
use RuntimeException;
@@ -66,49 +68,26 @@ function warning($output) {
6668
* Output errors to the console.
6769
*
6870
* @param string $output
69-
* @param boolean $exception
71+
* @param boolean $exception Optionally pass a boolean to indicate whether to throw an exception. If `true`, the error will be thrown as a `ValetException`. [default: `false`]
72+
*
73+
* @throws RuntimeException
74+
* @throws ValetException
75+
*
7076
* @return void
7177
*/
7278
function error(string $output, $exception = false) {
7379
if (isset($_ENV['APP_ENV']) && $_ENV['APP_ENV'] === 'testing') {
7480
throw new RuntimeException($output);
7581
}
7682
if ($exception === true) {
77-
// $errors = error_get_last();
78-
79-
// $outputTxt = getErrorTypeName($errors['type']) . ": "
80-
// . "$output\n"
81-
// . $errors['message']
82-
// . "\n{$errors['file']}:{$errors['line']}";
83-
// throw new \Exception($outputTxt);
84-
85-
86-
$errors = new Exception($output);
87-
88-
$errorCode = $errors->getCode();
89-
$errorMsg = $errors->getMessage();
90-
$errorTrace = $errors->getTrace();
91-
92-
$constructTrace = [];
93-
$count = 0;
94-
foreach ($errorTrace as $key => $value) {
95-
$count_num = $count++ . ") ";
96-
$class = isset($value["class"]) ? $value["class"] : "";
97-
$type = isset($value["type"]) ? $value["type"] : "";
98-
$func = isset($value["function"]) ? $value["function"] : "";
99-
100-
$file_n_line = isset($value["file"]) ?
101-
" ------ " . $value["file"] . ":" . $value["line"] : "";
102-
103-
$constructTrace[] = $count_num . $class . $type . $func . $file_n_line;
104-
}
105-
106-
$output = getErrorTypeName($errorCode) . ": $errorMsg\n\n" . implode("\n", $constructTrace);
83+
$errors = (new ValetException($output))->getError();
10784

10885
// Wait 1 microsecond, to make sure all output before the error call has reached
10986
// the terminal.
11087
usleep(1);
111-
(new ConsoleOutput())->getErrorOutput()->writeln("\n\n<error>$output</error>");
88+
89+
// Print the error message to the console.
90+
(new ConsoleOutput())->getErrorOutput()->writeln("\n\n<error>$errors</error>");
11291

11392
exit();
11493
}
@@ -117,17 +96,6 @@ function error(string $output, $exception = false) {
11796
}
11897
}
11998

120-
/**
121-
* Get the error type name.
122-
* Eg.: Inputs error code `0`, outputs error name `"FATAL"`
123-
*
124-
* @param mixed $code The numeric error type/code
125-
* @return string The error type name
126-
*/
127-
function getErrorTypeName($code) {
128-
return $code == 0 ? "FATAL" : array_search($code, get_defined_constants(true)['Core']);
129-
}
130-
13199
/**
132100
* Output the given text to the console.
133101
*
@@ -426,4 +394,4 @@ function str_contains_any($haystack, $needles) {
426394
}
427395
}
428396
return false;
429-
}
397+
}

0 commit comments

Comments
 (0)