Database abstraction layer ontop of mysqli

末鹿安然 提交于 2020-12-13 07:20:22

问题


when trying to build robust database code (table locking, transactions, etc) i am always annoyed by the mass of code that needs to be done.

For example a transaction out of two prepared statements where i want to delete a user and update something about him in an "actions" table:

  1. Lock Table users, actions
  2. Start a transaction (autocommit false)
  3. Make a prepared statement for the deletion of a user
  4. Check if statement is != false (cause it could have already failed at 3.
  5. Bind param
  6. Check errorState != "00000" on the statement (can also have failed at binding params)
  7. execute statement
  8. Check errorState != "00000" on the statement (can also have failed at executing)
  9. get Result of statement
  10. Close statement
  11. Make a new prepared statement for update actions
  12. Check if statement != false
  13. bind params
  14. check statement's errorState
  15. execute
  16. check statement's errorState
  17. get result
  18. close statement
  19. check overall transaction state, if valid commit, if not valid rollback
  20. unlock tables
  21. set autocommit back to true

This is how i do it (maybe im doing it wrong?). And if i do it that way its a lot of work and annoying. So i thought automateing that stuff.

What i want is something like this:

$DB->startTransaction();
$DB->query($query);
$DB->query($query2);
$DB->query($query3);
$DB->endTransaction();

And internally the database abstraction layer ontop of mysqli will take care of table locking, prepared statements and transactions itself. Shouldn't we be able to automate this?

This is one of my attempts:

public function query($query, $table, $params = null) {
            if($params == null) {
                $this->connection->query("LOCK TABLES $table WRITE");
                $query = str_replace("!", $table, $query);
                $result = $this->connection->query($query);
                $this->connection->query("UNLOCK TABLES");
                return $result;
            }
            else {
                if (!$this->checkParams($query, $params)) {
                    return false;
                }
                $this->connection->query("LOCK TABLES $table WRITE");
                $query = str_replace("!", $table, $query);

                $stmt = $this->connection->prepare($query);
                if ($stmt != false) {
                    $typesString = "";
                    foreach ($params as $param) {
                        if (is_numeric($param)) {
                            $typesString .= "i";
                        } else if (is_double($param)) {
                            $typesString .= "d";
                        } else {
                            $typesString .= "s";
                        }
                    }

                    $finalParamArray = array($typesString);
                    $finalParamArray = array_merge($finalParamArray, $params);
                    call_user_func_array(array($stmt, "bind_param"), $this->ref($finalParamArray));
                    $this->checkStatement($stmt);
                    $stmt->execute();
                    $this->checkStatement($stmt);
                    $result = $stmt->get_result();
                    $stmt->close();
                    $this->connection->query("UNLOCK TABLES");
                    return $result;
                }
                $this->query("UNLOCK TABLES");
                return false;
            }
        }

This would be callable like this:

$DB->query("DELETE FROM ! WHERE userID =?", "Users", array($userID));

I am however not feeling confident about this. I googled a bit and didn't find something like i want. So my question now is: Is something like i want actually possible (well it should be)? Am i doing it wrong?

EDIT: I also have 2 other attempts of doing this, which look MUCH MORE complicated (300+ lines of code). I can post them as well, if you want. I am still however not satisfied with them and not confident if this is actually correct!


回答1:


You are right there should be an easier way of doing this, and you are also correct to say that we need an abstraction layer on top of mysqli. It is not designed to be used on its own.

You do not need so many steps. In particular, you do not need to check the return code of each method. That should already eliminate 6 or more of your steps. You do not need to close a statement either.

There's no need to specify the type when binding. Just use string type all the time. Other types come in handy very rarely, almost never.

Some time ago I posted an example of what an abstraction layer on top of mysqli could look like.

class DBClass extends mysqli {
    public function __construct(
        $host = null,
        $username = null,
        $passwd = null,
        $dbname = null,
        $port = null,
        $socket = null
    ) {
        mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
        parent::__construct($host, $username, $passwd, $dbname, $port, $socket);
        $this->set_charset('utf8mb4');
    }

    public function safeQuery(string $sql, array $params = []): ?array {
        $stmt = $this->prepare($sql);
        if ($params) {
            $stmt->bind_param(str_repeat("s", count($params)), ...$params);
        }
        $stmt->execute();
        if ($result = $stmt->get_result()) {
            return $result->fetch_all(MYSQLI_BOTH);
        }
        return null;
    }
}

This is far from perfect, but it shows the main idea. You can wrap a prepared statement in one single method. Simple prepare/bind/execute/get_result. Nothing more. It works with and without parameters.

In the constructor the 3 mandatory steps to opening a connection: switching error reporting, creating instance of mysqli and setting the correct charset.

If you want transactions, then you can use mysqli's begin_transaction() and commit(). They are simple enough and do not require abstraction.

I do not know why you feel you need to lock tables, but again this is a simple SQL statement and doesn't need to be abstracted.

$db = new DBClass('localhost', 'user', 'pass', 'test');
$db->safeQuery('LOCK TABLES users WRITE');
$db->begin_transaction();
$db->safeQuery('DELETE FROM users WHERE userID =?', [$userID]);
$db->safeQuery('DELETE FROM otherTable WHERE userID =?', [$userID2]);
$db->commit();
$db->safeQuery('UNLOCK TABLES');


来源:https://stackoverflow.com/questions/30502444/database-abstraction-layer-ontop-of-mysqli

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!