Functions in PHP 5.6 - what's new?


    From left to right: Rasmus , Build 5.4 , Version 5.5 , Relise 5.6

    Today I want to share my vision of how work with functions will look in the next major PHP release - 5.6. To do this, I studied the working proposals and found a lot of sweets there:

    • New syntax for functions with a variable number of arguments and a joyful departure to the history of hassle with func_get_args () :
      function fn($reqParam, $optParam = null, ...$params) { }
      

    • Omit the indication of values ​​for optional arguments:
      function create_query($where, $order_by, $join_type = '', $execute = false, $report_errors = false) { }
      create_query('deleted=0', 'name', default, default, /*report_errors*/ true);
      

    • Import functions from namespace:
      use function foo\bar\baz;
      baz();
      

    • Exceptions instead of fatal bugs:
      method();
      }
      call_method(null); // oops!
      try {
          call_method(null); // oops!
      } catch (EngineException $e) {
          echo "Cool Exception: {$e->getMessage()}\n";
      }
      

    • Adding the deprecated modifier :
      deprecated function myOldFunction() { }
      

    • Calling methods and accessing the properties of the created object:
      new foo()->xyz;
      new baz()->bar(); 
      

    Most of the proposals are still under discussion. But among them are already approved and even implemented.

    An exclusive reader is also waiting for a sophisticated reader : studying other people's clever thoughts, I myself decided to write my own RFC. Now you will not see him in the list of offers, since at the moment he is at the very initial stage - on the mailing list internals@lists.php.net .

    And I’ll start the review with RFC, which has already been implemented and is guaranteed to fall into release 5.6.

    Syntax for Functions with Variable Arguments

    Implemented in PHP 5.6, Accepted by 36 votes against 1

    And immediately into battle: consider a code that shows as a variable argument ... $ params will be filled depending on the number of arguments passed:
    function fn($reqParam, $optParam = null, ...$params) {
        var_dump($reqParam, $optParam, $params);
    }
    fn(1);             // 1, null, []
    fn(1, 2);          // 1, 2, []
    fn(1, 2, 3);       // 1, 2, [3]
    fn(1, 2, 3, 4);    // 1, 2, [3, 4]
    fn(1, 2, 3, 4, 5); // 1, 2, [3, 4, 5]
    

    $ params will be an empty array if the number of arguments passed is less than the number declared. All subsequent arguments will be added to the $ params array (preserving the order). Indexes in the $ params array are populated from 0 and ascending.

    Currently, functions with a variable number of arguments are implemented using the func_get_args () function . The following example shows how a function with a variable number of arguments is being implemented to prepare and execute a MySQL query:
    class MySQL implements DB {
        protected $pdo;
        public function query($query) {
            $stmt = $this->pdo->prepare($query);
            $stmt->execute(array_slice(func_get_args(), 1));
            return $stmt;
        }
        // ...
    }
    $userData = $db->query('SELECT * FROM users WHERE id = ?', $userID)->fetch();
    

    Problems of the old approach and how to solve them.
    First, looking at the syntax of a public function query ($ query) function , it is impossible to understand that this is, in fact, a function with a variable number of arguments. It seems that this function is only executed with a normal request and does not support additional arguments.

    Secondly, since func_get_args () returns all the arguments passed to the function, you first need to remove the $ query parameter using array_slice (func_get_args (), 1) .

    This RFC proposes to solve these problems by adding special syntax for functions with a variable number of arguments:
    class MySQL implements DB {
        public function query($query, ...$params) {
            $stmt = $this->pdo->prepare($query);
            $stmt->execute($params);
            return $stmt;
        }
        // ...
    }
    $userData = $db->query('SELECT * FROM users WHERE id = ?', $userID)->fetch();
    

    The syntax ... $ params signals that it is a function with a variable number of arguments, and that all arguments after $ query must be stored in the $ params array . Using the new syntax, we solve both of the above problems.

    New syntax features

    • function fn ($ arg, ... $ args) : collects all variable arguments into an array of $ args
    • function fn ($ arg, & ... $ args) : collects by reference
    • function fn ($ arg, array ... $ args) : ensures that all variable arguments are arrays (it is possible to specify any other type)

    Benefits

    • Visibility: it’s immediately clear that this is a function with a variable number of arguments, without having to read the documentation.
    • There is no need to use array_slice () to get variable arguments from func_get_args ()
    • You can pass variable arguments by reference
    • You can specify the type of variable arguments
    • Type control of variable arguments via interface or inheritance



    Overriding the argument list is exclusive!

    It is discussed at internals@lists.php.net

    And so, at the moment, my proposal is already significantly different from the original one and comes down to the following: provide the ability to change the list of method arguments (their number and / or type) when inheriting classes:
    class Figure
    {
        public function calcPerimeter(array $angles)
        {
            return array_sum($angles);
        }
    }
    class Square extends Figure
    {
        public function calcPerimeter($angle)
        {
            return 4 * $angle;
        }
    }
    class Rectangle extends Figure
    {
        public function calcPerimeter($height, $width)
        {
            return 2 * ($height + $width);
        }
    }
    

    Now you can use both implementations of calculating the perimeter:
    $square = new Square();
    var_dump($square->calcPerimeter(array(1, 1, 1, 1))); // 4
    var_dump($square->calcPerimeter(1)); // 4
    

    The greater visibility and naturalness of the second call is obvious. Of course, something like this can be implemented confused with func_get_args () . But the proposed approach gives full clarity of exactly what the method implements, makes the code cleaner, eliminating the if-else cascade (by the number and type of arguments). In a clever way, this is called situational polymorphism , which has long and successfully been used in C ++, Java / Scala, Delphi, and many other languages. I am sure that the ability to change the set of method arguments during inheritance is very useful. Backward compatibility issues should not occur.

    Omit values ​​for optional arguments

    Discussion

    Since PHP does not support named parameters , quite often functions contain many optional parameters:
    function create_query($where, $order_by, $join_type='', $execute = false, $report_errors = true) {...}
    

    If we always use the default value, then there are no problems. But what if we only need to change the $ report_errors parameter and leave the rest unchanged? To do this, we have to find the definition of the function and copy all the other default values ​​into the call. And this is already quite boring, potentially buggy and can break if some of the default values ​​change.

    The suggestion is to allow to omit the indication of values ​​for default arguments in the call, thus preserving their default value:
    create_query("deleted=0", "name", default, default, /*report_errors*/ true);
    

    In the code above, $ join_type and $ execute will have a default value. Naturally, in this way we can omit only optional parameters. In case of required parameters, the same error will be generated as now, when insufficient arguments are passed to the function call.

    Problems

    The problem will arise with the support of internal functions using the manual call ZEND_NUM_ARGS () . The author of the proposal looked through all the functions in the standard distribution, but PECL extensions that do not use zend_parse_parameters or do it in a strange way with zval-type without correctly initializing them or checking the output results may require fixes. In other words, they can break if you pass default with a null pointer to a link. This does not look like a big problem from a security point of view, but in any case is not very pleasant.

    Import functions from namespace

    Adopted by 16 votes to 4, It is proposed to include in PHP 5.6

    PHP provides the ability to import namespaces and types (classes, interfaces, traits) through the use statement. However, there is no such possibility for functions. As a result, working with functions in the namespace looks rather cumbersome.

    A function can be called without specifying a full name only if the call is in the same namespace as the function itself:
    namespace foo\bar {
        function baz() {
            return 'foo.bar.baz';
        }
    }
    namespace foo\bar {
        function qux() {
            return baz();
        }
    }
    namespace {
        var_dump(foo\bar\qux());
    }
    

    You can avoid specifying the full name by importing the namespace in which the function is defined. However, its alias must still be specified when calling the function:
    namespace foo\bar {
        function baz() {
            return 'foo.bar.baz';
        }
    }
    namespace {
        use foo\bar as b;
        var_dump(b\baz());
    }
    

    Unable to import function directly. So far, PHP does not support this.

    Detailed proposal
    The essence of the proposal is to add a new combination of keywords that will allow you to import functions in the namespace. This should greatly facilitate the work with such functions and avoid the implementation of functions in the global namespace.

    Using the same use statement for both the class namespace and functions will most likely lead to conflicts and overhead.

    Instead of inventing a new operator, you can simply use a combination of use and function :
    namespace foo\bar {
        function baz() {
            return 'foo.bar.baz';
        }
        function qux() {
            return baz();
        }
    }
    namespace {
        use function foo\bar\baz, foo\bar\qux;
        var_dump(baz());
        var_dump(qux());
    }
    

    Moreover, this approach could be used not only for functions, but also for constants:
    namespace foo\bar {
        const baz = 42;
    }
    namespace {
        use const foo\bar\baz;
        var_dump(baz);
    }
    

    As with classes, it should be possible to do aliases for imported functions and constants:
    namespace {
        use function foo\bar as foo_bar;
        use const foo\BAZ as FOO_BAZ;
        var_dump(foo_bar());
        var_dump(FOO_BAZ);
    }
    


    Key questions and answers

    Why not just import the namespace?

    Indeed, you can import the namespace. This is not critical for classes and, moreover, for functions. But there is one case where importing functions can significantly improve code readability. This will be very useful when using small libraries, which are just a few functions. You could put them in a namespace, for example, by the name of the author: igorw \ compose () . This would help to avoid conflicts. And the user of such a function, who does not care about the name of its author, could just write compose () . Instead, the user is forced to invent a new meaningless alias just to use the function.

    Return to the global namespace

    By default, PHP will look for functions in the local namespace, and then it will return to the global. For functions that have been imported by the use statement , returning to the global namespace should not be necessary.
    namespace foo\bar {
        function strlen($str) {
            return 4;
        }
    }
    namespace {
        use function foo\bar\strlen;
        use function foo\bar\non_existent;
        var_dump(strlen('x'));
        var_dump(non_existent());
    }
    

    The call to strlen () is now unique. non_existent () is no longer searched in the global namespace.

    Why do we need the use function operator , why not just use ?

    In PHP, functions and classes are stored in separate namespaces. Function foo \ bar and class foo \ bar can coexist because of the context we can understand that we use (class or function):
    namespace foo {
        function bar() {}
        class bar {}
    }
    namespace {
        foo\bar(); // function call
        new foo\bar(); // class instantiation
        foo\bar::baz(); // static method call on class
    }
    

    If the use statement supports importing functions, this will cause backward compatibility issues.

    Example:
    namespace {
        function bar() {}
    }
    namespace foo {
        function bar() {}
    }
    namespace {
        use foo\bar;
        bar();
    }
    

    Behavior changed after use changed . Depending on the version of PHP, different functions will be called.


    Exceptions instead of fatal errors

    In discussion , Proposed for PHP 5.6.

    This RFC suggests allowing exceptions instead of fatal errors.

    For clarity, consider the following piece of code:
    method();
    }
    call_method(null); // oops!
    

    Now this code will lead to a fatal error:
    Fatal error: Call to a member function method() on a non-object in /path/file.php on line 4
    

    This RFC replaces the fatal error with an EngineException . If the exception is not processed, we still get a fatal error:
    Fatal error: Uncaught exception 'EngineException' with message 'Call to a member function method() on a non-object' in /path/file.php:4
    Stack trace:
    #0 /path/file.php(7): call_method(NULL)
    #1 {main}
      thrown in /path/file.php on line 4
    

    Of course, its not a problem to handle:
    try {
        call_method(null); // oops!
    } catch (EngineException $e) {
        echo "Exception: {$e->getMessage()}\n";
    }
    // Exception: Call to a member function method() on a non-object
    

    Potential problems
    Совместимость с E_RECOVERABLE_ERROR

    Сейчас можно игнорировать восстанавливаемые фатальные ошибки, используя кастомный обработчик ошибок. Заменив их на исключения, мы потеряем такую возможность, ломая, таким образом обратную совместимость.

    Я никогда не видел применения этой возможности на практике (не считая грязных хаков). В большинстве случаев, кастомный обработчик событий выкидывает ErrorException, то есть эмилирует предложенное поведение, лишь с другим типом исключения.

    Catch-all блоки в уже существующем коде

    Так как EngineException расширяет Exception, он будет отлавливаться блоками catch с типом Exception. Это может привести к незапланированному отлову исключений. Решением этой проблемы могло бы быть введение BaseException, который был бы родителем Exception. От BaseException можно было бы наследовать только те исключения, которые нежелательно отлавливать. Такой подход используется в Python (BaseException) и Java (Throwable).


    Добавление модификатора deprecated

    In discussion,

    This RFC suggests adding a deprecated modifierfor methods and functions that assign the ZEND_ACC_DEPRECATED flag to functions, thus throwing an E_DEPRECATED exception when called.

    Why is this needed?

    Marking used functions and methods as deprecated is common practice for large PHP frameworks when releasing new versions. Later, these functions are completely removed. Native functions require only the ZEND_ACC_DEPRECATED flagin order for Zend to automatically generate an E_DEPRECATED error when called . However, user-defined functions can only be deprecated by adding the @deprecated tag .(in the documentation comment) or by generating the error E_USER_DEPRECATED . But why should a typical developer generate an error manually, while a native function requires only a flag?

    Marking a function obsolete by simply adding a deprecated modifier will give developers greater readability, speed, and visibility.

    The error E_USER_DEPRECATED can be used rather for marking as an outdated whole library or a way of calling functions.

    Also, the ReflectionFunction :: isDeprecated () method is currently useless for custom functions. And this RFC solves the indicated problem.

    deprecated function myFunction() {
        // ...
    }
    myFunction();
    

    Deprecated: Function myFunction() is deprecated in ... on line 5
    

    class MyClass {
        public deprecated static function myMethod() {
            // ...
        }
    }
    MyClass::myMethod();
    

    Deprecated: Function MyClass::myMethod() is deprecated in ... on line 7
    


    Calling methods and accessing properties of the created object

    In discussion

    The purpose of this RFC is to provide support for calling methods and accessing properties of the created object in a single line. We can use one of the two syntaxes below.

    Syntax 1 (without brackets)

    • new foo-> bar ()
    • new $ foo () -> bar
    • new $ bar-> y () -> x

    class foo {
    	public $x = 'testing';
    	public function bar() {
    		return "foo";
    	}
    	public function baz() {
    		return new self;
    	}
    	static function xyz() {
    	}
    }
    var_dump(new foo()->bar());               // string(3) "foo"
    var_dump(new foo()->baz()->x);            // string(7) "testing"
    var_dump(new foo()->baz()->baz()->bar()); // string(3) "foo"
    var_dump(new foo()->xyz());               // NULL
    new foo()->www();                         // Fatal error: Call to undefined method foo::www()
    

    Syntax 2 (with brackets)

    • (new foo ()) -> bar ()
    • (new $ foo ()) -> bar
    • (new $ bar-> y) -> x
    • (new foo) [0]

    class foo {
    	public $x = 1;
    }
    class bar {
    	public $y = 'foo';
    }
    $x = 'bar';
    $bar = new bar;
    var_dump((new bar)->y);     // foo
    var_dump((new $x)->y);      // foo
    var_dump((new $bar->y)->x); // 1
    


    Epilogue


    That's it. The list of changes turned out to be quite impressive, but it affects only the work with functions! Personally, I really like the planned innovations, I want to try them now :)

    I will be glad to discuss all these innovations and, of course, start a heated discussion around my RFC. And, perhaps, someone else will offer his own :)

    See you in the comments!

    Also popular now: