33namespace Squirrel \Debug ;
44
55/**
6- * Debug functionality: create exception , sanitize data
6+ * Debug functionality: create exceptions , sanitize data and function arguments
77 */
8- class Debug
8+ final class Debug
99{
1010 /**
1111 * Create exception with correct backtrace while ignoring some classes/interfaces ($ignoreClasses)
@@ -21,24 +21,11 @@ public static function createException(
2121 string |array $ ignoreNamespaces = [],
2222 ?\Throwable $ previousException = null ,
2323 ): \Throwable {
24- if (\is_string ($ ignoreClasses )) {
25- $ ignoreClasses = [$ ignoreClasses ];
26- }
27-
28- if (\is_string ($ ignoreNamespaces )) {
29- $ ignoreNamespaces = [$ ignoreNamespaces ];
30- }
31-
32- $ removeEmptyStrings = function (string $ s ): bool {
33- if (\strlen ($ s ) === 0 ) {
34- return false ;
35- }
36-
37- return true ;
38- };
24+ $ ignoreClasses = self ::convertToArray ($ ignoreClasses );
25+ $ ignoreNamespaces = self ::convertToArray ($ ignoreNamespaces );
3926
40- $ ignoreClasses = \array_filter ($ ignoreClasses , $ removeEmptyStrings );
41- $ ignoreNamespaces = \array_filter ($ ignoreNamespaces , $ removeEmptyStrings );
27+ $ ignoreClasses = \array_filter ($ ignoreClasses , [Debug::class, ' isNotEmptyString ' ] );
28+ $ ignoreNamespaces = \array_filter ($ ignoreNamespaces , [Debug::class, ' isNotEmptyString ' ] );
4229
4330 // Get backtrace to find out where the query error originated
4431 $ backtraceList = \debug_backtrace (DEBUG_BACKTRACE_PROVIDE_OBJECT );
@@ -56,42 +43,14 @@ public static function createException(
5643
5744 $ lastInstance ??= $ backtrace ;
5845
59- $ classImplements = \class_implements ($ backtrace ['class ' ]);
60- $ classParents = \class_parents ($ backtrace ['class ' ]);
61-
62- // @codeCoverageIgnoreStart
63- if ($ classImplements === false ) {
64- $ classImplements = [];
65- }
66-
67- if ($ classParents === false ) {
68- $ classParents = [];
69- }
70- // @codeCoverageIgnoreEnd
71-
72- foreach ($ ignoreClasses as $ ignoreClass ) {
73- // Check if the class or interface we are looking for is implemented or used
74- // by the current backtrace class
75- if (
76- \in_array ($ ignoreClass , $ classImplements , true ) ||
77- \in_array ($ ignoreClass , $ classParents , true ) ||
78- $ ignoreClass === $ backtrace ['class ' ]
79- ) {
80- $ lastInstance = $ backtrace ;
81-
82- continue 2 ;
83- }
46+ if (self ::isIgnoredClass ($ backtrace ['class ' ], $ ignoreClasses )) {
47+ $ lastInstance = $ backtrace ;
48+ continue ;
8449 }
8550
86- foreach ($ ignoreNamespaces as $ ignoreNamespace ) {
87- // Check if the backtrace class starts with any ignored namespaces
88- if (
89- \str_starts_with ($ backtrace ['class ' ], $ ignoreNamespace )
90- ) {
91- $ lastInstance = $ backtrace ;
92-
93- continue 2 ;
94- }
51+ if (self ::isIgnoredNamespace ($ backtrace ['class ' ], $ ignoreNamespaces )) {
52+ $ lastInstance = $ backtrace ;
53+ continue ;
9554 }
9655
9756 // We reached the first non-ignored backtrace - we are at the top
@@ -106,26 +65,16 @@ public static function createException(
10665 $ parts = \explode ('\\' , $ lastInstance ['class ' ] ?? '' );
10766 $ shownClass = \array_pop ($ parts );
10867
109- $ classImplements = \class_implements ($ exceptionClass );
110- $ classParents = \class_parents ($ exceptionClass );
111-
112- // @codeCoverageIgnoreStart
113- if ($ classImplements === false ) {
114- $ classImplements = [];
115- }
116-
117- if ($ classParents === false ) {
118- $ classParents = [];
119- }
120- // @codeCoverageIgnoreEnd
121-
122- // Make sure the provided exception class inherits from Throwable, otherwise replace it with Exception
123- if (!\in_array (\Throwable::class, $ classImplements , true )) {
68+ // Make sure the provided exception class inherits from Throwable or replace it with Exception
69+ if (!\in_array (\Throwable::class, self ::getClassInterfaces ($ exceptionClass ), true )) {
12470 $ exceptionClass = \Exception::class;
12571 }
12672
12773 // If we have no OriginException child class, we assume the default Exception class constructor is used
128- if (!\in_array (OriginException::class, $ classParents , true ) && $ exceptionClass !== OriginException::class) {
74+ if (
75+ !\in_array (OriginException::class, self ::getClassParents ($ exceptionClass ), true )
76+ && $ exceptionClass !== OriginException::class
77+ ) {
12978 /**
13079 * @var \Throwable $exception At this point we know that $exceptionClass inherits from \Throwable for sure
13180 */
@@ -139,13 +88,12 @@ public static function createException(
13988 * @var OriginException $exception At this point we know that $exceptionClass inherits from OriginException for sure
14089 */
14190 $ exception = new $ exceptionClass (
142- $ shownClass . ($ lastInstance ['type ' ] ?? '' ) . ($ lastInstance ['function ' ] ?? '' ) .
143- '( ' . self ::sanitizeArguments ($ lastInstance ['args ' ] ?? []) . ') ' ,
144- $ lastInstance ['file ' ] ?? '' ,
145- $ lastInstance ['line ' ] ?? 0 ,
146- \str_replace ("\n" , ' ' , $ message ),
147- (isset ($ previousException ) ? $ previousException ->getCode () : 0 ),
148- $ previousException ,
91+ originCall: $ shownClass . ($ lastInstance ['type ' ] ?? '' ) . ($ lastInstance ['function ' ] ?? '' ) . '( ' . self ::sanitizeArguments ($ lastInstance ['args ' ] ?? []) . ') ' ,
92+ originFile: $ lastInstance ['file ' ] ?? '' ,
93+ originLine: $ lastInstance ['line ' ] ?? 0 ,
94+ message: \str_replace ("\n" , ' ' , $ message ),
95+ code: (isset ($ previousException ) ? $ previousException ->getCode () : 0 ),
96+ previous: $ previousException ,
14997 );
15098 }
15199
@@ -207,4 +155,106 @@ public static function sanitizeData(mixed $data): string
207155
208156 return '[ ' . \implode (', ' , $ result ) . '] ' ;
209157 }
158+
159+ private static function convertToArray (string |array $ list ): array
160+ {
161+ if (\is_string ($ list )) {
162+ $ list = [$ list ];
163+ }
164+
165+ return $ list ;
166+ }
167+
168+ private static function isNotEmptyString (string $ s ): bool
169+ {
170+ if (\strlen ($ s ) === 0 ) {
171+ return false ;
172+ }
173+
174+ return true ;
175+ }
176+
177+ /**
178+ * @param class-string $class
179+ * @return array<string, class-string>
180+ */
181+ private static function getClassParents (string $ class ): array
182+ {
183+ $ classParents = \class_parents ($ class );
184+
185+ // @codeCoverageIgnoreStart
186+ if ($ classParents === false ) {
187+ $ classParents = [];
188+ }
189+ // @codeCoverageIgnoreEnd
190+
191+ return $ classParents ;
192+ }
193+
194+ /**
195+ * @param class-string $class
196+ * @return array<string, string>
197+ */
198+ private static function getClassInterfaces (string $ class ): array
199+ {
200+ $ classImplements = \class_implements ($ class );
201+
202+ // @codeCoverageIgnoreStart
203+ if ($ classImplements === false ) {
204+ $ classImplements = [];
205+ }
206+ // @codeCoverageIgnoreEnd
207+
208+ return $ classImplements ;
209+ }
210+
211+ /**
212+ * @param class-string $class
213+ * @return array<string, string>
214+ */
215+ private static function getClasses (string $ class ): array
216+ {
217+ return \array_merge (self ::getClassInterfaces ($ class ), self ::getClassParents ($ class ));
218+ }
219+
220+ /**
221+ * @param class-string $backtraceClass
222+ * @param class-string[] $ignoreClasses
223+ */
224+ private static function isIgnoredClass (
225+ string $ backtraceClass ,
226+ array $ ignoreClasses ,
227+ ): bool {
228+ $ possibleClasses = self ::getClasses ($ backtraceClass );
229+
230+ foreach ($ ignoreClasses as $ ignoreClass ) {
231+ if (
232+ \in_array ($ ignoreClass , $ possibleClasses , true ) ||
233+ $ ignoreClass === $ backtraceClass
234+ ) {
235+ return true ;
236+ }
237+ }
238+
239+ return false ;
240+ }
241+
242+ /**
243+ * @param class-string $backtraceClass
244+ * @param string[] $ignoreNamespaces
245+ */
246+ private static function isIgnoredNamespace (
247+ string $ backtraceClass ,
248+ array $ ignoreNamespaces ,
249+ ): bool {
250+ foreach ($ ignoreNamespaces as $ ignoreNamespace ) {
251+ if (
252+ \str_starts_with ($ backtraceClass , $ ignoreNamespace )
253+ ) {
254+ return true ;
255+ }
256+ }
257+
258+ return false ;
259+ }
210260}
0 commit comments