Mysqli Php выполняет операторы в случайном порядке (очень странно) - PullRequest
0 голосов
/ 12 ноября 2011

РЕДАКТИРОВАТЬ: Мне очень жаль, я нашел ошибку, и это очень глупо.(См. Ответ)

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

При входе в систему я ищу в базе данных пользователя с переданным социальным идентификатором (который, в зависимости от выбранной социальной сети, идентификатором пользователя в FB или VK, для которого у меня есть два разных столбца), и извлекаюЭто.

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

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

Однако, когда скрипт выполняется, он выполняет UPDATE, который добавляет идентификатор соцсети, перед оператором DELETE, который удаляетстарый пользовательЭто приводит к ошибке, потому что он пытается добавить существующий социальный идентификатор (потому что старый пользователь все еще там).

Когда я проверяю базу данных после выполнения скрипта, старый пользователь исчезает, поэтому я предполагаю, что это означает, что оператор DELETE действительно выполняется, но с задержкой, в которой выполняются другие операторы.Журнал MySQL Workbench подтверждает это, хотя я не уверен, что он надежный.

У меня вопрос , как мне убедиться, что DELETE (или любой другой оператор MySQL в этом отношении) имеетна самом деле был выполнен до выполнения остальной части сценария?И почему это так или иначе происходит?

Вот адекватно прокомментированный код (хотя я с удовольствием приму ответ, в котором нет кода и который просто объясняет принцип).

Функция user_bind:

function user_bind($eSourceType)
    {
    //$eSourceType can be 'fb' or 'vk', depending on the social network of the secondary account

    $usrMe=get_gl_me(); //gets the user's account, through which he is logged in - the master account
    if ($eSourceType=='fb') //if the social network that we are binding this account to is Facebook
        {
        $vSidName='facebook_id'; //name of the column which contains the social id
        if (!$usrMe->get_private_property("facebook_id") & $usrMe->get_private_property("vkontakte_id") ) //check if the master account really doesn't have facebook_id set
            {
            $fb=get_facebook();//gets facebook object (from FB PHP SDK)
            $sid=$fb->getUser();//gets user's id in facebook (social id)
            }
        else
            {
            error("The account has facebook_id set");
            }
        }
    elseif($eSourceType=='vk')//same as before, but the id is fetched through $_GET, not object
        {
        $vSidName='vkontakte_id';
        if ($usrMe->get_private_property("facebook_id") & !$usrMe->get_private_property("vkontakte_id") ) //check if it's the right account
            {
            $sid=$_GET['uid'];
            }
        else
            {
            error("The account has vkontakte_id set");
            }
        }

    if(!$sid) //if we couldn't retrieve the social id
        {
        error("Can't bind: \$sid not set.");
        }

    $idNew=$usrMe->get_id();//get id (database id) of the master account

    $usrOld=fetch_user_by_sid($sid, $eSourceType, true); //fetches the 'user' object by the social id we retrieved before
    if ($usrOld)//if there is a user with this social id (if there is a secondary account)
        {
        $idOld=$usrOld->get_id();//get id of the secondary account
        $tblsRelink=array("comments", "posts", "users_private", "vote_posts", "vote_comments"); //get tables in which we have to relink users
        foreach($tblsRelink as $tbl)
            {
            //update set users_idusers to userid
            $sp=new Statement_Parameter; //this is a class from PHP.com: http://php.net/manual/en/mysqli-stmt.bind-param.php. It allows to bind variables to the prepared statement in MySQLi without much pain
            $query="UPDATE $tbl SET users_idusers=" . db_stmt_operands($idNew, $sp, 'idNew') . " WHERE users_idusers=". db_stmt_operands($idOld, $sp, 'idOld'); //db_stmt_operands inserts question marks in the query, while binding the variables through Statement_Parameter
            $affected_rows=db_submit($query, $sp);//see below for the db_submit() function explanation
            }

        //delete old user
        $sp=new Statement_Parameter; //clear Statement_Parameter

        $query="DELETE FROM users WHERE idusers=" . db_stmt_operands($idOld, $sp, 'idOld');
        $affected_rows=db_submit($query, $sp);
        echo "<br>affected: $affected_rows<BR>"; //this actually returns 1

        //lets see if the user was actually deleted
        $usrTest=fetch_user_by_sid($sid, $eSourceType, true); //fetch the user by the social id
        if($usrTest) //if a user is fetched
            {
            debug_array($usrTest); //custom implementation of print_r
            error("User still exsists. Oh no.");//it always does
            }
        }


    $usrMe->set_private_property($vSidName, $sid);//sets the property 'facebook_id' or 'vkontakte_id' to the social id that we got in the beginning
    $usrMe->update();//UPDATE statement, which brings the object's properties in the database up to date (in our case: adds the social id)
    //the UPDATE statement doesn't execute because the old user is still there
    }

Функция db_submit:

function db_submit($query, $sp=NULL)
    {
    $mysqli = db_connect(); //fetches PHP MySQLi object
    if ($stmt = $mysqli->prepare($query)) //if the statement is successfully prepared
        {
        if($sp)//if there is a Statement_Parameter passed
            {
            $sp->Bind_Params($stmt); //bind parameters from SP
            }

        if($stmt->execute())//try to execute the statement
            {
            //on success
            if ($mysqli->insert_id) //if this was an INSERT
                {
                return $mysqli->insert_id;
                }
            else //if this was DELETE or UPDATE
                {
                return $mysqli->affected_rows;
                }
            }
        else
            {
            //on failure
            error("Could not submit: could not execute statement. Query: $query." . $stmt->error); //this kills the script
            }
        }
    else
        {
        error("Could not submit. Query: $query." . $mysqli->error); 
        }
    }

1 Ответ

1 голос
/ 13 ноября 2011

Дело в том, что private_properties (включая идентификаторы социальных сетей) или объект 'user' хранятся в отдельной таблице ('users_private'), которая была связана с основной таблицей ('users') через внешний ключ.

Я включил таблицу 'users_private' в массив таблиц, для которых требуется повторное связывание:

        $tblsRelink=array("comments", "posts", "users_private", "vote_posts", "vote_comments"); 

Это привело к тому, что запись в users_private для старого пользователя была связана с новым пользователем (у которого сейчас было 2 записи - как безрассудно с моей стороны сделать это поле УНИКАЛЬНЫМ).Поэтому, когда старый пользователь был удален, его связанной записи «users_private» не было, потому что он теперь был связан с новым пользователем.Естественно, попытка добавить социальный идентификатор приводила к ошибке, потому что этот идентификатор уже был там, отсоединен от старого пользователя.

Это может быть предотвращено либо

  1. Еще немного подумав о том, что я делаю (почему я считаю 'users_private' таблицей, подходящей для повторного связывания?)
  2. Более тщательное структурирование базы данных (если поле должно быть уникальным - создайте УНИКАЛЬНЫЙ ключ!)

или, что еще лучше, оба.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...