В конце января команда разработчиков PHP выпустила версию PHP 5.4 RC 6. Это означает, что уже очень скоро свет увидит стабильная версия, а стало быть, самое время ознакомиться с нововведениями. Самым заметным и принципиальным нововведением этой версии являются трейты (traits) – механизм повторного использования кода. Но обо всем по порядку.

Чего не стало

В версии 5.4 PHP наконец-то избавился от идиотского режима Safe Mode. Кроме того, в лету канули ini директивы register_globals, register_long_arrays, allow_call_time_pass_reference, magic_quotes_gpc, maig_quotes_runtime и magic_quotes_sybase. Функции get_magic_quotes_gpc() и get_magic_quotes_runtime() всегда возвращают false. Функция set_magic_quotes_runtime() вызывает ошибку уровня E_CORE_ERROR. Удалены функции import_request_variables(), session_is_registered(), session_register() и session_unregister().

Что изменилось

Конструкция <?= теперь доступна всегда и не зависит от ini директивы short_open_tag. Напомню, эта конструкция равнозначна конструкции <?php echo. Значение по умолчанию для ini директивы default_charset теперь — UTF-8 (было ISO-8859-1), а для date.timezone – UTC (ранее PHP пытался определить временную зону автоматически). Уровень ошибки E_ALL теперь содержит уровень E_STRICT.

Что нового

Добавлена функция http_response_code(), позволяющая определить либо установить HTTP-код страницы. Ранее для установки статуса приходилось вызывать функцию header().


echo http_response_code(); // 200
http_response_code(404);
echo http_response_code(); // 404

Добавлена функция header_register_callback(), позволяющая задать функцию, выполняемую непосредственно перед отправкой заголовков:

header_register_callback(function() {
    header_remove('Content-Type');
    header('Content-Type: text/plain');
});

header('Content-Type: text/html');
echo 'Hello world!';

Такой код выведет текст со значением text/plain для заголовка Content-Type.

Добавлен интерфейс JsonSerializable. Он позволяет классу, использующему его, задавать, какие данные будут преобразованы в JSON функцией json_encode(). К сожалению, в данный момент документации по этому интерфейсу практически нет.

Нововведения в синтаксисе

В PHP 5.4 добавлен новый, короткий синтаксис для создания массивов:

$array = [1, 2, 3];
$assoc = ['foo' => 'bar', 'baz'];

Добавлена поддержка выражений при обращении к членам статических классов:

class Foo {
    public static $barbar = 'bar';
    public static function barbaz() {
        return 'baz';
    }
}

$bar = 'bar';
$baz = 'baz';

echo Foo::{$bar . $bar}; // 'bar'
echo Foo::{$bar . $baz}(); // 'baz'

Добавлена поддержка обращения к членам класса при инстанциировании:

class Foo {
    public function setBar($v) {
        $this->bar = $v;
        return $this;
    }
}

$foo = (new Foo)->setBar('bar');
echo $foo->bar; // 'bar'

Трейты

Самое главное и вкусное нововведение PHP 5.4 – трейты. Трейты – это наборы методов, которые можно подключить к произвольному классу, не затрагивая его цепь наследования. Рассмотрим область применения на примере: у нас есть класс Person и наследуемые от него классы John, Jack и Jill. John умеет плавать, Jack умеет петь, а Jill умеет и плавать и петь одновременно. Классической моделью наследования выразить это не просто, но с трейтами все элементарно:

abstract class Person {
    public $name;
    public function greet() {
        echo "Hi, I’m " . $this->name;
    }
}

trait Swimmer {
    public function swim() {
        echo $this->name . ' started swimming';
    }
}

trait Singer {
    public function sing() {
        echo $this->name . ' started singing';
    }
}

class John extends Person {
    use Swimmer;
    public $name = 'John';
}

class Jack extends Person {
    use Singer;
    public $name = 'Jack';
}

class Jill extends Person {
    use Swimmer, Singer;
    public $name = 'Jill';
}

$john = new John;
$jack = new Jack;
$jill = new Jill;

$john->greet(); // Hi, I'm John
$john->swim(); // John started swimming

$jack->greet(); // Hi, I'm Jack
$jack->sing(); // Jack started singing

$jill->greet(); // Hi, I'm Jill
$jill->swim(); // Jill started swimming
$jill->sing(); // Jill started singing

Как видим, трейты позволяют очень гибко компоновать функционал классов. Трейты могут содержать методы, свойства, а также другие трейты. Трейты могут содержать абстрактные и статические методы, но не статические свойства. У трейтов есть механизм разрешения конфликтов, однако он не совершенен. Например, если в классе, использующем трейт, есть свойство с таким же именем, что и свойство в трейте, то возникнет ошибка. Если значение и видимость свойств совпадает, то ошибка будет уровня E_STRICT, в противном случае возникнет фатальная ошибка.

С разрешением конфликтов в методах все намного лучше. Методу, унаследованному из базового класса, будет предпочтен метод из трейта:

class Base {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait SayWorld {
    public function sayHello() {
        parent::sayHello();
        echo 'World!';
    }
}

class MyHelloWorld extends Base {
    use SayWorld;
}

$o = new MyHelloWorld;
$o->sayHello(); // 'Hello World!'

Методу, внесенному трейтом, будет предпочтен метод из текущего класса:

trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

class TheWorldIsNotEnough {
    use HelloWorld;
    public function sayHello() {
        echo 'Hello Universe!';
    }
}

$o = new TheWorldIsNotEnough;
$o->sayHello(); // 'Hello Universe!'

Если класс использует более одного трейта и имена одного из методов этих трейтов совпадают, то конфликт необходимо решить самостоятельно:

trait A {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

trait B {
    public function smallTalk() {
        echo 'b';
    }
    public function bigTalk() {
        echo 'B';
    }
}

class Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
    }
}

class Aliased_Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
        B::bigTalk as talk;
    }
}

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

trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

// Изменим область видимости метода sayHello
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// Изменяя и название и область видимости метода,
// мы вводим алиас метода sayHello, не удаляя последний 
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}

Вдобавок ко всему этому в PHP 5.4 появились функции работы с трейтами: trait_exists() и get_declared_traits().

На этом тема трейтов, конечно же, не исчерпана, лично мне еще многое в них не понятно, так как документация по этому вопросу далеко не полная, да и PHP еще не в своей финальной версии. Остается только ждать.

Заключение

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