|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace HexMakina\Crudites; |
|
4
|
|
|
|
|
5
|
|
|
use HexMakina\BlackBox\Database\QueryInterface; |
|
6
|
|
|
|
|
7
|
|
|
/** |
|
8
|
|
|
* The CruditesExceptionFactory class make CruditesExcepton, a custom exception class that extends the built-in \Exception class in PHP. |
|
9
|
|
|
* The purpose of this class is to provide a more descriptive and meaningful error message when an exception is thrown in the CRUD operations. |
|
10
|
|
|
* It takes a message, an optional error code, and an optional previous exception as its parameters. |
|
11
|
|
|
* It also provides a method called transcript() which extracts error information from a QueryInterface object |
|
12
|
|
|
* and sets the error message to a more readable and understandable format. |
|
13
|
|
|
* |
|
14
|
|
|
* This is useful when dealing with database errors that may not be very clear or descriptive on their own. |
|
15
|
|
|
*/ |
|
16
|
|
|
|
|
17
|
|
|
class CruditesExceptionFactory |
|
18
|
|
|
{ |
|
19
|
|
|
public static function make(QueryInterface $query, \PDOException $exception = null): CruditesException |
|
20
|
|
|
{ |
|
21
|
|
|
$errorInfo = null; |
|
22
|
|
|
|
|
23
|
|
|
if (!$query->isSuccess()) |
|
24
|
|
|
$errorInfo = $query->errorInfo(); |
|
|
|
|
|
|
25
|
|
|
elseif ($exception !== null) |
|
26
|
|
|
$errorInfo = $exception->errorInfo; |
|
27
|
|
|
|
|
28
|
|
|
if (!is_array($errorInfo)) |
|
29
|
|
|
return new CruditesException('ERROR_INFO_UNAVAILABLE', 0, $exception); |
|
30
|
|
|
|
|
31
|
|
|
list($message, $code) = self::transcript($errorInfo); |
|
32
|
|
|
|
|
33
|
|
|
// TODO: losing the parsing work from transcript. IMPROVE |
|
34
|
|
|
if (is_array($message)) |
|
35
|
|
|
$message = array_shift($message); |
|
36
|
|
|
|
|
37
|
|
|
return new CruditesException($message, $code, null); // exception could reveal database credentials |
|
38
|
|
|
} |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* Attempts to humanize database errors |
|
42
|
|
|
* @param ?array errorInfo, consists of at least the following fields: |
|
|
|
|
|
|
43
|
|
|
* 0 SQLSTATE error code (a five characters alphanumeric identifier defined in the ANSI SQL standard). |
|
44
|
|
|
* 1 Driver-specific error code. |
|
45
|
|
|
* 2 Driver-specific error message. |
|
46
|
|
|
* |
|
47
|
|
|
* |
|
48
|
|
|
* @return array consists of the following fields: |
|
49
|
|
|
* 0 the transcripted error message. |
|
50
|
|
|
* 1 the error code |
|
51
|
|
|
* |
|
52
|
|
|
*/ |
|
53
|
|
|
private static function transcript(array $errorInfo): array |
|
54
|
|
|
{ |
|
55
|
|
|
list($state, $code, $message) = $errorInfo; |
|
56
|
|
|
$functs = [ |
|
57
|
|
|
// violation: column cannot be null |
|
58
|
|
|
1048 => function ($message) { |
|
59
|
|
|
preg_match("#Column '(.+)' cannot be null#", $message, $m); |
|
60
|
|
|
return ['FIELD_REQUIRED', $m[1]]; |
|
61
|
|
|
}, |
|
62
|
|
|
|
|
63
|
|
|
1054 => function ($message) { |
|
64
|
|
|
return ['COLUMN_DOES_NOT_EXIST', $message]; |
|
65
|
|
|
}, |
|
66
|
|
|
|
|
67
|
|
|
// violation: duplicate key |
|
68
|
|
|
1062 => function ($message) { |
|
69
|
|
|
if (preg_match("#'([^']+)' for key '([^']+)'#", $message, $m)) { |
|
70
|
|
|
$entry = $m[1]; |
|
71
|
|
|
$key = $m[2]; |
|
72
|
|
|
return ["DUPLICATE_KEY:$key:$entry"]; |
|
73
|
|
|
} |
|
74
|
|
|
|
|
75
|
|
|
if (preg_match("#for key '[a-z]+\.(.+)'$#", $message, $m) !== 1) { |
|
76
|
|
|
preg_match("#for key '(.+)'$#", $message, $m); |
|
77
|
|
|
} |
|
78
|
|
|
|
|
79
|
|
|
return ["DUPLICATE_KEY:".$m[1]]; |
|
80
|
|
|
}, |
|
81
|
|
|
|
|
82
|
|
|
1064 => function ($message) { |
|
83
|
|
|
preg_match("#right syntax to use near '(.+)'#", $message, $m); |
|
84
|
|
|
return ['SYNTAX_ERROR', $m[1]]; |
|
85
|
|
|
}, |
|
86
|
|
|
|
|
87
|
|
|
1146 => function ($message) { |
|
88
|
|
|
return ['TABLE_DOES_NOT_EXIST', $message]; |
|
89
|
|
|
}, |
|
90
|
|
|
|
|
91
|
|
|
1264 => function ($message) { |
|
92
|
|
|
preg_match("#for column '(.+)'#", $message, $m); |
|
93
|
|
|
return ['VALUE_OUT_OF_RANGE', $m[1]]; |
|
94
|
|
|
}, |
|
95
|
|
|
|
|
96
|
|
|
1364 => function ($message) { |
|
97
|
|
|
return ['FIELD_REQUIRED', $message]; |
|
98
|
|
|
}, |
|
99
|
|
|
|
|
100
|
|
|
1451 => function ($message) { |
|
101
|
|
|
preg_match("#CONSTRAINT `(.+)` FOREIGN#", $message, $m); |
|
102
|
|
|
return ['RELATIONAL_INTEGRITY', $m[1]]; |
|
103
|
|
|
}, |
|
104
|
|
|
|
|
105
|
|
|
]; |
|
106
|
|
|
if (isset($functs[$code])) |
|
107
|
|
|
return [call_user_func($functs[$code], $message), $code]; |
|
108
|
|
|
|
|
109
|
|
|
return ['FUBAR #' . $state . '-' . $code . '-' . $message, $code]; |
|
110
|
|
|
} |
|
111
|
|
|
} |
|
112
|
|
|
|
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.