21 private $simpleReturnTypes = [
22 '$this',
'void',
'string',
'int',
'bool',
'boolean',
'integer',
'null' 28 private $blockWhitelist;
35 private function getWhitelist(): array
37 if ($this->blockWhitelist ===
null) {
38 $whiteListFiles = str_replace(
41 realpath(
__DIR__) .
'/_files/whitelist/public_code*.txt' 44 foreach (glob($whiteListFiles) as
$fileName) {
45 $whiteListItems = array_merge(
47 file(
$fileName, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)
50 $this->blockWhitelist = $whiteListItems;
52 return $this->blockWhitelist;
63 public function testAllBlocksReferencedInLayoutArePublic($layoutFile)
65 $nonPublishedBlocks = [];
66 $xml = simplexml_load_file($layoutFile);
67 $elements = $xml->xpath(
'//block | //referenceBlock') ?: [];
69 foreach ($elements as $node) {
70 $class = (string) $node[
'class'];
72 $reflection = (new \ReflectionClass(
$class));
73 if (strpos($reflection->getDocComment(),
'@api') ===
false) {
74 $nonPublishedBlocks[] =
$class;
78 if (count($nonPublishedBlocks)) {
80 "Layout file '$layoutFile' uses following blocks that are not marked with @api annotation:\n" 81 . implode(
",\n", array_unique($nonPublishedBlocks))
109 $nonPublishedClasses = [];
110 $reflection = new \ReflectionClass(
$class);
111 $filter = \ReflectionMethod::IS_PUBLIC;
112 if ($reflection->isAbstract()) {
113 $filter = $filter | \ReflectionMethod::IS_PROTECTED;
115 $methods = $reflection->getMethods($filter);
117 if (
$method->isConstructor()) {
120 $nonPublishedClasses = $this->checkParameters(
$class,
$method, $nonPublishedClasses);
124 if (
$method->hasReturnType()) {
125 if (!
$method->getReturnType()->isBuiltin()) {
126 $returnTypes = [trim(
$method->getReturnType()->__toString(),
'?[]')];
129 $returnTypes = $this->getReturnTypesFromDocComment(
$method->getDocComment());
131 $nonPublishedClasses = $this->checkReturnValues(
$class, $returnTypes, $nonPublishedClasses);
134 if (count($nonPublishedClasses)) {
136 "Public type '" .
$class .
"' references following non-public types:\n" 137 . implode(
"\n", array_unique($nonPublishedClasses))
151 foreach (
$files as $file) {
153 if (strpos($fileContents,
'@api') !==
false) {
154 foreach ($this->getDeclaredClassesAndInterfaces($file) as
$class) {
155 if (!in_array(
$class->getName(), $this->getWhitelist())
172 private function getDeclaredClassesAndInterfaces($file)
174 $fileScanner = new \Magento\Setup\Module\Di\Code\Reader\FileScanner($file);
175 return $fileScanner->getClasses();
184 private function isPublished(\ReflectionClass
$class)
186 return strpos(
$class->getDocComment(),
'@api') !==
false;
196 private function areClassesFromSameVendor($classNameA, $classNameB)
198 $classNameA = ltrim($classNameA,
'\\');
199 $classNameB = ltrim($classNameB,
'\\');
200 $aVendor = substr($classNameA, 0, strpos($classNameA,
'\\'));
201 $bVendor = substr($classNameB, 0, strpos($classNameB,
'\\'));
202 return $aVendor === $bVendor;
215 return substr(
$className, -18) ===
'ExtensionInterface' || substr(
$className, -7) ===
'Factory';
226 private function getReturnTypesFromDocComment($docComment)
229 if (preg_match(
'/@return (\S*)/', $docComment, $matches)) {
232 explode(
'|', $matches[1])
249 private function checkReturnValues(
$class, array $returnTypes, array $nonPublishedClasses)
251 foreach ($returnTypes as $returnType) {
252 if (!in_array($returnType, $this->simpleReturnTypes)
253 && !$this->isGenerated($returnType)
256 $returnTypeReflection = new \ReflectionClass($returnType);
257 if (!$returnTypeReflection->isInternal()
258 && $this->areClassesFromSameVendor($returnType,
$class)
259 && !$this->isPublished($returnTypeReflection)
261 $nonPublishedClasses[$returnType] = $returnType;
265 return $nonPublishedClasses;
275 private function checkParameters(
$class, \ReflectionMethod
$method, array $nonPublishedClasses)
278 foreach (
$method->getParameters() as $parameter) {
279 if ($parameter->hasType()
280 && !$parameter->getType()->isBuiltin()
281 && !$this->isGenerated($parameter->getType()->__toString())
283 $parameterClass = $parameter->getClass();
292 if (!$parameterClass->isInternal()
293 && $this->areClassesFromSameVendor($parameterClass->getName(),
$class)
294 && !$this->isPublished($parameterClass)
296 $nonPublishedClasses[$parameterClass->getName()] = $parameterClass->getName();
300 return $nonPublishedClasses;
defined('TESTS_BP')||define('TESTS_BP' __DIR__
publicPHPTypesDataProvider()
testAllPHPClassesReferencedFromPublicClassesArePublic($class)
$_option $_optionId $class
layoutFilesDataProvider()
foreach($appDirs as $dir) $files