1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Rules\Exceptions;
4:
5: use Nette\Utils\Strings;
6: use PHPStan\Analyser\Scope;
7: use PHPStan\Reflection\ReflectionProvider;
8: use function count;
9:
10: /**
11: * @api
12: */
13: final class DefaultExceptionTypeResolver implements ExceptionTypeResolver
14: {
15:
16: /**
17: * @param string[] $uncheckedExceptionRegexes
18: * @param string[] $uncheckedExceptionClasses
19: * @param string[] $checkedExceptionRegexes
20: * @param string[] $checkedExceptionClasses
21: */
22: public function __construct(
23: private ReflectionProvider $reflectionProvider,
24: private array $uncheckedExceptionRegexes,
25: private array $uncheckedExceptionClasses,
26: private array $checkedExceptionRegexes,
27: private array $checkedExceptionClasses,
28: )
29: {
30: }
31:
32: public function isCheckedException(string $className, Scope $scope): bool
33: {
34: foreach ($this->uncheckedExceptionRegexes as $regex) {
35: if (Strings::match($className, $regex) !== null) {
36: return false;
37: }
38: }
39:
40: foreach ($this->uncheckedExceptionClasses as $uncheckedExceptionClass) {
41: if ($className === $uncheckedExceptionClass) {
42: return false;
43: }
44: }
45:
46: if (!$this->reflectionProvider->hasClass($className)) {
47: return $this->isCheckedExceptionInternal($className);
48: }
49:
50: $classReflection = $this->reflectionProvider->getClass($className);
51: foreach ($this->uncheckedExceptionClasses as $uncheckedExceptionClass) {
52: if ($classReflection->getName() === $uncheckedExceptionClass) {
53: return false;
54: }
55:
56: if (!$classReflection->isSubclassOf($uncheckedExceptionClass)) {
57: continue;
58: }
59:
60: return false;
61: }
62:
63: return $this->isCheckedExceptionInternal($className);
64: }
65:
66: private function isCheckedExceptionInternal(string $className): bool
67: {
68: foreach ($this->checkedExceptionRegexes as $regex) {
69: if (Strings::match($className, $regex) !== null) {
70: return true;
71: }
72: }
73:
74: foreach ($this->checkedExceptionClasses as $checkedExceptionClass) {
75: if ($className === $checkedExceptionClass) {
76: return true;
77: }
78: }
79:
80: if (!$this->reflectionProvider->hasClass($className)) {
81: return count($this->checkedExceptionRegexes) === 0 && count($this->checkedExceptionClasses) === 0;
82: }
83:
84: $classReflection = $this->reflectionProvider->getClass($className);
85: foreach ($this->checkedExceptionClasses as $checkedExceptionClass) {
86: if ($classReflection->getName() === $checkedExceptionClass) {
87: return true;
88: }
89:
90: if (!$classReflection->isSubclassOf($checkedExceptionClass)) {
91: continue;
92: }
93:
94: return true;
95: }
96:
97: return count($this->checkedExceptionRegexes) === 0 && count($this->checkedExceptionClasses) === 0;
98: }
99:
100: }
101: