89

I have some methods that can return one of two return types. I'm using a framework utilizing MCV so refactoring these few functions in particular is not appealing.

Is it possible to declare the return type returning one or the other and failing on anything else?

function test(): ?
{
    if ($this->condition === false) {
        return FailObject;
    }

    return SucceedObject;
}
1
  • 4
    Only if both FailObject and SucceedObject both share an interface or extend from a common parent
    – Mark Baker
    Commented May 4, 2016 at 16:22

5 Answers 5

161

As of PHP 8+, you may use union types:

function test(): FailObject|SuccessObject {}

Another way, available in all versions since PHP 4, is for the two objects to share an interface. Example:

interface ReturnInterface {}
class FailObject implements ReturnInterface {}
class SuccessObject implements ReturnInterface {}
function test(): ReturnInterface {}

In this example, ReturnInterface is empty. Its mere presence supports the needed return type declaration.

You could also use a base, possibly abstract, class.


To me, for this use case, interfaces are more clear and more extensible than union types. For example, if I later want a WarnObject I need only to define it as extending ReturnInterface -- rather than going through all signatures and updating them to FailObject|SuccessObject|WarnObject.

9
  • 2
    You could also use an abstract class here.
    – mystery
    Commented May 16, 2016 at 21:32
  • 12
    This is really limiting: think at entites (used, for example, by Doctrine): it is not possible to hint the return to null|Entity. Really limiting...
    – Aerendir
    Commented Oct 24, 2016 at 15:32
  • 2
    @Aerendir a solution/workaround for this would be having the repository method return an Option that wraps the null type or Entity. Here's an example of a package that provides an Option type: github.com/schmittjoh/php-option
    – John Hall
    Commented Aug 11, 2017 at 11:39
  • 4
    @tangobango As noted in the answer: no, the RFC was declined.
    – bishop
    Commented Jan 17, 2018 at 13:30
  • 22
    @Aerendir null|Entity is available in PHP 7.1 with nullable types: ?Entity. See secure.php.net/manual/en/migration71.new-features.php.
    – 0b10011
    Commented Apr 2, 2018 at 19:12
85

As noted by bishop, there is an RFC for adding multiple return types. However, I thought I'd add that as of PHP7.1 you can now specify a nullable return type like this:

function exampleFunction(string $input) : ?int
{
    // Do something
}

So this function would take in a string and by adding the question mark before int you are allowing it to return either null or an integer.

Here's a link to the documentation: http://php.net/manual/en/functions.returning-values.php

And here's a quote from that page explaining the usage: PHP 7.1 allows for void and null return types by preceding the type declaration with a ? — (e.g. function canReturnNullorString(): ?string)

Also, here's another thread that relates to this: Nullable return types in PHP7

2
  • "allows for void and null return types by" Returning null works for me, but void throws an exception. "...or null, none returned ..." Commented Oct 28, 2020 at 17:01
  • If I use "return;" the error becomes: "A function with return type must return a value" Commented Oct 28, 2020 at 17:02
27

PHP from 7.2 onward supports the object return type

http://php.net/manual/en/migration72.new-features.php

function test(object $obj) : object
// return any type of object ...
12

Since PHP 8.0 this is possible.

You can now use union types to specify this:

function test(): Success|Failure
{
    if ($this->condition === false) {
        return new Failure();
    }

    return new Success();
}

The fact that this is possible does not mean that it is always advisable. In many (probably most) situations, using an interface (e.g. Result, which both Failure and Failure would implement) as advised in a different answer, is still much preferable.

But there are other instances where union types could make sense to an alternative to weak typing. E.g. a method that accepts both string and int, or to describe the return type of a function like stripos(), which returns int|false.

-10

This ins't correct way:

function test(): ?
{
    if ($this->condition === false) {
        return FailObject;
    }

    return SucceedObject;
}

Multiple return type is a bad practice. Good practices:

You should define a exception:

class FailObjectException extends \Exception
{
    private $exampleExtraInfo;

    public function __construct($exampleExtraInfo, $message)
    {
        parent::__construct($message);
        $this->exampleExtraInfo = $exampleExtraInfo;
    }

    public function exampleExtraInfo(): int
    {
        return $this->exampleExtraInfo;
    }
}

Now, you can define function like:

function test(): SucceedObject
{
    if ($this->condition === false) {
        throw new FailObjectException(...,...);
    }

    return SucceedObject;
}

And use this function with try/catch:

try{
    $succeedObject = $any->test();
} catch (FailObjectException $exception){
    //do something
}
5
  • 7
    Exceptions should not be used to control the flow of an application. You're essentially treating the Exception here as an if statement, which is not the correct usage of an exception. Commented Oct 10, 2019 at 13:17
  • are you saying that failure case isn't a exception? Mmm... Better return a "FailObject" and control multiple outputs: if, elseif, elseif, else. (NO) Commented Oct 11, 2019 at 15:12
  • I'm suggesting the code is structured correctly so that you don't need to rely on either :) Commented Oct 14, 2019 at 13:31
  • It's necesaty set stric types to write clean, maintainable and robust code. Descripted case (with 'FailObject') is a manual exception. It is not a exception to controll the flow. The flow continue with success case. You dont have to controll the exception before app controller. Commented Oct 15, 2019 at 15:55
  • @calmohallag is missing the point that FailObject in OP's question does not have to mean an error. Could also be said that the returned object (even though differrent in type) can be used for control flow. Commented May 17 at 7:38

Not the answer you're looking for? Browse other questions tagged or ask your own question.