1

I have a function which returns a class name (string), but the returned class always implements the same interface:

<?php

interface BaseInterface {}
class SomeClass implements BaseInterface {}
class AnotherClass implements BaseInterface {}

/**
 * @return ....
 */
function getClassName($someCondition): string
{
    if ($someCondition === 42) {
        return SomeClass::class;
    } else {
        return AnotherClass::class;
    }
}

Is it possible to give this information in the @return tag? Something like this (imaginary syntax):

/** @return BaseInterface::class */

The final goal is to make IDEs understand that getClassName(...)::myInterfaceMethod() is valid.

EDIT: a concrete use case of the problem

Here is a runnable example which show how I use ::class in my actual code.

I reworked the code to be as short as possible, but it is actually part of a Symfony project, running with php 7.4.

The idea is to implement one class per social network; all these little classes implement SocialNetworkInterface and only have static methods. They are never instanciated, but they are consulted by calling their static methods.

Note: The variables $user should be typed as User, but in order to shorten the code, I just leave them untyped in the parameters.

The problem explained in my question (IDE is complaining) happens line 79, inside renderLinkForUserNetwork():

$class = $networkClass::getFontAwesomeLogoClass();
$url = $networkClass::getUrlForUsername($networkUsername);
<?php

interface SocialNetworkInterface
{
    public static function getName(): string;
    public static function getBaseUrl(): string;
    public static function getUrlForUsername(string $username): string;
    public static function getFontAwesomeLogoClass(): string;
}

abstract class AbstractSocialNetwork implements SocialNetworkInterface
{
    public static function getUrlForUsername(string $username): string
    {
        // Most social networks use this kind of urls, but of course, this can
        // be overloaded in final classes:
        $baseUrl = static::getBaseUrl();
        return "$baseUrl/$username";
    }

    public static function getFontAwesomeLogoClass(): string
    {
        $name = static::getName();
        return "fab fa-$name";
    }
}

class Facebook extends AbstractSocialNetwork
{
    public static function getName(): string
    {
        return 'facebook';
    }

    public static function getBaseUrl(): string
    {
        return ('https://www.facebook.com');
    }
}

class Twitter extends AbstractSocialNetwork
{
    public static function getName(): string
    {
        return 'twitter';
    }

    public static function getBaseUrl(): string
    {
        return ('https://twitter.com');
    }
}

class SocialNetworkHandler
{
    public function getSocialNetworksForUser($user): array {
        // Should return all the social network classes for which the user has
        // information to give (normally depends on the user, fake data is given
        // here for simplicity):
        return [
            Facebook::class,
            Twitter::class,
        ];
    }

    public function renderLinkForUserNetwork($user, string $networkClass): ?string
    {
        // Make it runnable for this example, normally retrieved from $user
        // and $networkClass:
        $networkUsername = 'example';

        // An ugly workaround: of course, $networkClass is not an instance of
        // SocialNetworkInterface, but calling static methods work the same
        // between class name strings and objects:
        /** @var SocialNetworkInterface $networkClass */

        // When not using the ugly workaround just above, the IDE is complaining
        // here (method not found in string):
        $class = $networkClass::getFontAwesomeLogoClass();
        $url = $networkClass::getUrlForUsername($networkUsername);

        return <<< HTML
            <a href="$url"><i class="$class"></i></a>
        HTML;
    }

    public function renderLinksForUser($user): string
    {
        $links = [];
        foreach ($this->getSocialNetworksForUser($user) as $networkClass) {
            $links[] = $this->renderLinkForUserNetwork($user, $networkClass);
        }
        return implode("\n", $links);
    }
}

$handler = new SocialNetworkHandler();
$user = null; // Should be a User entity
echo $handler->renderLinksForUser($user);

Output:

    <a href="https://www.facebook.com/example"><i class="fab fa-facebook"></i></a>
    <a href="https://twitter.com/example"><i class="fab fa-twitter"></i></a>
0

1 Answer 1

2

No, that's not how it works, unfortunately. You are supposed to specify a return type here, and even though BaseInterface::class is dynamic, it would be a return value (which is of type string).

Sign up to request clarification or add additional context in comments.

3 Comments

Let me clarify: in my question, /** @return BaseInterface::class */ is an imaginary syntax which would satisfy my needs. The final goal is to make IDEs understand that getClassName(...)::myInterfaceMethod() is valid. (Question edited)
Thanks for clarifying. But from what I can see, since the function getClassName() returns a string, you will be calling myInterfaceMethod() on the returned string, and not on the actual class that you want. This is sounding more and more like an XY-problem. I think we need to see the actual surrounding code to be able to get further on this one, as I don't think the function getClassName() is a viable solution to your problem, but I could be totally wrong.
Ok, question edited

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.