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