amcsi/amysql

A MySQL(i) 包装器,适用于 PHP 5.2 及更高版本,包含有用的工具,无需任何扩展依赖。

v1.1.4 2014-09-25 16:48 UTC

README

Build Status

一个保证在任何 PHP 5.2+ 或 HHVM 配置下都能工作的 MySQL 包装器,且 mysqli 扩展或 mysql_* 函数可用。还包括许多工具来帮助构建查询、管理它们和剖析它们。

AMysql 适合您吗?

  • 感觉 Zend_Db 太过笨重,但另一方面又没有提供有用的工具来解决常见问题?
  • 担心使用 PDO 或 Mysqli,因为它可能在生产服务器上不可用?
  • 不喜欢组装 INSERT 和 UPDATE 的无聊任务?

那么 AMysql 就是您项目的库!

AMysql 试图成为什么

  • 一个库,允许您在不需要任何扩展的情况下,使用任何 PHP 配置与 mysql 一起工作。
  • 一个实用的库,为 MySQL 的常见情况提供出色的工具。这包括在 MySQL API(扩展级别或 PHP 级别)中不可用的工具,或通常只能在几个不同的 API 中单独可用但从未一起使用的工具组合。

AMysql 不是什么

  • 它不是一个抽象库。 虽然我可能添加 PostgreSQL 支持,但主要关注点是 MySQL,因为抽象会将注意力从能够为特定类型的数据库制作良好工具的能力上移开。此外,对我来说一个人深入研究所有其他数据库的功能将是一项过于艰巨的工作。
  • 它不是一个 ORM。 支持将数据检索到类的新的实例中,但重点是构建查询的便捷性。

要求

需要 PHP 5.1.0+,尽管这仅在 5.2 及更高版本上进行了测试,并且 MySQLi 扩展或 mysql_* 函数必须可用。

安装

在 Packagist 上可用。

查看 INSTALL 文件。

用法

通常您希望为每个数据库连接创建一个 AMysql 实例。AMysql 默认是懒惰连接的。

此项目有 Apigen 文档: http://amcsi.github.io/amysql/apigen/

实例化 AMysql

实例化 AMysql 时,您可以传入 mysql 连接资源或以数组形式传入连接详情。

<?php
$this->_amysql = new AMysql;
$this->_amysql->setConnDetails(array (
    'host' => 'localhost',
    'username' => 'user',
    'password' => 'pass',
    'db'        => 'db',
));

<?php
$this->_amysql = new AMysql(array (
    'host' => 'localhost',
    'username' => 'user',
    'password' => 'pass',
    'db'        => 'db',
));

<?php
$conn = mysql_connect($host, $user, $pass);
$amysql = new AMysql($conn);
$amysql->selectDb($db);

完整的连接详情数组支持

  • host 连接的主机
  • username Mysql 用户用户名
  • password Mysql 用户密码
  • db 要选择的数据库
  • 端口号
  • clientFlags mysql_* 仅限。对于 mysql_connect()$client_flags 参数
  • driver mysqlmysqli。优先使用特定驱动程序。默认情况下,在创建新连接时,如果 MySQLi 可用,AMysql 将尝试使用 MySQLi,否则将回退到 mysql_* 函数
  • socket - 套接字
  • defaultAutoCommit 在此处指定是否启用自动提交。当在 MySQL 服务器上使用 mysqli 并且自动提交关闭时,此应设置为 false。
  • autoPingSeconds 每次执行查询时,都会检查自上次查询以来是否已过去指定秒数,如果是,则ping服务器并在连接丢失时重新连接。建议将其设置为20秒,以防止脚本因2006错误而死亡。默认情况下关闭(NULL)。
  • autoReconnect 如果此设置为TRUE,并且执行查询失败,则尝试使用连接详细信息重新连接,并在放弃之前尝试重新执行查询一次。建议将其设置为TRUE。警告:可能会有一些与写入数据和最大数据包设置相关的问题,即使INSERT/UPDATE操作成功,也可能导致2006 mysql错误,从而导致重复写入。我不太理解这个问题,但在我之前从未遇到过。如果您想格外小心,请不要将其设置为TRUE,而只依赖autoPingSeconds
<?php
$data = array (
    'name' => 'adam',
    'description' => 'blah'
);
$amysql->insert('tablename', $data);

插入mysql表达式

在预处理语句中,您可以故意绑定不会转义或用引号括起来的文本字符串。请谨慎使用。

要使用它,请参阅下面的示例

<?php
$date = $amysql->expr('CURRENT_TIMESTAMP');
// or new AMysql_Expr($amysql, 'CURRENT_TIMESTAMP');
$data = array (
    'name' => 'adam',
    'description' => 'blah',
    'date' => $date
);
$insertId = $amysql->insert('tablename', $data);
// INSERT INTO `tablename` (`name`, `description`, `date`) VALUES ('adam', 'blah', CURRENT_TIMESTAMP);

AMysql_Expr还支持一些预定义的特殊表达式,不仅限于文本值,例如用于创建IN列表或对LIKE表达式进行适当的转义。有关更多信息,请查看AMysql/Expr.php文件

IN列表的示例

<?php
$materialsIn = $amysql->expr(
    AMysql_Expr::COLUMN_IN,
    'material',
    array('wood', 'metal', 'paper', 'plastic')
);
$sql = "SELECT * FROM tableName WHERE :materialsIn";

$amysql->query($sql, array('materialsIn' => $materialsIn));
// SELECT * FROM tableName WHERE `material` IN ('wood', 'metal', 'paper', 'plastic')

LIKE转义的示例。注意自动处理等号( http://stackoverflow.com/a/3683868/1381550 )。不要担心实际的百分号和下划线符号,因为它们将被正确转义并作为文本字符处理。默认的LIKE模式是在两个括号(%)之间包装搜索字符串,以进行“包含”匹配。这内部表示为sprintf模式%%%s%s,即(文本%),(字符串),(文本%)。您可以在AMysql_Expr::__construct的第三个参数中更改此设置

<?php
$descriptionLike = $amysql->expr(
    AMysql_Expr::ESCAPE_LIKE,
    'part'
));

$sql = "SELECT * FROM articles WHERE description LIKE :descriptionLike";
$amysql->query($sql, array('descriptionLike' => $descriptionLike));
// SELECT * FROM articles WHERE description LIKE '%part%' ESCAPE '='



// "begins with" example

$descriptionLike = $amysql->expr(
    AMysql_Expr::ESCAPE_LIKE,
    '100% success',
    '%%%s', // begins with
));

$sql = "SELECT * FROM articles WHERE description LIKE :descriptionLike";
$amysql->query($sql, array('descriptionLike' => $descriptionLike));
// SELECT * FROM articles WHERE description LIKE '%100=% success' ESCAPE '='
// note the automatic escaping of the (%) sign

插入多行数据

<?php
$data = array (
    array (
        'name' => 'adam',
        'description' => 'blah'
    ),
    array (
        'name' => 'bob',
        'description' => 'blahblah'
    )
);
$id = $amysql->insert('tablename', $data);
$affectedRows = $amysql->lastStatement->affectedRows();

// or you can achieve the same result by having the column names be the outer indexes.

$data = array (
    'name' => array (
        'adam', 'bob'
    ),
    'description' => array (
        'blah', 'blahblah'
    )
);
$id = $amysql->insert('tablename', $data);
$affectedRows = $amysql->lastStatement->affectedRows();

更新单行

<?php
/**
 * Update the name to bob and the description to blahmodified for all rows
 * with an id of 2.
 */
$data = array (
    'name' => 'bob',
    'description' => 'blahmodified'
);
$success = $amysql->update('tablename', $data, 'id = ?', array('2'));
$affectedRows = $amysql->lastStatement->affectedRows();

更新多行

如果您通过传递多维数组,可以使用与单行相同的insert方法来更新多行。它可以是行数组或列值数组。

<?php
/**
 * Update the name to bob and the description to blahmodified for all rows
 * with an id of 2, and update the name to carmen and the description to
 * anothermodified for all rows with an id of 3.
 */
$data = array (
    array (
        'id'            => 2
        'name'          => 'bob',
        'description'   => 'blahmodified'
    ),
    array (
        'id'            => 3
        'name'          => 'carmen',
        'description'   => 'anothermodified'
    )
);
$success = $amysql->updateMultipleByData('tablename', $data, 'id');
$affectedRows = $amysql->multipleAffectedRows;

删除行

<?php
$where = 'id = ?';
$success = $amysql->delete('tablename', $where, array ('2'));
$affectedRows = $amysql->lastStatement->affectedRows();

默认情况下,查询会抛出AMysql_Exceptions。

<?php
try {
    $amysql->query("SELECTbad-syntaxError-!#");
}
catch (AMysql_Exception $e) {
    trigger_error($e, E_USER_WARNING); // the exception converted to string
    // contains the mysql error code, the error message, and the query string used.
    // $e->getCode() contains the mysql error code
}

可以通过相对简单且不太容易出错的方式检查AMysql_Exceptions中的多个约束相关拒绝。

<?php
try {
    $data = array('passwordHash' => $passwordHash, 'email' => 'myemail@email.com');
    $amysql->insert('tableName', $data);
} catch (AMysql_Exception $e) {
    if (AMysql_Exception::CODE_DUPLICATE_ENTRY === $e->getCode()) {
        // $e->getProps(0) === 'myemail@email.com'
        if ('emailUniqueIndexName' === $e->getProps(1)) {
            $formError = 'A user with this email has already registered';
        } else {
            // code shouldn't get to here
            $formError = 'An unknown error has occured';
            trigger_error($e, E_USER_WARNING);
        }
    } else {
        $formError = 'An unknown error has occured';
        trigger_error($e, E_USER_WARNING);
    }
}

选择(无AMysql_Select)

<?php
// with named parameters (do not use apostrophes)
$binds = array (
    'id' => 1,
    'state' => 'active'
);
$stmt = $amysql->query("SELECT * FROM tablename WHERE id = :id AND state = :state", $binds);
$results = $stmt->fetchAllAssoc();

// with unnamed parameters (never use apostrophes when binding values)
$binds = array (1, 'active');
$stmt = $amysql->query("SELECT * FROM tablename WHERE id = ? AND state = ?", $binds);
$results = $stmt->fetchAllAssoc();
$numRows = $stmt->numRows();

请注意,如果预处理字符串中只有一个问号,您还可以将$binds作为标量值传递,它将被视为数组中的值。如果您预计要绑定可能为null的值,则不要使用标量方法(在这种情况下始终使用数组)。

附言。这同样适用于每个期望$binds数组的方法。

首先准备选择,然后执行(无AMysql_Select)

<?php
$binds = array (
    'id' => 1,
    'state' => 'active'
);
$stmt = $amysql->prepare("SELECT * FROM tablename WHERE id = :id AND state = :state");
$stmt->execute($binds);
$results = $stmt->fetchAllAssoc();

####现在使用新的AMysql_Select类来帮助组装SELECT SQL字符串

<?php
$select = $mysql->select();
$select 
    ->option('DISTINCT')
    ->option('SQL_CALC_FOUND_ROWS')
    ->from(array ('table1', 't2alias' => 'table2'))
    ->from(array ('t3alias' => 'table3'), array ('t3_col1' => 'col1', 't3_col2' => 'col2'))
    ->column('t2alias.*')
    ->column (array ('t1_col1' => 'table1.col1'))
    ->columnLiteral('table7, table8, CURRENT_TIMESTAMP AS ctimestamp')
    ->join(
        '',
        array ('t4alias' => 'table4'),
        't4alias.t1_id = table1.id',
        array ('t4lol', 't4lol2aliased' => 't4lol2')
    )
    ->join('left', array ('table5'), 't2alias.colx = table5.coly', array (), true)
    ->join('cross', array ('table6'), 't3alias.colx = table6.coly', array ())
    ->groupBy('t2alias.col1')
    ->groupBy('t2alias.col2', true, true)
    ->groupBy('t2alias.col3', true)
    ->having('1 = 1')
    ->having('2 = 2')
    ->orderBy('t3alias.col1')
    ->orderBy('t3alias.col2', true, true)
    ->orderBy('t3alias.col3', true)
    ->where('3 = :aBind')
    ->where("'yes' = :anotherBind")
    ->limit(100)
    ->offset(200)
;
$select->execute(array ('aBind' => 3, 'anotherBind' => 'yes'));
/*
SELECT DISTINCT `t3alias`.`col1` AS `t3_col1`, `t3alias`.`col2` AS `t3_col2`,
    `t2alias`.*, `table1`.`col1` AS `t1_col1`, `t4alias`.`t4lol`,
    `t4alias`.`t4lol2` AS `t4lol2aliased`,
    table7, table8, CURRENT_TIMESTAMP AS ctimestamp'
    FROM `table1`, `table2` AS `t2alias`, `table3` AS `t3alias`
    LEFT JOIN `table5` ON (t2alias.colx = table5.coly)
    JOIN `table4` AS `t4alias` ON (t4alias.t1_id = table1.id)
    CROSS JOIN `table6` ON (t3alias.colx = table6.coly)
    WHERE 3 = 3 AND 'yes' = 'yes'
    GROUP BY `t2alias`.`col2` DESC, `t2alias`.`col1`, `t2alias`.`col3` DESC
    HAVING 1 = 1 AND 2 = 2
    ORDER BY `t3alias`.`col2` DESC, `t3alias`.`col1`, `t3alias`.`col3` DESC
    LIMIT 100
    OFFSET 200
 */
$foundRows = $amysql->foundRows(); // resolves "SELECT FOUND_ROWS()" for "SQL_CALC_FOUND_ROWS"

阅读注释中的AMysql_Select文件以获取更多信息。

有关绑定参数的文档可以在AMysql_Statement::execute()的注释中找到。请务必查看。

性能分析

一个简单的示例,无需制作自己的HTML模板

$profiler = $amysql->getProfiler();

// ... execute a bunch of MySQL queries

$view->profiler = $profiler; // add it to your view.
$view->render('layout.phtml');

// layout.phtml:

<html>
    <head>
    </head>
    <body>
        <?php include 'content.phtml' ?>

        <?php if ('dev' == APP_ENV): ?>
        <?php echo $this->profiler->getAsHtml() ?>
        <?php endif ?>
    </body>
</html>

或者,查看AMysql_Profiler以获取更多选项。

其他方法

还有许多其他有用的方法。请查看源文件并阅读方法的文档。

此项目有 Apigen 文档: http://amcsi.github.io/amysql/apigen/

许可证

MIT

贡献

如果您发现任何问题不符合您的预期,请将问题报告到github上。

如果您能提供修复问题的拉取请求(pull requests),那就更好了。请从最新分支 masterdevelop 进行工作。