lushobarlett / query-manager
用于提高数据库代码灵活性、安全性和简化数据库使用案例自动化的 MySQL 查询管理器
Requires
- php: ^7
- lushobarlett/cologger: ^2
Requires (Dev)
- phpunit/phpunit: ^9
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 ...")
相同。
格式化和字段
Formatter
和 Field
类是用于清理输入的非常有用的工具。它们在稍后解释的 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_food
的 mydb.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();