Is it possible to type hint more than one type?

33,925

Solution 1

Academically, this is called a type union.

Union types in PHP

You can cheat by creating interfaces, parent types, etc, as mentioned in other answers, but what's the point, apart for adding complexity and LoCs to your project? Plus, that can't work for scalar types as you can't extend/implement a scalar type.

Instead of making the code more readable, you'll get the opposite. Except if those classes/interfaces already existed and they are here because of OOP, not to solve a type hinting problem.

Workarounds

The canonical answer in PHP is... well, just don't put a type hint. The language was not thought to have a complex and powerful type system, and trying to workaround the flaws of the language is not a good answer.

Instead, document your function properly:

/**
 * Description of what the function does.
 *
 * @param User|File $multiTypeArgument Description of the argument.
 *
 * @return string[] Description of the function's return value.
 */
function myFunction($multiTypeArgument)
{

This will at least bring IDE support for autocompletion and static code analysis. Well enough when working on a private project, website, etc.

When designing a public API (a PHP library, etc), sometimes you may want to be more defensive about API consumers' inputs.

Then @tilz0R answer is the way to go:

function log($message) {
    if (!is_string($message) && !$message instanceof Message) {
        throw new \InvalidArgumentException('$message must be a string or a Message object.');
    }

    // code ...
}

The day PHP (almost) had union types

The 14th of February 2015, the Union Types PHP RFC was proposed for PHP 7.1. After discussion and vote, it's been rejected, 18 "no" against 11 "yes".

If the RFC had been accepted, PHP would have had union types exactly the way you've shown (User|File).

The RFC had some flaws, but the main reason for why it's been rejected is that the mainteners voters are quite resistive to change especially when it's about type strictness and other programming paradigms (ex. "why would we need type unions when the default takes all types of values" and "that's not good for performance").

Solution 2

As of PHP 8.0, this will be possible with the inclusion of Union Types.

The proposal has been voted 61 in favour to 5 against, and the implementation is ready to go.

There was a previous RFC proposing this, mentioned in another answer, but that one ended up being rejected.

It will work exactly as the example in your question:

class F
{
   public function foo (File|Resource $f) : int|float { /** implement this**// }
}

Which means that F::foo() expects either a File or a resource, and will return an int or a float.

A couple additional points:

Nullability

Additionally, you can declare an union with null. A|null is equivalent to ?A, but a more complex declaration as A|B|null is also possible.

"False" pseudo-type

It's also possible use the false type as part of a union-type declaration. E.g. int|false. This is included mostly because of historical reasons, since some internal functions return false on some types of error conditions. See strpos() as an example.

More modern functions should probably return null or throw an exception in these situations, but this alternative is included to account for legacy code.

Adding and removing part of a union type on inheritance

It is legal to add union types for parameter type hints (thus, making the function less restrictive), and to remove union types for return type hints (making the return type more specific).

Given the class F from above, this is legal:

class G extends F
{
    public function foo(File|Resource|string $f) : int { /** **/ }
}

But this is not:

class H extends F
{
    public function foo(File $f) : int|float|bool { /** **/ }
}

Solution 3

It's currently not possible in PHP. However you can have an interface, and implement it for User and File, and then use that interface as the type hint in log():

<?php
interface UserFile {
}

class User implements UserFile {
}
class File implements UserFile {
}

// snip

public function log (UserFile $requester) {
}

Solution 4

You can check type inside function.

function log ($requester) {
    if ($requester instanceof User || $requester instanceof File) {
        //Do your job
    }
}

Solution 5

You can create parent class for this twos:

abstract class Parent_class {}
class User extends Parent_class {
    // ...
}
class File extends Parent_class {
    // ...
}

And use it in function

function log (Parent_class $requester) {
    // ... code
}
Share:
33,925
Chris
Author by

Chris

Updated on July 05, 2022

Comments

  • Chris
    Chris almost 2 years

    Can I allow two different types using type hinting?

    E.g. parameter $requester could be either of User or File:

    function log (User|File $requester) {
    
    }
    
  • i336_
    i336_ almost 5 years
    I kind of don't believe this, but I just checked this out, sadly went and had a look at the RFC to brood about it, then decided to go look at the RFC index, AND UNION TYPES V2 WAS AT THE TOP OF THE RFC LIST. Wow. It literally started discussion 6 days ago. github.com/php/php-rfcs/pull/1 - it's the first RFC being discussed on GH too, even. Fascinating timing lol
  • jave.web
    jave.web almost 5 years
    @i336_ yes and I also think it will have a battle with this one - wiki.php.net/rfc/mixed-typehint
  • Katai
    Katai over 4 years
    One of the main needs for Union Types I repeatedly run into is that ArrayAccess doesn't allow for arrays to be passed. I've never found a good solution to work around that, defaulting to not have any type hinting at all in these cases.
  • Umair Khan
    Umair Khan over 3 years
    RFC for Union Types is accepted and implemented in PHP 8.
  • Adam
    Adam about 3 years
    Can you typehint exceptions for PHP 8 ?
  • yivi
    yivi about 3 years
    Why couldn't you? Exceptions are classes.
  • Adam
    Adam about 3 years
    I mean typehinting that a method may throw an exception - not returning it. myMethod(): void|MyException does not work
  • yivi
    yivi about 3 years
    It's a return type-hint. Whit that syntax you are saying "this method returns either void or an instance of MyException. There is no syntax for "may throw an exception", you need to use either annotations or attributes.