NewMagicMethodsSniff.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. <?php
  2. /**
  3. * PHPCompatibility, an external standard for PHP_CodeSniffer.
  4. *
  5. * @package PHPCompatibility
  6. * @copyright 2012-2019 PHPCompatibility Contributors
  7. * @license https://opensource.org/licenses/LGPL-3.0 LGPL3
  8. * @link https://github.com/PHPCompatibility/PHPCompatibility
  9. */
  10. namespace PHPCompatibility\Sniffs\FunctionNameRestrictions;
  11. use PHPCompatibility\AbstractNewFeatureSniff;
  12. use PHP_CodeSniffer_File as File;
  13. /**
  14. * Warns for non-magic behaviour of magic methods prior to becoming magic.
  15. *
  16. * PHP version 5.0+
  17. *
  18. * @link https://www.php.net/manual/en/language.oop5.magic.php
  19. * @link https://wiki.php.net/rfc/closures#additional_goodyinvoke
  20. * @link https://wiki.php.net/rfc/debug-info
  21. *
  22. * @since 7.0.4
  23. * @since 7.1.0 Now extends the `AbstractNewFeatureSniff` instead of the base `Sniff` class.
  24. */
  25. class NewMagicMethodsSniff extends AbstractNewFeatureSniff
  26. {
  27. /**
  28. * A list of new magic methods, not considered magic in older versions.
  29. *
  30. * Method names in the array should be all *lowercase*.
  31. * The array lists : version number with false (not magic) or true (magic).
  32. * If's sufficient to list the first version where the method became magic.
  33. *
  34. * @since 7.0.4
  35. *
  36. * @var array(string => array(string => bool|string))
  37. */
  38. protected $newMagicMethods = array(
  39. '__construct' => array(
  40. '4.4' => false,
  41. '5.0' => true,
  42. ),
  43. '__destruct' => array(
  44. '4.4' => false,
  45. '5.0' => true,
  46. ),
  47. '__get' => array(
  48. '4.4' => false,
  49. '5.0' => true,
  50. ),
  51. '__isset' => array(
  52. '5.0' => false,
  53. '5.1' => true,
  54. ),
  55. '__unset' => array(
  56. '5.0' => false,
  57. '5.1' => true,
  58. ),
  59. '__set_state' => array(
  60. '5.0' => false,
  61. '5.1' => true,
  62. ),
  63. '__callstatic' => array(
  64. '5.2' => false,
  65. '5.3' => true,
  66. ),
  67. '__invoke' => array(
  68. '5.2' => false,
  69. '5.3' => true,
  70. ),
  71. '__debuginfo' => array(
  72. '5.5' => false,
  73. '5.6' => true,
  74. ),
  75. // Special case - only became properly magical in 5.2.0,
  76. // before that it was only called for echo and print.
  77. '__tostring' => array(
  78. '5.1' => false,
  79. '5.2' => true,
  80. 'message' => 'The method %s() was not truly magical in PHP version %s and earlier. The associated magic functionality will only be called when directly combined with echo or print.',
  81. ),
  82. '__serialize' => array(
  83. '7.3' => false,
  84. '7.4' => true,
  85. ),
  86. '__unserialize' => array(
  87. '7.3' => false,
  88. '7.4' => true,
  89. ),
  90. );
  91. /**
  92. * Returns an array of tokens this test wants to listen for.
  93. *
  94. * @since 7.0.4
  95. *
  96. * @return array
  97. */
  98. public function register()
  99. {
  100. return array(\T_FUNCTION);
  101. }
  102. /**
  103. * Processes this test, when one of its tokens is encountered.
  104. *
  105. * @since 7.0.4
  106. *
  107. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  108. * @param int $stackPtr The position of the current token in the
  109. * stack passed in $tokens.
  110. *
  111. * @return void
  112. */
  113. public function process(File $phpcsFile, $stackPtr)
  114. {
  115. $functionName = $phpcsFile->getDeclarationName($stackPtr);
  116. $functionNameLc = strtolower($functionName);
  117. if (isset($this->newMagicMethods[$functionNameLc]) === false) {
  118. return;
  119. }
  120. if ($this->inClassScope($phpcsFile, $stackPtr, false) === false) {
  121. return;
  122. }
  123. $itemInfo = array(
  124. 'name' => $functionName,
  125. 'nameLc' => $functionNameLc,
  126. );
  127. $this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
  128. }
  129. /**
  130. * Get the relevant sub-array for a specific item from a multi-dimensional array.
  131. *
  132. * @since 7.1.0
  133. *
  134. * @param array $itemInfo Base information about the item.
  135. *
  136. * @return array Version and other information about the item.
  137. */
  138. public function getItemArray(array $itemInfo)
  139. {
  140. return $this->newMagicMethods[$itemInfo['nameLc']];
  141. }
  142. /**
  143. * Get an array of the non-PHP-version array keys used in a sub-array.
  144. *
  145. * @since 7.1.0
  146. *
  147. * @return array
  148. */
  149. protected function getNonVersionArrayKeys()
  150. {
  151. return array('message');
  152. }
  153. /**
  154. * Retrieve the relevant detail (version) information for use in an error message.
  155. *
  156. * @since 7.1.0
  157. *
  158. * @param array $itemArray Version and other information about the item.
  159. * @param array $itemInfo Base information about the item.
  160. *
  161. * @return array
  162. */
  163. public function getErrorInfo(array $itemArray, array $itemInfo)
  164. {
  165. $errorInfo = parent::getErrorInfo($itemArray, $itemInfo);
  166. $errorInfo['error'] = false; // Warning, not error.
  167. $errorInfo['message'] = '';
  168. if (empty($itemArray['message']) === false) {
  169. $errorInfo['message'] = $itemArray['message'];
  170. }
  171. return $errorInfo;
  172. }
  173. /**
  174. * Get the error message template for this sniff.
  175. *
  176. * @since 7.1.0
  177. *
  178. * @return string
  179. */
  180. protected function getErrorMsgTemplate()
  181. {
  182. return 'The method %s() was not magical in PHP version %s and earlier. The associated magic functionality will not be invoked.';
  183. }
  184. /**
  185. * Allow for concrete child classes to filter the error message before it's passed to PHPCS.
  186. *
  187. * @since 7.1.0
  188. *
  189. * @param string $error The error message which was created.
  190. * @param array $itemInfo Base information about the item this error message applies to.
  191. * @param array $errorInfo Detail information about an item this error message applies to.
  192. *
  193. * @return string
  194. */
  195. protected function filterErrorMsg($error, array $itemInfo, array $errorInfo)
  196. {
  197. if ($errorInfo['message'] !== '') {
  198. $error = $errorInfo['message'];
  199. }
  200. return $error;
  201. }
  202. }