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

Php

Насколько вам наплевать на фичи последней версии языка?

24.05.2021 00:09:13 | Автор: admin

Многие на собеседованиях любят гонять по последним фичам языка. У меня это всегда вызывало недоумение, во всяком случае в сфере веб-разработки. На фронтенде ты смотришь CanIUse(или сношаешься с полифиллами), а на бэкенде ты смотришь на шаблоны vps/vds, которые предоставляют хостеры и прикидываешь когда же в них появятся нужные тебе версии языка. И я абсолютно не против развертывания среды выполнения нужной версии, которая будет отличаться от системной, но давайте будем честными с самими собой. Какой процент из вас ориентируется на последнюю доступную версию языка, а не на то что будет на в ближайшие пару лет дано в ощущениях, браузерах и датацентрах. Внимание опрос!

Подробнее..
Категории: Javascript , Ruby , Python , Php , Хостинг , Vps , Perl , Vds

Опрос Насколько вам наплевать на фичи последней версии языка?

24.05.2021 02:21:51 | Автор: admin

Многие на собеседованиях любят гонять по последним фичам языка. У меня это всегда вызывало недоумение, во всяком случае в сфере веб-разработки. На фронтенде ты смотришь CanIUse (или сношаешься с полифиллами), а на бэкенде ты смотришь на шаблоны vps/vds, которые предоставляют хостеры и прикидываешь когда же в них появятся нужные тебе версии языка. И я абсолютно не против развертывания среды выполнения нужной версии, которая будет отличаться от системной, но давайте будем честными с самими собой. Какой процент из вас ориентируется на последнюю доступную версию языка, а не на то что будет на в ближайшие пару лет дано в ощущениях, браузерах и датацентрах. Внимание опрос!

Подробнее..
Категории: Javascript , Ruby , Python , Php , Хостинг , Vps , Perl , Vds

Браузерные Push-уведомления на Javascript и PHP

10.06.2021 02:22:28 | Автор: admin

Предисловие

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

В данной статье не будут "размусолены" принципы работы и тонкости Push уведомлений, только код, только хардкор.

Важные замечания

Push-уведомления работают только с HTTPS.
К слову, в добавок с HTTPS должен присутствовать валидный SSL сертификат, подойдет и Let's Encrypt

Для разработки подойдёт localhost. Проблем возникнуть не должно, но если все же возникли данная статья поможет разобраться с ними.

Да будет код

Авторизация (VAPID)

Для начала стоит установить библиотеку WebPush в ваш php проект:

$ composer require minishlink/web-push

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

Чтобы сгенерировать несжатый публичный и приватный ключ, закодированный в Base64, введите следующее в свой Linux bash:

$ openssl ecparam -genkey -name prime256v1 -out private_key.pem$ openssl ec -in private_key.pem -pubout -outform DER|tail -c 65|base64|tr -d '=' |tr '/+' '_-' >> public_key.txt$ openssl ec -in private_key.pem -outform DER|tail -c +8|head -c 32|base64|tr -d '=' |tr '/+' '_-' >> private_key.txt

Так же автор библиотеки предоставляет генерацию vapid ключей с помощью встроенного метода:

$vapidKeysInBase64 = VAPID::createVapidKeys();

Подписка

Этап 1 (JS)

В начале стоит проверить наличие поддержки ServiceWorker, PushManager, а так же showNotification в браузере:

app.js

function checkNotificationSupported() {return new Promise((fulfilled, reject) => {  if (!('serviceWorker' in navigator)) {      reject(new Error('Service workers are not supported by this browser'));      return;    }    if (!('PushManager' in window)) {      reject(new Error('Push notifications are not supported by this browser'));      return;    }    if (!('showNotification' in ServiceWorkerRegistration.prototype)) {      reject(new Error('Notifications are not supported by this browser'));    return;    }        fulfilled();  })}

Создаем файл sw.js и далее регистрируем его:

app.js

navigator.serviceWorker.register('sw.js').then(() => {      console.log('[SW] Service worker has been registered');    }, e => {      console.error('[SW] Service worker registration failed', e);    }  );

Так же нам понадобится функция для проверки состояния подписки:

app.js

function checkNotificationPermission() {    return new Promise((fulfilled, reject) => {        if (Notification.permission === 'denied') {            return reject(new Error('Push messages are blocked.'));        }        if (Notification.permission === 'granted') {            return fulfilled();        }        if (Notification.permission === 'default') {            return Notification.requestPermission().then(result => {                if (result !== 'granted') {                    reject(new Error('Bad permission result'));                } else {                    fulfilled();                }            });        }        return reject(new Error('Unknown permission'));    });}

С сервера нам нужно получить публичный ssh ключ сгенерированный выше:

<script>window.applicationServerKey = '<?= $yourPublicKeyFromServer ?>'</script>

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

app.js

document.addEventListener('DOMContentLoaded', documentLoadHandler);function documentLoadHandler() {    checkNotificationSupported()        .then(() => {          setTimeout(() => {            serviceWorkerRegistration.pushManager.subscribe({                    userVisibleOnly: true,                    applicationServerKey: urlBase64ToUint8Array(window.applicationServerKey),                })                .then(successSubscriptionHandler, errorSubscriptionHandler)          }, 10000);         },         console.error      );}function urlBase64ToUint8Array(base64String) {    const padding = '='.repeat((4 - (base64String.length % 4)) % 4);    const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');    const rawData = window.atob(base64);    const outputArray = new Uint8Array(rawData.length);    for (let i = 0; i < rawData.length; ++i) {        outputArray[i] = rawData.charCodeAt(i);    }    return outputArray;}function errorSubscriptionHandler(err) {    if (Notification.permission === 'denied') {        console.warn('Notifications are denied by the user.');    } else {        console.error('Impossible to subscribe to push notifications', err);    }}

Далее если процесс получения разрешения подписки прошел успешно вызываем функцию successSubscriptionHandler

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

app.js

function successSubscriptionHandler(subscriptionData) {    const key = subscription.getKey('p256dh');    const token = subscription.getKey('auth');    const contentEncoding = (PushManager.supportedContentEncodings || ['aesgcm'])[0];    const body = new FormData();    body.set('endpoint', subscription.endpoint);    body.set('publicKey', key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : null);    body.set('authToken', token ? btoa(String.fromCharCode.apply(null, new Uint8Array(token))) : null);    body.set('contentEncoding', contentEncoding);    return fetch('src/push_subscription.php', {      method,      body,    }).then(() => subscription);  }

Так же нам нужно сформировать отправляемое уведомление

Вы можете манипулировать данными уведомления при помощи Post Message API

self.addEventListener('push', function (event) {    if (!(self.Notification && self.Notification.permission === 'granted')) {        return;    }    const sendNotification = body => {        const title = "Заголовок уведомления";        return self.registration.showNotification(title, {            body,        });    };    if (event.data) {        const message = event.data.text();        event.waitUntil(sendNotification(message));    }});

Этап 2 (PHP)

Необходим php версии 7+

Далее в файле subscribeUserToPushNotifications на который мы сделали запрос с фронта при получении разрешения на подписку, мы формируем данные пользователя

subscribeUserToPushNotifications.php

<?php $subscription = $_POST;if (!isset($subscription['endpoint'])) {    echo 'Error: not a subscription';    return;}// save subscription from => $subscription 

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

Непосредственно сама отправка происходит следующим образом

Достаем юзера с места его сохранения, и далее создаем объект подписчика:

pushNotificationToClient.php

<?php use Minishlink\WebPush\WebPush;use Minishlink\WebPush\Subscription;$subscription = Subscription::create($subscriptionData);

Далее формируем VAPID для авторизации:

pushNotificationToClient.php

<?php $auth = array(    'VAPID' => array(        'subject' => 'https://your-project-domain.com',        'publicKey' => file_get_contents(__DIR__ . '/your_project/keys/public_key.txt'),        'privateKey' => file_get_contents(__DIR__ . '/your_project/keys/private_key.txt'),     ));

После того как сформировали нужные данные, создаем новый объект WebPush:

pushNotificationToClient.php

<?php$webPush = new WebPush($auth);

Ура! Наконец мы можем отправить запрос на отправку Push уведомления

<?php$report = $webPush->sendOneNotification(  $subscription,  "Тело пуш уведомления, оно поступило в тело sw.js");

Важное замечание

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

$webPush->queueNotification

Полезные источники

  1. О технологии push

  2. О WebPush от хабровчанина

  3. Библиотека WebPush

  4. Пример использования от разработчика библиотеки

Подробнее..

Есть будущее у Fullstack-разработчиков?

05.06.2021 16:05:21 | Автор: admin

"Неужели компании хотят так сильно экономить, что готовы терять в качестве и времени?"

Решил поделиться своим опытом, который достаточно тесно связан с Fullstack-разработчиками, в одном стартап (хотя бьются на рынке с 2016 года).

Сразу прокомментирую, что в этой статье нет цели оклеветать или высказать своё негативное мнение о компаниях. Нет! Я опираюсь только на их опыт и на полученные результаты.

Я занимаюсь подбором IT-специалистов и для того, чтобы не быть "тем самым HR, который сливает , а не помогает, своими оценочными тестами и вопросами вне понимания специфики работы", мне приходится изучать гигабайты информации на habr и других ресурсах. И в первых рядах моего несогласия, есть такая профессия - Fullstack-разработчик.

Немного для общего понимания поясню, почему я выбрал подход собирать информацию об IT-специалистах.

С 2018 года я начал вникать в эту сферу более плотно. Освоил основы HTML/CSS, познакомился с Python, PHP, Swift. Естественно эти познания подтолкнули меня сменить компанию из обычной на IT. Мне повезло и меня взяли в достаточно перспективный на мой взгляд стартап (разглашать не буду его название из добрых побуждений). Первые месяца три, я работал на должности специалиста по работе с клиентами, но сейчас понимаю, что на самом деле выполнял функционал Project-manager и одновременно Product Owner. Ну и по совместительству еще продавец и support.

Мне реально нравился проект и я горел его улучшить. Хотите верьте, хотите нет, но в этом проекте я стал хуже спать. Я постоянно думал, что можно улучшить. Я стал предлагать идеи по улучшению своему непосредственному, а тот вообще не про IT и очень скоро я понял, что я ему говорю свои идеи, а он просто не правильно их "продает" генеральному. Я не хочу сказать, что этот руководитель плохой. Наоборот, он достаточно внимательно относился ко всем сотрудникам и реально хотел донести мои идеи генеральному, но просто не владея техническим восприятием, он не мог объяснить так, как это говорил ему я.

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

Генеральный оценил мой подход и что больше радовало, мои предложения. Совсем скоро меня утвердили уже как project-manager, а еще пол года спустя, назначили техническим директором. Но для меня эти статусы были не важны. Я хотел менять и я менял.

И вот мне все же пришлось упереться в стену, которую непроизвольно построили разработчики. Сейчас вы поймете о чем я. Проработав почти год в проекте и коммуницируя с Team Lead, я не знал, сколько в команде разработчиков. Мне пояснили, что проект новый на рынке (хотя я знал точно, что они не уникальны) и что не могут себе позволить разработчиков со стороны. Что есть команда, (как позже я все же выяснил, что она из 5 человек), в которой есть back&front и парочка как раз Fullstack-разработчиков. На них и держался весь проект.

Естественно они сильные специалисты и я и сейчас в шоке, как они все это стойко держали. Да, Scrum (Agile), Jira. Пользовались теми же инструментами, которые хвалил рынок. Но "бэклог" еженедельно рос. Спрос на продукт стремительно набирал обороты и я начал "кричать", пытаясь быть услышанным, что нам срочно нужно расширять команду разработчиков и причем принципиально , среди них не должны быть больше Fullstack. К тому моменту, когда негодование users начинало зашкаливать, из-за постоянных срывов обещанных дедлайнов, я явно видел, что для быстрого роста и своевременного исправления скопившихся багов, нужно брать людей на отдельные блоки. И вот тут-то мы и выяснили, что Fullstack-разработчики писали код так, что его нельзя пока что разделить на блоки. Что если пустить со стороны человека, то он будет видеть весь код. Увы, но такова была реальность. Приняли решение максимально срочно декомпозировать, но вы наверно знаете, что на практике это совсем не просто.

"Страсти" в этом проекте накалялись. Разработчики сутками пытались исправлять баги, про которые ежедневно менеджерам по работе с клиентами звонили клиенты. Все мы принимали еженедельно новые алгоритмы, которые полностью меняли структуру работы и максимально выводили из процесса разработчиков (я их нарисовал 5 в период 7 месяцев). Пытались максимально разгрузить разработчиков, сняли все, что можно получить временно внешними ресурсами (сайт на Битрикс, CRM и так далее). Но в итоге, это не дало нужных результатов. Спустя 2 года, я покинул проект.

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

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

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

Успешных вам проектов!

Подробнее..

Как команда it-animals в финале Цифрового Прорыва выиграла

24.05.2021 16:18:49 | Автор: admin

Данная статья написана в соавторстве с тимлидом @Restlin

Выбор кейса и наше видение его решения

Изначально выбор пал на кейс МВД: Разработка автономного программного решения лингвистического анализа и преобразования в тексте лица повествования.

Формулировка кейса:

Учитывая специфику деятельности определенных служб МВД России, при подготовке документов требуется преобразование в тексте лица повествования от первого лица в третье с учетом рода. Например, фраза в исходном тексте Я увидел, что Иванов пошёл ко мне в итоговом тексте должна быть преобразована в Он увидел, что Иванов пошёл к нему. Разработанное программное решение позволит в автоматическом режиме проводить процесс конвертации лица повествования, что позволит сотрудникам уделить больше времени на иные аспекты служебной деятельности. Кейс подготовлен Департаментом информационных технологий, связи и защиты информации МВД России.

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

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

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

1) локальное решение, работающее без доступа в сеть;

2) интегрированные офисные пакеты посредством макросов.

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

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

Формулировка кейса:

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

Почему он? У нас было понимание как работать с электронной подписью на Open source решениях: OpenSSL. Пригодился опыт Ильи в разработке СЭД - он знал о существовании php библиотеки tcpdf для генерации файла pdf с возможностью встроить электронную подпись. Плюс на текущем проекте pirs.online мы уже копали эту тему, и оттого данной задачей заниматься было приятно вдвойне.

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

Панические атаки и ведро валерьянки

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

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

А что потом? Технические подробности

Дальше пошло по накатанной: первый чекпоинт и наши идеи по реализации, полученное одобрение экспертов и практически бессонная ночь работы.

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

С точки зрения технической реализации функционал прототипа выглядел просто:

  • вход пользователя под одной из двух ролей: гость и администратор;

  • формирование обращения администратору (почте РФ);

  • рассмотрение обращения и формирование ответа;

  • можно прикрепить файлы к обращению и ответу;

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

  • подписать файлы ответа электронной подписью;

  • выгрузить обращение с электронной подписью;

  • проверить электронную подпись в обращении.

Структура базы данных прототипа уместилась всего в 3 таблицы, размещенных в PostgreSQL:

  1. user - таблица пользователей с реквизитами и типами;

  2. message - таблица обращений и ответов. По сути это переписка клиента и администратора;

  3. file - таблица файлов, прикрепленных к обращениям и ответам.

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

Для работы с электронными подписями мы решили использовать OpenSSL, как открытый стандарт де-факто по работе с электронными подписями.

Как и ожидалось библиотека очень мощная, но из коробки не поддерживает отечественные алгоритмы шифрования. Какое-то время ушло на интеграцию и настройку криптографического движка (libengine-gost-openssl 1.1) на алгоритмы ГОСТ, в частности ГОСТ-2012. Затем мы создали и настроили удостоверяющий центр.

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

Пилим прототип дальше

PHP содержит функции для работы с openssl по созданию сертификатов и подписи файлов, но после тщательного изучения документации, выяснилось, что переключить openssl engine на ГОСТ невозможно.

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

Создание сертификата пользователя:

exec("openssl req -nodes -newkey gost2012_512 -keyout $eSignPath/client.key -pkeyopt paramset:A -out $eSignPath/client.csr -subj \"/C=RU/ST=Udm/L=Izhevsk/O=IT/OU=animals/CN=user-{$user->id}\" -config $caPath/openssl.cnf ");

exec("openssl ca -engine gost -keyfile $caPath/ca.key -cert $caPath/ca.crt -in $eSignPath/client.csr -out $eSignPath/client.crt -batch -config $caPath/openssl.cnf 2>&1", $output);

где $eSignPath - путь до папки с ключами пользователей, а $caPath - путь до папки удостоверяющего центра.

Удаление сертификата пользователя:

exec("openssl ca -config $caPath/openssl.cnf -keyfile $caPath/ca.key -cert $caPath/ca.crt -revoke $eSignPath/client.crt 2>&1", $output);

exec("openssl ca -gencrl -config $caPath/openssl.cnf -keyfile $caPath/ca.key -cert $caPath/ca.crt -out $caPath/crl.pem 2>&1", $output);

где $eSignPath - путь до папки с ключами пользователей, а $caPath - путь до папки удостоверяющего центра.

Подписание файла сертификатом пользователя:

exec("openssl smime -engine gost -sign -in $fp -out $fp.sig -nodetach -binary -signer $clientKeysPath/client.crt -inkey $clientKeysPath/client.key -outform SMIME 2>&1", $output);

где $fp - путь до файла, $clientKeysPath - путь до папки с ключами пользователя.

Проверка подписи файла:

$output = exec("openssl cms -engine gost -verify -in $sigPath -inform SMIME -CAfile $pathCA/ca.crt -out $fp -certsout $clientKeysPath/client.crt 2>&1");

где $fp - путь до файла, $clientKeysPath - путь до папки с ключами пользователя, $sigPath - путь до электронной подписи.

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

Появилась идея: Илья вспоминает, что в одной не самой популярной библиотеке tcpdf по формированию pdf файлов была возможность встраивания электронной подписи в pdf файл. А это значит, что можно из обращения создать pdf файл и сразу встроить в него электронную подпись.

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

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

$fields = [

'r' => 'api/sign',

'filePath' => $tempdoc,

'userId' => $user->id,

];

$query = http_build_query($fields);

$ch = curl_init();

$host = \Yii::$app->params['apiHost'] ?? '';

curl_setopt($ch, CURLOPT_URL, $host . '/index.php?' . $query);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$signature = curl_exec($ch);

/*if (empty($this->signature_data['extracerts'])) {

openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);

} else {

openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED, $this->signature_data['extracerts']);

}*/

И все же мы успеваем в последний момент и к утру воскресенья прототип полностью работает!

Последний рывок и мы у цели

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

Было страшновато: осознание, что ты соревнуешься с лучшими (в финал попали топ 5 команд из отборочных региональных туров) подстегивало выкладываться на полную.

Неожиданно результатов пришлось ждать до вечера, хотя на Северо-Западном хабе объявили победителей чуть ли не через час после защит. Время тянулось как доставка Почты России.

Офтоп: мы победители! 750 тысяч на команду, Карл! 750 за 2 дня, Карл! А значит едем на грандфинал Цифрового прорыва в Москву!

Репозиторий нашего решения

Подробнее..

Prototype Design Pattern в Golang

24.05.2021 18:21:26 | Автор: admin

Привет друзья! С вами Алекс и я продолжаю серию статей, посвящённых применению шаблонов проектирования в языке Golang.

Интересно получать обратную связь от вас, понимать на сколько применима данная область знаний в мире языка Golang. Ранее уже рассмотрели шаблоны: Simple Factory, Singleton и Strategy. Сегодня хочу рассмотреть еще один шаблон проектирования - Prototype.

Для чего нужен?

Это порождающий шаблон проектирования, который позволяет копировать объекты, не вдаваясь в подробности их реализации.

Какую проблему решает?

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

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

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

Шаблон Prototype поручает создание копий самим копируемым объектам. Он вводит общий интерфейс для всех объектов, поддерживающих клонирование. Это позволяет копировать объекты, не привязываясь к их конкретным классам. Обычно такой интерфейс имеет всего один метод clone.

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

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

Диаграмма классов

Prototype Class DiagramPrototype Class Diagram

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

Как реализовать?

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

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

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

Каждую рубрика, как конечный элемент рубрикатора, может быть представлен интерфейсом prototype, который объявляет функцию clone. За основу конкретных прототипов рубрики и раздела мы берем тип struct, которые реализуют функции show и clone интерфейса prototype.

Итак, реализуем интерфейс прототипа. Далее мы реализуем конкретный прототип directory, который реализует интерфейс prototype представляет раздел рубрикатора. И конкретный прототип для рубрики. Обе структуру реализуют две функции show, которая отвечает за отображение конкретного контента ноды и clone для копирования текущего объекта. Функция clone в качестве единственного параметра принимает аргумент, ссылающийся на тип указателя на структуру конкретного прототипа - это либо рубрика, либо директория. И возвращает указатель на поле структуры, добавляя к наименованию поля _clone.

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

Open directory 2  Directory 2    Directory 1        category 1    category 2    category 3Clone and open directory 2  Directory 2_clone    Directory 1_clone        category 1_clone    category 2_clone    category 3_clone

Когда применять?

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

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

Итог

Друзья, шаблон Prototype предлагает:

  • Удобную концепцию для создания копий объектов.

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

  • В объектных языках позволяет избежать наследования создателя объекта в клиентском приложении, как это делает паттерн abstract factory, например.

Кстати, друзья, вот тут можно посмотреть результаты опроса читателей хабра. 63% опрошенных считают, что применение шаблонов проектирования в Golang - это зло. Связано, скорее всего, с тем, что язык Golang процедурный и ему чужды подходы объектно-ориентированных языков. Но рассматривать реализации и применение шаблонов стоит, так как это позволяет больше их понимать и периодически применять для решения тех или иных задач. Каждый подход требует, конечно, дискуссии и разумного применения.

Друзья, рад был поделиться темой, Алекс. На английском статью можно найти тут.
Удачи!

Подробнее..

Принцип подстановки Барбары Лисков (предусловия и постусловия)

28.05.2021 00:20:41 | Автор: admin

Почему у многих возникают проблемы с этим принципом? Если взять не заумное, а более простое определение, то оно звучит так:

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

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

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

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

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

<?phpclass Customer{    protected float $account = 0;    public function putMoneyIntoAccount(int|float $sum): void    {        if ($sum < 1) {            throw new Exception('Вы не можете положить на счёт меньше 1$');        }        $this->account += $sum;    }}class  MicroCustomer extends Customer{    public function putMoneyIntoAccount(int|float $sum): void    {        if ($sum < 1) {            throw new Exception('Вы не можете положить на счёт меньше 1$');        }        // Усиление предусловий        if ($sum > 100) {             throw new Exception('Вы не можете положить на больше 100$');        }        $this->account += $sum;    }}

Добавление второго условия как раз является усилением. Так делать не надо!

К предусловиям также следует отнести Контравариантность, она касается параметров функции, которые может ожидать подкласс.

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

Этот пример показывает, как расширение допускается, потому что метод Bar->process() принимает все типы параметров, которые принимает метод в родительском классе.

<?phpclass Foo{    public function process(int|float $value)    {       // some code    }}class Bar extends Foo{    public function process(int|float|string $value)    {        // some code    }}

Пример ниже показывает, как дочерний класс VIPCustomer может принимать в аргумент переопределяемого метода putMoneyIntoAccount более широкий (более абстрактный) объект Money, чем в его родительском методе (принимает Dollars).

<?phpclass Money {}class Dollars extends Money {}class Customer{    protected Money $account;    public function putMoneyIntoAccount(Dollars $sum): void    {        $this->account = $sum;    }}class VIPCustomer extends Customer{    public function putMoneyIntoAccount(Money $sum): void    {        $this->account = $sum;    }}

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

Постусловия не могут быть ослаблены в подклассе

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

<?phpclass Customer{    protected Dollars $account;    public function chargeMoney(Dollars $sum): float    {        $result = $this->account - $sum->getAmount();        if ($result < 0) { // Постусловие            throw new Exception();        }        return $result;    }}class  VIPCustomer extends Customer{    public function chargeMoney(Dollars $sum): float    {        $result = $this->account - $sum->getAmount();        if ($sum < 1000) { // Добавлено новое поведение            $result -= 5;          }               // Пропущено постусловие базового класса              return $result;    }}

Условное выражение проверяющее результат является постусловием в базовом классе, а в наследнике его уже нет. Не делай так!

Сюда-же можно отнести и Ковариантность, которая позволяет объявлять в методе дочернего класса типом возвращаемого значения подтип того типа (ШО?!), который возвращает родительский метод.

На примере будет проще. Здесь в методе render() дочернего класса, JpgImage объявлен типом возвращаемого значения, который в свою очередь является подтипом Image, который возвращает метод родительского класса Renderer.

<?phpclass Image {}class JpgImage extends Image {}class Renderer{    public function render(): Image    {    }}class PhotoRenderer extends Renderer{    public function render(): JpgImage    {    }}

Таким образом в дочернем классе мы сузили возвращаемое значение. Не ослабили. Усилили :)

Инвариантность

Здесь должно быть чуть проще.

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

Инварианты это некоторые условия, которые остаются истинными на протяжении всей жизни объекта. Как правило, инварианты передают внутреннее состояние объекта.

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

<?php class Wallet{    protected float $amount;    // тип данного свойства не должен изменяться в подклассе}

Здесь также стоит упомянуть исторические ограничения (правило истории):

Подкласс не должен создавать новых мутаторов свойств базового класса.

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

<?phpclass Deposit{    protected float $account = 0;    public function __construct(float $sum)    {        if ($sum < 0) {            throw new Exception('Сумма вклада не может быть меньше нуля');        }        $this->account += $sum;    }}class VipDeposit extends Deposit{    public function getMoney(float $sum)    {        $this->account -= $sum;    }}

С точки зрения класса Deposit поле не может быть меньше нуля. А вот производный класс VipDeposit, добавляет метод для изменения свойства account, поэтому инвариант класса Deposit нарушается. Такого поведения следует избегать.

В таком случае стоит рассмотреть добавление мутатора в базовый класс.

Выводы

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

Стоит упомянуть, что нужно страться избавляться от пред/пост условий. В идеале они должны быть определенны как входные/выходные параметры метода (например передачей в сигнатуру готовых value objects и возвращением конкретного валидного объекта на выход).

Надеюсь, было полезно.

Источники

  1. Вики - Принцип подстановки Барбары Лисков

  2. Metanit

  3. PHP.watch

  4. Telegram канал, с короткими заметками

Подробнее..

Перевод Развертывание приложения Symfony в AWS Lambda

11.06.2021 18:10:18 | Автор: admin

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

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

AWS Lambda обеспечивает высокую доступность, причем плата взимается только за фактически затрачиваемое время вычислений. Этот сервис может быть весьма полезен для таких задач, как запуск cron-заданий, отправка уведомлений в режиме реального времени, предоставление доступа к API, обработка каких-нибудь событий при выполнении различных операций и т.д. В сети можно найти массу примеров использования сервиса.

Наш сценарий использования

Предоставить доступ к API, созданному с помощью Symfony, который публикует сообщения в LinkedIn. Процесс разработки будет включать этапы от написания до развертывания кода.

Пишем код

В Symfony 5-й версии появился новый компонент под названием Notifier, который дает возможность отправлять уведомления через разные сервисы (Slack, Twitter, Twilio и др.).

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

Приступаем

$ symfony new --full aws-lambda-linkedin-notifier$ cd aws-lambda-linkedin-notifier$ composer require eniams/linkedin-notifier

Включаем шлюз (см. документацию)

<?php// config/bundles.phpreturn [// others bundles,Eniams\Notifier\LinkedIn\LinkedInNotifierBundle::class => ['all' => true]];// .envLINKEDIN_DSN=

Логика публикации контента

<?phpclass PostContentController{    /**     * @Route("/contents", name="post_content", methods="POST")     */    public function __invoke(NotifierInterface $notifier, Request $request)    {        if(null !== $message = (\json_decode($request->getContent(), true)['message'] ?? null)) {            $notifier->send(new Notification($message, ['chat/linkedin']));            return new JsonResponse('message posted with success', 201);        }        throw new BadRequestException('Missing "message" in body');    }}

Логика проста: мы предоставляем доступ к API через маршрут /contents, который принимает запрос POST с сообщением message в его теле.

В 11-й строке мы отправляем публикуемое сообщение в LinkedIn благодаря Symfony и шлюзу это делается очень просто!

Будучи профессиональными разработчиками, покроем этот код автотестами:

<?phpclass PostContentControllerTest extends WebTestCase    /**     * @dataProvider methodProvider     */    public function testANoPostRequestShouldReturnA405(string $method)    {        $client = static::createClient();        $client->request($method, '/contents');        self::assertEquals(405, $client->getResponse()->getStatusCode());    }    public function testAPostRequestWithoutAMessageInBodyShouldReturnA400()    {        $client = static::createClient();        $client->request('POST', '/contents');        self::assertEquals(400, $client->getResponse()->getStatusCode());    }    public function testAPostRequestWithAMessageInBodyShouldReturnA201()    {        $request = new Request([],[],[],[],[],[], json_encode(['message' => 'Hello World']));        $notifier = new class implements NotifierInterface {            public function send(Notification $notification, Recipient ...$recipients): void            {            }        };        $controller = new PostContentController();        $response = $controller->__invoke($notifier, $request);        self::assertEquals(201, $response->getStatusCode());    }    public function methodProvider()    {        return [            ['GET'],            ['PUT'],            ['DELETE'],        ];    }view rawTestPostContentController.php hosted with  by GitHub

Код готов! Пришло время его развернуть

Стоп! Что?! AWS Lambda не поддерживает PHP!

Именно так: AWS Lambda поддерживает не все языки программирования, а только некоторые, в том числе Go, Java, Python, Ruby, NodeJS и .NET.

Теперь надо учить новый язык и переписывать код? Надеюсь, нет!

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

Bref позволяет развертывать PHP-приложения в AWS и запускать их на AWS Lambda.

Конфигурация развертывания

Добавим в kernel.php обработку логов:

<?php    // Kernel.php    public function getLogDir(): string    {        if (getenv('LAMBDA_TASK_ROOT') !== false) {            return '/tmp/log/';        }        return parent::getLogDir();    }    public function getCacheDir()    {        if (getenv('LAMBDA_TASK_ROOT') !== false) {            return '/tmp/cache/'.$this->environment;        }        return parent::getCacheDir();    }

Подготовим фреймворк Serverless (см. документацию):

$ npm install -g serverless$ serverless config credentials --provider aws --key  --secret$ composer require bref/bref

Зададим конфигурацию в файле serverless.yaml:

service: notifier-linkedin-apiprovider:    name: aws    region: eu-west-3    runtime: provided    environment: # env vars        APP_ENV: prod        LINKEDIN_DSN: YOUR_DSNplugins:    - ./vendor/bref/breffunctions:    website:        handler: public/index.php # bootstrap         layers:            - ${bref:layer.php-73-fpm} # https://bref.sh/docs/runtimes/index.html#usage         timeout: 28 # Timeout to stop the compute time        events:            - http: 'POST /contents' # Only POST to /contents are allowedpackage:    exclude:        - 'tests/**'view rawserverless.yaml hosted with  by GitHub

Развертываем!

$ serverless deployServerless: Packaging service...Serverless: Excluding development dependencies...Serverless: Uploading CloudFormation file to S3...Serverless: Uploading artifacts...Serverless: Uploading service notifier-linkedin-api.zip file to S3 (10.05 MB)...Serverless: Validating template...Serverless: Updating Stack...Serverless: Checking Stack update progress.......................Serverless: Stack update finished...Service Informationservice: notifier-linkedin-apistage: devregion: eu-west-3stack: notifier-linkedin-api-devresources: 15api keys:  Noneendpoints:  POST - https://xxx.execute-api.eu-west-3.amazonaws.com/dev/contentsfunctions:  website: notifier-linkedin-api-dev-websitelayers:  NoneServerless: Removing old service artifacts from S3...Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.

Теперь наш код развернут в AWS Lambda, а API доступен по адресу https://xxx.execute-api.eu-west-3.amazonaws.com/dev/contents.

Попробуем:


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

Подробнее..

PHP-Compiler, или ныряем в кроличью нору FFI

17.06.2021 14:15:17 | Автор: admin

Однажды Энтони Феррара (Anthony Ferrara) решил скомпилировать PHP в низкоуровневый код, но результат получился слабым. Главной проблемой, с которой он столкнулся, было отсутствие подходящего бэкенда. К лучшему все изменилось после того, как в дело вступил FFI.

Я советую прочитать статью A PHP Compiler, aka The FFI Rabbit Hole, перевод который вы найдёте под катом.

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

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

Виды компиляторов

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

  • Интерпретация: подавляющее большинство динамических языков (например, PHP, Python (CPython), Ruby и т. д.) можно интерпретировать с помощью какой-либо виртуальной машины.

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

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

  • Компиляция: значительная часть языков, которые мы считаем статическими, компилируется заранее (ahead of time, AOT) прямо в нативный машинный код. Многие языки (C, Go, Rust и т. д.) используют AOT-компилятор.

Суть AOT в том, что вся компиляция происходит целиком до выполнения кода. Это значит, что вы сначала компилируете код и через некоторое время выполняете его.

Основное преимущество AOT-компиляциигенерация очень эффективного кода, а главный недостатокдлительность компиляции.

  • Just In Time (JIT): JIT относительно недавно стал популярным методом, благодаря которому можно взять лучшее от виртуальной машины и AOT. Многие языки программированияLua, Java, JavaScript, Python через интерпретатор PyPy, HHVM, PHP 8 и прочиеиспользуют JIT.

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

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

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

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

Главный плюс JIT-компиляции в том, что в некоторых сценариях использования она объединяет скорость развертывания виртуальной машины с производительностью, характерной для AOT. Это невероятно сложный процесс, так как нужно собрать два полнофункциональных компилятора и интерфейс между ними.

Подытожим:

  • интерпретатор выполняет код;

  • AOT-компилятор генерирует машинный код, который потом выполняет компьютер;

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

Немного объяснений

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

  • Компилятор: значение термина компилятор зависит от контекста.

Если мы говорим о сборке рантаймов языков программирования (эти рантаймы тоже называют компиляторами), то компиляторпрограмма, которая преобразовывает код из одного языка в другой с отличной от него семантикой. Это не просто иное представление кодакод именно преобразовывают. Примеры такого преобразования: из PHP в опкоды, из C в промежуточное представление, из ассемблера или регулярного выражения в машинный код. Да, в версии PHP 7.0 есть компилятор, который компилирует исходный код языка PHP в опкоды.

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

Да уж, запутано...

  • Виртуальная машина (VM): я упомянул выше, что виртуальная машинагигантский switch в цикле. Чтобы понять, почему ее называют виртуальной, давайте немного поговорим о том, как работает настоящий физический CPU.

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

Этот код просто добавляет 1 к регистру rsi, затем добавляет к нему 2.

Посмотрите, как та же операция представлена в опкодах PHP:

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

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

а) делает существование PHP и любого интерпретируемого языка возможным,

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

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

  • Дерево абстрактного синтаксиса (Abstract Syntax Tree, AST): ASTвнутренняя структура данных, которая представляет исходный код программы в виде дерева. Таким образом, вместо $a = $b + $c; получаем что-то вроде Assign($a, Add($b, $c)). Главная характеристика деревау каждого узла только один родитель. PHP выполняет внутреннее преобразование исходного файла в AST перед компиляцией в опкоды.

Если дан следующий код:

то можно ожидать, что AST будет выглядеть так:

  • Граф потока управления (control flow graph, CFG): CFG во многом похож на AST, но если у первого может быть несколько корневых элементов, то у второго только один. Это можно объяснить так: CFG включает в себя связи между циклами и т. п., так что можно увидеть все возможные пути управления, проходящие через весь код. Расширение Opcache Optimizer для PHP использует внутри CFG.

Если дан следующий код:

то можно ожидать, что CFG будет выглядеть так:

В этом случае longцелое число PHP, numericцелое число или число с плавающей запятой, jumpzпереход к другой команде в зависимости от того, равна ли bool_21 0 или нет.

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

  • Промежуточное представление (Intermediary Representation, IR): IRязык программирования, который полностью живет в компиляторе. Вы никогда не будете писать на этом языке, его для вас генерируют. Тем не менее, стоит отметить, что IR нужен для некоторых манипуляций компилятора (например, для реализации оптимизаций), а также для того, чтобы компоненты компилятора были разделены (в итоге их легче настраивать). Вышеупомянутые структуры AST и CFGформы IR.

Немного предыстории

Я впервые попытался выполнить PHP поверх PHP в рамках проекта PHPPHP еще в 2013 году. Суть проекта состояла в том, чтобы перевести исходный код из репозитория php-src с языка C на PHP. Не было и речи о том, что виртуальная машина будет работать быстро (слово быстро в кавычках, так как эта машина примерно в 200 раз медленнее, чем PHP, и нет никакого способа ее разогнать). Я просто развлекался, мне нравилось экспериментировать и учиться чему-то новому и интересному.

Полтора года спустя я создал набор инструментов Recki-CT, который работал по иной схеме. Вместо того, чтоб реанимировать прошлую попыткуPHP в PHP, я создал многоступенчатый компилятор. Он парсил PHP в AST, преобразовывал AST в CFG, проводил оптимизацию, затем выдавал код через бэкенд. Для этой задачи я собрал два начальных бэкенда: один компилировал код в расширение PECL, а второй использовал расширение JitFu для непосредственного выполнения кода, оперативно компилировал его и запускал в виде нативного машинного кода. Эта реализация работала довольно неплохо, но была мало применима на практике по ряду причин.

Несколько лет спустя я снова вернулся к этой идее, но решил не создавать единый монолитный проект, а заняться серией взаимосвязанных проектов по парсингу и анализу PHP. В рамках этих проектов были реализованы следующие инструменты: PHP-CFGпарсинг CFG, PHP-Typesсистема вывода типов, PHP-Optimizerбазовый набор оптимизаций поверх CFG. Я разработал эти инструменты для того, чтобы встроить их в другие проекты для различных целей (например, Tuliранняя версия статического анализатора кода PHP). В проекте PHP-Compiler я пытался компилировать PHP в низкоуровневый код, но результат получился слабым.

Главной проблемой, с которой я столкнулся при создании полезного низкоуровневого компилятора, было наличие (точнее отсутствие) подходящего бэкенда. Библиотека libjit (используемая расширением JitFu) работала хорошо и быстро, но не могла генерировать бинарники. Я мог бы написать расширение на C, привязанное к LLVM (HHVM использовала инфраструктуру LLVM и многие другие), но это ОЧЕНЬ трудозатратный процесс. Я не захотел идти этим путем и отложил эти проекты до лучших времен.

В игру вступают PHP 7.4 и FFI

Нет, версия PHP 7.4 еще не вышла (Пост был опубликован в 22 апреля 2019 года). Возможно, она будет выпущена через полгода. Несколько месяцев назад небольшое предложение по включению расширения FFI в PHP успешно прошло голосование. Я решил поиграть с этим расширением, чтобы узнать, как оно работает.

Спустя некоторое время я вспомнил о своих проектах, связанных с компиляторами. Я стал думать, насколько сложно будет загрузить libjit в PHP, но затем сообразил, что эта библиотека не может генерировать исполняемые файлы. Я начал искать возможные варианты решения проблемы и наткнулся на библиотеку libgccjit. Так началось мое путешествие по кроличьей норе.

Вот все новые проекты, над которыми я работал последние несколько месяцев.

FFIMe

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

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

Встречайте FFIMe.

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

По сути, FFIMe получает путь к Shared Object File и к директивам #include. Он парсит получившийся С, убирает несовместимый с FFI код и затем генерирует класс. Хорошо, МНОГО классов. Теперь сгенерированный файл можно рассмотреть (файл из примера выше на GitHub).

Если вы посмотрите на этот файл, то увидите ОГРОМНОЕ количество кодапочти 5000 строк. Он включает в себя все числовые #define заголовков C как константы класса, все ENUM как константы класса, а также все функции и классы-обертки всех базовых типов C. Файл также рекурсивно содержит все другие заголовки (поэтому у заголовка выше есть некоторые на первый взгляд лишние файловые функции).

Код использовать весьма просто. Примечание: не обращайте внимание на то, что делает библиотека, просто смотрите на типы и на вызовы, затем сравните с эквивалентным кодом на C:

Теперь можно работать с библиотеками C в PHP как будто бы они в C! Ура!

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

PHP-CParser

Сделав рефакторинг FFIMe, я решил собрать полнофункциональный C parser. Он работает так же, как PHPParser разработчика Никиты Попова, но не в PHP, а в C.

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

В начале препроцессор C обрабатывает заголовочные файлыон резолвит все стандартные директивы, такие как #include, #define, #ifdef и т. д. Потом PHP-CParser парсит код в AST (по мотивам CLANG).

Таким образом, например, следующий код C:

и includes_and_typedefs.h:

даст такой AST:

Синие именаимена классов объектов, а красные имена в нижнем регистреимена свойств указанных объектов. Так, внешний объект здесьPHPCParser\Node\TranslationUnitDecl с массивом свойств declarations. И так далее...

Очень редко кому-то надо парсить код C в PHP, поэтому я полагаю, что эту библиотеку будут использовать только с FFIMe. Но если вы захотите ее использоватьвперед!

PHP-Compiler

Я вернулся к проекту PHP-Compiler и начал работу над ним. В этот раз я добавил несколько этапов к компилятору. Я решил не компилировать непосредственно из CFG в нативный код, а применить Virtual Machine interpreter (именно так и работает PHP). Это ГОРАЗДО более зрелый подход, чем тот, который я использовал в PHPPHP. Я не остановился на этом и создал компилятор, который может брать опкоды виртуальной машины и генерировать нативный машинный код. Это позволило применить как JIT-компиляцию (Just In Time), так и AOT-компиляцию (Ahead of Time). Таким образом, я могу не только запускать код или компилировать его во время запуска, но и предоставить компилятору кодовую базу для того, чтобы он сгенерировал файл машинного кода.

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

Я начал собирать PHP-Compiler поверх libgccjit и получил весьма интересные результаты. Простой набор бенчмарков, взятых из пакета PHP, показывает, что хотя сейчас и есть МНОГО накладных расходов, скомпилированный код может быть действительно блестящим.

Следующие тесты сравнивают производительность PHP-Compiler, PHP 7.4 с OPcache (Zend Optimizer) и без него, а также экспериментального JIT (в включенном и выключенном состоянии) для PHP 8.

Заметен ощутимый простой при старте (помните, это PHP!). Однако компиляция кода (как в режиме JIT, так и в режиме AOT) происходит значительно быстрее, чем в PHP 8 с JIT-компиляцией в особо сложных вариантах использования.

Стоит отметить, что мы сравниваем совершенно разные вещи. Не стоит ожидать такие же показатели в продакшен-проектах, но по ним можно сделать вывод о перспективности такого подхода...

Сейчас можно использовать эти 4 команды:

  • php bin/vm.phpзапустить код в виртуальной машине;

  • php bin/jit.phpкомпилировать весь код, затем запустить его;

  • php bin/compile.phpкомпилировать весь код и вывести файл a.o;

  • php bin/print.phpкомпилировать и вывести CFG и сгенерированные опкоды (полезно для отладки).

В командной строке все работает как PHP:

Да, здесь echo "Hello World\n"; работает как нативный машинный код. Перебор? Определенно. Прикольно? Однозначно!

Подробности в описании проекта.

Я приостановил сборку, потому что не знал, стоит ли и дальше использовать libgccjit или лучше перейти на LLVM?

Есть только один способ выяснить это...

PHP-Compiler-Toolkit

Как вы уже поняли, я не умею давать названия вещам...

PHP-Compiler-Toolkitуровень абстракции поверх libjit, libgccjit и LLVM.

Вы просто встраиваете код, похожий на код языка C, в кастомное промежуточное представление через нативный интерфейс PHP. Например, это выражение (обратите внимание, что long long 64-битное целое число, как и тип PHP int):

можно использовать так:

Это описывает код. Отсюда можно передать контекст бэкенду для компиляции:

и потом просто получить в ответ:

Вот мы и получили чистый нативный код.

Теперь я могу собрать фронтенд (PHP-Compiler) поверх этой абстракции и менять бэкенды для тестирования.

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

Хотя все бэкенды и имеют сравнимые показатели в рантайме, продолжительность компиляции для libgccjit зашкаливает. Хм, может я был прав, когда думал перейти на LLVM?..

Да, и для такой простой функции накладные расходы FFI весьма значительные. На запуск этого же кода в PHP уходит примерно 0,02524 секунды.

Чтобы продемонстрировать, что PHP-Compiler может в перспективе работать гораздо быстрее, чем PHP, представьте себе такой бенчмарк:

В нативном PHP запуск этого кода миллион раз займет примерно 2,5 секунды. Не то чтобы медленно, но и не супер быстро. Однако с PHP-Compiler мы видим следующее:

В этом искусственном примере можно увидеть десятикратный прирост производительности по сравнению с нативным PHP 7.4.

Вы можете посмотреть этот пример и скомпилированный код в examples folder of php-compiler-toolkit.

PHP-LLVM

После проекта PHP-Compiler-Toolkit я начал работу над PHP-LLVM. Эксперименты с Toolkit показали, что у libgccjit нет реальных преимуществ перед LLVM, у которой есть преимущества в производительности и функциональности, поэтому я решил перевести PHP-Compiler исключительно на нее.

Я не стал обращаться непосредственно к LLVM C-API, а написал обертку над ним. Я преследовал две цели:

1) я получаю более объектно-ориентированный API, так как, чтобы получить тип значения, пишу $value->typeOf(), а не LLVMGetType($value);

2) с оберткой я могу не обращать внимание на различия в версиях LLVM.

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

PHP-ELF-SymbolResolver

Стоит отметить, что в LLVM были ошибки, поэтому мне нужно было видеть, какие символы на самом деле компилируются в LLVM.Таким образом, я хотел проверить общий файл объекта (.so), который содержит скомпилированную библиотеку LLVM. Для этого я написал PHP-ELF-SymbolResolver, который парсит файлы формата ELF и показывает объявленные символы.

По определенным причинам я сомневаюсь, что этот проект будет востребован вне FFIMe, но, возможно, кому-то будет нужно декодировать нативную библиотеку ОС в PHP. В таком случае вы знаете, где взять библиотеку!

Использование макросов

Портируя PHP-Compiler на PHP-LLVM, я понял, что генерация кода с использованием API как билдера быстро становится многословной. Код невозможно прочитать. Например, возьмем сравнительно простую встроенную функцию __string__alloc, которая определяет новую внутреннюю структуру строки. Если использовать API как билдер, то она будет выглядеть примерно так:

Просто куча мусора. Трудно понять что-либо, а если какую-то часть кода и можно прочитать, то с ней очень сложно работать).

Чтобы избежать такого результата, я написал систему макросов с помощью PreProcess.ioиYay. Теперь тот же код выглядит так:

Читать код стало гораздо легче. Это смесь синтаксиса C и PHP, заточенная под нужды PHP-Compiler.

Язык макросов частично задокументирован на GitHub-е.

Подробности о применении макросов смотрите на src/macros.yay (GitHub).

Беспокоитесь о производительности? Правильно делаете. На обработку файлов нужно время (примерно одна секунда на файл). Есть два способа борьбы с этим:

1) предварительная обработка возможна только при установке PHP-Compiler с dev-зависимостями с помощью композера, иначе будут загружены только скомпилированные файлы PHP;

2) предварительная обработка произойдет на лету только при изменении файла .pre, даже с dev-зависимостями.

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

Запуск

Сначала установите PHP 7.4 с включенным расширением FFI. Насколько мне известно, эта версия PHP еще не вышла (и до того, как выйдет, пройдет еще немало времени).

Запуск FFIMe:

Объявите FFIMe dev-зависимостью композера ("ircmaxell/ffime": "dev-master") и запустите генератор кода через файл стиля rebuild.php. Например, файл rebuild.php, используемый PHP-Compiler-Toolkit, выглядит так:

Потом сравните сгенерированные файлы. Я предлагаю включать сгенерированные файлы через композер с ключевым словом files, а не загружать их автоматически, потому что композер сгенерирует ОГРОМНОЕ количество классов в один файл.

Замените строку "...so.0" путем к общей библиотеке, которую вы хотите загрузить, и файл .h заголовками, которые нужно парсить (можно много раз вписать ->include()).

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

Запуск PHP-Compiler нативным образом

Сейчас PHP-Compiler работает нестабильно, что-то может ломаться, поэтому сначала установите зависимости (можно использовать LLVM 4.0, 7, 8 и 9).

После окончания установки просто запустите их.

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

Еще можно задать файл:

При компиляции bin/compile.php также можно задать выходной файл с -o (по умолчанию будет перезаписан исходный файл без расширения .php). Для системы будет сгенерирован готовый к выполнению бинарник:

Или по умолчанию:

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

Запуск PHP-Compiler с Docker

Удобства ради я опубликовал два docker-образа для PHP-Compiler. Оба основаны на старой версии Ubuntu (16.04) из-за некоторых проблем с PHP-C-Parser, до которых у меня не дошли руки. Но вы можете скачать их и поиграть с ними:

  • Ircmaxell/php-compiler:16.04полнофункциональный компилятор, полностью установленный и сконфигурированный со всем необходимым для его запуска;

  • Ircmaxell/php-compiler:16.04-devтолько dev-зависимости. Контейнер предназначен для работы с вашей собственной сборкой PHP-Compiler, чтобы вы могли разрабатывать его в стабильной среде.

Для запуска кода:

Код будет по умолчанию запущен с bin/jit.php. Если вы хотите запустить код с другой точки входа, то ее можно изменить:

Да, и если вы хотите передать скомпилированный код, то можно расширить docker-файл. Например:

Во время запуска сборки docker код будет скомпилирован в index.php, а файл машинного кода будет сгенерирован в /app/index. Затем этот бинарник будет выполнен при запуске docker run ..... Обратите внимание: контейнер не предназначен для продакшена, так как в нем много лишнего, это просто демонстрация работы.

Что дальше

Теперь, когда PHP-Compiler поддерживает LLVM, можно продолжать работу по расширению поддержки языка. Еще многое предстоит сделать:например, массивы, объекты, нетипизированные переменные, обработку ошибок, стандартную библиотеку и т. д.). Хе-хе. В PHP-CFG и PHP-Types также есть, чем заняться: поддержкой исключений и ссылок, исправлением пары ошибок и многим другим.

Да, нужны тесты. Много тестов. И тестировщики. Пробуйте, ломайтеэто легко, обещаю. И создавайте ишью.

Протестируете?

PHP Russia 2021пройдет28 июнявМосква, Radisson Slavyanskaya. Но уже сейчас можно ознакомиться срасписаниеми присмотреть доклады, которые вы точно не захотите пропустить.

А еще вы можете выбрать формат участия: онлайн или офлайн. Не забудьтекупить билетыуже сегодня!

На всех наших офлайн площадках мы соблюдаем антиковидные меры:

1. Все сотрудники конференции сдают тест ПЦР и ходят в масках.

2. Участникам выдаём комплект медицинских масок и санитайзеры.

3. Во всех помещениях конференции и в фойе работают мощные рециркуляторы воздуха.

4. Каждый микрофон мы протираем спиртовыми салфетками, прежде чем передать его участнику.

5. В зоне кофебрейков и обедов соблюдаются нормы социальной дистанции.

Подробнее..

Телеграмм-бот для анализа опционов

07.06.2021 10:04:02 | Автор: admin

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

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

Автор статьи реализовал анализ опционов с помощью телеграмм-бота, что дает возможность быстрого получения информации об инвестиционном портфеле на смартфоне.

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

Запоминание состояния бота между вебхуками

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

$id_init = file_get_contents('php://input');$id=sbs($id_init, '"from":{"id":',',"is_bot":'); //в эту переменную записываем уникальный номер пользователяfunction sbs ($str,$m1,$m2){ //из строки str возвращает подстроку между двумя метками-словами m1 и m2$p1=strpos($str,$m1)+strlen($m1); //длина слова-метки слева$p2=strpos($str,$m2);return substr($str,$p1,$p2-$p1);}

Для каждого пользователя строится следующая структура данных:

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

  2. файл данных по составу портфеля ( идентификационный номер ценной бумаги, тип, количество, цена, дата экспирации)

  3. файлы для построения графика

После каждого вебхука происходит инициация данных в программе из указанных файлов.

Построение графика для анализа портфеля

График строится по состоянию на момент экспирации опционов, что дает возможность упрощенно представить график каждого инструмента в виде y=kx+b,

где y это размер прибыли/убытка на момент экспирации

х стоимость базового актива

После записи данных в портфель создаются файлы с расширением png с помощью библиотеки GD в несколько этапов:

1) определение Х-координат всех точек перелома на графике (очевидно, что сумма всех линий даст ломанную кривую)

2) определение масштаба изображения (средняя всех координат точек перелома по оси Х, максимальное отклонение от среднего, и максимальный размер по оси Y)

3) создание ассоциативного массива точек, в котором координата X это ключ, координата Y величина, для всей цифровой полуплоскости:

$typ опцион колл, пут или фьючерс

$q количество ( отрицательное продажа)

$cena цена приобретения ценной бумаги

$strike страйк для опционов

$x0 начальная координата по оси Х

$sx масштаб по оси Х

function pparr($typ, $q, $cena, $strike,$x0,$sx){ //функция выдает одномерный массив - координаты x=>y точек по //типу цб, направлению (покупка продажа), цене приобретения и страйку (для опционов)  if ($q<0) { $q=-$q; $drct='-'; }  else $drct='+'; $a=array(); $b=array(); $delta=$sx; //расстояние между точками равно масштабу $scalx for ($i=0;$i<740;$i++){ //кол во точек 740 определено заранее $xkk=$x0+$delta*$i; //значение по оси X if ($typ=='fut') { if ($drct=='+') $a[$xkk]=($xkk-$cena)*$q; else $a[$xkk]=(-$xkk+$cena)*$q; } if ($typ=='call'){ if ($drct=='+') { if ($xkk<=$strike) $a[$xkk]=-$cena*$q; else $a[$xkk]=$q*($xkk-$strike-$cena);} else { if ($xkk<=$strike) $a[$xkk]=$q*$cena; else $a[$xkk]=(-$xkk+$strike+$cena)*$q;}  } if ($typ=='put'){ if ($drct=='+') { if ($xkk<=$strike) $a[$xkk]=(-$xkk+$strike-$cena)*$q; else $a[$xkk]=-$q*$cena;} else { if ($xkk<=$strike) $a[$xkk]=($xkk-$strike+$cena)*$q; else $a[$xkk]=$cena*$q;}  } $b[(string)$xkk]=(string)$a[$xkk]; }return $b; };

4) создание файлов изображений, при этом одновременно строится график для каждой строки из портфеля ( зеленый цвет) и результирующий для портфеля ( красный цвет). Кроме того, know-how заключается в том, что одновременно строится еще четыре изображения для увеличения/уменьшения изображения по оси Х и по оси Y. За счет этого достигается эффект работы он-лайн с клавишами X+,X-,Y+,Y- под графиком. Таким образом, для каждого пользователя в каждый момент времени существует пять файлов изображения.

Анализ рисков опционного портфеля

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

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

Гарантийное обеспечение для фьючерсов считается по формуле, которая предложена самой Московской биржей:

ГО=БГО+(Цена-Расчетная_Цена)*БП;

где Расчетная_Цена определяется по результатам клиринга и практически равна цене базового актива на момент клиринга, определяется в пунктах

БП стоимость пункта в рублях, зависит, в частности, от курса доллара,

БГО базовое гарантийное обеспечение, определяется биржей, может произвольно увеличиваться, например, перед праздниками

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

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

Вот как выглядит покупка колла в нашем боте:

Запись в портфеле покупки опциона "колл" страйк 75000 дата экспирации 03.06.2021 по цене 25 Запись в портфеле покупки опциона "колл" страйк 75000 дата экспирации 03.06.2021 по цене 25 График зависимости прибыли/убытка по купленному опциону "колл" в зависимости от стоимости базового актива на дату экспирации График зависимости прибыли/убытка по купленному опциону "колл" в зависимости от стоимости базового актива на дату экспирации

Гарантийное обеспечение=23.

Что нам показывает график: если стоимость базового актива ( в данном случае стоимость фьючерса на курс рубля к доллару) [вечером] 03.06.2021 будет 75000 и ниже, то наш убыток составит 23 . При повышении этой стоимости до 75023 мы выйдем в безубыток, при дальнейшем росте получим прибыль.

Что мы имеем с точки зрения риска: не при каких обстоятельствах наш убыток не превысит сумму 23. Следовательно, наш опцион совершенно не похож на фьючерс, и в расчете ГО мы можем записать просто сумму 23.

Покупка пута примерно та же картина.

Продажа пута.

Запись в портфеле продажи опциона "пут" страйк 72750 по цене 44 с датой экспирации 03.06.2021Запись в портфеле продажи опциона "пут" страйк 72750 по цене 44 с датой экспирации 03.06.2021График зависимости прибыли/убытка по проданному опциону "пут" от стоимости базового актива на дату экспирацииГрафик зависимости прибыли/убытка по проданному опциону "пут" от стоимости базового актива на дату экспирации

Гарантийное обеспечение= 5436.

Можно убедиться, что при стоимости базового актива выше 72750 мы имеем прибыль 44. При снижении стоимости БА до 72706 мы выходим в ноль. При дальнейшем падении стоимости БА наш убыток НИЧЕМ НЕ ОГРАНИЧИВАЕТСЯ.

С точки зрения рисков это фьючерс, купленный по цене 72706. Подставляем это число в формулу ГО для фьючерса и получаем ГО для опциона! Этот ГО достаточно велик (5436), но может превратится в прибыль в течение нескольких дней.

Стоит ли овчинка выделки? Если при условных затратах сегодня 5436 мы получим через несколько дней 44? Мне кажется, риск очень велик. С другой стороны, курс рубля к доллару был таким примерно 3 месяца назад.

С продажей колла будет аналогичная ситуация.

А если одновременно продать пут и кол?

Запись в портфеле продажи опциона "пут" страйк 72750 и продажи опциона "колл" страйк 75000Запись в портфеле продажи опциона "пут" страйк 72750 и продажи опциона "колл" страйк 75000График зависимости прибыли/убытка по портфелю от стоимости базового актива на дату экспирации График зависимости прибыли/убытка по портфелю от стоимости базового актива на дату экспирации

Гарантийное обеспечение не изменилось!

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

с наибольшим ГО , оно и будет мерилом риска.

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

Интерфейс телеграмм-бота

На экране отображаются следующие группы данных:

  • подробная инструкция по работе с ботом

  • текущие котировки ближайших фьючерсов по трем базовым активам

  • таблица Портфель, отображающая состав портфеля, сделанная с помощью интерфейса телеграмм ( можно редактировать)

  • значение гарантийного обеспечения

  • таблица, дублирующая состав портфеля, построенная как изображение формата png, которую можно копировать и сохранять

  • нижняя клавиатура, которую можно скрывать, с кнопками: Добавить позицию в портфель, Анализ портфеля с помощью графика, Обновить котировки

Таблица Портфель создана с помощью InlineKeyboard.

При нажатии на клавиши в этой таблице происходят следующие действия:

  • клавиша в столбце К-во редактирует количество ценных бумаг, соответствующее строке, в которой нажата клавиша

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

  • клавишей Х можно удалить строку.

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

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

Заключение

В телеграмм-боте реализовано запоминание портфеля ценных бумаг для каждого пользователя. Под ценными бумагами понимаются опционы и фьючерсы, базовым активом для которых являются: курс рубля к доллару (Si), стоимость нефти брент (BR), а также индекс РТС (RI). Это самые высоколиквидные деривативы на московской бирже.

В телеграмм-бот заложен алгоритм подсчета гарантийного обеспечения, которое является мерой риска.

С помощью телеграмм-бота можно анализировать опционный портфель на графике прибылей/убытков (P/L график).

Протестировать телеграмм-бота можно по ссылке t.me/@test09062020bot. Или попробовать найти в телеграмме по названию опционный портфель.

Подробнее..

PHP Дайджест 204 (17 31 мая 2021)

31.05.2021 14:10:41 | Автор: admin
Фото: Christian Mnch.

В эти две недели core команда PHP активно обсуждала предложение по Partial function Application и в качестве альтернативы Никита Попов предложил более простой синтаксис для получения ссылки на любые функции. Также в уже принятые в PHP 8.1 енумы предлагается добавить статические свойства.

Symfony 6 будет требовать PHP 8.0, а вышедшая Doctrine 2.9 поддерживает указание метаданных в атрибутах вместо PHPDoc.

Еще в выпуске классная статья про регекспы и инструкция по использованию Deployer, а также другие полезные статьи, пачка инструментов, немного видео и подкастов.

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


PHP Internals


  • [RFC] First-class callable syntax


    В качестве альтернативы довольно сложному [RFC] Partial Function Application Никита предлагает более простое решение проблемы получения ссылки на любую функцию или метод.

    // Сейчас вот так$fn = Closure::fromCallable('strlen');$fn = Closure::fromCallable([$this, 'method']);$fn = Closure::fromCallable([Foo::class, 'method']);// Предлагается вот такое$fn = strlen(...);$fn = $this->method(...);$fn = Foo::method(...);
    


    И соответственно, такой синтаксис можно будет применять везде, где ожидается Callable. Например, вот так:
    array_map(Something::toString(?), [1, 2, 3]);array_map(strval(...), [1, 2, 3]);// вместоarray_map([Something::class, 'toString'], [1, 2, 3])array_map('strval', [1, 2, 3]);
    

  • [RFC] Disable autovivification on false


    Сейчас PHP позволяет инициализировать массив из переменной со значением null или false. Предлагается для false все-таки бросать Fatal error:
    $a = true;$a[] = 'value'; // Fatal error: Uncaught Error: Cannot use a scalar value as an array$a = null;$a[] = 'value'; // Ok$a = false;$a[] = 'value'; // Сейчас это работает, но предлагается задепрекейтить
    
    3v4l.org/UucOC

  • [RFC] Allow static properties in enums


    В PHP 8.1 будут енумы. Подробный разбор был videoна стриме PHP-дайджеста и в тексте на php.watch.

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

    Пример использования
    enum Environment {    case DEV;    case STAGE;    case PROD;    private static Environment $currentEnvironment;    /**     * Read the current environment from a file on disk, once.     * This will affect various parts of the application.     */    public static function current(): Environment {        if (!isset(self::$currentEnvironment)) {            $info = json_decode(file_get_contents(__DIR__ . '/../../config.json'), true);            self::$currentEnvironment = match($info['env']) {                'dev' => self::DEV,                'stage' => self::STAGE,                'prod' => self::PROD,            };        }        return self::$currentEnvironment;    }    // Other methods can also access self::$currentEnvironment}printf("Current environment is %s\n", Environment::current()->name);
    

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

    Кстати, в релизе PhpStorm 2021.2 уже будет поддержка enum, а пощупать можно будет на этой неделе в выпуске 2021.2 EAP.

  • [PR] Поддержка HTTP Early Hint support


    По умолчанию, PHP поддерживает отправку только одного набора заголовков. Но статус коды HTTP 1xx могут потребовать отправки нескольких наборов хедеров. В частности, для использования 103, нужно сначала отправить заголовки Link, и затем, когда весь ответ будет готов, отправить обычные 200 OK.

    Сейчас такое можно сделать, но немного криво: заголовки 103 отправить, как обычно, через header(), а следующую порцию заголовков вручную прям через echo.

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

  • check[RFC] Add IntlDatePatternGenerator


    Предложение принято. В PHP 8.1 будет класс IntlDatePatternGenerator для быстрого создания дат в локализированном формате. Подробнее в PHP Internals News #85 с автором RFC.

  • [RFC] Final class constants


    На голосовании.

  • В Internals обсуждается идея задепрекейтить багтрекер bugs.php.net


    Вместо него предлагается использовать issues на GitHub. У идеи есть как плюсы, так и минусы. Но как первый шаг, все баги документации теперь будут Гитхабе. Так что если вы нашли ошибку в мануале PHP, то можно просто создать issue в репозитории php/doc-en или php/doc-ru. Вот пример.


Инструменты


  • Doctrine ORM 2.9 Большое обновление популярной ORM. Под капотом поддержка атрибутов PHP 8, типизированные свойства, и другое.
  • Flarum 1.0.0 Релиз популярного движка для форума на PHP.
  • moneyphp/money 4.0 Пакет для правильной работы с денежными значениями.
  • phpast.com Просмотр дерева абстрактного синтаксиса PHP. Полезно при отладке инструментов на базе nikic/PHP-Parser. Код на гитхабе: ryangjchandler/phpast.com.
  • JBZoo/CI-Report-Converter Всеядный конвертер отчетов для CI. Основное призвание утилиты совместить самый разный результат линтеров с самыми разными CI (TeamCity, GitHub Actions, etc). Прислал smetdenis.
  • veewee/xml Все для удобной работы с XML в одном пакете.


Symfony




Laravel




Статьи




Аудио/Видео




community Сообщество





Подписывайтесь на Telegram-канал PHP Digest.

Если вам понравился дайджест, поставьте, пожалуйста, ему плюс это очень мотивирует продолжать делать.

Заметили ошибку или опечатку? Сообщите в личку хабра или телеграм.

Прислать ссылку можно через форму или просто написав мне в телеграм.
Поиск ссылок по всем дайджестам
Предыдущий выпуск: PHP-Дайджест 203

Подробнее..

Зачем нужен static при объявлении анонимных функций?

07.06.2021 22:20:16 | Автор: admin

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

<?phpusort($firstArray, static function($first, $second) {    return $first <=> $second;});

Звучал он так:

Зачем делать callbackи в функции сортировки (usort), статическими?

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

В чем проблема?

Начнем с определения из документации, чтобы засинхронизироваться:

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

Анонимные функции реализуются с использованием классаClosure.

Там-же, но это почти никто не читает :

При объявлении в контексте класса, текущий класс будет автоматически связан с ним, делая $this доступным внутри функций класса. Если вы не хотите автоматического связывания с текущим классом, используйте статические анонимные функции.

Выходит, что когда Сlosure объявляется в контексте класса, то класс автоматически привязывается к замыканию. Это означает, что $this доступен внутри области анонимной функции:

Код, чтобы протестить самостоятельно
<?phpclass ExampleTest extends TestCase{     public function testBasicTest(): void    {        $array = [2, 1];        usort($array, function ($first, $second) {            var_dump($this);            return $first <=> $second;        });       self::assertTrue(true);    }}

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

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

Вот пример без использования static:

<?php class LargeObject {    protected $array;    public function __construct() {        $this->array = array_fill(0, 2000, 15);    }    public function getItemProcessor(): Closure {        return function () { // Внутри функции любые вычисления            $a = 1;            $b = 2;            return $a + $b;        };    }}function getPeakMemory(): string{    return sprintf('%.2F MiB', memory_get_peak_usage() / 1024 / 1024);}$start = microtime(true);$processors = [];for ($i = 0; $i < 2000; $i++) {    $lo = new LargeObject();    $processors[] = $lo->getItemProcessor();}var_dump(getPeakMemory());

Как результат, мы получим string(10) "134.10 MiB"

Но в случае, если мы добавим static в 11 строке, то потребление памяти составит string(8) "1.19 MiB"

Всё потому, что в processors[] мы продолжаем накапливать массив, внутри которого находятся Сlosures которые связаны с классом, а значит, содержат все те данные, которые в нём хранятся.

Выводы

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

P.S.

Часто для полноценного поста на Хабре мало короткой заметки. Такие выдержки я публикую в своем телеграм-канале https://t.me/beerphp. Подписывайся и сможешь получить больше интересного материала ;)

Подробнее..

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

09.06.2021 18:07:37 | Автор: admin

Меня зовут Евгений Тупиков, я ведущий PHP-разработчик в Badoo и Bumble. У нас в команде более 200 бэкенд-разработчиков, которые работают над сотнями модулей и отдельных сервисов в наших приложениях. Но поначалу всё было не так масштабно. В 2006 году это был один проект, над которым работала небольшая команда. Каждый разработчик хорошо понимал, как всё устроено: легко ориентировался в коде, знал, какие есть сервисы и как они взаимодействуют между собой. Однако по мере роста проекта всё больше времени занимал поиск хранителей знаний тех, кто отвечает за ту или иную функциональность и к кому можно обратиться с вопросом или предложением.

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

Предыстория

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

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

  • @team команда, ответственная за данную часть системы;

  • @maintainer человек, разрабатывающий данную функциональность (таких сотрудников может быть несколько).

/** * @team Team name <team@example.com> * @maintainer John Smith <john.smith@example.com> * @maintainer .... */

Такой подход очень просто внедрить и использовать. Например, можно настроить шаблон в PhpStorm и нужные теги будут автоматически проставляться при создании нового файла. Мы ещё сделали отдельный Git hook, следящий за тем, чтобы у всех файлов были проставлены нужные теги в требуемом формате.

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

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

Так мы пришли к компонентному подходу.

Что такое компонент

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

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

При переходе на компонентный подход мы сформировали ряд правил и ограничений:

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

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

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

  • только менеджер или тимлид команды может добавлять и удалять компоненты;

  • у каждого компонента должен быть уникальный идентификатор (alias).

Для управления компонентами у нас есть специальный интерфейс в интранете. Страница компонента выглядит следующим образом:

Пример страницы компонента в интранете Пример страницы компонента в интранете

Мы видим, что у компонента есть:

  • уникальный идентификатор;

  • email;

  • название команды, которая отвечает за данный компонент;

  • название проекта, к которому относится компонент, в Jira;

  • краткое описание, которое помогает понять, для чего нужен компонент и какая у него зона ответственности;

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

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

Докблок файла

/** * @component component_alias */

Мы доработали Git hook, проверяющий докблок. Он следит за тем, чтобы файлы, в которые были внесены изменения, содержали тег @component и чтобы указанный компонент существовал.

remote: ERROR in SomeClass.php:        remote: * Unknown @component: UnknownComponent. You have to create component before using it in the code   

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

Сервис для работы с компонентами в коде

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

$componentManager = new \Components\ComponentManager();$component = $componentManager->getComponent('component_alias');$recipients = [];foreach ($component->getMaintainers() as maintainer) {    $recipients[] = $maintainer->getEmail();}

или найти дежурного по компоненту:

$componentManager = new \Components\ComponentManager();$component = $componentManager->getComponent('component_alias');foreach ($Component->getMaintainers() as $maintainer) {    if ($maintainer->isDuty()) {        return $maintainer;    }}

Интеграция с PhpStorm

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

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

Дежурный по компоненту

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

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

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

Кроме того, дежурства позволяют делиться знаниями внутри команды, тем самым повышая bus factor.

Интеграция с внутренними системами

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

Система сбора и анализа PHP-ошибок

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

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

Эту информацию можно использовать:

  • для поиска ошибок по определённому компоненту;

  • для построения отчётов и графиков в разбивке по компонентам.

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

Реестр баз данных

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

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

  1. Найти в коде, на каком хосте живёт база.

  2. Подключиться к хосту через любой удобный инструмент (консольная утилита, phpMyAdmin, Sequel Pro, IDE и т. д.).

  3. Найти нужную базу и таблицу.

  4. Изучить информацию о таблице.

А если нужно узнать размер таблицы на продакшене?

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

Другой пример: нужно найти список таблиц, которые долгое время не используются и просто занимают место на сервере. Тут уже нужно писать скрипт, который обойдёт все нужные серверы и соберёт статистику.

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

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

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

Заключение

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

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

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

Подробнее..

Дайджест Joomla за весну 2021

15.06.2021 00:15:33 | Автор: admin

Все главные новости из мира Joomla за осень 2020 и весну 2021, в одной статье.

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

Главные новости о Joomla

Начнем с того, что в 2020 году Joomla исполнилось 15 лет. 17 августа 2005 года состоялся первый публичный релиз Joomla 1.0. За это время было фактически 15 мажорных выпусков CMS, а всего было выпущено официально более 80 релизов Joomla.

С июня 2020 года вышло 7 релизов Joomla: с 3.9.20 по 3.9.27.

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

Релизы Joomla 4 и Joomla 3.10

Joomla 4

Joomla 4 в 2020 году прошла путь от Beta 2 до RC1. Проделана работа по включению в Joomla 4 css-фреймворка Bootstrap 5 и отказу от js-библиотеки jQuery. Bootstrap 5 используется как в панели администратора, так и в шаблоне по умолчанию Cassiopea.

Cообществом Joomla начата работа над локализацией Joomla 4 на русский язык. Переводчики всегда рады помощи в переводе Joomla 4 и исправлении ошибок.

Github локализаторов:https://github.com/JPathRu/localization

Joomla 3.10

Переход с Joomla 3.9 на Joomla 4.0 является миграцией. В Joomla 3.10 добавили новую функцию в компонент обновления Joomla, чтобы помочь в процессе мини-миграции: инструмент проверки перед обновлением (Pre-update Checker). Эта ветка развивается исключительно для облегчения миграции с Joomla 3 на Joomla 4 и будет поддерживаться 2 года с момента выпуска.

Статьи о Joomla

Аутентификация на основе токенов - как использовать ее в Joomla 4

Англоязычная статья и видео урок о новых возможностях Joomla 4. В статье рассказано об аутентификации без пароля (аутентификация на основе токенов) и API веб-сервисов Joomla.

Видео урок

Статья

90 баллов в Pagespeed для сайта на Joomla

Небольшая статья с рекомендациями по увеличению скорости загрузки сайта на Joomla и достижению заветной зеленой зоны по Google Page Speed. В качестве подопытного выбран сайт на на базе фреймворка Astroid от JoomDev.

Статья

Работа с шаблонами e-mail в Joomla 4

В Joomla 4 появится новая функция - шаблонизация HTML писем. В данной статье проведен небольшой мастер класс по настройке шаблонов писем для различных компонентов. Статья освещает важный момент мультиязычности рассылаемых писем.

Статья на английском

Пример обертки над API Joomla 4

На Github стали появляться примеры для оберток над API Joomla 4, что позволяет рассматривать Joomla 4 в качестве бэкенда для реактивных сайтов и сервисов.

Ссылка на Github

Официальный сайт

Информация об API Joomla

Google заявила о спонсировании разработки Joomla

В рамках инициативы CMS с отрытым исходным кодом компания Google объявила о спонсорстве CMS Joomla. Google предоставит контент, который охватывает новейшие передовые практики, технологии и инструменты, направленные на повышение удобства работы пользователей, информационную поддержку. Финансовая поддержка коснется проектов, направленных на повышение производительности, конфиденциальности и безопасности, а также на поддержку новых возможностей веб-платформы.

Статья

Большой мануал по созданию каталога на базе полей Joomla

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

Статья

Интеграция форм обратной связи и Битрикс 24 на сайте Joomla

Небольшой мастер класс по интеграции CRM Битрикс 24 и сайта на CMS Joomla.

Статья

Обзор новой функции в Joomla 4: процессы публикации

В Joomla 4 появилась новая функция Workflows. В данной статье рассказано о новой функции. В русской локализации Joomla 4 термин Workflows будет переведен, как Процессы.

Статья

Статьи о подготовке шаблонов и расширений к совместимости с Joomla 4.

В официальном журнале Joomla регулярно выходят статьи о подготовке шаблонов и расширений для Joomla 4.

Тесты

Результаты тестирования Joomla 3.9 под PHP 8

По последним результатам тестирования Joomla показала относительно небольшой прирост под PHP 8.

Так что если вы сейчас используете хотя бы PHP 7.3 можете продолжать его использовать и не беспокоиться о том, что PHP 8 откроет вам новые горизонты. Однако если вы до сих пор используете PHP 5.6, то переход на PHP 7.4 или на 8, даст почти 35% к производительности.

Результаты тестов

Хотелось бы отметить, что многие подчеркивают производительность WordPress по сравнению с Joomla в этом тесте. В реальности надо смотреть на сравнение WordPress 5.6 - WooCommerce 4.8.0 vs Joomla. Во всех подобных тестах всегда возникает вопрос, почему для теста WP и Drupal выбирается одна из самых легких страниц, а для теста Joomla одна из самых тяжелых, так как формирования страницы избранного с кучей модулей, гораздо тяжелее чем формирование страницы статьи.

Расширения для Joomla

YOOtheme Pro

За прошедший год вышло несколько релизов популярного шаблона и сайт-билдера YOOtheme PRO. Шаблон построен на css-фреймворке UiKit, разработанным командой YOOtheme.

Билдер имеет конструкт динамического контента, то есть можно не просто конструировать страницы из HTML блоков, а сконструировать допустим вывод блога категории или непосредственный вывод статьи.

Плагин jYProExtra

Это плагин, расширяющий возможности сайтбилдера YOOTheme Pro. Он позволяет использовать улучшение изображений YOOtheme Pro на всем сайте: отложенная загрузка и возможность использовать изображения в формате WebP. Дочерние темы, удаление JavaScript, постраничная навигация YOOTheme PRO во всех компонентах и многое другое.

Страница расширения

Pro2Store для Yootheme PRO

Это аддон интернет магазина для популярного билдера сайтов Yootheme PRO.

Магазин предназначен исключительно для Yootheme PRO и распространяется бесплатно. Платно лишь дополнительные плагины оплаты.

Видео

Страница расширения

Phoca Email - компонент e-mail рассылок

Все знают про Acymailing, и когда речь заходит про email-рассылки на базе Joomla, его обычно и рекомендуют, но на самом деле таких компонентов под Joomla множество. Phoca Email - это простой компонент управления подписками и рассылками для Joomla.

Возможности компонента:

  • Рассылка по подписчикам.

  • Замена шаблонов рассылки компонентов, таких как Vituemart или PhocaCart.

  • Отправка сообщений от лица администрации.

  • Поддержка HTML сообщений.

  • Функция отправки статьи с сайта.

Страница расширения

Helix Ultimate 2.0 от JoomShaper

Helix - это один из самых популярных шаблонов для Joomla, на базе, которого строятся многие клубные шаблоны. За прошедший год было выпущено несколько alpha и beta версий этого фреймворка. На момент написания обзора была доступна Beta 3 на базе Bootstrap 5.

Так же JoomShaper имеет свой конструктор страниц SP Page Builder, которым пользуются более 200000 разработчиков по всему миру. Основной упор JoomShaper делает на редактирование дизайна сайта с фронта.

Статья

Обновление CCK для Joomla ZOO 4.0

YOOtheme выпустила некогда мега популярный CCK для Joomla. Спустя 6 лет после последнего значимого релиза ZOO 3.3 CCK обновился до версии 4. По сути ZOO 4 выпущен для расширения возможностей Page Builder YOOtheme Pro.

Разработчики заявляют о тесной интеграции с YOOtheme Pro и не случайно все страницы ZOO выпущены в качестве шаблонов для YOOtheme Pro.

Видео

Официальная новость

Обновления JBZoo v4.11 и 4.12

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

За эти 2 релиза проделана большая работа, добавлено много новых элементов и хуков (оплата через Сбербанк, Юкасса; доставка СДЭК, Boxberry), новый роутинг.

Подробнее

JL Like v4.0.5

Бесплатный плагин социальных кнопок для Joomla.

WT JoomShopping Bitrix24 PRO

Бесплатный плагин для двухсторонней интеграции CRM Битрикс24 и интернет-магазина на базе JoomShopping. Плагин предоставляет гибкую настройку сопоставления полей CRM и данных интернет-магазина, позволяет создавать лиды и сделки на определенной стадии воронки продаж, ищет дубли контактов, передает UTM-метки. Позволяет изменять статусы заказа в интернет-магазине в зависимости от стадии лида или сделки.

Плагин интегрирован с профессиональным плагином формы обратной связи RadicalForm и позволяет создавать лиды из форм обратной связи, созданных с помощью RadicalForm.

Страница расширения

Видео-гайд по настройке

Обновление WT Virtuemart Bitrix24

Плагин позволяет отправлять данные о заказах Virtuemart в CRM Битрикс24 в виде лидов. Своеобразная лайт-версия аналогичного плагина для JoomShopping.

Видео

Страница расширения

Обновление модуля вывода материалов JUNewsUltra Pro 6.9

Модуль заточен под работу с большими нагрузками, работает на сайтах-новостниках с посещаемостью более 230 000 уникальных посетителей в сутки.

Основной функционал.

  • Шаблонизация внешнего вида.

  • Поддержка Youtube, RSS, Vimeo, JUMultiThumb, Content Multicategories, JComments, Komento.

  • Очень гибкие настройки работы с изображениями.

  • Большой список настроек вывода материалов.

Что нового в JUNewsUltra?

  • Поддержка отдельного формата изображений WebP

  • Адаптивная поддержка изображений WebP вместе с обычными через тег <picture>

  • Добавлена поддержка нативной ленивой загрузки loading="lazy"

  • Добавлена поддержка нативного свойства decoding="async"

  • Добавлена поддержка Joomla 4

  • далены зависимости jQuery в админке Joomla 4

Страница расширения

TCI Telegram Content Import для Joomla

Системный плагин для CMS Joomla!, обеспечивающий импорт постов из Telegram в материалы Joomla.

Парсер умеет автоматически наполнять сайт Joomla из телеграмм канала создавая:

  • Заголовок поста

  • Краткое и полное описание

  • Дату создания поста

  • Автора

  • Картинки для полного и краткого описания, а так же их описание.

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

  • Создавать теги на основе хештегов и выставлять избранное.

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

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

  • При наличии авторизации из телеграма позволяет полноценно использовать на сайте систему комментариев телеграма. Подключение производится посредством загрузки соответствующего скрипта API Telegram

Русскоязычное сообщество Joomla использует этот плагин для связи Joomlaportal.ru и телеграмм-канала сообщества.

Описание расширения

Компонент форума Kunena 5.2.1

Обновился один из самых популярных форумов для Joomla. В этом релизе значимое изменение, минимальной версией PHP стала 7.0.4. А главная цель релиза это поддержка PHP 8.

Официальная новость

Akeeba Engage v1.0

Компонент комментариев от Akeeba. Комментарии совместимы с Joomla 3 и Joomla 4.

Возможности компонента:

  • Поддержка HTML комментариев.

  • Разметка Schema.org

  • Подготовлен под AMP сайты

  • Email уведомления

  • Поддержка Gravatar

  • Полная поддержка Joomla, в том числе и журнал действий и GDPR.

  • Защита от спама: CAPTCHA и Akismet.

  • Есть позиции для вывода модулей.

  • Полная поддержка кеша Joomla.

Официальная новость

Convert Forms конструктор форм для Joomla

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

Возможности компонента:

  • Конструктор форм.

  • Более 20 полей для формы.

  • Шаблоны форм.

  • Интеграции с различными онлайн сервисами рассылок.

  • Различные сценарии действий после отправки форм.

  • Зависимые поля.

  • Ajax.

  • Уведомления на email.

  • Импорт - экспорт.

  • Настройки шрифтов, цветов, границ и форм.

  • Встраивание с помощью шорткодов и модулей.

  • Адаптивность.

  • Поддержка популярных западных CRM и сервисов.

https://www.tassos.gr/joomla-extensions/convert-forms

Профессиональный плагин форм обратной связи RadicalForm

За год вышло несколько релизов. Плагин позволяет отправлять сообщения не только на e-mail, но и в Телеграм, Verbox, Jivosite и даже SMS. При необходимости разработчики могут создавать свои дополнительные плагины и отправлять данные форм куда угодно.

Страница расширения

JD Builder 1.8.0 и совместимость билдера с Joomla 4

JoomDev представил обновление своего Page Builder для Joomla. Основным нововведением считает совместимость с Joomla 4 и Joomla 3.10.

Что нового?

  • Интеграция с ACYMailing.

  • Элемент прайс-листа.

  • Бесплатные шаблоны для пользователей PRO.

  • Изменения структуры настроек.

Небольшой обзор нового функционала

Quantum Manager 1.6.0 современный файловый менеджер для Joomla.

Бесплатный файловый менеджер для Joomla! с помощью которого Вы сможете загружать, редактировать и вставлять в редактор (а так же и пользовательские поля Joomla) файлы. Есть возможность переопределить вызовы стандартного файлового менеджера.

  • Интеграция с фотостоками.

  • Автоматическая обработка картинок.

  • Встроенные средства редактирования изображений.

  • Поддержка WebP

  • Удобная навигация и пакетная работа с файлами.

  • Разграничение по группам пользователям

  • Настройка цвета иконок файлов.

  • Интеграция с SP Page Builder.

  • Интеграция с YOOTheme Pro

Страница расширения

Radical Multi Fields 3.0.1

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

Основная фишка этого обновления тесная интеграция с Quantum Manager v1.7.0.

Страница расширения

Обновление CFI v.1.0.6

CFI - плагин для импорта и экспорта данных стандартных материалов и кастомных (настраиваемых) полей.

Страница расширения

WT SEO Meta templates плагин сео-шаблонов для

Позволяет использовать сео-формулы (шаблоны, маски) для тега <title> и meta-тега description, например Купить {PRODUCT_NAME} в {CITY_NAME} за {PRODUCT_PRICE}. Принимает данные (в том числе и сео-шаблоны) из дополнительных плагинов-провайдеров.

На данный момент доступны плагины-провайдеры для категорий, материалов Joomla и их пользовательских полей, а так же компонентов Virtuemart и My City Selector.

https://web-tolk.ru/dev/joomla-plugins/wt-seo-meta-templates.html

Подробнее..

PHP Дайджест 205 (1 15 июня 2021)

15.06.2021 00:15:33 | Автор: admin


Подборка свежих новостей и материалов из мира PHP. В выпуске: первая альфа PHP 8.1.0, Composer 2.1, Symfony 5.3 и другие релизы. Обзор новых предложений для PHP 8.1: Partial Function Application, pipe оператор, readonly свойства. А также порция полезных инструментов, статьи, видео и подкасты.

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

Новости


  • PHP 8.1.0 alpha 1


    Вышел первая альфа и тем самым стартовал рели-процесс PHP 8.1. Обновления будут выходить каждые две недели по расписанию. Финальный релиз запланирован на 25 ноября.

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

    • Enum они же перечисления RFC;
    • Новый тип never для возвращаемых значений RFC;
    • Файберы RFC;
    • Финальные константы в классах RFC;
    • Оператор распаковки поддерживает массивы со строковыми ключами RFC;
    • Объявлено устаревшим преобразование float в int, где теряется дробная часть RFC;
    • Интерфейс Serializable объявлен устаревшим RFC;
    • Запись восьмеричных чисел с префиксом 0o RFC;
    • Ограничено использование $GLOBALS RFC;

    Полный список изменений можно посмотреть на php.watch/versions/8.1.

  • PHP 8.0.7, PHP 7.4.20


    Багфикс релизы актуальных веток.

  • Стартовала программа раннего доступа PhpStorm 2021.2


    Каждую неделю публикуем новые билды, которые можно использовать бесплатно. А также анонсируем то, над чем идет работа в релизе.
    Уже доступны: поддержка енамов PHP 8.1, переработанный и улучшенный рефакторинг Extract Method, исправлены ошибки форматирования.

  • Composer 2.1.0


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

  • У каждого пакета на packagist.org теперь есть статистика по PHP-версиям


    Один из авторов Composer, Jordi Boggiano, каждые полгода публиковал в блоге пост со статистикой используемых версий PHP.

    Теперь вместо блога, эта общая статистика всегда доступна на packagist.org/php-statistics.

    Кроме того, у каждого пакета есть своя подобная страница, например, symfony/console/php-stats.

  • PHP Russia 2021


    Конференция состоится уже 28 июня. Программа сформирована habrничего лишнего, только хардкор, только технологии.

    Для читателей дайджеста есть промокод со скидкой: php_digest.


PHP Internals


  • [RFC] Partial Function Application


    Предложение было существенно переработано и объединено с более узким RFC от Никиты First-class callable syntax.

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

    Итого предлагается три способа получить ссылку на функцию:
    1. $func = some_func(...) так можно получить ссылку на любую функцию. Собственно, предложение Никиты.
    2. $func = some_func(1, 2, ?, 5) так можно получить ссылку с одним аргументом, что может быть полезно для различных колбэков.
    3. $func = any_func($all, $params, ...) так можно передать все аргументы в функцию, но при этом не вызывать ее. Ссылку позже можно вызвать, не передавая никаких параметров.

  • [RFC] Pipe Operator v2


    Если предложение выше пройдет голосование, то пайп-оператор станет его логичным продолжением.

    Вместо вложенных вызовов типа:

    array_filter(array_map('strtoupper', str_split(htmlentities("Hello World"))), fn($v) => $v != 'O');
    

    можно будет писать более понятные цепочки вида:

    $result = "Hello World"    |> htmlentities(?)    |> str_split(?)    |> array_map(strtoupper(?), ?)    |> array_filter(?, fn($v) => $v != 'O');
    

  • [RFC] Pure intersection types


    Предложение добавить пересечения типов находится на голосовании и похоже, что преодолеет необходимый порог. Тем временем можно послушать подкаст audioPHP Internals News #88 с George Peter Banyard, автором RFC.

  • [RFC] Readonly properties 2.0


    В качестве альтернативы довольно сложному и громоздкому предложению по акссессорам свойств сам же Никита выдвинул на рассмотрение RFC по readonly свойствам.

    Предлагается добавить модификатор readonly для свойств. Такие свойства нельзя будет изменить после инициализации.

    Скрытый текст
    class Test {    public readonly string $prop;    public function __construct(string $prop) {        // Legal initialization.        $this->prop = $prop;    }}$test = new Test("foobar");// Legal read.var_dump($test->prop); // string(6) "foobar"// Illegal reassignment. It does not matter that the assigned value is the same.$test->prop = "foobar";// Error: Cannot modify readonly property Test::$prop
    


    А в комбинации с constructor property promotion из PHP 8.0, можно будет сократить вообще до вот такого:

    class User {    public function __construct(        public readonly string $name    ) {}}$user = new User('Roman');echo $user->name; // Ok$user->name = 'Nikita'; // Error
    

  • [RFC] Make reflection setAccessible() no-op


    Сейчас чтобы получить доступ к свойству или методу через рефлексию, надо обязательно предварительно вызвать ->setAccessible(true).

    Marco Ocramius Pivetta предлагает убрать этот вызов, то есть ReflectionProperty и ReflectionMethod будут вести себя так, как если бы уже был вызван setAccessible(true).

    class Foo { private $bar = 'a'; }(new ReflectionProperty(Foo::class, 'bar'))->getValue();
    



Инструменты


  • nunomaduro/php-interminal Инструмент для чтения PHP Internals обсуждений в терминале. Пока умеет выводить только последние сообщения, но выглядит красиво.
  • joonlabs/php-graphql PHP-реализация спецификаций GraphQL. Автор утверждает, что быстрее чем другие реализации.
  • spiral/attributes Позволяет читать атрибуты из PHP 8 на PHP 7.2+ и дополнительно может работать с аннотациями доктрины. Фреймворк-агностик и для работы требует лишь nikic/php-parser. Прислал SerafimArts.
  • spiral/storage Компонент для работы с распределёнными файловыми хранилищами. Работает поверх thephpleague/flysystem и предоставляет более удобный API. Прислал SerafimArts.
  • kalessil/production-dependencies-guar Предотвращает добавление дев-пакетов в секцию require в composer.json.

    В тему у Валентина Удальцова на канале Пых была заметка с идеями проверок на CI.


Symfony




Laravel




Yii




Статьи




Аудио/Видео





Подписывайтесь на Telegram-канал PHP Digest.

Если вам понравился дайджест, поставьте, пожалуйста, ему плюс это очень мотивирует продолжать делать.

Заметили ошибку или опечатку? Сообщите в личку хабра или телеграм.

Прислать ссылку можно через форму или просто написав мне в телеграм.
Поиск ссылок по всем дайджестам
Предыдущий выпуск: PHP-Дайджест 204

Подробнее..

Велосипед длиной в полжизни

25.05.2021 20:20:29 | Автор: admin
Вперёд, в будущее!Вперёд, в будущее!

Начало

Да, именно так: я начинал писать основу PHP движка в 2001-ом году.

Тогда всё было проще: каталог inc/, в нём header.php, footer.php, common.php.

Но это было скучно и неинтересно, надо было ещё больше фишек, которые я бы мог предложить заказчикам. Так появился раздел admin/. Но примерно в тоже время появился PHPNuke, это была бомба! Такой уровень кастомизации! Я понял, чтоничего подобного я предложить не смогу, но и по требованиям мой движок былкуда как проще, хотя позволял организовать каталог товаров, список статей, галерею. Несколько лет я дальше двигался по основной своей работе: администрирование Linux-серверов.

Но как-то мне предложили работу в фирме именно PHP-разработчиком. Я ознакомилсяс проектом, и меня порадовали две вещи:

1. Все шаблоны в своём формате.

2. И они вместе с настройками лежат в базе данных.

Это же прекрасно! Какая гибкость! Ну и пусть на открытие заглавной страницы уходит 50+ запросов!С таблиц, в которых и 100 строк бывает редко, MySQL делает выборки мгновенно.

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

Ещё с той работы я привнёс в свою работу Subversion: это было круто! Пара команд - и мой домашний репозиторий с рабочим синхронизирован за бесплатные 60 секунд через 8w180! А когда начальство говорит что я нифига не делал то легко подвести статистику добавленных/изменённых строк! Работал я тогда,как правило,сдельно

Продолжение

Так я "проспал" появление AJAX, появление ООП в PHP, хотя последнее я просто не воспринял: зачем, когда у тебя в inc/ теперь уже не три файла, а пять, но всего пять и всё работает?

И да, примерно в то время благодаря одному из своих друзей я поставил на основной сервер Gentoo. Это было круто! Совсем другой уровень управления сервером! Когда на основном сервере стоит и абсолютно правильно работает Gentoo - на админов всяких Редхатов и Дебианов смотришь известно как :)

А как программист я стагнировал :( Переломным моментом должен был стать сайт для моих друзей с продвинутым каталогом товаров: тогда количество костылей в catalog/index.phpпереполнило все мыслимые пределы. Там на несколько лет появилась строка

$input["list_id"];

И всё. Просто написал! Ничего ничему не присваивается, никак не обрабатывается!

Но я несколько лет не удалял эту строку, она просто была незаметна на фоне огромного спагетти кода подпёртого костылями.

И тут очень хороший человек предлагает для его сайта сделать весьма интересный функционал. Ладно, я готов к новым испытаниям. Я тогда не знал, что такое Битрикс... Специалисты по нему, увидев мой код, могут упасть в обморок: я писал исключительно прямыми запросами к базе данных через $DB->Query. Часто они бывали весьма многострочными, но это всё равно было лучше, чем использовать недоORM Битрикса. И да, сколько вечеров было проведено, пока мы с другим очень хорошим человеком разгребали почему штатная выгрузка с 1С не работает! И тогда был курьёзный случай: я уже в модуль ядра полез, чтобы логирование добавить чтобы узнать на каком именно элементе каталога стопорится выгрузка, коллега подходит и говорит: Игорь, это же нифига не английский!? И тут я понимаю, что это, блин, немецкий: функции и переменные в XML парсере Битрикса на немецком! А я по запарке даже и не заметил! Хоть раз в карьере пригодился язык, который я в школе и институте учил. Проект был выполнен, но сейчас, к сожалению, он оказался никому не нужен :(

Позднее, когда пара людей мне предложили развить интересный проект на Yii1, я понял,как много я упустил за эти годы! Но свою CMS я не трогал особо:и страшно и вроде как не нужно. Проект на Yii1 в итоге не взлетел, но это был бесценный опыт. И когда мне предложили сделать свой, достаточно серьёзныйпроект, с нуля я недолго раздумывал: Yii2. С базой данных у меня не было сомнений: MariaDB, ибо с MySQL я, будучи сисадмином, научился делать EXPLAIN, чтобы запрос вместо трёх минут выполнялся меньше секунды.

Прозрение

И тут я споткнулся об реальность: я же толком никогда ООП не использовал в PHP. Спасибо видеоурокам Дмитрия Елисеева и шаблону приложения от Vova07. За пару месяцев я написал CRUD'ы для админского раздела, разумеется используя миграции и RBAC, дальше самразобрался как сделать REST API, разбил всё на модули и так далее. Но сегодня не об этом.

В своей CMS всё так же несколько файлов в каталоге inc/. Для приличия переименовал вinclude/. Поломал обратную совместимость :( На сервере ln -s ./inc ./include, но даже полному идиоту понятно, что это нереальный костыль :( Собираю и раскидываю функции по include/lib_*, лезть в скрипты, касающиеся каталога, боязно. Но тут опять же спасибо Дмитрию Елисееву: его курс про микрофрэймворк стал определяющим: нафиг свой шаблонизатор, когда есть Twig? Какие ещё отдельные JS скрипты и CSS файлы, когда благодаря laravel-mix можнов пару команд собирать бандл? Причём вместо CSS использовать SASS, который для меня сталнебольшим, но всё же открытием, и я за минут двадцать все стили перевёл на него.

А тесты!.. Это отдельная тема. С начала своей карьеры как сисадмина я видел, что в руководствах часто между make и make install бывает make test. Как долго идёт ./configure с ключами и при этом много что тестируется, какие ещё тесты нужны? Но тут попробовал - и понравилось! Меняю что-то в ключевых классах, запускаю composer test - и как приятно видеть, что они проходят! А если не проходят, то сразу понятно, где я что поломал. К сожалению, связность в моей CMS очень сильная, TDD я попробовал, но этот подход при такой связности скорее замедляет разработку, чем помогает :(

В итоге решил переписать 100500 костылей под нормальную архитектуру.

Решение

Это было непросто. Особенно морально: сисадмин внутри меня даже не говорил - кричал:"Работает? Не трожь! Обратную совместимость поломать решил? Идиот, сам же будешьчинить!". Но жребий был брошен, Рубикон перейдён: начав с самого простого: модуля статей, я в итоге переписал все модули, даже модуль каталога товаров. Да, я кодом недоволен и сейчас: до чистого кода далеко. Но код ради кода - это не то, к чему я стремлюсь: например DI контейнер в такой простой задаче будет перебором. Хотя кроме DI контейнера можно многиепрактики привнести, например, использовать request, response, log, cache, да и сам контейнерпо PSR, но я пока не вижу смысла тащить такие зависимости в проект :)Ведь чем меньше зависимостей тем проект надёжнее. Но для миграций я добавил в проект Phinx, который за собой притянул symfony/console и фреймворк Сakephp. Первый грех было не использовать а с Сake я в итоге взял модули кэширования и логер: зачем в очередной раз писать свои костыли или тянуть зависимости если эти компоненты и так уже в проекте?

Итог

В итоге это был непростой путь, но я доволен результатом. Конечно, впереди ещё многое предстоит сделать, но уже сейчас я готов поделиться своими наработками с сообществом. Жаль, что заказчики не видят разницы между набором костылей и более чистым кодом, в который можно вносить изменения, не опасаясь выбить карту на которой весь костыльный домик держится. Но оно того стоило, однозначно!

Ах, да: https://github.com/crlam0/cms

Подробнее..
Категории: Linux , Php , Yii , Yii2 framework

Книга Создаем динамические веб-сайты на PHP. 4-е межд. изд.

24.05.2021 18:21:26 | Автор: admin
image Привет, Хаброжители! Сложно найти что-то толковое про PHP? Проверенная временем, обновленная в четвертом издании, эта книга помогает начинающим разработчикам научиться всему, что необходимо для создания качественных веб-приложений.

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

Вы получите множество рекомендаций по стилю программирования и процессу разработки ПО от Кевина Татро и Питера Макинтайра. Этот материал, изложенный в доступной и компактной форме, поможет вам овладеть мастерством программирования на PHP. Общие сведения о том, какой результат можно получить, используя PHP. Основы языка, включая типы данных, переменные, операторы, управляющие команды. Функции, строки, массивы и объекты. Решение распространенных задач разработки: обработка форм, проверка данных, отслеживание сеансовых данных и cookie. Работа с реляционными базами данных (MySQL) и базами данных NoSQL (например MongoDB). Генерирование изображений, создание файлов PDF, парсинг файлов XML. Безопасность скриптов, обработка ошибок, оптимизация быстродействия и другие нетривиальные темы.

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

Регулярные выражения


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

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

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

Большинство символов в регулярных выражениях являются литеральными, что отражается на поиске совпадений. Например, если вы ищете совпадение для регулярного выражения "/cow/" в строке Dave was a cowhand, то совпадение будет найдено, потому что последовательность символов cow встречается в этой строке.

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

preg_match("/^cow/", "Dave was a cowhand"); // возвращает falsepreg_match("/^cow/", "cowabunga!"); // возвращает true

Аналогичным образом символ $ в конце регулярного выражения означает, что совпадение должно завершаться в конце строки (то есть обозначает привязку регулярного выражения к концу строки).

preg_match("/cow$/", "Dave was a cowhand"); // возвращает falsepreg_match("/cow$/", "Don't have a cow"); // возвращает true

Точка в регулярном выражении обозначает один любой символ:

preg_match("/c.t/", "cat"); // возвращает truepreg_match("/c.t/", "cut"); // возвращает truepreg_match("/c.t/", "c t"); // возвращает truepreg_match("/c.t/", "bat"); // возвращает falsepreg_match("/c.t/", "ct"); // возвращает false

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

preg_match("/\$5.00/", "Your bill is $5.00 exactly"); // возвращает truepreg_match("/$5.00/", "Your bill is $5.00 exactly"); // возвращает false

Регулярные выражения по умолчанию учитывают регистр символов, поэтому регулярное выражение "/cow/" не совпадет со строкой COW. Чтобы выполнить поиск совпадения символов без учета регистра, установите соответствующий флаг (показан далее в этой главе).

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

1. Набор допустимых символов, которые могут присутствовать в строке (например, алфавитные символы, цифры, конкретные знаки препинания).

2. Набор альтернатив для строки (например, com, edu, net или org).

3. Повторяющиеся последовательности в строке (например, как минимум одна, но не более пяти цифр).

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

Символьные классы

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

preg_match("/c[aeiou]t/", "I cut my hand"); // возвращает truepreg_match("/c[aeiou]t/", "This crusty cat"); // возвращает truepreg_match("/c[aeiou]t/", "What cart?"); // возвращает falsepreg_match("/c[aeiou]t/", "14ct gold"); // возвращает false

Движок регулярных выражений находит в строке символ c, после чего проверяет, является ли следующий символ гласной буквой (a, e, i, o или u). Если нет, то движок переходит к поиску следующего символа c. Если да, движок проверяет, является ли следующий символ буквой t. Если совпадение обнаружено, движок возвращает true или, в противном случае, возобновляет поиск следующего символа c.

Символьный класс можно инвертировать, поставив символ ^ в начало перечисления символов:

preg_match("/c[^aeiou]t/", "I cut my hand"); // возвращает falsepreg_match("/c[^aeiou]t/", "Reboot chthon"); // возвращает truepreg_match("/c[^aeiou]t/", "14ct gold"); // возвращает false

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

Символ (дефис) в символьных классах используется для определения диапазонов символов. Он упрощает определение таких символьных классов, как все буквы и все цифры:

preg_match("/[0-9]%/", "we are 25% complete"); // возвращает truepreg_match("/[0123456789]%/", "we are 25% complete"); // возвращает truepreg_match("/[a-z]t/", "11th"); // возвращает falsepreg_match("/[a-z]t/", "cat"); // возвращает truepreg_match("/[a-z]t/", "PIT"); // возвращает falsepreg_match("/[a-zA-Z]!/", "11!"); // возвращает falsepreg_match("/[a-zA-Z]!/", "stop!"); // возвращает true

Когда вы задаете символьный класс, некоторые специальные символы теряют свой смысл, тогда как другие, наоборот, приобретают новые роли. Так, якорный символ $ и точка теряют свои роли в символьных классах, тогда как символ ^ уже не обозначает привязку к началу строки, а инвертирует символьный класс, если является первым символом после открывающей квадратной скобки. Например, [^\]] совпадает с любым символом, кроме закрывающей квадратной скобки, тогда как [$.^] совпадает с любым из трех знаков (доллар, точка или крышка).

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

Символ | (вертикальная черта) используется для определения альтернатив в регулярных выражениях:

preg_match("/cat|dog/", "the cat rubbed my legs"); // возвращает truepreg_match("/cat|dog/", "the dog rubbed my legs"); // возвращает truepreg_match("/cat|dog/", "the rabbit rubbed my legs"); // возвращает false

Приоритет применения альтернатив может показаться странным: так, "/^cat|dog$/" выбирает один из двух вариантов "^cat" и dog$. Это означает, что совпадение будет найдено в строке, которая либо начинается с cat, либо завершается dog. Если вам нужна строка, содержащая только cat или dog, используйте регулярное выражение "/^(cat|dog)$/".

Символьные классы и альтернативы могут применяться, например, для проверки строк, которые не должны начинаться с буквы в верхнем регистре:

preg_match("/^([a-z]|[0-9])/", "The quick brown fox"); // возвращает falsepreg_match("/^([a-z]|[0-9])/", "jumped over"); // возвращает truepreg_match("/^([a-z]|[0-9])/", "10 lazy dogs"); // возвращает true

Повторяющиеся последовательности

Для определения паттернов с повторениями используются квантификаторы. Квантификатор определяет, сколько раз выбранный фрагмент должен повториться. В табл. 4.6 перечислены квантификаторы, поддерживаемые регулярными выражениями PHP.

Чтобы искать повторы отдельного символа, просто поставьте квантификатор за символом:

preg_match("/ca+t/", "caaaaaaat"); // возвращает truepreg_match("/ca+t/", "ct"); // возвращает falsepreg_match("/ca?t/", "caaaaaaat"); // возвращает falsepreg_match("/ca*t/", "ct"); // возвращает true

image


С квантификаторами и символьными классами можно решать такие задачи, как проверка на действительность телефонных номеров США:

preg_match("/[0-9]{3}-[0-9]{3}-[0-9]{4}/", "303-555-1212"); // возвращает truepreg_match("/[0-9]{3}-[0-9]{3}-[0-9]{4}/", "64-9-555-1234"); // возвращает false

Подпаттерны

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

preg_match("/a (very )+big dog/", "it was a very very big dog"); // возвращаетtruepreg_match("/^(cat|dog)$/", "cat"); // возвращает truepreg_match("/^(cat|dog)$/", "dog"); // возвращает true

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

preg_match("/([0-9]+)/", "You have 42 magic beans", $captured);// возвращает true и заполняет $captured

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

Ограничители

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

preg_match("/\/usr\/local\//", "/usr/local/bin/perl"); // возвращает truepreg_match("#/usr/local/#", "/usr/local/bin/perl"); // возвращает true

Скобки круглые (), фигурные {}, квадратные [] и угловые <> тоже могут использоваться в качестве ограничителей паттернов:

preg_match("{/usr/local/}", "/usr/local/bin/perl"); // возвращает true

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

Следующие два паттерна эквивалентны, но второй читается намного проще:

'/([[:alpha:]]+)\s+\1/''/( # начать сохранение[[:alpha:]]+ # слово\s+ # пробел\1 # снова то же слово) # завершить сохранение/x'

Поведение при поиске совпадения

Точка. совпадает с любым символом, кроме символа новой строки (\n). Знак $ совпадает с концом строки или, если строка завершается символом новой строки, с позицией, непосредственно предшествующей этому символу:

preg_match("/is (.*)$/", "the key is in my pants", $captured);// $captured[1] содержит 'in my pants'

Символьные классы

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

image

image

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

[@[:digit:][:upper:]]

Однако символьный класс не может использоваться как конечная точка диапазона:
preg_match("/[A-[:lower:]]/", string);// недопустимое регулярное выражение
Символьная последовательность, которая в локальном контексте рассматривается как один символ, называется сверткой. Чтобы найти совпадение для одной из многосимвольных последовательностей в символьном классе, заключите ее в маркеры [. и .]. Например, если в локальном контексте присутствует свертка ch, следующий символьный класс будет совпадать с s, t или ch:

[st[.ch.]]

Последнее расширение символьных классов класс эквивалентности, для определения которого символы заключаются в [= и =]. Классы эквивалентности совпадают с символами, имеющими одинаковый приоритет упорядочения по правилам локального контекста. Например, локальный контекст может определять, что символы a, и имеют одинаковый приоритет упорядочения при сортировке. Чтобы найти совпадение для любого из них, используйте класс эквивалентности [=a=].

Более подробно с книгой можно ознакомиться на сайте издательства
Оглавление
Отрывок

Для Хаброжителей скидка 25% по купону PHP

По факту оплаты бумажной версии книги на e-mail высылается электронная книга.
Подробнее..

Наследование шаблонов в ванильном PHP за 35 строк кода?

29.05.2021 12:13:17 | Автор: admin

Попал мне как-то под руку проект на WordPress (WP), где понадобилось сделать кастомную тему. В WP шаблоны нативные, что хорошо, - не надо учить дополнительный язык. Но очень захотелось понаследовать шаблоны как в Twig, а PHP из коробки так не умеет.

Осталось решить вопрос наследования. После изучения проблемы, вдохновляться было решено библиотекой phpti, в которой нашлось пару моментов, которые очень захотелось поправить:

  • Автор библиотеки большими буквами написал Every Block is Always Executed!, т. е. все блоки выполняются, даже если переопределены, и никогда не будут выведены.

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

  • Третий момент - хочу ещё быстрее. В библиотеке активно используется ob_start на чём и попробуем сэкономить пару спичек.

Библиотека phpti построена вокруг основной конструкции startblock/endblock и наследования при помощи import в начале файла:

// layout.php<!-- разметка --><?php startblock('blockName') ?><?php endblock() ?><!-- разметка -->
// index.php<?php include 'layout.php' ?> <!-- указываем родительский шаблон --><?php startblock('blockName') ?>    <!-- контент блока --><?php endblock() ?>

Некоторые наблюдения:

  • Вызовы вроде start/end можно заменить на анонимную функцию. Это избавит от необходимости сопоставлять вложенные старты и энды, а также сделает код контента ленивым.

  • Родительский шаблон указывается сверху файла. Это значит, что придётся сначала отрабатывать родительский, а потом дочерний шаблон. А значит придётся много чего буферизировать. А почему бы родителя не указать в конце?

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

С учётом вышесказанного, переделаем конструкцию на следующую:

// layout.php<!-- разметка --><?php slot('blockName', function(){ ?><?php }) ?><!-- разметка -->
// index.php<?php block('blockName', function(){ ?>    <!-- контент блока --><?php }) ?><?php include 'layout.php' ?> <!-- указываем родительский шаблон -->

Разделив блоки на slot и block, библиотеке больше не нужно пытаться понять, когда нужно выводить результат, а когда нужно только переопределить блок.

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

root.php - базовый шаблон, внизу иерархии:

<!DOCTYPE html><html>  <head>    <title><?php slot('title') /* слот - место в шаблоне */ ?></title>  </head>  <body>    <div id="root">      <?php slot('body', function () { /* слот с контентом по дефолту */?>        <p>'body' :: root.php</p>      <?php }) ?>    </div>  </body></html>

two-columns.php - промежуточный шаблон:

<?php block('title', function () { /* блок - контент для вставки в слот */?>  Title :: two-columns.php<?php });block('body', function () { ?>  <div id="two-columnts">    <div id="main">      <?php slot('main', function () { /* слот внутри блока */?>    <p>'main' :: two-columns.php</p>  <?php }) ?></div><div id="side">  <?php slot('side', function () { ?><p>'side' :: two-columns.php</p>  <?php }) ?></div>  </div>  <div id="footer">    <?php slot('footer', function () { ?>  <p>'footer' :: two-columns.php</p><?php }) ?>  </div><?php });include './root.php'; // наследуем от root.php

index.php - страница сайта, верхний шаблон:

<?phprequire_once '../src/InheritTpl.php'; block('title', function () { ?> 'title' :: index.php <?php });block('side', function () { ?>  <p>'side' :: index.php</p><?php }); block('main', function () { ?>  <div id="main-index"> <!-- Обернём содержимое от родителя -->    <?php super() /* тут выводим контент из родительского блока */?>  </div><?php });block('main', function () { /* Ещё раз тот же блок, почему бы и нет? */?>  <div id="main-index"> <!-- И ещё раз обернём содержимое -->    <?php super() ?>  </div><?php });// А 'footer' пусть остаётся как былinclude './two-columns.php';

Результат рендеринга (отформатирован для читабельности):

<!DOCTYPE html><html>  <head>    <title> 'title' :: index.php </title>  </head>  <body>    <div id="root">      <div id="two-columnts">        <div id="main">          <div id="main-index"> <!-- Обернём содержимое от родителя -->            <div id="main-index"> <!-- И ещё раз обернём содержимое -->              <p>'main' :: two-columns.php</p>            </div>          </div>        </div>        <div id="side">          <p>'side' :: index.php</p>        </div>      </div>      <div id="footer">        <p>'footer' :: two-columns.php</p>      </div>    </div>  </body></html>

Хотелки наследования удовлетворены. Но вот интересно, удалось ли сделать эту штуку быстрее?

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

Сравнивать будем время 10,000 рендеров на PHP 8.0.2 и процессоре 3.6ГГц.

  • phpti: 0.831 секунд

  • сабж: 0.353 секунд

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

Посмотреть исходный код можно тут.

Подробнее..
Категории: Php , Template , Inheritance , Native , Plain

Чистим пхпшный код с помощью DTO

31.05.2021 00:23:47 | Автор: admin

Это моя первая статья, так что ловить камни приготовился.

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

Я постоянно встречаю код, где если у метода больше двух входных аргументов, добавляется условный (array $args), что влечет за собой реализацию проверки наличия ключа, либо она отсутствует и тогда увеличивается вероятность того, что метод может закрашиться в рантайме.

Возможно, такой подход в PHP сложился исторически, из-за отсутствия строгой типизации и такого себе ООП. Ведь как по мне, то только с 7 версии можно было более-менее реализовать типизацию+ООП, используя strict_types иtype hinting.

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

$userService->create([      'name' => $object->name,      'phone' => $object->phone,      'email' => $object->email,  ]);

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

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

Собственно, так и появился мой пакет.

Использование ClassTransformer

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

class UserController extends Controller {public function __construct(      private UserService $userService,) {}public function createUser(CreateUserRequest $request){      $dto = ClassTransformer::transform(CreateUserDTO::class, $request);      $user = $this->userService->create($dto);      return response(UserResources::make($user));}}
class CreateUserDTO{    public string $name;    public string $email;    public string $phone;}

В запросе к нам приходит массив параметров: name, phone и email. Пакет просто смотрит есть ли такие параметры у класса, и, если есть, сохраняет значение. В противном случае просто отсеивает их. На входе transform можно передавать не только массив, это может быть другой object, из которого также будут разобраны нужные параметры.

Но наименования аргументов могут отличаться. Тогда, в созданной нами DTO, мы можем спокойно описать свою реализацию приведения:

class CreateUserDTO{    public string $name;    public string $email;    public string $phone;        public static function transform(mixed $args):CreateUserDTO    {        $dto = new self();        $dto->name = $args['fullName'];        $dto->email = $args['mail'];        $dto->phone = $args['phone'];        return $dto;    }}

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

class PurchaseDTO{    /** @var array<\DTO\ProductDTO> $products Product list */    public array $products;        /** @var \DTO\UserDTO $user */    public UserDTO $user;}

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

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

Что мы получаем?

  • Метод сервиса работает с конкретным набором данным

  • Знаем все параметры, которые есть у объекта

  • Можно задать типизацию каждому параметру

  • Вызов метода становится проще, за счет удаления приведения вручную

  • В IDE работают все подсказки.

Аналоги

Увы, я не нашел подобных решений. Отмечу лишь пакет от Spatie - https://github.com/spatie/data-transfer-object

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

Я же, в свою очередь, был вдохновлен методом преобразования из NestJS - plainToClass. Такой подход не заставляет реализовывать свои интерфейсы, что позволяет делать преобразования более гибким, и любой набор данных можно привести к любому классу. Хоть массив данных сразу в ORM модель (если прописаны параметры), но лучше так не надо:)

Roadmap

  • Реализовать метод afterTransform, который будет вызываться после инициализации DTO. Это позволит более гибко кастомизировать приведение к классу. В данный момент, если входные ключи отличаются от внутренних DTO, нужно самому описывать метод transform. И если у нас из 20 параметров только у одного отличается ключ, нам придется описать приведение всех 20. А с методом afterTransform мы сможем кастомизировать приведение только нужного нам параметра, а все остальные обработает пакет.

  • Поддержка атрибутов PHP 8

Вот и все.

Подробнее..

Мой штрихкод. Code128

14.06.2021 12:13:27 | Автор: admin

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

Первая мысль поиск готовых библиотек. Навскидку определили критерии с pl/sql не связываемся, пусть это будет внешний сервис: возможно кусок на javascript для генерации прямо на страничке, либо обращение за картинкой к ближайшему серверу где имеется php. Беглый поиск в интернете показал что тема истоптана весьма плотно. Есть как наколенные поделки уровня лабораторки по программированию, так и мощные библиотеки для всех вариантов кодирования вплоть до qr-кодов. Варианты с JavaScript пришлось отбросить т.к. они во-первых практически все обфусканы (даже непонятно, то ли для сокращения объема, то ли стыдно исходники показать), во-вторых генерируют строку для отображения определенным шрифтом, наличие которого не всегда можно обеспечить на клиентском месте и требует дополнительных обработок для экранирования спецсимволов. Внимательное изучение библиотек и кусков кода на php тоже произвело тягостное впечатление на первый взгляд всё вроде бы правильно: и классы написаны на все случаи жизни, комментарии в наличии, украшательства типа выбора цвета и рамочек, примеры подготовлены. Начнёшь вникать хотят либо php самой распоследней версии (на боевых серверах не всегда это получается добыть), либо внутренняя логика не различима совсем, либо штрихкод на выходе получается длиннее ожидаемого. Вот последнее не дало покоя и подтолкнуло к собственной реализации.

Самое время пощупать теорию. Вернее мы с ней познакомились намного раньше, просто до последнего не хотелось ввязываться в дополнительное программирование. Исторические факты опустим, а вот очень хорошее техническое описание имеется на http://code128.narod.ru/ (в архиве это файл Descript.doc ) либо в Википедии. В принципе, это всё что нам потребуется для понимания и собственной реализации алгоритма (тут я немного лукавлю из любой готовой библиотеки нужно выдрать таблицы толщин штрихов, чтобы не вбивать их вручную). Ну и напишем всё это безобразие на php, заодно посмотрим пару прикольных моментов, про которые все забывают или стесняются использовать.

Теория гласит, что code128 позволяет закодировать (сюрпрайз!) 128 символов, при этом нам доступно 3 алфавита, между которыми можно переключаться по ходу дела. Наибольший практический интерес представляют алфавит B для буквенно-цифровых символов и алфавит С который используется для кодирования цифр, но с некоторой оптимизацией одним штриховым символом можно закодировать 2 исходных символа и получить более короткий штрихкод. Вот эта оптимизация пока не даётся ни одному php-разработчику максимум что я видел это попытка в начале кодирования определить состав строки и при наличии только цифр переключаться на алфавит С. В остальных библиотеках это банальная подстановка символов штрихкода по таблице.

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

Разберём пару примеров. Допустим у нас есть последовательность ABC12DE попробуем её закодировать разными методами, на примере изображены слева только алфавит B, справа - совместно B и С:

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

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

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

<?phpclass code128 {    private $code = '';    private $leafB = NULL, $leafC = NULL;    public function __construct($text, $mode = 'B')    {if (strlen($text) == 0) return NULL;$this->mode = $mode;if ($mode == 'B') {    $this->code = substr($text, 0, 1);    $text = substr($text, 1);}else if ($mode == 'C') {    if (strlen($text) < 2) return NULL;    if (!is_numeric($text[0])) return NULL;    if (!is_numeric($text[1])) return NULL;    $this->code = substr($text, 0, 2);    $text = substr($text, 2);}else    return NULL;$this->leafB = new code128($text, 'B');$this->leafC = new code128($text, 'C');    }    public function draw()    {echo "Code [" . $this->code . "]\n";if ($this->leafB != NULL) $this->leafB->draw();if ($this->leafC != NULL) $this->leafC->draw();    }}    $n = new code128('s92317lsdfa4324', 'B');    $n->draw();?>

и сразу ловим конкретный косяк куча "пустых" объектов. И это не смотря на то что мы явно отказались создаваться и вроде как железно возвращаем NULL! В общем сразу надо понять, что в php объект создаётся в любом случае. Полагаю что также и в остальных объектно-ориентированных языках. И все эти условия надо проверять перед созданием объекта. Следовательно правильный конструктор будет выглядеть примерно так:

    public function __construct($text, $mode = 'B')    {$this->mode = $mode;if ($mode == 'B') {    $this->code = substr($text, 0, 1);    $text = substr($text, 1);}else if ($mode == 'C') {    $this->code = substr($text, 0, 2);    $text = substr($text, 2);}if(strlen($text)>0) $this->leafB = new code128($text, 'B');if(strlen($text)>1)    if(is_numeric($text[0]) && is_numeric($text[1])) $this->leafC = new code128($text, 'C');    }

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

if($mode == 'B') list($this->code, $text) = sscanf($text, '%c%s');if($mode == 'C') list($this->code, $text) = sscanf($text, '%2d%s');if(strlen($text)>0)    if(array_key_exists(substr($text, 0, 1), $symCode)) $this->leafB = new code128($text, 'B', $this);if(strlen($text)>1)    if(array_key_exists(substr($text, 0, 2), $symCode)) $this->leafC = new code128($text, 'C', $this);

$symCode - это алфавиты, которые я загнал в файл tables.php и включаю его через require в начале исходника. Формат простой - символ алфавита => код штрихкода.

$symCode = array(/*alphabet Balphabet C */' ' => 0,'00' => 0,'!' => 1,'01' => 1,'"' => 2,'02' => 2,'#' => 3,'03' => 3,'$' => 4,'04' => 4,'%' => 5,'05' => 5,'&' => 6,'06' => 6,

Дерево строится, но результата пока не видно. Следующим этапом необходимо определить самую короткую ветку: Первый вариант - заводим в классе счётчик и увеличиваем его при каждом переходе на ветку. Как только достигнем дна (конца исходного текста), на концах дерева будет указан размер финального кода. Второй вариант конечные веточки ставят себе размер 1, а дальше родитель решает какой из потомков имеет код короче и ставит себе размер на 1 или 2 больше. Почему на 2? Надо учитывать накладные расходы на переключение алфавита. Кстати и в первом варианте это тоже надо учитывать. В итоге в корне дерева будет длина самой короткой последовательности. Чем хорош первый вариант? Получение итоговой последовательности практически мгновенное возвращаешься по папе к корню дерева. Ну и недостаток чтобы найти самую короткую ветку надо сделать полный обход дерева. Второй вариант длина самой короткой последовательности известна и находится в одном месте, но получение последовательности чуток посложнее, хотя и не требует полного обхода. Попробуем проработать первый вариант доделать надо совсем немного, просто родитель после создания потомков должен выбрать самого короткого потомка и сохранить ссылку на конечный элемент ветки. Выразился невероятно коряво, но Вы посмотрите код - там ещё страшнее :)

require 'tables.php';class code128 {    private $code = NULL;    private $text = '';    private $mode = 'Auto';    private $len = 1;    private $leafB = NULL, $leafC = NULL, $parent = NULL, $minCode = NULL;    public function __construct($text, $mode = 'Auto', $parent = NULL)    {global $symCode;$this->parent = $parent;$this->text = $text;$this->mode = $mode;if($parent != NULL) {    $this->len = $this->parent->len + 1;    if($this->parent->mode != $mode) $this->len++;     }    if($mode == 'B') list($this->code, $text) = sscanf($text, '%c%s');    if($mode == 'C') list($this->code, $text) = sscanf($text, '%2d%s');    if(strlen($text)>0)       if(array_key_exists(substr($text, 0, 1), $symCode)) $this->leafB = new code128($text, 'B', $this);    if(strlen($text)>1)       if(array_key_exists(substr($text, 0, 2), $symCode)) $this->leafC = new code128($text, 'C', $this);    if($this->leafB == NULL && $this->leafC == NULL) $this->minCode = $this;    else {       $this->minCode = ($this->leafB != NULL) ? $this->leafB->minCode : $this->leafC->minCode;       if($this->leafC != NULL)        if($this->minCode->len > $this->leafC->minCode->len) $this->minCode = $this->leafC->minCode;    }    return $this;     }

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

 private function getCode() {$stack = array();$p = $this->minCode;while($p != NULL) {    array_push($stack, $p->code);    if($p->parent != NULL) {if($p->parent->mode == 'Auto') { array_push($stack, 'Start'.$p->mode); break;}if($p->mode != $p->parent->mode) array_push($stack, 'Code'.$p->mode);    }    $p = $p->parent;}return $stack;  }

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

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

  private function printPattern($code, $posX, $res, $height)  {for($i = 0; $i < strlen($code); $i++) {    $w = $res*intval($code[$i]);    if(!($i%2))echo "  <rect x='$posX' y='0' width='$w' height='$height' fill='#0'/>\n";    $posX += $w;}return $posX;   }   public function printSVG($resolution=1, $height=50)   {global $symCode;global $barPattern;$s = $this->getCode();$pos = 1;$offset = $resolution*11;$width = ((count($s) + 4)*11 + 2)*$resolution;echo "<svg xmlns='http://www.w3.org/2000/svg' width='$width' height='$height'>\n";$start = $symCode[array_pop($s)];$checksum = $start;$offset = $this->printPattern($barPattern[$start], $offset, $resolution, $height);while(!empty($s)) {    $code = $symCode[array_pop($s)];    $offset = $this->printPattern($barPattern[$code], $offset, $resolution, $height);    $checksum += $code*$pos;    $pos++;}$offset = $this->printPattern($barPattern[$checksum%103], $offset, $resolution, $height);$offset = $this->printPattern($barPattern[$symCode['Stop']], $offset, $resolution, $height);echo "</svg>\n";    }

Появился глобальный массив $barPattern. Искать в файле tables.php рядом с $symCode. Кусочек приведу. Там всё просто - для заданного кода выходного символа чередуются толщины черных и белых штрихов:

$barPattern = array('212222',/* 0 */'222122',/* 1 */'222221',/* 2 */'121223',/* 3 */'121322',/* 4 */

Как этим пользоваться? В файл с классом в конец добавим пару строк:

    header('Content-Type: image/svg+xml');    echo "<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n\n";    $n = new code128(html_entity_decode($_SERVER["QUERY_STRING"]));    $n->printSVG();

попробовать можно сразу, вставив в html-страничку примерно вот такой тэг:

<img src="barcode128.php?ad32324adsFAE13413ldsFf">

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

Подробнее..
Категории: Php , Штрихкод , Code128

Категории

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

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru