Skip to content

Instantly share code, notes, and snippets.

@Danack
Last active August 26, 2016 12:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Danack/ee739f7fafa742a4c435ee89f6e0d80b to your computer and use it in GitHub Desktop.
Save Danack/ee739f7fafa742a4c435ee89f6e0d80b to your computer and use it in GitHub Desktop.
object RFC

Introduction

PHP 7 introduced scalar types for parameters and also for declaring return types for functions.

However it is not currently possible to declare that a function either needs to be passed an object as a parameter, or to declare that a function must return an object.

Proposal

This RFC proposes that 'object' should be used as a parameter type and as a return type. Any object would pass the type check.

Passing a value that is not an object to a parameter that is declared as type 'object' would fail the type check, and a TypeError would be thrown.

Similarly, if a function is declared as returning an 'object' but does not return an object, again a TypeError would be thrown.

As it would be used internally, object would become a reserved classname, unavailable for use as a class name in userland code.

For class methods that use object as either a parameter or return type, the same inheritance rules would apply for inherited methods as all other parameter/return types i.e. invariant.

Examples

Parameter type

function acceptsObject(object $obj) {
    ...
}

// This code can be statically analyzed to be correct
acceptsObject(json_decode('{}'));

// This code can be statically analyzed to be correct
acceptsObject(new \MyObject());

// This can be statically analysed to contain an error.
// and would throw an TypeError at runtime.
acceptsObject("Ceci n'est pas une object.");

Return type

Functions and methods can be declared with an object return type, to enforce that the function must return an object.

// This function can be statically analysed to conform to the
// return type
function correctFunction() : object {
    $obj = json_decode('{}');

    return $obj;
}

// This function can be statically analysed to contain an error
// and will also fail at runtime.
function errorFunction() : object {
    return [];
}

Benefits

Make code easier to understand

Although most code deals with specific types, there are several types of library where it is common to handle arbitrary object types.

Hydrators + data extractor

Hydrators allow objects to be filled with data from an array. Extractors do the opposite, they take an object and return an array of data.

interface Hydration {
  // Hydrate an object by populating data
  public function hydrate(array $data, object $object) : object;
}

interface Extraction {
  // Extract values from an object
  public function extract(object $object) : array;
}

The extraction step can take an arbitrary object as the sole parameter. The hydration step can take an arbitrary object as the second parameter, and will return an arbitrary object. Having the type for the parameters and the return type be set as 'object' will make the expected types be clearer to anyone using these functions, as well as detect incorrect types if there is an error in the code.

Service containers and DIC libraries

For both service containers and dependency injection libraries, it is common to want to put services and other objects into the container.

interface ServiceContainer {
  // Set service definition
  public function set(string $id, object $service);

  // Retrieve service object from container
  public function get(string $id) : object;
}

Additionally, ORM libraries such as Doctrine have functions that will either consume or produce arbitrary objects.

Catching return errors

Having an object return type would allow some errors to be detected more quickly. The following function is meant to return an object. With the object return typ set for the function, failing to return an object would cause a TypeError to be thrown in the location where the bug is.

function unserialize($data) : object {

    $type = $data['type'];
    
    switch ($type) {
        case 'foo': { return new Foo(); }
        case 'bar': { return new Ba(); }
        case 'zot': { new zot(); }  // Ooops, this is an error
    }
}

Without the object return type, an incorrect value of null is returned from the function. This error can only be found by debugging at runtime.

Enforcing signature in inheritance

Currently, as PHP does not allow 'object' to be used as a return type, it is not possible to enforce the return type in a child class. In this example the method is supposed to return an object, but a programmer has changed what the function does in the child class.

class WidgetFactory {
    function create() {
        return new Widget();
    }
}

class CustomWidgetFactory extends WidgetFactory {
    function create() {
        $object = new Widget();

        return true; //This is an error that cannot be statically analyzed.
    }
}

This type of error can only be detected when running the code.

If we had object as a type, even if a programmer misunderstood what the method was supposed to do, and accidentally tried to create a child class that had a different signature, the code would not compile, and the error would be caught before the code is run:

class WidgetFactory {
    function create() : object {
        return new Widget();
    }
}

class CustomWidgetFactory extends WidgetFactory {
    // This class would not compile, as the signature of the metod in
    // the child class is not compatible with the method signature in 
    // the parent class.
    function create() : bool {
       ...
    }
}

If the programmer wrote the correct signature for the method in the child class, but returned the wrong value, this error would also be caught:

class WidgetFactory {
    function create() : object {
        return new Widget();
    }
}

class CustomWidgetFactory extends WidgetFactory {
    function create() : object {
        $object = new Widget();

        // returning something that is not an object throws a TypeError exception.
        return true;
    }
}

This would also be an error detectable by a static analyzer.

Reflection

There are two changes in reflection:

  • ReflectionType::isBuiltin returns true for parameters and return types declared as 'object'.

  • ReflectionType::__toString returns “object” for parameters and return types declared as 'object'.

Backward Incompatible Changes

Althought object is soft reserved word this RFC adds object as reserved classname. This may collide with existing classes which are neither namespaced nor prefixed.

Proposed PHP Version(s)

PHP 7.2.

RFC Impact

Unaffected PHP Functionality

This doesn't affect the behaviour of cast operators.

Proposed Voting Choices

As this is a language change, a 2/3 majority is required. The vote is a straight Yes/No vote for accepting the RFC and merging the patch.

@brzuchal
Copy link

I really like it the way you rewrote this :)

@brzuchal
Copy link

@brzuchal
Copy link

I've added Container example to the same gist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment