Verification: a143cc29221c9be0

Php 5 handler что это

Обработка событий с помощью методов jQuery

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

В jQuery повесить событие (слушатель событий) на определённый элемент можно с помощью функций on и one, а также кратких записей on.

// функция on
.on(events,handler);
// функция one
.one(events,handler);

// events - событие или список событий через пробел, при наступлении которых необходимо выполнить обработчик (handler)
// handler - функция (обработчик события)

// краткая запись функции on
.event(handler);

// event - название события (можно использовать для обработки только тех событий, для которых в jQuery создана такая краткая запись)

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

Например, добавим с помощью функции on событие click для всех элементов с классом btn:

// использование в качестве обработчика анонимной функции
$('.btn').on('click', function() {
  // действия, которые будут выполнены при наступлении события...
  console.log($(this).text());
});

// использование обычной функции в качестве обработчика
var myFunction = function() {
  console.log($(this).text());
}
$('.btn').on('click', myFunction);

Вышеприведённый код, записанный с использованием короткой записи функции on:

$('.btn').click(function() {
  // действия, которые будут выполнены при наступлении события...
  console.log($(this).text());
});

Дополнительная информация о событии

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

$('#demo').on('click', function(e){
  // e – объект Event, содержащий дополнительную информацию о произошедшем событии
  // часто используемые свойства объекта Event
  e.preventDefault(); //отменить выполнение действия по умолчанию
  e.stopPropagation(); //остановить дальнейшее всплытие события
  // e.type – получить тип события
  // e.target – ссылка на элемент, на котором произошло событие
  // e.currentTarget - ссылка на текущий элемент (для которого сработал обработчик). Это свойство, как правило, равно функции this.
  // e.currentTarget === this
  // e.which – код клавиши (для мыши), код кнопки или символа (для клавиатуры)
  //e.pageX, e.pageY – координаты курсора, относительно левого верхнего угла документа
});

Пространство имён

В jQuery после указания имени события вы можете ещё дополнительно указать пространство имён.

Например:

// событие click в пространстве имён first
$('#demo').on('click.first',function(){
  console.log('1 обработчик события click');
});
// событие click в пространстве имён second
$('#demo').on('click.second',function(){
  console.log('2 обработчик события click');
});

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

// вызвать событие click в пространстве имён first
$('#demo').trigger('click.first');

// вызвать событие click в пространстве имён second
$('#demo').trigger('click.second');

Также с его помощью очень просто удалять определённые события:

//удалить обработчики события click в пространстве имён first
$('#demo').off('click.first');

//удалить обработчики события click в пространстве имён second
$('#demo').off('click.second');

Описание и примеры использования функций trigger и off рассматриваются в статье немного ниже.

Передача дополнительных данных в обработчик

При необходимости вы можете передать данные в обработчик события (посредством указания дополнительного аргумента в функции on). Доступ к переданным данным внутри обработчика осуществляется через объект Event.

Осуществляется это так (пример):

...

Как повесить несколько событий на один элемент

Пример использования одного обработчика для нескольких (2 или более) событий:

$('#id').on('keyup keypress blur change', function(e) {
  console.log(e.type); // тип события
});

// или так
var myFunction = function() {
  ...
}

$('#id')
  .keyup(myFunction)
  .keypress(myFunction)
  .blur(myFunction)
  .change(myFunction);

Для каждого события своя функция:

$("#id").on({
  mouseenter: function() {
    // обработчик события mouseenter...
  },
  mouseleave: function() {
    // обработчик события mouseleave...
  },
  click: function() {
    // обработчик события click...
  }
});

Пример использования в jQuery несколько обработчиков (функций) на одно событие:

$("#demo").click(function(){
  console.log('1 обработчик события click');
});
$("#demo").click(function(){
  console.log('2 обработчик события click');
});

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

var eventList = $._data($('#demo')[0], 'events');
console.log(eventList);

Программный вызов события

Для вызова события из кода в jQuery есть 2 метода:

  • trigger - вызывает указанное событие у элемента.
  • triggerHandler - вызывает обработчик события, при этом само событие не происходит.
$('#header').on('click',
  function() {
    console('Произошёл клик на элементе #header');
  }
});

// программный вызов события click элемента
$('#header').trigger('click');
// короткая запись данного вызова
$('#header').click();
// вызов обработчика события click у выбранного элемента
$('#header').triggerHandler('click');

jQuery - Событие загрузки страницы (ready)

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

Самая короткая запись события загрузки страницы в jQuery выглядит так:

$(function(){
  // действия, которые необходимо выполнить после загрузки документа...

});

Но, можно использовать и более длинную запись:

$(document).ready(function(){
  // действия, которые необходимо выполнить после загрузки документа...

});

jQuery - Событие загрузки (load)

Событие load браузер генерирует элементу, когда он и все вложенные в него элементы были полностью загружены. Данное событие предназначено только для элементов, в которых присутствует URL: image, script, iframe и window.

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

$(window).on('load', function() {
  // действия после полной загрузки страницы...

});

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


...

jQuery - События мыши

В jQuery для отслеживания действий мыши наиболее часто используют следующие события:

  • mousedown
  • mouseup
  • click
  • mousemove
  • wheel
  • hover
  • mouseenter
  • mouseover
  • mouseleave
  • mouseout

jQuery - Событие клик (click)

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

Например, повесим обработчик на событие onclick элемента window. При наступлении данного события обработчик будет выводить номер нажатой клавиши и координаты курсора:

$(window).on('click', function(e) {
  // обработка события click...
  console.log('Нажата кнопка: ' + e.which); //1 - левая кнопка, 2 - средняя кнопка, 3 - правая
  console.log('Координаты курсора: x = ' + e.pageX + ' ; y = ' + e.pageY);
});

Например, повесим событие onclick на все элементы с классом btn:

$('.btn').on('click', function(){
  // код обработчика события нажатия кнопки
   ...
});

Краткая запись события по клику:

$('.btn').click(function(){
   ...
});

Например, разберем, как можно скрыть блок через некоторое время после события click:


...

jQuery - Событие при наведении (hover)

Событие при поднесении курсора является сложным и состоит из 2 событий:

  • вхождения (mouseenter, mouseover);
  • покидания (mouseleave, mouseout).

События mouseenter и mouseleave в jQuery отличаются от mouseover и mouseout только тем, что они не возникают когда курсор соответственно входит и покидает внутренние элементы прослушиваемого элемента. Другими словами события mouseover и mouseout всплывают, а mouseenter и mouseleave – нет.

Например, изменим цвет элемента списка при поднесении к нему курсора:


  • Ручка
  • Карандаш
  • Линейка
...

Те же самые действия, но использованиям mouseover и mouseout:

$('ul>li').
  mouseover(function(){
    // при вхождении в элемент
    $(this).css('color','orange');
  }).
  mouseout(function(){
    // при покидании элемента
    $(this).css('color','black');
  });

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

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

Количество: 0
...

Вместо использования mouseenter и mouseleave можно использовать событие hover.

Например, перепишем вышеприведённый пример, используя hover:

$('ul>li').hover(
  function(){
    // при вхождении в элемент
    $(this).css('color','orange');
  },
  function(){
    // при покидании элемента
    $(this).css('color','black');
  }
);

При использовании события hover в jQuery, первый обработчик используется для задания действий при вхождении курсора в элемент (mouseenter), а второй - при покидании (mouseleave).

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

Например:

$('h1').hover(function(){
  console.log('Произошло события входа в элемент или выхода из него');
});

jQuery - Событие движения мыши

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

$('.target').mousemove(function(e) {
  console.log('Вызван обработчик для события mousemove.');
  console.log('Координаты относительно левого верхнего угла документа: ' + e.pageX + ', ' + e.pageY);
  console.log('Координаты курсора внутри цели: ' + e.offsetX + ', ' + e.offsetY);
});

jQuery - Событие колёсика мыши (wheel)

Прослушивание события прокрутки колёсика (wheel) мышки можно осуществить так:

$(window).on('wheel', function(e) {
  // код обработчика (например)...
  console.log('Количество прокрученных пикселей: ' + e.originalEvent.deltaY);
  if (e.originalEvent.deltaY 

Данное событие в отличие от scroll генерируется браузером только для колёсика мышки, при этом неважно прокручивается элемент или нет, т.е. с ним можно работать на элементах с overflow, равным hidden. Еще одно отличие заключается в том, что wheel генерируется до прокрутки, а scroll - после неё.

jQuery – События клавиатуры

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

keydown -> keypress -> keyup
  • keydown (клавиша нажата, но ещё не отпущена);
  • keypress (событие генерируется для букв, цифр и других клавиш, за исключением управляющих) – предназначено для того чтобы получить код символа (события keydown и keyup позволяют узнать только о коде клавиши, но не символа);
  • keyup (генерируется браузером при отпускании клавиши).

Например, напишем обработчик для прослушивания всех событий, которые происходят при нажатии клавиши:


...

Пример, в котором показано, как можно прослушать событие keypress и узнать, нажато ли указанное сочетание клавиш:

$(document).keypress("c",
  function(e) {
    if(e.ctrlKey) {
      console.log('Нажато сочетание клавиш Ctrl+c');
    }
});

Пример, как можно прослушать сочетание клавиш Ctrl+Enter:

$(document).keydown(function(e) {
  // с поддержкой macOS X
  if ((e.ctrlKey || e.metaKey) && (e.keyCode == 13 || e.keyCode == 10)) {
    // ваши действия...

  }
}

Пример, с использованием событий keydown и keyup:


...

jQuery – События элементов формы

В jQuery можно выделить следующие события для элементов формы и не только:

  • focus (focusin)
  • blur (focusout)
  • change
  • input (для текстовых элементов формы)
  • select
  • submit

jQuery - События получения и потери фокуса

Событие focus посылается элементу, когда он получает фокус. Данное событие генерируется для элементов input, select и ссылок (a href="..."), а также любых других элементов, у которых установлено свойство tabindex. Получение элементом фокуса обычно осуществляется посредством клика или нажатия клавиши Tab на клавиатуре. Событие focus не всплывает.

Кроме focus есть ещё похожее событие, называется оно focusin. В отличие от focus данное событие всплывает, и оно может, например, использоваться для обнаружения события фокуса родительскими элементами.

Событие blur посылается элементу, когда он теряет фокус. Так же как и focus, событие blur имеет похожее событие focusout. Данное событие отличается от blur тем, что оно может всплывать. Это возможность можно использовать, например, для получения его на родительских элементах, а не только на том элементе, который его вызвал (target).

Например, при получении элементом div события фокуса установим ему фон оранжевого цвета:

...

Точно такое же выполнить с помощью событий focus и blur не получится, т.к. они не всплывают:

$('#demo input').
  focus(function(){
    $(this).parent().css('background-color','orange');
  })
  .blur(function(){
    $(this).parent().css('background-color','inherit');
  });

jQuery - Событие изменения (change)

Событие change предназначено для регистрации изменения значения элементов input, textarea и select. Для элементов select, checkboxes, и radio кнопок данное событие возникает сразу (т.е. как только пользователь делает какой-то выбор). Но для других элементов данное событие не будет происходить до тех пор, пока этот элемент не потеряет фокус.

Пример использования события change для слежения за состоянием элемента checkbox. Доступность кнопки будет определять в зависимости от того в каком состоянии (checked или нет) находиться флажок:


...

Пример, в котором рассмотрим, как получить значение элемента select при его изменении:

...

Пример, в котором рассмотрим, как получить все выбранные элементы select при его изменении:


...

Пример программного вызова события change для элемента select:

// list - id элемента change
$('#list').trigger('change');
// краткая запись
$('#list').change();
// вызов только обработчика события change
$('#list').triggerHandler('change');

Пример использования события изменения change для получения значения элемента input:


...

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

Пример, использования события ввода для получения значения элемента input:

$('input[name="name"]').on('input',function(){
  var value = $(this).val();
  console.log(value);
});

Пример, в котором представлен один из способов получения значения элемента textarea:


...

Пример, в котором рассмотрим, как с помощью события change получить значение выбранного элемента input с type, равным radio:

Windows
Linux
macOS
...

jQuery - Событие выбора (select)

Событие выбора select генерируется браузером, когда пользователь внутри элементов input с type="text" или textarea выделяет текст.

$( "#target" ).select(function() {
  console.log('Вызван обработчик события select');
});

jQuery – Событие отправки формы (submit)

Событие submit возникает у элемента, когда пользователь пытается отправить форму. Данное событие может быть добавлено только к элементам form.

Пример, использования события submit:


...
...

Программный вызов отправки формы:

$('#feedback').submit();
$('#feedback').trigger('submit');

Для отслеживания состояния скроллинга в jQuery используется событие scroll.

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

// краткая запись функции
$(window).scroll(function() {
  // действия при скроллинге страницы...
  if ($(this).scrollTop()>200) {
    $('.scrollup').fadeIn();
  } else {
    $('.scrollup').fadeOut();
  }
});

jQuery - Событие изменения размеров окна (resize)

Для прослушивания события изменения окна браузера используется resize:

Например, создадим обработчик, который будет при изменении окна браузера выводить в конец страницы её ширину и высоту:

$(window).resize(function() {
  $('body').append('

Ширина x Высота = ' + window.innerWidth + ' x ' + window.innerHeight + '

'); });

jQuery - Отмена стандартного поведения события

Некоторые элементы в HTML имеют стандартное поведение. Например, когда пользователь нажимает на ссылку, он переходит по адресу указанному в атрибуте href. Если вам это действие не нужно, то его можно отменить. Для отмены стандартного поведения необходимо вызвать в обработчике этого события метод preventDefault объекта event.

Например, отменим стандартное поведение всех ссылок на странице, имеющих класс service:

$('a.service').on('click',function(e){
  //отменяем стандартное действие браузера
  e.preventDefault();
  // действия, которые будет выполнять ссылка
  ...
});

Что такое всплытие и как его остановить

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

текущий элемент (цель) -> родительский элемент цели -> прародительский элемент -> ... -> document -> window

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

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

Некоторый текст...фрагмент......продолжение...
...

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


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

$('a').on('click', function(e){
  //e.preventDefault();
  //e.stopPropagation();
  ...
  return false;
});

Добавление событий к динамически созданным объектам

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

$(document).on(eventName, selector, handler);

// document или любой другой существующий родительский элемент
// eventName - имя события
// selector - селектор, осуществляющий фильтрацию потомков, для которых необходимо запустить обработчик события
// handler - обработчик события

Это действие можно осуществить благодаря тому, что событие всплывает, и, следовательно, возникает у всех предков этого элемента. А объект, до которого всплывают все события на странице, является document. Поэтому в большинстве случаев выбирают именно его. После этого зная селектор, функция on может программно отобрать среди элементов (элемента, который вызвал это событие (target) и всех его предков включая родителя) те, которые соответствуют ему. И затем для всех отобранных элементов выполнить указанный в функции on обработчик. Действия, посредством которых обработка события переносится на другой элемент (предок), называется в jQuery ещё процессом делегирования события.

Например, добавим событие к элементу, которого ещё нет на странице:



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

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

$(document).on('click','#comment a',function(e) {
  if(!(location.hostname === this.hostname || !this.hostname.length)) {
    e.preventDefault();
    location.href='away?link='+encodeURIComponent($(this).attr('href'));
  }
});

jQuery - Удалить обработчик события

Удаление обработчиков события осуществляется с помощью метода off. При этом с помощью него можно удалить только те обработчики, которые добавлены посредством метода on.

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

Например, отключим все обработчики у элементов с классом link:

$('.link').off();

Например, удалим событие click у всех элементов с классом link:

$('.link').off('link');

Специальный селектор (**) позволяет удалить только делегированные события с сохранением не делегированных:

$('.link').off('click','**');

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

// добавление делегированного события
$('ul').on('click','li', function(){
  // выводим в консоль контент элемента li
  console.log($(this).text());
});

// удаление делегированного события
$('ul').off('click','li');

Удалить все обработчики openModal делегированного события click в пространстве имён modal для элементов с классом show:

$('body').on('click.modal', '.show', openModal);

Проблемы с традиционными приложениями на PHP

Масштабируемость является неизбежной проблемой традиционного LAMP-стека. Под масштабируемым приложением мы понимаем такое приложение, которое может обрабатывать сильно меняющиеся уровни пользовательского трафика. Часто PHP-приложения масштабируются горизонтально путём добавления веб-серверов по мере необходимости. Управление трафиком происходит с помощью балансировщика нагрузки, который перенаправляет запросы на разные серверы. Каждый новый сервер приводит к дополнительным накладным расходам, связанным с сетью, администрированием, объёмом хранилища, системами резервного копирования и восстановления, а также необходимостью добавления каждого сервера в систему по управлению активами (asset management). Кроме того, каждый новый сервер работает независимо от других, что может привести к проблемам с синхронизацией конфигурации между ними.

Горизонтальное масштабирование приложений в традиционном LAMP-стеке

Горизонтальное масштабирование приложений в традиционном LAMP-стеке

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

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

Бессерверная архитектура LAMP

Традиционное веб-приложение можно разделить на две части:

  • Статические ресурсы (медиа-файлы, CSS, JS)
  • Динамическое приложение (PHP, MySQL)

Бессерверный подход к работе с этими частями изображён ниже:

Бессерверный LAMP-стек

Бессерверный LAMP-стек

Все запросы на динамические ресурсы (то есть, всё, кроме пути /assets/*) перенаправляются в Amazon API Gateway. Это полностью управляемый сервис для создания, публикации и защиты API любого масштаба. Он действует как «входная дверь» в PHP-приложение, направляя запросы к соответствующим Lambda-функциям. В свою очередь, Lambda-функции содержат бизнес-логику приложения и осуществляют работу с базой данных MySQL. В качестве входных данных в Lamda-функцию вы можете передавать любые комбинации заголовков запросов, переменных пути, параметров строки запроса и тела запроса.

Важные возможности AWS для PHP-разработчиков

Amazon Aurora Serverless

Во время re:Invent 2017 компания AWS анонсировала Aurora Serverless, бессерверную реляционную базу данных с оплатой по факту использования. Этот сервис управляет созданием базы данных, а также её масштабированием.

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

Во время re:Invent 2018 компания AWS анонсировала две новые возможности Lambda. Они позволяют разработчикам создавать свои среды выполнения кода, а также обмениваться и управлять исходным кодом, который используется несколькими функциями.

Улучшенная работа Lambda-функций внутри VPC

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

Amazon RDS Proxy

Во время re:Invent 2019 компания AWS анонсировала запуск нового сервиса под названием Amazon RDS Proxy. Это полностью управляемый прокси, который находится между вашим приложением и реляционной базой данных. Он объединяет подключения к базе данных в пулы и позволяет использовать их совместно для повышения масштабируемости вашего приложения.

Важные моменты на временной шкале бессерверного LAMP-стека

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

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

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

Важные моменты на временной шкале бессерверного LAMP-стека

Важные моменты на временной шкале бессерверного LAMP-стека

Также для создания среды выполнения вам необходимо собрать требуемую версию PHP под операционную систему Amazon Linux совместимой с Lambda версии. Чтобы это сделать, следуйте пошаговым инструкциям в GitHub.

Файл bootstrap

Ниже представлен пример простого файла boostrap для PHP. Он предназначен только для объяснения принципа работы и не покрывает обработку ошибок или дополнительные абстракции. Чтобы удостовериться, что вы корректно обрабатываете исключения, во время создания своей среды выполнения обратитесь к документации по API.

#!/opt/bin/php
get('http://' . $_ENV['AWS_LAMBDA_RUNTIME_API'] . '/2018-06-01/runtime/invocation/next');

    return [
      'invocationId' => $response->getHeader('Lambda-Runtime-Aws-Request-Id')[0],
      'payload' => json_decode((string) $response->getBody(), true)
    ];
}

function sendResponse($invocationId, $response)
{
    $client = new \GuzzleHttp\Client();
    $client->post(
    'http://' . $_ENV['AWS_LAMBDA_RUNTIME_API'] . '/2018-06-01/runtime/invocation/' . $invocationId . '/response',
       ['body' => $response]
    );
}

Декларация #!/opt/bin/php сообщает загрузчику о необходимости использовать бинарный файл PHP, скомпилированный для Amazon Linux.

Файл bootstrap в рабочем цикле производит следующие действия:

  1. Получает следующий запрос.
  2. Выполняет исходный код для обработки запроса.
  3. Возвращает ответ.

Следуйте шагам по ссылке для упаковки файла bootstrap и скомпилированного бинарного файла PHP в архив с названием `runtime.zip`.

Библиотеки и зависимости

В файле bootstrap используется локальный интерфейс на основе HTTP. С помощью него можно получить данные о событии, которое было передано в Lambda-функцию, а также вернуть ответ, полученный от неё. В нашем случае для выполнения запросов к указанному API-интерфейсу используется Guzzle, популярный HTTP-клиент для PHP. Пакет Guzzle устанавливается с помощью менеджера пакетов Composer. Такой способ установки позволяет простым образом добавлять новые библиотеки и зависимости по мере развития приложения.

Следуйте указанным шагам для создания и упаковки зависимостей в файл `vendors.zip`.

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

  • Bref -Serverless PHP on AWS Lambda
  • Stackery – PHP Lambda layer

Шаги ниже показывают, как опубликовать созданные ранее файлы `runtime.zip` и `vendor.zip` в слои Lambda, а также как их потом использовать для создания Lambda-функции на языке PHP:

  1. Используйте интерфейс командной строки AWS (AWS CLI) для публикации слоёв на основе ранее созданных файлов
    aws lambda publish-layer-version \
        --layer-name PHP-example-runtime \
        --zip-file fileb://runtime.zip \
        --region eu-west-1

    aws lambda publish-layer-version \
        --layer-name PHP-example-vendor \
        --zip-file fileb://vendors.zip \
        --region eu-west-1

  2. Запомните значение поля LayerVersionArn в выводе каждой команды (например, arn:aws:lambda:eu-west-1:XXXXXXXXXXXX:layer:PHP-example-runtime:1), он вам понадобится через несколько шагов.

Создание Lambda-функции на PHP

Вы можете создать Lambda-функцию с помощью AWS CLI, AWS Serverless Application Model (SAM) или напрямую в Консоли управления AWS. В случае использования консоли, следуйте указанным ниже шагам:

  1. Перейдите в секцию Lambda в консоли AWS и нажмите Create function.
  2. Введите “PHPHello в поле Function name и выберите Provide your own bootstrap в поле Runtime. Затем нажмите Create function.
  3. Нажмите правой кнопкой мыши по файлу bootstrap.sample и выберите Delete.
  4. Нажмите на иконку Layers, а затем на кнопку Add a layer.
  5. Выберите опцию Provide a layer version ARN, затем введите в поле Layer version ARN значение ARN слоя среды выполнения кода, который был создан в предыдущем разделе статьи.
  6. Повторите шаг 5 для ARN второго слоя (PHP-example-vendor).
  7. В секции Function Code создайте директорию с названием src, а внутри неё файл index.php.
  8. Вставьте следующий код в файл index.php:
    //index function
    function index($data)
    {
     return "Hello, ". $data['name'];
    }
    
  9. В поле Handler введите “index”. Таким образом вы указываете Lambda, что во время вызова необходимо запускать функцию index.
  10. Нажмите Save в правом верхнем углу страницы.
  11. Нажмите кнопку Test в правом верхнем углу страницы, затем введите “PHPTest” в поле Event name. Введите следующий текст в основное поле для параметров теста и нажмите Create: {"name": "world"}
  12. Нажмите Test, затем раскройте выпадающий список Details рядом с заголовком execution result.

Вы можете увидеть, что значение параметра “name” используется для вывода “Hello, world”. Само значение берётся из параметра $data['name'], который передаётся в Lambda-функцию. Вывод в секции Log output содержит информацию о фактической продолжительности запуска, оплачиваемой продолжительности и объёме оперативной памяти, который потребовался для запуска кода.

Простейшая реализация

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


class Season
{
    public const SUMMER = 'summer';
    public const AUTUMN = 'autumn';
    public const WINTER = 'winter';
    public const SPRING = 'spring';

    private string $value;

    public function __construct(string $value)
    {
        if (
            self::SUMMER !== $value &&
            self::AUTUMN !== $value &&
            self::WINTER !== $value &&
            self::SPRING !== $value
        ) {
            throw new InvalidArgumentException(sprintf(
                "Wrong season value. Awaited '%s', '%s', '%s' or '%s'.",
                self::SUMMER,
                self::AUTUMN,
                self::WINTER,
                self::SPRING
            ));
        }

        $this->value = $value;
    }

Продемонстрировать процесс создания перечисления можно тестом.


    public function testCreation(): void
    {
        $summer = new Season(Season::SUMMER);
        $autumn = new Season(Season::AUTUMN);
        $winter = new Season(Season::WINTER);
        $spring = new Season(Season::SPRING);

        $this->expectException(InvalidArgumentException::class);

        $wrongSeason = new Season('Wrong season');
    }

Для получения внутреннего состояния можно реализовать метод-запрос. К примеру toValue() или getValue(). В случае, если внутреннее состояние описывается строкой, то для его получения идеально подходит магический метод __toString().


    public function __toString(): string
    {
        return $this->value;
    }

Реализация __toString() даёт возможность исользовать объект-перечисление напрямую в конкатенациях строк и в виде строковых аргументов методов.


    public function testStringConcatenation(): void
    {
        $autumn = new Season(Season::AUTUMN);
        $spring = new Season(Season::SPRING);
        $value = $autumn . ' ' . $spring;

        $this->assertIsString($value);
        $this->assertSame(Season::AUTUMN . ' ' . Season::SPRING, $value);
    }

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


    public function testEquality(): void
    {
        $firstSummer = new Season(Season::SUMMER);
        $secondSummer = new Season(Season::SUMMER);
        $winter = new Season(Season::WINTER);

        $this->assertTrue($firstSummer == $secondSummer);
        $this->assertFalse($firstSummer == $winter);
        $this->assertFalse($firstSummer === $secondSummer);
        $this->assertFalse($firstSummer === $winter);
    }

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


    public function equals(Season $season): bool
    {
        return $this->value === $season->value;
    }

    public function testEquals(): void
    {
        $firstSummer = new Season(Season::SUMMER);
        $secondSummer = new Season(Season::SUMMER);
        $firstWinter = new Season(Season::WINTER);
        $secondWinter = new Season(Season::WINTER);

        $this->assertTrue($firstSummer->equals($secondSummer));
        $this->assertTrue($firstWinter->equals($secondWinter));
        $this->assertFalse($firstSummer->equals($secondWinter));
    }

Можно обратить внимание на тавтологию при создании экземпляров перечисления: слово Season повторяется дважды. Её можно избежать если для создания использовать статические методы.

Предположим: в магазине аудиотехники продаются микрофоны. В ассортименте представлены модели использующих для подключения xlr 3pin, jack, mini jack и usb разъёмы.


class MicrophoneConnector
{
    public const XLR_3PIN = 'xlr_3pin';
    public const JACK = 'jack';
    public const MINI_JACK = 'mini_jack';
    public const USB = 'usb';

    private string $value;

    private function __construct(string $value)
    {
        $this->value = $value;
    }

    public function __toString(): string
    {
        return $this->value;
    }

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


    public static function xlr3pin(): self
    {
        return new self(self::XLR_3PIN);
    }

По аналогии необходимо реализовать методы jack, miniJack и usb.


    public function testEquality(): void
    {
        $firstJack = MicrophoneConnector::jack();
        $secondJack = MicrophoneConnector::jack();
        $xlr3pin = MicrophoneConnector::xlr3pin();

        $this->assertTrue($firstJack == $secondJack);
        $this->assertFalse($firstJack == $xlr3pin);
        $this->assertFalse($firstJack === $secondJack);
        $this->assertFalse($firstJack === $xlr3pin);
    }

Перечисление как одиночка

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

Предположим: в организацию приходит заказ на оказание некоторой услуги. Заказ может быть принят, отвергнут либо, при возникновении нестандартной ситуации, решение может быть отложено для выяснения обстоятельств. Решение можно описать как перечисление из значений agree, disagree и hold.


class Decision
{
    public const AGREE = 'agree';
    public const DISAGREE = 'disagree';
    public const HOLD = 'hold';

    private string $value;

    private function __construct(string $value)
    {
        $this->value = $value;
    }

    private function __clone() { }

    public function __toString(): string
    {
        return $this->value;
    }

С целью предотвращения создания нескольких экземпляров обьекта необходимо заблокировать конструктор и магический метод __clone(). Для каждого варианта перечисления реализуется статический метод.


    private static $agreeInstance = null;

    public static function agree(): self
    {
        if (null === self::$agreeInstance) {
            self::$agreeInstance = new self(self::AGREE);
        }

        return self::$agreeInstance;
    }

По аналогии c agree реализуются методы disagree и hold.


    public function testEquality(): void
    {
        $firsAgree = Decision::agree();
        $secondAgree = Decision::agree();
        $firstDisagree = Decision::disagree();
        $secondDisagree = Decision::disagree();

        $this->assertTrue($firsAgree == $secondAgree);
        $this->assertTrue($firstDisagree == $secondDisagree);
        $this->assertFalse($firsAgree == $secondDisagree);
        $this->assertTrue($firsAgree === $secondAgree);
        $this->assertTrue($firstDisagree === $secondDisagree);
        $this->assertFalse($firsAgree === $secondDisagree);
    }

Такая реализация позволяет сравнивать перечисления напрямую. А в купе с реализацией __toString() обеспечивает интуитивную работу с перечислением как с простым типом. Но у такого подхода есть серьёзный недостаток: всё равно есть возможность создать новый экземпляр объекта с помощью десериализации. Для обеспечения корректной десериализации придётся добавить механизм создания экземпляра из произвольного значения.


    public static function from($value): self
    {
        switch ($value) {
            case self::AGREE:
                return self::agree();

            case self::DISAGREE:
                return self::disagree();

            case self::HOLD:
                return self::hold();

            default:
                throw new InvalidArgumentException(sprintf(
                    "Wrong decision value. Awaited '%s', '%s' or '%s'.",
                    self::AGREE,
                    self::DISAGREE,
                    self::HOLD
                ));
        }
    }

Предположим: простейший заказ можно описать как пояснение к заказу и решение принятое по нему. Между запросами существует необходимость сохранять заказ в кэш. Для этого можно воспользоваться стандартным механизмом сериализации: реализовать магические методы __sleep() и __wakeup(). В методе __sleep() перечислить поля для сериализации. В методе __wakeup() восстановить конкретный экземпляр при помощи статического метода from() класса Decision.


class Order
{
    private Decision $decision;
    private string $description;

    public function getDecision(): Decision
    {
        return $this->decision;
    }

    public function getDescription(): string
    {
        return $this->description;
    }

    public function __construct(Decision $decision, string $description)
    {
        $this->decision = $decision;
        $this->description = $description;
    }

    public function __sleep(): array
    {
        return ['decision', 'description'];
    }

    public function __wakeup(): void
    {
        $this->decision = Decision::from($this->decision);
    }
}

Процесс сериализации/десериализации можно представить в виде теста.


    public function testSerialization(): void
    {
        $order = new Order(Decision::hold(), 'Some order description');

        $serializedOrder = serialize($order);
        $this->assertIsString($serializedOrder);

        /** @var Order $unserializedOrder */
        $unserializedOrder = unserialize($serializedOrder);
        $this->assertInstanceOf(Order::class, $unserializedOrder);

        $this->assertTrue($order->getDecision() === $unserializedOrder->getDecision());
    }

Перечисление с большим числом вариантов

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


class Season
{
    public const SEASONS = ['summer', 'autumn', 'winter', 'spring'];

    private string $value;

    public function __construct(string $value)
    {
        if (!in_array($value, self::SEASONS)) {
            throw new InvalidArgumentException(sprintf(
                "Wrong season value. Awaited one from: '%s'.",
                implode("', '", self::SEASONS)
            ));
        }

        $this->value = $value;
    }

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

Предположим: в магазине одежды продают одежду разных размеров. Тогда возможные размеры можно описать перечислением.


class Size
{
    public const SIZES = ['xxs', 'xs', 's', 'm', 'l', 'xl', 'xxl'];

    private string $value;

    private function __construct(string $value)
    {
        $this->value = $value;
    }

    public function __toString(): string
    {
        return $this->value;
    }

    public static function __callStatic($name, $arguments)
    {
        $value = strtolower($name);
        if (!in_array($value, self::SIZES)) {
            throw new BadMethodCallException("Method '$name' not found.");
        }

        if (count($arguments) > 0) {
            throw new InvalidArgumentException("Method '$name' expected no arguments.");
        }

        return new self($value);
    }
}

    public function testEquality(): void
    {
        $firstXxl = Size::xxl();
        $secondXxl = Size::xxl();
        $firstXxs = Size::xxs();
        $secondXxs = Size::xxs();

        $this->assertTrue($firstXxl == $secondXxl);
        $this->assertTrue($firstXxs == $secondXxs);
        $this->assertFalse($firstXxl == $secondXxs);
        $this->assertFalse($firstXxl === $secondXxl);
        $this->assertFalse($firstXxs === $secondXxs);
        $this->assertFalse($firstXxl === $secondXxs);
    }

Недостатком использования такого подхода является, то, что синтаксический анализатор IDE не может распознать методы вызываемые через __callStatic(). Методы приходится описывать в DocBlock'ах, что практически сводит на нет пользу от обобщения кода.


/**
 * @method static Size xxs()
 * @method static Size xs()
 * @method static Size s()
 * @method static Size m()
 * @method static Size l()
 * @method static Size xl()
 * @method static Size xxl()
 */
class Size
{

Критика других реализаций

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

В PECL-пакете SPL есть класс SplEnum. Однако SPL пакет может быть не установлен в исполняющей среде (либо его и не хочется ставить).


Установка FastRoute:

Установка происходит как всегда через composer, введите в терминал такую команду:

composer require nikic/fast-route

Работает это на PHP 7.4 или новее.

Работа с FastRoute:

Перед тем как начать с ним работать, имеет смысл зайти или создать файл «.htaccess» и написать там вот это:

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-f

RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule . index.php [QSA,L]

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

После вам нужно создать диспачер, который будет проверять URL, введённый в браузерной строке, для этого заходим или создаём файл «index.php» и вот что пишем:

require 'vendor/autoload.php';

$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {

    $r->addRoute('GET', '/article', 'get_article_handler');

});

Первым делом мы подключаем файл «autoload.php», который хранит все библиотеки которые мы скачали через composer, следующие это мы создаём диспчер, для этого используем метод FastRoute\simpleDispatcher(), который принимает в себя функцию.

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

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

Теперь нужно немного обработать URL, делается это так:

// Получаем метод запроса и сам URL

$httpMethod = $_SERVER['REQUEST_METHOD'];

$uri = $_SERVER['REQUEST_URI'];

// Проверяем, есть ли GET параметры

if (false !== $pos = strpos($uri, '?')) {

    $uri = substr($uri, 0, $pos);

}

// Декодируем URL

$uri = rawurldecode($uri);

В начале мы тут получаем тип запроса, это может быть GET или POST и т.д., и сам URL тоже получаем, следующие фильтруем, проверяем, есть ли там параметры GET, последние декодируем URL.

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

// Берём информацию о роутинге

$routeInfo = $dispatcher->dispatch($httpMethod, $uri);

// Проверяем

switch ($routeInfo[0]) {

    // Если нет страницы

    case FastRoute\Dispatcher::NOT_FOUND:

        // ... 404 Не найдена страница

        break;

    // Если нет метода для обработки

    case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:

        $allowedMethods = $routeInfo[1];

        // ... 405 Нет метода

        break;

    // Если всё нашлось

    case FastRoute\Dispatcher::FOUND:

        $handler = $routeInfo[1];

        $vars = $routeInfo[2];

        // ... Вызываем $handler с $vars

        break;

}

В начале тут мы берём все данные о роутинге, потом проверяем диспачер, с помощью оператора switch, в начале проверяем, нашли вообще такой URL, если нет, то выводим ошибку 404, потом, если всё есть, то проверяем есть ли метод обработчик, если нет, то выводим ошибку 405, если всё нашло, то вызываем обработчик, с переменными которые идут к нему.

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