XF 2.1 Есть ли возможность откатить регистрацию пользователя, проведенную через REST API?

Версия XenForo
2.1.2

Fertnam

Участники
Сообщения
9
Реакции
0
Баллы
298
Добрый день, есть сайт и форум со связанными аккаунтами. При регистрации пользователя на сайте, по задумке, должен создаваться профиль и на форуме с идентичными данными.

Ниже прикреплен класс, отвечающий за регистрацию пользователя на различных ресурсах (в данном случае на сайте и форуме). В нём содержится метод registerComplex(), который создаёт аккаунты пользователя сразу на обеих площадках. Так вот, сперва приходится выполнять форумную регистрацию, так как для создания аккаунта на сайте необходимо уже иметь id форумного профиля (внешний ключ, если короче), что приводит к следующему неприятному инциденту: если регистрация на сайте пройдёт неудачно, то форумный аккаунт никуда не исчезает. Была мысль посылать запрос на удаление (delete: user/{id}/) в случае данной ошибки, но опять-таки проблема может возникнуть и при выполнении данного запроса, а что делать дальше уже непонятно...

Как быть в данной ситуации? Есть ли возможность откатить регистрацию или нужно искать иной вариант решения? Спасибо!

PHP:
<?php
    namespace models\user;

    use components\exceptions\failed\registration\ForumRegistrationFailedException as ForumRegistrationFailedException;
    use components\exceptions\failed\registration\SiteRegistrationFailedException as SiteRegistrationFailedException;
    use GuzzleHttp\Client as XFClient;
    use GuzzleHttp\Exception\ClientException as XFClientException;

    class Registration {
        private $_username,
                $_email,
                $_password;

        public function __construct(string $username, string $email, string $password) {
            $this->_username = $username;
            $this->_email = $email;
            $this->_password = $password;
        }

        //Регистрация аккаунта на сайте
        public function registerOnSite(\PDO $DbConnect, $xfUserId = null) : string {
            try {
                $passwordHash = password_hash($this->_password, PASSWORD_BCRYPT);
                $activationCode = password_hash($this->_username, PASSWORD_BCRYPT);

                $WritingQuery = $DbConnect->prepare('INSERT INTO site_user(username, email, password, activation_code, xf_user_id) VALUES (:username, :email, :password, :activation_code, :xf_user_id)');

                $WritingQuery->bindParam(':username', $this->_username);
                $WritingQuery->bindParam(':email', $this->_email);
                $WritingQuery->bindParam(':password', $passwordHash);
                $WritingQuery->bindParam(':activation_code', $activationCode);
                $WritingQuery->bindParam(':xf_user_id', $xfUserId);

                $WritingQuery->execute();
            } catch (\PDOException $Exception) {
                $message = "Возникла ошибка при регистрации аккаунта $this->_username с адресом $this->_email на сайте. Пожалуйста, обратитесь к администрации проекта.";

                throw new SiteRegistrationFailedException($Exception, $message);
            }

            return $activationCode;
        }

        //Регистрация аккаунта на форуме
        public function registerOnForum() : int {
            try {
                require_once ROOT_PATH . '/forum/src/vendor/autoload.php';

                $Client = new XFClient();

                $params = [
                    'headers' => [
                        'XF-Api-Key' => 'ключ',
                        'XF-Api-User' => '1'
                    ],
                    'form_params' => [
                        'username' => $this->_username,
                        'password' => $this->_password,
                        'email' => $this->_email,
                        'user_state' => 'moderated'
                    ]
                ];

                $ClientAnswer = $Client->post('mvc.caelestis/forum/api/users', $params);
                $answerBody = json_decode($ClientAnswer->getBody(), true);
            } catch (XFClientException $Exception) {
                $message = "Возникла ошибка при регистрации аккаунта $this->_username с адресом $this->_email на форуме. Пожалуйста, обратитесь к администратрации проекта.";

                throw new ForumRegistrationFailedException($Exception, $message);
            }

            return $answerBody['user']['user_id'];
        }

        //Комплексная регистрация (на сайте и на форуме)
        public function registerComplex(\PDO $DbConnect) : string {
            $activationCode;

            try {
                $xfUserId = $this->registerOnForum();
                $activationCode = $this->registerOnSite($DbConnect, $xfUserId);
            } catch (ForumRegistrationFailedException | SiteRegistrationFailedException $Exception) {
                throw $Exception;
            }

            return $activationCode;
        }
    }
?>
 
Побольше бы информации. Что за сайт, например? Если WP, то проще мост поставить, чем изобретать велосипед.
 
Почему бы после успешной регистрации на сайте не делать регистрацию на форуме, а потом после ответа форума присваивать полученный айдишник уже после регистрации?
Или сделать на форуме кнопку "Вход через сайт" и сделать все через OAuth
 
  • Мне нравится
Реакции: Hope
Fertnam, Ну так ты посылай после регистрации на сайте запрос на форум. Зачем сначала регистрировать на форуме, а потом на сайте? Ведь регистрация и так проходит на сайте. Если неизвестно как получить id пользователя форума, то среда же уже загружена и классы их так же. Соответственно можно дёрнуть энтити форума метод
getDeferredValue и он уже вытянет из пресейва будущий id. К тому же в коде уже видна загрузка среды XF и заиницилизировав главную аппу можно слать сразу запрос на ентитю юзверя
 
Fertnam, Ну так ты посылай после регистрации на сайте запрос на форум. Зачем сначала регистрировать на форуме, а потом на сайте? Ведь регистрация и так проходит на сайте. Если неизвестно как получить id пользователя форума, то среда же уже загружена и классы их так же. Соответственно можно дёрнуть энтити форума метод
getDeferredValue и он уже вытянет из пресейва будущий id. К тому же в коде уже видна загрузка среды XF и заиницилизировав главную аппу можно слать сразу запрос на ентитю юзверя
Добрый день. Прочёл документацию, поизучал классы; также попытался методом тыка найти желаемое решение, но, увы, результата нет.
PHP:
\XF::start(ROOT_PATH . '/forum/');

$app = \XF::app();
//print_r($app); Много разных полей

//$userEntity = \XF::em()->create('XF:User'); //user_id = null
$userEntity = \XF::visitor();

echo $userEntity->get('user_id'); //0
echo $userEntity->getExistingValue('user_id'); //0
echo $userEntity->user_id; //0

В коде выше был дан старт XF, инициализирован app (в самом классе у меня не получилось найти ничего связанного с будущим user_id).

Далее я получил User Entity, но вытянуть user_id также не смог (либо null, либо 0). Мной была предпринята попытка вытащить Forum Entity, но и там тоже ничего для решения задачи не было найдено.

Ещё вы упоминали про метод getDeferredValue(), но найти его получилось только в классе Manager (не знаю, наследует ли его Entity, но вызвать этот метод я не смог). Дополнительно с этим данный метод требует в качестве параметра экземпляр Closure. Как из всего этого получить id будущего ещё несуществующего пользователя я не могу понять.
 
Последнее редактирование:
Мне кажется, самым лучшим решением будет немного пересмотреть логику вашей системы регистрации
А Кэп, если я правильно его понял, предлагал вам использовать _preSave, тем самым получить айдишник пользователя форума, а сохранять(save) его только при успешной регистрации на сайте
 
Мне кажется, самым лучшим решением будет немного пересмотреть логику вашей системы регистрации
А Кэп, если я правильно его понял, предлагал вам использовать _preSave, тем самым получить айдишник пользователя форума, а сохранять(save) его только при успешной регистрации на сайте

Это _preSave() в User Entity. Если я правильно понял, то он выполняется перед сохранением данных в БД. Но как его использовать? Мне нужно написать отдельный аддон, в котором следует расширить данную сущность, после чего переопределить сий метод? Или всё-таки тут другая логика.
PHP:
protected function _preSave()
    {
        if ($this->isChanged('user_group_id') || $this->isChanged('secondary_group_ids'))
        {
            $groupRepo = $this->getUserGroupRepo();
            $this->display_style_group_id = $groupRepo->getDisplayGroupIdForUser($this);
        }

        if ($this->isChanged(['user_group_id', 'secondary_group_ids']))
        {
            // Do not allow a primary user group also be a secondary user group.
            $this->secondary_group_ids = array_diff(
                $this->secondary_group_ids,
                [$this->user_group_id]
            );
        }

        if (!$this->secret_key)
        {
            $this->secret_key = \XF::generateRandomString(32);
        }

        if ($this->isInsert() && !$this->isChanged('email') && empty($this->_errors['email']))
        {
            $this->email = '';
        }

        if ($this->isChanged('email') && $this->email && empty($this->_errors['email']))
        {
            // Redo the duplicate email check. This tries to reduce a race condition that can be extended
            // due to third-party spam checks.
            $matchUserId = $this->db()->fetchOne("
                SELECT user_id
                FROM xf_user
                WHERE email = ?
            ", $this->email);
            if ($matchUserId && (!$this->user_id || $matchUserId != $this->user_id))
            {
                $this->error(\XF::phrase('email_addresses_must_be_unique'), 'username');
            }
        }

        if ($this->isChanged('username') && empty($this->_errors['username']))
        {
            // Redo the duplicate name check. This tries to reduce a race condition that can be extended
            // due to third-party spam checks.
            $matchUserId = $this->db()->fetchOne("
                SELECT user_id
                FROM xf_user
                WHERE username = ?
            ", $this->username);
            if ($matchUserId && (!$this->user_id || $matchUserId != $this->user_id))
            {
                $this->error(\XF::phrase('usernames_must_be_unique'), 'username');
            }
        }
    }

По поводу логики, тут я вижу следующие варианты: отказаться от внешнего ключа в таблице пользователей сайта; выполнить SELECT max(user_id) + 1 FROM xf_user с блокировкой таблиц от сторонней записи (но, как мне кажется, это того не стоит); либо просто оставить всё как есть и логировать такие ошибки, ибо вероятность их возникновения крайне мала.
 
Последнее редактирование:
В коде выше был дан старт XF, инициализирован app (в самом классе у меня не получилось найти ничего связанного с будущим user_id).
Приложение не запустилось. Накидал простой пример,
PHP:
<?php

$dir = __DIR__;

require ($dir . '/src/XF.php');

XF::start($dir);
$app = XF::setupApp('XF\App');
$session = $app['session.public'];

\XF::dump($session->userId);
Если нужно получать визитера, то нужно запустить публичную, не из главной аппы
PHP:
<?php

$dir = __DIR__;

require ($dir . '/src/XF.php');

XF::start($dir);
$app = XF::setupApp('XF\Pub\App');
$app->start();

\XF::dump(\XF::visitor()->user_id);
Как пример
Код:
<?php

$dir = __DIR__;

require ($dir . '/src/XF.php');

XF::start($dir);
$app = XF::setupApp('XF\App');
...
$content = \XF::visitor();
$siteUser->user_id = $content->em()->getDeferredValue(function() use ($content)
    {
        return $content->getEntityId();
    }, 'save');
После создания пользователя на сайте и создание на форуме, на сайте должен подцепится id пользователя.
 
  • Мне нравится
Реакции: Hope
Приложение не запустилось. Накидал простой пример,
PHP:
<?php

$dir = __DIR__;

require ($dir . '/src/XF.php');

XF::start($dir);
$app = XF::setupApp('XF\App');
$session = $app['session.public'];

\XF::dump($session->userId);
Если нужно получать визитера, то нужно запустить публичную, не из главной аппы
PHP:
<?php

$dir = __DIR__;

require ($dir . '/src/XF.php');

XF::start($dir);
$app = XF::setupApp('XF\Pub\App');
$app->start();

\XF::dump(\XF::visitor()->user_id);
Как пример
Код:
<?php

$dir = __DIR__;

require ($dir . '/src/XF.php');

XF::start($dir);
$app = XF::setupApp('XF\App');
...
$content = \XF::visitor();
$siteUser->user_id = $content->em()->getDeferredValue(function() use ($content)
    {
        return $content->getEntityId();
    }, 'save');
После создания пользователя на сайте и создание на форуме, на сайте должен подцепится id пользователя.
Я просчитался: у меня таблица InnoDB, следовательно, чтобы внести внешний ключ необходима уже существующая запись в таблице xf_user. То есть мне априори придётся сперва регистрировать пользователя на форуме.

Можно, конечно, по дефолту вставлять временно NULL, а после регистрации делать UPDATE внешнего ключа. Но это весьма затратно (3 запроса вместо условных 2-х).

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

Видимо, мне нужно просто убрать этот внешний ключ и ссылаться во всех случаях по username, поскольку они уникальные.
 
Последнее редактирование:
Опять таки я дал вариант, который работает только при создание пользователя, если не удастся, то оно и не должно. Просто работает запись в 2 таблицы.
 
  • Мне нравится
Реакции: Hope
Современный облачный хостинг провайдер | Aéza
Назад
Сверху Снизу