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>