Skip to content

Instantly share code, notes, and snippets.

@GubaEvgeniy
Last active August 30, 2019 12:31
Show Gist options
  • Select an option

  • Save GubaEvgeniy/021c68d554c5b9e11af1566ef6c63f0d to your computer and use it in GitHub Desktop.

Select an option

Save GubaEvgeniy/021c68d554c5b9e11af1566ef6c63f0d to your computer and use it in GitHub Desktop.

Наверх

Null Object Pattern

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

<!-- где-то в воображаемом шаблоне articles/index.phtml -->
<!-- $this - предоставляется фреймворком, это объект View внутри которого лежат данные и полезные методы -->

<? if ($this->userSignedIn() && $this->currentUser->hasArticles()): ?>
    <? foreach ($this->currentUser->getArticles() as $article): ?>
        <!-- тут выводим статьи -->
    <? endforeach ?>
<? endif ?>

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

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

<?php

class Guest
{
    public function hasArticles()
    {
        return false;
    }

    public function getArticles()
    {
        return [];
    }
}

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

<!-- может быть гостем, а может быть реальным пользователем -->
<? if ($this->currentUser->hasArticles()): ?>
    <? foreach ($this->currentUser->getArticles() as $article): ?>
        <!-- тут выводим статьи -->
    <? endforeach ?>
<? endif ?>

Условные конструкции уйдут по всем шаблонам, но остаётся вопрос. А где и как происходит сам процесс создания нашего пользователя? И вот здесь останется тот единственный if, благодаря которому произойдёт создание правильного объекта. Это происходит на этапе обработки входящего запроса, и конкретное место зависит от используемого фреймворка. Код в этом месте выглядит примерно так:

<?php

function fetchCurrentUser()
{
    $userId = $_SESSION['user_id'] ?? null;
    // Если id есть в сессии, то выбираем пользователя из базы, иначе возвращаем гостя
    return $userId ? User::find($userId) : new Guest();
}

У такого способа использования полиморфизма есть особое название: шаблон проектирования null object. Он часто используется внутри фреймворков и иногда встречается в прикладном коде.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment