private function createTypeMap(string $fileName) : array
{
$objectTypes = [];
$typeMap = [];
$processTypeString = function (string $typeString, string $className = null) use(&$typeMap, &$objectTypes) {
if (isset($typeMap[$typeString])) {
return;
}
$type = $this->getTypeFromTypeString($typeString, $className);
if ($type instanceof ArrayType) {
$nestedItemType = $type->getNestedItemType();
if ($nestedItemType->getItemType() instanceof ObjectType) {
if ($nestedItemType->getItemType()->getClass() === $className) {
$typeMap[$typeString] = ArrayType::createDeepArrayType(new NestedArrayItemType(new ObjectType($className, false), $nestedItemType->getDepth()), $type->isNullable());
} else {
$objectTypes[] = ['type' => $nestedItemType->getItemType(), 'typeString' => $typeString, 'arrayType' => ['depth' => $nestedItemType->getDepth(), 'nullable' => $type->isNullable()]];
}
} else {
$typeMap[$typeString] = $type;
}
return;
}
if (!$type instanceof ObjectType) {
$typeMap[$typeString] = $type;
return;
} elseif ($type->getClass() === $className) {
$typeMap[$typeString] = $type;
return;
}
$objectTypes[] = ['type' => $type, 'typeString' => $typeString];
};
$patterns = ['#@param\\s+' . self::TYPE_PATTERN . '\\s+\\$[a-zA-Z0-9_]+#', '#@var\\s+' . self::TYPE_PATTERN . '#', '#@var\\s+\\$[a-zA-Z0-9_]+\\s+' . self::TYPE_PATTERN . '#', '#@return\\s+' . self::TYPE_PATTERN . '#'];
/** @var \PhpParser\Node\Stmt\ClassLike|null $lastClass */
$lastClass = null;
$this->processNodes($this->parser->parseFile($fileName), function (\PhpParser\Node $node, string $className = null) use($processTypeString, $patterns, &$lastClass) {
if ($node instanceof Node\Stmt\ClassLike) {
$lastClass = $node;
}
if (!in_array(get_class($node), [Node\Stmt\Property::class, Node\Stmt\ClassMethod::class, Node\Expr\Assign::class], true)) {
return;
}
$comment = CommentHelper::getDocComment($node);
if ($comment === null) {
return;
}
foreach ($patterns as $pattern) {
preg_match_all($pattern, $comment, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$processTypeString($match[1], $className);
}
}
});
if (count($objectTypes) === 0) {
return $typeMap;
}
$fileString = file_get_contents($fileName);
if ($lastClass !== null) {
$classType = 'class';
if ($lastClass instanceof Interface_) {
$classType = 'interface';
} elseif ($lastClass instanceof Trait_) {
$classType = 'trait';
}
$classTypePosition = strpos($fileString, sprintf('%s %s', $classType, $lastClass->name));
$nameResolveInfluencingPart = trim(substr($fileString, 0, $classTypePosition));
} else {
$nameResolveInfluencingPart = $fileString;
}
if (substr($nameResolveInfluencingPart, -strlen('final')) === 'final') {
$nameResolveInfluencingPart = trim(substr($nameResolveInfluencingPart, 0, -strlen('final')));
}
if (substr($nameResolveInfluencingPart, -strlen('abstract')) === 'abstract') {
$nameResolveInfluencingPart = trim(substr($nameResolveInfluencingPart, 0, -strlen('abstract')));
}
$namespace = null;
$uses = [];
$this->processNodes($this->parser->parseString($nameResolveInfluencingPart), function (\PhpParser\Node $node) use($processTypeString, $patterns, &$namespace, &$uses) {
if ($node instanceof \PhpParser\Node\Stmt\Namespace_) {
$namespace = (string) $node->name;
} elseif ($node instanceof \PhpParser\Node\Stmt\Use_ && $node->type === \PhpParser\Node\Stmt\Use_::TYPE_NORMAL) {
foreach ($node->uses as $use) {
$uses[$use->alias] = (string) $use->name;
}
} elseif ($node instanceof \PhpParser\Node\Stmt\GroupUse) {
$prefix = (string) $node->prefix;
foreach ($node->uses as $use) {
if ($node->type === \PhpParser\Node\Stmt\Use_::TYPE_NORMAL || $use->type === \PhpParser\Node\Stmt\Use_::TYPE_NORMAL) {
$uses[$use->alias] = sprintf('%s\\%s', $prefix, $use->name);
}
}
}
});
foreach ($objectTypes as $key => $objectType) {
$objectTypeType = $objectType['type'];
$objectTypeTypeClass = $objectTypeType->getClass();
if (preg_match('#^[a-zA-Z_\\\\]#', $objectTypeTypeClass) === 0) {
unset($objectTypes[$key]);
continue;
}
if (strtolower($objectTypeTypeClass) === 'new') {
unset($objectTypes[$key]);
continue;
}
}
foreach ($objectTypes as $objectType) {
if (isset($objectType['arrayType'])) {
$arrayType = $objectType['arrayType'];
$typeMap[$objectType['typeString']] = ArrayType::createDeepArrayType(new NestedArrayItemType(new ObjectType($this->resolveStringName($namespace, $objectType['type']->getClass(), $uses), false), $arrayType['depth']), $arrayType['nullable']);
} else {
$objectTypeString = $objectType['typeString'];
$objectTypeType = $objectType['type'];
$typeMap[$objectTypeString] = new ObjectType($this->resolveStringName($namespace, $objectType['type']->getClass(), $uses), $objectTypeType->isNullable());
}
}
return $typeMap;
}