В нашей предыдущей статье мы говорили о преимуществах системы типов PHP 7, и в частности, о новой поддержке типизированных возвращаемых значений. Что само по себе является не только большим подспорьем в поддержке кода, но делает для PHP большой шаг вперед.
До сих пор мы говорили о типах только в отношении классов и интерфейсов. В течение многих лет мы только их (и массивы) и могли использовать. Однако же, PHP 7 добавляет возможность использовать и скалярные величины тоже, такие как
int
, string
и float
.Но постойте. В PHP большинство примитивов являются взаимозаменяемыми. Мы можем передать
"123"
в функцию, которая хочет int
, и довериться PHP, который все сделает «правильно». Так для чего же тогда нужны скалярные типы?Также как и типы возвращаемых значений, скаляры увеличивают ясность кода, дают возможность поймать больше ошибок на раннем этапе. Что, в свою очередь, повышает надежность кода.
PHP 7 добавляет четыре новых типа, которые могут быть заданы параметрами или возвращаемым значениям:
int
, float
, string
и bool
. Они присоединятся к уже существующим array
, callable
, классам и интерфейсам. Давайте дополним наш предыдущий пример с учетом новой возможности:interface AddressInterface {
public function getStreet() : string;
public function getCity() : string;
public function getState() : string;
public function getZip() : string;
}
class EmptyAddress implements AddressInterface {
public function getStreet() : string { return ''; }
public function getCity() : string { return ''; }
public function getState() : string { return ''; }
public function getZip() : string { return ''; }
}
class Address implements AddressInterface {
protected $street;
protected $city;
protected $state;
protected $zip;
public function __construct(string $street, string $city, string $state, string $zip) {
$this->street = $street;
$this->city = $city;
$this->state = $state;
$this->zip = $zip;
}
public function getStreet() : string { return $this->street; }
public function getCity() : string { return $this->city; }
public function getState() : string { return $this->state; }
public function getZip() : string { return $this->zip; }
}
class Employee {
protected $id;
protected $address;
public function __construct(int $id, AddressInterface $address) {
$this->id = $id;
$this->address = $address;
}
public function getId() : int {
return $this->id;
}
public function getAddress() : AddressInterface {
return $this->address;
}
}
class EmployeeRepository {
private $data = [];
public function __construct() {
$this->data[123] = new Employee(123, new Address('123 Main St.', 'Chicago', 'IL', '60614'));
$this->data[456] = new Employee(456, new Address('45 Hull St', 'Boston', 'MA', '02113'));
}
public function findById(int $id) : Employee {
if (!isset($this->data[$id])) {
throw new InvalidArgumentException('No such Employee: ' . $id);
}
return $this->data[$id];
}
}
$r = new EmployeeRepository();
try {
print $r->findById(123)->getAddress()->getStreet() . PHP_EOL;
print $r->findById(789)->getAddress()->getStreet() . PHP_EOL;
}
catch (InvalidArgumentException $e) {
print $e->getMessage() . PHP_EOL;
}
Изменения коснулись некоторых методов, отдающих строку или число. Даже сделав такой простой шаг, мы уже получили некоторые преимущества.
- Теперь известно, что различные поля класса
Address
являются просто строками. Раньше можно было только предполагать, что они были строками, а не объектамиStreet
(состоящими из номера улицы, ее названия и номера квартиры) или ID города из базы данных. Конечно, обе эти вещи совершенно разумны в определенных обстоятельствах, но в данной статье они не рассматриваются. - Известно, что идентификаторы сотрудников — целые числа. Во многих компаниях ID сотрудника является алфавитно-цифровой строкой или, возможно, номером с лидирующим нулем. Раньше не было способа узнать, теперь же иные трактовки исключены.
- Плюсом является и безопасность. Мы гарантированно знаем, что внутри
findById()
$id
— это значение типаint
. Даже если оно изначально пришло из пользовательского ввода, оно станет целочисленным. Это означает, что оно не может содержать, например, SQL-инъекции. Опора на проверку типов при работе с пользовательским вводом не единственная, и даже не лучшая защита от нападения, но еще один слой защиты.
Кажется, что первые два преимущества избыточны при наличии документации. Если у вас есть хорошие doc-блоки в коде, вы уже знаете, что
Address
состоит из строк и ID сотрудника является целочисленным, верно? Это правда; однако, не все придерживаются фанатичности в вопросе документирования своего кода, или же просто забывают обновить ее. С «активной» информацией из самого языка вы гарантированно будете знать, что рассинхронизации нет, ведь PHP выбросит исключение, если это не так.Явное указание типов также открывает двери к более мощным инструментам. Программы — либо сам PHP, либо инструменты для анализа от третьих лиц — могут изучать исходный код, находя возможные ошибки или возможности оптимизации на основании полученной информации.
Например, мы можем проверить, что следующий код является некорректным и всегда будет падать, даже без его запуска:
function loadUser(int $id) : User {
return new User($id);
}
function findPostsForUser(int $uid) : array {
// Obviously something more robust.
return [new Post(), new Post()];
}
$u = loadUser(123);
$posts = findPostsForUser($u);
loadUser()
всегда возвращает объект типа User
, а findPostsForUser()
всегда возвращает integer, нет никакой возможности сделать этот код верным. Об этом можно сказать лишь взглянув на функции и способ их использования. А это, в свою очередь, означает, что и IDE тоже знает заранее и может предупредить нас об ошибке до запуска. И поскольку IDE может отслеживать намного больше частей, чем мы, то она может и предупреждать о большем количестве ошибок, чем можно заметить самостоятельно… при этом не исполняя код!Этот процесс называется «статический анализ», и он является невероятно мощным способом оценки программ с целью поиска и исправления ошибок. Этому немного мешает стандартная слабая типизация PHP. Передача целого числа в функцию, которая ожидает строку, или строку в функцию, ожидающую целое, все это продолжает работать и PHP молча конвертирует примитивы между собой, также как и всегда. Что делает статический анализ, нами или с помощью утилит, менее полезным.
Введение режима строгой типизации является краеугольным камнем новой системы типов PHP 7.
По умолчанию, при работе со скалярными типами (параметрами или возвращаемыми значениями), PHP будет делать все возможное, чтобы привести значение к ожидаемому. То есть, передача
int
в функцию, ожидающую string
будет прекрасно работать, а передавая bool
при ожидаемом int
вы получите целое число 0 или 1, потому что это естественное, ожидаемое от языка поведение. У объекта, переданного в функцию, ожидающую string
, будет вызваться __toString()
, тоже самое произойдет и с возвращаемыми значениями.Единственным исключением является передача строки в ожидаемый
int
или float
. Традиционно, когда функция рассчитывает получить значения int
/float
, а передается string
, PHP просто будет обрезать строку до первого нечислового символа, в результате чего возможно потеря данных. В случае со скалярными типами, параметр будет работать нормально, если строка действительно является числовой, но если же значение будет усекаться, то это приведет к вызову E_NOTICE
. Все будет работать, но на текущий момент такая ситуация рассматривается как незначительная ошибка в условии.Авто-преобразование имеет смысл, в тех случаях, когда практически все входные данные передаются как строки (из базы данных или http-запросы), но в то же время оно ограничивает полезность проверки типов. Как раз для этого в PHP 7 предлагается
strict_types
режим. Его использование является несколько тонким и неочевидным, но при должном понимании разработчик получает невероятно мощный инструмент.Чтобы включить режим строгой типизации, добавьте объявление в начало файла, вот так:
declare(strict_types=1);
Это объявление должно быть первой строкой в файле, до выполнения какого-либо кода. Оно затрагивает только логику, расположенную в файле и только вызовы и возвращаемые значения в этом файле. Чтобы понять как работает
strict_types
, давайте разобьем наш код на несколько отдельных файлов и немного его изменим:// EmployeeRespository.php
class EmployeeRepository {
private $data = [];
public function __construct() {
$this->data[123] = new Employee(123, new Address('123 Main St.', 'Chicago', 'IL', '60614'));
$this->data[456] = new Employee(456, new Address('45 Hull St', 'Boston', 'MA', 02113));
}
public function findById(int $id) : Employee {
if (!isset($this->data[$id])) {
throw new InvalidArgumentException('No such Employee: ' . $id);
}
return $this->data[$id];
}
}
// index.php
$r = new EmployeeRepository();
try {
$employee_id = get_from_request_query('employee_id');
print $r->findById($employee_id)->getAddress()->getStreet() . PHP_EOL;
}
catch (InvalidArgumentException $e) {
print $e->getMessage() . PHP_EOL;
}
Что же можно гарантировать? Самое главное, мы наверняка знаем, что
$id
внутри findById()
является int
, а не строкой. Неважно, чем будет $employee_id
, $id
всегда примет тип int
, даже если будет выброшен E_NOTICE
. Если мы добавим декларацию strict_type
в EmployeeRepository.php
, то ничего не произойдет. Мы все также будем иметь int
внутри findById()
.Однако, если объявить использование режима строгой типизации в
index.php
, а затем там же использовать вызов findById()
, то в случае если $employee_id
будет являться строкой, или float
или чем-то другим кроме int
, то это приведет к выбросу TypeError
.Другими словами, методы и функции будут затронуты строгой типизацией только, если они вызваны в соответствующе задекларированном файле. Код в любой другой части проекта не будет задет. Желание разработчиков библиотек следовать принципу строгой типизации не будет влиять именно на ваш код, только на их.
Так что же в этом хорошего? Проницательный читатель может заметить, что я сделал очень коварный баг в последнем примере кода. Проверьте конструктор
EntityRespository
, там где мы создаем наши фейковые данные. Вторая запись передает ZIP-код как int, не строку. Большую часть времени это не будет иметь никакого значения. Тем не менее, в США почтовые коды на северо-востоке начинаются именно с ведущего нуля. Если ваш int
начинается с ведущего нуля, PHP будет интерпретировать это как восьмеричное число, то есть число с основанием 8.При слабой типизации это означает, что в Бостоне адрес будет интерпретироваться не как zip-код 02113, а как целое число 02113, что по основанию 10 будет: 1099, вот его-то PHP и переведет в почтовый индекс «1099». Поверьте мне, жители Бостона ненавидят это. Такую ошибку в итоге можно отловить где-то в базе или при валидации, когда код принудительно заставит ввести именно шестизначное число, но в тот момент вы и понятия не будете иметь откуда пришло 1099. Может быть позднее, часов через 8 отладки, будет понятно.
Вместо этого, мы переключим
EntityRespository.php
в strict-режим и сразу же поймаем несоответствие типов. Если запустить код, то получим вполне конкретные ошибки, которые скажут нам точные строки, где их искать. А хорошие утилиты (либо IDE) могут поймать их еще до запуска!Единственное место, где режим строгой типизации позволяет автоматические преобразования — из
int
в float
. Это безопасно (кроме случаев с экстремально большими или малыми значениями, когда есть последствия переполнения) и логично, поскольку это int
по определению также является и значением с плавающей точкой.Когда же мы должны использовать строгую типизацию? Мой ответ прост: как можно чаще. Скалярная типизация, типы возвращаемых значений и strict-mode предлагают огромные преимущества для дебаггинга и поддержки кода. Все они должны использоваться максимально, и как результат, будет более надежный, поддерживаемый и безглючный код.
Исключением из этого правило может быть код, который напрямую взаимодействует с каналом входных данных, таким как базы данных или входящие http-запросы. В этих случаях входные данные собираются всегда быть строками, потому что работа в интернете это просто более проработанный способ конкатенации строк. При ручной работе с http или БД можно переводить данные из строк в необходимые типы, но обычно это выливается в большое количество ненужной работы; если SQL-поле имеет тип
int
, то вы знаете (даже если не знает IDE), что из него будет всегда отдаваться числовая строка и, следовательно, сможете быть уверены в безопасности и отсутствии потерь данных при передаче их в функцию, ожидающую int
.Это означает, что в нашем примере
EmployeeRespository
, Employee
, Address
, AddressInterface
и EmptyAddress
должны иметь включенный strict-режим. index.php
же взаимодействует с входящими запросами (через вызов get_from_request_query()
), и таким образом, вероятно, легче будет доверить PHP разбираться с типами, а не делать это вручную самостоятельно. Как только неопределенные значения из запроса передадутся в типизированную функцию, тогда-то и можно переключаться в строготипизированную работу.Будем надеяться, что переход на PHP 7 будет намного быстрее, чем как это было с PHP 5. Это действительно стоит того. Одной из главных причин как раз и является расширенная система типов, дающая нам возможность сделать код более самодокументированны и более понятным друг другу и нашим инструментам. В результате получится намного меньше «хммм, я даже и не знаю что с этим делать» моментов, чем когда-либо прежде.
Комментарии (94)
gernovich
28.09.2015 18:43-1Самый лучший вариант самому написать и по выполнять тесты.
Начиная от версии 4.3.0 до 7.0.0rc3 и hhvm
3v4l.org/3sQhg
evalexdy
28.09.2015 18:51-6Так и вижу анонс: «готовится к релизу PHP12, только теперь он будет называться С#»
Fesor
28.09.2015 19:18+7Вы так говорите как будто бы это что-то плохое. В целом я был бы рад увидеть в PHP8 JIT, в PHP9 async/await ну и т.д.
saksmt
30.09.2015 08:57ИМХО: не ляжет async/await в PHP, лучше уж нормальные thread-ы с synchronized/volatile и методы wait
Fesor
30.09.2015 10:50+1вы сами себе противоречите, значит async/await на пых не лягут а треды ложатся, при том что это весьма универсальный механизм управления потоком исполнения, с тредами или просто асинхронным I/O — не суть. Если что, async/await уже реализовано в Hack и уже идет обуждение будущего этого добра в PHP.
saksmt
02.10.2015 07:40Я не против многопоточности в пыхе, я просто против её реализации через async/await. А так это всё пока не очень-то актуально, вот когда я смогу запускать пыху, а не наблюдать как она запускается, отрабатывает и умирает, вот тогда уже можно будет нормально подискутировать о многопоточности, иначе оно просто не имеет смысла.
Fesor
02.10.2015 09:33вот когда я смогу запускать пыху, а не наблюдать как она запускается, отрабатывает и умирает
php-pm, и да, в этом плане больше подходит не многопоточность а event-loop и несколько процессов-воркеров. Многопоточность более актуальна для CLI скриптов.
saksmt
05.10.2015 07:36-1Штука конечно замечательная, но пых просто сожрёт всю память и на этом всё закончится (слишком много утечек в ядре, т.к. не рассчитан пых на такую работу)
Fesor
05.10.2015 10:26+1слишком много утечек в ядре
у меня в продакшене уже по пол года демоны на PHP крутятся и все хорошо. В самом ядре утечек не сильно много, их очень оперативно находят и фиксят. Более того, утечки в ядре влияли бы на php-fpm. В целом сейчас риск схлопотать текущую память есть только если пользоваться какими-то экстеншенами сторонними.saksmt
05.10.2015 21:58Видимо у меня устаревшая информация, кстати с хабра (увидел, разочаровался, стал ждать пока поправят и забыл). Что ж я только рад, теперь надо только ждать миграции всего и вся на эту вещь.
Fesor
05.10.2015 22:31Там к слову ни слова о утечках в ядре. Только в коде автора статьи и возможные подтекания в расширениях и библиотеках.
Да и решения в духе php-pm не требуют «переписывания», если у них есть слой абстракций над SAPI (PSR-7, HttpKernel). Эта штука позволяет просто в короткие сроки ускорить приложение за счет невилирования расходов на бутстрапинг приложения. Вы можете после каждой 1000-чи запросов например перезапускать воркеры для надежности.saksmt
06.10.2015 07:03Ещё не мало интересно что там со скоупами сервисов делать…
Fesor
06.10.2015 10:25А что именно вы хотите с ними делать? Пусть себе висят в памяти, если они состояние не сохраняют. Скажем у доктрины можно просто отчистить unit-of-work и дальше себе пусть висит.
saksmt
07.10.2015 07:39Да пусть висят, просто интересно поведение, если я допустим делаю сервис, зависящий от security.token_storage, то будет ли это нормально работать (пересоздавать мой сервис для каждого нового запроса) или нет?
P.S. В спринге это решается указанием области видимости сервиса: запрос, синглтон,…Fesor
07.10.2015 10:16можно дропать сервисы в скоупе реквеста, это бы было пожалуй правильно.
saksmt
09.10.2015 08:00Не очень-то это правильно на самом деле это — костыль. К тому же из замороженного контейнера не так-то просто выпилить сервис…
P.S. Ковыряюсь в билдере контейнера, там вроде как есть методы {enter,leave}Scope, осталось придумать как входить в скоуп до создания скоупа реквеста.
qRoC
06.10.2015 12:05Ещё не мало интересно что там со скоупами сервисов делать…
Для этого есть свойство shared и Kernel Events.saksmt
07.10.2015 07:44Не то, мне не нужно пересоздавать сервис на каждый новый подзапрос, мне нужно пересоздавать на каждый новый master запрос. А вот как запилить свой кастомный скоуп я так и не нашёл (может плохо искал?).
qRoC
07.10.2015 11:40P.S. В спринге это решается указанием области видимости сервиса: запрос, синглтон,…
В старых версиях symfony2 аналогично(смотрите ContainerAwareHttpKernel, компонент HttpKernel) — устанавливается область видимости request, и инжектится request(но опять же проблемы с саб-реквестами).
Сейчас области видимости удалены. Вместо container/prototype — свойство shared. Вместо request — сервис @request_stack.saksmt
09.10.2015 08:04Где курить про shared? (Гугл упорно не отвечает)
Fesor
09.10.2015 10:04saksmt
10.10.2015 14:06Беда, зря это они так :(
Fesor
10.10.2015 14:24saksmt
12.10.2015 08:13Например потому, что скоуп это не нечто привязанное к запросу: я мог бы с лёгкостью (относительной) запилить коннект к rabbitmq и затолкать его ЖЦ в отдельный скоуп, чем городить десяток лишних методов для получения «текущего» коннекта (да, пример высосан из пальца, но всё же… в конце концов можно эту идею привернуть например к long poll и сокетам) + в большинстве случаев проще явно указывать зависимость от текущего состояния в явном виде, а в случае полной замены на request_stack придётся дёргать его в каждом из методов сервиса, а не единожды в конструкторе — это немного ломает всю суть инверсии зависимостей.
Естественно все эти рассуждения нужны лишь если всё запускается демоном, в остальном не актуально ровно полностью.
Athari
29.09.2015 15:23Скорее PHP#. :)
Впрочем, после перетаскивания ООПа из мейнстримовых строго типизированных языков добавление строгой типизации в передачу аргументов выглядит закономерно, хоть и непривычно и даже холиваристо. Интересно, добавится ли в следующей версииdeclare(strict_operators=1)
и ещё что-нибудь в таком духе…
Вообще, если уж мутировать в шарп, то скорее бы лямбды с нормальным синтаксисом родили. Они уже даже в джаве и плюсах есть, а разрабы похапэ всё упираются.Fesor
29.09.2015 15:46Справедливости ради, сейчас в стадии голосования почти «нормальные лямбды». Холивар внутри интерналс из-за синтаксиса (почему-то автор RFC не хочет использовать вариант, который ввели уже давным давно в Hack).
Athari
29.09.2015 15:54Оба-на. Где посмотреть обсуждение?
Fesor
29.09.2015 16:04+1RFC, Рассылка PHP Internals ну и на гитхабе в пулреквесте к RFC.
p.s. О, занятно, async/await хотят сделать зарезирвированным словом уже в php 7.1. Найс. Асинхронный пых не загорами, того и глядишь, как и в случае с корутинами, ноду обгоним.Athari
29.09.2015 23:05Ох, и опять проголосовали против…
Лучше б оператор поменяли на==>
, тогда в запросах YaLinqo будет достаточно удалить кавычки вокруг «строковых лямбд», и синтаксис идеально совпадёт. :)
Странно видеть спор вокруг type hints. Автор в RFC явно сказал, что это техническое ограничение, а не проявление нелюбви к типизации. Добавить их в синтаксис большой проблемы не составит, полагаю, но это ж ещё реализовать надо будет в разумные сроки.
В споре вокруг явности и неявностиuse
какую-либо позицию не принял. Хоть что-нибудь сделали бы уже, хоть как-нибудь.Fesor
29.09.2015 23:16Ох, и опять проголосовали против…
Против проголосовали именно из-за упертости относительно синтаксиса. Сейчас в интерналс вообще ведут дискуссию по этому поводу исходя из того, что есть же еще тайп хинтинг… ну вы поняли. Все сложно как всегда. В любом случае до 7.1 еще довольно много времени, так что может и сделают норм.
saksmt
30.09.2015 08:54Пых-таки ближе к яве и я был бы не прочь, если бы он продолжал шагать в эту сторону, в конце концов иметь яву с поздним статическим связыванием и опциональной динамической типизацией — есть хорошо, настолько же, насколько хорошо иметь пых с вложенными и анонимными классами, областью видимости для классов в пространствах имён, дефолтной реализацией интерфейсов, и чего больше всего сейчас не хватает: объектами в константах… Эх, что-то я совсем размечтался.
Athari
30.09.2015 18:54Если вам хочется джавы с поздним связыванием, то существует C# с dynamic. Конечно, на шарпе писать код полностью в dynamic не принято, но это вполне работает. :) В последней версии добавили using static, string interpolation и прочее, так что есть и «глобальные функции», и выполнение кода в строчках, как в похапэ.
Анонимные классы в джаве в основном использовались для колбэков, с введением лямбд они уже не так актуальны. Что подразумевается под объектами в константах не понял, но статические неизменяемые поля иммутабельных типов возможны.
Вот дефолтной реализации методов интерфейсов нет, здесь джава в кои-то веки утёрла нос шарпу. Что забавно, главные пропагандисты простоты ещё дальше отошли от мантры «множественное наследование — зло». :)VolCh
30.09.2015 19:34+1Как там Mono поживает?
Athari
30.09.2015 19:38Без понятия. А что с ним?
VolCh
30.09.2015 19:43А можно ещё как-то запускать С# под никсами?
Athari
30.09.2015 19:54.NET Core скоро релизнется.
Так что с Mono? Не слышал, чтобы оно работать переставало. :)VolCh
30.09.2015 20:10Слышал и видел, что оно не очень хорошо работало, да при этом ресурсов жрало больше чем PHP на той же задаче (сравнивали ASP .NET MVC и Symfony2).
Athari
01.10.2015 04:58Не слышал и не видел. :)
А ресурсов каких больше жрало? Если при небольшой нагрузке сожрало больше памяти — допускаю. Если жрало больше процессорного времени — это уже что-то странное: шарп при прочих равных должен быть быстрее за счёт статической типизации.
Fesor
01.10.2015 10:07Ну с учетом того что .NET MVC теперь будут официально супортить под mono и того что мелкомягкие потиху открывают исходники, можно ожидать что через пару лет моно будет вполне себе годной штукой. Вообще жалко что этого не произошло много раньше, как по мне шарп приятнее джавы.
VolCh
01.10.2015 11:15Примерно так, да, через пару лет можно будет об использовании моно в серьёзном (под)проекте с нуля.
Athari
01.10.2015 12:21Да ладно, многие умудряются делать это уже сейчас. Если верить W3Techs, то ASP.NET имеет 16.5% веба при 12.9% IIS (топ 10 миллионов сайтов по Алексе). Сам удивляюсь. :))
Fesor
01.10.2015 14:28Вы еще вспомните Xamarin, все жалуются но работает.
Athari
01.10.2015 14:42Что далеко ходить — все на PHP, Java и C++ жалуются, весь мейнстрим завален жалобами по уши. :)
Fesor
01.10.2015 16:28На то он мэйнстрим, не жалуются только на то чем не пользуются.
Athari
01.10.2015 17:08+2По-разному. В случае шарпа жалобы на язык слышно редко (ну… от страстных плюсовиков разве что), а вот жалоб на экосистему — хоть попой кушай. На питон жалобы очень редко слышу, хотя, может, я просто слишком далёк от него. При этом людей, считающих синтаксис Obj-C нормальным очень редко вижу.
Ещё у поклонников разных языков разная психологическая устойчивость. Эффект от комментария «PHP говно», «Java говно» и «C# говно» в топиках про PHP, Java и C#, соответственно, будет разным в пересчёте на минусы и карму. :)
Вот, кстати, PHP7 добавил похапэшникам уверенности. Обычно при написании сообщений «на грани», как у меня в этом топике, я без минусов не уходил. А тут — чистота. Первый признак, что язык развивается в нужном направлении, и у похапэшников пропадают сомнения в своём выборе. ;)
saksmt
02.10.2015 07:36Мне просто шарп как вид не очень мил. Моему сердцу родней пачка наблюдателей, а не сигналы/слоты, да и управление многопоточностью я привык оставлять на akka и при необходимости разруливать с тредами, а не лапшой с async/await.
Тезис был больше про вложенные классы, а анонимные — приятное дополнение.
Касательно объектов в константах (в пыхе так нельзя, но ОЧЕНЬ хочется):
<?php class Example { private final static $logger = new Logger(__CLASS__); private $someSimpleDependency = new SimpleDependency(); public function doSomething(Argument $argument = new Argument()) { } } class Enum { const SOME_VALUE = new self('some value); // ... }
Это не единственное, чем ява 8 шарпу нос утёрла, ещё монады появились :). Я бы не стал использовать эту фичу в этих целях…Athari
02.10.2015 13:45+1В первый раз вижу человека, который предпочитает ручное разруливание потоков асинк-эвейтам. :)
Моему сердцу родней пачка наблюдателей, а не сигналы/слоты
А какая разница?saksmt
05.10.2015 07:40Вот такой вот я больной :) Просто если не охота с ними возиться, то и не нужно, есть решения которые это делают сами, а коли уж понадобилось, так пусть у меня будет полный контроль и без лапши в коде.
В механизме управления и «явственности». Ну это опять-таки просто дело вкуса.Fesor
05.10.2015 10:28+1без лапши в коде
скажите честно, вы работали с async/await? Ибо пачка наблюдателей проще приведет к лапше.saksmt
05.10.2015 22:01Приходилось тыкать в go и шарпе. И таки да, как по мне, эта фича больше способствует лапшекоду, нежели набор вменяемых классов (естественно при создании анонимных будет сильно хуже, чем с async/await) наблюдателей.
artyfarty
28.09.2015 19:35+2Одна беда, generics пока нет. Без них выразительность всего этого весьма ограничена.
Я не представляю как бы я писал на TypeScript без генериков.Fesor
28.09.2015 19:56Вы точно про дженерики? Они то будут может в php 7,1 а вот применимость их к массивам — вот это было бы славно. В TS с этим проще.
artyfarty
28.09.2015 20:00+2Да-да, про них.
Ну, int[], string[] и MyClass[] это вообще само собой разумеющиеся вещи, я сейчас даже не задумался о том, что в опсианной в статье реализации их может не быть.
Более кошерные объявления массивов через женерики тоже приветствуются.
А в TS вообще очень классно систему описания типов сделали. То как там можно описать любой потенциальный Hash с полями, которые в нем точно должны быть, могут быть, могут быть но немного не так…
WindDrop
29.09.2015 08:00+1loadUser() всегда возвращает объект типа User, а findPostsForUser() всегда возвращает integer, нет никакой возможности сделать этот код верным.
Не возвращает, а принимает
function findPostsForUser(int $uid): array {
// Obviously something more robust.
return [new Post(), new Post()];
}
qRoC
29.09.2015 08:18Введение режима строгой типизации
Вам уже второй раз говорят что это не строгая типизация, а type hinting. Вводите людей в заблуждение, а потом они спрашивают про увеличение производительности.VolCh
29.09.2015 10:13+1Если программа валится от неправильного типа в параметре или return при теоретической возможности неявного привидения, то это уже не hinting, а строгая типизация (на уровне сигнатур). Но её наличие не должно приводить к увеличению производительности, оно лишь может быть использовано транслятором для оптимизации. Может, но не должно. Основное назначение строгой типизации — это не увеличение производительности, а исключение ошибок неявного преобразования типов.
iGusev
29.09.2015 13:00type-hinting будет работать и без
declare(strict_types=1);
, под которым я и подразумеваю термин «режим строгой типизации»
qRoC
29.09.2015 10:46Если программа валится от неправильного типа в параметре или return при теоретической возможности неявного привидения, то это уже не hinting, а строгая типизация (на уровне сигнатур).
т.е. если мы в коде напишем if is_array/… то у нас сразу строгая типизация?VolCh
29.09.2015 12:59+1Эмуляция строгой типизации в языке на уровне приложения.
qRoC
29.09.2015 15:02+1Эмуляция строгой типизации в языке на уровне приложения.
Это обычный контроль типов для аргументов, который существует в php начиная с пятой версии(объекты и интерфейсы, а в дальнейшем и массивы с колбеками). Просто в седьмой версии добавлена возможность указывать скалярные типы, и контролировать тип возвращаемого значения. Это никакая не строгая типизация. Никто Вам не запрещает написать:
function test(int $a) { $a = "hi"; }
PHP был, есть, и будет динамическим языком программирования.VolCh
29.09.2015 17:52А что мешает при этом ему быть строго типизированным?
Вы вообще отличаете строгость системы типов языка от времени присваивания типа переменной? Строгость — это именно контроль типов на уровне языка, недопущение использования одного типа там, где ожидается другой. А получен тип в компайл или рантайме — дело десятое, к строгости оно никакого отношения не имеетFesor
29.09.2015 18:03ну так в том то и суть — контроля нет, это именно тайп хинтинг и не более. Контроль только на уровне аргументов и результата работы функций и методов.
VolCh
29.09.2015 18:25Хинтинг — это когда варнинги, а когда программа падает — это уже контроль.
qRoC
29.09.2015 19:11По Вашим словам, php строго типизированный начиная с версии 5.0. Спасибо что открыли нам всем глаза.
VolCh
30.09.2015 07:26+2Частично строго типизированный. Сейчас эта часть увеличивается. Глядишь когда-нибудь и тип локальных переменных можно будет обновлять, и '0123' + 456 будет вылетать.
Fesor
29.09.2015 20:42Хинтинг — это когда варнинги
in PHP 5, this will be a recoverable fatal error, while PHP 7 will throw a TypeError exception.
То есть по сути это «варнинги». Просто более громкие. Если вы обернули все в try/catch в php7 то никаких падений.VolCh
30.09.2015 07:27+1Нет, это не варнинги. Функция или метод не выполнятся, если неправильный тип передаётся.
HaruAtari
29.09.2015 11:01Мы недавно с колегой обсуждали эту фичу. Изначально я был только за и всячески пытался показать ее полезность. Но в итоге он заставил меня усомниться не только в пользе скалярного, но и type hinting-а вообще.
Все эти проверки производятся в рантайме, а не на этапе компиляции, которого у PHP нет. И без тестов или просто тестового запуска мы не увидим этих ошибок. И получается, что это нафиг не надо.
Единственный плюс — это помощь IDE-шке. Но эта задача намного удобней решается phpDoc-ами.
И хотя я был ярым сторонником type hinting-а, я не смог не согласиться с его доводом. От статичной типизации есть пользя т.к. проект просто не соберется при наличии ошибки. Но php — это не тот случай. Без проверки (или тестов) мы все-равно потащим ошибку в продакшен и там ее словит пользователь. А с тестами эта штука не нужна.
Я не говорю, что это плохое нововведение. Но во-превых, оно пока еще сыровато, а во-вторых, несколько замедляет разработку. Не сильно но ощутимо. Все-таки не за строгость мы любим php.Fesor
29.09.2015 11:24+3И без тестов или просто тестового запуска мы не увидим этих ошибок. И получается, что это нафиг не надо.
1) Вы так или иначе при разработке запускаете запускаете код (тестами или вручную). Тайпхинтинг позволяет вам быстро определить что на вход методов/функций пришло что-то не то (или ушло что-то не то). То есть увеличивает шансы поймать багу еще на этапе девелопмента. Некоторые настолько упарываются что изобретают DbC для этого, используют ассерты и т.д.
2) тайп хинтинг предоставляет статический анализ PHP кода может быть произведен куда более качественно если у анализатора будет вся необходимая информация о типах.
3) тайп хинтинг в weak моде всегда гарантирует нам что на вход придет именно то что мы запросили, это уменьшает количество кода в различных библиотечных функциях и при этом не нужно хэндлить приведение типов руками. Ошибки будут валиться только при потере данных при касте типов.
3) тайп хинтинг предоставляет тому самому рантайму информацию о типах, которую например может использовать opcache для более глубоких оптимизаций. В частности в internals обсуждалось это дело с позиции будущего внедрения JIT, которому для анпакинга и прочих радостей информация о типах весьма полезна.
От статичной типизации есть пользя т.к. проект просто не соберется при наличии ошибки. Но php — это не тот случай.
неудачный прогон статического анализатора — вот тебе и «не собрался». То что в PHP нет компиляции не означает что мы не можем организовать сборку.
А с тестами эта штука не нужна.
Посмотрите на phpspec. Там на тайпхинтинге сладкий сахар для моков сделан. Да и опять же автокомлит для IDE-шечки, статический анализ они нынче тоже умеют делать на лету и это позволяет уменьшить обратную связь. Почти как парное программирование.
оно пока еще сыровато
не сырова-то, а набор необходимых штук что бы прочувствовать всю прелесть тайп хинтинга в пыхе не полон. Не хватает дженериков и типизированных массивов.
VolCh
29.09.2015 13:05+3В дополнение к вышеизложенному: пускай лучше пользователь словит 500 и в логах будет запись о том, что произошло что-то неожиданное для разработчика, чем в результате очень нестрогого неявного преобразования типов, например, утечёт вся база данных.
AmdY
29.09.2015 15:07-1Типизация здесь не решает, в той же sql-инъекции строка остаётся string несмотря на наличие кавычек. Лучше в коде явно привести параметр к int, чем каждый раз падать из-за неправильно переданного параметра. Последнее время на проектах некоторые части ипишек на .net или java, сил нет бороться с их 500-ми и трейсами, даже есть правило, если апишка не на php, умножаем время разработки в три раза.
VolCh
29.09.2015 18:01Вы в своей функции укажете, что параметр должен быть int и вам не нужно больше ничего приводить — или вызов до вашей функции не дойдёт, или будет приведение. Я же как клиент вашей функции буду выбирать один из минимум трех вариантов: включить неявное приведение при вызове (вернее не включать строгий контроль), явно приводить при вызове, или пускай падает.
Сейчас у меня как клиента такого выбора нет: вы можете приводить явно, а можете не приводить, можете бросать исключение, а можете не бросать, можете вообще не подумать, что придёт строка, а не число и в случае какой-нибудь '0777' даже сходу не сможете мне сказать, как ваша функция отработает, воспримет строку как десятичное число или как восьмеричное.AmdY
29.09.2015 18:25Клиент то точно не должен иметь возможности указывать как мне обрабатывать параметры метода, это нарушает принцип чёрного ящика и ведёт к непредсказуемым результатам. Потому всегда нужно приводить данные к нужному виду, а не доверять пользователям.
Падать приложение не должно, можно выбросить null или false или пустой массив-коллекцию в зависимости от конракта и бросить в лог сообщение с ошибкой. Исключение — это вообще крайний случай. Не подгрузятся товары по акции — ну и фиг с ним, в логе ошибка есть, исправят, а пользователю блок не покажем и т.д.
А если где-то критично важен тип, то не надо принимать скаляры, проси их завернуть в обёртки для примитивных типов, создав Acme\Int, Acme\String, тайпхинтя по ним и работая с умными объектами.VolCh
29.09.2015 18:36Так в том-то и дело, что не должен, вы ему показываете контракт, который он должен соблюдать. Как он будет его соблюдать, и что ему делать когда почему-то не получилось соблюсти — решать ему, а по умолчанию язык будет пытаться привести к указанному вами контракту.
Это разработчику клиента решать, что должно делать его приложение, когда выполняет недопустимую операцию — или пытаться по тихому её привести к допустимой, или обработать исключение, или упасть.
А почему такое разделение на скаляры и объекты? Почему тогда вообще не отказаться от тайп хинтинга?AmdY
30.09.2015 16:29>>А почему такое разделение на скаляры и объекты?
Потому что в вэбе почти всё строки, от того утиная типизация в php так удобна и экономит столько времени, сил и средств. А все эти новые костыли работают очень криво, от неймспейсов я до сих пор офигеваю открыв исходник и увидев шапку из use.Fesor
30.09.2015 18:37+3Потому что в вэбе почти всё строки
Угу, строки, которые нужно потом закастить в инты и флоты, обсчитать чего-нибудь, запихнуть в базу…
Есть не только сайтики в вэбе. А нэймспейсы — это лучше чем классы вида My_Super_Class_With_Snake_Case_Class_NamesAmdY
30.09.2015 19:15+1Так я об этом и пишу, скаляры надо обрабатывать либо заворачивать в объекты вроде Money или Int, потому и разделяю их.
Про неймспейсы — хрен редьки не слаще. new sfRequest было гораздо удобнее use Symfony\Component\HttpFoundation; new Request;
Есть исторически сложившиеся моменты из-за которых нет нормальной возможности их реализовать удобно, чтобы использовать use Acme\*, нет приватных классов из-за чего скоуп загажен служебными классами и неймспейсами.
Я вот решил написать статейку как мы в кучерявые времена делали свое неправильное DDD. Начал переписывать примеры с php4 на php5 и ловлю себя на мысли, что с интерфейсами и неймспейсами кода стало ещё больше писаться.
Athari
30.09.2015 19:15от неймспейсов я до сих пор офигеваю открыв исходник и увидев шапку из use
Это потому что неймспейсы можно использовать двумя способами: правильно и как принято в PHP. :) Многие библиотеки множат мелкие неймспейсы, а импортировать каждый класс нужно отдельно. В результате импортирование стало несмешной пародией на инклюды, когда без IDE невозможно угадать, где какой класс разместился.
Подробная статья: pornel.net/phpns
Там не без перегибов (не знаю, есть ли вообще язык, реализующий все хотелки автора), что-то исправлено в PHP7, но проблемы обозначены верно.VolCh
30.09.2015 19:42а импортировать каждый класс нужно отдельно
Зачем?
Вполне работает что-то вроде:
namespace AppBundle\Entity; use AppBundle\Entity\SfGuard; $user = new SfGuard\User();
Fesor
30.09.2015 19:45+1wiki.php.net/rfc/group_use_declarations
// from FooLibrary use Foo, Bar, Baz; // Nope use FooLibrary{ Foo, Bar, ClassD as Baz };
хотя на счет синтаксиса — вариант с from как-то лучше, но… контекстно-зависимого лексера на момент реализации этого RFC в PHP еще небыло.
SOLON7
Насколько увеличивается скорость выполнения пхп-скрипта ???
Есть ли сравнения по времени загрузки среды окружения ??
iGusev
На RC3 пока не видел бенчмарков, а в целом примерно так.
Alexufo
Сегодня тестировал на винде этот скрипт между 5.6 и 7rc3
www.php-benchmark-script.com
7 RC3 сильно проигрывает в нем по тесту test_stringmanipulation. Под линухом не пробовал.
Fesor
Смотря какого PHP-скрипта. В целом на реальных приложениях ожидается 30%-50% прирост производительности. Далее все зависит от того что вы делаете в приложении. Огромный профит будет там где идет большой объем работы со структурами данных.