Новый PHP, часть 2: Scalar types

http://devzone.zend.com/6622/a-new-type-of-php-part-2-scalar-types/
  • Перевод
  • Tutorial

В нашей предыдущей статье мы говорили о преимуществах системы типов 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;
}

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

  1. Теперь известно, что различные поля класса Address являются просто строками. Раньше можно было только предполагать, что они были строками, а не объектами Street (состоящими из номера улицы, ее названия и номера квартиры) или ID города из базы данных. Конечно, обе эти вещи совершенно разумны в определенных обстоятельствах, но в данной статье они не рассматриваются.
  2. Известно, что идентификаторы сотрудников — целые числа. Во многих компаниях ID сотрудника является алфавитно-цифровой строкой или, возможно, номером с лидирующим нулем. Раньше не было способа узнать, теперь же иные трактовки исключены.
  3. Плюсом является и безопасность. Мы гарантированно знаем, что внутри 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

    –3
    Насколько увеличивается скорость выполнения пхп-скрипта ???
    Есть ли сравнения по времени загрузки среды окружения ??
      0
      На RC3 пока не видел бенчмарков, а в целом примерно так.
        0
        Сегодня тестировал на винде этот скрипт между 5.6 и 7rc3
        www.php-benchmark-script.com
        7 RC3 сильно проигрывает в нем по тесту test_stringmanipulation. Под линухом не пробовал.
        0
        Смотря какого PHP-скрипта. В целом на реальных приложениях ожидается 30%-50% прирост производительности. Далее все зависит от того что вы делаете в приложении. Огромный профит будет там где идет большой объем работы со структурами данных.
        –1
        Самый лучший вариант самому написать и по выполнять тесты.
        Начиная от версии 4.3.0 до 7.0.0rc3 и hhvm
        3v4l.org/3sQhg
          –6
          Так и вижу анонс: «готовится к релизу PHP12, только теперь он будет называться С#»
            +7
            Вы так говорите как будто бы это что-то плохое. В целом я был бы рад увидеть в PHP8 JIT, в PHP9 async/await ну и т.д.
              0
              Тогда уж сразу go/chan :)
                0
                ИМХО: не ляжет async/await в PHP, лучше уж нормальные thread-ы с synchronized/volatile и методы wait
                  +1
                  вы сами себе противоречите, значит async/await на пых не лягут а треды ложатся, при том что это весьма универсальный механизм управления потоком исполнения, с тредами или просто асинхронным I/O — не суть. Если что, async/await уже реализовано в Hack и уже идет обуждение будущего этого добра в PHP.
                    0
                    Я не против многопоточности в пыхе, я просто против её реализации через async/await. А так это всё пока не очень-то актуально, вот когда я смогу запускать пыху, а не наблюдать как она запускается, отрабатывает и умирает, вот тогда уже можно будет нормально подискутировать о многопоточности, иначе оно просто не имеет смысла.
                      0
                      вот когда я смогу запускать пыху, а не наблюдать как она запускается, отрабатывает и умирает

                      php-pm, и да, в этом плане больше подходит не многопоточность а event-loop и несколько процессов-воркеров. Многопоточность более актуальна для CLI скриптов.

                        –1
                        Штука конечно замечательная, но пых просто сожрёт всю память и на этом всё закончится (слишком много утечек в ядре, т.к. не рассчитан пых на такую работу)
                          +2
                          Покажите пример кода, где будут проявляться утечки.
                            +1
                            Когда последний раз проверяли php-демоны на утечки памяти? На какой версии?
                              +1
                              слишком много утечек в ядре

                              у меня в продакшене уже по пол года демоны на PHP крутятся и все хорошо. В самом ядре утечек не сильно много, их очень оперативно находят и фиксят. Более того, утечки в ядре влияли бы на php-fpm. В целом сейчас риск схлопотать текущую память есть только если пользоваться какими-то экстеншенами сторонними.
                                0
                                Видимо у меня устаревшая информация, кстати с хабра (увидел, разочаровался, стал ждать пока поправят и забыл). Что ж я только рад, теперь надо только ждать миграции всего и вся на эту вещь.
                                  0
                                  Там к слову ни слова о утечках в ядре. Только в коде автора статьи и возможные подтекания в расширениях и библиотеках.

                                  Да и решения в духе php-pm не требуют «переписывания», если у них есть слой абстракций над SAPI (PSR-7, HttpKernel). Эта штука позволяет просто в короткие сроки ускорить приложение за счет невилирования расходов на бутстрапинг приложения. Вы можете после каждой 1000-чи запросов например перезапускать воркеры для надежности.
                                    0
                                    Ещё не мало интересно что там со скоупами сервисов делать…
                                      0
                                      А что именно вы хотите с ними делать? Пусть себе висят в памяти, если они состояние не сохраняют. Скажем у доктрины можно просто отчистить unit-of-work и дальше себе пусть висит.
                                        0
                                        Да пусть висят, просто интересно поведение, если я допустим делаю сервис, зависящий от security.token_storage, то будет ли это нормально работать (пересоздавать мой сервис для каждого нового запроса) или нет?

                                        P.S. В спринге это решается указанием области видимости сервиса: запрос, синглтон,…
                                          0
                                          можно дропать сервисы в скоупе реквеста, это бы было пожалуй правильно.
                                            0
                                            Не очень-то это правильно на самом деле это — костыль. К тому же из замороженного контейнера не так-то просто выпилить сервис…

                                            P.S. Ковыряюсь в билдере контейнера, там вроде как есть методы {enter,leave}Scope, осталось придумать как входить в скоуп до создания скоупа реквеста.
                                        0
                                        Ещё не мало интересно что там со скоупами сервисов делать…

                                        Для этого есть свойство shared и Kernel Events.
                                          0
                                          Не то, мне не нужно пересоздавать сервис на каждый новый подзапрос, мне нужно пересоздавать на каждый новый master запрос. А вот как запилить свой кастомный скоуп я так и не нашёл (может плохо искал?).
                                            0
                                            Сервисы не должны особо хранить состояние. Просто подключаем request_stack и забираем текущий master request.
                                              0
                                              Это конечно хорошо, до тех пор пока кто-нибудь не додумается забирать реквест из стека в конструкторе…
                                              0
                                              P.S. В спринге это решается указанием области видимости сервиса: запрос, синглтон,…

                                              В старых версиях symfony2 аналогично(смотрите ContainerAwareHttpKernel, компонент HttpKernel) — устанавливается область видимости request, и инжектится request(но опять же проблемы с саб-реквестами).

                                              Сейчас области видимости удалены. Вместо container/prototype — свойство shared. Вместо request — сервис @request_stack.
                                                0
                                                Где курить про shared? (Гугл упорно не отвечает)
                                                    0
                                                    Беда, зря это они так :(
                                                      0
                                                        0
                                                        Например потому, что скоуп это не нечто привязанное к запросу: я мог бы с лёгкостью (относительной) запилить коннект к rabbitmq и затолкать его ЖЦ в отдельный скоуп, чем городить десяток лишних методов для получения «текущего» коннекта (да, пример высосан из пальца, но всё же… в конце концов можно эту идею привернуть например к long poll и сокетам) + в большинстве случаев проще явно указывать зависимость от текущего состояния в явном виде, а в случае полной замены на request_stack придётся дёргать его в каждом из методов сервиса, а не единожды в конструкторе — это немного ломает всю суть инверсии зависимостей.

                                                        Естественно все эти рассуждения нужны лишь если всё запускается демоном, в остальном не актуально ровно полностью.
                                                          0
                                                          Скоупы ограничивают доступ и только, они не решают полностью проблем с состояниями, а зависеть от состояния это плохо.
                                                            0
                                                            И всё же они ограничивают не доступ, а скорее контролируют ЖЦ. Да, не очень хорошо, но скоупы — это как раз решение, а не проблема.
                        0
                        Скорее PHP#. :)

                        Впрочем, после перетаскивания ООПа из мейнстримовых строго типизированных языков добавление строгой типизации в передачу аргументов выглядит закономерно, хоть и непривычно и даже холиваристо. Интересно, добавится ли в следующей версии declare(strict_operators=1) и ещё что-нибудь в таком духе…

                        Вообще, если уж мутировать в шарп, то скорее бы лямбды с нормальным синтаксисом родили. Они уже даже в джаве и плюсах есть, а разрабы похапэ всё упираются.
                          0
                          Справедливости ради, сейчас в стадии голосования почти «нормальные лямбды». Холивар внутри интерналс из-за синтаксиса (почему-то автор RFC не хочет использовать вариант, который ввели уже давным давно в Hack).
                            0
                            Оба-на. Где посмотреть обсуждение?
                              +1
                              RFC, Рассылка PHP Internals ну и на гитхабе в пулреквесте к RFC.

                              p.s. О, занятно, async/await хотят сделать зарезирвированным словом уже в php 7.1. Найс. Асинхронный пых не загорами, того и глядишь, как и в случае с корутинами, ноду обгоним.
                                0
                                Ох, и опять проголосовали против…

                                Лучше б оператор поменяли на ==>, тогда в запросах YaLinqo будет достаточно удалить кавычки вокруг «строковых лямбд», и синтаксис идеально совпадёт. :)

                                Странно видеть спор вокруг type hints. Автор в RFC явно сказал, что это техническое ограничение, а не проявление нелюбви к типизации. Добавить их в синтаксис большой проблемы не составит, полагаю, но это ж ещё реализовать надо будет в разумные сроки.

                                В споре вокруг явности и неявности use какую-либо позицию не принял. Хоть что-нибудь сделали бы уже, хоть как-нибудь.
                                  0
                                  Ох, и опять проголосовали против…

                                  Против проголосовали именно из-за упертости относительно синтаксиса. Сейчас в интерналс вообще ведут дискуссию по этому поводу исходя из того, что есть же еще тайп хинтинг… ну вы поняли. Все сложно как всегда. В любом случае до 7.1 еще довольно много времени, так что может и сделают норм.
                          0
                          Пых-таки ближе к яве и я был бы не прочь, если бы он продолжал шагать в эту сторону, в конце концов иметь яву с поздним статическим связыванием и опциональной динамической типизацией — есть хорошо, настолько же, насколько хорошо иметь пых с вложенными и анонимными классами, областью видимости для классов в пространствах имён, дефолтной реализацией интерфейсов, и чего больше всего сейчас не хватает: объектами в константах… Эх, что-то я совсем размечтался.
                            0
                            Если вам хочется джавы с поздним связыванием, то существует C# с dynamic. Конечно, на шарпе писать код полностью в dynamic не принято, но это вполне работает. :) В последней версии добавили using static, string interpolation и прочее, так что есть и «глобальные функции», и выполнение кода в строчках, как в похапэ.

                            Анонимные классы в джаве в основном использовались для колбэков, с введением лямбд они уже не так актуальны. Что подразумевается под объектами в константах не понял, но статические неизменяемые поля иммутабельных типов возможны.

                            Вот дефолтной реализации методов интерфейсов нет, здесь джава в кои-то веки утёрла нос шарпу. Что забавно, главные пропагандисты простоты ещё дальше отошли от мантры «множественное наследование — зло». :)
                              +1
                              Как там Mono поживает?
                                0
                                Без понятия. А что с ним?
                                  0
                                  А можно ещё как-то запускать С# под никсами?
                                    0
                                    .NET Core скоро релизнется.

                                    Так что с Mono? Не слышал, чтобы оно работать переставало. :)
                                      0
                                      Слышал и видел, что оно не очень хорошо работало, да при этом ресурсов жрало больше чем PHP на той же задаче (сравнивали ASP .NET MVC и Symfony2).
                                        0
                                        Не слышал и не видел. :)

                                        А ресурсов каких больше жрало? Если при небольшой нагрузке сожрало больше памяти — допускаю. Если жрало больше процессорного времени — это уже что-то странное: шарп при прочих равных должен быть быстрее за счёт статической типизации.
                                          0
                                          Именно памяти, да.
                                            0
                                            А какой масштаб чисел вообще? В метрах, в частоте запросов, в процессорном времени?
                                          0
                                          Ну с учетом того что .NET MVC теперь будут официально супортить под mono и того что мелкомягкие потиху открывают исходники, можно ожидать что через пару лет моно будет вполне себе годной штукой. Вообще жалко что этого не произошло много раньше, как по мне шарп приятнее джавы.
                                            0
                                            Примерно так, да, через пару лет можно будет об использовании моно в серьёзном (под)проекте с нуля.
                                              0
                                              Да ладно, многие умудряются делать это уже сейчас. Если верить W3Techs, то ASP.NET имеет 16.5% веба при 12.9% IIS (топ 10 миллионов сайтов по Алексе). Сам удивляюсь. :))
                                                0
                                                Вы еще вспомните Xamarin, все жалуются но работает.
                                                  0
                                                  Что далеко ходить — все на PHP, Java и C++ жалуются, весь мейнстрим завален жалобами по уши. :)
                                                    0
                                                    На то он мэйнстрим, не жалуются только на то чем не пользуются.
                                                      +2
                                                      По-разному. В случае шарпа жалобы на язык слышно редко (ну… от страстных плюсовиков разве что), а вот жалоб на экосистему — хоть попой кушай. На питон жалобы очень редко слышу, хотя, может, я просто слишком далёк от него. При этом людей, считающих синтаксис Obj-C нормальным очень редко вижу.

                                                      Ещё у поклонников разных языков разная психологическая устойчивость. Эффект от комментария «PHP говно», «Java говно» и «C# говно» в топиках про PHP, Java и C#, соответственно, будет разным в пересчёте на минусы и карму. :)

                                                      Вот, кстати, PHP7 добавил похапэшникам уверенности. Обычно при написании сообщений «на грани», как у меня в этом топике, я без минусов не уходил. А тут — чистота. Первый признак, что язык развивается в нужном направлении, и у похапэшников пропадают сомнения в своём выборе. ;)
                                  0
                                  Мне просто шарп как вид не очень мил. Моему сердцу родней пачка наблюдателей, а не сигналы/слоты, да и управление многопоточностью я привык оставлять на 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 шарпу нос утёрла, ещё монады появились :). Я бы не стал использовать эту фичу в этих целях…
                                    +1
                                    В первый раз вижу человека, который предпочитает ручное разруливание потоков асинк-эвейтам. :)

                                    Моему сердцу родней пачка наблюдателей, а не сигналы/слоты

                                    А какая разница?
                                      0
                                      Вот такой вот я больной :) Просто если не охота с ними возиться, то и не нужно, есть решения которые это делают сами, а коли уж понадобилось, так пусть у меня будет полный контроль и без лапши в коде.

                                      В механизме управления и «явственности». Ну это опять-таки просто дело вкуса.
                                        +1
                                        без лапши в коде

                                        скажите честно, вы работали с async/await? Ибо пачка наблюдателей проще приведет к лапше.
                                          0
                                          Приходилось тыкать в go и шарпе. И таки да, как по мне, эта фича больше способствует лапшекоду, нежели набор вменяемых классов (естественно при создании анонимных будет сильно хуже, чем с async/await) наблюдателей.
                              +2
                              Одна беда, generics пока нет. Без них выразительность всего этого весьма ограничена.

                              Я не представляю как бы я писал на TypeScript без генериков.
                                0
                                Вы точно про дженерики? Они то будут может в php 7,1 а вот применимость их к массивам — вот это было бы славно. В TS с этим проще.
                                  +2
                                  Да-да, про них.

                                  Ну, int[], string[] и MyClass[] это вообще само собой разумеющиеся вещи, я сейчас даже не задумался о том, что в опсианной в статье реализации их может не быть.

                                  Более кошерные объявления массивов через женерики тоже приветствуются.

                                  А в TS вообще очень классно систему описания типов сделали. То как там можно описать любой потенциальный Hash с полями, которые в нем точно должны быть, могут быть, могут быть но немного не так…
                                +1
                                loadUser() всегда возвращает объект типа User, а findPostsForUser() всегда возвращает integer, нет никакой возможности сделать этот код верным.

                                Не возвращает, а принимает

                                function findPostsForUser(int $uid): array {
                                // Obviously something more robust.
                                return [new Post(), new Post()];
                                }
                                  0
                                  Введение режима строгой типизации

                                  Вам уже второй раз говорят что это не строгая типизация, а type hinting. Вводите людей в заблуждение, а потом они спрашивают про увеличение производительности.
                                    +1
                                    Если программа валится от неправильного типа в параметре или return при теоретической возможности неявного привидения, то это уже не hinting, а строгая типизация (на уровне сигнатур). Но её наличие не должно приводить к увеличению производительности, оно лишь может быть использовано транслятором для оптимизации. Может, но не должно. Основное назначение строгой типизации — это не увеличение производительности, а исключение ошибок неявного преобразования типов.
                                      0
                                      type-hinting будет работать и без declare(strict_types=1);, под которым я и подразумеваю термин «режим строгой типизации»
                                      0
                                      не туда запостил
                                        0
                                        Если программа валится от неправильного типа в параметре или return при теоретической возможности неявного привидения, то это уже не hinting, а строгая типизация (на уровне сигнатур).

                                        т.е. если мы в коде напишем if is_array/… то у нас сразу строгая типизация?
                                          +1
                                          Эмуляция строгой типизации в языке на уровне приложения.
                                            +1
                                            Эмуляция строгой типизации в языке на уровне приложения.

                                            Это обычный контроль типов для аргументов, который существует в php начиная с пятой версии(объекты и интерфейсы, а в дальнейшем и массивы с колбеками). Просто в седьмой версии добавлена возможность указывать скалярные типы, и контролировать тип возвращаемого значения. Это никакая не строгая типизация. Никто Вам не запрещает написать:
                                            function test(int $a) { $a = "hi"; }
                                            

                                            PHP был, есть, и будет динамическим языком программирования.
                                              0
                                              А что мешает при этом ему быть строго типизированным?

                                              Вы вообще отличаете строгость системы типов языка от времени присваивания типа переменной? Строгость — это именно контроль типов на уровне языка, недопущение использования одного типа там, где ожидается другой. А получен тип в компайл или рантайме — дело десятое, к строгости оно никакого отношения не имеет
                                                0
                                                ну так в том то и суть — контроля нет, это именно тайп хинтинг и не более. Контроль только на уровне аргументов и результата работы функций и методов.
                                                  0
                                                  Хинтинг — это когда варнинги, а когда программа падает — это уже контроль.
                                                    0
                                                    По Вашим словам, php строго типизированный начиная с версии 5.0. Спасибо что открыли нам всем глаза.
                                                      +2
                                                      Частично строго типизированный. Сейчас эта часть увеличивается. Глядишь когда-нибудь и тип локальных переменных можно будет обновлять, и '0123' + 456 будет вылетать.
                                                      0
                                                      Хинтинг — это когда варнинги


                                                      in PHP 5, this will be a recoverable fatal error, while PHP 7 will throw a TypeError exception.


                                                      То есть по сути это «варнинги». Просто более громкие. Если вы обернули все в try/catch в php7 то никаких падений.
                                                        +1
                                                        Нет, это не варнинги. Функция или метод не выполнятся, если неправильный тип передаётся.
                                            0
                                            Мы недавно с колегой обсуждали эту фичу. Изначально я был только за и всячески пытался показать ее полезность. Но в итоге он заставил меня усомниться не только в пользе скалярного, но и type hinting-а вообще.
                                            Все эти проверки производятся в рантайме, а не на этапе компиляции, которого у PHP нет. И без тестов или просто тестового запуска мы не увидим этих ошибок. И получается, что это нафиг не надо.
                                            Единственный плюс — это помощь IDE-шке. Но эта задача намного удобней решается phpDoc-ами.

                                            И хотя я был ярым сторонником type hinting-а, я не смог не согласиться с его доводом. От статичной типизации есть пользя т.к. проект просто не соберется при наличии ошибки. Но php — это не тот случай. Без проверки (или тестов) мы все-равно потащим ошибку в продакшен и там ее словит пользователь. А с тестами эта штука не нужна.

                                            Я не говорю, что это плохое нововведение. Но во-превых, оно пока еще сыровато, а во-вторых, несколько замедляет разработку. Не сильно но ощутимо. Все-таки не за строгость мы любим php.
                                              +3
                                              И без тестов или просто тестового запуска мы не увидим этих ошибок. И получается, что это нафиг не надо.


                                              1) Вы так или иначе при разработке запускаете запускаете код (тестами или вручную). Тайпхинтинг позволяет вам быстро определить что на вход методов/функций пришло что-то не то (или ушло что-то не то). То есть увеличивает шансы поймать багу еще на этапе девелопмента. Некоторые настолько упарываются что изобретают DbC для этого, используют ассерты и т.д.
                                              2) тайп хинтинг предоставляет статический анализ PHP кода может быть произведен куда более качественно если у анализатора будет вся необходимая информация о типах.
                                              3) тайп хинтинг в weak моде всегда гарантирует нам что на вход придет именно то что мы запросили, это уменьшает количество кода в различных библиотечных функциях и при этом не нужно хэндлить приведение типов руками. Ошибки будут валиться только при потере данных при касте типов.
                                              3) тайп хинтинг предоставляет тому самому рантайму информацию о типах, которую например может использовать opcache для более глубоких оптимизаций. В частности в internals обсуждалось это дело с позиции будущего внедрения JIT, которому для анпакинга и прочих радостей информация о типах весьма полезна.

                                              От статичной типизации есть пользя т.к. проект просто не соберется при наличии ошибки. Но php — это не тот случай.

                                              неудачный прогон статического анализатора — вот тебе и «не собрался». То что в PHP нет компиляции не означает что мы не можем организовать сборку.

                                              А с тестами эта штука не нужна.

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

                                              оно пока еще сыровато

                                              не сырова-то, а набор необходимых штук что бы прочувствовать всю прелесть тайп хинтинга в пыхе не полон. Не хватает дженериков и типизированных массивов.
                                                +3
                                                В дополнение к вышеизложенному: пускай лучше пользователь словит 500 и в логах будет запись о том, что произошло что-то неожиданное для разработчика, чем в результате очень нестрогого неявного преобразования типов, например, утечёт вся база данных.
                                                  –1
                                                  Типизация здесь не решает, в той же sql-инъекции строка остаётся string несмотря на наличие кавычек. Лучше в коде явно привести параметр к int, чем каждый раз падать из-за неправильно переданного параметра. Последнее время на проектах некоторые части ипишек на .net или java, сил нет бороться с их 500-ми и трейсами, даже есть правило, если апишка не на php, умножаем время разработки в три раза.
                                                    0
                                                    Вы в своей функции укажете, что параметр должен быть int и вам не нужно больше ничего приводить — или вызов до вашей функции не дойдёт, или будет приведение. Я же как клиент вашей функции буду выбирать один из минимум трех вариантов: включить неявное приведение при вызове (вернее не включать строгий контроль), явно приводить при вызове, или пускай падает.

                                                    Сейчас у меня как клиента такого выбора нет: вы можете приводить явно, а можете не приводить, можете бросать исключение, а можете не бросать, можете вообще не подумать, что придёт строка, а не число и в случае какой-нибудь '0777' даже сходу не сможете мне сказать, как ваша функция отработает, воспримет строку как десятичное число или как восьмеричное.
                                                      0
                                                      Клиент то точно не должен иметь возможности указывать как мне обрабатывать параметры метода, это нарушает принцип чёрного ящика и ведёт к непредсказуемым результатам. Потому всегда нужно приводить данные к нужному виду, а не доверять пользователям.

                                                      Падать приложение не должно, можно выбросить null или false или пустой массив-коллекцию в зависимости от конракта и бросить в лог сообщение с ошибкой. Исключение — это вообще крайний случай. Не подгрузятся товары по акции — ну и фиг с ним, в логе ошибка есть, исправят, а пользователю блок не покажем и т.д.

                                                      А если где-то критично важен тип, то не надо принимать скаляры, проси их завернуть в обёртки для примитивных типов, создав Acme\Int, Acme\String, тайпхинтя по ним и работая с умными объектами.
                                                        0
                                                        Так в том-то и дело, что не должен, вы ему показываете контракт, который он должен соблюдать. Как он будет его соблюдать, и что ему делать когда почему-то не получилось соблюсти — решать ему, а по умолчанию язык будет пытаться привести к указанному вами контракту.

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

                                                        А почему такое разделение на скаляры и объекты? Почему тогда вообще не отказаться от тайп хинтинга?
                                                          0
                                                          >>А почему такое разделение на скаляры и объекты?
                                                          Потому что в вэбе почти всё строки, от того утиная типизация в php так удобна и экономит столько времени, сил и средств. А все эти новые костыли работают очень криво, от неймспейсов я до сих пор офигеваю открыв исходник и увидев шапку из use.
                                                            +3
                                                            Потому что в вэбе почти всё строки

                                                            Угу, строки, которые нужно потом закастить в инты и флоты, обсчитать чего-нибудь, запихнуть в базу…

                                                            Есть не только сайтики в вэбе. А нэймспейсы — это лучше чем классы вида My_Super_Class_With_Snake_Case_Class_Names
                                                              +1
                                                              Так я об этом и пишу, скаляры надо обрабатывать либо заворачивать в объекты вроде Money или Int, потому и разделяю их.

                                                              Про неймспейсы — хрен редьки не слаще. new sfRequest было гораздо удобнее use Symfony\Component\HttpFoundation; new Request;
                                                              Есть исторически сложившиеся моменты из-за которых нет нормальной возможности их реализовать удобно, чтобы использовать use Acme\*, нет приватных классов из-за чего скоуп загажен служебными классами и неймспейсами.

                                                              Я вот решил написать статейку как мы в кучерявые времена делали свое неправильное DDD. Начал переписывать примеры с php4 на php5 и ловлю себя на мысли, что с интерфейсами и неймспейсами кода стало ещё больше писаться.
                                                              0
                                                              от неймспейсов я до сих пор офигеваю открыв исходник и увидев шапку из use

                                                              Это потому что неймспейсы можно использовать двумя способами: правильно и как принято в PHP. :) Многие библиотеки множат мелкие неймспейсы, а импортировать каждый класс нужно отдельно. В результате импортирование стало несмешной пародией на инклюды, когда без IDE невозможно угадать, где какой класс разместился.

                                                              Подробная статья: pornel.net/phpns

                                                              Там не без перегибов (не знаю, есть ли вообще язык, реализующий все хотелки автора), что-то исправлено в PHP7, но проблемы обозначены верно.
                                                                0
                                                                а импортировать каждый класс нужно отдельно


                                                                Зачем?

                                                                Вполне работает что-то вроде:

                                                                namespace AppBundle\Entity;
                                                                
                                                                use AppBundle\Entity\SfGuard;
                                                                
                                                                $user = new SfGuard\User();
                                                                
                                                                  +1
                                                                  wiki.php.net/rfc/group_use_declarations

                                                                  // from FooLibrary use Foo, Bar, Baz; // Nope
                                                                  
                                                                  use FooLibrary{ Foo, Bar, ClassD as Baz };
                                                                  


                                                                  хотя на счет синтаксиса — вариант с from как-то лучше, но… контекстно-зависимого лексера на момент реализации этого RFC в PHP еще небыло.

                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                  Самое читаемое