CodeUnit.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of sebastian/code-unit.
  4. *
  5. * (c) Sebastian Bergmann <sebastian@phpunit.de>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace SebastianBergmann\CodeUnit;
  11. use function range;
  12. use function sprintf;
  13. use ReflectionClass;
  14. use ReflectionFunction;
  15. use ReflectionMethod;
  16. /**
  17. * @psalm-immutable
  18. */
  19. abstract class CodeUnit
  20. {
  21. /**
  22. * @var string
  23. */
  24. private $name;
  25. /**
  26. * @var string
  27. */
  28. private $sourceFileName;
  29. /**
  30. * @var array
  31. * @psalm-var list<int>
  32. */
  33. private $sourceLines;
  34. /**
  35. * @psalm-param class-string $className
  36. *
  37. * @throws InvalidCodeUnitException
  38. * @throws ReflectionException
  39. */
  40. public static function forClass(string $className): ClassUnit
  41. {
  42. self::ensureUserDefinedClass($className);
  43. $reflector = self::reflectorForClass($className);
  44. return new ClassUnit(
  45. $className,
  46. $reflector->getFileName(),
  47. range(
  48. $reflector->getStartLine(),
  49. $reflector->getEndLine()
  50. )
  51. );
  52. }
  53. /**
  54. * @psalm-param class-string $className
  55. *
  56. * @throws InvalidCodeUnitException
  57. * @throws ReflectionException
  58. */
  59. public static function forClassMethod(string $className, string $methodName): ClassMethodUnit
  60. {
  61. self::ensureUserDefinedClass($className);
  62. $reflector = self::reflectorForClassMethod($className, $methodName);
  63. return new ClassMethodUnit(
  64. $className . '::' . $methodName,
  65. $reflector->getFileName(),
  66. range(
  67. $reflector->getStartLine(),
  68. $reflector->getEndLine()
  69. )
  70. );
  71. }
  72. /**
  73. * @psalm-param class-string $interfaceName
  74. *
  75. * @throws InvalidCodeUnitException
  76. * @throws ReflectionException
  77. */
  78. public static function forInterface(string $interfaceName): InterfaceUnit
  79. {
  80. self::ensureUserDefinedInterface($interfaceName);
  81. $reflector = self::reflectorForClass($interfaceName);
  82. return new InterfaceUnit(
  83. $interfaceName,
  84. $reflector->getFileName(),
  85. range(
  86. $reflector->getStartLine(),
  87. $reflector->getEndLine()
  88. )
  89. );
  90. }
  91. /**
  92. * @psalm-param class-string $interfaceName
  93. *
  94. * @throws InvalidCodeUnitException
  95. * @throws ReflectionException
  96. */
  97. public static function forInterfaceMethod(string $interfaceName, string $methodName): InterfaceMethodUnit
  98. {
  99. self::ensureUserDefinedInterface($interfaceName);
  100. $reflector = self::reflectorForClassMethod($interfaceName, $methodName);
  101. return new InterfaceMethodUnit(
  102. $interfaceName . '::' . $methodName,
  103. $reflector->getFileName(),
  104. range(
  105. $reflector->getStartLine(),
  106. $reflector->getEndLine()
  107. )
  108. );
  109. }
  110. /**
  111. * @psalm-param class-string $traitName
  112. *
  113. * @throws InvalidCodeUnitException
  114. * @throws ReflectionException
  115. */
  116. public static function forTrait(string $traitName): TraitUnit
  117. {
  118. self::ensureUserDefinedTrait($traitName);
  119. $reflector = self::reflectorForClass($traitName);
  120. return new TraitUnit(
  121. $traitName,
  122. $reflector->getFileName(),
  123. range(
  124. $reflector->getStartLine(),
  125. $reflector->getEndLine()
  126. )
  127. );
  128. }
  129. /**
  130. * @psalm-param class-string $traitName
  131. *
  132. * @throws InvalidCodeUnitException
  133. * @throws ReflectionException
  134. */
  135. public static function forTraitMethod(string $traitName, string $methodName): TraitMethodUnit
  136. {
  137. self::ensureUserDefinedTrait($traitName);
  138. $reflector = self::reflectorForClassMethod($traitName, $methodName);
  139. return new TraitMethodUnit(
  140. $traitName . '::' . $methodName,
  141. $reflector->getFileName(),
  142. range(
  143. $reflector->getStartLine(),
  144. $reflector->getEndLine()
  145. )
  146. );
  147. }
  148. /**
  149. * @psalm-param callable-string $functionName
  150. *
  151. * @throws InvalidCodeUnitException
  152. * @throws ReflectionException
  153. */
  154. public static function forFunction(string $functionName): FunctionUnit
  155. {
  156. $reflector = self::reflectorForFunction($functionName);
  157. if (!$reflector->isUserDefined()) {
  158. throw new InvalidCodeUnitException(
  159. sprintf(
  160. '"%s" is not a user-defined function',
  161. $functionName
  162. )
  163. );
  164. }
  165. return new FunctionUnit(
  166. $functionName,
  167. $reflector->getFileName(),
  168. range(
  169. $reflector->getStartLine(),
  170. $reflector->getEndLine()
  171. )
  172. );
  173. }
  174. /**
  175. * @psalm-param list<int> $sourceLines
  176. */
  177. private function __construct(string $name, string $sourceFileName, array $sourceLines)
  178. {
  179. $this->name = $name;
  180. $this->sourceFileName = $sourceFileName;
  181. $this->sourceLines = $sourceLines;
  182. }
  183. public function name(): string
  184. {
  185. return $this->name;
  186. }
  187. public function sourceFileName(): string
  188. {
  189. return $this->sourceFileName;
  190. }
  191. /**
  192. * @psalm-return list<int>
  193. */
  194. public function sourceLines(): array
  195. {
  196. return $this->sourceLines;
  197. }
  198. public function isClass(): bool
  199. {
  200. return false;
  201. }
  202. public function isClassMethod(): bool
  203. {
  204. return false;
  205. }
  206. public function isInterface(): bool
  207. {
  208. return false;
  209. }
  210. public function isInterfaceMethod(): bool
  211. {
  212. return false;
  213. }
  214. public function isTrait(): bool
  215. {
  216. return false;
  217. }
  218. public function isTraitMethod(): bool
  219. {
  220. return false;
  221. }
  222. public function isFunction(): bool
  223. {
  224. return false;
  225. }
  226. /**
  227. * @psalm-param class-string $className
  228. *
  229. * @throws InvalidCodeUnitException
  230. */
  231. private static function ensureUserDefinedClass(string $className): void
  232. {
  233. try {
  234. $reflector = new ReflectionClass($className);
  235. if ($reflector->isInterface()) {
  236. throw new InvalidCodeUnitException(
  237. sprintf(
  238. '"%s" is an interface and not a class',
  239. $className
  240. )
  241. );
  242. }
  243. if ($reflector->isTrait()) {
  244. throw new InvalidCodeUnitException(
  245. sprintf(
  246. '"%s" is a trait and not a class',
  247. $className
  248. )
  249. );
  250. }
  251. if (!$reflector->isUserDefined()) {
  252. throw new InvalidCodeUnitException(
  253. sprintf(
  254. '"%s" is not a user-defined class',
  255. $className
  256. )
  257. );
  258. }
  259. // @codeCoverageIgnoreStart
  260. } catch (\ReflectionException $e) {
  261. throw new ReflectionException(
  262. $e->getMessage(),
  263. (int) $e->getCode(),
  264. $e
  265. );
  266. }
  267. // @codeCoverageIgnoreEnd
  268. }
  269. /**
  270. * @psalm-param class-string $interfaceName
  271. *
  272. * @throws InvalidCodeUnitException
  273. */
  274. private static function ensureUserDefinedInterface(string $interfaceName): void
  275. {
  276. try {
  277. $reflector = new ReflectionClass($interfaceName);
  278. if (!$reflector->isInterface()) {
  279. throw new InvalidCodeUnitException(
  280. sprintf(
  281. '"%s" is not an interface',
  282. $interfaceName
  283. )
  284. );
  285. }
  286. if (!$reflector->isUserDefined()) {
  287. throw new InvalidCodeUnitException(
  288. sprintf(
  289. '"%s" is not a user-defined interface',
  290. $interfaceName
  291. )
  292. );
  293. }
  294. // @codeCoverageIgnoreStart
  295. } catch (\ReflectionException $e) {
  296. throw new ReflectionException(
  297. $e->getMessage(),
  298. (int) $e->getCode(),
  299. $e
  300. );
  301. }
  302. // @codeCoverageIgnoreEnd
  303. }
  304. /**
  305. * @psalm-param class-string $traitName
  306. *
  307. * @throws InvalidCodeUnitException
  308. */
  309. private static function ensureUserDefinedTrait(string $traitName): void
  310. {
  311. try {
  312. $reflector = new ReflectionClass($traitName);
  313. if (!$reflector->isTrait()) {
  314. throw new InvalidCodeUnitException(
  315. sprintf(
  316. '"%s" is not a trait',
  317. $traitName
  318. )
  319. );
  320. }
  321. // @codeCoverageIgnoreStart
  322. if (!$reflector->isUserDefined()) {
  323. throw new InvalidCodeUnitException(
  324. sprintf(
  325. '"%s" is not a user-defined trait',
  326. $traitName
  327. )
  328. );
  329. }
  330. } catch (\ReflectionException $e) {
  331. throw new ReflectionException(
  332. $e->getMessage(),
  333. (int) $e->getCode(),
  334. $e
  335. );
  336. }
  337. // @codeCoverageIgnoreEnd
  338. }
  339. /**
  340. * @psalm-param class-string $className
  341. *
  342. * @throws ReflectionException
  343. */
  344. private static function reflectorForClass(string $className): ReflectionClass
  345. {
  346. try {
  347. return new ReflectionClass($className);
  348. // @codeCoverageIgnoreStart
  349. } catch (\ReflectionException $e) {
  350. throw new ReflectionException(
  351. $e->getMessage(),
  352. (int) $e->getCode(),
  353. $e
  354. );
  355. }
  356. // @codeCoverageIgnoreEnd
  357. }
  358. /**
  359. * @psalm-param class-string $className
  360. *
  361. * @throws ReflectionException
  362. */
  363. private static function reflectorForClassMethod(string $className, string $methodName): ReflectionMethod
  364. {
  365. try {
  366. return new ReflectionMethod($className, $methodName);
  367. // @codeCoverageIgnoreStart
  368. } catch (\ReflectionException $e) {
  369. throw new ReflectionException(
  370. $e->getMessage(),
  371. (int) $e->getCode(),
  372. $e
  373. );
  374. }
  375. // @codeCoverageIgnoreEnd
  376. }
  377. /**
  378. * @psalm-param callable-string $functionName
  379. *
  380. * @throws ReflectionException
  381. */
  382. private static function reflectorForFunction(string $functionName): ReflectionFunction
  383. {
  384. try {
  385. return new ReflectionFunction($functionName);
  386. // @codeCoverageIgnoreStart
  387. } catch (\ReflectionException $e) {
  388. throw new ReflectionException(
  389. $e->getMessage(),
  390. (int) $e->getCode(),
  391. $e
  392. );
  393. }
  394. // @codeCoverageIgnoreEnd
  395. }
  396. }