Комментарии 76
P.S. Да и статья как бы не об этом
О каком способе идёт речь? Поделитесь ссылкой на rfc
Это не RFC, это пакет Никиты: https://github.com/nikic/scalar_objects
Ну это такое себе решение. Запихнуть в строку весь stdlib языка — сомнительный профит. Мне больше нравится с распихиванием функций по неймспейсам и прокидывание фоллбеков. Если я не путаю, то что-то в стиле Groovy, когда вызов println
фактически ссылается на System.out.println
. В PHP возможно реализовать такое же, где map([...])
будет ссылаться на какой-нибудь use function Core\Array\map
Так а почему бы не заалиасить? И тянуть какое-то время по 2+ наименований функций.
и не рассматриваете использование PHP в своём следующем проекте, то вы что-то делаете неправильно
Я понимаю использование в существующих проектах, которые уже написаны на PHP и работают. Но на кой мне PHP в следующем проекте, когда есть современные, намного лучше спроектированные языки, не обременённые обратной совместимостью с древним легаси.
Чем пхп лучше для веба по сравнению с питоном или руби?
Чем пхп лучше для веба по сравнению с питоном или руби?
Я извиняюсь за излишнюю резкость в суждениях, но: В PHP популярные фреймворки следуют хотя бы SOLID и GRASP (ну или пытаются), а в приведённых вами — я не видел ни одного решения, где бы был адекватный код. Лично у меня глаза кровоточат от засирания глобального пространства процедурками в Django или харкод с завязыванием на контроллеры внутри объекта реквеста в каком-нибудь RoR. Да и разработчики даже не слышали про инверсию зависимостей, учитывая то, что в этих языках даже интерфейсов нет.
Короче, как бы не смешно это звучало, но решения на PHP намного качественнее сабжей.
Допускаю. Можете привести пример инверсии контроля не на основе ключевого слова "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')
);
}
}
Прошу заметить, что из подобной реализации мы можем двумя пинками подменить реализации и даже перейти с классических репозиториев на какой-нибудь CQRS, т.к. реализация нам не важна, а интерфейс с одним методом (за счёт ISP) вполне себе команда.
P.S. Я на всякий случай хочу напомнить, что ни в питоне, ни в руби нельзя реализовать методы без их имплементации. Только суперкостылями в виде выкидывания исключений (ну или пилить декораторы, если мы про питон).
from 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)
Давайте посмотрим:
1) Множественное наследование.
2) Реализация абстрактных методов через декоратор + костыль с pass.
Т.е. никто не ударит по рукам, если реализация потеряется, только в рантайме во время вызова. Задача интерфейсов — гарантировать реализацию, а тут игра на внимательность. Ну такое, вроде и повторяет поведение, но костылями и половина плюшек срезается.
Возможно, действительно, при наличии множественного наследования и желании можно и без интерфейсов обойтись, но выглядит чужеродно как-то.
class MockUsersRepository implements FindableByIdInterface, ProvidesAuthenticationInterface
что ровно то же самое множественное наследование, которое обозвали словом implements. Собственно, как написали в комменте ниже, наличие интерфейсов на уровне синтаксиса — результат того, что разработчики компилятора не шмогли или поленились реализовать полноценное множественное наследование и предоставили вместо него легковесный суррогат — интерфейсы.
Питон динамический язык. За строгими проверками во время компиляции — это к товарищам C#, Java…
pass — это просто элемент синтаксиса. Впрочем, в сколько-нибудь нетривиальных случаях вместо pass пишут док-строку к методу.
"""Method description."""
что ровно то же самое множественное наследование, которое обозвали словом 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 и все нужные методы имеют реализацию во время компиляции.
Там говорилось об одном важном пункте композиция вместо наследования.
Рекомендую почитать аргументацию, она развенчивает все мифы, указанные по вашей ссылке и дает понимание, когда использовать то или другое.
К тому же я хочу обратить ваше внимание на наличие частичного наследования в PHP, реализованного в виде трейтов.
P.S. Строка выше относится к тому, что оригинальная статья опирается на синтаксис языка Java, которая не позволяет использовать множественное наследование. Кроме того, рекомендую прочитать википедию по поводу множественного наследования и убедиться, что оно далеко не так «красиво», как его изображают в вашем материале.
Статья написана человеком, сформулировавшим принципы SOLID. Он лжец?
Смотрите. Кирилл говорит о том, что реализация на языке Python приводит к проблемам, которые будут найдены непосредственно в рантайме, что недопустимо. Что можно избежать в PHP использовав принципы того же Мартина — ISP + DI с использованием встроенных средств языка, а не внимательности разработчика. Кроме того, он апелирует к тому, что мы перестаем хардкодить и начинаем работать с абстракциями.
Суть комментария в том, что не важно, является ли ключевое слово вредным или нет: такое решение в языках есть и с ним мы живем. Если в Python реализовали множественное наследование через пень-колоду (к слову, сам автор в комментариях согласен с тем, что интерфейсы нужны), не дали нормального способа создавать абстрактные классы и методы, а в PHP это сделать реально, пусть и с использованием «вредного» interface (к слову, я не вижу дублирования кода в данном контексте, хотя если судить по Мартину оно здесь должно быть) — я выберу более безопасный, а как следствие, подходящий инструмент, который меня защитит от этого.
Кроме того. Мартин аппелирует к Eiffel, который решил проблему множественного наследования. Каким образом он это сделал? Мы можем указать, какие методы родительского класса мы наследуем. То есть потенциально доступна ситуация, когда родительский класс имеет какой-то метод, а дочерний — не имеет. Для меня такая ситуация является недопустимой и именно для этого принципиально и нужны интерфейсы. Это контракт, который гарантирует мне работу и ошибки компиляции.
P.S. Я не спорю с самой статьей и идеей. Если бы решилась проблема множественного наследования, принципиально у нас бы было всего два инструмента: класс и интерфейс, а абстрактный класс был бы не нужен, так как его функции перенял бы интерфейс. Ну или наоборот.
Но PHP ведь не компилируется, и проверки тайпхинтов все равно происходят в рантайме. Или нет? Конечно, еще в редакторе будут ошибки подсвечиваться, это плюс, не спорю.
php -f index.php
где он ругнется на неверный синтаксис и бросит (сразу) error, то для python мы это увидим когда непосредственно столкнемся, что может произойти далеко не сразу (знатоки питона, если это не правда — поправляйте). Однако, и правда, все это происходит в рантайме.Это дело решается тестами, но, поверьте, тесты не всегда хорошие и не всегда имеют 100% покрытия.
Это можно запросто проверить написав:
return 42;
class Example {}
Класс Example будет доступен всегда, т.к. это инструкция раннего этапа, а return — позднего (рантайма).
return 42;
if (true) {
class Example {}
}
Построение иерархии зависимостей и перегрузки (переопределения) методов происходят точно так же на раннем этапе. Т.е. язык уже перед тем как запустить какой-либо код внутри файла (файл в PHP это единица компиляции) заранее знает правильно ли сделано наследование/имплементация.
Ну а затем уже происходит рантайм с проверками типов. И вот на этом этапе могут возникнуть другие ошибки типизации.
Т.е. другими словами в PHP есть несколько этапов работы и один из них очень похож на поведение компиляторов. На этом этапе собирается из исходников опокод, оптимизируется (включая DCE), выводятся типы и прочее. А затем уже через VM запускаются опкоды, которые уже непосредственно выполняют императивные инструкции.
const SOME = 'hello' . 42 . 'world';
Эта конструкция выполняется после парсинга, но до рантайма, собираясь в одну единственную инструкцию виртуальной машины. Любая ошибка в ней приведёт к её возникновению ещё до того как код запустился. Если мы добавим что-то спереди — оно выполнено не будет.
$var = 'hello' . 42 . 'world';
А вот это уже инструкция рантайма (т.е. набор инструкций для VM) и поломать её до запуска самой VM могут лишь синтаксические ошибки.
засирания глобального пространства процедурками в Django
Что? В Питоне нет глобального пространства имен, все имена в модулях.
P.S. А не, действительно в самом первом моём комментарии я излишне эмоционально всё это расписал со словами «засирание глобального пространства», что вполне интерпретируется как «говнокод». Только кто-то (не будут тыкать в себя пальцем) забыл про то, что этот код инкапсулируется с помощью магии модулей и PEP во что меня можно смело тыкать носом. В любом случае он лучше того, что обычно творится в JS с теми самыми же модулями. Но всё равно плохой. =)))
Так вроде кричите
«Нет, с ним всё в порядке, проблем никаких, делает то, что нужно, неудобств не доставляет. Но всё равно плохой.»
Так и скажите, что не нравится, но ещё не придумали, почему.
1) Магические константы
2) Нарушение open/close с харкодом логики
В пыхе для таких штук вначале создаётся интерфейс, а на него уже навешиваются реализации. Отдельно для сессий, отдельно для флеш-токенов, отдельно для…
Вон, у зенда даже зависимости на request нет: github.com/zendframework/zend-expressive-csrf/blob/master/src/SessionCsrfGuard.php Можно прикрутить к консольке. Не знаю зачем, но главное что можно.
P.S. Знаю даже зачем. Можно запилить интеграционное тестирование без селениума и даже без запуска HTTP слоя.
Что такое «попахивает магическими константами»? Там либо есть магические константы, либо нет. В данном случае я ничего такого не вижу. Если вам «попахивает магическими константами», возможно это где-то рядом с вами чем-то пахнет.
Рискну спросить: а как бы вы это написали на PHP?
Конкретно этот кейс? Тогда за предоставление 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 {}
Конечно, это адовый оверинжинеринг в рамках обычных проектов, где можно просто TODO оставить и переписать код в любой момент. Но именно так и обязаны писать внутри фреймворков и ядре, где предполагается то, что всё можно заменить. А "META", если не путаю (поправьте) — это HTTP заголовки, которые обязаны быть заменяемыми.
привык к строгости Java/C#/PHP, где один файл — один класс
Не могу перестать удивляться с вашего комментария. В приведенном вами в пример файле ровно один класс.
Но вы не задумывались отчего в PHP (про остальные не скажу) такая строгость (один файл — один класс)? Она там далеко не от излишнего удобства. Просто в PHP есть такое понятие, как автолоад. Если класс не будет лежать в строго определенном месте, он просто не будет найден.
В питоне такой проблемы нет, код может быть организован так, как удобно разработчику. А благодаря импортам в родительские модули, может быть доступен из мест, более удобных для пользователя.
Но вы не задумывались отчего в PHP (про остальные не скажу) такая строгость (один файл — один класс)? Она там далеко не от излишнего удобства. Просто в PHP есть такое понятие, как автолоад. Если класс не будет лежать в строго определенном месте, он просто не будет найден.
Класс будет вполне найден. Как раз из-за удобства так и сделано. Автолоад не влияет на ограничения по количеству кода в файлах, их внутренности, никак не влияет на именование и вообще ни на что. Автолоад — это императивная функция языка, которой достаточно подсунуть нужный файл во время триггера события «Class not found»: www.php.net/manual/ru/function.spl-autoload-register.php
Вы путаете инструкцию автолоадинга со стандартами PSR-0 и PSR-4. Ну или PHP перепутали с Java. Т.е. это примерно как сказать то, что все приватные методы должны быть с нижним подчёркиванием. Спека (это в PEP кажется?) говорит одно, а на деле никто не ограничивает.
Не могу перестать удивляться с вашего комментария. В приведенном вами в пример файле ровно один класс.
И две публичные функции с захардкоженными внутри строчками.
Судя по всему, современный веб развивается настолько бурно, что «готовые решения» существуют только для legacy проектов.
Приведём в пример graphql. С чего бы гипотетический «другой язык», появившийся в 2013 году может иметь менее зрелое решение, чем PHP, если graphql появился в 2015?
Если говорить о «хорошо спроектированных языках», то Laravel появляется в 2011 году, в то время как Rails — в 2005. И Django в 2005. Symphony правда тоже появляется в 2005, но даже если считать что это технологии одного поколения — о каком отсутствии фреймворков вы говорите? А ведь в этих Питонах и Рубях всегда были ООП, и история «объектно ориентированного проектирования» у них побольше чем у PHP.
Кроме того, сегодня среды череды бесконечных одинаковых MVC фреймворков каждый пытается предложить свой изюм. Кто-то говорит «интегрируй АИ легко и нативно» как фреймворка на Python. Кто-то говорит «разрабатывай фронт и бэк одинаково» как нода решения. Кто-то гонится за бешеной скоростью работы на Го. Или бешеной скоростью разработки на рельсах. Или горизонтальным масштабированием из коробки у Erlang/Elixir… Где изюмчик у PHP — для меня загадка. И поэтому слышать «PHP всегда фигурирует при выборе стека разработки» для меня тоже загадка.
Действительно непонятно что это даст в итоге. Надеюсь машинный код тоже кешируется? А то каждый запрос компилить в машинный код наверное не хорошо.
Не хотим хвастаться, но PHP доминирует в вебе. Если вы занимаетесь веб-разработкой и не рассматриваете использование PHP в своём следующем проекте, то вы что-то делаете неправильно.
Простите, при всей моей ностальгической любви к PHP, вы тег «ирония» потеряли. Осторожнее, люди же читают и верят.
1) w3techs.com/technologies/details/pl-php/all/all
2) w3techs.com/technologies/history_overview/programming_language
Убираем из этой статистики друпалы, вордпрессы, битриксы и так далее — получим совершенно другую картину.
Ну или можно пойти от обратного — как много вы знаете крупных порталов, продуктов и систем, которые написаны на PHP?
Если кто-то правда сидит и думает, на чём ему написать очередной CRUD — да, PHP отлично под это подходит. Но разбрасываться фразами про «доминирование» и «делаете неправильно» — это очень странно. Причём вне зависимости от контекста.
Если мы хотим узнать, что именно «на рынке» — можно, посмотреть последний опрос на stackoverflow по используемым языкам, например. Или зайти на hh и сравнить количество вакансий по разным языкам. И увидеть, что там и в помине нет никаких 80% PHP.
Убираем из этой статистики друпалы, вордпрессы, битриксы и так далее — получим совершенно другую картину.
А заодно 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? Так-с, дайте подумать, даже не знаю, вроде Вы действительно правы, таких сайтов не существует в природе!
Например, если мы сравниваем Spring /.Net / Symfony — они как браться близнцы уже=) Особенно если учитывать магию конфигов и аннотаций.
Почему это плохо! Чем сильнее php будет становиться интерпрайз языком, тем выше станут требования к уровню разработчика. А если граница между ПХП и Java станет очень близкой, смысл учить ПХП про пропадет, ведь за тот же уровень усилий за Java платят лучше.
Например, если сейчас попытаться найти разработчика на Symfony, который могет в ООП, Паттерны, Стратегию развития интерпрайз проектов и прочее — вы поймете, что на рынке единицы таких разработчиков, а их уровень оплаты 120 + (что уже не сильно отличается от Java).
т.е. смотрите, почему умер Ruby c ROR (не умер, а потерял популярность) — он оказался сложнее, чем ПХП для тех же задач, и он не мог быть универсльным, как Python.
Если ПХП потеряет свою простоту для веба, у него ничего не будет против Java/.Net
Надо сделать jit, и написать на пхп интерпретатор питона. Во будет срача..
Мне честно кажется, что python уже опоздал. Пока народ холиварил о "красоте" python, PHP выкатило много крутых обновлений и системы типов, и объектов и производительности. Тот же python ничего сравнимого за эти годы, скажем с PHP 5.5, не сделал.
Ну в питоне тоже в 3ке подвезли типизацию: https://docs.python.org/3/library/typing.html Почему сразу ничего? =)
А вот чего хочется видеть, так это некого аналога go-рутин и каналов(нативно, без смс, костылей и регистрации)
PHP GR8: повысит ли JIT производительность PHP 8