Skip to content

Instantly share code, notes, and snippets.

@Danack
Last active August 29, 2015 14:16
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/3826a5c9115d1ed1c869 to your computer and use it in GitHub Desktop.
Save Danack/3826a5c9115d1ed1c869 to your computer and use it in GitHub Desktop.
Draft of constructor behaviour.

Correct internal classes behaviour

Several internal classes in extensions that ship with PHP exhibit some highly surprising behaviour. This RFC has two aims:

i) to make the behaviour of these classes behave more consistently with the behaviour that most people would expect them to have.

ii) setting the coding standards that should be followed by internal classes as well as fixing the behaviour of the classes listed in this RFC.

If other internal classes that are not listed in this RFC are found to have behaviour that is not consistent with the behaviour listed in this RFC they should be treated as bugs and fixed at the next appropriate release.

Constructors returning null rather than throwing an exception

For the internal classes:

  • finfo
  • PDO
  • Collator
  • IntlDateFormatter
  • MessageFormatter
  • NumberFormatter
  • ResourceBundle
  • IntlRuleBasedBreakIterator

when their constructor is called with parameters that cannot be used to create a valid instance of the class, the constructor returns null. This is inconsistent with classes which are declared in userland which are not capable of returning null value, but instead have to indicate an error by throwing an exception.

Having a different behaviour for internal clases is inconsistent and highly suprising when users encounter it:

$mf = new MessageFormatter('en_US', '{this was made intentionally incorrect}');
if ($mf === null) {
    echo "Surprise!";
}

This RFC proposes setting a standard that internal classes should either return a valid instance of the class or throw an exception. Also for the classes listed for them to be changed in PHP 7 so that they follow this behaviour.

Factory methods

The classes IntlDateFormatter, MessageFormatter, NumberFormatter, ResourceBundle and IntlRuleBasedBreakIterator all have a static factory method that can create an instance of the class, as well as the constructor. The factory methods do not currently throw an exception when invalid arguments are passed to it. It is the position of this RFC that it is correct for those factory methods return either null or a valid usuable instance of the class.

PHP is a multi-paradigm programming language which support writing code in either procedural or OO style. Having the factory methods behave like this is perfectly consistent with userland code.

class NumberFormatter {
    static function create($locale , $style , $pattern = null) {
        try {
            return new NumberFormatter($locale, $style, $pattern);
        }
        catch(NumberFormatterException $nfe) {
            trigger_error($nfe->getMessage(), E_USER_ERROR);
            return null;
        }
    }
}

There is also a function finfo_open which is also a procedural way of instantiating an finfo object. This function would not be modified.

Constructors give warning, but are then in an unusable state

The constructors of several classes check the parameters that they are given and just give a warning when they are not acceptable. This leaves the object instantiated but in an unusable state.

<?php
//Reflection function is not meant to accept an array
$reflection = new ReflectionFunction([]);
//Outputs Warning: ReflectionFunction::__construct() expects parameter 1 to be string, array given in...

var_dump($reflection->getParameters());
//Fatal error: ReflectionFunctionAbstract::getParameters(): Internal error: Failed to retrieve the reflection object in..

This RFC proposes that this behaviour should be changed so that the constructor should throw an exception if the class cannot be created in usuable state i.e. convert the current warning to an exception of the appropriate.

The list of classes that show this behaviour is:

  • UConverter
  • SplFixedArray
  • ReflectionFunction
  • ReflectionParameter
  • ReflectionMethod
  • ReflectionProperty
  • ReflectionExtension
  • ReflectionZendExtension
  • Phar
  • PharData
  • PharFileInfo

Constructor generates fatal error.

The class PDORow gives a fatal error when a user attempts to instantiate it.

$foo = new PDORow();
// PHP Fatal error:  PDORow::__construct(): You should not create a PDOStatement manually in test.php on line 5

Fatal errors should only be used for fatal errors. This RFC proposes that the constructor for PDORow should be changed to throw an appropriate exception rather than giving a fatal error.

BC Break considerations

Judging by the bugs listed in bugs.php.net most people are not aware that the classes listed can show this behaviour. For these people none of the changes would be a BC break.

For people who are aware that the constructor can fail there would be a small BC break.

For the classes that have a factory creation method the code that currently tests against the constructor returning null:

$mf = new MessageFormatter('en_US', $thisMightBeInvalid);
if ($mf === null) {
    // error handling code
}

could be changed to using the factory method:

$mf = MessageFormatter::create('en_US', $thisMightBeInvalid);
if ($mf === null) {
    // error handling code
}

For the other classes which do not have an equivalent procedural method which will still return null, the user would need to wrap the call to the constructor in a try/catch block.

Patch incoming

I hope to fix all of these in a branch before opening the vote, so that people can see the details of the changes.

At the very least all of the exception to be used for each class will be determined, and will be an appropriate exception i.e. not \Exception.

Vote

Should the standard method for constructors for internal objects be to throw an exception if they encounter an error and should the code for the class below be modified to follow this standard?

These clases will be corrected by making the constructor throw an exception rather than return null if the construction of the object fails.

  • finfo
  • PDO
  • Collator
  • IntlDateFormatter
  • MessageFormatter
  • NumberFormatter
  • ResourceBundle
  • IntlRuleBasedBreakIterator

These class would be corrected by detecting the invalid data in the constructor and throwing an exception at object construction time, rather giving an error when the created instance is used.

  • UConverter
  • SplFixedArray
  • ReflectionFunction
  • ReflectionParameter
  • ReflectionMethod
  • ReflectionProperty
  • ReflectionExtension
  • ReflectionZendExtension
  • Phar
  • PharData
  • PharFileInfo

The class PDORow will be changed to give an exception if an attempt is made to instantiate it from userland.

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