php function implementation overrides non-nullable argument

php implement multiple interfaces
why we use interface in php
php interface constructor
interface in php w3schools
php abstract class vs interface
php abstract function
php abstract class implements interface
why use abstract class in php

I ran into this today with PHP (7.1 and 7.2 at least) with the following code:

namespace PlaceHolderX\Tests\PHPUnit\Unit;

use PHPUnit\Framework\TestCase;

final class BreakingClassesTest extends TestCase
{

    public function testBreak(): void
    {
        $tester = new SomeClassA();
        $tester->test();
        $this->assertNull($tester->get());
    }

}

interface InterfaceA {

    public function test(string $testString): void;

}

class SomeClassA implements InterfaceA
{
    /** @var null|string */
    private $testString;

    public function test(string $testString = null): void
    {
        $this->testString = $testString;
    }

    public function get(): ?string
    {
        return $this->testString;
    }
}

So I have an interface (InterfaceA) that has a method that requires a string. This argument is not nullable, cause if I wanted that I would have specified it as:

public function test(?string $testString): void;

But in the implementation class (SomeClassA) I can override the argument definition with a default value of null which results in a behavior I didn't intend with my interface.

So my main question is: Why is this possible? Of course, we will need to check this in code reviews, but it is something that is easy to miss.

I tried searching what causes this behavior but was not able to find an explanation. Maybe my search criteria are off.

PHP allows you to set, or modify, default values in implementations as long as the type matches. One caveat is that all hinted types permit null as the default value.

If someone finds a specific explanation for this then I can update this answer, but prior to 7.1, the only way to declare an optional parameter was to assign a default value of null. The ?string syntax didn't exist, so this behavior may stem from that and still exists for backwards compatibility.

If you try to set a default value of say an integer, you'll see an error message that shows:

Fatal error: Default value for parameters with a string type can only be string or NULL

As of right now, it seems to be the developer's responsibility to ensure the default value of an implementation matches the interface declaration of nullable or not nullable.

PHP - override function with different number of parameters, http://www.php.net/manual/en/function.func-get-args.php you can declare them explicitely to NULL, so if they are not given, no error will occur  If you use defaultGreeter as implementation of Greeter and pass null through the interface (could be unintended), you loose the ability to switch the implementation to SimpleGreeter or produce fatal errors. Such code can potentially introduce unexpected behaviour and therefore i would avoid it.

Nullable PHPDoc overrides non-nullable typehint on methods , <?php declare(strict_types=1); namespace PurpleBooth\Example; class Test { /** * @param string|null $test */ public function callee(string  What is Overriding in PHP? Overriding is an Object-Oriented Programming concept that is similar to a concept like Class, Object, Encapsulation , Polymorphism , Overloading etc in PHP . Overriding of functions and classes are done when a method in the derived class is created which is the same as that of the method in the base class or parent class.

Superclass should be replaceable by its subclasses. So subclass must be able to do everything superclass does, but it can also do more.

In your example, superclass/interface does not know how to handle null. But subclass does, and it's fine because users of superclass will pass only non-nulls as they think superclass contract is in effect.

Class Abstraction - Manual, For example, if the abstract method is defined as protected, the function implementation must be defined Our child class may define optional arguments not in the parent's signature echo "myProduct overrides the defaultProductImplementation's doBuy() here {$this->_bought}". public function get_Age($today=NULL){ There is a function available called override_function that actually fits the bill. However, given that this function is part of The Advanced PHP Debugger extension, it's hard to make an argument that override_function() is intended for production use. Therefore, I would say "No", it is not possible to overwrite a function with the intent that the original questioner had in mind.

Object Interfaces - Manual, Prior to PHP 5.3.9, a class could not implement two interfaces that specified a method with the override constants. public function __construct($arg=null) Nullable types won't change anything in inherited method compatibility rules. We will still use Covariance for return types and Contravariance for arguments. This means, that methods with nullable return types may be overridden by methods with non-nullable, but methods with non-nullable return types can't be overridden by methods with nullable.

Anonymous functions - Manual, Anonymous functions are implemented using the Closure class. in /example.​php on line 6 NULL string(5) "hello" string(5) "hello" string(5) "hello" string(5) Global variables exist in the global scope, which is the same no matter what $c​('test CCC'); # this overwrites content held by $b, because it refers to the same object The vote for this RFC is split into two votes. One vote will be for accepting the idea of explicitly nullable types with the short-hand syntax. The second vote determines whether to merge only nullable return types or to also merge nullable parameter types as well.

Function arguments - Manual, Example #2 Passing function parameters by reference The default value must be a constant expression, not (for example) a variable, The declaration can be made to accept NULL values if the default value of the parameter is set to NULL . Note: By default, only userspace functions may be removed, renamed, or modified. In order to override internal functions, you must enable the runkit.internal_override setting in php.ini.

Comments
  • Thanks for your answer. It seems likely that this is one of the many things PHP would do to keep backward compatibility intact. I just wished it was better documented.
  • @RickVH, right. I think most languages permit null values in place of other types, so it's not uncommon. With Philip's pointing out of the rfc that was implemented in 7.2, it looks like your concern will be even worse since they can actually nullify the interface's type and allow types other than string/null to be passed, so going forward, the developer has even more responsibility if they want to stay true to the interfaces.
  • I agree. But this is good to know. I will set this post as the answer to my question. With a big thanks to @Phillip for pointing out that it will be getting worse.
  • I actually didn't know 7.2 enabled this, but I don't think it's related to what the OP is asking. The type hasn't been changed, or widened, in the declaration, just the default value has been assigned.
  • Imho a nullable type is a wider type than a non nullable type - doesn't matter, if there's a default value or not
  • But that behavior didn't change with the rfc you're referencing. A default value of null has always been permitted since the introduction of type hinting. (Your answer doesn't explain this behavior for 7.0 and 7.1)
  • @Philipp thanks for your answer, but I think I have to agree with Devon that the widening is not the reason for this behavior. Mainly as it is also present in 7.1. Being one of PHP backward compatibility features seems more likely. It is really annoying me though ;)