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

Декораторы

Перевод Декораторы в JavaScript с нуля

07.12.2020 16:06:42 | Автор: admin

Будущих студентов курса "JavaScript Developer. Professional" приглашаем записаться на открытый урок по теме "Делаем интерактивного telegram бота на Node.js".

А сейчас делимся традиционным переводом полезного материала.


Разбираемся с функциями-декораторами

Что такое декоратор?

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

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

Зачем нужны декораторы?

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

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

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

Совет. Делитесь компонентами многоразового использования для разных проектов на платформе Bit(Github). Это простой способ документировать и систематизировать независимые компоненты из любых проектов и делиться ими.

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

Bitподдерживает Node, TypeScript, React, Vue, Angular и другие фреймворки JS.

Примеры React-компонентов многоразового использования на Bit.devПримеры React-компонентов многоразового использования на Bit.dev

Декораторы функций

Что такое декораторы функций?

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

Как работают декораторы функций?

Рассмотрим пример.

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

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

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

//decorator functionconst allArgsValid = function(fn) {  return function(...args) {  if (args.length != fn.length) {      throw new Error('Only submit required number of params');    }    const validArgs = args.filter(arg => Number.isInteger(arg));    if (validArgs.length < fn.length) {      throw new TypeError('Argument cannot be a non-integer');    }    return fn(...args);  }}//ordinary multiply functionlet multiply = function(a,b){return a*b;}//decorated multiply function that only accepts the required number of params and only integersmultiply = allArgsValid(multiply);multiply(6, 8);//48multiply(6, 8, 7);//Error: Only submit required number of paramsmultiply(3, null);//TypeError: Argument cannot be a non-integermultiply('',4);//TypeError: Argument cannot be a non-integer

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

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

//ordinary add functionlet add = function(a,b){return a+b;}//decorated add function that only accepts the required number of params and only integersadd = allArgsValid(add);add(6, 8);//14add(3, null);//TypeError: Argument cannot be a non-integeradd('',4);//TypeError: Argument cannot be a non-integer

Декораторы классов: предложение к стандарту, рассматриваемое комитетом TC39

В функциональном программировании на JavaScript декораторы функций используются уже давно. Предложение о декораторах классов находится на 2-м этапе рассмотрения.

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

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

Рассмотрим на примере, как можно реализовать этот подход.

function log(fn) {  return function() {    console.log("Execution of " + fn.name);    console.time("fn");    let val = fn();    console.timeEnd("fn");    return val;  }}class Book {  constructor(name, ISBN) {    this.name = name;    this.ISBN = ISBN;  }  getBook() {    return `[${this.name}][${this.ISBN}]`;  }}let obj = new Book("HP", "1245-533552");let getBook = log(obj.getBook);console.log(getBook());//TypeError: Cannot read property 'name' of undefined

Ошибка возникает потому, что при вызове метода getBook фактически вызывается анонимная функция, возвращаемая функцией-декоратором log. Внутри анонимной функции вызывается метод obj.getBook. Но ключевое слово this внутри анонимной функции ссылается на глобальный объект, а не на объект Book. Возникает ошибка TypeError.

Это можно исправить, передав экземпляр объекта Book в метод getBook.

function log(classObj, fn) {  return function() {    console.log("Execution of " + fn.name);    console.time("fn");    let val = fn.call(classObj);    console.timeEnd("fn");    return val;  }}class Book {  constructor(name, ISBN) {    this.name = name;    this.ISBN = ISBN;  }  getBook() {    return `[${this.name}][${this.ISBN}]`;  }}let obj = new Book("HP", "1245-533552");let getBook = log(obj, obj.getBook);console.log(getBook());//[HP][1245-533552]

Нам также нужно передать объект Book в функцию-декоратор log, чтобы затем можно было передать его в метод obj.getBook, используя this.

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

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

Декораторы классов

В новых декораторах используется специальный синтаксис с префиксом @. Для вызова функции-декоратора log будем использовать такой синтаксис:

@log

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

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

Рассмотрим пример, в котором используется класс Book, мы с ним уже знакомы.

function log(target) {  return function(...args) {    console.log("Constructor called");    return new target(...args);  };}@logclass Book {  constructor(name, ISBN) {    this.name = name;    this.ISBN = ISBN;  }  getBook() {    return `[${this.name}][${this.ISBN}]`;  }}let obj = new Book("HP", "1245-533552");//Constructor Calledconsole.log(obj.getBook());//HP][1245-533552]

Как видите, декоратор logполучает аргумент targetи возвращает анонимную функцию. Она выполняет инструкцию log, а затем создает и возвращает новый экземпляр target, который является классом Book. Можно добавить к target прототипы с помощью target.prototype.property.

Более того, с классом могут использоваться несколько функций-декораторов, как показано в этом примере:

function logWithParams(...params) {  return function(target) {    return function(...args) {      console.table(params);      return new target(...args);    }  }}@log@logWithParams('param1', 'param2')class Book {//Class implementation as before}let obj = new Book("HP", "1245-533552");//Constructor called//Params will be consoled as a tableconsole.log(obj.getBook());//[HP][1245-533552]

Декораторы свойств класса

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

Декораторы методов класса

Аргументы, которые передаются в декоратор метода класса, будут отличаться от аргументов декоратора класса. Декоратор метода класса получает не один, а три параметра:

  • target объект, в котором содержатся конструктор и методы, объявленные внутри класса;

  • name имя метода, для которого вызывается декоратор;

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

Большинство манипуляций будет выполняться с аргументом descriptor. При использовании с методом класса объект дескриптора имеет 4 атрибута:

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

  • enumerable логическое значение, которое определяет, будет ли свойство видимым при перечислении свойств объекта;

  • value значение свойства. В нашем случае это функция;

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

Рассмотрим пример с классом Book.

//readonly decorator functionfunction readOnly(target, name, descriptor) {  descriptor.writable = false;  return descriptor;}class Book {  //Implementation here  @readOnly  getBook() {    return `[${this.name}][${this.ISBN}]`;  }}let obj = new Book("HP", "1245-533552");obj.getBook = "Hello";console.log(obj.getBook());//[HP][1245-533552]

В нем используется функция-декоратор readOnly, которая делает метод getBook в классе Bookдоступным только для чтения. С этой целью для свойства дескриптора writable устанавливается значение false. По умолчанию для него установлено значение true.

Если значение writable не изменить, свойство getBook можно будет перезаписать, например, так:

obj.getBook = "Hello";console.log(obj.getBook);//Hello

Декораторы поля класса

Декораторы могут использоваться и с полями классов. Хотя TypeScript поддерживает поля классов, предложение добавить их в JavaScript пока находится на 3-м этапе рассмотрения.

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

Если полю значение не присвоено (undefined), атрибут writable объекта дескриптора использоваться не будет.

Рассмотрим пример. Будем работать с уже знакомым нам классом Book.

function upperCase(target, name, descriptor) {  if (descriptor.initializer && descriptor.initializer()) {    let val = descriptor.initializer();    descriptor.initializer = function() {      return val.toUpperCase();    }  }}class Book {    @upperCase  id = "az092b";  getId() {    return `${this.id}`;  }  //other implementation here}let obj = new Book("HP", "1245-533552");console.log(obj.getId());//AZ092B

В этом примере значение свойства id переводится в верхний регистр. Функция-декоратор upperCase проверяет наличие функции initializer, чтобы гарантировать, что значению поля присвоено значение (тоесть значение не является undefined). Затем она проверяет, является ли присвоенное значение условно истинным (прим. пер.: англ. truthy значение, превращающееся в true при приведении к типу Boolean), и затем переводит его в верхний регистр. При вызове метода getId значение будет выведено в верхнем регистре. При использовании декораторов с полями классов можно передавать параметры точно так же, как и в других случаях.

Варианты использования

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

Декораторы в Angular

Если вы знакомы с TypeScript и Angular, вы наверняка сталкивались с использованием декораторов в классах Angular, например @Component, @NgModule, @Injectable, @Pipe и т.д. Это встроенные декораторы классов.

MobX

Декораторы в MobX широко использовались вплоть до 6-й версии. Среди них @observable, @computed и @action. Но сейчас в MobX использование декораторов не приветствуется, поскольку предложение к стандарту еще не принято. В документации говорится:

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

Библиотека Core Decorators

Это библиотека JavaScript, в которой собраны готовые к использованию декораторы. Хотя она основана на предложении о декораторах этапа 0, ее автор планирует обновление, когда предложение перейдет на 3-й этап.

В библиотеке есть такие декораторы, как @readonly, @time, @deprecate и др. С другими декораторами можно ознакомиться здесь.

Библиотека Redux для React

В библиотеке Redux для React есть метод connect, с помощью которого можно подключить компонент React к хранилищу Redux. Библиотека позволяет использовать метод connectтакже в качестве декоратора.

//Before decoratorclass MyApp extends React.Component {  // ...define your main app here}export default connect(mapStateToProps, mapDispatchToProps)(MyApp);//After decorator@connect(mapStateToProps, mapDispatchToProps)export default class MyApp extends React.Component {  // ...define your main app here}

В ответе пользователя Felix Kling на Stack Overflow можно найти некоторые пояснения.

Хотя connectподдерживает синтаксис декоратора, в настоящее время команда Redux не приветствует его использование. Связано это в основном с тем, что предложение о декораторах находится на 2-м этапе рассмотрения, а значит, в него могут быть внесены изменения.

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

Спасибо, что прочитали, и чистого вам кода!


Узнать подробнее о курсе "JavaScript Developer. Professional".

Записаться на открытый урок по теме "Делаем интерактивного telegram бота на Node.js".

Подробнее..

Декораторы Python хватит это терпеть

02.06.2021 12:04:43 | Автор: admin
Конец страданиям.Конец страданиям.

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

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

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

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

Давайте разбираться!

Как работают декораторы

def decorator_function(wrapped_func):    def wrapper():        print('Входим в функцию-обёртку')        print('Оборачиваемая функция: ', wrapped_func)        print('Выполняем обёрнутую функцию...')        wrapped_func()        print('Выходим из обёртки')    return wrapper

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

Теперь можно декорировать:

@decorator_functiondef hello_world():        print('Hello world!')hello_world()

Здесь декоратор получает функцию hello_world, и подменяет её своей вложенной функцией wrapper.

Вывод:

Входим в функцию-обёрткуОборачиваемая функция:  <function hello_world at 0x0201B2F8>Выполняем обёрнутую функцию...Hello world!Выходим из обёртки

Важно помнить!

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

Мы это увидим, если добавим две строчки в наш декоратор:

def decorator_function(wrapped_func):    print('Входим в декоратор')    def wrapper():        ...    print('Выходим из декоратора')    return wrapper
@decorator_functiondef hello_world():        print('Hello world!')
Входим в декораторВыходим из декоратора
hello_world()
Входим в функцию-обёрткуОборачиваемая функция:  <function hello_world at 0x0201B2F8>Выполняем обёрнутую функцию...Hello world!Выходим из обёртки

А вот и страдания: аргументы функции и аргументы декоратора

У функции, которую мы декорируем, могут быть аргументы. Принимает их вложенная функция wrapper:

def decorator_function(wrapped_func):    def wrapper(*args):        ...        wrapped_func(args)        ...    return wrapper  @decorator_functiondef hello_world(text):        print(text)    hello_world('Hello world!')

А ещё, аргументы могут быть переданы непосредственно в декоратор:

def fictive(decorator_text):    def decorator_function(wrapped_func):                def wrapper(*args):            print(decorator_text, end='')            wrapped_func(*args)        return wrapper        return decorator_function@fictive(decorator_text='Hello, ')def hello_world(text):        print(text)hello_world('world!')

Здесь аргумент decorator_text передаётся при декорировании в строке 11 и попадает в функцию fictive, строка 1. Таким образом, появился ещё один уровень вложенности только для того, чтобы принять аргументы декоратора.

Вывод:

Hello, world!

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

def fictive(_func=None, *, decorator_text=''):    def decorator_function(wrapped_func):        def wrapper(*args):            print(decorator_text, end='')            wrapped_func(*args)        return wrapper    if _func is None:        return decorator_function    else:        return decorator_function(_func)      @fictivedef hello_world(text):        print(text)hello_world('Hello, world!')@fictive(decorator_text='Hi, ')def say(text):        print(text)say('world!')

Вывод:

Hello, world!Hi, world!

Как Вам код? Вспомним, мантру Питонистов из начала статьи:

Декораторы - это удобный способ передать...

Ничего, на помощь придёт DecoratorHelper! Но, перед этим, ещё пара слов о декораторах.

Мифы декораторов

  1. Декораторы удобны. Думаю, с этим мы уже разобрались.

  2. В декораторы нужно передавать функции. Передавать можно не только функции, но и любые callable объекты. Это такие объекты, у которых определён дандер метод (магический метод) __call__. Этот метод отвечает за операции, которые будут произведены при вызове объекта (когда вы ставите скобочки после имени объекта: object()). Вместо функции может быть метод или класс.

  3. Декораторы - это функции. И опять: это может быть любой callable объект.

  4. Декоратор возвращает функцию. Декоратор может возвращать что угодно. Стоит лишь помнить, что если декоратор возвращает не callable объект, то вызывать его не получится.

  1. Передавать можно не только функции, но и аргументы. Вместо функции может быть метод или некоторые классы.


DecoratorHelper: решение проблем

Устанавливаем модуль:

pip install DecoratorHelper

Импортируем и используем как декоратор:

from DecoratorHelper import DecoratorHelper@DecoratorHelperdef hello_world(text):        print(text)hello_world('Hello, world!')

Что это даёт?

  1. Вы больше не думаете над тем, будут ли аргументы у декоратора. DecoratorHelper думает об этом вместо Вас.

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

В итоге Вы получаете вместо функции объект, который имеет следующие атрибуты:

  • self.function - оборачиваемая функция

  • self.decorator_args- аргументы декоратора. Кортеж позиционных аргументов, последний элемент которого - словарь с именованными аргументами.

  • self.function_args- аргументы функции. Кортеж позиционных аргументов, последний элемент которого - словарь с именованными аргументами.

  • self.pre_function - то, что будет происходить перед выполнением функции (так можно превратить функцию в коллбэк).

  • self.post_function - то, что будет происходить после выполнения функции (так можно добавить функции в коллбэк).

Как использовать?

Перепишем приведённый ранее код декоратора, который может принимать/не принимать аргументы:

from DecoratorHelper import DecoratorHelperdef fictive(object):    object.pre_function = lambda : print(*object.decorator_args[:-1], end='')    return object    @fictive@DecoratorHelper('Hello, ')def hello_world(text):        print(text)hello_world('world!')

Как мы можем видеть, тело декоратора сократилось в 8 раз. Profit!

Ограничение! Первым аргументом нельзя передавать callable объекты, иначе всё сломается :) Думаю, для большинства задач, это не смертельно...

Что дальше?

В следующих версиях планируется:

  • Улучшенная обработка аргументов.

  • Встроенный счётчик вызовов.

  • Возможность превратить объект в синглтон.

  • Возможность превратить объект в буилдер.

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

    И всё это в максимально удобном формате: singleton = True.

P. S. Если в комментариях будет интерес к теме, напишу вторую статью о том, как DecoratorHelper устроен. Но сразу скажу, что это уровень Junior+.

Подробнее..

HTTP-клиент на стероидах

14.09.2020 14:13:59 | Автор: admin

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

Смена версии протокола

К нам пришёл HTTP/2, но не каждый сервер, как и не каждый клиент его поддерживает. Если попробовать отправить запрос, принудительно указав версию протокола 2, можно получить от сервера ошибку 505 HTTP Version Not Supported. Пакет webclient/ext-protocol-version решает эту, возможно надуманную, проблему. При получении ответа 505 клиент повторит запрос, но уже с версией протокола, указанной в ответе сервера.

<?phpuse Webclient\Extension\ProtocolVersion\Client;use Psr\Http\Client\ClientInterface;use Psr\Http\Message\RequestInterface;/**  * @var ClientInterface $client Ваш PSR-18 совместимый HTTP Client. */$http = new Client($client);/** @var RequestInterface $request */$response = $http->sendRequest($request);

Редиректы

Очень редко, но встречается, когда клиент не умеет следовать редиректам при ответе с кодом 3xx. В этом случае поможет пакет webclient/ext-redirect. Тут всё элементарно, Передаём в конструктор наш клиент и максимальное количество допустимых редиректов на один запрос.

<?phpuse Webclient\Extension\Redirect\Client;use Psr\Http\Client\ClientInterface;use Psr\Http\Message\RequestInterface;/**  * @var ClientInterface $client Ваш PSR-18 совместимый HTTP Client. * @var int $maxRedirects Максимальное количество допустимых редиректов. */$http = new Client($client, $maxRedirects);/** @var RequestInterface $request */$response = $http->sendRequest($request);

Логирование

Иногда нам необходимо логировать запросы и ответы. Пакет webclient/ext-log позволяет настроить логирование так, как этого требует проект. Помимо клиента, вам понадобится PSR-3 совместимый логгер. Для формирования строки лога используется интерфейс Webclient\Extension\Log\Formatter\Formatter, а для формирования ID запроса (для поиска в логах пары запрос-ответ) - Webclient\Extension\Log\IdGenerator\IdGenerator. По одной реализации этих интерфейсов поставляется из коробки:

  • Webclient\Extension\Log\IdGenerator\UniqueIdGenerator - элементарный генератор на основе uniqid()

  • Webclient\Extension\Log\Formatter\RawHttpFormatter - логирование запросов и ответов в виде RAW-текста.

<?phpuse Psr\Http\Client\ClientInterface;use Psr\Http\Message\RequestInterface;use Psr\Log\LoggerInterface;use Psr\Log\LogLevel;use Webclient\Extension\Log\Client;use Webclient\Extension\Log\Formatter\Formatter;use Webclient\Extension\Log\IdGenerator\IdGenerator;/**  * @var ClientInterface $client Ваш PSR-18 совместимый HTTP Client. * @var LoggerInterface $logger Ваш PSR-3 совместимый логгер. * @var IdGenerator|null $idGenerator Ваш ID-генератор. *      При передаче null будет использоваться  *      Webclient\Extension\Log\IdGenerator\UniqueIdGenerator. * @var Formatter|null $formatter Ваш форматировщик лога.  *      При передаче null будет использоваться  *      Webclient\Extension\Log\Formatter\RawHttpFormatter. */$http = new Client(    $client,    $logger,    $idGenerator,    $formatter,    LogLevel::INFO, // Уровень логирования запросов    LogLevel::INFO, // Уровень логирования информационных ответов (Коды 1xx)    LogLevel::INFO, // Уровень логирования успешных ответов (Коды 2xx)    LogLevel::INFO, // Уровень лоигрования ответов с редиректом (Коды 3xx)    LogLevel::ERROR, // Уровень логирования ответов об ошибках клиента (Коды 4xx)    LogLevel::ERROR, // Уровень логирования ответов об ошибках сервера (Коды 5xx)    LogLevel::WARNING // Уровень логирования исключений HTTP клиента);/** @var RequestInterface $request */$response = $http->sendRequest($request);

Куки

Бывают проекты, в которых не достаточно просто отправить запрос и получить ответ. Иногда нужно поддерживать сессию (ну или ещё что-то). Чтобы добавить вашему клиенту поддержку печенек, нужно отнаследоваться от абстрактного класса Webclient\Extension\Cookie\Cookie\Storage из пакета webclient/ext-cookie, либо воспользоваться одной из поставляемых с пакетом реализацией:

  • Webclient\Extension\Cookie\Cookie\ArrayStorage - держит куки в памяти. Слетает после завершения скрипта;

  • Webclient\Extension\Cookie\Cookie\NetscapeCookieFile - хранит куки в файле в соответствии с форматом Netscape.

<?phpuse Psr\Http\Client\ClientInterface;use Psr\Http\Message\RequestInterface;use Webclient\Extension\Cookie\Client;use Webclient\Extension\Cookie\Cookie\Storage;/**  * @var ClientInterface $client Ваш PSR-18 совместимый HTTP Client. * @var Storage $storage Хранилище куков. *      Вы можете отнаследоваться от этого класса для реализации своего хранилища. */$http = new Client($client, $storage);/** @var RequestInterface $request */$response = $http->sendRequest($request);

Кэширование

О кэшировании, его плюсах и минусах сказано уже много. Если вам хочется разгрузить какой-то из своих редко обновляемых микросервисов (и нагрузить кэш), поможет пакет webclient/ext-cache. Чтобы завернуть в него свой клиент, вам понадобится реализация Psr\SimpleCache\CacheInterface из PSR-6, Psr\Http\Message\ResponseFactoryInterface и Psr\Http\Message\StreamFactoryInterface из PSR-18.

Кэширование происходит на основе соответствующих заголовков HTTP-запросов и ответов.

<?phpuse Psr\Http\Client\ClientInterface;use Psr\Http\Message\RequestInterface;use Psr\Http\Message\ResponseFactoryInterface;use Psr\Http\Message\StreamFactoryInterface;use Psr\SimpleCache\CacheInterface;use Webclient\Extension\Cache\Client;/**  * @var ClientInterface $client Ваш PSR-18 совместимый HTTP Client. * @var CacheInterface $cache Ваш PSR-6 совместимый кэш. * @var ResponseFactoryInterface $responseFactory  *      Ваша PSR-17 совместимая фабрика ответов. * @var StreamFactoryInterface $streamFactory *      Ваша PSR-17 совместимая фабрика потоков. * @var string Строка, уникальная для приватного кэша (например, ID сессии). */$http = new Client(  $client,  $cache,  $responseFactory,  $streamFactory,  $privateKey);/** @var RequestInterface $request */$response = $http->sendRequest($request);

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

Создание запросов с файлами

Как известно, в PSR-7 есть два вида запросов - обычный запрос и серверный запрос. Они оба реализуют интерфейс Psr\Http\Message\RequestInterface (Psr\Http\Message\ServerRequestInterface его расширяет). При отправке файлов на сервер мы не можем просто взять серверный запрос, выполнить его метод withUploadedFiles($files) и передать полученный объект в HTTP-клиент. Для того, чтобы клиент корректно отправил запрос с файлами, эти файлы должны быть записаны в поток тела запроса. Библиотека webclient/helper-form предназначена для упрощения создания таких запросов. Для работы вам понадобятся реализации интерфейсов Psr\Http\Message\ResponseFactoryInterface и Psr\Http\Message\StreamFactoryInterface.

фейковый клиент

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

Для такого тестирования можно воспользоваться webclient/fake-http-client, который является реализацией Psr\Http\Client\ClientInterface, но под капотом вместо запроса к серверу вызывает Psr\Http\Server\RequestHandlerInterface из PSR-15 (преобразовав при необходимости Psr\Http\Message\RequestInterface в Psr\Http\Message\ServerRequestInterface). Реализация Psr\Http\Server\RequestHandlerInterface остаётся за вами - эмулируйте поведение, как вам нужно для тестирования.

<?phpuse Webclient\Fake\Client;use Psr\Http\Message\RequestInterface;use Psr\Http\Server\RequestHandlerInterface;/**  * @var RequestHandlerInterface $handler Ваш обработчик запроса.  * @var array $serverParams Параметры сервера, которые будут добавлены  *      при преобразовании из Psr\Http\Message\RequestInterface в *      Psr\Http\Message\ServerRequestInterface. */$client = new Client($handler, $serverParams);/** * @var RequestInterface $request Ваш HTTP-запрос */$response = $client->sendRequest($request);

Если вы передаете объект Psr\Http\Message\ServerRequestInterface клиенту и хотите, чтобы обработчик получил его как есть, добавьте атрибут Webclient\Fake\Client::NOREPLACEATTRIBUTE (иначе будет создан новый объект запроса).

<?phpuse Webclient\Fake\Client;use Psr\Http\Server\RequestHandlerInterface;/**  * @var Client $client.  * @var ServerRequestInterface $request. */$request = $request->withAttribute(Client::NOREPLACEATTRIBUTE, true);$response = $client->sendRequest($request);

Чтобы хоть чуть-чуть упростить вам написание обработчика, в пакете поставляется класс Webclient\Fake\Handler\SimpleRoutingHandler - обработчик с примитивным роутингом.

<?phpuse Webclient\Fake\Client;use Webclient\Fake\Handler\SimpleRoutingHandler;use Psr\Http\Message\RequestInterface;use Psr\Http\Server\RequestHandlerInterface;/**  * @var RequestHandlerInterface $notFoundHandler Обработчик запросов,  *      для которых не нашлось роута. * @var RequestHandlerInterface $entityCreatedHandler Обработчик запросов *      для эмуляции созания сущности (POST /entities). * @var RequestHandlerInterface $entityHandler Обработчик запросов *      для получения сущности (GET /entities/1). * @var RequestHandlerInterface $entityDeletedHandler обработчик запросов *      для удаления сущности (DELETE /entities/2). * @var RequestInterface $errorRequest Запрос несуществующего URI (GET /users). * @var RequestInterface $entityCreatingRequest Запрос создания  *      сущности (POST /entities). * @var RequestInterface $entityRequest Запрос получения  *      сущности (GET /entities/1). * @var RequestInterface $entityDeletingRequest Запрос удаления  *      сущности (DELETE /entities/2). */$handler = new SimpleRoutingHandler($notFoundHandler);$handler    ->route(['GET', 'HEAD'], '/entities/1', $entityHandler)    ->route(['POST'], '/entities', $entityCreatedHandler)    ->route(['DELETE'], '/entities/2', $entityDeletedHandler);$client = new Client($handler);$resp1 = $client->sendRequest($errorRequest); // Вернёт ошибку 404$resp2 = $client->sendRequest($entityCreatingRequest); // Вернёт ответс кодом 201$resp3 = $client->sendRequest($entityRequest); // Вернёт ответ с кодом 200$resp4 = $client->sendRequest($entityDeletingRequest); // Вернёт ответ с кодом 204

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

Подробнее..

Категории

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

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru