voku/simple-mysqli

8.3.1 2022-07-28 22:34 UTC

README

Build Status FOSSA Status Coverage Status Codacy Badge Latest Stable Version Total Downloads License Donate to this project using Paypal Donate to this project using Patreon

💎 简单 MySQLi 类

这是一个与 PHP 7+ 和 PHP 8.0 兼容的简单 MySQL 抽象层,它使用 mysqli_* 函数在核心处提供简单且 安全 的数据库交互。这对于像 cron 作业、facebook canvas 活动、微型框架或网站等小型应用非常理想。

你还可以使用 💍 "Simple Active Record" 类,它基于此数据库类并添加了一些面向对象的语法。但在使用之前,请了解“Active Record”与“数据映射器”之间的区别。

获取 "Simple MySQLi"

你可以在此处下载,或者使用 composer 安装。

  {
      "require": {
        "voku/simple-mysqli": "8.*"
      }
  }

通过 "composer require" 安装

  composer require voku/simple-mysqli

启动驱动器

  use voku\db\DB;

  require_once 'composer/autoload.php';

  $db = DB::getInstance('yourDbHost', 'yourDbUser', 'yourDbPassword', 'yourDbName');
  
  // example
  // $db = DB::getInstance('localhost', 'root', '', 'test');

多例 && 单例

你可以无参数地使用 DB::getInstance() 并获取你的(作为“单例”)第一个初始化的连接。或者,你可以更改参数,这样你将创建一个新的“多例”实例,它的工作方式类似于单例,但你需要再次使用相同的参数,否则(没有相同的参数)你会得到一个新的实例。

Doctrine/DBAL 作为父驱动器

  use voku\db\DB;

  require_once 'composer/autoload.php';
  
  $connectionParams = [
      'dbname'   => 'yourDbName',
      'user'     => 'yourDbUser',
      'password' => 'yourDbPassword',
      'host'     => 'yourDbHost',
      'driver'   => 'mysqli', // 'pdo_mysql' || 'mysqli'
      'charset'  => 'utf8mb4',
  ];
  $config = new \Doctrine\DBAL\Configuration();
  $doctrineConnection = \Doctrine\DBAL\DriverManager::getConnection(
      $connectionParams,
      $config
  );
  $doctrineConnection->connect();

  $db = DB::getInstanceDoctrineHelper($doctrineConnection);

使用 "DB" 类

使用此库有无数种方式,这里有一些最常见方法的示例。

从表中选择和检索数据

  use voku\db\DB;
  
  $db = DB::getInstance();
  
  $result = $db->query("SELECT * FROM users");
  $users  = $result->fetchAll();

但你也可以使用一个方法进行选择查询

  $db->select(string $table, array $where); // generate an SELECT query

示例:选择

  $where = [
      'page_type ='         => 'article',
      'page_type NOT LIKE'  => '%öäü123',
      'page_id >='          => 2,
  ];
  $articles = $db->select('page', $where);
  
  echo 'There are ' . count($articles) . ' article(s):' . PHP_EOL;
  
  foreach ($articles as $article) {
      echo 'Type: ' . $article['page_type'] . PHP_EOL;
      echo 'ID: ' . $article['page_id'] . PHP_EOL;
  }

下面是 "WHERE" 数组的连接器列表:"NOT","IS","IS NOT","IN","NOT IN","BETWEEN","NOT BETWEEN","LIKE","NOT LIKE",">","<",">=","<=","<>","+","-"

INFO:将数组用作 "[NOT] IN" 和 "[NOT] BETWEEN" 的 $value

INFO:在值中使用 + / - 而不是在 $data 的键中使用

示例:使用 "page_template = page_template + 1" 的 UPDATE

  $where = [
      'page_type LIKE'     => '%foo',
      'page_type NOT LIKE' => 'bar',
  ];
  $data = [
      'page_template' => ['page_template +' => 1],
      'page_type'     => 'lall',
  ];
  $resultSelect = $db->update('page', $data, $where);

示例:使用 "NOT IN" 的 SELECT

  $where = [
      'page_type NOT IN' => [
          'foo',
          'bar'
      ],
      'page_id >'        => 2,
  ];
  $resultSelect = $db->select('page', $where);

示例:使用缓存的 SELECT

  $resultSelect = $db->execSQL("SELECT * FROM users", true, 3600);

结果(通过 $result->fetchAllArray())仅在查询为 SELECT 语句时缓存 3600 秒,否则你将得到来自 $db->query() 函数的默认结果。

在表中插入数据

要操纵表,你可以使用最基本的方法封装,它们的工作方式都相同:解析键/值对的数组并形成安全的查询

这些方法是

  $db->insert( string $table, array $data );                // generate an INSERT query
  $db->replace( string $table, array $data );               // generate an REPLACE query
  $db->update( string $table, array $data, array $where );  // generate an UPDATE query
  $db->delete( string $table, array $where );               // generate a DELETE query

所有方法都将返回结果 mysqli_insert_id() 或根据上下文返回 true/false。正确的方法是始终检查它们是否成功执行,总是返回正确的结果

示例:DELETE

  $deleteArray = ['user_id' => 9];
  $ok = $db->delete('users', $deleteArray);
  if ($ok) {
    echo "user deleted!";
  } else {
    echo "can't delete user!";
  }

注意:在执行之前,所有参数值都会进行清理,你不需要事先转义值。

示例:INSERT

  $insertArray = [
    'name'   => "John",
    'email'  => "johnsmith@email.com",
    'group'  => 1,
    'active' => true,
  ];
  $newUserId = $db->insert('users', $insertArray);
  if ($newUserId) {
    echo "new user inserted with the id $new_user_id";
  }

示例:REPLACE

  $replaceArray = [
      'name'   => 'lars',
      'email'  => 'lars@moelleken.org',
      'group'  => 0
  ];
  $tmpId = $db->replace('users', $replaceArray);

在查询中绑定参数

绑定参数是防止MySQL注入的有效方法,因为在执行之前参数都会被清理。

  $sql = "SELECT * FROM users 
    WHERE id_user = :id_user
    AND active = :active
    LIMIT 1
  ";
  $result = $db->query($sql, ['id_user' => 11, 'active' => 1]);
  if ($result) {
    $user = $result->fetchArray();
    print_r($user);
  } else {
    echo "user not found";
  }

事务

使用begin()commit()rollback()来管理事务

$db->beginTransaction();

$db->query(
    'UPDATE `users` SET `foo` = :foo WHERE id = :id',
    ['foo' => 100, 'id' => 1]
);
$db->query(
    'UPDATE `users_noop` SET `foo` = :foo WHERE id = :id',
    ['foo' => 100, 'id' => 2]
);

$db->endTransaction();

begin()commit()之间的任何SQL错误都会引发一个RuntimeException

您还可以使用DB->transact()方法。以下代码与上述代码等效

$db->transact(function($db) {
    $db->query(
        'UPDATE `users` SET `foo` = :foo WHERE id = :id',
        ['foo' => 100, 'id' => 1]
    );
    $db->query(
        'UPDATE `users_noop` SET `foo` = :foo WHERE id = :id',
        ['foo' => 100, 'id' => 2]
    );
});

使用 "Result" 类

执行一个SELECT查询后,您将收到一个Result对象,该对象可以帮助您操作结果数据。有不同方式访问这些数据,请查看下面的示例

获取所有数据

  $result = $db->query("SELECT * FROM users");
  $allUsers = $result->fetchAll();

获取所有数据与Result::RESULT_TYPE_*工作相同,fetchAll()fetch()方法将返回默认值,基于$_default_result_type配置。其他方法包括

  $row = $result->fetch();        // fetch an single result row as defined by the config (array, object or Arrayy)
  $row = $result->fetchArray();   // fetch an single result row as array
  $row = $result->fetchArrayy();  // fetch an single result row as Arrayy object
  $row = $result->fetchObject();  // fetch an single result row as object
  $row = $result->fetchYield();   // fetch an single result row as Generator
  
  $data = $result->fetchAll();        // fetch all result data as defined by the config (array, object or Arrayy)
  $data = $result->fetchAllArray();   // fetch all result data as array
  $data = $result->fetchAllArrayy();  // fetch all result data as Array object
  $data = $result->fetchAllObject();  // fetch all result data as object
  $data = $result->fetchAllYield();   // fetch all result data as Generator
  
  $data = $result->fetchColumn(string $column, bool $skipNullValues);    // fetch a single column as string
  $data = $result->fetchAllColumn(string $column, bool $skipNullValues); // fetch a single column as an 1-dimension array
  
  $data = $result->fetchArrayPair(string $key, string $value);           // fetch data as a key/value pair array

获取数据库表字段

返回结果集中的字段信息行

$fields = $result->fetchFields();

如果您想以关联数组的形式返回每个字段信息,请将true作为参数传递。默认情况下,每个字段信息都以对象返回,这与mysqli_fetch_fields函数完全相同。

获取 + 可调用

获取一行或行内的单个列

$data = $result->fetch($row_number, $column);

此方法构成了所有fetch_方法的基础。所有形式的fetch_都将内部行指针向前移动到下一行。如果没有更多的行可以获取,将返回null

获取 + 转置

一次性返回所有行,转置为二维数组

$plan_details = $plans->fetchTranspose();

转置具有X行和Y列的结果集将导致一个二维数组,其中每个Y行包含X列。

将列名作为参数传递,以返回每个列作为包含键的关联数组,键来自提供的列值。如果没有提供,则键将从零开始为数字。

例如

$transposedExample = [
  'title' => [
    1 => 'Title #1',
    2 => 'Title #2',
    3 => 'Title #3',
  ],
);

获取 + 对

一次性将所有行作为键值对返回,使用第一个参数中的列作为键

$countries = $result->fetchPairs('id');

将列名作为第二个参数传递,以仅返回每个对中的单个列作为值

$countries = $result->fetchPairs('id', 'name');

/*
[
  1 => 'Title #1',
  2 => 'Title #2',
  3 => 'Title #3',
]
*/

获取 + 组

一次性将所有行作为分组数组返回

$students_grouped_by_gender = $result->fetchGroups('gender');

将列名作为第二个参数传递,以仅返回每个组中的单个列作为值

$student_names_grouped_by_gender = $result->fetchGroups('gender', 'name');

获取 + 第一个

从结果中返回第一行元素

$first = $result->first();

将列名作为参数传递,以返回第一行中的单个列

$name = $result->first('name');

获取 + 最后一个

从结果中返回最后一行元素

$last = $result->last();

将列名作为参数传递,以返回最后一行中的单个列

$name = $result->last('name');

获取 + 范围

从结果中返回行切片

$slice = $result->slice(1, 10);

上述代码将返回10行,跳过第一行。第一个参数是零基于偏移量;第二个参数是要求数量;第三个参数是布尔值,表示是否保留键(可选,默认为false)。此方法基本上与PHP内置的array_slice()函数的行为相同。

获取 + 映射

设置用于Result->fetchCallable()方法内的回调函数的映射器

$result->map(function($row) {
    return (object) $row;
});
$object = $result->fetchCallable(0);

上述示例将结果中的一个行(0)映射到一个对象。将映射回调函数设置为null以禁用它。

获取 + 别名

  $db->get()                  // alias for $db->fetch();
  $db->getAll()               // alias for $db->fetchAll();
  $db->getObject()            // alias for $db->fetchAllObject();
  $db->getArray()             // alias for $db->fetchAllArray();
  $db->getArrayy()            // alias for $db->fetchAllArrayy();
  $db->getYield()             // alias for $db->fetchAllYield();
  $db->getColumn($key)        // alias for $db->fetchColumn($key);

获取 + 迭代

要迭代结果集,可以使用上面列出的任何fetch()方法。

  $result = $db->select('users');

  // using while
  while ($row = $result->fetch()) {
    echo $row->name;
    echo $row->email;
  }

  // using foreach (via "fetchAllObject()")
  foreach($result->fetchAllObject() as $row) {
    echo $row->name;
    echo $row->email;
  }
  
  // using foreach (via "Result"-object)
  foreach($result as $row) {
    echo $row->name;
    echo $row->email;
  }
  
  // using foreach (via "Generator"-object)
  foreach($result->fetchAllYield() as $row) {
    echo $row->name;
    echo $row->email;
  }
  
  // INFO: "while + fetch()" and "fetchAllYield()" will use less memory that "foreach + "fetchAllObject()", because we will fetch each result entry seperatly

执行多查询

要执行多个查询,可以使用$db->multi_query()方法。您可以使用分号;分隔多个查询。

返回类型

  • 通过SELECT查询返回"Result"-Array
  • 仅通过INSERT查询返回"bool"
  • 仅通过(affected_rows)通过UPDATE / DELETE查询返回"bool"
  • 仅通过例如"DROP"查询返回"bool"

例如

$sql = "
    INSERT INTO foo
      SET
        page_template = 'lall1',
        page_type = 'test1';
    INSERT INTO lall
      SET
        page_template = 'lall2',
        page_type = 'test2';
    INSERT INTO bar
      SET
        page_template = 'lall3',
        page_type = 'test3';
";
$result = $this->db->multi_query($sql); // true

$sql = "
    SELECT * FROM foo;
    SELECT * FROM lall;
    SELECT * FROM bar;
";
$result = $this->db->multi_query($sql); // Result[]
foreach ($result as $resultForEach) {
    $tmpArray = $resultForEach->fetchArray();
    ...
}

使用 "Prepare" 类

预处理语句的优点是它们在MySQL-Server中一起构建,因此性能更好。

但调试更难,日志记录不可能(通过PHP),因此我们添加了一个名为"bind_param_debug"的"bind_param"包装器。使用此包装器,我们通过PHP预先构建SQL查询(仅用于调试/记录)。现在您可以将查询echo。

INFO:您仍然可以使用"bind_param"而不是"bind_param_debug",例如,如果您需要更好的性能。

INSERT-Prepare-Query (示例)

  use voku\db\DB;
  
  $db = DB::getInstance();
  
  // ------------- 
  // prepare the queries
  
  $query = 'INSERT INTO users
    SET 
      name = ?, 
      email = ?
  ';
  
  $prepare = $db->prepare($query);
  
  $name = '';
  $email = '';
  
  $prepare->bind_param_debug('ss', $name, $email);
  
  // -------------
  // execute query no. 1
  
  // INFO: "$template" and "$type" are references, since we use "bind_param" or "bind_param_debug" 
  $name = 'name_1_中';
  $email = 'foo@bar.com';
  
  $prepare->execute();
  
  // DEBUG
  echo $prepare->get_sql_with_bound_parameters();
  
  // -------------
  // execute query no. 2
  
  // INFO: "$template" and "$type" are references, since we use "bind_param" or "bind_param_debug"  
  $name = 'Lars';
  $email = 'lars@moelleken.org';
  
  $prepare->execute();
  
  // DEBUG
  echo $prepare->get_sql_with_bound_parameters();

SELECT-Prepare-Query (示例)

  use voku\db\DB;
  
  $db = DB::getInstance();
  
  // -------------
  // insert some dummy-data, first
  
  $data = [
      'page_template' => 'tpl_test_new123123',
      'page_type'     => 'ö\'ä"ü',
  ];

  // will return the auto-increment value of the new row
  $resultInsert[1] = $db->insert($this->tableName, $data);
  $resultInsert[2] = $db->insert($this->tableName, $data);

  // ------------- 
  // prepare the queries

  $sql = 'SELECT * FROM ' . $this->tableName . ' 
    WHERE page_id = ?
  ';

  $prepare = $this->db->prepare($sql);
  $page_id = 0;
  $prepare->bind_param_debug('i', $page_id);

  // ------------- 
  // execute query no. 1

  $page_id = $resultInsert[1];
  $result = $prepare->execute();
  $data = $result->fetchArray();

  // $data['page_template'] === 'tpl_test_new123123'
  // $data['page_id'] === $page_id

  // ------------- 
  // execute query no. 2

  $page_id = $resultInsert[2];
  $result = $prepare->execute();
  $data = $result->fetchArray();

  // $data['page_id'] === $page_id
  // $data['page_template'] === 'tpl_test_new123123'

日志和错误

您可以将钩子插入到“DB”类中,这样您就可以使用您自己的“Logger”类。但您必须覆盖这些方法。

$this->trace(string $text, string $name) { ... }
$this->debug(string $text, string $name) { ... }
$this->info(string $text, string $name) { ... }
$this->warn(string $text, string $name) { ... } 
$this->error(string $text, string $name) { ... }
$this->fatal(string $text, string $name) { ... }

您还可以通过“DB”类的“getInstance()”参数“logger_level”禁用每个sql查询的记录。如果您将“logger_level”设置为除“TRACE”或“DEBUG”之外的其他值,则“DB”类将仅记录错误。

DB::getInstance(
    getConfig('db', 'hostname'),        // hostname
    getConfig('db', 'username'),        // username
    getConfig('db', 'password'),        // password
    getConfig('db', 'database'),        // database
    getConfig('db', 'port'),            // port
    getConfig('db', 'charset'),         // charset
    true,                               // exit_on_error
    true,                               // echo_on_error
    'cms\Logger',                       // logger_class_name
    getConfig('logger', 'level'),       // logger_level | 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
    getConfig('session', 'db')          // session_to_db
);

显示查询日志:日志包含执行的SQL语句、执行时间和结果行数。

  print_r($db->log());

要调试mysql错误,请使用$db->errors()获取所有错误(如果没有错误则返回false)或使用$db->lastError()获取关于最后一个错误的详细信息。

  if ($db->errors()) {
    echo $db->lastError();
  }

但最简单的调试方法是配置“DB”类,通过“DB::getInstance()”显示错误并在错误时退出(请参见上面的示例)。现在,如果您在“localhost”上工作,您可以在浏览器中看到SQL错误;或者您可以通过一个简单的函数实现自己的“checkForDev()”,而无需扩展“Debug”类。如果您将通过电子邮件接收错误信息,则可以代替扩展“Debug”类实现自己的“mailToAdmin()”函数。

更新日志

请参阅CHANGELOG.md

支持

有关支持和捐款,请访问Github | 问题 | PayPal | Patreon

有关状态更新和发布公告,请访问发布 | Twitter | Patreon

如需专业支持,请联系

感谢

  • 感谢GitHub(微软)提供代码托管和良好的基础设施,包括问题管理等。
  • 感谢IntelliJ,他们制作了最好的PHP IDE,并为我提供了PhpStorm的开源许可!
  • 感谢Travis CI,它是最好的、最简单的持续集成工具!
  • 感谢StyleCI提供的简单但强大的代码风格检查。
  • 感谢PHPStanPsalm,它们提供了真正出色的静态分析工具,并帮助发现代码中的错误!

许可证

FOSSA Status