src/Phan/Language/FQSEN/FullyQualifiedFunctionName.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

namespace Phan\Language\FQSEN;

use ast\Node;
use Phan\Exception\EmptyFQSENException;
use Phan\Exception\FQSENException;
use Phan\Language\Context;

/**
 * A Fully-Qualified Function Name
 */
class FullyQualifiedFunctionName extends FullyQualifiedGlobalStructuralElement implements FullyQualifiedFunctionLikeName
{

    /**
     * @return int
     * The namespace map type such as \ast\flags\USE_NORMAL or \ast\flags\USE_FUNCTION
     */
    protected static function getNamespaceMapType(): int
    {
        return \ast\flags\USE_FUNCTION;
    }

    /**
     * @return string
     * The canonical representation of the name of the object. Functions
     * and Methods, for instance, lowercase their names.
     * TODO: Separate the function used to render names in phan errors
     *       from the ones used for generating array keys.
     */
    public static function canonicalName(string $name): string
    {
        return $name;
    }

    /**
     * @param string $fqsen_string
     * An FQSEN string like '\Namespace\myfunction'
     *
     * @param Context $context
     * The context in which the FQSEN string was found
     *
     * @throws FQSENException
     * if $fqsen_string has an empty/invalid name component.
     */
    public static function fromStringInContext(
        string $fqsen_string,
        Context $context
    ): FullyQualifiedFunctionName {

        // Check to see if we're fully qualified
        if (0 === \strpos($fqsen_string, '\\')) {
            return static::fromFullyQualifiedString($fqsen_string);
        }

        // Split off the alternate ID
        $parts = \explode(',', $fqsen_string);
        $fqsen_string = $parts[0];
        $alternate_id = (int)($parts[1] ?? 0);

        $parts = \explode('\\', $fqsen_string);
        $name = \array_pop($parts);

        if ($name === '' || $name === false) {
            throw new EmptyFQSENException("The name cannot be empty", $fqsen_string);
        }

        // Check for a name map
        if ($context->hasNamespaceMapFor(static::getNamespaceMapType(), $fqsen_string)) {
            return $context->getNamespaceMapFor(
                static::getNamespaceMapType(),
                $fqsen_string
            );
        }

        // For functions we don't use the context's namespace if
        // there is no NS on the call.
        $namespace = \implode('\\', \array_filter($parts));

        return static::make(
            $namespace,
            $name,
            $alternate_id
        );
    }

    /**
     * Generates a deterministic FQSEN for the closure of the passed in node.
     * @param Node $node a Node type AST_CLOSURE, within the file $context->getFile()
     */
    public static function fromClosureInContext(
        Context $context,
        Node $node
    ): FullyQualifiedFunctionName {
        $hash_material =
            $context->getFile() . '|' .
            $node->lineno . '|' .
            $node->children['__declId'];

        $name = 'closure_' . \substr(\md5($hash_material), 0, 12);

        // @phan-suppress-next-line PhanThrowTypeAbsentForCall this is valid
        return static::fromStringInContext(
            $name,
            $context
        );
    }

    /**
     * @return bool
     * True if this FQSEN represents a closure
     */
    public function isClosure(): bool
    {
        return \strncmp('closure_', $this->name, 8) === 0;
    }
}