PHP — один из основных языков разработки в Badoo. В наших дата-центрах тысячи процессорных ядер заняты выполнением миллионов строк кода на PHP. Мы внимательно следим за новинками и активно ищем пути улучшения производительности, так как на наших объёмах даже небольшая оптимизация приводит к существенной экономии ресурсов. Одна из главных новостей в области производительности PHP — появление JIT в восьмой версии языка. Это, безусловно, не могло остаться без нашего внимания, и мы перевели статью о том, что есть JIT, как он будет реализован в PHP, зачем его решили делать и что от него ждать.
Если вы не вышли из пещеры или не прибыли из прошлого (в этом случае добро пожаловать), то уже знаете, что в PHP 8 будет JIT: на днях тихо-мирно завершилось голосование, и подавляющее большинство участников высказались за внедрение, так что всё решено.
Можно в порыве радости даже изобразить несколько безумных движений как на фото (это, к слову, называется «детройтский JIT»:
А теперь присядьте и прочтите эту развенчивающую мифы статью. Я хочу прояснить недопонимание, связанное с тем, что собой представляет JIT и чем он полезен, и рассказать о том, как он работает (но не слишком подробно, чтобы вы не заскучали).
Поскольку я не знаю, кто будет читать статью, пойду от простых вопросов к сложным. Если вы уже знаете ответ на вопрос в заголовке, можете смело пропускать соответствующую главу.
Что такое JIT?
PHP реализован на базе виртуальной машины (мы называем её Zend VM). Язык компилирует исходный PHP-код в инструкции, которые понимает виртуальная машина (это называется стадией компиляции). Полученные на стадии компиляции инструкции виртуальной машины мы называем опкодами (opcodes). На стадии исполнения (runtime) Zend VM исполняет опкоды, выполняя тем самым требуемую работу.
Эта схема прекрасно работает. Кроме того, инструменты вроде APC (раньше) и OpCachе (сегодня) кешируют результаты выполнения стадии компиляции, так что эта стадия выполняется лишь в случае необходимости.
Если коротко, то JIT — это стратегия компиляции just in time (в нужный момент), при которой код сначала переводится в промежуточное представление, которое затем в ходе исполнения превращается в машинный код, зависящий от архитектуры.
В PHP это означает, что JIT будет рассматривать полученные на стадии компиляции инструкции для виртуальной машины как промежуточное представление и выдавать машинный код, который будет выполняться уже не Zend VM, а непосредственно процессором.
Для чего PHP нужен JIT?
Незадолго до появления PHP 7.0 основным направлением работы команды PHP стала производительность языка. Большинство основных изменений в PHP 7.0 содержались в патче PHPNG, который значительно улучшил то, как PHP использует память и процессор. С тех пор каждому из нас приходится поглядывать за производительностью языка.
После выхода PHP 7.0 улучшения производительности продолжились: оптимизирована хеш-таблица (основная структура данных в PHP), внедрены специализация определённых опкодов в Zend VM и специализация определённых последовательностей в компиляторе, постоянно улучшается Optimizer (компонент OpCache) и реализовано ещё множество других изменений.
Суровая правда заключается в том, что в результате всех этих оптимизаций мы быстро приближаемся к пределу возможностей улучшения производительности.
Обратите внимание: под «пределом возможностей улучшения» я имею в виду тот факт, что компромиссы, на которые придётся пойти ради дальнейших улучшений, больше не выглядят привлекательными. Когда речь идёт об оптимизации производительности, мы всегда говорим о компромиссах. Нередко ради производительности нам приходится жертвовать простотой. Каждому хотелось бы думать, что самый простой код является и самым быстрым, но в современном мире программирования на С это не так. Самым быстрым чаще всего оказывается код, который подготовлен к использованию преимуществ внутреннего устройства архитектуры или встроенных в платформу/компилятор конструкций. Простота сама по себе не гарантирует лучшей производительности.
Поэтому на данном этапе оптимальным способом выжать из PHP ещё больше производительности выглядит внедрение JIT.
JIT ускорит работу моего сайта?
По всей вероятности, незначительно.
Возможно, это не тот ответ, который вы ожидали. Дело в том, что в общем случае PHP-приложения ограничены по вводу-выводу (I/O bound), а JIT лучше всего работает с кодом, который ограничен по процессору (CPU bound).
Что означает «ограничен по вводу-выводу и по процессору»?
Для описания характеристик общей производительности какого-то кода или приложения мы используем термины «ограничен по вводу-выводу» и «ограничен по процессору».
Самое простое определение:
- ограниченный по вводу-выводу код будет работать значительно быстрее, если мы найдём способ улучшить (уменьшить, оптимизировать) выполняемые операции ввода-вывода;
- ограниченный по процессору код будет работать значительно быстрее, если мы найдём способ улучшить (уменьшить, оптимизировать) выполняемые процессором инструкции или волшебным образом увеличим тактовую частоту процессора.
Код и приложение могут быть ограничены по вводу-выводу, по процессору или по тому и другому.
В целом PHP-приложения склонны быть ограничены по вводу-выводу: основным их узким местом зачастую оказываются операции ввода-вывода — подключение, чтение и запись в базу данных, кеши, файлы, сокеты и т. д.
Как выглядит ограниченный по процессору PHP-код?
Возможно, некоторые PHP-программисты плохо знакомы с ограниченным по процессору кодом из-за самой природы большинства PHP-приложений: обычно они выполняют роль связующего звена с базой данных или с кешем, поднимают и выдают небольшие количества HTML/JSON/XML-ответов.
Вы можете посмотреть на свою кодовую базу и найти много кода, который не имеет ничего общего с вводом-выводом, кода, который вызывает функции, никак не связанные с вводом-выводом. И вас может смутить, что это не делает ваше приложение ограниченным по процессору, хотя в его коде больше строк, не работающих с вводом-выводом, чем работающих.
Дело в том, что PHP — один из самых быстрых интерпретируемых языков. Не существует заметной разницы между вызовом функции, не задействующей ввод-вывод, в Zend VM и в машинном коде. Конечно, какая-то разница есть, но и машинный код, и Zend VM используют соглашение о вызовах (calling convention), поэтому не имеет значения, вызываете вы
какую-то_функцию_уровня_С()
в опкодах или в машинном коде, — это не окажет заметного влияния на производительность всего приложения, которое совершает вызов.Примечание: если говорить упрощённо, то соглашение о вызовах — это последовательность инструкций, исполняемых до входа в другую функцию. В обоих случаях соглашение о вызовах передаёт аргументы в стек.
Вы спросите: «А что насчёт циклов, хвостовых вызовов (tail calls) и прочего»? PHP достаточно сообразителен — и при включённом компоненте Optimizer из OpCache ваш код будет волшебным образом преобразован в более эффективную версию написанного вами.
Здесь нужно отметить, что JIT не изменит соглашения о вызовах Zend VM. Сделано так, потому что PHP должен уметь в любой момент переключаться между режимами JIT и VM (поэтому решили сохранить текущие соглашения). В результате любые вызовы, которые вы видите повсюду, с использованием JIT будут работать ненамного быстрее.
Если хотите увидеть, как выглядит ограниченный по процессору PHP-код, загляните сюда: https://github.com/php/php-src/blob/master/Zend/bench.php. Это крайний пример, но он показывает, что всё великолепие JIT раскрывается в математике.
Пришлось пойти на такой экстремальный компромисс, чтобы ускорить математические вычисления в PHP?
Нет. Мы пошли на это ради расширения спектра применения языка (и расширения значительного).
Не хотим хвастаться, но PHP доминирует в вебе. Если вы занимаетесь веб-разработкой и не рассматриваете использование PHP в своём следующем проекте, то вы что-то делаете неправильно (по мнению очень предвзятого разработчика PHP).
На первый взгляд может показаться, что ускорение математических вычислений в PHP имеет очень узкое применение. Однако это открывает нам дорогу, например, к машинному обучению, 3D-рендерингу, 2D-рендерингу (GUI) и анализу данных.
Почему это нельзя реализовать в PHP 7.4?
Выше я назвал JIT экстремальным компромиссом, и я действительно так считаю: это одна из самых сложных стратегий компилирования среди всех существующих, если не самая сложная. Внедрение JIT — это значительное повышение сложности.
Если вы спросите Дмитрия, автора JIT, сделал ли он PHP сложным, он ответит: «Нет, я ненавижу сложность» (это цитата).
По сути, «сложное» означает «то, что мы не понимаем». И сегодня мало кто из разработчиков языка действительно понимает имеющуюся реализацию JIT.
Работа над PHP 7.4 идёт быстрыми темпами, и внедрение JIT в эту версию приведёт к тому, что лишь единицы смогут отлаживать, исправлять и улучшать язык. Это неприемлемо для тех, кто голосовал против JIT в PHP 7.4.
До релиза PHP 8 многие из нас будут разбираться в реализации JIT. Есть фичи, которые мы хотим реализовать, и инструменты, которые хотим переписать для восьмой версии, поэтому вникнуть в JIT нам необходимо в первую очередь. Нам нужно это время, и мы очень благодарны, что большинство проголосовали за то, чтобы дать нам его.
Сложное не синоним ужасного. Сложное может быть прекрасным как звёздная туманность, и это как раз про JIT. Иными словами, даже когда у нас в команде человек 20 станут разбираться в JIT не хуже Дмитрия, это не изменит сложности самой природы JIT.
Разработка PHP замедлится?
Нет причин так думать. У нас достаточно времени, поэтому можно утверждать, что к моменту готовности PHP 8 среди нас будет достаточно тех, кто освоился с JIT настолько, чтобы работать не менее эффективно, чем сегодня, когда речь пойдёт об исправлении ошибок и развитии PHP.
Когда будете пытаться соотнести это с представлением об изначальной сложности JIT, помните, что большая часть времени, которое мы тратим на внедрение новых фич, уходит на их обсуждение. Чаще всего при работе над фичами и исправлении ошибок написание кода занимает минуты или часы, а обсуждения — недели или месяцы. В редких случаях код приходится писать часами или днями, но и тогда обсуждения всегда длятся дольше.
Это всё, что я хотел сказать.
И раз уж мы заговорили о производительности, приглашаю на доклад моего коллеги Павла Мурзакова 17 мая на конференции PHP Russia. Паша знает, как выжать последнюю CPU секунду из PHP-кода!
Комментарии (76)
Amikko
19.04.2019 22:13и не рассматриваете использование PHP в своём следующем проекте, то вы что-то делаете неправильно
Я понимаю использование в существующих проектах, которые уже написаны на PHP и работают. Но на кой мне PHP в следующем проекте, когда есть современные, намного лучше спроектированные языки, не обременённые обратной совместимостью с древним легаси.Dimensi
18.04.2019 23:38У современных языков нет готовых фреймворков (даже если есть, то молодые) и решение тривиальных задач может требовать некоторых усилий. Вместе с легаси у старых языков (не мертвых) есть и сообщество, есть куча решений которые прошли не один бой и даже выиграли войну, когда у новых языков всего этого нет и там опять все это нужно строить. Я не совсем уверен, но думаю бизнес чаще будет предпочитать стабильность вместо современности, а мы разработчики чаще всего работаем на бизнес. Хотя сам пхп меня не очень радует.
slonopotamus
18.04.2019 23:57Чем пхп лучше для веба по сравнению с питоном или руби?
Dimensi
18.04.2019 23:58Не знаю, разве в этом была тема? Тема была в том, чтоб уйти со старого языка на современные.
SerafimArts
19.04.2019 01:10+3Чем пхп лучше для веба по сравнению с питоном или руби?
Я извиняюсь за излишнюю резкость в суждениях, но: В PHP популярные фреймворки следуют хотя бы SOLID и GRASP (ну или пытаются), а в приведённых вами — я не видел ни одного решения, где бы был адекватный код. Лично у меня глаза кровоточат от засирания глобального пространства процедурками в Django или харкод с завязыванием на контроллеры внутри объекта реквеста в каком-нибудь RoR. Да и разработчики даже не слышали про инверсию зависимостей, учитывая то, что в этих языках даже интерфейсов нет.
Короче, как бы не смешно это звучало, но решения на PHP намного качественнее сабжей.Amikko
19.04.2019 03:41Интерфейсы — это концепция в голове у разработчика а вовсе не ключевое слово «interface» в языке. На python и ruby эта концепция прекрасно реализуется.
SerafimArts
19.04.2019 03:53Допускаю. Можете привести пример инверсии контроля не на основе ключевого слова "interface"?
Ну, например, примитивный пример из мира PHP с ISP + DI:
Код// Репа, которая предоставляет несколько возможностей interface UsersRepository extends FindableById, ProvidesAuthentication, ... { ... } // Одна из возможностей - получение Authentcatable объекта по почте+паролю interface ProvidesAuthentication { public function findUsingCredentials(string $email, string $password): ?Authentcatable; }
+
class AuthController { // Внедрение зависимостей объекта реквеста и репы // для получения Authentcatable сущности public function login(RequestInterface $request, ProvidesAuthentication $repository) { $user = $repository->findUsingCredentials( ...$request->only('email', 'password') ); } }
Amikko
19.04.2019 05:47from abc import ABC, abstractmethod from typing import Mapping, Optional import hashlib def hashfunc(string: str) -> str: return hashlib.md5(string.encode("utf-8")).hexdigest() class Request(ABC): @property @abstractmethod def form(self) -> Mapping[str, str]: pass class MockRequest(Request): def __init__(self, dummy_form: Mapping[str, str]) -> None: self.dummy_form = dummy_form @property def form(self) -> Mapping[str, str]: return self.dummy_form class Identifiable(ABC): @abstractmethod def get_id(self) -> int: pass class Authenticable(ABC): pass class User(Identifiable, Authenticable): def __init__(self, id: int, email: str, password_hash: str ) -> None: self.id = id self.email = email self.password_hash = password_hash def get_id(self) -> int: return self.id def get_email(self) -> str: return self.email def get_password_hash(self) -> str: return self.password_hash def __repr__(self) -> str: return f"id=={self.id}, email=={self.email}, password_hash=={self.password_hash}" class FindableById(ABC): @abstractmethod def find_by_id(self, id: int) -> Optional[Identifiable]: pass class ProvidesAuthentication(ABC): @abstractmethod def find_using_credentials(self, email: str, password: str ) -> Optional[Authenticable]: pass class MockUsersRepository(FindableById, ProvidesAuthentication): def __init__(self): self.dummy_users = ( User(1, "example@company.ru", hashfunc("123")), User(2, "test@ya.ru", hashfunc("456")), User(3, "foobar@gmail.com", hashfunc("789")), ) def find_by_id(self, id: int) -> Optional[Identifiable]: return next(( user for user in self.dummy_users if user.get_id() == id), None ) def find_using_credentials(self, email: str, password: str ) -> Optional[Authenticable]: for user in self.dummy_users: if user.get_email() == email and user.get_password_hash() == hashfunc(password): return user return None class AuthController: def login(self, request: Request, repository: ProvidesAuthentication): user = repository.find_using_credentials( request.form["email"], request.form["password"]) # debug print: print(f"user: {user}") if user: ... # login and redirect to '/index' else: ... # render template login_form.html if __name__ == "__main__": authController = AuthController() request = MockRequest({ "email": "test@ya.ru", "password": "456", }) users_repository = MockUsersRepository() authController.login(request, users_repository)
codemafia
19.04.2019 08:41И вы считает, что это ноимальный код?
Amikko
19.04.2019 13:17Вполне.
SerafimArts
19.04.2019 13:48Давайте посмотрим:
1) Множественное наследование.
2) Реализация абстрактных методов через декоратор + костыль с pass.
Т.е. никто не ударит по рукам, если реализация потеряется, только в рантайме во время вызова. Задача интерфейсов — гарантировать реализацию, а тут игра на внимательность. Ну такое, вроде и повторяет поведение, но костылями и половина плюшек срезается.
Возможно, действительно, при наличии множественного наследования и желании можно и без интерфейсов обойтись, но выглядит чужеродно как-то.
Amikko
19.04.2019 14:12В какой-нибудь джаве было бы
class MockUsersRepository implements FindableByIdInterface, ProvidesAuthenticationInterface
что ровно то же самое множественное наследование, которое обозвали словом implements. Собственно, как написали в комменте ниже, наличие интерфейсов на уровне синтаксиса — результат того, что разработчики компилятора не шмогли или поленились реализовать полноценное множественное наследование и предоставили вместо него легковесный суррогат — интерфейсы.
Питон динамический язык. За строгими проверками во время компиляции — это к товарищам C#, Java…
pass — это просто элемент синтаксиса. Впрочем, в сколько-нибудь нетривиальных случаях вместо pass пишут док-строку к методу.
"""Method description."""
SerafimArts
19.04.2019 16:05+2что ровно то же самое множественное наследование, которое обозвали словом implements.
Проблемы множественное наследования:
1) Не позволяет понимать какая реализация будет использована
2) Не позволяет ссылаться на родителя или потомка (через позднее статическое связывание)
Пересечение:
Интерфейсы и решают эту проблему, позволяя запросто пересекаться методам:
interface ProvidesUuid { public function getId(): UuidInterface; } interface Identifiable { public function getId(); } class User implements Identifiable, ProvidesUuid { ... }
В данном случае интерфейсы говорят о том, что юзер имеет идентификатор и предоставляет UUID, просто методы могут логически пересекаться. В случае множественного наследования никто гарантий не даёт.
Иерархия
Допустим, у нас есть потомок с переопределением метода:
class Storage { public function save(...) { ... } } class SomeAnotherStorage { public function save(...) { ... } } // Наследуемся от этих реализаций (в PHP так нельзя) class FileStorage extends Storage, SomeAnotherStorage { final public function save(File $file) { $file->saveTo('some/directory'); return parent::save($file); // И к какому parent должно быть обращение? } }
Можно и обратно, из родителя к потомку через late-binding. Главное в том, что сохраняется чёткая иерархия в том случае, если "extends" допускает только одного родителя.
Питон динамический язык. За строгими проверками во время компиляции — это к товарищам C#, Java…
И PHP. Он тоже проверяет что всё корректно перегружено по LSP и все нужные методы имеют реализацию во время компиляции.
RENESiS
19.04.2019 07:41be_a_dancer
19.04.2019 17:44+3Вы видимо не захотели прочитать книгу «Паттерны проектирования» от банды четырех.
Там говорилось об одном важном пункте композиция вместо наследования.
Рекомендую почитать аргументацию, она развенчивает все мифы, указанные по вашей ссылке и дает понимание, когда использовать то или другое.
К тому же я хочу обратить ваше внимание на наличие частичного наследования в PHP, реализованного в виде трейтов.
P.S. Строка выше относится к тому, что оригинальная статья опирается на синтаксис языка Java, которая не позволяет использовать множественное наследование. Кроме того, рекомендую прочитать википедию по поводу множественного наследования и убедиться, что оно далеко не так «красиво», как его изображают в вашем материале.RENESiS
19.04.2019 19:20Композиция, как и само понятие «интерфейс», прекрасно существует и без ключевого слова «interface».
Статья написана человеком, сформулировавшим принципы SOLID. Он лжец?be_a_dancer
19.04.2019 20:14+3Да, как бы странно это не было, я не считаю в данном случае Мартина правым в данной ситуации. Вернее, не согласен с тем, как этот материал повернут вами в контексте комментария, на который вы его отправили.
Смотрите. Кирилл говорит о том, что реализация на языке Python приводит к проблемам, которые будут найдены непосредственно в рантайме, что недопустимо. Что можно избежать в PHP использовав принципы того же Мартина — ISP + DI с использованием встроенных средств языка, а не внимательности разработчика. Кроме того, он апелирует к тому, что мы перестаем хардкодить и начинаем работать с абстракциями.
Суть комментария в том, что не важно, является ли ключевое слово вредным или нет: такое решение в языках есть и с ним мы живем. Если в Python реализовали множественное наследование через пень-колоду (к слову, сам автор в комментариях согласен с тем, что интерфейсы нужны), не дали нормального способа создавать абстрактные классы и методы, а в PHP это сделать реально, пусть и с использованием «вредного» interface (к слову, я не вижу дублирования кода в данном контексте, хотя если судить по Мартину оно здесь должно быть) — я выберу более безопасный, а как следствие, подходящий инструмент, который меня защитит от этого.
Кроме того. Мартин аппелирует к Eiffel, который решил проблему множественного наследования. Каким образом он это сделал? Мы можем указать, какие методы родительского класса мы наследуем. То есть потенциально доступна ситуация, когда родительский класс имеет какой-то метод, а дочерний — не имеет. Для меня такая ситуация является недопустимой и именно для этого принципиально и нужны интерфейсы. Это контракт, который гарантирует мне работу и ошибки компиляции.
P.S. Я не спорю с самой статьей и идеей. Если бы решилась проблема множественного наследования, принципиально у нас бы было всего два инструмента: класс и интерфейс, а абстрактный класс был бы не нужен, так как его функции перенял бы интерфейс. Ну или наоборот.RENESiS
19.04.2019 20:56Спасибо за развернутый ответ.
Но PHP ведь не компилируется, и проверки тайпхинтов все равно происходят в рантайме. Или нет? Конечно, еще в редакторе будут ошибки подсвечиваться, это плюс, не спорю.be_a_dancer
19.04.2019 21:11Вот тут вы правы. PHP — интерпретируемый язык программирования, несмотря на opcache и ввод JIT в 8 ветке языка. Но если у PHP есть что-то вроде
php -f index.php
где он ругнется на неверный синтаксис и бросит (сразу) error, то для python мы это увидим когда непосредственно столкнемся, что может произойти далеко не сразу (знатоки питона, если это не правда — поправляйте). Однако, и правда, все это происходит в рантайме.
Это дело решается тестами, но, поверьте, тесты не всегда хорошие и не всегда имеют 100% покрытия.
SerafimArts
19.04.2019 22:01+3И да и нет. Вывод сигнатур языка происходит во время раннего связывания, так что язык знает о них ещё ДО того как что-либо стартануть.
Это можно запросто проверить написав:
return 42; class Example {}
Класс Example будет доступен всегда, т.к. это инструкция раннего этапа, а return — позднего (рантайма).
Заголовок спойлераЭто почти всегда так за редкими исключениями, декларации в сочетании с if:
return 42; if (true) { class Example {} }
barbeer
21.04.2019 12:17Ну, если совсем грубо — то никто не мешает сделать ровно то же самое на любом друом языке (включая python/ruby), только без сахара в виде вынесения единственного публичного метода класса в интерфейс и проверки типов. Можно точно так же определить соглашение, точно так же реализовать это соглашение в классах, точно так же подменять конкретную реализацию, и т.п. Язык в этом никак не поможет, да, но сделать это всё равно можно, и кода будет написано плюс-минус столько же.
homm
19.04.2019 12:24засирания глобального пространства процедурками в Django
Что? В Питоне нет глобального пространства имен, все имена в модулях.
SerafimArts
19.04.2019 13:08Да, это правда. Я некорректно выразился, имел ввиду вроде таких вот файликов: github.com/django/django/blob/master/django/middleware/csrf.py Где вперемешку кони и люди. Это всё действительно скрыто и наружу отправится только при явном импорте, но выглядит всё равно довольно стрёмно и с повышенной связанностью (cohesion который).
Amikko
19.04.2019 13:45В этом файлике две публичные функции get_token, rotate_token и один публичный класс CsrfViewMiddleware. Прочие функции приватные в соответствии с соглашением об именовании.
quantum
19.04.2019 14:30Тут просто нужно смириться, что файлики в пайтоне — это не файлики в пхп, а модули и тогда станет легче это воспринимать)
SerafimArts
19.04.2019 16:24Так я же не кричу во всю глотку что это говнокод (хотя он действительно попахивает магическими константами и хардкодом логики с нарушением open/close). Я просто написал выше, что тем, кто привык к строгости Java/C#/PHP, где один файл — один класс подобные простыни в стиле Python/JS доставляют моральные страдания.
P.S. А не, действительно в самом первом моём комментарии я излишне эмоционально всё это расписал со словами «засирание глобального пространства», что вполне интерпретируется как «говнокод». Только кто-то (не будут тыкать в себя пальцем) забыл про то, что этот код инкапсулируется с помощью магии модулей и PEP во что меня можно смело тыкать носом. В любом случае он лучше того, что обычно творится в JS с теми самыми же модулями. Но всё равно плохой. =)))homm
19.04.2019 16:25Так вроде кричите
SerafimArts
19.04.2019 16:53Угу, я уже дописал в «P.S.» это. С кем не бывает?
homm
19.04.2019 16:59«Нет, с ним всё в порядке, проблем никаких, делает то, что нужно, неудобств не доставляет. Но всё равно плохой.»
Так и скажите, что не нравится, но ещё не придумали, почему.
SerafimArts
19.04.2019 17:31+1Ну я же придумал, в самом начале коммента выше отписал:
1) Магические константы
2) Нарушение open/close с харкодом логики
В пыхе для таких штук вначале создаётся интерфейс, а на него уже навешиваются реализации. Отдельно для сессий, отдельно для флеш-токенов, отдельно для…
Вон, у зенда даже зависимости на request нет: github.com/zendframework/zend-expressive-csrf/blob/master/src/SessionCsrfGuard.php Можно прикрутить к консольке. Не знаю зачем, но главное что можно.
P.S. Знаю даже зачем. Можно запилить интеграционное тестирование без селениума и даже без запуска HTTP слоя.
homm
19.04.2019 17:06Что такое «попахивает магическими константами»? Там либо есть магические константы, либо нет. В данном случае я ничего такого не вижу. Если вам «попахивает магическими константами», возможно это где-то рядом с вами чем-то пахнет.
SerafimArts
19.04.2019 17:33+2github.com/django/django/blob/master/django/middleware/csrf.py#L85-L92 Т.е. это нормально три раза дублировать одну и ту же строчку? А как её подменить на другую?
Sannis
19.04.2019 18:00Рискну спросить: а как бы вы это написали на PHP?
SerafimArts
19.04.2019 18:16+2Конкретно этот кейс? Тогда за предоставление csrf токена должен отвечать соответствующий интерфейс:
interface CsrfProviderInterface { public function getToken(): string; }
Заголовок спойлера// Для HTTP реквеста class RequestCsrfProvider implements CsrfProviderInterface { // Можно в константу с переопределением через наследование. // А можно и в переменную. protected const CSRF_REQUEST_ATTRIBUTE = 'CSRF_COOKIE'; public function __construct(ServerRequestInterface $request) { ... } public function getToken(): string { ... }; }
// Блокирующий (для тестов, например) class FlushCsrfProvider implement CsrfProviderInterface { public function getToken(): string { return \random_bytes(32); } }
class ConstantCsrfProvider implements CsrfProviderInterface {}
homm
19.04.2019 17:13-3привык к строгости Java/C#/PHP, где один файл — один класс
Не могу перестать удивляться с вашего комментария. В приведенном вами в пример файле ровно один класс.
Но вы не задумывались отчего в PHP (про остальные не скажу) такая строгость (один файл — один класс)? Она там далеко не от излишнего удобства. Просто в PHP есть такое понятие, как автолоад. Если класс не будет лежать в строго определенном месте, он просто не будет найден.
В питоне такой проблемы нет, код может быть организован так, как удобно разработчику. А благодаря импортам в родительские модули, может быть доступен из мест, более удобных для пользователя.
SerafimArts
19.04.2019 17:36+2Но вы не задумывались отчего в PHP (про остальные не скажу) такая строгость (один файл — один класс)? Она там далеко не от излишнего удобства. Просто в PHP есть такое понятие, как автолоад. Если класс не будет лежать в строго определенном месте, он просто не будет найден.
Класс будет вполне найден. Как раз из-за удобства так и сделано. Автолоад не влияет на ограничения по количеству кода в файлах, их внутренности, никак не влияет на именование и вообще ни на что. Автолоад — это императивная функция языка, которой достаточно подсунуть нужный файл во время триггера события «Class not found»: www.php.net/manual/ru/function.spl-autoload-register.php
Вы путаете инструкцию автолоадинга со стандартами PSR-0 и PSR-4. Ну или PHP перепутали с Java. Т.е. это примерно как сказать то, что все приватные методы должны быть с нижним подчёркиванием. Спека (это в PEP кажется?) говорит одно, а на деле никто не ограничивает.
Не могу перестать удивляться с вашего комментария. В приведенном вами в пример файле ровно один класс.
И две публичные функции с захардкоженными внутри строчками.
Virviil
20.04.2019 18:32-2Судя по всему, современный веб развивается настолько бурно, что «готовые решения» существуют только для legacy проектов.
Приведём в пример graphql. С чего бы гипотетический «другой язык», появившийся в 2013 году может иметь менее зрелое решение, чем PHP, если graphql появился в 2015?
Если говорить о «хорошо спроектированных языках», то Laravel появляется в 2011 году, в то время как Rails — в 2005. И Django в 2005. Symphony правда тоже появляется в 2005, но даже если считать что это технологии одного поколения — о каком отсутствии фреймворков вы говорите? А ведь в этих Питонах и Рубях всегда были ООП, и история «объектно ориентированного проектирования» у них побольше чем у PHP.
Кроме того, сегодня среды череды бесконечных одинаковых MVC фреймворков каждый пытается предложить свой изюм. Кто-то говорит «интегрируй АИ легко и нативно» как фреймворка на Python. Кто-то говорит «разрабатывай фронт и бэк одинаково» как нода решения. Кто-то гонится за бешеной скоростью работы на Го. Или бешеной скоростью разработки на рельсах. Или горизонтальным масштабированием из коробки у Erlang/Elixir… Где изюмчик у PHP — для меня загадка. И поэтому слышать «PHP всегда фигурирует при выборе стека разработки» для меня тоже загадка.
zim32
18.04.2019 23:00Действительно непонятно что это даст в итоге. Надеюсь машинный код тоже кешируется? А то каждый запрос компилить в машинный код наверное не хорошо.
jehy
18.04.2019 23:37Не хотим хвастаться, но PHP доминирует в вебе. Если вы занимаетесь веб-разработкой и не рассматриваете использование PHP в своём следующем проекте, то вы что-то делаете неправильно.
Простите, при всей моей ностальгической любви к PHP, вы тег «ирония» потеряли. Осторожнее, люди же читают и верят.SerafimArts
19.04.2019 01:13+1~80% сайтов в интернете на PHP:
1) w3techs.com/technologies/details/pl-php/all/all
2) w3techs.com/technologies/history_overview/programming_languagejehy
19.04.2019 02:03Есть ложь, есть наглая ложь, а есть статистика.
Убираем из этой статистики друпалы, вордпрессы, битриксы и так далее — получим совершенно другую картину.
Ну или можно пойти от обратного — как много вы знаете крупных порталов, продуктов и систем, которые написаны на PHP?
Если кто-то правда сидит и думает, на чём ему написать очередной CRUD — да, PHP отлично под это подходит. Но разбрасываться фразами про «доминирование» и «делаете неправильно» — это очень странно. Причём вне зависимости от контекста.roman901
19.04.2019 02:22Тем не менее тезисы «PHP доминирует в вебе» и «PHP занимает 80% рынка» и имеют схожий смысл.
jehy
19.04.2019 08:15Я к тому что эти «80% рынка» не имеют отношения к разработке, и являются готовыми настроенными решениями. Что никак не должно влиять на решение выбора языка при разработке своего проекта.
Если мы хотим узнать, что именно «на рынке» — можно, посмотреть последний опрос на stackoverflow по используемым языкам, например. Или зайти на hh и сравнить количество вакансий по разным языкам. И увидеть, что там и в помине нет никаких 80% PHP.
SerafimArts
19.04.2019 03:22Убираем из этой статистики друпалы, вордпрессы, битриксы и так далее — получим совершенно другую картину.
А заодно RoR, Django, .NET… да давайте вообще всё уберём. Ну просто почему бы и нет? За компанию. И вообще интернет — это миф, его не существует!
Ну или можно пойти от обратного — как много вы знаете крупных порталов, продуктов и систем, которые написаны на PHP?
Я так понимаю, что какие-то левые ресурсы mail, yandex и rambler не в счёт? Так же как и госуслуги, ну или кусок vk, кусок facebook, кусок youtube, продукты 1c (тьфу-тьфу), badoo, альфа-банк, delicious, wikipedia, dailymotion, w3 counter, photobucket, avito, lamoda, yahoo, ted, roave, onliner, skyeng, blablacar, pornhub… Кстати, а habr считается крупным или toster? Так-с, дайте подумать, даже не знаю, вроде Вы действительно правы, таких сайтов не существует в природе!
Terras
19.04.2019 07:31Если честно, чем дальше идет развитие PHP — тем сильнее он становится похожим на Java/Net. Т.е. если мы условно отходим от парадигмы того, что PHP — это для небольших сайтов, а PHP — это интерпрайз, ПХП просто забирает проверенные фичи из Java/.Net
Например, если мы сравниваем Spring /.Net / Symfony — они как браться близнцы уже=) Особенно если учитывать магию конфигов и аннотаций.
Почему это плохо! Чем сильнее php будет становиться интерпрайз языком, тем выше станут требования к уровню разработчика. А если граница между ПХП и Java станет очень близкой, смысл учить ПХП про пропадет, ведь за тот же уровень усилий за Java платят лучше.
Например, если сейчас попытаться найти разработчика на Symfony, который могет в ООП, Паттерны, Стратегию развития интерпрайз проектов и прочее — вы поймете, что на рынке единицы таких разработчиков, а их уровень оплаты 120 + (что уже не сильно отличается от Java).
т.е. смотрите, почему умер Ruby c ROR (не умер, а потерял популярность) — он оказался сложнее, чем ПХП для тех же задач, и он не мог быть универсльным, как Python.
Если ПХП потеряет свою простоту для веба, у него ничего не будет против Java/.NetArik
19.04.2019 07:46+1, как бы не получили две официальные версии PHP: энтерпрайз решение для нагруженных проектов (платную?), а вторую лайтовую для простых страничек и шард-хостингов (айфон 8? :) ). Как несколько лет назад простой PHP vs PHP-phalcon, HHVM (бесплатные решения, но точка выхода выше)
psFitz
19.04.2019 10:04+1Какая же статья о пхп будет без срача о том что руби или Пайтон лучше?
JekaMas
19.04.2019 12:56Мне честно кажется, что python уже опоздал. Пока народ холиварил о "красоте" python, PHP выкатило много крутых обновлений и системы типов, и объектов и производительности. Тот же python ничего сравнимого за эти годы, скажем с PHP 5.5, не сделал.
SerafimArts
19.04.2019 13:25Ну в питоне тоже в 3ке подвезли типизацию: https://docs.python.org/3/library/typing.html Почему сразу ничего? =)
Pydeg
20.04.2019 11:39Вы, похоже, сильно далеки от python. JIT, «прекомпиляция», асинхронность, ffi, и прочие мастхев фичи, которые в php появляются/планируются только сейчас и подаются как революшн, в python существуют и развиваются уже добрый десяток дет
JekaMas
20.04.2019 12:04Программировал на обоих. Раньше да, python кое в чем положительно отличался, но сейчас он мало развивается и куда-то непонятно куда.
AirLight
19.04.2019 18:25Нельзя не упомянуть, что уже существует альтернатива JIT для PHP www.peachpie.io
Tatikoma
20.04.2019 18:46Как-то их сайт не очень быстро работает, для компилируемого языка. Или дело в Microsoft Azure на котором их сайт лежит?
Если говорить про такие решения, то есть ещё KPHP. По крайней мере был.
serf
19.04.2019 18:35Вот еще подход к ускорению medium.com/wasmer/php-ext-wasm-migrating-from-wasmi-to-wasmer-4d1014f41c88
Bazis007
21.04.2019 12:16Для своих задач PHP отличный язык, который стремиться всё больше и больше расширить свою сферу применения, не смотря на огромную армию хейтеров. (предлагаю о вкусах не спорить)
А вот чего хочется видеть, так это некого аналога go-рутин и каналов(нативно, без смс, костылей и регистрации)
vdem
Вот если бы еще кашу из функций как-то в порядок привели в 8-й версии. Разные стили именования, разная очередность параметров и прочие соглашения…
SerafimArts
То получили бы полную и катастрофическую поломку обратной совместимости, ещё хуже чем в Python 2 -> Python 3
P.S. Да и статья как бы не об этом
bolk
Это достаточно просто сделать без поломки. Способ предлагался, почему-то по нему не пошли. Надо считать примитивные типы некими псевдообъектами с возможность вызова у них методов. Тогда ф-и можно оставить как есть, а новые методы проименовать нормально.
Compolomus
О каком способе идёт речь? Поделитесь ссылкой на rfc
SerafimArts
Это не RFC, это пакет Никиты: https://github.com/nikic/scalar_objects
bolk
RFC там не было, вроде, был какой-то модуль и идея за ним в виде какой-то статьи или сообщения в internal, не помню точно.
SerafimArts
Ну это такое себе решение. Запихнуть в строку весь stdlib языка — сомнительный профит. Мне больше нравится с распихиванием функций по неймспейсам и прокидывание фоллбеков. Если я не путаю, то что-то в стиле Groovy, когда вызов
println
фактически ссылается наSystem.out.println
. В PHP возможно реализовать такое же, гдеmap([...])
будет ссылаться на какой-нибудьuse function Core\Array\map
Gemorroj
Так а почему бы не заалиасить? И тянуть какое-то время по 2+ наименований функций.