Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Функции в PHP 5.6 — что нового?

Anna | 31.05.2014 | нет комментариев

Слева направо: RasmusBuild 5.4Version 5.5Relise 5.6

Сегодня хочу поделиться своим видением того, как будет выглядеть работа с функциями теснее в ближайшем мажорном релизе PHP — 5.6. Для этого я изучил рабочие предложения и нашёл там много вкусняшек:

  • Новейший синтаксис для функций с переменным числом доводов и счастливый отход в историю мороки сfunc_get_args():
    function fn($reqParam, $optParam = null, ...$params) { }
    
  • Спускаем указание значений для необязательных доводов:
    function create_query($where, $order_by, $join_type = '', $execute = false, $report_errors = false) { }
    create_query('deleted=0', 'name', default, default, /*report_errors*/ true);
    
  • Импорт функций из пространства имён:
    use function foobarbaz;
    baz();
    
  • Исключения взамен набивших оскомину неизбежных ошибок:
    <?php
    function call_method($obj) {
        $obj->method();
    }
    
    call_method(null); // oops!
    
    try {
        call_method(null); // oops!
    } catch (EngineException $e) {
        echo "Cool Exception: {$e->getMessage()}n";
    }
    
  • Добавление модификатора deprecated:
    deprecated function myOldFunction() { }
    
  • Вызов способов и доступ к свойствам создаваемого объекта:
    new foo()->xyz;
    new baz()->bar(); 
    

Множество из приведенных предложений пока находятся на стадии обсуждения. Но среди них теснее есть утверждённые и даже реализованные.

Также искушённого читателя ждёт эксклюзив: постигая чужие мудрые мысли, я и сам решился написать личный RFC. Теперь вы не увидите его в списке предложений, так как на данный момент он находится на самом исходном этапе — на рассылке internals@lists.php.net.

А начну обзор с RFC, тот, что теснее реализован и гарантированно попадает в релиз 5.6.

Синтаксис для функций с переменным числом доводов

Реализовано в PHP 5.6, Принято 36 голосами вопреки 1

И сразу в бой: разглядим код, тот, что показывает как переменный довод …$params будет заполняться в зависимости от числа переданных доводов:

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 будет пустым массивом, если число переданных доводов поменьше, чем число объявленных. Все дальнейшие доводы будут добавлены в массив $params (с сохранением порядка). Индексы в массиве $paramsзаполняются от 0 и по возрастанию.

На данный момент функции с переменным числом доводов реализуются при помощи функции func_get_args(). Дальнейший пример показывает, как теперь реализуется функция с переменным числом доводов для подготовки и выполнения запроса MySQL:

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();

Задачи ветхого подхода и как их решить.

Во-первых, глядя на синтаксис функции public function query($query) немыслимо осознать, что это, собственно говоря, функция с переменным числом доводов. Кажется, что это функция выполняется только с обыкновенным запросом и не поддерживает дополнительных доводов.

Во-вторых, так как func_get_args() возвращает все доводы, переданные в функцию, то вам вначале нужно удалить параметр $query применяя array_slice(func_get_args(), 1).

Данное RFC предлагает решить эти задачи добавлением особого синтаксиса для функций с переменным числом доводов:

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();

Синтаксис …$params сигнализирует о том, что это функция с переменным числом доводов, и что все доводы позже $query обязаны быть внесены в массив $params. Применяя новейший синтаксис, мы решаем обе вышеперечисленные задачи.

Вероятности нового синтаксиса
  • function fn($arg, …$args): собирает все переменные доводы в массив $args
  • function fn($arg, &…$args): собирает по ссылке
  • function fn($arg, array …$args): гарантирует, что все переменные доводы будут массивами (допустимо указать всякий иной тип)
Превосходства
  • Наглядность: сразу ясно, что это функция с переменным числом доводов, без необходимости читать документацию.
  • Нет потребности применять array_slice() для приобретения переменных доводов из func_get_args()
  • Дозволено передавать переменные доводы по ссылке
  • Дозволено указать тип переменных доводов
  • Контроль типа переменных доводов через интерфейс либо наследование

Переопределение списка доводов — эксклюзив!

Обсуждается на internals@lists.php.net

И так, на данные момент моё предложение теснее значительно отличается от изначального и сводится к дальнейшему: предоставить вероятность изменять список доводов способа (их число и/или тип) при наследовании классов:

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);
    }
}

Сейчас дозволено применять обе реализации расчёта периметра:

$square = new Square();
var_dump($square->calcPerimeter(array(1, 1, 1, 1))); // 4
var_dump($square->calcPerimeter(1)); // 4

Огромная наглядность и естественность второго вызова очевидна. Безусловно, что-то сходственное дозволено реализовать заморочившись с func_get_args(). Но предложенный подход даёт полную ясность того, что реально реализовывает способ, делает код больше чистым, освобождая от каскада if-else (по числу и типу доводов). Я уверен, что вероятность изменять комплект доводов способа при наследовании — это дюже благотворно. Задач с обратной совместимостью появиться не должно.

Спускаем указание значений для необязательных доводов

На обсуждении

Так как в PHP нет поддержки именованных параметров, достаточно Зачастую функции содержат много необязательных параметров:

function create_query($where, $order_by, $join_type='', $execute = false, $report_errors = true) {...}

Если мы неизменно используем дефолтные значение, то задач не появляется. Но что, если нам нужно поменять только параметр $report_errors, а остальные оставить без изменений? Для этого нам придётся обнаружить определение функции и скопировать все остальные дефолтные значения в вызов. А это теснее достаточно тоскливо, допустимо глючно и может поломаться, если часть дефолтных значений изменится.

Предложение заключается в том, Дабы позволить спускать указание значений для дефолтных доводов в вызове, сберегая, таким образом, их значение по умолчанию:

create_query("deleted=0", "name", default, default, /*report_errors*/ true);

В коде выше, $join_type и $execute будут иметь дефолтное значение. Безусловно, таким образом мы можем спускать только необязательные параметры. В случае непременных параметров будет генерироваться такая же оплошность, как и теперь, когда в вызов функции передано неудовлетворительно доводов.

Задачи

Задача возникнет с помощью внутренних функций, использующих ручной вызов ZEND_NUM_ARGS(). Автор предложения просмотрел все функции в стандартном дистрибутиве, впрочем растяжения PECL, которые не применяют using zend_parse_parameters либо делают это необычным образом с zval-type без их правильной инициализации либо проверки выходных итогов, могут требовать фиксов. Другими словами, они могут поломаться, если передать default с нулевым указателем на ссылку. Это не выглядит огромный задачей с точки зрения безопасности, но в любом случае не особенно славно.

Импорт функций из пространства имён

Принято 16 голосами вопреки 4, Предложено включить в PHP 5.6

PHP предоставляет вероятность импортировать пространства имён и типы (классы, интерфейсы, трейты) через оператор use. Тем не менее, для функций такой вероятности нет. В итоге, работа с функциями в пространстве имён выглядит достаточно массивно.

Функцию дозволено вызвать без указания полного имени, только если вызов находится в том же пространстве имён, что и сама функция:

namespace foobar {
    function baz() {
        return 'foo.bar.baz';
    }
}

namespace foobar {
    function qux() {
        return baz();
    }
}

namespace {
    var_dump(foobarqux());
}

Дозволено избежать указания полного имени, если импортировать пространство имён, в котором определена функция. Впрочем его алиас всё равно должен быть указан при вызове функции:

namespace foobar {
    function baz() {
        return 'foo.bar.baz';
    }
}

namespace {
    use foobar as b;
    var_dump(bbaz());
}

Немыслимо импортировать функцию напрямую. Пока что PHP не поддерживает это.

Детальная суть предложения

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

Применение одного и того же оператора use и для пространства имён классов, и для функций, скорее каждого, приведёт к раздорам и оверхеду.

Взамен изобретения нового оператора, дозволено легко применять комбинацию use и function:

namespace foobar {
    function baz() {
        return 'foo.bar.baz';
    }
    function qux() {
        return baz();
    }
}

namespace {
    use function foobarbaz, foobarqux;
    var_dump(baz());
    var_dump(qux());
}

Причём, такой подход дозволено было бы применять не только для функций, но и для констант:

namespace foobar {
    const baz = 42;
}

namespace {
    use const foobarbaz;
    var_dump(baz);
}

Так же как и для классов, должна быть вероятность делать алиасы для импортируемых функций и констант:

namespace {
    use function foobar as foo_bar;
    use const fooBAZ as FOO_BAZ;
    var_dump(foo_bar());
    var_dump(FOO_BAZ);
}
Основные вопросы и результаты
Отчего бы легко не импортировать пространство имён?

Подлинно, вы можете импортировать пространство имён. Это не критично для классов и, подавно, для функций. Но есть один случай, когда импорт функций может ощутимо усовершенствовать читаемость кода. Это будет дюже благотворно при применении маленьких библиотечек, которые представляют собой легко несколько функций. Дозволено было бы помещать их в пространство имён, скажем, по имени автора:igorwcompose(). Это бы помогло избежать раздоров. И пользователь такой функции, которому нет дела до того как зовут её автора, мог бы легко писать compose(). Взамен этого пользователь вынужден изобретать новейший лишенный смысла алиас легко для того, Дабы применять функцию.

Возврат в глобальное пространство имён

По умолчанию PHP будет искать функции в локальном пространстве имён, а после этого будет возвращаться в глобальное. Для функций, которые были импортированы оператором use, возврата в глобальное пространство имён быть недолжно.

namespace foobar {
    function strlen($str) {
        return 4;
    }
}

namespace {
    use function foobarstrlen;
    use function foobarnon_existent;
    var_dump(strlen('x'));
    var_dump(non_existent());
}

Вызов strlen() сейчас однозначен. non_existent() больше не ищется в глобальном пространстве имён.

Для чего необходим оператор use function, отчего не легко use?

В PHP функции и классы хранятся в отдельных пространствах имён. Функция foobar и класс foobar могут сосуществовать, потому что из контекста дозволено осознать, что мы используем (класс либо функцию):

namespace foo {
    function bar() {}
    class bar {}
}

namespace {
    foobar(); // function call
    new foobar(); // class instantiation
    foobar::baz(); // static method call on class
}

Если оператор use будет поддерживать импорт функций, то это повлечёт задачи с обратной совместимостью.

Пример:

namespace {
    function bar() {}
}

namespace foo {
    function bar() {}
}

namespace {
    use foobar;
    bar();
}

Поведение изменилось позже того, как поменялся use. В зависимости от версии PHP, будут вызваны различные функции.

Исключения взамен неизбежных ошибок

На обсуждении, Предложено для PHP 5.6

Это RFC предлагает позволить применение исключений взамен неизбежных ошибок.

Для наглядности предложения, разглядим дальнейший кусок кода:

<?php
function call_method($obj) {
    $obj->method();
}
call_method(null); // oops!

Теперь данный код приведёт к неизбежной ошибке:

Fatal error: Call to a member function method() on a non-object in /path/file.php on line 4

Данный RFC заменяет неизбежную ошибку на EngineException. Если исключение не будет обработано, мы всё равно получим неизбежную ошибку:

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

Безусловно, его не задача и обработать:

try {
    call_method(null); // oops!
} catch (EngineException $e) {
    echo "Exception: {$e->getMessage()}n";
}

// Exception: Call to a member function method() on a non-object

Потенциальные задачи

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

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

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

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

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

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

На обсуждении

Данный RFC предлагает добавить модификатор deprecated для способов и функций, которые присваивают функциям флаг ZEND_ACC_DEPRECATED, таким образом, при вызове выкидывая исключение E_DEPRECATED.

Для чего это необходимо?

Помечать используемые функции и способы как устаревшие — это обыкновенная практика для крупных PHP фреймворков при релизе новых версий. Позднее эти функции убираются окончательно. Нативные функции требуют только флага ZEND_ACC_DEPRECATED для того, Дабы Zend при их вызове механически сгенерировал ошибку E_DEPRECATED. Тем не менее, пользовательские функции могут быть подмечены как устаревшие только добавлением тега @deprecated (в комментарии документации) либо генерацией ошибкиE_USER_DEPRECATED. Но отчего обыкновенный разработчик должен генерировать ошибку вручную, в то время как нативная функция требует только флага?

Если помечать функцию устаревшей через примитивное добавление модификатора deprecated, это даст огромную читабельность, быстроту и наглядность для разработчиков.

Оплошность E_USER_DEPRECATED может быть скорее использована для пометки как устаревшей целой библиотеки либо метода вызова функций.

Так же, способ ReflectionFunction::isDeprecated() на данный момент непотребен для пользовательских функций. И данный RFC решает указанную загвоздку.

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

Вызов способов и доступ к свойствам создаваемого объекта

На обсуждении

Цель этого RFC — предоставить поддержку вызова способов и доступа к свойствам сделанного объекта одной строкой. Мы можем применять один из 2-х нижеприведенных синтаксисов.

Синтаксис 1 (без скобок)
  • 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()
Синтаксис 2 (со скобками)
  • (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

Эпилог

Ну, вот и всё. Список изменений получился достаточно значительный, а чай он затрагивается только работу с функциями! Лично мне планируемые нововведения дюже нравятся, хочется их испробовать теснее теперь :)

Буду рад обсудить все эти новшества и, безусловно же, пуститься в жгучую дискуссию вокруг моего RFC. А, допустимо, кто-нибудь ещё и свой предложит :)

До встречи в комментариях!

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB