编辑:我很抱歉,我发现了错误,这是非常愚蠢的。(见回答)
我似乎有一个很奇怪的问题。我有一个网站,使用第三方授权登录。我的用户使用两个社交网络:Facebook和Vkontakte(俄罗斯版)。登录时,我用传递的社交id在数据库中搜索用户(根据所选择的社交网络,用户在FB或VK中的id,我有两个不同的列)并获取它。
如果用户在FB和VK都有账户,并通过这两个账户登录,他或她现在在我的网站上有两个独立的账户。然而,他可以通过一个社交网络(这将是他的主账户)登录,并使用另一个社交网络的"user_bind"功能,将它们加入到一个社交网络中。
此函数查找用户的另一个帐户,并将数据库中的所有数据链接到主帐户。然后删除另一个帐户,并将其社交id添加到主帐户,这样用户就可以通过两个社交网络登录了。Social id列自然有一个惟一索引。
然而,当脚本执行时,它似乎执行了UPDATE,它在 DELETE语句之前添加了社会id ,删除了旧用户。这会产生一个错误,因为它试图添加一个现有的社会id(因为旧用户仍然在那里)。
当我在脚本执行后检查数据库时,旧用户已经消失,所以我猜这意味着DELETE语句确实被执行了,但是有一个延迟,在这个延迟中执行了其他语句。MySQL工作台的日志证实了这一点,尽管我不确定它是否可靠。
我的问题是我如何确保在执行脚本的其余部分之前实际执行了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);
}
}
问题是:private_properties(包括社交id)或对象'user'存储在单独的表('users_private')中,该表通过外键链接到主表('users')。
我将'users_private'表包含在需要重链接的表数组中:
$tblsRelink=array("comments", "posts", "users_private", "vote_posts", "vote_comments");
这导致'users_private'中旧用户的记录被重新链接到新用户(现在有2条记录-我不使这个字段唯一是多么鲁莽)。因此,当旧用户被删除时,其关联的'users_private'记录不存在,因为它现在链接到新用户。当然,尝试添加社交id会产生错误,因为该id已经存在,从旧用户重新链接。
这可以通过
- 思考更多关于我在做什么(为什么我认为'users_private'表有资格重新链接?)
- 更仔细的数据库结构(如果一个字段应该是唯一的-创建一个唯一的键!)