0

我有一堂课,里面有一百多个函数/方法。其中许多需要在事务中运行 MySQLi 查询。所以,在一个简单的形式中,大多数函数看起来像这样:

class MyClass {
    private $connection = ...; // mysqli connection
    public function a () {
        $this->connection->autocommit(FALSE);
        // do something and run multiple queries
        $this->connection->commit();
    }
    public function b () {
        $this->connection->autocommit(FALSE);
        // do something and run multiple queries
        $this->connection->commit();
    }
}
// to run the code:
$myclass = new MyClass();
$myclass->a(); // all queries are run inside a transaction
$myclass->b(); // all queries are run inside a transaction

这个概念很简单:每次我运行一个公共函数/方法时,它都会启动一个事务并在结束函数/方法之前提交。现在,我的问题是......如果我想在另一个方法中运行一个方法,同时保持两个方法都是公开的并且可以被类外的调用者调用怎么办?例如:

class MyClass {
    private $connection = ...; // mysqli connection
    public function a () {
        $this->connection->autocommit(FALSE);
        // do something and run multiple queries
        $this->connection->commit();
    }
    public function b () {
        $this->connection->autocommit(FALSE);
        // do something and run multiple queries
        $this->a(); // running function a inside function b here
        $this->connection->commit();
    }
}
// to run the code:
$myclass = new MyClass();
$myclass->a(); // all queries are run inside a transaction
$myclass->b(); // transaction is not working as I initially wished. This function consists of more than one commit, hence, not everything is run inside one transaction

使用额外的变量进行手动 if/else 检查,我想我可以用两个函数解决上述问题。但是,我的问题是我有多个(可能将近一百个)功能/方法与上述类似。我不认为我可以使用一些 if/else 来处理如此复杂的事务意大利面地狱问题。

我该如何解决这个问题?

PS:

以上只是我的课程的简化。为了让事情变得更加复杂,在我的课堂上我不只是使用:

function func() {
    $this->connection->autocommit(FALSE);
    // do something
    $this->connection->commit();
}

相反,我做了这样的事情:

function func() {
    if ($result = $this->connection->query('SELECT @@autocommit')) {
        $row = mysqli_fetch_row($result);
        $originalAutoCommitValue = $row[0];
        mysqli_free_result($result);
    }
    if (!$originalAutoCommitValue) {
        $this->connection->autocommit(FALSE);
    }
    // do something...
    $this->connection->commit();
    $this->connection->autocommit($originalAutoCommitValue);
}

这将允许我的类中的函数/方法启动事务,提交事务并将自动提交状态返回到之前。这确实很笨拙,但我希望我班的用户可以选择是否在课外使用事务/提交。我希望这种方法在使用我的任何类的方法之前不会影响他们原始的自动提交状态。

更新

我在类中添加了以下属性和两个函数:

private $connectionTransactionStack = 0;
private $connectionOriginalAutoCommitState = NULL;

private function startTransaction() {
    if (is_null($this->connectionOriginalAutoCommitState)) {
        if ($result = $this->connection->query('SELECT @@autocommit')) {
            $row = mysqli_fetch_row($result);
            $this->connectionOriginalAutoCommitState = $row[0];
            mysqli_free_result($result);
        }
    }
    if ($this->connectionOriginalAutoCommitState === 1) {
        $this->connection->autocommit(FALSE);
    }
    $this->connectionTransactionStack++;
}
private function commitTransaction() {
    $this->connectionTransactionStack--;
    if ($this->connectionTransactionStack < 1) {
        $this->connectionTransactionStack = 0;
        if ($this->connectionOriginalAutoCommitState === 1) {
            $this->connection->commit();
            $this->connection->autocommit($this->connectionOriginalAutoCommitState);
        }
    }
}

所以,现在当我想运行原始函数时,我可以这样做:

class MyClass {

    public function a () {
        $this->startTransaction();
        // do something and run multiple queries
        $this->commitTransaction();
    }
    public function b () {
        $this->startTransaction();
        // do something and run multiple queries
        $this->a(); // running function a inside function b here
        $this->commitTransaction();
    }
}

只要我提交所有事务,如果我在任何方法中启动任何事务,非最终的 $this->commitTransaction() 将不会运行真正的 mysqli->commit(),但最终的 $this->commitTransaction() 将运行mysqli->commit 如有必要(取决于在我的类中第一次运行任何方法之前的初始状态)。所以如果我想运行:

$this->a();

或者

$this->b();

,这根本不重要,因为有一个堆栈来计算我会开始的事务。然而,实际上,只有一个事务要启动和提交。

这可以解决我上面的问题吗?

4

1 回答 1

1

一种选择可能是删除

$this->connection->autocommit(FALSE);
$this->connection->commit();

在每个相关的函数(a,b)中,然后将它们包装在您的函数调用中。

class MyClass {
    private $connection = ...; // mysqli connection
    public function a () {
        // do something and run multiple queries
    }
    public function b () {
        // do something and run multiple queries
        $this->a(); // running function a inside function b here
    }
}
// to run the code:
$myclass = new MyClass();

$myclass->connection->autocommit(FALSE);
$myclass->a(); // all queries are run inside a transaction
$myclass->b(); 
$this->connection->commit();

更新:

或者也许更好,使用包装函数来调用类中的函数:

$myclass->doTransaction('a'); 
$myclass->doTransaction('b'); 

function doTransaction($functionCall) {
    //Do some sanitizing of the functionCall-variable
    $this->connection->autocommit(FALSE);
    call_use_func($functionCall); 
    $this->connection->commit();
}
于 2013-06-27T06:13:29.737 回答