PageRenderTime 26ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/ExtensibleInterfacesTest.php

https://gitlab.com/crazybutterfly815/magento2
PHP | 265 lines | 209 code | 11 blank | 45 comment | 8 complexity | cd8c578408d8f3cd444f5759937a5398 MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright © 2016 Magento. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Test\Integrity\Magento\Framework\Api;
  7. use Magento\Framework\App\Utility\Files;
  8. /**
  9. * Check interfaces inherited from \Magento\Framework\Api\ExtensibleDataInterface.
  10. *
  11. * Ensure that all interfaces inherited from \Magento\Framework\Api\ExtensibleDataInterface
  12. * override getExtensionAttributes() method and have correct return type specified.
  13. */
  14. class ExtensibleInterfacesTest extends \PHPUnit_Framework_TestCase
  15. {
  16. const EXTENSIBLE_DATA_INTERFACE = \Magento\Framework\Api\ExtensibleDataInterface::class;
  17. /**
  18. * Check return types of getExtensionAttributes() methods.
  19. */
  20. public function testGetSetExtensionAttributes()
  21. {
  22. $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
  23. $invoker(
  24. /**
  25. * @param string $filename
  26. */
  27. function ($filename) {
  28. $errors = [];
  29. $fileContent = file_get_contents($filename);
  30. $extendsFromExtensibleDataInterface = preg_match(
  31. '/' . str_replace('\\', '\\\\', self::EXTENSIBLE_DATA_INTERFACE) . '/',
  32. $fileContent
  33. );
  34. if ($extendsFromExtensibleDataInterface
  35. && preg_match('/namespace ([\w\\\\]+).*interface ([\w\\\\]+)/s', $fileContent, $matches)
  36. ) {
  37. $namespace = $matches[1];
  38. $interfaceName = $matches[2];
  39. $fullInterfaceName = '\\' . $namespace . '\\' . $interfaceName;
  40. $interfaceReflection = new \ReflectionClass($fullInterfaceName);
  41. if ($interfaceReflection->isSubclassOf(self::EXTENSIBLE_DATA_INTERFACE)) {
  42. $interfaceName = '\\' . $interfaceReflection->getName();
  43. $extensionClassName = substr($interfaceName, 0, -strlen('Interface')) . 'Extension';
  44. $extensionInterfaceName = $extensionClassName . 'Interface';
  45. /** Check getExtensionAttributes method */
  46. $errors = $this->checkGetExtensionAttributes(
  47. $interfaceReflection,
  48. $extensionInterfaceName,
  49. $fullInterfaceName
  50. );
  51. /** Check setExtensionAttributes method */
  52. $errors = array_merge(
  53. $errors,
  54. $this->checkSetExtensionAttributes(
  55. $interfaceReflection,
  56. $extensionInterfaceName,
  57. $fullInterfaceName
  58. )
  59. );
  60. }
  61. }
  62. $this->assertEmpty(
  63. $errors,
  64. "Error validating $filename\n" . print_r($errors, true)
  65. );
  66. },
  67. $this->getInterfacesFiles()
  68. );
  69. }
  70. /**
  71. * Check getExtensionAttributes methods
  72. *
  73. * @param \ReflectionClass $interfaceReflection
  74. * @param string $extensionInterfaceName
  75. * @param string $fullInterfaceName
  76. * @return array
  77. */
  78. private function checkGetExtensionAttributes(
  79. \ReflectionClass $interfaceReflection,
  80. $extensionInterfaceName,
  81. $fullInterfaceName
  82. ) {
  83. $errors = [];
  84. try {
  85. $methodReflection = $interfaceReflection->getMethod('getExtensionAttributes');
  86. /** Ensure that proper return type of getExtensionAttributes() method is specified */
  87. $methodDocBlock = $methodReflection->getDocComment();
  88. $pattern = "/@return\s+" . str_replace('\\', '\\\\', $extensionInterfaceName) . "/";
  89. if (!preg_match($pattern, $methodDocBlock)) {
  90. $errors[] =
  91. "'{$fullInterfaceName}::getExtensionAttributes()' must be declared "
  92. . "with a return type of '{$extensionInterfaceName}'.";
  93. }
  94. } catch (\ReflectionException $e) {
  95. $errors[] = "The following method should be declared in "
  96. . "'{$extensionInterfaceName}'. '{$extensionInterfaceName}' must be specified as"
  97. . " a return type for '{$fullInterfaceName}::getExtensionAttributes()'";
  98. }
  99. return $errors;
  100. }
  101. /**
  102. * Check setExtensionAttributes methods
  103. *
  104. * @param \ReflectionClass $interfaceReflection
  105. * @param string $extensionInterfaceName
  106. * @param string $fullInterfaceName
  107. * @return array
  108. */
  109. private function checkSetExtensionAttributes(
  110. \ReflectionClass $interfaceReflection,
  111. $extensionInterfaceName,
  112. $fullInterfaceName
  113. ) {
  114. $errors = [];
  115. try {
  116. $methodReflection = $interfaceReflection->getMethod('setExtensionAttributes');
  117. /** Ensure that proper argument type for setExtensionAttributes() method is specified */
  118. $methodParameters = $methodReflection->getParameters();
  119. if (empty($methodParameters)) {
  120. $errors[] = "'{$extensionInterfaceName}' must be specified as the parameter type "
  121. . "in '{$fullInterfaceName}::setExtensionAttributes()'.";
  122. } else {
  123. // Get the parameter name via a regular expression capture because the class may
  124. // not exist which causes a fatal error
  125. preg_match('/\[\s\<\w+?>\s([\w]+)/s', $methodParameters[0]->__toString(), $matches);
  126. $isCorrectParameter = false;
  127. if (isset($matches[1]) && '\\' . $matches[1] != $extensionInterfaceName) {
  128. $isCorrectParameter = true;
  129. }
  130. if (!$isCorrectParameter) {
  131. $errors[] = "'{$extensionInterfaceName}' must be specified as the parameter type "
  132. . "in '{$fullInterfaceName}::setExtensionAttributes()'.";
  133. }
  134. }
  135. } catch (\ReflectionException $e) {
  136. $errors[] = "'{$fullInterfaceName}::setExtensionAttributes()' must be declared "
  137. . "with a '{$extensionInterfaceName}' parameter type.";
  138. }
  139. return $errors;
  140. }
  141. /**
  142. * Ensure that all classes extended from extensible classes implement getter and setter for extension attributes.
  143. */
  144. public function testExtensibleClassesWithMissingInterface()
  145. {
  146. $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
  147. $invoker(
  148. /**
  149. * @param string $filename
  150. */
  151. function ($filename) {
  152. $errors = [];
  153. $fileContent = file_get_contents($filename);
  154. $extensibleClassPattern = 'class [^\{]+extends[^\{]+AbstractExtensible';
  155. $abstractExtensibleClassPattern = 'abstract ' . $extensibleClassPattern;
  156. if (preg_match('/' . $extensibleClassPattern . '/', $fileContent) &&
  157. !preg_match('/' . $abstractExtensibleClassPattern . '/', $fileContent)
  158. ) {
  159. $fileReflection = new \Zend\Code\Reflection\FileReflection($filename, true);
  160. foreach ($fileReflection->getClasses() as $classReflection) {
  161. if ($classReflection->isSubclassOf(self::EXTENSIBLE_DATA_INTERFACE)) {
  162. $methodsToCheck = ['setExtensionAttributes', 'getExtensionAttributes'];
  163. foreach ($methodsToCheck as $methodName) {
  164. try {
  165. $classReflection->getMethod($methodName);
  166. } catch (\ReflectionException $e) {
  167. $className = $classReflection->getName();
  168. $errors[] = "'{$className}::{$methodName}()' must be declared or "
  169. . "'{$className}' should not be inherited from extensible class.";
  170. }
  171. }
  172. }
  173. }
  174. }
  175. $this->assertEmpty(
  176. $errors,
  177. "Error validating $filename\n" . print_r($errors, true)
  178. );
  179. },
  180. $this->getPhpFiles()
  181. );
  182. }
  183. /**
  184. * Retrieve a list of all interfaces declared in the Magento application and Magento library.
  185. *
  186. * @return array
  187. */
  188. public function getInterfacesFiles()
  189. {
  190. $codeInterfaceFiles = $this->getFiles(BP . '/app', '*Interface.php');
  191. $libInterfaceFiles = $this->getFiles(BP . '/lib/Magento', '*Interface.php');
  192. $interfaces = [];
  193. $filesToCheck = $this->blacklistFilter(array_merge($codeInterfaceFiles, $libInterfaceFiles));
  194. foreach ($filesToCheck as $file) {
  195. $interfaces[substr($file, strlen(BP))] = [$file];
  196. }
  197. return $interfaces;
  198. }
  199. /**
  200. * Retrieve a list of all php files declared in the Magento application and Magento library.
  201. *
  202. * @return array
  203. */
  204. public function getPhpFiles()
  205. {
  206. $codeFiles = $this->getFiles(BP . '/app', '*.php');
  207. $libFiles = $this->getFiles(BP . '/lib/Magento', '*.php');
  208. $phpFiles = [];
  209. $filesToCheck = $this->blacklistFilter(array_merge($codeFiles, $libFiles));
  210. foreach ($filesToCheck as $file) {
  211. $phpFiles[substr($file, strlen(BP))] = [$file];
  212. }
  213. return $phpFiles;
  214. }
  215. /**
  216. * Retrieve all files in a directory that correspond to the given pattern
  217. *
  218. * @param string $dir
  219. * @param string $pattern
  220. * @return array
  221. */
  222. protected function getFiles($dir, $pattern)
  223. {
  224. $files = glob($dir . '/' . $pattern, GLOB_NOSORT);
  225. foreach (glob($dir . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $newDir) {
  226. $files = array_merge($files, $this->getFiles($newDir, $pattern));
  227. }
  228. return $files;
  229. }
  230. /**
  231. * Filter blacklisted files out of an array
  232. *
  233. * @param array $preFilter
  234. * @return array
  235. */
  236. protected function blacklistFilter($preFilter)
  237. {
  238. $postFilter = [];
  239. $blacklist = Files::init()->readLists(__DIR__ . '/_files/ExtensibleInterfacesTest/blacklist*');
  240. foreach ($preFilter as $file) {
  241. if (!in_array($file, $blacklist)) {
  242. $postFilter[] = $file;
  243. }
  244. }
  245. return $postFilter;
  246. }
  247. }