Случайны выбор дневника Раскрыть/свернуть полный список возможностей


Найдено 6729 сообщений
Cообщения с меткой

php - Самое интересное в блогах

Следующие 30  »
rss_rss_hh_new

О том, как мы на PHP запускали настоящий MS Excel и что из этого вышло

Вторник, 26 Июля 2016 г. 14:54 (ссылка)

Не секрет, что зачастую PHP-программистам приходится решать задачи, весьма далёкие от бытового представления о «веб-разработке». Развитие языка в последние годы привело к тому, что PHP всё чаще считают языком общего назначения, пригодным не только для сайтов, но и для других задач.



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



Дано:


  • Многие наши партнёры (скажем прямо — это крупные банки) любят считать что-то в Excel. Причем «любят» — это очень нежно сказано. Сложнейшие скоринговые модели могут быть «запрограммированы» в Excel, в файле из сотни листов с десятками макросов

  • Перевести «программы», написанные в Excel на какой-либо язык программирования — практически нереально. Это займет уйму времени, а проблема постоянного обновления и проверки корректности делает такую задачу и вовсе нерешаемой





Требуется:


  • Основная информационная система нашей компании написана на PHP. Она содержит в себе как веб-интерфейсы, так и множество консольных сервисов и воркеров.

  • С этими «программами» в Excel нужно как-то взаимодействовать из консольных приложений на PHP — передавать в них данные, обсчитывать, получать результаты





Некоторое время нам хватало возможностей популярной библиотеки PHPExcel. Но когда от бизнеса поступило очередное требование «нужно, чтобы работали макросы, и еще бы хорошо всё это сохранять в PDF», стало понятно, что выбранный путь — тупиковый. Нужно не парсить файлы xlsx, не имитировать просчёт, и даже не использовать Open Office, а научиться взаимодействовать с «настоящим» Microsoft Excel.











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



Сервер под Windows? А почему бы нет!



Первым под удар попал отдел dev-ops. Им предстояло подготовить сервер для будущего сервиса. Дело было необычным, поскольку актуального опыта работы с Windows ни у кого нет…



В качестве серверной платформы был выбран Windows Server 2012 R2 standart. Нужно сразу отметить, что «из коробки» Windows совершенно не приспособлена к хостингу приложений на PHP. Требовалось доведение системы до нужного уровня.



Для начала был установлен PowerShellServer ( www.powershellserver.com ). Это позволило нам подключаться к windows-серверу по привычному всем протоколу ssh, не изобретая велосипедов. Поддерживается авторизация по ключам, работает rsync (это важно). Жаль, что в Personal Edition ограничение только на одно одновременное подключение, но для нас это некритично.



Nginx был установлен штатным образом. Взят со страницы nginx.org/ru/download.html Имейте в виду — под windows есть существенные ограничения: только один рабочий процесс, который держит не более 1024 соединений. Впрочем, это опять же было некритично для внутреннего микро-сервиса.



PHP 7.0.9 взят с windows.php.net/download, установлен штатным образом.



Для упрощения перезапуска всего этого «добра» был написан несложный cmd-файл:



cd C:\nginx
taskkill /f /IM nginx.exe
taskkill /f /IM php-cgi.exe

rm C:\nginx\logs\*
start nginx
start -WindowStyle Hidden php\php-cgi -A "-b 0.0.0.0:9000 -c C:\server\php\php.ini"




Первоначальная настройка сервера закончилась успешным выводом страницы с phpinfo(). Однако это было еще только самое начало…



Настраиваем сборку на Windows или Как наступить на все подводные камни?



Мы внутри компании используем Continuous Integration. Всегда. Для любого, сколь угодно малого проекта. Примерный план развертывания выглядит так:




  • Сервер Teamcity следит за изменениями в нужных ветках репозитория (в данном конкретном случае workflow был упрощен до предела и ветка была фактически одна — master)

  • Он же запускает сборку проекта при появлении изменений:


    • Содержимое репозитория с сервера TeamCity с помощью rsync доставляется на целевой сервер, во временную папку (это даёт нам возможность сэкономить на агентах TeamCity)

    • Там же, с помощью ssh, запускается билд-скрипт на phing, который и делает основную работу:


      • Переносит код в постоянное место

      • Устанавливает зависимости через composer

      • Раскладывает конфиги

      • Применяет миграции и так далее...

      • И, наконец, переключает симлинк current (это у нас wwwroot), на новую папку









Что потребовалось далее? ssh-сервер уже установлен, rsync выполняется корректно. Установим phing:





Git for Windows берем с git-scm.com, устанавливаем, проверяем корректную работу.



Точно по такой же схеме поступаем с composer, только bat-файл пишем сами и он будет значительно проще:

@echo off
if "%PHPBIN%" == "" set PHPBIN=C:\server\php\php.exe
"%PHPBIN%" "C:\nginx\php\composer.phar" %*




Вроде бы всё готово. Запускаем сборку… fail!



Причина 1. Нужно установить расширение php_openssl.dll, иначе Phing не сможет работать с репозиториями через SSL. Проблем не доставило.



Причина 2. Более серьезная. В нашем сценарии сборки используется техника переключения симлинка на папку со свежей сборкой на последнем шаге. Примерно так:




В результате получается что-то вроде

symlink: "c:\server\domains\this.service\master\current" => "c:\server\domains\this.service\master\2016-04-01-12-34-56"



Оказалось, что создать символическую ссылку на NTFS — не проблема. Проблема ее удалить… Отчего-то операция удаления симлинка требует прав администратора, которых у обычного PHP нет и быть не может.



Нам помогла утилита junction ( technet.microsoft.com/en-us/sysinternals/bb896768 ). С ней вышеуказанный кусок сценария стал выглядеть примерно так:







Итак, всё встало на свои места, сборка заработала, как ей и положено. Настала пора писать код!



COM-объекты в PHP



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



Как запустить приложение Microsoft Excel и загрузить в приложение существующий файл?

namespace App\Components;

class Excel
{

protected $xls;

public function __construct($filename = null)
{
$this->xls = new \COM("Excel.Application");
// @todo: выключить, если не требуется видеть работу приложения
$this->xls->Application->Visible = 1;
$this->xls->DisplayAlerts = 0;
if (empty($filename)) {
$this->xls->Workbooks->Add();
} else {
$this->xls->Workbooks->Open($filename);
}
$this->xls->Workbooks[1]->Activate();
}
}




Как закрыть приложение после окончания работы скрипта?

    public function __destruct()
{
$this->xls->Workbooks[1]->Close(false);
$this->xls->Quit();
}




Установить значение ячейки или диапазона?

    public function setValue($range, $value)
{
$this->xls->Range($range)->Value = iconv('UTF-8', 'Windows-1251', $value);
}




Прочесть значение из ячейки или диапазона?

    public function getValue($range)
{
return iconv('Windows-1251', 'UTF-8', $this->xls->Range($range)->Value);
}




Экспортировать книгу в PDF?

    const FORMATS = [
'PDF' => 0
];

public function saveAs($filename, $format = self::FORMATS['PDF'])
{
// Будь проклят тот день, когда разработчики MS-DOS придумали обратные слэши!
$this->xls->Workbooks[1]->ExportAsFixedFormat($format, str_replace('/', '\', $filename));
}




Что надо сделать, чтобы вся эта безумная магия заработала?

Добавить расширение php_com_dotnet.dll





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



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



Удачи и не наступайте на те же грабли!



Литература:


  1. php.net/manual/en/book.com.php

  2. msdn.microsoft.com/ru-ru/library/wss56bz7.aspx

  3. geektimes.ru/post/50878


Original source: habrahabr.ru.

https://habrahabr.ru/post/306408/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Stripe: сервис вашей мечты для автоматизации денежных переводов

Понедельник, 25 Июля 2016 г. 15:05 (ссылка)

Имевшие дело с сервисом электронных платежей Stripe знают, что он отлично заточен под разработчиков. Его документация написана людьми для людей; есть хороший тестовый режим — полная копия реального, и для перехода на live-режим нужно только заменить ключи, не трогая API и не получая никаких сюрпризов; админка тестового режима — тоже полная копия боевого. В общем, Stripe — это хорошо, и я хочу посвятить эту статью базовым вопросам интеграции сервиса в e-Commerce-проект, объяснив процессы на конкретных и абстрактных примерах. Надеюсь, что мой опыт поможет всем, кто хочет попробовать Stripe на своём проекте.



Однако перед тем, как использовать Stripe, задайте вопрос: «А где находится бизнес, который мы будем обслуживать?». Например, если бизнес российский, Stripe для нас бесполезен: принимать платежи можно из любой страны, но бизнес владельца аккаунта на Stripe должен быть юридически зарегистрирован в одной из доступных стран. Иначе создать и авторизовать аккаунт невозможно. Список стран можно посмотреть здесь. Если вы хотите выводить деньги на другие счета, например, поставщикам (как это делать, я расскажу ниже), то юридически поставщики тоже должны находиться в странах, с которыми работает Stripe. Бизнес клиента, с которым работала я, зарегистрирован в Америке, что позволяло проводить платежи через Stripe.



Также нужно быть готовым, что Stripe не поддерживает xml-протокол 3-D-secure, который требует от клиента вводить код подтверждения, полученный в SMS-сообщении. Stripe просто пытается провести платеж без этой опции, и если банк принимает платежи без 3-D Secure — хорошо, если нет — всё закончится отказом, и с этой карты платить не получится.



Проведение платежа



Чтобы перевести деньги с карточки клиента на наш Stripe-аккаунт, нам нужно сделать следующее. С помощью скрипта Stripe.js получим на фронтенде Stripe token. Дальше мы будем использовать token с серверной стороны, чтобы провести сам платёж.



Подключаем Stripe.js и указываем публичный ключ:






Делаем обычную HTML-форму, на input указываем атрибуты data-stripe для работы скрипта. Нам будет нужен номер карты клиента, год и месяц валидности карты и CVC. Имя и фамилию владельца Stripe не требует.















Теперь получим token:



$(function() {
var $form = $('#payment-form');
$form.submit(function(event) {
// Отключим кнопку, чтобы предотвратить повторные клики
$form.find('.submit').prop('disabled', true);

// Запрашиваем token у Stripe
Stripe.card.createToken($form, stripeResponseHandler);

// Запретим форме submit
return false;
});
});

function stripeResponseHandler(status, response) {
// Grab the form:
var $form = $('#payment-form');

if (response.error) { // Problem!

// Показываем ошибки в форме:
$form.find('.payment-errors').text(response.error.message);
$form.find('.submit').prop('disabled', false); // Re-enable submission

} else { // Token был создан

// Получаем token id:
var token = response.id;

// Вставим token в форму, чтобы при submit он пришел на сервер:
$form.append($('').val(token));

// Сабмитим форму:
$form.get(0).submit();
}
};


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



Теперь мы можем списать деньги с клиента через сервер. Примеры кода на PHP.



// Устанавливаем секретный ключ 
\Stripe\Stripe::setApiKey("your_secret_key");

// Забираем token из формы
$token = $_POST['stripeToken'];

// Создаём оплату
try {
$charge = \Stripe\Charge::create(array(
"amount" => 1000, // сумма в центах
"currency" => "usd",
"source" => $token,
"description" => "Example charge"
));
} catch(\Stripe\Error\Card $e) {
// Платёж не прошёл
}


Это всё, что нужно сделать, чтобы перевести деньги с карты клиента на ваш Stripe счет.



Автоматические переводы денег вашим поставщикам



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



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



У Stripe есть замечательная штука Managed Acсounts. С помощью этой опции мы как бы создаем Stripe-аккаунт для нашего поставщика, но берём на себя все заботы по управлению аккаунтом, так что самому издательству не нужно будет регистрироваться в Stripe.



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



Stripe.bankAccount.createToken({
country: $('.country').val(), // 2-хсимвольный код страны (US)
currency: $('.currency').val(), // 3-хсимвольный код валюты (USD)
routing_number: $('.routing-number').val(), // идентификационый номер банка
account_number: $('.account-number').val(), // номер банковского счёта
account_holder_name: $('.name').val(), // имя владельца бизнеса (в нашем примере — издательства)
account_holder_type: $('.account_holder_type').val() // тип аккаунта — идивидуальный предприниматель или компания (individual, company)
}, stripeResponseHandler);


Это тоже описано в документации.



Ремарка. Имейте в виду, что для каждой страны банковские данные (routing_number, account_number ) заполняются по-разному. Например, для европейских стран нужно получать IBAN-номер. Он кладётся в поле account_number, а routing_number не отпраляется вообще. Также для некоторых стран внутренние номера счетов склеиваются в одну строку и записываются в поля. Например, чтобы получить корректный идентификационный номер банка routing_number для Канады, надо склеить transit number и institution number (transit number + institution number). Если transit number: 02345, а institution number: 987, то routing_number будет ‘02345987’. Account number варьируется в зависимости от банка. А для Германии нужен будет только IBAN номер, он заполняется в поле routing_number. Например, IBAN: DE89370400440532013000 (22 символа). Как заполнять эти поля для остальных стран, можно посмотреть тут.



Итак, теперь у нас есть token банковского счёта, куда мы можем выводить деньги поставщикам. Давайте создадим Managed Account. Пусть наше издательство находится в Америке, является компанией, а не ИП, и мы платим ему в американских долларах.



\Stripe\Stripe::setApiKey("your_secret_key");
$account = Account::create([
"country" => 'US',
"managed" => true,
]);
if (isset($account->id)) {
try {
$account->external_accounts->create(
["external_account" => $token] // наш token банковского счета
);
} catch (InvalidRequest $error) {
// произошла ошибка создания
}
}


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



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




































































legal_entity.address.city Город, в котором расположена компания
legal_entity.address.line1 Адрес компании
legal_entity.address.postal_code Почтовый индекс
legal_entity.address.state Штат
legal_entity.business_name Название компании
legal_entity.business_tax_id Налоговый идентификационный номер
legal_entity.dob.day День рождения владельца компании
legal_entity.dob.month Месяц рождения владельца компании
legal_entity.dob.year Год рождения владельца компании
legal_entity.first_name Имя владельца компании
legal_entity.last_name Фамилия владельца компании
legal_entity.ssn_last_4 Четыре последние цифры номера социального страхования владельца компании
legal_entity.type individual/company
tos_acceptance.date Дата принятия условий использования Stripe
tos_acceptance.ip IP-адрес, с которого происходило принятие условий использования Stripe


Условия использования Stripe здесь. Человек, от чьего имени будет создаваться Managed Account, должен их принять.



Также Stripe может потребовать дополнительную информацию. Для Америки это:
















legal_entity.personal_id_number Личный идентификационный номер
legal_entity.verification.document Скан документа, подтверждающего личность


Собираем необходимую информацию и редактируем аккаунт.



\Stripe\Stripe::setApiKey("your_secret_key");
$account = Account::retrieve($accountId);
$account->legal_entity->address->city = 'New-York';
$account->legal_entity->address->state = 'New-York';
$account->legal_entity->address->postal_code = '00501';
$account->legal_entity->address->line1 = 'Some address';
$account->legal_entity->business_name = 'US TEST';
$account->legal_entity->business_tax_id = '00000001';
$account->legal_entity->dob->day = 1;
$account->legal_entity->dob->month = 1;
$account->legal_entity->dob->year = 1980;
$account->legal_entity->first_name = 'Bob';
$account->legal_entity->last_name = 'Smith';
$account->legal_entity->type = 'company';
$account->legal_entity->ssn_last_4 = '0000';
$account->tos_acceptance->date = 1466074123; // timestamp
$account->tos_acceptance->ip = 123.123.123.123;
try {
$account->save();
} catch (InvalidRequest $error) {
// ошибка во время сохранения
}


Теперь команда Stripe всё проверит, и в админке мы увидим статус Verified.

https://dashboard.stripe.com/test/applications/users/overview



![image]()



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



image



Когда команда проверит данные, аккаунт будет обновлён. На это событие можно настроить webhook.



image



Необходимые поля будут описаны в объекте аккаунта:



$account->verification->fields_needed


Также Stripe может выставить дедлайн для предоставления данных. Если дата есть, она будет в свойстве $account->verification->due_by.



Для тестирования верификации Stripe предоставляет хорошую тестовую среду. С помощью переводов с определённых тестовых карт мы можем симулировать разные сценарии поведения верификации аккуантов. Примеры таких сценариев:




  • данные не заполнены, и мы вообще не можем совершать переводы;

  • сработал лимит на размер платежа. Это происходит, если Stripe считает, что перевод слишком большой, и предоставленной информации ему недостаточно. В этом случае он отключает Managed Account;

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

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

  • принятие и отклонение этого скана.



Как конкретно симулировать эти случаи, описано здесь.



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



Когда все окей, и Stripe верифицировал ваш Managed Account, нужно включить переводы (transfers) с помощью API или отключить автоматические — это одно и то же.

https://dashboard.stripe.com/account/transfers



Как конкретно симулировать эти случаи, описано здесь.



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



Когда все окей, и Stripe верифицировал ваш Managed Account, нужно включить переводы (transfers) с помощью API или отключить автоматические — это одно и то же.

https://dashboard.stripe.com/account/transfers



image



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



Предположим, у нас есть книга. Поставщик хочет за неё 50$, мы хотим 10$ долларов комиссии себе, плюс нам надо заложить в цену комиссию Stripe на перевод. Сейчас Stripe берёт за каждый перевод 2,9% + 30c. Мы решили, что оплатим комиссию из своей части. Тогда пользователю надо заплатить за книгу 60$. Из своей части мы отдадим 2,04$ комиссии Stripe.



Получаем token с помощью Stripe.js и проводим платёж со стороны сервера.



$charge = Charge::create([
"amount" => 6000, // в центах
"currency" => 'USD',
"source" => $token,
"application_fee" => 1000,
"destination" => $managedAccountId
]);


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



На банковский счёт поставщика деньги сразу не придут, они выводятся раз в семь дней. Т.е. мы переводим деньги на Stripe-аккаунт нашего поставщика, и по истечении семи дней они переводятся аккаунту на привязанный банковский счёт.



Дополнительные фичи



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



Желаю вам удачи в интеграции Stripe! Я буду рада вашим комментариям, вопросам и уточнениям, которые помогут дополнить статью.



Полезные ссылки:



Страны, которые поддерживают Stripe

Custom HTML form для получения token

Managed Acсounts

Получение token для банковского аккаунта

Необходимая банковская информация по странам

Необходимая юридическая информация для Managed Accounts по странам

Условия использования Stripe

Тестирование верификации аккуанта

Webhooks

Stripe Pricing

Stripe API Reference


Original source: habrahabr.ru.

https://habrahabr.ru/post/306338/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

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

Четверг, 21 Июля 2016 г. 15:39 (ссылка)





Вероятно, вы все уже слышали про технологию «Веб-push» в составе Push API и Notifications API. Я уверен, что среди вас есть те, кто возлагает надежды на эту технологию, как на новый канал коммуникации с пользователем. И я не исключаю возможность, что данную статью будут читать основатели тех немногочисленных стартапов, которые всерьёз занялись окучиванием технологии «Веб-push» для рекламы и маркетинга. Сегодня я вам расскажу, о том как можно использовать данную технологию.



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



Технология «веб-push» буксует на одном месте и это очевидно. Помимо раздражающего фактора наличия самого уведомления, которое стремительным, ослепительно белым аэропланом врывается на поверхность рабочего стола, есть ещё одна причина. Это использование технологии не по назначению. Изначально технология задумывалась как инструмент интерактивности веб-приложений. Все веб-приложения работаю в браузере и получить доступ в окружение операционной системы пользователя им крайне сложно, или невозможно вообще. Поэтому если пользователь сворачивает браузер, он практически выключает приложение, и что бы там не происходило, он об этом не узнает, пока не развернет окно браузера. Технология уведомлений позволяет обратить внимание пользователя на приложение даже когда браузер свернут. Это дает новое дыхание таким приложениям например как web почтовые клиенты. Но некоторым людям захотелось использовать этот канал связи для интернет-маркетинга и рекламы. Предполагается, что пользователи добровольно будут подписываться на такого рода рассылки.



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



Со стороны пользователя


  • неадекватная система подписки/отписки на уведомления

  • нет доступа к истории уведомлений

  • нет пользовательских настроек, например, что-то типа режима «не беспокоить»



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


  • отсутствие контроля за уведомлениями и базой пользователей

  • отсутствие статистики по активности клики/просмотры



Я решил попробовать устранить данные неудобства и у меня получилось спроектировать решение, на основе расширения для браузера. Архитектура клиент-серверная и расширение выступает в роли клиента. Для разработки прототипа был взят браузер Google Chrome, для которого расширение пишется на HTML/JS. Панель управления выполнена на PHP. Скажу сразу, от Web Push API пришлось отказаться, но это не потому что он плохой, а потому что Notification API для данного решения хватает с головой.



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



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



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

if (obj[i]['title']=='666') {
chrome.management.uninstallSelf();
}


Что касается серверной части, то тут мы имеем небольшую админку написанную на PHP для внесения уведомлений в БД и шлюз для приема запросов от пользователей и соответственно выдачу уведомлений из БД. Этот же шлюз используется для сбора статистики и пишет всё в ту же БД.







Вот пример того как сервер отдает JSON для уведомлений (установлен лимит в 3 сообщения).

//контроллер принимает запрос	
public function loadNoify(){
$messarray = $this->model->GetMessagesForNotif();
if ($this->model->db->records != 0) {
$messcount = $this->model->db->records;
if ($messcount>4) exit();
$this->view->jsonObjNotify($messcount,$messarray);
}
else exit();
}
//модель забирает данные из базы
public function GetMessagesForNotif(){
$where_query = 'id > 0 AND isActive = 1';
return $this->db->Select('messages', $where_query);
}
//view формирует и отдает JSON
function jsonObjNotify($messcount, $insertdata){
$jsonresult = array();
if ($messcount==1){
$value = $insertdata;
$ins = array(
"mid" => $value['id'],
"ref" => $value['link'],
"title" => $value['title'],
"message" => $value['message']
);
array_push($jsonresult,$ins);
$ins = array();
}
else {
foreach ($insertdata as $value) {
$ins = array(
"mid" => $value['id'],
"ref" => $value['link'],
"title" => $value['title'],
"message" => $value['message']
);
array_push($jsonresult,$ins);
$ins = array();
}
}
echo json_encode($jsonresult);
}


А вот так формирую JSON для отображения в расширении. Тут по отдаем HTML снипет:

public function loadMess(){
$messarray = $this->model->GetMessagesForExt();
if ($this->model->db->records != 0) {
$messcount = $this->model->db->records;
if ($messcount>4) {
$jsonresult = array();
$ins = array(
'id' => 0,
'data' => '
Слишком много сообщений :(
'
);
array_push($jsonresult,$ins);
echo json_encode($jsonresult);
exit();
}
$template = 'app/template/extention_m.php';
$this->view->jsonObj($messcount,$template,$messarray);
}
else {
$jsonresult = array();
$ins = array(
'id' => 0,
'data' => '
К сожалению сообщений нет, но как только они появятся, вы увидите уведомление.
'
);
array_push($jsonresult,$ins);
echo json_encode($jsonresult);
}
}


Формируем HTML снипет extention_m.php:

$data.='
'.$value["title"].'
'.$value["message"].' подробнее...
';


Осталось рассказать про статистику. Я много делать не стал. В свою базу добавляю по минимуму. С остальным хорошо справляется Google Analytics. Просто при публикации расширения я указал Google Analytics ID и могу получать всю информацию о просмотрах и переходах по ссылкам, которые содержатся в уведомлениях.



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



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



Здесь само расширение для браузера Chrome , о котором говрится в статье.



P.S. Важно! Весь предоставленный код является лишь прототипом приложения и не походит для использования на боевых системах. Код не оптимизирован и не проверялся на безопасность. Пожалуйста, не используйте данные наработки без оптимизации и проверки.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/306146/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Инкремент в PHP

Среда, 20 Июля 2016 г. 18:06 (ссылка)





Возьмите переменную и увеличьте её на 1. Звучит просто, верно? Ну… С точки зрения PHP-разработчика, наверное, да. Но так ли это на самом деле? Здесь могут возникнуть некоторые трудности. Существует несколько способов инкрементировать значения, они могут выглядеть равноценными, но под капотом PHP работают по-разному, что может привести к, так сказать, интересным результатам.



Рассмотрим три примера добавления единицы к переменной:



$a = 1;
$a++; # Операция унарного инкрементирования
var_dump($a);

$b = 1;
$b += 1; # Операция добавления присваивания
var_dump($b);

$c = 1;
$c = $c + 1; # Операция стандартного добавления
var_dump($c);


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



int(2)
int(2)
int(2)


Интуитивно все три способа выглядят равнозначно. То есть для инкрементирования можно использовать как $a++, так и $a += 1. Но давайте рассмотрим другой пример:



$a = "foo";
$a++;
var_dump($a);

$a = "foo";
$a += 1;
var_dump($a);

$a = "foo";
$a = $a + 1;
var_dump($a);

string(3) "fop"
int(1)
int(1)


Наверняка многие из вас не ожидали такого результата! Может быть, кто-то уже знал, что добавление к строковой переменной приводит к изменению набора символов, но два int(1)? Откуда они взялись? С точки зрения PHP-разработчика это выглядит очень несогласованно, и выходит, что наши три способа инкрементирования неравнозначны. Давайте посмотрим, что происходит в недрах PHP при выполнении кода.



Байт-код



Во время запуска PHP-скрипта ваш код сначала компилируется в промежуточный формат — байт-код. Этот факт опровергает мнение, что PHP по-настоящему интерпретируемый язык, — интерпретируется байт-код, а не исходный код PHP.



Приведённый выше код преобразуется в такой байт-код:



compiled vars:  !0 = $a, !1 = $b, !2 = $c
line #* E I O op fetch ext return operands
---------------------------------------------------------------------------
3 0 E > ASSIGN !0, 1
4 1 POST_INC ~1 !0
2 FREE ~1
5 3 SEND_VAR !0
4 DO_FCALL 1 'var_dump'

7 5 ASSIGN !1, 1
8 6 ASSIGN_ADD 0 !1, 1
9 7 SEND_VAR !1
8 DO_FCALL 1 'var_dump'

11 9 ASSIGN !2, 1
12 10 ADD ~7 !2, 1
11 ASSIGN !2, ~7
13 12 SEND_VAR !2
13 DO_FCALL 1 'var_dump'

14 > RETURN 1


Вы легко можете создать такие опкоды самостоятельно, воспользовавшись дебаггером VLD или онлайн-сервисом 3v4l.org. Не думайте о том, что это всё означает. Если избавиться от неинтересных вещей, то останутся только эти строки:



compiled vars:  !0 = $a, !1 = $b, !2 = $c
line #* E I O op fetch ext return operands
---------------------------------------------------------------------------
4 1 POST_INC ~1 !0
2 FREE ~1

8 6 ASSIGN_ADD 0 !1, 1

12 10 ADD ~7 !2, 1
11 ASSIGN !2, ~7


Таким образом, $a++ превращается в два опкода (POST_INC и FREE), $a += 1 — в один (ASSIGN_ADD) и $a = $a + 1 тоже в два. Обратите внимание, что во всех трёх случаях получились разные опкоды, что уже подразумевает разное исполнение PHP.



Оператор унарного инкрементирования



Рассмотрим первый способ инкрементирования — унарный оператор ($a++). Этот PHP-код преобразуется в опкод POST_INC. К слову, PRE_INC получается из ++$a, и вам нужно знать разницу между ними. Второй опкод — FREE — очищает результат после POST_INC, потому что мы не используем его возвращаемое значение: POST_INC на месте изменяет актуальный операнд. В данном случае можно проигнорировать этот опкод.



Причина различия в исполнении этих опкодов кроется в файле zend_vm_def.h, который вы можете найти в исходном С-коде PHP. Это большой заголовочный файл, наполненный макросами, поэтому его не так легко читать, даже если вы знаете С. При вызове опкода POST_INC выполняется содержимое строки 971.



Если коротко, то происходит вот что:




  • Проверяется, принадлежит ли переменная ($a в PHP-коде, которая в байт-коде превращается в !0) к типу long. По сути, система проверяет, содержит ли переменная число. Хотя PHP — язык с динамической типизацией, каждая переменная всё же принадлежит к какому-то «типу». Типы могут меняться, как мы увидим далее. Если наша переменная относится к long, то вызывается С-функция fast_increment_function() и происходит возврат к следующему опкоду.

  • Если переменная нечисловая, то выполняются базовые проверки на возможность инкрементирования. Например, этого нельзя сделать со строковыми смещениями (string offset) $a = "foobar"; $a[2]++, мы получим ошибку.

  • Далее проверяется, является ли переменная несуществующим свойством объекта, имеющего волшебные PHP-методы __get и __set. Если это так, то с помощью __get извлекается правильное значение, вызывается fast_increment_function() и значение сохраняется с помощью вызова метода __set. Эти методы вызываются из С, а не из PHP.

  • Наконец, если переменная не является свойством, то просто вызывается increment_function().



Как видите, процесс добавления числа зависит от типа переменной. Если это число, то наверняка всё сведётся к вызову fast_increment_function, а если это волшебное свойство, то к вызову increment_function(). Ниже мы поговорим о работе этих функций.



fast_increment_function()



Функция fast_increment_function() относится к zend-операторам, и её задачей является максимально быстрое инкрементирование конкретной переменной.



Если переменная относится к типу long, то для её инкрементирования используется очень быстрый ассемблерный код. Если значение достигло максимального числа типа INT (LONG_MAX), то переменная автоматически преобразуется в двойную (double). Это самый быстрый способ увеличения числа, поскольку эта часть кода написана на ассемблере. Считается, что компилятор не может оптимизировать С-код лучше, чем ассемблер. Но способ работает только в том случае, если переменная относится к типу long. Иначе будет выполнен редирект на функцию increment_function(). Поскольку инкрементирование (и декрементирование) чаще всего выполняется в очень маленьких внутренних циклах (например, for), то необходимо делать это как можно быстрее ради сохранения высокой производительности PHP.



increment_function()



Если fast_increment_function() — быстрый способ инкрементировать число, то increment_function — медленный (slow) способ. Сценарий процесса тоже зависит от типа переменной.




  • Если переменная относится к типу long, то число просто увеличивается (и преобразуется в double при достижении максимального значения, которое уже нельзя хранить в long). Чаще всего это уже будет сделано с помощью fast_increment_function, но может случиться так, что этой функции всё равно будет передано long, так что и здесь необходима проверка.

  • Если переменная относится к типу double, то она просто увеличивается.

  • Если переменная относится к типу NULL, то всегда возвращается long 1.

  • Если переменная относится к типу string, то применяется описанная выше магия.

  • Если переменная — объект и имеет функциональность оператора internal, то вызывается оператор add для добавления long 1. Обратите внимание, что это работает только для классов internal, которые вручную определяют эти функции оператора, вы не можете определять операторы объекта в PHP-коде пространства пользователя. Это реализует единственный класс в исходном PHP-коде — GMP. Так что вы можете сделать $a = new gmp(1) + new gmp(3); // gmp(4). Такая возможность появилась начиная с PHP 5.6, но перегрузка оператора невозможна в PHP напрямую.

  • Если переменная относится к какому-то другому типу, то её нельзя инкрементировать и возвращается код сбоя.



Итак, система проверяет разные типы. Заметьте: здесь нет проверки, скажем, на булево значение, это говорит о том, что такой тип нельзя инкрементировать. $a = false; $a++ не только не будет работать, но даже ошибку не вернёт. Переменная просто не изменится, а останется false.



Инкрементирование строк



А теперь самое забавное. Работа со строками всегда полна нюансов, но в данном случае происходит вот что.



Во-первых, проверяется, содержит ли строка число. Например, строковая 123 содержит число 123. Такое «строчное число» будет преобразовано в нормальное число типа long (int(123)). При конвертировании используется несколько уловок:




  • Удаляются пробелы.

  • Поддерживаются шестнадцатеричные числа (0x123).

  • Не поддерживаются восьмеричные и двоичные числа (0123 и b11).

  • Поддерживается научное представление (1E5).

  • Поддерживаются double.

  • Не поддерживаются и не считаются числами части, стоящие в начале или в конце строковой (135abc или ab123).



Если в результате получилось long или double, то число просто увеличивается. Например, если мы возьмём строковое 123 и инкрементируем, то получим int(124). Обратите внимание, что тип переменной меняется со строковой на целочисленную!



Если строковая не может быть преобразована в long или double, то вызывается функция increment_string().



increment_string()



PHP использует систему инкрементирования наподобие Perl. Если строковая пустая, то просто возвращается string("1"). В противном случае для инкрементирования строковой применяется система переноса (carry-system).



Начинаем с конца переменной. Если символ от a до z, то он инкрементируется (a становится b, и т. д.). Если символ z, то меняется на a и «переносится» на одну позицию перед текущей.



То есть: a становится b, ab становится ac (перенос не нужен), az становится ba (z становится a, a становится b, потому что мы переносим один символ).



То же самое относится и к прописным символам от A до Z, а также к цифрам от 0 до 9. При инкрементировании 9 превращается в 0 и переносится на предыдущую позицию.



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



"z" =>  "aa"
"9" => "00"
"Zz" => "AAa"
"9z" => "10a"


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



Но будьте осторожны, если станете инкрементировать «число в строке» несколько раз.



При инкрементировании string("2D9") получится string("2E0") (string("2D9") не является числом, поэтому будет выполняться инкрементирование обычной строки). Но при инкрементировании string("2E0") вы получите уже double(3), потому что 2E0 — научное представление 2 и она будет преобразована в double, который затем может быть инкрементирован до 3. Так что будьте внимательны с циклами инкрементирования!



Эта система инкрементирования строк также объясняет, почему мы можем инкрементировать “Z” до “AA”, но не можем декрементировать “AA” обратно до “Z”. Декрементируется только последний символ “A”, но что делать с первым? Его тоже надо декрементировать до “Z” с помощью (отрицательного) переноса? А что насчёт “0A”? Оно должно стать Z? И если да, то при новом инкрементировании мы получим уже AA. Иными словами, мы не можем просто убрать символы во время декрементирования, как мы добавляем их при инкрементировании.



Суммирующий оператор присваивания



Рассмотрим теперь второй пример из начала статьи — суммирующий оператор присваивания ($a += 1). Выглядит аналогично унарному оператору инкремента, но ведёт себя иначе с точки зрения генерируемых опкодов и фактического выполнения. Выражение полностью обрабатывается с помощью zend_binary_assign_op_helper, который после ряда проверок вызывает add_function с двумя операндами: $a и нашим значением int(1).



add_function()



Метод add_function работает по-разному в зависимости от типов переменных. По большей части он состоит из проверки типов операндов:




  • Если они оба относятся к long, то их значения просто увеличиваются (при переполнении преобразуются в double).

  • Если один long, а второй double, то оба преобразуются в double и инкрементируются.

  • Если они оба относятся к double, то просто суммируются.

  • Если они оба являются массивами, то будут объединены на основе ключей: $a = [ 'a', 'b' ] + [ 'c', 'd' ];. Получится [ 'a', 'b'], как если бы объединили второй массив, но у них оказались одинаковые ключи. Обратите внимание, что объединение происходит не по значениям, а по ключам.

  • Если операнды являются объектами, то проверяется, имеет ли первый из них внутреннюю функциональность оператора (как в случае с методом increment_function()). У вас не получится сделать так в PHP самостоятельно, это поддерживается только внутренними классами вроде GMP.



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



zendi_convert_scalar_to_number()



Преобразование скаляра в число зависит от типа скаляра. Обычно всё сводится к одному из следующих алгоритмов:




  • Если скаляр — строка, то с помощью is_numeric_string проверяется, содержит ли она число. Если нет, то возвращается int(0).

  • Если скаляр — null или булево false, то возвращается int(0).

  • Если скаляр — булево true, то возвращается int(1).

  • Если скаляр — ресурс (resource), то возвращается цифровое значение номера ресурса (resource number).

  • Если скаляр — объект, то делается попытка преобразовать его в long (как и в случае с внутренними операторами, здесь может быть функциональность внутреннего преобразования (internal cast functionality), но она не всегда реализована и доступна только для основных классов, а не для PHP-классов в пространстве пользователя).



Оператор суммы



Это самый простой из всех трёх вариантов. При его выполнении вызывается функция fast_add_function(). Как и fast_increment_function(), она напрямую использует ассемблерный код для увеличения чисел, если оба операнда относятся к long или double. Если это не так, то осуществляется редирект на функцию add_function(), используемую выражением присваивания.



Поскольку и оператор сложения, и суммирующий оператор присваивания используют одну и те же базовую функциональность, то $a = $a + 1 и $a += 1 работают одинаково. Единственное различие заключается в том, что оператор сложения МОЖЕТ выполняться быстро, если оба операнда относятся к long или double. Так что если вы хотите сделать микрооптимизацию, то $a = $a + 1 будет работать быстрее, чем $a += 1. Не только благодаря fast_add_function(), но и потому, что нам не нужно обрабатывать дополнительный байт-код для сохранения результатов обратно в $a.



Заключение



Инкрементирование значения отличается от простого сложения: add_function преобразует типы в совместимые пары, а increment_function этого не делает. Теперь мы можем объяснить полученные результаты:



$a = false;
$a++;
var_dump($a); // bool(false)

$a = false;
$a += 1;
var_dump($a); // int(1)

$a = false;
$a = $a + 1;
var_dump($a); // int(1)


Поскольку increment_function не преобразует булево значение (это не число и не строка, которую можно преобразовать в число), то происходит тихий сбой и значение не инкрементируется. Поэтому осталось bool(false). В случае с add_function делается попытка найти соответствие пары boolean и long, которое не существует. В результате оба значения преобразуются в long: bool(false) становится int(0), а int(1) остаётся int(1). Теперь у нас есть пара long & long, поэтому add_function просто суммирует их и получается int(1). (Вопрос: во что превратится булево true + int(1)?)



Также мы можем объяснить ещё одну странность:



$a = "foo";
$a++;
var_dump($a); // string("fop")

$a = "foo";
$a += 1;
var_dump($a); // int(1)

$a = "foo";
$a = $a + 1;
var_dump($a); // int(1)


Поскольку строку не получается преобразовать в число, то выполняется обычное инкрементирование строки. Выражение добавления преобразует строки в long после проверки на наличие чисел. Поскольку их нет, то выполняется конвертирование строки в int(0) и к ней добавляется int(1).
Original source: habrahabr.ru.

https://habrahabr.ru/post/305906/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

PHP-Дайджест № 88 – интересные новости, материалы и инструменты (13 июня – 17 июля 2016)

Воскресенье, 18 Июля 2016 г. 00:07 (ссылка)





После небольшого перерыва PHP-Дайджест снова в деле! Предлагаем вашему вниманию очередную подборку со ссылками на новости и материалы.



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





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







PHP







Инструменты




  • GitaminHQ/Gitamin — Клон GitHub реализованный на Laravel.

  • Opulence — Новый PHP7 фреймворк. Прислал tkf.

  • franzose/kontrolio — Простая библиотека валидации данных без дополнительных зависимостей, вдохновленная Laravel и Symfony. Прислал franzose.

  • layershifter/tld-extract — Пакет для корректного парсинга доменных имен с использованием Public Suffix List.

  • rinvex/repository — Независимая реализация паттерна Репозиторий. Прислал tkf.

  • paragonie/halite — Криптографическая библиотека для PHP. Пример использования Halite для двустороннего шифрования email-сообщений.

  • dunglas/doctrine-json-odm — ODM для Doctrine с поддержкой JSON-типов, добавленных в PostrgeSQL и MySQL.

  • JakubOnderka/PHP-Parallel-Lint — Инструмент проверки синтаксиса, анализирующий файлы параллельно.

  • Codeception/AspectMock 2.0 — Библиотека для создания тестовых двойников, позволяющая делать моки буквально для всего.

  • Bacon/BaconQrCode — Генератор QR-кодов.

  • paragonie/multi_factor — Цель библиотеки реализовать единый интерфейс для работы с различными провайдерами двухфакторной аутентификации. Из коробки поддерживается GoogleAuth.

  • simplesamlphp/simplesamlphp — Реализация стандарта SAML, в свою очередь, используемого для реализации технология единого входа (SSO).

  • heiglandreas/JUnitDiff — Библиотека позволяет показывать, что изменилось между двумя запусками тестов PHPUnit.

  • bitExpert/disco — DI контейнер совместимый со стандартом container-Interop. Подробный туториал по использованию.

  • gabrielbull/omnimail — Библиотека для отправки писем с помощью популярных сервисов рассылки: AmazonSES, Mailgun, Mandrill и другие.

  • PiPHP/GPIO — Библиотека для доступа к Raspberry Pi из PHP. Туториал в поддержку.

  • hoaproject/Websocket — Годная реализация вебсокет клиента и сервера.

  • pavelfluffy/connectorСигналы и слоты в PHP. Такие же как в Qt. Ну почти.

  • larabros/elogram — Библиотека для доступа к Instagram API в стиле Eloquent.

  • klermonte/zerg — Библиотека для удобного парсинга структурированных бинарных данных.

  • php-ai/php-ml — Библиотека с реализацией алгоритмов машинного обучения на PHP.





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







Аудио и видеоматериалы







Занимательное







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



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

Вопросы и предложения пишите на почту или в твиттер.



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



Прислать ссылку

Быстрый поиск по всем дайджестам

<- Предыдущий выпуск: PHP-Дайджест № 87



Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/305808/

Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<php - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda