lushobarlett/query-manager

用于提高数据库代码灵活性、安全性和简化数据库使用案例自动化的 MySQL 查询管理器

v3.0.0 2021-06-20 19:43 UTC

This package is auto-updated.

Last update: 2024-09-21 02:48:10 UTC


README

查询构建和执行管理器。

目标

  • 查询代码的灵活性和可重用性
  • 解决安全和精确性问题
  • 强制使用,不对程序员造成任何惩罚
  • 使简单的数据库使用案例更容易编码

实现

查询块

查询构建块是一个 QueryPiece 类。从数学的角度来看,它不过是一个幺半群,因为它充当字符串和数组的对。第一个构造函数参数是语句,其余的是填充该语句的值,称为 片段。所有参数都是可选的,如果没有提供任何内容,您将得到恒等元、空字符串和数组。

$qp = new QueryPiece(
	"SELECT * FROM mytable WHERE id = ? and name = ?", 1, "some name"
);
$qp->template // "SELECT * FROM mytable WHERE id = ? and name = ?"
$qp->fragments // [1, "some name"]

这些可以变得更小,然后合并。空格将自动添加。

$qp1 = new QueryPiece("SELECT * FROM mytable");
$qp2 = new QueryPiece("WHERE id = ?", 1);
$qp3 = new QueryPiece("AND name = ?", "some name");

// produces the same object as the first example
$qp = QueryPiece::merge($qp1, $qp2, $qp3);

还有许多静态方法可以使它看起来更好,例如 QueryPiece::Select(...),它与 new QueryPiece("SELECT ...") 相同。

格式化和字段

FormatterField 类是用于清理输入的非常有用的工具。它们在稍后解释的 Table 类中非常有用,但它们并不局限于那种用途。

Field 定义了对值执行的操作链。这里有映射、替换、选项、类型和类限制以及类型转换。

$pipe = new Field("name")
	->cast(Field::String)
	->in(["Hi", "Bye", "Hello", "Goodbye"])
	->replace([
		"Hi" => "Hello"
		"Bye" => "Goodbye"
	]),
	->map(fn($v) => $v . "!");

$pipe->pipeline("Hi"); // "Hello!"
$pipe->pipeline("Goodbye"); // "Goodbye!"

Formatter 只是一系列字段,但我们可以对这些字段使用新的限制。字段可以是 可选的必需的。如果它们是可选的,它们可以有一个默认值,在管道中使用。此外,Formatter 还可以接受字符串,这些代表没有默认值和管道的可选字段。

$f = new Formatter(
	Field::default("first", 0),
	Field::required("second"),
	Field::optional("third")
);

接着,您可以使用一些数据调用格式化函数。请注意,格式化数组和对象的方式相同,并且将以这种方式返回。

$data = ["unwanted key" => 0, "second" => 1];

$f->format($data); // ["first" => 0, "second" => 1]
$f->format((object)$data); // {"first": 0, "second": 1}

Column 类只保存一个字符串,即列的名称。它还可以指定它是否是主列(也意味着唯一),是否唯一,以及是否是外键。在后一种情况下,它将保存一个指向该外键列的 Name 类。

$column = new Column("this_id")
	->primary()
	->foreign(new Name("db", "other_table", "other_id"));

echo $column // "this_id"

名称

Name 是一个包含数据库、表、列名称或别名的类。它可以生成 SQL 可以使用的有效字符串,或仅用于内部。

对于数据库,接受 IConnection。对于列,也接受 Column

不是所有四个都是必需的,任何组合都可以工作。请注意,某些组合没有意义。

echo (new Name)
	->table("table")
	->alias("t") // `table` AS `t`

$fullname = new Name("database", "table", "column", "alias");

echo $fullname->db; // database
echo $fullname->table; // table
echo $fullname->column; // column
echo $fullname->alias; // alias

Table 是任何表的 静态 基类。它实现了许多基本 静态 函数,这些函数可用于子类。

子类需要实现一个函数,即 connect。在那里,子类将构建一个 TableData 对象,并将其与在 connect 中提供的连接一起传递给 initialize

假设我们有一个具有列 id, name, age, fav_foodmydb.person 表。

构建

class Person extends Table {

	public static function connect(IConnection $conn) {
		// Note: if you don't need Column utilities,
		// you can use plain strings.
		$columns = [
			Column::make_primary("id"),
			"name",
			"age",
			"fav_food"
		];

		// forbids primary key insert
		$insert = new Formatter(
			"name",
			"age",
			Field::default("fav_food", "banana")
		);

		// also forbids name update
		$update = new Formatter(
			"age",
			"fav_food"
		);

		$data = (new TableData)
			->db("mydb")
			->name("person")
			->columns($columns)
			->on_insert($insert)
			->on_update($update);

		static::initialize($conn, $data);
	}
}

//...

$conn = get_my_connection();
Person::connect($conn);

执行查询的表是公开的,因此您可以从外部使用任何子类调用。并且始终记得调用 $conn->commit()

// $data can be put directly here, the formatter takes care of cleanup.
// Table and Connection take care statement preparation,
// which prevents SQL inyection.
$data = get_evil_raw_data();
Person::insert($data);

连接

连接包含连接到数据库所需的数据。它还准备作为查询块传递的语句,并自动使用事务模型。构建方式与正常的 mysqli 类相同。

// Note: database is optional
$c = new Connection("host", "user", "password", "database");

但是它始终执行查询准备,并且还自动暴露并使用事务管理功能。它在构造函数中启动事务,在发生任何错误时回滚,并在销毁时关闭。它不会自动提交,所以您需要自己完成这一步骤。

$qp = new QueryPiece(...);
$result = $c->execute($qp);
$c->commit();
$c->transaction();
$c->rollback();