Русский
Русский
English
Статистика
Реклама

Php

Из песочницы Как высчитать ключи перехода от лобой системы координат к WGS с сантиметровой точностью?

03.10.2020 18:20:42 | Автор: admin
Для кого этот пост картографы, геодезисты, генпланисты, строители и т.д.

Коллеги, привет!

Решаемая проблема получение 100% достоверных параметров для пересчета координат, например в привычные картографические градусы (WGS84). Коллеги уже поняли про что я, а любопытным поясню дело в том, что гуляющие по интернету приложения и алгоритмы с параметрами пересчета координат например из выписки ЕГРН на вашу дачу в координаты для GPS приемника, в подавляющем большинстве будут лаптем по карте. Для поиска объекта размером с дом, это не будет проблемой, а вот для инженерной затеи, уже слабовата точность. К примеру мы хотим обозначить границы на местности с сантиметровой точностью, найти трубу под землей или кабель, запустить безпилотник по картам с плоскими координатами, чертить чертеж в плоских координатах с картографической онлайн основой из интернета и многое другое, что требует субметровой точности.

Почему точные координаты становятся не точными


Плоские метровые координаты, знакомые нам из сведений о нашей недвижимости или с проектов и чертежей очень точны локально, но для привязки их к земному шару одной математики мало. Дело в том, что математическая модель плоской, метровой системы координат из документов, сначала была реализована на местности в виде геодезических пунктов, с точностью тех технологий, какие были на тот момент (в РФ большая часть систем координат развита в советское время и действуют по сей день). И уже потом от этих геодезических пунктах первого класса, создавались другие, от тех еще другие, от них всех производные секретные системы координат такие как СК63 с разворотами и искажениями координатной сетки, дабы врага запутать. При каждом таком преобразовании допускались искажения, незначительные, но нарастают они не линейно относительно количества преобразований, а намного прогрессивнее. В итоге большая часть координатных сеток сейчас похожа на чуть помятую и стянутую с одного краю простыню. Именно по этому 99% геокалькуляторов не спасут Вас от помятой простыни координатной сетки. Есть несколько геодезических сервисов для пересчета координат, платных, могу предположить, что там люди считают не по теоретическим параметрам системы координат, а обладают всеми параметрами помятой простыни. В большей части РФ надо рассчитывать параметры системы координат для небольших территорий, радиус этих территорий часто не превышает 15км. При таких небольших территориях искажения координатной сетки часто не превышает сантиметра, система координат очень точно лежит на земном шаре. Если Ваш интерес вылезает за 20-30км пространства, то необходимо несколько локальных параметров перехода рассчитывать на меньшие территории, дробить систему координат на более мелкие подзоны.

Изобретаем велосипед?


Для целей расчета истинных параметров систем координат оказывается вагон ПО. Правда это как правило составляющие сложных и дорогостоящих профессиональных программных комплексов для геодезии и картографии, которые далеко не всем профессионалам даже нужны, еще часть ПО работает на винде с непонятными и сложно употребляемыми форматами фалов. Есть еще варианты из командной строки. Вдохновившись статьей на профильном форуме решили написать своё ПО с блекджеком и вебом.

image

Расскажу кратко как это работает


Для расчета параметров нам нужны три точки (можно больше, будет точнее) в двух системах координат, в WGS и в той самой местной системе координат, параметры которой Вы хотите знать. Плоские метровые координаты берем из каталогов, кадастровых планов территорий, карточек привязок и т.д. Градусные координаты или из тех же источников, либо часто используем координаты GNSS базовых станций, либо берем спутниковый приемник, и просто замеряем эти известные в плоских координатах точки.

Веб форма высчитывает параметры системы координат и выводит на экран в двух популярных и применимых в 99% ГИС системах форматах proj строка и WKT.

Тут немного рассказов про те самые параметры и немного терминологии


Много непонятных букв
Геоцентрическая система координат, это система где есть три пространственные координатные оси проходящие через центр земли. Координаты в такой системе имеют вид x,y,z или привычные нам широта и долгота измеряемые градусами угла от нулевой точки через землю lat long h. При этом высота h отсчитывается не от центра земли как в первом случае, а от эллипсоида, сферы, геоида (упрощённой модели поверхности земли).

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

Местная система координат (МСК) как правило прямоугольная система координат, обслуживающая небольшую локальныю территорию. Часто распостраняется на территорию района или города, где её искажения не критичны для точности строительства, кадастра и т.д.

Параметры системы координат состоят из нескольких отдельных параметров, опишем каждый из них. Возьмём строку параметров PROJ4 (MapInfp, ArcGIS и т. д. Так же используют эти параметры, только структура записи иная): +proj=omerc +lat_0=59.8338730825 +lonc=33 +alpha=-0.0001 +gamma=-1.771957267229058 +k=0.9996584453038837 +x_0=2365031.423134961 +y_0=426397.2888527482 +ellps=krass

Модель земного шара (+ellps=krass) в нашем случае это эллипсоид Красовского. Под этим названием эллипсоида скрывается параметры примерного описания земного шара: направление координатных осей и углы между осями, диаметр, сжатие на полюсах и т. д. Выбрать необходимый эллипсоид можно опытным путём либо зная основываясь на какой системы координат родилась интересующая вас МСК. На территории РФ, большая часть МСК выходцы из СК42 с эллипсоидом Красовского.

Проекция земного шара на плоскость (+proj=omerc) метод с помощью которого прямоугольные координаты проецируются за круглую землю. Самый распространённый алгоритм это апельсиновые дольки, если порезать апельсин по долькам, отделить от долек шкурки.

Расправленную шкурку положить на лист в клетку и получится проекция Меркатора. Бывают разные проекции с разными направлениями и размерами долек, бывают цилиндрические проекции, это когда апельсин превращают в цилиндр и раскатывают кожуру на плоскость, конические и т. д. Выбрать необходимую можно опытным путём либо зная основываясь на какой системы координат родилась интересующая вас МСК. На территории РФ, большая часть МСК выходцы из СК42 с проекцией Меркатора. Для точных локальных параметров МСК на малых территориях рекомендуем применять косую проекцию Меркатора (omerc).

Центр проекции в градусах (+lat_0=59.8338730825 +lonc=33) это то место, где расправленная шкурка апельсина меньше меньше всего растягивается для достижения плоскости (обычно серединка шкурки дольки), место с наименьшими искажениями. Грубо говоря место где плоский лист МСК прикасается к шарику нашей планеты. Часто для центральной точки выбирают точку центра района геодезических работ.

Развороты (+alpha=-0.0001 +gamma=-1.771957267229058) разворот осей координат МСК относительно меридиана.

Масштабный коэффициент (+k=0.9996584453038837), в идеале должен быть единицей. Показывает, на сколько реальное расстояние отличается от координатного. С помощью масштаба можно сразу прикинуть, как увеличивается искажение размеров при отдалении от центральной точки МСК.

Координаты центра проекции в метрах (+x_0=2365031.423134961 +y_0=426397.2888527482), можно рассматривать как значение смещения начала отсчёта координат в плоской МСК.

За основу взяты опенсорсные пакеты

  • proj4 для геодезических трансформаций
  • Leaflet для отображения информации на карте
  • geophp для расчета территории действия параметров с сантиметровой точностью (на момент написании статьи не реализовано)

Исходный код веб формы доступен с лицензией AGPL в открытом репозитории.

Обсуждение веб формы тутачки.
Подробнее..

Из песочницы Как я за вечер написал быструю CMS для статических сайтов по правилам бизнес-логики в одном файлике

15.10.2020 22:20:39 | Автор: admin

Не Wordpress-ом единым


Не Wordpress-ом единым

Рынок CMS длительное время оставался местом, где Wordpress, Joomla, Drupal тройка абсолютных лидеров. Эти прекрасные времена уже постепенно проходят, хотя WP, сбавляя в динамике появления новых сайтов, все ещё сохраняет лидерство. Не мудрено: активное сообщество, огромное количество плагинов. Но, эта статья вовсе не будет посвящена восходящим звёздам рынка систем управления контентом (привет, решениям на базе Laravel). Скорее даже, объектом нашего внимания будет черная материя, которая находится несколько в стороне. А именно барабанная дробь

Статические сайты


CMS для статических сайтов

Кому нужна статика в 2k20?


Рациональный вопрос! Казалось бы, времена телефонного интернета и Windows95 уже прошли, но спрос на статическую генерацию html кода вновь начинает набирать обороты. И всему виной корпорация добра, разумеется. Google PageSpeed тот самый великий и могучий Урфи В общем, именно этот измеритель производительности веб-страниц стал и двигателем прогресса мирового интернета, и головной болью всех веб-разработчиков, а уж тем более фрилансеров. Результаты измерений сего инструмента базируются на стандартах Google, а далече известно, что онные положено в основу ранжирования. Да и объективно, загрузка страницы более трёх секунд увеличивает количество отказов автоматически. Таким образом, статика становится одним из альтернативных решений на смену или в дополнение к динамической сборке страниц силой любой CMS, фреймворка либо самописных вариантов.

Хьюстон, у нас проблемы или мой случай


По долгу службы я занимаюсь обслуживанием множества проектов, среди которых есть парсеры, но и также решения в сфере e-commerce. На жизнь не жалуюсь, но возникла задачка не из разряда 2 класс, начальная школа. Я, как разработчик, и менеджер (а управление своим маленьким делом, как известно, требует навыков и из этой сферы), как это на польском dostaem si do martwego kta (попал в глухой угол, одним словом). Условие следующее: нужно написать в течение нескольких дней решение, которое должно просто устанавливаться и обслуживать любое количество статических страниц. Более того, администратор должен иметь возможность быстро удалять и добавлять такое решение на любой проект через FTP/SFTP соединение или даже если у него нет доступа к FTP/SFTP. С другой же стороны условием было то, что минимальная версия это PHP 5.6 и она должна поддерживаться также прекрасно, как и каждый более современный вариант.

Администратор должен


  1. удалять/добавлять/изменять страницы при помощи админки;
  2. глобально и быстро искать в контенте страниц (учитывая разные кодировки, языки;
  3. искать по названиям файлов;
  4. удалять/вставлять/изменять на всех страницах нужные элементы одним кликом из админки;
  5. решение должно быть простым как в установке, так и в удалении.

Cекьюрность не должна позволять использовать SQL инъекции либо какие-либо другие попытки вмешательства в работу.

Дополнительным обязательством была бы возможность использовать админ панель и все ее функции через API. Грубо говоря, при наличии таких админок на 50+ доменах, должен быть доступ к ним удалённо путем возможности делать запросы.

Чехия, вечер, лето, кофе


Крайне спокойные вечера в Крконошах только способствуют лени. Но, как говорит старая пословица, кто не работает

Таким образом, собравшись с мыслями и воспряв духом да и решительностью, я принялся проектировать. Вещь предельно ясная, что создание процедурного решения не может быть чем-то максимально успешным в данном случае. Не секрет, что есть перспектива поддерживать систему и в будущем, при этом менять все не слишком хочется.

И да, чёрт возьми, я решил перенести в один файл уже устоявшийся подход в большинстве фреймворков ООП и MVC концепцию.

С точки зрения быстродействия процедурщина, быть может, была бы наиболее лучшим вариантом, но:

  • не забудем об аспекте роста потребностей клиента и параллельной надобности добавлять новые возможности к интерфейсам нашего веб-приложения;
  • проблемы с ментальным состоянием программиста хоть я и был согласен мириться с тем, что всё будущее этого проекта будет возложено исключительно на мои могучие плечи, но вряд ли я бы морально перенёс бесконечное усложнение процедурного решения и отсутствие внятной бизнес логики;
  • потребность возможной публикации проекта для всенародных масс такая надобность к моменту публикации сего опуса становится актуальной, поэтому лучше когда руки развязаны и будущую документацию можно оснастить вполне понятными даже не функциями, а структурами объектно-ориентированного мышления и разделения зон ответственности приложения на ярко выраженный фронт-энд, бэк-энд.

От баз было принято решение пока что отказаться по причине отсутствия какой-либо надобности, поэтому термин модель во всём, что выше это нереализованная концепция в моей архитектуре. Нереализованность здесь услужила хорошую службу, ведь SQL инъекция перестаёт быть возможна в принципе.

Структурирование


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

Каково же решение?


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

/** This is the part for routing*Additional information...*/

Нейминг согласно функциональному предназначению методы классов получили имена согласно camelCase практике и соответствуют функциональной роли, к примеру checkAdminCredentials это более чем понятная формулировка для метода.

Попытка описать всё что можно не скажу, что соответствовал этому намерению до самого конца разработки, в конце концов, я лавировал между быстротой и читабельностью кода, но ключевые решения мне удалось сопроводить комментариями вида //to get static html.

Как сделан роутинг?


Надобность более-менее понятного разделения интерфейсов была мною сразу же принята в качестве ключевой потребности. Этот компонент, если не должным образом реализовать, может стать точкой пика роста приложения из-за запутанности в том, какой роут какой контроллер вызывает.

Исходя из упомянутой выше проблемы, я сделал класс для роутинга достаточно примитивным, но функциональным. Роутинг работает при помощи GET запросов конечно, не совсем эстетично, но сиюминутную потребность в быстрой реализации решает на 100%. Роутер стал единственной точкой входа для веб-приложения, что, как по мне, идеальное решение для простоты разработки. В этом классе происходит сборка фронт-энда и бэк-энда апликации и вывод return-ом сформированного HTML.

Вопрос: написал ли я велосипед?


Я склонен к подозрению, что подобные решения уже написаны в прошлом, но на момент июля 2020 ни английский, ни польский, ни чешский, ни русский с украинским не помогли мне найти что-то стоящее и применительное к моему случаю. Поэтому во мне включился создатель, и я от всей души проектировал решение актуальных задач бизнеса. Ведь, если есть проблема её нужно решить максимально быстро и экономно, да ещё и про качество не позабыть.

Наглядный пример производительности


Одна крайне известная CMS, имя которой не принято называть:



Моё решение:



Очевидно, что скорость получения первого байта также выше, как и все прочие показатели, связанные со скоростью загрузки, даже не буду демонстрировать количество запросов на front-end в классических CMS и в этом мини-решении.

Будет ли продолжение?


Сейчас система в большей степени написана и успела увидеть несколько обновлений. В ближайшее время я планирую подготовить документацию и в том либо ином виде, но выпустить это решение в открытое плаванье, хотя, нужно сказать, что задачи внутренние решает каждый день великолепно. Есть ещё несколько интересных и занимательных моментов из раздела файлового поиска и поиска контента в html файлах, которые удалось реализовать в обход возможностей PHP, что сделало выполнение этих запросов существенно быстрее. Об этих темах обещаюсь написать в ближайшее время.
Подробнее..

29 ноября в Москве конференция по PHP в России будет офлайн

16.10.2020 14:10:19 | Автор: admin
Пандемия повлияла на все бизнес-процессы, мы долго были в онлайне. Но 29 ноября PHP-разработчики смогут наконец встретиться офлайн в тёплой атмосфере, увидеть лучших спикеров PHP-вселенной, и задав им вопросы, разобрать актуальные кейсы и обсудить проблемы. PHP Russia 2020 пройдёт в Москве в гостинице Radisson Slavyanskaya. Приходите, если хотите получить ускорение и направление в развитии плюс набраться новых идей для своих проектов!

Александр Макаров расскажет о предстоящих активностях на конференции, о некоторых интерактивах и других нюансах. Александр эксперт в PHP, лидер фреймворка Yii, соавтор Yii 2 и представитель Yii в PHP-FIG. Кроме разработки фреймворка успел поработать в разных компаниях, таких как Skyeng, Wrike и Stay.com и перепробовать в бою целые поколения разных технологий.

Мы расспросили Александра как главу программного комитета по PHP Russia 2020 обо всех активностях и интересностях встречи.



Саша, что нас ждет на первой в этом году оффлайновой конференции?
Будет много интересных докладов :)

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

Мы отобрали доклады, в которых раскроем разные способы писать код: микросервисы, параллельная обработка, долгоживущие процессы. Покажем, как ещё вы можете использовать PHP, а с чем не стоит связываться, и разберем, как лучше оформить решения распространенных практических проблем. Поговорим, как организовать системную работу с legacy и справиться с техдолгом. Покажем, как legacy-проекты продолжают развиваться без радикального переписывания кода.

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

Будет, естественно, информация про PHP 8, и затронем интересную тему: она не совсем по PHP, а про написание плагинов для нашей любимой IDE PhpStorm.

Как и в прошлые разы, неплохо представлены такие лидеры PHP-разработки, как BADOO, Skyeng, ManyChat, Onliner, Lamoda, Авито и SuperJob. Они работают не только на PHP, но PHP это самое главное в их стеке. Необязательно все выступят с докладами, но представители этих компаний будут все.

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

Что необычного будет в этот раз?
Будет необычный формат с Дмитрием Стоговым из команды самого PHP. Он сделал JIT PHP.
Дмитрий приезжает не с докладом, а пообщаться в свободной форме с сообществом. Будет шанс задать ему любые вопросы не только про PHP 8 (и вообще про PHP), но и про остальную разработку, и даже чем он занимается в его свободное время. У Дмитрия неисчерпаемое множество тем для разговора, с ним очень интересно, а мы будем модерировать эту сессию вопросов и ответов.

Ещё в прошлый 2019-й офлайн мы попробовали такой замечательный формат как Unconference куда все желающие могли прийти и в блиц-формате рассказать про то, что они делают. Это тогда классно зашло, потому что были такие совершенно неожиданные рассказы, из которых потом получились и opensourse-бибилиотеки, и всякие крутые штуки. На онлайн-митапах, на которых мы сейчас иногда пересекаемся, до сих пор все с теплотой вспоминают эту встречу. Мы решили, что раз так, то этот формат забрасывать не надо, он классный. И мы повторим его в этом году.

Получается, в этом году будет сделан упор на общение, по которому все так соскучились?
Да. Мы решили доклады сделать немного разряженнее, а перерывы побольше. Это к тому же будет безопаснее в плане вирусов. Разумеется, по всем сегодняшним стандартам площадку мы обезопасим по максимуму, и это будет намного безопаснее, чем ездить в метро за счет дезинфекций, за счет проветривания, измерения температуры на входе, санитайзеров. Докладчик в маске будет вряд ли он стоит больше чем за полтора метра от первого ряда, но для остальных маски будут обязательными.

Что будет с конференцией и билетами, если введут карантин и мы все вынужденно останемся дома?
Сейчас мы исходим из того, что короновирус с нами надолго, мы делаем максимальные предосторожности и все-таки встречаемся, потому что жить как-то надо, новую информацию получать надо, и мы готовы её давать, даже несмотря на такие обстоятельства.
Но если вторая волна накроет нас совсем по полной, и запретят все-все-все мероприятия, билеты покупать на конференцию всё равно стоит.

Почему?
Потому что даже если произойдет закрытие всех конференций, у нас есть план Б. Билет никуда не девается, не сгорает, его можно использовать на следующий раз или на перенесенной конференции. То есть просто сдвинется дата, у нас есть об этом договоренности с площадкой и всеми подрядчиками.

То есть, если ничего не будет, то PHP-конференции не уйдет в онлайн?
Да. В прошлый раз она была онлайн, это было сильно, классно, но это показало, что онлайн это другая среда, совершенно другая штука. Классно то, что можно не привозя спикеров из очень дальних уголков, их всё-таки послушать. И просто и замечательно было в том, что в плане накладок ни мы, ни они ничего не теряли.

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

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

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

Много в этом году желающих выступить? Спикеры соскучились по офлайн-формату?
Да, несмотря на все преграды, для офлайн-встречи была достаточно большая конкуренция. Даже пришлось отклонить действительно очень хорошие доклады, потому что они пересекались с теми, которые подходили по тематике больше. Вообще мы можем зарубить даже интересный доклад, если спикер не говорит ничего о применимости темы на практике это обязательный элемент.

Открой, пожалуйста, процесс, который начинается после выбора доклада.
Открываю. Каждый доклад после подачи закрепляется за кем-то из ПК. После этого мы смотрим черновой вариант доклада, даже без слайдов на первом этапе. И мы обязательно валидируем начальный вариант доклада: какие мысли хочется рассказать и т.д.

То есть мы обсуждаем всем программным комитетом и вместе принимаем решение брать это направление или нет, зайдет или нет, на чем лучше акцентировать внимание, а на чем не стоит, и т.д. По большому счету, это зависит от того, знакома тема всем или эта тема еще неизвестна многим. Так мы задаем спикеру путь, куда копать.

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

За счет прогонов и крутой экспертизы у ПК (они все очень опытные докладчики, которые выступали в том числе и в Европе), получаются очень качественные доклады на выходе, и мы стараемся не опускать планку.

Есть ли шанс, что не очень известный и не очень опытный разработчик найдёт тему, которая зайдет так же, как от более опытных коллег?
Да. Приведу пример.

В этом году мы спросили региональные PHP-сообщества, хотят ли они выступать и есть ли что у них для митапов (чтобы с ними созвонились с конференции и послушали, что у них там делается в компаниях, проектах, и так далее). Они начали присылать, и мы смотрели две вещи: годная ли информация для докладов в принципе и достаточно ли она крутая для конференции.

И результаты есть. Мы созванивались с ними 2-3 недели, всех послушали, и действительно есть классные и сильные ребята. Многие темы в этом году не подойдут, потому что они уже закрыты или авторами самих инструментов или кем-то супер-опытным. Но в общем очень достойно всё кое-кого мы включим в основную программу.

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

Пользуясь случаем, передаю привет и спасибо PHP-сообществам, которые нас поддерживают.

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

Must learn PHP Tools:


PHP Engineer Things to Learn:


На конференции PHP Russia 2020 Александр Макаров выступит с докладом Поговорим про код в рамках лучших практик PHP. Вы узнаете принципы, позволяющие писать код, который ломается меньше. Например, о композиции и как её форсировать. О private по умолчанию и именованных конструкторах. О состоянии и иммутабельности, а также о цепочке вызовов и многом другом.

29 ноября мы встретимся в ламповом инфопространстве, чтобы наконец увидеть друг друга вживую. Здесь можно забронировать билет на PHP Russia 2020. Подключайтесь к telegram-сообществу, чтобы обсудить архитектурные вызовы и любые другие вопросы по PHP.

Присоединяйтесь к нам на конференции, не только доклады будут полезны, но и профессиональное общение (настоящее, офлайновое!) даст энергию, чтобы воплощать услышанное. Мы с нетерпением ждём встречи в офлайне!
Подробнее..

Перевод Эволюция PHP от 5.6 до 8.0 (Часть 1)

20.10.2020 14:07:37 | Автор: admin

Перевод статьи подготовлен в преддверии старта курсаBackend-разработчик на PHP.

Шпаргалка по изменениям в PHP v7.x

PHP_v8.0PHP_v8.0

После релиза PHP версии 7.3 я решил уделить больше внимания развитию PHP: что собственно развивается и в каком направлении искать понимание потенциала и оптимизации этого невероятно популярного языка программирования.

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

За основу мы возьмем PHP 5.6 и рассмотрим, что было добавлено и изменено. Я также добавил везде где смог ссылки на официальную документацию по каждой из упомянутых фич, поэтому, если вам вдруг понадобится получить более подробную информацию - пожалуйста.

PHP 7.0

Поддержка анонимных классов

Анонимный класс может использоваться вместо именованного класса:

  • Когда класс не нужно документировать

  • Когда класс во время выполнения используется только один раз

new class($i) {   public function __construct($i) {       $this->i = $i;   }}

Функция целочисленного деления - безопасный способ деления (даже на 0).

Возвращает целочисленный результат деления первого операнда на второй. Если делитель (второй операнд) равен нулю, функция пробрасывает EWARNING и возвращает FALSE.

intdiv(int $numerator, int $divisor)

Добавлен новый оператор объединения с null - ??

$x = NULL;$y = NULL;$z = 3;vardump($x ?? $y ?? $z); // int(3)$x = ["c" => "meaningfulvalue"];vardump($x["a"] ?? $x["b"] ?? $x["c"]); // string(16) "meaningfulvalue"

Добавлен новый оператор - spaceship (космический корабль)(<=>)

Используется для оптимизации и упрощения операций сравнения.

// Преждеf unction orderfunc($a, $b) {return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);}// Используя оператор <=>function orderfunc($a, $b) {return $a <=> $b;}

Объявления скалярных типов

Это всего лишь первый шаг к реализации преимуществ более строго типизированного языка программирования в PHP - v0.5.

function add(float $a, float $b): float {return $a + $b;}add(1, 2); // float(3)

Объявления типов возвращаемых значений

Добавлена возможность возвращать типы помимо скалярных - классы, включая наследование. Хех, упустив при этом возможность сделать это необязательным (что будет введено в v7.1 :))

interface A {static function make(): A;}class B implements A {static function make(): A {return new B();}}

Групповые объявления use

// Явный use синтаксис:use FooLibrary\Bar\Baz\ClassA;use FooLibrary\Bar\Baz\ClassB;use FooLibrary\Bar\Baz\ClassC;use FooLibrary\Bar\Baz\ClassD as Fizbo;// Групповой use синтаксис:use FooLibrary\Bar\Baz{ ClassA, ClassB, ClassC, ClassD as Fizbo };

Делегация генератора

В теле функций генераторов разрешен следующий новый синтаксис:

yield from <expr>

Повышение производительности

PHP 7 почти вдвое быстрее, чем PHP 5.6.

Значительное сокращение использования памяти

Как видно из диаграмм, PHP 7.0 стал громадным шагом вперед с точки зрения производительности и использования памяти. Для страницы с запросами к базе данных версия 7.0.0 более чем в 3 раза быстрее, чем 5.6 с включенным opcache в 2.7 раза быстрее без opcache! С точки зрения использования памяти разница тоже существенная!

Интерфейс Throwable

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

Error и Exception теперь реализуют Throwable.

Иерархия Throwable:

interface Throwable|- Error implements Throwable|- ArithmeticError extends Error|- DivisionByZeroError extends ArithmeticError|- AssertionError extends Error|- ParseError extends Error|- TypeError extends Error|- ArgumentCountError extends TypeError|- Exception implements Throwable|- ClosedGeneratorException extends Exception|- DOMException extends Exception|- ErrorException extends Exception|- IntlException extends Exception|- LogicException extends Exception|- BadFunctionCallException extends LogicException|- BadMethodCallException extends BadFunctionCallException|- DomainException extends LogicException|- InvalidArgumentException extends LogicException|- LengthException extends LogicException|- OutOfRangeException extends LogicException|- PharException extends Exception|- ReflectionException extends Exception|- RuntimeException extends Exception|- OutOfBoundsException extends RuntimeException|- OverflowException extends RuntimeException|- PDOException extends RuntimeException|- RangeException extends RuntimeException|- UnderflowException extends RuntimeException|- UnexpectedValueException extends RuntimeException

Внимание! Вы можете реализовать Throwable только через Error и Exception.

Синтаксис кодирования Unicode \u{xxxxx}

echo "\u{202E}Reversed text"; // выводит Reversed textecho "maana"; // "ma\u{00F1}ana"echo "maana"; // "man\u{0303}ana" "n" комбинирована с символом ~ (U+0303)

Чувствительный к контексту лексер

С этим нововведением глобально зарезервированные словами стала полу-зарезервированными:

callable class trait extends implements static abstract final public protected private constenddeclare endfor endforeach endif endwhile and global goto instanceof insteadof interfacenamespace new or xor try use var exit list clone include includeonce throw arrayprint echo require requireonce return else elseif default break continue switch yieldfunction if endswitch finally for foreach declare case do while as catch die self parent

За исключением того, что по-прежнему запрещено определять константу класса с именем class из-за разрешения имени класса ::class.

Выражения return в генераторах

Единый синтаксис переменных

Поддержка уровня вложенности для функции dirname()

PHP 7.1

Обнуляемые типы

function answer(): ?int {return null; // ок}function answer(): ?int {return 42; // ок}function answer(): ?int {return new stdclass(); // ошибка}function say(?string $msg) {if ($msg) {echo $msg;}}say('hello'); // ок - выводит hellosay(null); // ок - ничего не выводитsay(); // ошибка - отсутствует параметрsay(new stdclass); // ошибка - не подходящий тип

Ничего не возвращающие функции

function shouldreturnnothing(): void {return 1; // Fatal error: A void function must not return a value}

В отличие от других типов, которые принудительно применяются при вызове функции, этот тип проверяется во время компиляции, что означает, что ошибка возникает без необходимости вызова функции.

Функция с типом возврата void или void функция может либо возвращать неявно, либо иметь оператор возврата без значения:

function lacksreturn(): void {// валидно}

Псевдотип Iterable

Обычно функция принимает или возвращает array или объект, реализующий Traversable, который используется с foreach. Однако, поскольку array является примитивным типом, а Traversable - интерфейсом, в настоящее время нет способа использовать объявление типа для параметра или возвращаемого типа, чтобы указать, что значение является итерируемым.

function foo(iterable $iterable) {foreach ($iterable as $value) {// }}

iterable также может использоваться в качестве возвращаемого типа, чтобы указать, что функция вернет итерируемое значение. Если возвращаемое значение не является массивом или экземпляром Traversable, будет проброшена TypeError.

function bar(): iterable {return [1, 2, 3];}

Параметры, объявленные как iterable могут использовать null или массив в качестве значения по умолчанию.

function foo(iterable $iterable = []) {// }

Closure из callable

class Closure {public static function fromCallable(callable $callable) : Closure {}}

Синтаксис квадратных скобок для деструктуририрующего присваивания в массиве

$array = [1, 2, 3];// Присваивает $a, $b и $c значения соответствующих элементов массива $array с ключами, пронумерованными от нуля[$a, $b, $c] = $array;// Присваивает $a, $b и $c значения элементов массива $array с ключами "a", "b" и "c" соответственно["a" => $a, "b" => $b, "c" => $c] = $array;

Синтаксис квадратных скобок для list()

$powersOfTwo = [1 => 2, 2 => 4, 3 => 8];list(1 => $oneBit, 2 => $twoBit, 3 => $threeBit) = $powersOfTwo;

Видимость констант класса

class Token {    // Константы по умолчанию public   const PUBLICCONST = 0;// Константы также могут иметь определенную пользователем видимостьprivate const PRIVATECONST = 0;protected const PROTECTEDCONST = 0;public const PUBLICCONSTTWO = 0;// Константы могут иметь только один список объявлений видимостиprivate const FOO = 1, BAR = 2;}

Перехват нескольких типов исключений

try {// Какой-то код} catch (ExceptionType1 | ExceptionType2 $e) {// Код обработки исключения} catch (\Exception $e) {// }

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

Читать ещё:

Подробнее..

2R2L кеширование

23.10.2020 18:08:20 | Автор: admin
Кеширование широко освещенная и известная тема. Но и в ней могут появляться новые решения. В частности в области высокоуровневых продуктов (например, в веб-разработке). Столкнувшись с недостатками классического подхода, я попробовал вывести идеальную схему кеширования для случая, когда актуальность данных не является критической. Потом я попробовал найти описание подобной схемы, а лучше готовые решения. Не нашел. Поэтому назвал ее сам 2R2L (2 Range 2 Location) двух-диапазонное двух-пространственное кеширование. Хотя наверняка оно уже где-то применяется.

Началось все с простой задачи отобразить пользователю новинки неких товаров с учетом его индивидуальных предпочтений. И если с получением новинок проблем не было, то соотнесение новинок с предпочтениями (анализ статистики) уже создавал ощутимую нагрузку (для примера определим ее в 4 секунды). Особенность задачи состояла в том, что в качестве пользователей у нас могут выступать целые организации. И нередки случаи, когда одномоментно (в течение 2-3 секунд) на сервер прилетает 200-300 запросов, относящихся к одному пользователю. Т.е. генерируется один и тот же блок сразу для многих пользователей.

Очевидное решение надо кешировать в RAM (не будем подвергать СУБД насилию, заставляя отрабатывать большой поток обращений). Классическая схема:

  1. Пришел запрос
  2. Проверяем кеш. Если данные в нем есть, и они не устарели просто отдаем их.
  3. Данных нет => генерируем выдачу
  4. Отправляем пользователю
  5. Дополнительно складываем в кеш, указывая TTL

Недостаток такого решения: если данных в кеше нет, генерировать их будут все запросы, пришедшие за время первой генерации, затрачивая на это ресурсы сервера (пики нагрузки). Ну и конечно, все пользователи при первом обращении будут ждать.

Также отметим, что при индивидуальных кеш-значениях количество записей может вырасти на столько, что доступной ОЗУ сервера просто не хватит. Тогда логичным выглядит использование локального HDD сервера в качестве хранилища кешей. Но мы сразу теряем в скорости.

Как же быть?

Первое, что приходит в голову: было бы здорово хранить записи в 2 местах в RAM (часто запрашиваемые) и HDD (все или только редко запрашиваемые). Концепция горячих и холодных данных в чистом виде. Реализаций такого подхода множество, поэтому останавливаться на нем не будем. Просто обозначим эту составляющую как 2L. В моем случае она успешно реализуется на базе СУБД Scylla.

Но как избавиться от просадок в моменты, когда кеш устарел? А здесь мы и подключаем концепцию 2R, смысл которой заключается в простой вещи: для кеш-записи надо указывать не 1 значение TTL, а 2. TTL1 метка времени, которая означает данные устарели, надо бы перегенерировать, но использовать еще можно; TTL2 все устарело настолько, что использовать уже нельзя.

Таким образом получаем немного иную схему работы кеширования:

  1. Пришел запрос
  2. Ищем данные в кеше. Если данные есть и не устарели (t<TTL1) отдаем пользователю, как обычно и больше ничего не делаем.
  3. Данные есть, устарели, но можно использовать (TTL1 < t < TTL2) отдаем пользователю И инициализируем процедуру обновления кеш-записи
  4. Данных нет совсем (убиты по истечении TTL2) генерируем как обычно и записываем в кеш.
  5. После отдачи контента пользователю или в параллельном потоке выполняем процедуры обновления кеш-записей.

В результате мы имеем:

  • если кеш-записи используются достаточно часто, пользователь никогда не попадет в ситуацию ожидаем актуализации кеша он всегда будет получать уже готовый результат.
  • если правильно организовать очередь актуализаций, то можно добиться того, что в случае нескольких одновременных обращений к записи с TTL1 < t < TTL2, в очереди будет находиться только 1 задача на обновление, а не несколько одинаковых.

В качестве примера: для ленты новинок можно указать TTL1 = 1 час (все же не сильно интенсивно новый контент появляется), а TTL2 1 неделя.

В простейшем случае код на PHP для реализации 2R может быть таким:

$tmp = cache_get($key);If (!$tmp){$items = generate_items();cache_set($items, 60*60, 60*60*24*7);}else{$items = $tmp[items];If (time()-$tmp[tm] > 60*60){$need_rebuild[] = [to=>$key, method=>generate_items];}}// отдаем данные пользователюecho json_encode($items);// поскольку данные пользователю уже отправлены, можно и повычислятьIf (isset($need_rebuild) && count($need_rebuild)>0){foreach($need_rebuild as $k=>$v){$tmp = ['tm'=>time(), 'items'=>$$v[method]];cache_set($tmp, 60*60, 60*60*24*7);}}

На практике, конечно, реализация, скорее всего, будет посложнее. Например, генератор кеш-записей отдельный скрипт, запущенный в качестве сервиса; очередь через Rabbit, признак такой ключ уже есть в очереди на перегенерацию через Redis или Scylla.

Итого, если объединить двух-диапазонный подход и концепцию горячие/холодные данные, как раз и получим 2R2L.

Спасибо!
Подробнее..

LaravelДайджест (28 сентября 4 октября 2020)

04.10.2020 20:13:41 | Автор: admin

Пакеты для настройки каркаса аутентификации под себя. Подключаем Google Drive. Сравниваем интернет-магазины. Кастомные логи и исключения.


Laravel Дайджест


Релизы


  • Laravel 8.8
    За неделю вышли две версии фреймворка. Описание изменений сделано лишь для 8.7.
  • Jetstrap
    Пакет переключающий Jetstream c TailwindCSS на Bootstrap.
  • Laravel Model Expires
    Пакет следящий за сроком годности ваших моделей, например, срок подписки пользователя.
  • Laravel Mula
    Пакет для упрощения работы с деньгами
  • Fortify UI
    Пакет каркаса аутентификации для Laravel Fortify. Минимальная разметка и без стилей.
  • Laravel Make-Testable
    Пакет для создания тестов вместе с классами при использовании artisan-команды make:*

Уроки



Видео



Телеграм на русском


Подробнее..

PHP-Дайджест 189 (21 сентября 5 октября 2020)

05.10.2020 12:23:18 | Автор: admin

Свежая подборка со ссылками на новости и материалы. В выпуске: PHP 8.0 RC 1 и переименование параметров внутренних функций, PhpStorm 2020.3 EAP, многострочные короткие лямбды, атрибуты для групп свойств и другие новости PHP Internals, порция полезных инструментов, статьи, стримы, подкасты.

Приятного чтения!



Новости и релизы


  • PHP 8.0.0 RC 1 Стартовал цикл релиз-кандидатов ветки 8. Запланировано 4 выпуска и второй релиз-кандидат ожидается 15 октября.

    Усилия core-команды сосредоточены на пересмотре имен аргументов во всех модулях. Пример переименований в PDO. За ходом можно наблюдать здесь.
  • PhpStorm 2020.3 EAP Стартовала программа раннего доступа. Уже реализована полная поддержка PHP 8 поможет быстро сделать пакеты совместимыми с новой версией интерпретатора. Запланированы Xdebug 3, PHPStan/Psalm (в следующих билдах), интеграция Guzzle с HTTP-клиентом и другие фичи.
  • PHP 7.2.34
  • PHP 7.3.23
  • PHP 7.4.11
  • ruphpcommunity.ru PHP-митапы, чаты и ютуб-каналы.
  • Традиционный Hacktoberfest с возможностью получить футболку за 4 пул-реквеста в открытые проекты пошел в этом году не по плану.

    Какой-то ютубер опубликовал инструкцию и показал, как делать примитивные пул-реквесты с изменениями в readme. Посыпался шквал бессмысленных PR. В итоге DigitalOcean теперь учитывают пул-реквесты только в те репозитории, у которых авторы явно указан топик hacktoberfest.

    Если вы хотите поучаствовать в опенсорсе и получить футболку, то вот инструкция как сделать хороший пул-реквест и список и issues с тегом #hacktoberfest в PHP-проектах.

PHP Internals


  • check[PR] Attributes on property groups Атрибуты можно будет указывать сразу для группы свойств, а не только по одному, так же как это работает для модификаторов доступа.
    class FooBar {    #[NonNegative]    public int $x, $y, $z;}
    
  • check[PR] Attributes and strict types Также атрибуты будут принимать во внимание директиву strict_types=1.
  • [PR] OPCache: Direct execution opcode file without php source code file Концепт в виде в PR, в котором автор предлагает сделать возможным сохранять бинарный файл опкеша и запускать его уже без исходника. По сути, это что-то напоминающее подход в Java или очень похожее на питоновские файлы .pyc / .pyo.

    Теоретически, в этом случае можно было бы распространять предварительно скомпилированный PHP-код без исходников. Типа как .phar, но уже интерпретированный.

    Но в обсуждении указали на проблемы такого подхода. Формат опкода в PHP нестабилен и несовместим от версии к версии. Причем даже в рамках патч-релизов, то есть код скомпилированный на PHP 7.4.22 может просто свалиться с segfault на PHP 7.4.23. А сделать его стабильным маловероятно.
  • [PR] Multiline arrow functions Короткие лямбды, добавленные в PHP 7.4, могут содержать только одно выражение. В этом пул-реквесте представлена реализация многострочных коротких лямбд:
    $guests = array_filter($users, fn ($user) => {  $guest = $repository->findByUserId($user->id);  return $guest !== null && in_array($guest->id, $guestsIds);});
    

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

    Также остается открытым вопрос синтаксиса, а именно стоит ли добавлять стрелку =>:
    fn() => {}
    
    или
    fn() {}
    

Инструменты


  • thephpleague/event 3.0.0 Популярный пакет для событий теперь совместим с PSR-14.
  • terrylinooo/simple-cache Драйверы кеша по стандарту PSR-16 для хранения в файлах, Redis, MySQL, SQLite, APC, APCu, Memcache, Memcached и WinCache.
  • Code With Me (EAP) В тестовом режиме доступен плагин для совместной работы в IDE от JetBrains.
  • Bolt 4.0 Обновление популярной CMS на Symfony- компонентах.

Symfony



Laravel



Yii


  • W3C откажется от WordPress и будут использовать CraftCMS, который сделан на базе Yii 2. Сама новость не была бы такой интересной без отличного документа о том, какие аспекты принимались во внимание при выборе.

Async PHP


  • micc83/mailamie Простой SMTP-сервер для тестирования отправки почты. Реализован на ReactPHP.

Материалы для обучения



Аудио/Видео






Спасибо за внимание!

Если вы заметили ошибку или неточность сообщите, пожалуйста, в личку.
Вопросы и предложения пишите на почту или в твиттер.

Больше новостей и комментариев в Telegram-канале PHP Digest.

Прислать ссылку
Поиск ссылок по всем дайджестам
Предыдущий выпуск: PHP-Дайджест 188
Подробнее..

LaravelДайджест (511 октября 2020)

11.10.2020 20:14:29 | Автор: admin

Полное руководство по аутентификации в Laravel. Новый фасад для ограничения скорости запросов. Быстрое параллельное тестирование. Стрим Тейлора: видео и текстовое резюме. Видеокурс по использованию Tailwind в Laravel.


Laravel Дайджест


На русском языке



Релизы



Уроки



Видео



Телеграм на русском


Подробнее..

Я сомневался в юнит-тестах, но

13.10.2020 14:23:08 | Автор: admin
Когда я пишу тест, то часто не уверен, что мой дизайн будет на 100% удачным. И хочу, чтобы он давал гибкость в рефакторинге кода например, чтобы затем изменить класс, не меняя код теста.



Но если у меня стандартная пирамида, внизу которой много юнит-тестов, то не получится ли так, что тесты будут знать не про поведение системы, а про то, какие классы там есть?

Всем привет! Это расшифровка подкаста Между скобок моих интервью с интересными людьми из мира разработки на PHP.


Запись, если вам удобнее слушать. В полной аудиоверсии мы также обсуждаем больше вопросов code coverage.

С Владимиром vyants Янцем мы познакомились на февральском PHP-митапе в Ростове: я рассказывал про свой опыт с асинхронностью, он делал доклад про тесты. С того выступления у меня остались вопросы и в период карантина мы созвонились, чтобы обсудить их.

Сергей Жук, Skyeng: Начнем с главного. Нужны ли юнит-тесты? Не рискую ли я получить слишком большую связность теста и кода? Да и проект наверняка будет меняться, почему бы мне не пойти от вершины пирамиды тестирования, с тех же функциональных тестов?

Владимир Янц, Badoo: Это очень хороший вопрос. Давай начнем с того, нужны ли они в принципе. Может, и правда, только функциональные?

Функциональные тесты классные, но закрывают немного другие потребности. Они позволяют убедиться, что приложение или кусок фичи целиком работает. И у них есть своя проблема цена поддержки, написания и запуска.

Например, у нас на данный момент 20 000 приемочных функциональных тестов. Если запустить их в один поток, они будут бежать несколько дней. Соответственно, нам пришлось придумывать, как получить результаты за минуты: отдельные кластеры, куча машин, большая массивная инфраструктура на поддержку всех этих вещей. А еще ваш тест зависит от окружения: других серверов, того, что лежит в базе данных, кэше. И чтобы его починить, нужно тратить много ресурсов, денег и времени разработчиков.

Чем хорош юнит-тест? Ты можешь протестировать всякие безумные кейсы.


Функциональным же не проверишь, как функция будет реагировать на отрицательное значение. Тут же ты можешь проверить все кейсы, чтобы убедиться, что функция работает идеально: дата-провайдеры создают достаточно много вариантов, а прогнать вариант это доли секунды.

Поэтому, отвечая на главный вопрос. Нужны ли хорошие юнит-тесты? Да. Нужны ли плохие? Нет. Один из главных принципов тестирования: тест можно инвестировать в контракт, а не реализацию. Контракт это договоренность, что ваш метод или ваша функция принимает на входе, и что она должна с этим сделать.

Действительно, проекты на начальных этапах могут менять свою архитектуру, хрупкие тесты будут здесь бесполезны. Но я бы начинал писать юнит-тесты на раннем этапе: не покрывая части, которые хрупки и зависимы от архитектуры. Ведь бывают места, по которым сразу сразу понятно, что ты не будешь их рефакторить. Допустим, тебе нужно посчитать сложные вещи, у тебя есть хелпер, который ты много раз переиспользуешь. Вряд ли ты будешь менять логику этих расчетов. А покрыв ее юнит-тестом, будешь уверен, что там все делается правильно.

Сергей Жук, Skyeng: Ок, смотри, у меня какое-то веб-приложение, и я начинаю в нем писать юнит-тесты. Скажем, я знаю, что у этого класса может быть 5 разных input, я меняю реализацию, и просто делаю юнит-тест провайдеру, чтобы не тестить каждый раз. И еще есть какая-то опенсорсная либа тут без юнит-теста тоже никуда. А для каких еще кейсов их нужно и не стоит писать?

Владимир Янц, Badoo: Я бы написал тесты на то, где есть какая-то бизнес-логика: не в базе данных, а именно в PHP-коде. Какие-то хелперы, которые считают что-то и выводят, какие-то бизнес-правила отличные кандидаты для тестирования. Также видел, что пытались тестировать тонкие контроллеры в приложении.

Основное, что должны делать юнит-тесты, спасать чистую функцию.


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

Напротив, если у вас какая-то админка с CRUDом и с большим количеством одинаковых форм, юнит-тесты не очень помогут. Логики мало, тестировать такой код в изоляции от базы данных и окружения проблематично. Даже с точки зрения архитектуры. Здесь лучше взять какие-то тесты более высокого уровня.

Сергей Жук, Skyeng: Когда я начинал, мне казалось, что круто юнит-тестировать максимально детально. Вот у меня есть объект, у него есть какой-то метод и несколько зависимостей (например, модель, которая ходит в базу). Я мокал все. Но со временем понял, что ценность таких тестов нулевая. Они тестируют опечатки в коде и также еще больше связывают тесты с ним. Но я до сих пор общаюсь с теми, кто за такой вот тру-подход. Твоя позиция какова: нужно ли так активно мокать? И в каких кейсах оно того точно стоит?

Владимир Янц, Badoo: В целом, мокать полезно. Но одна из самых вредных конструкций, которых, мне кажется, есть в том же PHPUnit, это ожидания.

Например, плохо, когда люди пытаются сделать так, чтобы метод вызвался с определенным набором параметров и определенное количество раз.

Мы пытаемся проверить тестом не контракт. Хороший тест этим не занимается.


Это делает тесты очень хрупкими. Вы будете тратить время на фикс, а вероятность поймать баг очень мала. В один момент вам надоест. Я видел такое: ребята начинали писать юнит-тесты, делали это неправильно, и в какой-то момент отказывались от инструмента, потому что он не приносил пользы.

Возьмем функцию rant. Она принимает на входе два числа от 0 и выше, а должна вернуть случайное число между этими двумя. Это контракт. В этом случае можно потестить, что она работает на граничных значениях и так далее.

Если у вас есть какой-то хэлпер, который складывает в модели два числа, вы должны сделать фейк-объект так, чтобы проверить, что хэлпер сложит числа правильно. Но не нужно проверять, сколько раз он дернул эту модель, сходил ли он в свойства или дернул геттер это детали реализации, которые не должны быть предметом тестирования.

Сергей Жук, Skyeng: Вот ты начал про вред ожиданий. Моки про них. Нужны ли тогда вообще моки, если есть фейки и стабы?

Владимир Янц, Badoo: Проблема стабов в том, что они не всегда удобны: ты должен их заранее создать, описать. Это удобно, когда есть объект, который часто переиспользуется ты один раз его написал, а затем во всех тестах, где нужно, используешь.

А для объекта, который ты применяешь в одном-двух тестах, писать стаб очень долго.


Поэтому я активно использую моки как стабы. Можно создать мок, замокать все нужные функции и работать с ними. Я использую их только в таком ключе.

Ну а фейки мой любимый подход. Очень помогает тестировать многие вещи. Например, когда ты хочешь протестировать, что все правильно закэшировал. Для Memcached легко создать фейк-объект, он есть в большинстве стандартных поставок фреймворков. В качестве типизации вы используете интерфейс, а не конечный класс, и ваш фейк может быть безопасно передан в качестве аргумента в любую функцию. А внутри, вместо того, чтобы ходить в сам кэш, вы реализуете функционал кэша внутри объекта, делая массив. Это очень здорово облегчает тестирование, потому что вы можете проверить логику с кэшированием иногда это бывает важно.

Сергей Жук, Skyeng: Смотри, еще одна крайность. Я встречал людей, которые говорят: Ок, а как мне протестировать приватный метод/класс?

Владимир Янц, Badoo: Бывает, что не очень правильно структурирован код, и правильный метод может быть отдельной чистой функцией, которая реализовывает функционал. По-хорошему, это должно быть вынесено в отдельный класс, но по каким-то причинам лежит в protected-элементе. В крайнем случае, можно протестировать такое юнит-тестом. Но лучше сделать отдельной сущностью, которая будет иметь свой тест. Что-то вроде своего явного контракта.

Сергей Жук, Skyeng: Вот ты говоришь, есть контракт, а остальное детали реализации. Если мы говорим о веб-приложении с точки зрения интерфейса: у него есть контракт, по которому оно общается с юзерами. В то же время мы формируем реквест, отправляем, инспектируем респонс, сайд-эффекты, БД, еще что-то. Если делать юнит-тесты, то можно вынести логику общения с БД в отдельный слой, завести интерфейс для репозитория, сделать отдельную in memory реализацию репозитория для тестов. Но стоит ли оно того?

Владимир Янц, Badoo: У тебя есть приложение, которое должно правильно работать целиком. И юнит-тесты должны прикрыть какие-то важные вещи, у которых есть большая вариативность ответов, и которые легко выделены в чистую функцию. Вот там они должны быть в первую очередь.

Юнит-тесты могут дать самую быструю обратную связь: запустил, через минуту получил результаты. Если нашелся баг, навряд ли все приложение работает правильно, если что-то не так с его частью.

Умные люди не зря придумали пирамиду тестирования. Юнит-тесты быстрые, их можно написать много. В этом их смысл задешево протестировать очень много участков твоего приложения, чтобы убедиться, что они работают правильно. Чем больше кусочков покрыты юнит-тестами, тем лучше, но это не значит, что надо стремиться к 100-процентному покрытию. Есть много вещей, которые тесты не поймают, потому что не созданы для этого. Но если на их уровне что-то вылезло, нет смысла дальше идти и запускать тяжелые функциональные тесты.

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

Сергей Жук, Skyeng: Давай напоследок поговорим о мутационных тестах. Нужны ли они?

Владимир Янц, Badoo: Нужно внедрять мутационные тесты, как только вы задумались о юнит-тестах. Они поднимут из-под ковра все те проблемы, которые так часто обсуждают бесполезное тестирование, coverage ради coverage.

Там не нужно ничего писать дополнительно. Это просто библиотека: она берет coverage, который у вас где-то есть, идет в исходный код и начнет менять операторы кода на противоположные. После каждой такой мутации она прогоняет тесты, которые, согласно code coverage, эту строчку покрывают. Если ваши тесты не упали, они бесполезны. И, наоборот, можно находить строчки, для которых есть потенциальные мутации, но нет coverage.

Внедрить это на ранних этапах ничего не стоит. А пользы много.
Подробнее..

LaravelДайджест (1218 октября 2020)

18.10.2020 18:16:23 | Автор: admin

Практические советы от Spatie по продажам цифровых продуктов с помощью Laravel. Новый пакет для генерации API-документации. Правильная валидация электронной почты. Применение Коллекций для работы со сложными данными.


Laravel Дайджест


Релизы


  • Laravel 8.10
  • Laravel Livewire 2.3
  • Laravel Enlighten
    Пакет для генерации API-документации
  • Laravel Venture
    Пакет для создания и управления сложными асинхронными процессами
  • Laravel Wagonwheel
    Пакет для онлайн-просмотра пользователями полученных от системы писем.
  • Laravel Swift
    Пакет объединяющий Laravel, Livewire, Bootstrap и Font Awesome. Вдохновлён SwiftUI

Уроки



Видео



Телеграм на русском


Подробнее..

PHP-Дайджест 190 (5 19 октября 2020)

19.10.2020 12:14:37 | Автор: admin
Фото: Илья Шихалеев.

Свежая подборка со ссылками на новости и материалы. В выпуске: PHP 8.0 RC 2, Xdebug 3 beta, PhpStorm EAP с поддержкой PHPStan и Psalm, порция полезных инструментов, статьи, видео, митапы.

Приятного чтения!



Новости и релизы



Инструменты


  • PHP-DI Независимый от фреймворка DI-контейнер.
  • markrogoyski/math-php Мощная современная математическая библиотека для PHP.
  • Danack/FloatHex Функции для преобразования числа с плавающей точкой в шестнадцатеричную строку и обратно, а также для отображения двух чисел с плавающей точкой в виде двоичного представления. Или еще раз почему 0.1 + 0.2 === 0.3 -> false
    Скрытый текст
    echo float_compare(0.3, 0.1 + 0.2);>> Sign  Exponent     Mantissa                                                 0  01111111101  0011001100110011001100110011001100110011001100110011     0  01111111101  0011001100110011001100110011001100110011001100110100     -  -----------  -------------------------------------------------xxx 
    
  • marcocesarato/PHP-Antimalware-Scanner Сканер для поиска вредоносного кода в PHP-файлах.
  • Prometheus PHP Клиент для prometheus.io на PHP.
  • shivammathur/setup-php GitHub action для установки PHP, расширений, и прочего для последующего использования в своих пайплайнах. Небольшой обзор в блоге GitHub.

Symfony



Laravel



Async PHP


  • clue/reactphp-mq Легковесная очередь сообщений в памяти на базе ReactPHP.

Материалы для обучения



Аудио/Видео





Если вы заметили ошибку или неточность сообщите, пожалуйста, в личку.
Вопросы и предложения пишите на почту или в твиттер.

Больше новостей и комментариев в Telegram-канале PHP Digest.

Прислать ссылку
Поиск ссылок по всем дайджестам
Предыдущий выпуск: PHP-Дайджест 189
Подробнее..

Месяц до релиза PHP8. А на какой версии ты в основном сидишь сейчас?

20.10.2020 16:06:30 | Автор: admin
С этим вопросом мы пошли к докладчикам ульяновского PHP-митапа: его можно будет аккуратно посетить или свободно посмотреть в интерактивном формате уже в эти выходные.


Зрители субботней трансляции смогут задать вопрос голосом прямо из браузера.

Ответы и другие интересные истории от выступающих под катом.

Роман Ананьев, Simtech Development. Расскажет про анатомию PHP


На какой версии PHP ты в основном сейчас сидишь?

Много 7.2. Местами обновляемся до 7.3. А местами уже попробовали php8.0.0-rc1

Твой доклад называется Думай как PHP и посвящен мифам о производительности языка. Как пришла его идея?

Люблю, когда сайты работают быстро это важный параметр, он влияет на то, как люди будут покупать. А чтобы оптимизировать что-то, нужно понимать, как оно работает.

Что передать тем, кто хочет прийти на митап?

Что у нас будут все меры предосторожности: свободная рассадка, антисептики, раздача масок и перчаток, измерение температуры и проветривания. В очном общении можно будет детальнее поспрашивать. Сейчас, когда все чаще в онлайне, этого немного не хватает.

Что передать тем, кто хочет посмотреть митап в трансляции?

Тоже отличный выбор!


Александр Нагорнов, Lifehacker. Расскажет про принятие автодеплоя в небольшом отделе разработки


На какой версии PHP ты в основном сейчас сидишь?

В основном сидим на 7.3. Из-за монолита на WordPress каждый переезд даётся тяжело: у нашего проекта долгая история, разработчики часто пользовались, чем хотели и как хотели, есть куча сторонних пакетов, которые уже не поддерживаются. И перед каждый переездом приходится тщательно изучать и дописывать-переписывать пакеты.

В докладе ты собираешься осветить путь от отрицания до принятия автодеплоя и при чем тут фатальное падение продакшен-сервера. Почему выбрал такую тему?

Общался с разработчиками из нескольких других медиа. Оказалось, не у всех есть автодеплой, но все бы хотели его ввести. Хочу на примере показать, с какой стороны подступиться к вопросу, если у вас маленький отдел разработки и вы испытываете постоянные боли с развертыванием приложений.

Что передать тем, кто хочет прийти на митап и посмотреть его в онлайне?

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


Максим Шамаев, Skyeng. Расскажет про аспект отказоустойчивости при переходе от монолита к 100+ сервисам


На какой версии PHP ты в основном сейчас сидишь?

7.1 потому что библиотеки, о которых расскажу, построены под PHP 7.1. C 7.3 оно конфликтует, а под 7.2 переносить лень не дошли руки)

Твой доклад это прогон выступления для PHPRussia. Как появилась эта тема?

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

Что передать тем, кто хочет прийти на митап и посмотреть трансляцию?

Для тех, кто захочет прийти: мы начнем в 11, это в центре и можно запытать докладчика после доклада. А для онлайн-участников: помните, что 11 в Ульяновске это 10 по более привычному многим московскому времени.

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

P.S. Еще одна причина смотреть или зайти


В конце митапа мы проведем викторину с призами:



Таким вот фирменным слоном, а также лицензией на любую IDE JetBrains и билетом на PHPRussia (если конференцию перенесут, он будет действовать).

Участвовать в PHP-викторине смогут как зрители трансляции, так и пришедшие очно.
Подробнее..

Создание современного API на PHP в 2020 году

01.10.2020 22:19:19 | Автор: admin
Итак, на примере этого API, я хочу показать современную PHP архитектуру для высоко нагруженных проектов. Когда проект еще в самом начале, и не то, что бизнеслогика (взаимоотношения с базой данных) не прописана, но и сама бизнесмодель не очень ясна, построение эффективной IT архитектуры может идти только одним путем: необходимо жестко разделить frontend и backend.

Что обычно делали в таких ситуациях два-три года назад? Брался монолитный фрейворк типа Laravel или Yii2, вся бизнес модель разбивалась, худо-бедно, на блоки, а эти блоки уже имплементировались как модули фреймворка. В итоге еще через 3 года получалась огромная не поворотная машина, которая сама по себе медленная, а становилась почти невыносимо медленной, в которой фронтенд рендится через бэкенд по средством классической MVC архитеркутуры (пользователь отправил запрос, контроллер его подхватил, вызвал модель, та в свою очередь чего-то там натворила с базой данных, вернула все контроллеру, а том наконец-то вызвал вьювер, вставил туда данные из модели и отдал это все пользователю, который уже успел открыть очередную банку пива...). А ну еще особо продвинутые ребята они не просто вьюверели Tweeter Bootstrap, а во вьювер вкручивали самом деле очень хорошие библиотеки типа JQuery или вместо вьювера использовали какой-нибудь фронтенд фреймворк. В итоге поддерживать такой БеЛаЗ становилось все сложнее, а ввести нового программиста в команду было очень сложно ибо не все рождаются Энштейнами. Добавим сюда тотальное отсутствие документации разработчика (камменты в 9000 файлах почитаешь там все есть!) и в итоге, смотря на это все, становилось по-настоящему грустно

Но тут произошли ряд событий которые в корне изменили ситуацию. Во-первых, наконец-то, вышли стандарты PSR и Symfony внезапно перестал быть единственным модульным фремворком, во-вторых вышел ReactJS, который позволил полноценно разделить фронтенд от бэкенда и заставить их общаться через API. И добивая последний гвоздь в крышку гроба старой системы разработки (MVC это наше все!) выходит OpenAPI 3.0, собственно который и регулирует стандарты этого общения через API между фронтендом и бэкендом.

И в мире PHP стало возможно делать следущее:

1. Разделить бизнес модель на сервисы и микросервисы, и не поднимать для этого весь БеЛаЗ, а обслуживать запросы микросервисов буквально в пару строк кода самый банальный пример: похожие товары (отдельный запрос GET отдельный, малюпасенький API, который его обработал и вернул, и мгновенный вывод этой информации ReactJS в браузере пользователя. Основной БеЛаЗ даже и не узнал о том, что произошло

2. Писать API стандартизировано по стандарту OpenAPI 3.0 ( swagger.io )в виде YAML или JSON файла, когда каждый программист не лезет в грязных сапогах в ядро системы, а например, культурно дописывает свою часть в общем YAML файле. тем самым устраняя вероятность ошибки от человека к человеку и уменьшая количество седых волос у тестеровщиков. Просто потом из готового YAML сгенерировал полностью рабочий и даже с миделваре сервер. На каком угодно языке и фреймфорке.

3. Теперь не надо стало нанимать кого-то, чтобы он, этот кто-то писал для вашего API библиотеки, которыми ваши клиенты будут обращаться к вашему API: github.com/OpenAPITools/openapi-generator я насчитал генерацию более 40 серверов для API и даже не стал считать библиотеки для доступа к ним, ибо единственный язык программирования который я там не нашел Dlang)

Итак с API думаю разобрались. Пишем YAML файл в сваггере или инсомнии, через OpenAPITools генерируем сервер и пользовательские библиотеки. Абстрактные классы не трогаем (папочка lib) а всю бизнеслогику выносим в наследуемые классы (папочка scr), для того чтобы при последующей перегенерации сервера мы ничего не сломали, а просто тупо скопировали папочку lib к себе в корень фреймфорка и добавили новую бизнес логику в не перемещаемой папочке scr. API готов Быстро, просто, функционально. Клиентский библиотеки тоже готовы. Директор даже не успел вернуться с Мальдивов

Теперь становится вопросы, точнее два вопроса, а что у нас перед API и соответственно, что у нас под хвостом после API.

Ответы:
1.Там вдали за рекой, далеко перед API у нас цветет, расползается на новую функциональность и картинки ФРОНТЕНД (Предпочтительнее ReactJS, но Vue тоже сойдет. Хотя там из коробки всего столько много, что утяжелять процесс он будет, а вот насколько в реальной жизни это понадобится не совсем понятно и зависит напрямую от бизнес модели). И НЕТ! Я к этому зверю даже близко подходить не буду, ибо с детства у меня на него аллергия. Тут нужен отдельный специалист. Я не фулстак и не пишу фронтенд.

2. Прямо вот перед самим API у нас НЕ УГАДАЛИ не NGINX, а RoadRunner roadrunner.dev/features. Заходим, читаем, понимаем, что это быстрее и вокеры расписываются по количеству процессоров, а посему никогда не будет таблички М НА ПРОФИЛАКТИКЕ, ибо просто надо вокеры переключить.

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

1. В случае если весь API написан уже, и будет написан в дальнейшем, на PHP голову ломать не зачем, ставим RoadRunner с prometheus.io
2. В случае если система собирается из разных кусков, разные сервисы написаны на разных языках и дальше тоже не понятно на чем их писать будут:
2.1. Ставим NGINX UNIT пользуемся поддерживаемыми языками.
2.2. Поднимаем ВСЕ РАВНО КАКУЮ систему контейнеров, Docker, LXC, LXD. Выбор опять же зависит от размера проекта поддерживать сборку PROXMOX-LXC на хостинге в 12 процессоров, c 32Гб памяти, за 40 евро в месяц будет в разы дешевле, чем Docker сборки на Google Cloud Platform. В каждый контейнер ставим подходящий к языку сервер, и связываем все это HAProxy www.haproxy.org. HAProxy шикарный балансер и прокси сервер который в корпоративной среде, не менее популярен чем NGINX. Что он делает, а чего нет читаем тут cbonte.github.io/haproxy-dconv/2.3/intro.html пункт 3.1. При такой архитектуре сервисы или микросервисы могут писаться на чем угодно и никто не зависит от ограничений накладываемыми RoadRunner или NGINX UNIT.

3. Под хвостом Cycle ORM. Не ленимся смотрим видио
www.youtube.com/watch?v=o1wzzSoJJHg&ab_channel=fwdays, что будет стоять за ней конкретно MySQL или Postgres опять, я бы оставил на после того, как будет понятна бизнес схема проекта. MySQL проще масштабируется, в Postgres больше бизнес логики перенесенной внутрь самой базы.

4. Пример который можно посмотреть и пощупать
bitbucket.org/rumatakira/api-example/src/master. Там за основу взято тестовое задание. Все самые полезные вещи находятся в папке EXTRAS. Там уже сть jar file генератора, YAML swagger файл API, сгенерированный API stub через OpenAPITools в SLIM4. Даже с аутентификацией и мидлваре. документация на API сгенрированная, правда swagger, не OpenAPITools. Предполагается, что некоторые юзеры залогинены и им выдан токен. Там уже стоит RoadRunner впереди. Стек PHP 7.4.10, PostgreSQL 12.4.

После Git clone, composer install в файле /bootstrap.php прописываем юзера и пароль к базе, которую вначале создаем, потому что это PostgreSQL, по умолчанию сервер слушает локальный порт 8888, если нужно меняем в файле /.rr.yaml, и
выполняем команду: composer run-script fill-database. Все никаких миграций )). Пользуемся.

P.S. Всем залогиненым пользователям присвоен одинаковый токен. Вообще в примере нет никаких валидаций ввода пользователя и почти нет защит это пример в основном нацеленный на архитектуру.

С уважением,
Кирилл Лапчинский
api-studio.com
mail@api-studio.com
Подробнее..

Перенос форума IPB в bbPress WordPress

03.10.2020 00:12:20 | Автор: admin

"Invision Power Board" он же "Invision Community", я его назваю IPB.

Форум IPB хороший, функциональный во всех отношениях, даже больше того. Даже больше того - это практически полноценный CMS движок для сайта, взамен WP и подобным.

Но я давно использую для нескольких проектов CMS WP, хотя и люблю когда сайт написан с нуля без лишнего мусора. Примеры моих проектов для сравнения:

  • lagonaki.ru - CMS WP + серьезная букинг тема, дописанная-переписанная, симбиоз CRM с Bitrix24. Постоянно обновляемая + множество плагинов, которые так же обновляются. Обслуживается самостоятельно, нужная информация вносится без привлечения внешних разработчиков.

  • nik38.ru - написанный с нуля сайт, ничего лишнего. Много лет назад создан, чуть-чуть морально устарел, но работает исправно, доволен. Но единственный минус, чтобы что-то изменить, нужно привлекать отдельного программиста.

CMS WP как раз удобен тем, что он замечательно модернизируется, обновляется и поддерживается большинством плагинов. Например, нужен вам интерфейс для интернет коммерции - пожалуйста. Нужен плагин для SEO - пожалуйста. Нужен плагин для оптимизации - пожалуйста - несколько сот вариантов, и т.д. и т.п. В общем, лирика, теперь по делу.

Назрела пора реанимировать замороженный туристическо-альтруистический проект fisht.ru, у которого один из разделов "Форум" forum.fisht.ru, но жил он своей жизнью от основной части сайта. Закрыл форум вынуждено на регистрацию новых пользователей 3 года назад из-за обилия спамеров и отсутствия решения по борьбе со спамом. Предлагалось только обновить движок и заплатить за это 700$ + русификация...

Сейчас, начал изучать как можно объединить проект под WP и перенести форум с IPB платформы.

Наткнулся на cms2cms.com - проект, который конвертирует много форумов в другие форумы. Автоматизация процесса и техподдержка. Интересно, но очень дорого... Тем более автоматизация неважно сработала на первом этапе.

CMS2CMS - сайт для миграции форумов. Но дорогой :)CMS2CMS - сайт для миграции форумов. Но дорогой :)

Свои прогеры по горло загружены, решил поискать исполнителя на "Фрилансер". Нашел толкового парня, но он специалист в IPB, решает вопросы с модернизацией, дизайном, обновлением и т.д. Если нужно - обращайтесь к нему, зовут Олег. Ему огромное спасибо, за то что решил оперативно помочь, но я все же хотел не на IPB остаться, а именно с него "съехать". Много причин, но две основные "Лицензия" дорогая и разделение сайта на форум и сайт. Олег и подсказал, что оказывается есть возможность съехать стандартными средствами bbPress. Вот та самая статья: Invision IPB v3.1x, v3.2x, v3.3x & v3.4x Importer for bbPress. За что ему отдельное спасибо, люблю когда не навязывают свою услугу, а показывают как в действительности обстоят дела.

Установка bbPress и перенос данных из IPB

Работающая версия на 02.10.2020. Если хотите сэкономить себе часы, а может даже и дни свободного времени, то рекомендую воспользоваться работающей связкой версий WP и bbPress:

Долго мне пришлось разбираться, чтобы понять, что последняя версия Wordpress 5.5.1 и предыдущие версии 5.4 не идут с модулем bbPress 2.6.5, который обновлялся 2 месяца назад. В общем, это основная сложность, которая съела уйму времени.

Далее, активируем плагин bbPress, заходим в "Инструменты" - "Форумы" - "Импорт форумов" и выбираем платформу "Invision", далее по вашим настройкам. Там все дальше просто.

Плагин "bbPress" - Инструменты - Форумы - Импорт форумовПлагин "bbPress" - Инструменты - Форумы - Импорт форумов

Если сервер с которого вы будете переносить форум находится на одной площадке, то указываем localhost, если же другой, то, соответственно, тот, с которого переносим.

Карту настроек, которую использовал я, - используйте.Карту настроек, которую использовал я, - используйте.

Если у вас идет вот такая картинка, значит, перенос производится правильно!

Производится перенос форума IPB в bbPressПроизводится перенос форума IPB в bbPress

Если заметили, что все повисло там, где не должно было виснуть - нажмите паузу и потом запустите далее. Сразу в phpMyAdmin в вашей базе увидите, что цифра начала увеличиваться, значит процесс переноса идет.

Для кого вообще эта статья?

Этот статью написал для тех, кто будет искать выход переноса форума IPB на платформу WP. Я выбрал bbPress, т.к. это по сути создатели WP - оригинальная интеграция всегда лучше. Хотя отсутствие обновлений, у меня "съело" очень много времени...

Если используете nginx

Рекомендую сразу внести правки в конфигурационном файле nginx
Требуется установить в location @fallback - для http и для https

    proxy_connect_timeout       600;    proxy_send_timeout          600;    proxy_read_timeout          600;    send_timeout                600;

В противном случае, на определенных операциях настройки форума будет выдаваться ошибка. В частности у меня постоянно выдавалась ошибка, если я в bbPress "Инструменты" - "Форум" - "Восстановление форума" запускал процесс "Пересчет темы для меток тем", то операция уходила и заканчивалась "504 Gateway Time-outnginx/1.14.1".

Также огромное спасибо Никите Максименко и Егору Шалаев из службы тех поддержки TimeWeb, которая поучаствовала в запуске новой жизни проекта.

Буду признателен комментариям от тех, кто сталкивался детально с настройкой bbPress, т.к. один вопрос еще не решил. В частности это ошибочно считает темы и посты, нужно пересчитать, все никак не получается.

Ошибка в количестве тем и сообщений.Ошибка в количестве тем и сообщений.

Что интересно, если создать ФОРУМ, в него потом добавить другой подфорум и потом его вывести из-под него, то в новом созданном форме остается тоже самое количество из подфорума. Пример, я создал форум "Горы", в него перенес "Общие обсуждения", после "Общие обсуждения" вывел из "Горы" итог - равное количество Тем и сообщений. В общем, все, кроме этого, уже нормально и разобрался. Помогите, если кто-то сталкивался с этой проблемой.

Подробнее..

Recovery mode Типобезопасная работа с массивами PHP, часть 2

07.10.2020 02:18:32 | Автор: admin

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

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

И конечно я напишу о работе над ошибками.

Для тех кто не знал и забыл, что такое ArrayHandler

Spoiler

Ответим на вопрос: "что такое типобезопасная работа с массивами в PHP ?"

Типобезопасная это:

  • Когда мы можем получить элемент массива без опасности получить эксепшен о не существующем индексе;

  • Когда мы можем передать полученный элемент в метод и мы точно не получим эксепшен несоответствия типов;

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

$a = 0;if (key_exists($key, $collection)) {$a = (int) $collection[$key];}

И даже когда я узнал о современном способе писать через:

$a = (int) $collection[$key] ?? 0;

Мне от этого не полегчало, потому что вложенность у массивов бывает "мама не горюй".

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

Не буду копипастить примеры работы с либой, их можно посмотреть в первой части или можно почитать документацию.

Либа устанавливается через Композер:

composer require sbwerewolf/language-specific

Есть версии для PHP 5.6 / 7.0 / 7.2 .

Это было долгое вступление теперь по существу.

Обновления

Пару дней назад мне было и грустно и скучно, захотелось сделать что ни будь хорошее, например сделать так что бы, когда пробегаешь по элементам с помощью foreach(), можно было не только получить элемент ( ValueHandler ), но индекс этого элемента.

Я с энтузиазмом принялся за работу и уже написав тонны кода, наткнулся на комент в документации к PHP, который сделал весь новый код бесполезным.

Оказывается можно делать так:

yield $key => $value;

И в foreach() будет возвращён индекс элемента. Эврика!

Теперь IArrayHandler::pulling() возвращает и новый IArrayHandler от элемента массива, и индекс этого элемента. Я был счастлив, кажется теперь ArrayHandler стал идеальной библиотекой для работы с массивами (в том виде как я обозначил в начале статьи).

На эмоциях я запилил ещё два метода. То есть ещё один метод - IArrayHandler::getting(), плюс я добавил поддержку интерфейса Iterator и теперь экземпляр ArrayHandler можно использовать в foreach() как обычный массив.

Теперь IArrayHandler::pulling() возвращает ArrayHandler для каждого вложенного массива (будут проигнорированы элементы исходного массива, которые не являются массивами). Название метода "pulling" образовалось от названия другого метода - IArrayHandler::pull(), с помощью которого можно получить экземпляр ArrayHandler от элемента массива.

IArrayHandler::getting() возвращает IValueHandler для всех элементов массива, не являющимися массивами. Название метода "getting" образовалось от названия другого метода - IArrayHandler::get(), с помощью которого можно получить экземпляр IValueHandler от элемента массива.

Теперь у нас IArrayHandler::pulling() для массивов, и IArrayHandler::getting() для всех остальных типов.

Более наглядно:

$data = new ArrayHandler(    [        'first' => ['A' => 1],        'next' => ['B'=>2],        'last' => ['C'=>3],        4=>[5,6],        7,        8,        9    ]);echo 'arrays'.PHP_EOL;foreach ($data->pulling() as $key => $value) {    echo "[$key] => class is ".get_class($value).' '.PHP_EOL;}echo 'values'.PHP_EOL;foreach ($data->getting() as $key => $value) {    echo "[$key] => {$value->asIs()} , class is ".get_class($value).' '.PHP_EOL;}

Вывод скрипта:

arrays[first] => class is LanguageSpecific\ArrayHandler [next] => class is LanguageSpecific\ArrayHandler [last] => class is LanguageSpecific\ArrayHandler [4] => class is LanguageSpecific\ArrayHandler values[5] => 7 , class is LanguageSpecific\ValueHandler [6] => 8 , class is LanguageSpecific\ValueHandler [7] => 9 , class is LanguageSpecific\ValueHandler

Если нам надо пробежаться по всем элементам исходного массива, мы используем обычный foreach():

$data = new ArrayHandler(    [        'first' => ['A' => 1],        'next' => ['B'=>2],        'last' => ['C'=>3],        4=>[5,6],        7,        8,        9    ]);echo 'ALL'.PHP_EOL;foreach ($data as $key => $value) {    $type = gettype($value->asIs());    echo "[$key] => value type is $type , class is ".get_class($value).PHP_EOL;}

Вывод скрипта:

ALL[first] => value type is array , class is LanguageSpecific\ValueHandler[next] => value type is array , class is LanguageSpecific\ValueHandler[last] => value type is array , class is LanguageSpecific\ValueHandler[4] => value type is array , class is LanguageSpecific\ValueHandler[5] => value type is integer , class is LanguageSpecific\ValueHandler[6] => value type is integer , class is LanguageSpecific\ValueHandler[7] => value type is integer , class is LanguageSpecific\ValueHandler

Соответственно, если мы какие то элементы хотим обработать как элементы, а какие то как массивы, то мы в foreach(), делаем так:

foreach ($data as $key => $value) {    /* @var \LanguageSpecific\ValueHandler $value */    if($value->type() === 'array'){        $handler = new ArrayHandler($value->array());        /* some code */    }}

Работа над ошибками

Метод IValueHandler::default() перестал быть статическим, его опасность до меня пытался донести @GreedyIvan, до меня дошло через неделю, спасибо.

Метод ArrayHandler::simplify() был удалён, потому что на самом деле

Зачем ArrayHandler->simplify(), когда есть array_column? (c) @olegmar

Cпасибо @olegmar.

Метод IArrayHandler::next() был заменён на IArrayHandler::pulling(), этот метод перебирает все вложенные массивы (первый уровень вложенности). Не то что бы комент от @Hett меня прямо убедил, но к размышлениям подтолкнул.

Спасибо @ReDev1L за поддержку в коментах.

Был добавлен метод IArrayHandler::raw(), что бы можно было получить исходный массив. Раньше, когда не было возможности получить индекс элемента, приходилось перебирать исходный массив, сейчас по опыту использования, бывает необходимость добавить/убавить элементы массива и создать из изменённого массива новый ArrayHandler.

На этом всё. Спасибо за чтение.

Подробнее..

Ловим баги на клиенте как мы написали свою систему для сбора клиентских ошибок

08.10.2020 16:23:54 | Автор: admin

У нас в Badoo довольно много клиентских приложений. Помимо основных продуктов Badoo и Bumble, у которых есть как веб-версии (десктопная и мобильная), так и клиенты под нативные платформы (Android и iOS), ещё есть с десяток внутренних инструментов со своими UI. Для сбора клиентских ошибок мы используем собственную разработку под кодовым названием Gelatо. Последние два года я работал над её серверной частью и за это время открыл для себя много нового из мира разработки Error Tracking систем.

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


Что вас ждёт:

  • я расскажу, как мы работаем с ошибками, какие инструменты использовали раньше и почему разработали свою систему, а не выбрали готовое решение.

  • дам краткий обзор нашей системы, её архитектуры и технологического стека;

  • перечислю особенности, которые нужно учитывать, если вы решите разрабатывать свою систему сбора клиентских ошибок.

Как мы используем информацию об ошибках

Первое и самое очевидное отслеживаем ошибки на продакшене. От багов на продакшене никто не застрахован, поэтому важно отслеживать такие проблемы, понимать, сколько пользователей они затронули, и оперативно фиксить наиболее критичные из них.

Второе проводим анализ ошибок. Мы в Badoo релизимся довольно часто:

веб-приложения: один-два раза в день, включая серверную часть;

нативные приложения: раз в неделю (хотя многое зависит от того, как быстро билд примут в App Store и Google Play).

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

Третье. Информация об ошибках, собранная в одном месте, значительно упрощает работу разработчиков и QA-инженеров.

Что использовали раньше

Исторически для сбора клиентских ошибок в Badoo использовались две системы: HockeyApp для сбора краш-репортов из нативных приложений и самописная система для сбора JS-ошибок.

HockeyApp

HockeyApp полностью удовлетворяла наши потребности, до тех пор пока в 2014 году её не приобрела Microsoft и не начала менять политику использования, чтобы подтолкнуть людей к переходу на свою систему App Center. App Center на тот момент нашим требованиям не соответствовала: она находилась на стадии активной разработки, и часть необходимой нам функциональности отсутствовала, в частности деобфускация стек-трейсов Android-приложений с использованием DexGuard mapping-файлов, без которой невозможна группировка ошибок. О деобфускации я расскажу ниже; если вы слышите об этом впервые, значит, точно узнаете что-то новое.

Microsoft установила дедлайн 16 октября 2019 года, к этому дню все пользователи HockeyApp должны были мигрировать в App Center. К слову, поддержка DexGuard появилась в App Center лишь в конце декабря 2019 года, спустя несколько месяцев после официального закрытия HockeyApp.

В дополнение к этому мы столкнулись с проблемой некорректного подсчёта общего количества ошибок в HockeyApp. Так как Microsoft фактически остановила разработку HockeyApp, нам пришлось начать дублировать эту информацию во внутреннюю систему аналитики, чтобы понимать реальное количество ошибок. Это послужило ещё одним толчком к поиску альтернативного решения.

Наша система для сбора JS-ошибок

Что касается самописной системы для сбора JS-ошибок, то долгие годы она работала без нареканий, хотя обладала лишь базовой функциональностью.

Архитектура у неё была довольно простой:

  • данные хранились в MySQL (мы хранили информацию о последних 1020 релизах);

  • для каждой версии приложения была отдельная таблица;

  • работал поиск по фиксированному набору полей средствами MySQL, плюс мы использовали Sphinx для полнотекстового поиска.

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

Собрав и проанализировав все требования, мы поняли, что обойтись малой кровью не получится. Система, которая разрабатывалась в 2014 году, морально устарела, и затраты на её рефакторинг превысили бы стоимость внедрения нового решения.

Поэтому мы решили постепенно переходить на новую систему, которая бы покрывала весь существующий функционал и удовлетворяла всем нашим актуальным требованиям.

Требования к новой системе

  • Хранение всех клиентских ошибок в одном месте.

  • Хранение событий минимум за полгода без семплирования. При исследовании фантомного бага (бага, у которого нет чёткого сценария воспроизведения) возможность посмотреть ошибки для конкретного пользователя в определённый момент времени может значительно облегчить жизнь инженера. Поэтому чем больше информации мы можем хранить, тем лучше.

  • Лёгкое масштабирование. Периодически случаются ошибки, которые порождают миллионы событий. Система должна быть способна обработать такой поток данных.

  • Отказоустойчивость чтобы минимизировать риск потери данных.

  • Способность давать ответ не только на вопрос Сколько произошло ошибок?, но и на вопрос Сколько пользователей это затронуло?.

  • Группировка похожих ошибок с сохранением мета-информации (время первой и последней ошибки в группе, в каком релизе появилась ошибка, статус ошибки и т. п.).

  • Гибкий поиск по любой комбинации полей с поддержкой полнотекстового поиска.

  • Разнообразная статистика: количество ошибок за период, количество ошибок в разбивке по релизам, по браузерам, по операционным системам и т. п.

  • Интеграция с Jira. Наша разработка ведётся с использованием Jira, поэтому нам необходима возможность создавать тикеты для определённых ошибок.

  • Self-hosted. Мы хотим, чтобы система работала на нашем железе. Это обеспечит нам полный контроль над данными и возможность в любой момент изменить конфигурацию кластера.

Почему мы не выбрали готовое решение

Конечно, прежде чем писать своё решение, мы проанализировали существующие на рынке системы.

Платные сервисы

Сбором клиентских ошибок занимаются многие SaaS-решения, и это неудивительно: быстрое обнаружение и исправление ошибок один из ключевых аспектов современной разработки. Среди самых популярных решений можно выделить Bugsnag, TrackJS, Raygun, Rollbar и Airbrake. Все они обладают богатым функционалом и в целом соответствуют нашим требованиям, но мы не рассматривали облачные решения: не было уверенности в том, что ценовая политика и политика использования со временем не изменятся, как это случилось с HockeyApp. А миграция на новое решение довольно сложная и длительная процедура.

Open-source-решения

С open-source-системами всё было не так радужно. Большинство из них либо перестали развиваться, либо так и не вышли из стадии разработки и были непригодны для использования в продакшене.

Фактически лишь Sentry продолжал активно развиваться и обладал функциональностью, покрывающей большую часть наших потребностей. Но на тот момент (начало 2018 года) восьмая версия сервиса не подошла нам по следующим причинам:

  • вместо хранения всех событий в ней использовалось семплирование;

  • в качестве базы данных использовалась PostgreSQL, с которой у нас не было опыта работы;

  • были сложности с добавлением новой функциональности, так как система написана на Python, а наши основные языки PHP и Go;

  • отсутствие части необходимой нам функциональности (например, интеграции с Jira).

В июле 2018 года вышла девятая версия Sentry, в которой появилась интеграция с issue-трекерами и был заложен фундамент для одного из ключевых, на мой взгляд, улучшений перехода на ClickHouse для хранения событий (рекомендую к прочтению серию статей об этом). Но к сожалению, на момент нашего ресёрча всего этого ещё не было даже в планах. Поэтому мы решили, что оптимальным вариантом в нашем случае будет реализация собственной системы, заточенной под наши процессы, которую легко интегрировать с другими внутренними продуктами.

Так на свет появилась система под кодовым названием Gelato (General Error Logs And The Others), о разработке которой и пойдёт речь дальше.

Краткий обзор системы

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

На главной странице находятся список приложений и общая статистика ошибок по заданному критерию.

Кликнув на то или иное приложение, мы попадём на страницу со статистикой по его релизам.

Кликнув на версию, мы попадём на страницу со списком ошибок.

Здесь можно узнать, какие ошибки произошли, сколько их было, сколько пользователей это затронуло, когда ошибка встретилась впервые и время последнего события. Также есть сортировка по большинству полей и возможность создания тикета в Jira для любой ошибки.

Так эта страница выглядит для нативных приложений:

Кликнув на ошибку, мы попадём на страницу с детальной информацией о ней.

Здесь доступны общая информация об ошибке (1), график общего количества ошибок (2) и различная аналитика (3).

Также можно получить информацию о конкретных событиях, что в основном используется для анализе проблемы.

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

Выбираем версии для сравнения:

И попадаем на страницу со списком ошибок, которые есть в одной версии и которых нет в другой:

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

  • интеграция с нашим A/B-фреймворком для отслеживания ошибок, которые появились в том или ином сплит-тесте;

  • система уведомлений о новых ошибках;

  • расширенная аналитика (больше графиков и диаграмм);

  • email-дайджесты со статистикой по приложениям.

Общая схема работы

Теперь поговорим о том, как всё устроено под капотом. Схема довольно стандартная и состоит из трёх этапов:

  1. Сбор данных.

  2. Обработка данных.

  3. Хранение данных.

Схематически это можно изобразить следующим образом:

Давайте для начала разберёмся со сбором данных.

Сбор данных

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

Что входит в задачи API:

  • прочитать данные;

  • проверить данные на соответствие требуемому формату, чтобы сразу отсечь шум (чаще всего это запросы от мамкиных хакеров, которые натравили сканеры на наше API);

  • сохранить всё в промежуточную очередь.

Для чего нужна промежуточная очередь?

Если полагаться на то, что у нас достаточно низкий EPS (errors per second), и на то, что все части нашей системы будут работать стабильно всё время, то можно значительно упростить систему и сделать весь процесс синхронным.

Но мы-то с вами знаем, что в реальном мире так не бывает и на каждом из этапов в самый неподходящий момент может произойти нечто непредвиденное. И к этому наша система должна быть готова. Так, ошибка в одной из внешних зависимостей приложения приведёт к тому, что оно начнёт крашиться, что повлечёт за собой рост EPS (как это было с iOS Facebook SDK 10 июля 2020 года). Как следствие, значительно увеличится нагрузка на всю систему, а вместе с этим и время обработки одного запроса.

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

Поэтому, чтобы снизить риск потери данных и сделать их сбор максимально быстрым (чтобы клиенту не нужно было долго ждать ответа), мы сохраняем все входящие данные в промежуточную очередь, которая разбирается отдельным скриптом в нашем скриптовом облаке.

Что можно использовать в качестве очереди?

  1. Первое, что приходит в голову, популярные брокеры сообщений, например Redis или RabbitMQ.

  2. Также можно использовать Apache Kafka, которая хорошо подходит для случаев, когда требуется хранить хвост входящих данных за определённый период (например, для какой-то внутренней аналитики). Kafka используется в частности в последней (десятой) версии Sentry.

  3. Мы остановились на LSD (Live Streaming Daemon). По сути, это очередь на файлах. Система используется в Badoo довольно давно и хорошо себя зарекомендовала, плюс у нас в коде уже есть вся необходимая обвязка для работы с ней.

Хранение данных

Тут нужно ответить на два вопроса: Где хранить? (база данных) и Как хранить? (модель данных).

База данных

При реализации прототипа системы мы остановились на двух претендентах: Elasticsearch и ClickHouse.

Elasticsearch

Из очевидных плюсов данной системы можно выделить следующие:

  • горизонтальное масштабирование и репликация из коробки;

  • большой набор агрегаций, что удобно при реализации аналитической части нашей системы;

  • полнотекстовый поиск;

  • поддержка UPDATE по условию (процесс обработки данных у нас асинхронный, и нужна возможность выполнять некоторые шаги пайплайна повторно, а значит, и возможность обновлять определённые поля у конкретных событий);

  • поддержка DELETE по условию (мы храним данные за полгода, а значит, нам нужна возможность удалять устаревшие данные);

  • гибкая настройка через API, что позволяет разработчикам менять настройки индексов в зависимости от задач.

Конечно, как у любой системы, у Elasticsearch есть и недостатки:

  • сложный язык запросов, поэтому документация всегда должна быть под рукой (в последних версиях появилась поддержка синтаксиса SQL, но это доступно только в платной версии (X-Pack) либо при использовании Open Distro от Amazon);

  • JVM, которую нужно уметь готовить, в то время как наши основные языки это PHP и Go (например, для оптимизации сборщика мусора под определённый профиль нагрузки требуется глубокое понимание того, как всё работает под капотом; мы столкнулись с этой проблемой при обновлении с версии 6.8 до 7.5, благо тема не нова и есть довольно много статей в интернете (например, тут и тут));

  • плохое сжатие строк; мы планируем хранить довольно много данных и, хотя железо с каждым годом дешевеет, хотим использовать ресурсы максимально эффективно (конечно, можно использовать deflate-сжатие вместо LZ4, но это увеличит потребление CPU, что может негативно сказаться на производительности всего кластера).

ClickHouse

Плюсы данной базы:

  • отличная производительность на запись;

  • хорошее сжатие данных, особенно длинных строк;

  • MySQL-совместимый синтаксис запросов, что избавляет от необходимости учить новый язык запросов, как в случае с Elasticsearch;

  • горизонтальное масштабирование и репликация из коробки, хоть это и требует больше усилий по сравнению с Elasticsearch.

Но на начало 2018 года в ClickHouse отсутствовала часть необходимых нам функций:

  • Поддержка DELETE по условию (мы планировали хранить данные за полгода, поэтому нам была необходима возможность удаления устаревших данных; в ClickHouse удаление данных по произвольному критерию предусмотрено не было, а партиционирование по произвольному полю (в нашем случае по дате) на тот момент находилось на стадии экспериментальной фичи и не рекомендовалось к использованию в продакшене);

  • поддержка UPDATE по условию: ClickHouse заточена под неизменяемые данные, поэтому реализация обновления произвольных записей задача не из лёгких (этот вопрос не раз поднимался на GitHub и в конце 2018 года функцию всё же реализовали, но она не подходит для частых обновлений);

  • полнотекстовый поиск (была опция поиска по RegExp, но он требует сканирования всей таблицы (full scan), а это довольно медленная операция).

На самом деле, все упомянутые ограничения можно было обойти (и в Сети даже было несколько статей на эту тему (например)). Но мы хотели реализовать прототип нашей системы с минимальными затратами, и для этого нам требовалась более гибкая база данных, поэтому мы остановили свой выбор на Elasticsearch.

Конечно, в плане производительности на запись Elasticsearch уступает ClickHouse, но для нас это было не критично. Куда важнее были поддержка нужной нам функциональности и масштабируемость из коробки. Сыграло свою роль и то, что у нас уже был кластер Elasticsearch, который мы использовали для сбора логов от демонов, отпадала необходимость в поднятии инфраструктуры.

Модель данных

Теперь немного поговорим о том, как мы храним события.

Все данные у нас разбиты на несколько групп и хранятся в отдельных индексах:

  • мета-информация;

  • сырые события.

Данные изолированы в рамках конкретного приложения (отдельный индекс) это позволяет кастомизировать настройки индекса в зависимости от нагрузки. Например, для непопулярных приложений можно хранить данные на warm-нодах в кластере (мы используем hot-warm-cold-архитектуру).

Для того чтобы в одной системе можно было хранить и JS-ошибки, и краш-репорты нативных приложений, мы вынесли на верхний уровень всё, что используется для формирования общей статистики (время ошибки, в каком релизе она произошла, информация о пользователе, ключ группировки). А то, что уникально для каждого типа ошибки, хранится во вложенном поле attributes со своим маппингом.

Сама идея была позаимствована из Sentry и незначительно доработана в процессе эксплуатации. В Sentry у события есть базовые поля, есть поле tags для данных, по которым нужна возможность поиска, и поле extra для всех остальных специфических данных.

Обработка данных

Вот мы и подошли к, на мой взгляд, самому интересному в разработке системы сбора клиентских ошибок обработке данных. Без неё информация, которую мы собрали на предыдущем этапе, будет бесполезна и мы не сможем получить ничего, кроме сигнала, что в нашем приложении что-то пошло не так. А ведь наша цель иметь возможность максимально быстро отслеживать и фиксить наиболее критичные ошибки.

Начнём со случая попроще.

Обработка краш-репортов из Android-приложений

Чтобы максимально уменьшить размер приложения, в Android-мире принято при сборке билда использовать специальные утилиты, которые:

  • удаляют весь неиспользуемый код (code shrinking сокращение);

  • оптимизируют всё, что осталось после первого этапа (optimization оптимизация);

  • переименовывают классы, методы и поля в соответствии со специальным форматом, что позволяет уменьшить размер кодовой базы, а также усложнить процесс реверс-инжиниринга приложения (obfuscation обфускация).

Подробнее про это можно узнать из официальной документации.

Сегодня есть несколько популярных утилит:

  • ProGuard (бесплатная версия);

  • DexGuard на базе ProGuard (платная версия с расширенным функционалом);

  • R8 от Google.

Если приложение собрано с режимом обфускации, то стек-трейс будет выглядеть примерно так:

o.imc: Error loading resources: Security check requiredat o.mef.b(:77)at o.mef.e(:23)at o.mef$a.d(:61)at o.mef$a.invoke(:23)at o.jij$c.a(:42)at o.jij$c.apply(Unknown Source:0)at o.wgv$c.a_(:81)at o.whb$e.a_(:64)at o.wgs$b$a.a_(:111)at o.wgy$b.run(:81)at o.vxu$e.run(:109)at android.os.Handler.handleCallback(Handler.java:790)at android.os.Handler.dispatchMessage(Handler.java:99)at android.os.Looper.loop(Looper.java:164)at android.app.ActivityThread.main(ActivityThread.java:6626)at java.lang.reflect.Method.invoke(Native Method)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:811)

Из него мало что можно понять, за исключением сообщения об ошибке. Чтобы извлечь полезную информацию из такого стек-трейса, его нужно расшифровать. Процесс дешифрования обфусцированных классов и методов называется деобфускацией, для этого необходим специальный файл mapping.txt, который генерируется в момент сборки билда. Вот фрагмент такого файла:

AllGoalsDialogFragment -> o.a:    java.util.LinkedHashMap goals -> c    kotlin.jvm.functions.Function1 onGoalSelected -> e    java.lang.String selectedId -> d    AllGoalsDialogFragment$Companion Companion -> a    54:73:android.view.View onCreateView(android.view.LayoutInflater,android.view.ViewGroup,android.os.Bundle) -> onCreateView    76:76:int getTheme() -> getTheme    79:85:android.app.Dialog onCreateDialog(android.os.Bundle) -> onCreateDialog    93:97:void onDestroyView() -> onDestroyView

Следовательно, нам нужен сервис, которому мы могли бы скормить обфусцированный стек трейс и mapping-файл и на выходе получить оригинальный стек-трейс.

Нам не удалось найти подходящих готовых решений в открытом доступе (может, конечно, плохо искали), но, к счастью, инженеры ProGuard (а мы используем DexGuard для обфускации) позаботились о разработчиках и выложили в открытый доступ утилиту ReTrace, которая реализует весь необходимый функционал для деобфускации.

На её базе наши Android-разработчики написали простенький сервис на Kotlin, который:

  • принимает на вход стек-трейс и версию приложения;

  • скачивает необходимый mapping-файл из Ceph (маппинги заливаются автоматически при сборке релиза в TeamCity);

  • деобфусцирует стек-трейс.

Обработка краш-репортов из iOS-приложений

Краш-репорты из iOS-приложения содержат в себе довольно много полезной информации, включая стек-трейсы всех запущенных в момент падения тредов (подробнее про формат краш-репортов можно почитать тут и тут). Но есть одна загвоздка: стек-трейсы содержат лишь информацию об адресах памяти, по которым располагаются классы и методы.

Thread 0:0   libsystem_kernel.dylib              0x00000001bf3468b8 0x1bf321000 + 1537841   libobjc.A.dylib                     0x00000001bf289de0 0x1bf270000 + 1059522   Badoo                               0x0000000105c9c6f4 0x1047ec000 + 216941963   Badoo                               0x000000010657660c 0x1047ec000 + 309755004   Badoo                               0x0000000106524e04 0x1047ec000 + 306416685   Badoo                               0x000000010652b0f8 0x1047ec000 + 306670006   Badoo                               0x0000000105dce27c 0x1047ec000 + 229464287   Badoo                               0x0000000105dce3b4 0x1047ec000 + 229467408   Badoo                               0x0000000104d41340 0x1047ec000 + 5591872

Процесс сопоставления адреса памяти с именем функции называется символикацией. Для того чтобы символицировать краш-репорт, необходимы специальные архивы с дебаг-символами (dSYM), которые генерируются в момент сборки билда, и софт, который с этими архивами умеет работать.

Что можно использовать для символикации?

  • Можно написать свой сервис на базе консольных утилит, но, скорее всего, он подойдёт только для ручной символикации и будет доступен только на macOS. У нас же, например, всё работает на Linux.

  • Можно взять сервис Symbolicator от Sentry, который недавно был выложен в открытый доступ (рекомендую почитать статью о том, как он разрабатывался). Мы какое-то время экспериментировали с ним и пришли к выводу, что as is этот сервис будет сложно интегрировать в нашу схему: пришлось бы допиливать его под наши нужды, а опыта использования Rust у нас нет.

  • Можно написать свой сервис на базе библиотеки Symbolic от Sentry, которая, хоть и написана на Rust, но предоставляет C-ABI её можно использовать в языке с поддержкой FFI.

Мы остановили свой выбор на последнем варианте и написали сервис на Golang с учётом всех наших нюансов, который под капотом обращается к Symbolic через cgo.

Группировка ошибок

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

Человек, не знакомый с тем, как устроены системы обработки ошибок, может подумать, что в них используются какие-то сложные алгоритмы для определения схожести строк. Но в действительности все популярные системы используют группировку по ключу (fingerprint), потому что это просто в реализации и покрывает большинство кейсов. В самом элементарном случае это может быть хеш от текста ошибки и стек-трейса. Но это подходит не для всех типов ошибок, поэтому некоторые системы позволяют явно указывать, какие ключи вы хотите использовать для подсчёта ключа группировки (либо же вы можете передать ключ явно).

Мы решили не усложнять свою систему и остановились на группировке по хешу:

  • JS-ошибки группируются по полям message, type и origin;

  • Android краш-репорты группируются по первым трём строчкам стек-трейсов (плюс немного магии);

  • iOS краш-репорты группируются по первому несистемному фрейму из упавшего треда (тред, который отмечен как crashed в краш-репорте).

В качестве заключения

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

Однако если сегодня перед вами стоит задача начать собирать и обрабатывать клиентские ошибки, то я рекомендую присмотреться к Sentry, так как этот сервис активно развивается и является одним из лидеров рынка.

Ну а если вы решите последовать нашему примеру и станете разрабатывать собственную систему, то в этой статье я постарался максимально раскрыть основные моменты, на которые следует обратить внимание.

Если резюмировать, то:

  • храните как можно больше данных: это может сэкономить вам драгоценные часы и нервы, которые в противном случае придётся потратить на дебаг;

  • будьте готовы к резкому росту количества ошибок;

  • чем больше аналитики вы построите, тем лучше;

  • символикация iOS краш-репортов это сложно, присмотритесь к Symbolicator;

  • предусмотрите систему уведомления о новых ошибках: это даст вам больше контроля и возможность своевременно реагировать на резкий рост ошибок;

  • запаситесь терпением и приготовьтесь к увлекательному путешествию в мир разработки систем сбора ошибок.

Подробнее..

Из песочницы ObjectManager в Magento 2

10.10.2020 14:23:52 | Автор: admin

ObjectManager можно назвать одной из основных концепций, которая лежит в основе построения Magento2, и абсолютно новый если брать в сравнении с Magento1. Если мы вспомним Magento1, то там, для создания нужных нам для работы объектов, мы использовали класс Mage, который предоставлял статические методы для создания разных типов объектов будь то модели, ресурс-модели, хелперы, или для создания объектов, которые мы хотели иметь в едином экземпляре(метод Mage::getSingleton). При создании Magento2 команда разработчиков отказалась от этой идеи и имплементировали принцип инъекции зависимостей и сервис-контрактов(ServiceContracts). Именно это позволило сделать Magento2 такой гибкой, легко кастомизируемой и тестируемой. Так же наличие функционала построенного вокруг ObjectManagerа делает возможным наличие всего функционала кастомизации поведения системы, который мы можем настраивать посредством конфигурационного файла di.xml.


Если смотреть глобально на функционал, который реализует ObjectManager, то можно сказать, что он является некой реализацией DI container, который в мире PHP представлен в виде PSR-11, хотя сам ObjectManager напрямую не реализует Psr\Container\ContainerInterface (и не имеет метода has, наличие которого предполагает Psr\Container\ContainerInterface). Он является централизованным средством для создания и получения объектов. Наличие такого централизованного класса для генерации необходимых объектов несет в себе следующие преимущества.


  • Нам не нужно в ручную инициализировать и менеджить объекты (так же нужно сказать, что ObjectManager используется для генерации объектов внутри классов Factory и Proxy, которые создаются посредством кодогенерации)
  • является возможным через настройки прописать какую именно реализацию некоторого интерфейса должен получать класс и использовать принцип инверсии зависимостей
  • систему становится легче тестировать
  • возможно использовать Proxy-классы и классы фабрики (Factory)
  • Экономия ресурсов сервера, так как некоторые из объектов повторно не инициализируются, а берутся из кэша уже созданные до этого объекты (настройка shared)

Если более детально рассмотреть последний пункт, то нужно сказать, что сам класс Magento\Framework\ObjectManager\ObjectManager имеет protected атрибут $_sharedInstances = []. Именно этот атрибут содержит объекты, которые не должны быть созданы более 1 раза и при запросе на их получение просто берет их из этого массива (кэша) по ключу ключом является полное имя класса, которое включает пространство имен(namespace). Но как именно класс ObjectManager знает какие классы должны быть помещены в этот массив?

Все объекты, которые создаются через ObjectManager по-умолчанию имеют настройку shared=true. Но это поведение можно поменять для этого служит специальная настройка shared в xml-файле. Для примера можно взять объявление классов слушателей (observer).



<observer name="legacy_model_save"          instance="Magento\Framework\EntityManager\Observer\BeforeEntitySave" shared="false"/>

Такой класс будет инстанциироваться каждый раз как новый объект. Также такая настройка есть у объектов, которые передаются в конструктор через конструкцию type. Как было сказано выше, этот параметр равен true по-умолчанию, чтобы экономить ресурсы сервера.


Сейчас хотелось бы сфокусироваться на процессе инициализации класса ObjectManager и том как он инстанциирует объекты. В этом месте нужно сделать оговорку, что ниже в статье будет рассматриваться процесс применимо к приложению типа Http.


Если посмотреть на файл index.php, то мы можем увидеть, что в нем используется класс Magento\Framework\App\Bootstrap
И сначала вызывается его статический метод create, который принимает 2 параметра:


  • адрес корневой директории проекта
  • массив параметров $_SERVER + настройки адресов директорий

Внутри метода create вызывается статический метод createObjectManagerFactory, который инстанциирует объект класса Magento\Framework\App\ObjectManagerFactory и передает его в конструктор класса Bootstrap, где он используется, чтобы создать объект класса Magento\Framework\App\ObjectManager. Именно этот объект и используется в последствии, чтобы инстанциировать все далее используемые объекты. Далее в методе Magento\Framework\App\Http::launch объекту класса ObjectManager задается конфигурация через вызов метода Magento\Framework\App\ObjectManager::configure. Параметром этого метода является уже подготовленный для использования массив настроек с файлов di.xml. Именно благодаря этим настройкам ObjectManager реализует работу таких возможностей как preference, type, virtualType. Сами эти настройки не хранятся в классе ObjectManager он делегирует их использование классу Magento\Framework\ObjectManager\Config\Config, который реализует Magento\Framework\ObjectManager\ConfigInterface. Этот класс обрабатывает массив настроек с файлов di.xml и объединяет их по типам: preference, type, virtualType и потом позволяет получать их в подготовленном виде. Так же именно этот класс хранит карту preferenceов и отдает ObjectManagerу правильное имя объекта для создания.


Далее в работе приложения используются 2 метода класса ObjectManager: create и get. Разница этих методов состоит в том, что метод create всегда создает новый объект переданного класса (можно сказать, что это реализация паттерна Factory method), а метод get при повторном запросе объекта класса, который уже создавался ранее, просто отдаст его из кэша.


Если рассмотреть детальнее эти методы то они оба создают объекты через специальную фабрику (класс, который имплементирует интерфейс Magento\Framework\ObjectManager\FactoryInterface), которая в зависимости от режима работы приложения может быть представлена классами, которые находятся в пространстве имен Magento\Framework\ObjectManager\Factory. Такая фабрика получает через механизм рефлексии php аргументы конструктора необходимого класса и рекурсивно их инициализирует. Потом эти подготовленные аргументы передаются в метод Magento\Framework\ObjectManager\Factory\AbstractFactory::createObject, который и возвращает готовый к использованию объект. Далее ObjectManager может или просто вернуть такой объект или сохранить его в кэше для дальнейшего использования, в зависимости от настроек, как было указано выше.


Так же следует обратить внимание на пространство имен Magento\Framework\ObjectManager\Code\Generator. Классы, которые находятся в нем, используются для того, чтобы сгенерировать Factory и Proxy классы, которые передаются в пользовательские классы как аргументы конструктора (Proxy должен задаваться как аргумент конструктора через файл di.xml) и создаются через механизм кодогенерации мадженты. Proxy-классы, как очевидно из названия, используются для того, чтобы уменьшить нагрузку на сервер путем замены оригинального класса на его proxy-класс, который инициализирует оригинальный класс только в том случае, если он вызывается (реализация паттерн Proxy). Классы фабрики используются для создания объектов внутри кастомного кода, обычно это объекты классов доменных моделей. Сами классы Factory реализуют паттерн factory method, имея публичный метод create, который внутри себя вызывает ObjectManager и инстанциирует объекты посредством использования метода Magento\Framework\App\ObjectManager::create.

Подробнее..
Категории: Php , Magento , Magento 2

Recovery mode Task framework

16.10.2020 22:15:04 | Автор: admin

О фреймворке


Task framework основан на MVC парадигме с удобством использования и минимум функционала для решения простых задач.

В отличие от стандартных решений вместо контроллера тут используется задача (task)



ссылка на фреймворк который так же использует task

jsock-framework-tutorial.blogspot.com

java-framework-jsocket.blogspot.com
github.com/nnpa/jsock

Установка фреймворка task



1. Скачайте архив с фреймворком

2. Распакуйте в папку task в директорию где у вас хранятся сайты.

3. Скачайте каркас для приложения

5. Распакуйте в папку site в в директорию где у вас хранятся сайты.

6. Создайте базу данных в mysql.

7. Скачайте таблицу users и экспортируйте в созданную базу данных.

Должно получится такое дерево каталогов

/webroot/task

/webroot/site

8. Настройте веб сервер что бы корневая папка /webroot/site была привязана к определенному хосту при помощи веб сервера который вы используете.

9. Зайдите в папку config и откройте config.php и отредактируйте массив подключения к базе данных на ваши значения подключения и переменную host.

MVC парадигма


Task framework использует MVC парадигму для лучшего разделения логики шаблонов представлений, моделей и контроллера.

Вместо контроллера в Task framework используются задачи Task задачи расположены в папке tasks и предназначены для выполнения логики приложения.

Модели хранятся в папке models и предназначены для работы с логикой базы данных.

Представления хранятся в папке view и предназначены для работы с логикой представления.

Task


Task (или Controller) располагаются в папке tasks.

Task создаются по переменной в url сайта request:

Если переменная request = test то будет создан экземпляр класса Task который хранится в папке tasks в файле test.php и называется test.

index.php?request=test

Пример класса test.php:
include_once('WebTask.php');class Test extends WebTask{public function run(){          //логика приложения        }}


Обязательно task должен быть унаследован от WebTask и в нем должен быть создан метод run()

Models


Models располагаются в папке models и отвечают за логику работы с базой данных.

Модели привычнее всего создавать в tasks.

Модель должна быть создана в папке models и быть унаследована от Model так же должно быть прописано поле $table_name.

Пример класса models/users.php:

class Users extends Model{    public $table_name = 'users';}


В классе Model заранее реализован набор методов для работы с базой данных.

findBySql

$users = new Users(); $users->findBySql("SELECT * FROM `users`");foreach($users as $user) {      echo $user['email'] . "<br>";}

findByPk

$users = new Users(); $users->findByPk(3); echo $users->email;


find

$users = new Users(); $users->find("email <> ''");foreach($users as $user) {     echo $user['email'] . "<br>";}


update

$users = new Users(); $users->findByPk(3);$users->email = "yandex@mail.ru";$users->update();


save

$users = new Users();$users->email = "yandex@mail.ru";$users->id    = NULL;$users->save()
;

delete

$users = new Users();$users->delete("id = 6");

exec

$users = new Users();$users->exec("free sql string"); //mysqli_result

DB

App::$DB->exec("free sql string");//mysqli_result


view


Шаблоны представлений хранятся в папке /view/ отвечают за логику представлений.

Представление вызывается в конце метода run класса task при помощи метода render.

В представление передаются переменные которые будут использованы в логике представления.

Пример site task:

include_once('WebTask.php');class Site extends WebTask{public function run(){              $users = new Users();               $users->find("email <> ''");       $this->render('site',['users' => $users,]);}}


В методе run модель с пользователями передается в шаблон view/site.php где происходит обработка результатов поиска и генерация html:

<?phpforeach($users as $user) {    echo $user['email'] . "<br>";}?>


Так же в папке view/layout расположен основной шаблон main.php который является главным шаблоном куда в переменную {content} подгружаются наши представления.

Авторизация пользователя


В фреймворке уже реализована регистрация и авторизация по ссылкам login и register.

Метод приложения который позволяет проверять являет ли пользователь авторизованным App::isGuest()

В завершении
task-framework blog

Фреймворк будет дальше разрабатывать и тестироваться на разрабатываемых на нем приложениях.

Cпасибо.
Подробнее..
Категории: Php , Php framework

Подключение автоплатежей через TeleWalletAbot к своему Telegram-боту

20.10.2020 20:09:53 | Автор: admin
Доброго времени суток.
Сегодня хочу рассказать о том, как работать с платежным API не так давно появившегося на просторах Telegram кошелька TeleWallet Статья будет интересна в первую очередь владельцам и разработчикам ботов Telegram, поскольку эта платежная система позволяет принимать платежи в Телеграме, не покидая Telegram

Хочу сразу отметить, что я не выступаю за или против какой-либо платежной системы, а хочу лишь описать, как работать с API.

Преимущества и недостатки


Основные преимущества (на мой взгляд) приема платежей через TeleWallet:
  • Низкая комиссия (0.8% суммарно за автопополнение и автовыплату)
  • Отсутствие минимальной суммы (точнее, минимальная сумма составляет 0.01)
  • Доступность нескольких валют (фиатных и крипто)
  • Возможность подключения неограниченного количества проектов на один профиль


Основные недостатки:
  • Слабая распространенность ресурса (хотя это скорее всего исправимо)
  • Отсутствие английского интерфейса (кошелек рассчитан на русскоязычную аудиторию и соответственно распространенные в СНГ платежные системы)
  • Недостаточно автоматизированные переводы средств в сам кошелек (на данный момент доступно автоматическое пополнение кошелька только через Яндек.Деньги и Visa/MasterCard. Для других платежных систем доступно полуавтоматическое пополнение, из-за чего присутствует время ожидания)


Подключение автоплатежей


Итак приступим. Для начала набросаем небольшого тестового бота на PHP c 3-мя кнопками:
  • Баланс чтоб просматривать баланс и видеть, что он изменился
  • Пополнить
  • Вывести

Про регистрацию бота в BotFather рассказывать не буду: слишком уж много сказано до меня на эту тему. Для работы с Telegram-bot-api будем использовать irazasyed/telegram-bot-sdk. Как работать с этим SDK, и кстати как зарегистрировать бота в BotFather и установить webhook на него неплохо описано здесь.
И конечно для работы с TeleWallet API будем использовать их официальный SDK. Там же у них есть подробная инструкция, как работать с платежами и есть примеры кода. Так что ниже я просто покажу как совместить приведенные там примеры кода с реальным ботом.

Создаем платежный счёт



  1. Перейдите в бота t.me/TeleWalletAbot
  2. Запустите его
  3. Нажмите кнопку Прием платежей в главном меню
  4. Под появившимся сообщением нажмите кнопку Открыть платежный счёт
  5. Выберите валюту счёта
  6. Появится сообщение Счёт успешно создан

image
Шаги, начиная с 3-го, показаны на картинке. Только что добавленный счет вы увидите последним в списке ваших платежных счетов (7 на картинке). Нажмите на команду-ссылку напротив него, чтоб перейти к его настройкам (8 на картинке). Сообщение, которое мы получаем в ответ, выглядит вот так:
Настройка счёта ap110741100
Баланс: 0 RUB

Название магазина: Не задано
API ключ: eHW2IQZQYjlJjgQ
URL для уведомлений: Не задан
URL перехода после успешного платежа: Не задан
URL перехода после неудачи(отказа): Не задан
Плательщик комиссии при выплате: Магазин
Удалить счёт (/delapsch_100Re6)

С помощью кнопок под этим сообщением отредактируйте необходимые параметры


Создаем файл настроек


Создадим файл config.php и вставим туда следующий код
<?php  $dblocation = "localhost";  $dbname = "имя базы данных mysql";    $dbuser = "пользователь базы данных mysql";  $dbpasswd = "пароль к бд mysql";  /* Подключение к серверу MySQL */   $link = mysqli_connect($dblocation,$dbuser,$dbpasswd,$dbname);         if(!$link)  exit("<P>Невозможно установить соединение с сервером БД.</P>" );   mysqli_query($link,"SET NAMES 'utf8'");  //апи-ключ и счёт TeleWalletAbot  $tlwkey = "eHW2IQZQYjlJjgQ";  $tlwacc = "ap110741100";?>

Здесь мы подключим базу данных mysql (она нам понадобиться, чтоб хранить балансы пользователей и информацию о платежах) и заведем 2 переменные $tlwkey и $tlwacc для хранения API ключа и номера счёта из сообщения, полученного от бота.
В нашей базе данных создадим 2 таблицы:
1) users(id,name,balance,outnumber) будем хранить данные о пользователях
2) donate(id,user_id,sum,finished) информация о пополнениях

Далее создадим файл index.php (на него будем направлять наш webhook от Telegram) и вставим туда следующий код:
<?php      include('vendor/autoload.php');  //подключение библиотек, загруженных через composer    //классы для работы с Telegram bot api    use Telegram\Bot\Api;     use Telegram\Bot\Commands\Command;    use Telegram\Bot\Keyboard\Keyboard;    // -----------------------------------------    require_once "commands.php"; //скрипт, где мы реализуем основную логику    require_once "config.php"; //файл настроек    require_once "TeleWallet.php"; /*SDK Telewallet  https://github.com/tlwadmin/TeleWalletSDK/blob/main/TeleWallet.php  */    $telegram = new Api("токен, полученный от Telegram");    $result = $telegram -> getWebhookUpdates(); //получаем обновления    $chat_id = $result["message"]["chat"]["id"];    $text = $result["message"]["text"];    $callback_query = $result['callback_query'];        $data = $callback_query['data'];     $chat_id_in = $callback_query['message']['chat']['id'];     $uname = $result["message"]["from"]["username"]; if($chat_id>0 && $text){ //блок обработки основных команд$sm=['chat_id' => $chat_id, 'text' => $text];$ans_arr=getAnsw($text,$chat_id,$uname);    for($i=0;$i<count($ans_arr);$i++){$ans=$ans_arr[$i];$reply = $ans['text'];$sm=[ 'chat_id' => $chat_id, 'text' => $reply, 'caption'=>$reply];if(array_key_exists('inline_keyboard',$ans)) {$keyboard=$ans['inline_keyboard'];$replyMarkup = json_encode($keyboard);    $sm['reply_markup'] =$replyMarkup;}       else if(array_key_exists('keyboard',$ans)){$keyboard=$ans['keyboard'];$reply_markup = $telegram->replyKeyboardMarkup([ 'keyboard' => $keyboard, 'resize_keyboard' => true, 'one_time_keyboard' => false ]);$sm['reply_markup']=$reply_markup;}$telegram->sendMessage($sm);}            }    if($data){  //блок обработки инлайн-команд$ans_arr=getAnsw($data,$chat_id_in);for($i=0;$i<count($ans_arr);$i++){$ans=$ans_arr[$i];$reply = $ans['text'];$sm=[ 'chat_id' => $chat_id_in, 'text' => $reply, 'caption'=>$reply];if(array_key_exists('inline_keyboard',$ans)) {$keyboard=$ans['inline_keyboard'];$replyMarkup = json_encode($keyboard);    $sm['reply_markup'] =$replyMarkup;}       else if(array_key_exists('keyboard',$ans)){$keyboard=$ans['keyboard'];$reply_markup = $telegram->replyKeyboardMarkup([ 'keyboard' => $keyboard, 'resize_keyboard' => true, 'one_time_keyboard' => false ]);$sm['reply_markup']=$reply_markup;}$telegram->sendMessage($sm);}}    ?>


Здесь мы определяем, какое сообщение получено от пользователя. Отдаем его, а также идентификатор пользователя в функцию getAnsw(). Она возвращает массив сообщений, которые мы пересылаем пользователю.
Теперь создадим файл commands.php и вставим в него реализацию функции getAnsw()
<?phpfunction getAnsw($command,$chat_id, $name=""){global $link;global $telegram;global  $tlwkey;    global$tlwacc;$r=mysqli_query($link,"select * from users where id='$chat_id'");$ud=mysqli_fetch_assoc($r); //данные о пользователеif($command=="/start") {//добавим в бд если нетif(!$ud) mysqli_query($link,"INSERT INTO `users`(`id`,`name`) values('$chat_id','$name')");$res['text']="Привет. Я бот, позволяющий протестировать пополнение и выплату через TeleWallet";$res['keyboard']=[["Баланс","Пополнить","Выплата"]];return [$res];}if($command=="Баланс") {$res['text']="Ваш баланс: {$ud['balance']} руб";return [$res];}if($command=="Пополнить") {$res['text']="Выберите сумму, на которую хотите пополнить счёт";$res['inline_keyboard']['inline_keyboard']=[[['text'=>'1 руб','callback_data'=>'popoln_1'],['text'=>'5 руб','callback_data'=>'popoln_5'],['text'=>'10 руб','callback_data'=>'popoln_10']]];return [$res];}if($command=="Выплата") {$res['text']="Сколько вы хотите вывести?";$res['inline_keyboard']['inline_keyboard']=[[['text'=>'1 руб','callback_data'=>'vivod_1'],['text'=>'5 руб','callback_data'=>'vivod_5'],['text'=>'10 руб','callback_data'=>'vivod_10']]];addCmd("vivod_",$chat_id);return [$res];}$tlw = new TeleWallet($tlwkey,$tlwacc);if(strpos($command,'popoln_')!==false) {$arr = explode("_",$command);mysqli_query($link,"INSERT INTO `donate`( `user_id`, `sum`) values('$chat_id','{$arr[1]}')");$payId = mysqli_insert_id($link);$resp = $tlw->getСheque($arr[1],$payId);$res['text']="Вы выбрали пополнение на {$arr[1]} руб. Пополнение доступно через TeleWallet. Для продолжения нажмите кнопку под этим сообщением";$res['inline_keyboard']['inline_keyboard']=[[["text"=>"Пополнить","url"=>$resp['url']]]];return [$res];}       if(strpos($command,'setnumber_')!==false) { //пользователь задает номер счета$arr = explode("_",$command);mysqli_query($link,"UPDATE `users` SET `outnumber`='{$arr[1]}' where `id`='$chat_id'");$res['text']="Номер счёта обновлен.";return [$res];}if(strpos($command,'vivod_')!==false) {$arr = explode("_",$command);if($ud['balance']<$arr[1]) $res['text']="недостаточно средств на балансе";else {if(empty($ud['outnumber']))$res['text']="У вас не задан номер счета TeleWallet для вывода. Отправьте боту setnumber_(номер ваше счета) (без скобок), чтоб задать номер счёта";else {$resp = $tlw->sendOutpay($arr[1],$ud['outnumber']);if($resp['error']==0) { //выплата удалась$res['text']="На указанный Вами номер счета выведено {$arr[1]} руб";mysqli_query($link,"UPDATE `users` SET `balance`=`balance`-{$arr[1]} where `id`='$chat_id'");}else if($resp['error']==4 || $resp['error']==5) { //выплата удалась$res['text']="Вы указали неправильный номер счёта";}else $res['text']="Выплата не удалась. Код ошибки: {$resp['error']}. Обратитесь к администратору проекта";}}return [$res];}}?>

Когда пользователь нажимает Пополнить и выбирает сумму с помощью инлайн-кнопок под сообщением, создается платежная ссылка в строке
$resp = $tlw->getСheque($arr[1],$payId);

Функция getСheque вернет ассоциативный массив с параметрами error и url. error должно быть 0, и это желательно тоже проверять, но я для краткости опустил эту проверку. Параметр url мы используем, чтоб сформировать сообщение с инлайн-кнопкой, при нажатии на которую пользователь нашего бота попадет в @TeleWalletAbot и совершит оплату (или не совершит). Проверка факта оплаты описана ниже.
Когда пользователь заказывает вывод, у него должен быть задан номер счета для вывода. Если это не так, мы сообщаем ему об этом. Если счет задан, мы пытаемся выполнить вывод, используя функцию sendOutpay, и если возвращенный ею код ошибки 0 значит вывод прошел успешно, и мы списываем средства с баланса пользователя в нашем боте.

Проверка факта оплаты


Для проверки факта оплаты и зачисления средств на баланс пользователя создадим еще один скрипт: notice.php. Вот его код:
<?phpinclude('vendor/autoload.php'); use Telegram\Bot\Api; use Telegram\Bot\Commands\Command;use Telegram\Bot\Keyboard\Keyboard;    require_once "config.php";require_once "TeleWallet.php";$telegram = new Api("ключ апи вашего бота, полученный от ботфазер");$tlw = new TeleWallet($tlwkey,$tlwacc);$ri = mysqli_query($link,"SELECT * FROM `donate` WHERE `id`={$_POST['payId']}");$pay_info = mysqli_fetch_assoc($ri);if($tlw->testPayIn($_POST) && $pay_info['sum']==$_POST['sum']) {echo "YES";mysqli_query($link,"UPDATE `users` SET `balance`=`balance`+{$pay_info['sum']} where `id`={$pay_info['user_id']}");       mysqli_query($link,"UPDATE `users` SET `balance`=`balance`+{$pay_info['sum']} where `id`={$_POST['payId']}");try {$telegram->sendMessage(["text"=>"Ваш баланс пополнен на {$pay_info['sum']} руб","chat_id"=>$pay_info['user_id']]);}catch(Exception $e) {}}else echo "NO";?>

На этот файл будет прилетать вебхук от кошелька, когда пользователь успешно завершит оплату. Описание параметров POST-запроса смотрите в документации к SDK github.com/tlwadmin/TeleWalletSDK

Давайте вернемся теперь к нашему платежному счёту в кошельке. Нажмем кнопку URL для уведомлений и отправим боту ссылку на наш файл notice.php
Также укажите название магазина (точнее вашего проекта). В качестве URL успешно и URL fail просто укажите ссылку на ваш проект (бот)

Ну вот пожалуй и всё. Мы создали с вами тестового бота, который позволяет понять, как работать с платежным API TeleWallet

Посмотреть, как работает этот тестовый пример (там правда еще добавлен ручной ввод суммы и счёта при выводе) можно по ссылке: http://t.me/TlwSdkBot

Для лучшего понимания кода в статье, а также, чтоб узнать описание кодов ошибок и параметры запросов, смотрите документацию github.com/tlwadmin/TeleWalletSDK
Подробнее..

Перевод Composer 2 Что нового?

24.10.2020 22:20:03 | Автор: admin

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

Ускорение и оптимизация

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

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

Одной из лучших функций, для меня, стала параллельная загрузка, которая теперь поддерживается из коробки, и нам не нужно устанавливать дополнительные пакеты, как hirak/prestissimo, чтобы это работало.

Composer v2 почти в 2 раза быстрее, при установке laravel/laravel без кэширования.Composer v2 почти в 2 раза быстрее, при установке laravel/laravel без кэширования.

Поддержка оффлайн

Composer представил возможность использовать его в оффлайн режиме. Это может быть интересно для бенчмарков или при возникновении проблем с подключением к интернету, поэтому вам не нужно удалять Composer install/update из списка команд.

Чтобы это заработало, нужно добавить COMPOSER_DISABLE_NETWORK=1 при выполнении команд:

COMPOSER_DISABLE_NETWORK=1 composer install

или установите его в переменных среды. Он выдаст предупреждение о том, что сеть отключена.:

https://repo.packagist.org could not be fully loaded (Network disabled, request canceled: https://repo.packagist.org/packages.json), package information was loaded from the local cache and may be out of date

кроме того, вы увидите сообщение для пакетов, без кэширования:

The required git reference for gabrielanhaia/laravel-circuit-breaker is not in cache and network is disabled, aborting

Поддержка --dry-run для require и remove

Эта опция уже была доступна при обновлении пакетов (composer update --dry-run. Она позволяет нам увидеть, что произойдет при запуске команды, просто отображая данные в терминале, без реальных изменений в вашем проекте или папке vendor.

Composer 2.* дает возможность использовать опцию с composer require и composer remove что делает нашу жизнь проще

Предотвращение проблем при работе от root

При выполнении команд от пользователя root теперь требуется подтверждение, чтобы предотвратить наши ошибки.

В предыдущих версиях, когда вы пытались выполнить команду, она вызывала следующее сообщение:

Do not run Composer as root/super user! See https://getcomposer.org/root for details

С Composer 2.* вы должны подтвердить выполнение:

https://getcomposer.org/root for detailsContinue as root/super user [yes]?

Если интерактивный режим не поддерживается вашим терминалом, то сообщение не покажется. Вы можете обойти это сообщение, с командой --no-interaction

composer install --no-interaction

Канонические репозитории

Эта функция решает определенную проблему, с которой сталкиваются несколько разработчиков при использовании одного и того же пакета в разных репозиториях. Composer 1.0 следовал определенному порядку при установке/обновлении своих пакетов. Он начал искать пакеты в своем списке репозиториев до тех пор, пока пакет не будет найден (также последняя версия).

Иногда это было не совсем ожидаемое поведение при работе с различными версиями пакетов в других репозиториях (используемых одним и тем же проектом). Представьте себе, что иногда вы хотите загрузить пакет из своего частного репозитория, а не из packagist, в котором есть пакет с тем же именем.

По умолчанию, в composer 2.x, все репозитории являются каноническими. Composer 1.x рассматривал все репозитории как неканонические, и для того, чтобы поменять поведение вручную, вы можете сделать:

{    "repositories": [        {            "type": "composer",            "url": "https://example.org",            "canonical": false        }    ]}

Вы также можете отфильтровать пакеты, которые репозиторий сможет загрузить, либо выбрав те, которые вы хотите, либо исключив те, которые вы не хотите.

Например, здесь мы хотим выбрать только пакет foo/barи все пакеты из some-vendor/из определенного composer репозитория.:

{    "repositories": [        {            "type": "composer",            "url": "https://example.org",            "only": ["foo/bar", "some-vendor/*"]        }    ]}

А в этом примере мы исключаем toy/packageиз репозитория, который мы, возможно, не захотим загружать в этот проект.

{    "repositories": [        {            "type": "composer",            "url": "https://example.org",            "exclude": ["toy/package"]        }    ]}

И only, и exclude должны быть массивами с именами пакетов, которые также могут содержать знаки (*), которые соответствуют любым символам.\

Игнорировать определенное требование платформы

Если по какой-то причине вы хотите проигнорировать какое-либо определенное требование платформы, вы можете просто запустить команду: composer install --ignore-platform-req php

Она пропустит требование для PHP или конкретной версии. Если вы собираетесь игнорировать все требования, вам следует использовать команду, уже доступную в предыдущих версиях --ignore-platform-reqs

Другие обновления

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

Подробнее..
Категории: Php , Composer , Composer пакеты

Категории

Последние комментарии

© 2006-2020, personeltest.ru