>
Создание lazy-load функции на php

Создание lazy-load функции на php

Волков Михаил Php 31 августа 2019
0 6874
Сложность: Начинающий

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

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

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

Реальный пример 

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

Разбираемся

Пусть у нас есть функция, возвращающая все роли текущего пользователя.

function roles()
{
/** здесь как-то загружаются $roles из БД ... */
return $roles;
}

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

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

$roles = roles();

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

$roles = $roles ?? roles();

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

Что-то не то

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

function isAdmin()
{
global $roles;
return in_array('admin', $roles = $roles ?? roles());
}

Решение

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

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

class Roles
{
static $roles;

public static function roles()
{
/** здесь как-то загружаются $roles из БД ... */
return $roles;
}

public static function isAdmin()
{
return in_array('admin', static::$roles = static::$roles ?? static::roles());
}

}

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

Lazy-load в ООП

class Roles
{
static $roles;

public static function roles()
{
if (null !== static::$roles) {
/** здесь как-то загружаются $roles из БД ... */
static::$roles = $roles;
}
return static::$roles;
}

public static function isAdmin()
{
return in_array('admin', static::roles());
}

}

Теперь каждый раз вызывая Roles::roles() вы будете получать либо данные из БД, если вызов происходит первый раз, либо закешированный вариант из статичной переменной. Супер, это именно то, что нам и нужно было. И обратите внимание как просто и естественно выглядит этот код.

Lazy-load без ООП

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

function roles()
{
static $roles;
if (null !== $roles) {
/** здесь как-то загружаются $roles из БД ... */
}
return $roles;
}

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

Заключение

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

  • создаем статичную переменную в теле функции;
  • проверяем ее на пустоту (сравнивая с null или проверяем функцией is_null());
  • если она пустая - то загружаем в нее данные из БД;
  • если она не пустая, то возвращаем ее значение.

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


Комментарии

Никто не оставлял свои комментарии