Я решил это, выполнив следующее в кодовой базе песочницы:
- Реализовал альтернативный валидатор для sfGuardValidatorUser и указал app.yml на этот класс с ключом auth_user_validator.
- Реализованзакрытый метод проверки _checkMainApp, который вызывается с предоставленными пользователем учетными данными, если пользователь не найден в пользовательской таблице песочницы.Он выполняет следующие действия:
- устанавливает специальное соединение с основной базой данных приложения (работает с некоторыми ошибками symfony , doctrine )
- запросить у пользователя основную базу данных (а затем восстановить исходное соединение с базой данных)
- если пользователь найден и пароль правильный, выполнить глубокое копирование объекта
- , наконец, толькоПользователь и его профиль должны быть скопированы, и любой другой связанный объект (доступный через пользователя с глубоким копированием) должен быть расположен в базе данных «песочница» и должен заменить существующие отношения - это то, что делает _fixCopiedRelations (довольно громоздким образом).
# lib/validator/yiValidatorUserSandbox.class.php
protected function doClean($values)
{
// snip
// don't allow to sign in with an empty username
if ($username)
{
// snip
// user exists?
if ($user) {
// password is ok?
// snip
} else if ($user = $this->_checkMainApp($username, $password)) {
return array_merge($values, array('user' => $user));
}
}
// snip
}
private function _checkMainApp($username, $password)
{
$sandConn = Doctrine_Core::getTable('sfGuardUser')->getConnection();
$readOnlyConn = Doctrine_Manager::connection(
'mysql://root@localhost/maindb', 'readonly' # readonly is only the conn name, not its state
);
$user = Doctrine_Core::getTable('sfGuardUser')
->getAllUserDetailsUsingConnection($username, $readOnlyConn);
Doctrine_Manager::getInstance()->closeConnection($readOnlyConn);
Doctrine_Manager::getInstance()->setCurrentConnection($sandConn->getName());
if ( $user instanceof sfGuardUser && $user->getIsActive()
&& $user->checkPassword($password)
) {
$sandboxUser = $user->copy(true);
$this->_fixCopiedRelations($sandboxUser);
$sandboxUser->setPasswordHash($user['password']);
$sandboxUser->save($sandConn);
return $sandboxUser;
}
return false;
}
private function _fixCopiedRelations(Doctrine_Record $rec)
{
$rel = $rec->getReferences();
foreach ($rel as $name => $related) {
if ($name == 'Responsibilities') {
$coll = new Doctrine_Collection('Client');
foreach ($related as $client) {
$o = Doctrine_Core::getTable('Client')->findOneByCode($client['code']);
if ($o instanceof Client == false) {
throw new UnexpectedValueException(
'Cannot find related object in the sandbox database, therefore not copying sfGuardUser to the sandbox.'
);
}
$coll->add($o);
}
$rec[$name] = $coll;
} else if ($name == 'Groups' || $name == 'Permissions') {
$coll = new Doctrine_Collection(($name == 'Groups' ? 'sfGuardGroup' : 'sfGuardPermission'));
foreach ($related as $instance) {
$o = Doctrine_Core::getTable(($name == 'Groups' ? 'sfGuardGroup' : 'sfGuardPermission'))->findOneByName($instance['name']);
if ($o instanceof Doctrine_Record == false) {
throw new UnexpectedValueException(
'Cannot find related object in the sandbox database, therefore not copying sfGuardUser to the sandbox.'
);
}
$coll->add($o);
}
$rec[$name] = $coll;
} else if ($name == 'Profile') {
$this->_fixCopiedRelations($related);
} else {
throw new UnexpectedValueException(
'Method does not know how to copy this related object to the sandbox, therefore not copying sfGuardUser to the sandbox'
);
}
}
}
Стоит отметить, что для предотвращения сбоя _fixCopiedRelations я должен убедиться, что любые связанные объекты, которые существуют в основной базе данных, также существуют в базе данных песочницы,но создание новых таких объектов очень ограничено, так что в данном случае это не проблема.
Я не особо в восторге от решения, но оноработает в этом ограниченном контексте, и это достаточно хорошо.