alexya-framework / database
Alexya的数据库工具
Requires
- php: >=7.0
- alexya-framework/tools: >=3.0
This package is not auto-updated.
Last update: 2024-09-14 19:36:48 UTC
README
Alexya的数据库组件
内容
连接
类 \Alexya\Database\Connection
提供了一个易于连接到数据库并执行查询的层。
连接到数据库
要连接到数据库,您需要实例化一个 \Alexya\Database\Connection
对象,构造函数接受以下参数:
- 一个字符串,代表服务器的地址/ip
- 一个整数,代表服务器的端口
- 一个字符串,代表数据库用户名
- 一个字符串,代表数据库密码
- 一个字符串,代表数据库密码
执行查询
方法 \Alexya\Database\Connection::execute
接受一个字符串参数,该字符串是要执行的SQL查询
<?php $Database = new \Alexya\Database\Connection("localhost", 3306, "root", "", "alexya"); $users = $Database->execute("SELECT * FROM `users`"); print_r($users);
您还可以发送一个布尔值,指示连接是否应检索所有结果或仅一个,您还可以通过第三个参数指定结果的检索方式,默认为 PDO::FETCH_ASSOC
。
高级数据库功能
如果您想完全控制 \Alexya\Database\Connection
类,可以使用方法 \Alexya\Database\Connection::getConnection
,该方法返回当前的PDO对象及其数据库连接。
当发生错误时,方法 \Alexya\Database\Connection::getError
返回最新的错误
<?php $Database = new \Alexya\Database\Connection("localhost", 3306, "root", "", "alexya"); $users = $Database->query("SELECT FROM `users`"); if(empty($users)) { echo "An error happened!\n". $Database->getError(); }
您可以使用属性 \Alexya\Database\Connection::lastQuery
查看最后执行的查询
<?php $Database = new \Alexya\Database\Connection("localhost", 3306, "root", "", "alexya"); $users = $Database->query("SELECT FROM `users`"); echo "Last query: ". $Database->lastQuery; // Last query: SELECT FROM `users`
查询构建器
类 \Alexya\Database\QueryBuilder
提供了一种流畅的方式来生成查询。
构造函数接受一个参数,即包含数据库连接的 \Alexya\Database\Connection
对象。一旦生成了查询,您可以直接使用方法 execute
执行它,或者使用方法 getQuery
获取SQL。
如果您想使用同一个 \Alexya\Database\QueryBuilder
构建多个查询,则在每次完成一个查询后都使用方法 clear
。
选择查询
方法 \Alexya\Database\QueryBuilder::select
开始一个 SELECT
查询,并接受3种类型的参数:
- 无(与传递 "*" 作为参数相同)
- 一个包含要选择列名的字符串
- 一个包含要选择列的数组
<?php $query->select(); // SELECT * $query->select("name"); // SELECT `name` $query->select(["name", "password", "email"]); // SELECT `name`, `password`, `email`
接下来,我们必须指定我们将用于获取列的表,我们使用方法 \Alexya\Database\QueryBuilder::from
来完成,该方法接受一个字符串作为参数,包含表的名称
<?php $query->select() ->from("users"); // SELECT * FROM `users`
插入查询
方法 \Alexya\Database\QueryBuilder::insert
开始一个 INSERT
查询,并接受一个字符串作为参数,该字符串是要插入新记录的表的名称
<?php $query->insert("users"); // INSERT INTO `users`
接下来,我们需要向表中添加要插入的值,我们使用方法 \Alexya\Database\QueryBuilder::values
来完成,该方法接受一个数组作为参数,包含要插入到表中的值
<?php $query->insert("users") ->values([ "id" => 1, "name" => "test", "password" => "test", "email" => "test@test.test" ]); // INSERT INTO `users` (`id`, `name`, `password`, `email`) VALUES (1, 'test', 'test', 'test@test.test')
如果数组的索引是一个对象或数组,它将序列化它以将其转换为字符串。
<?php $query->insert("users") ->values([ "login_log" => ["date1", "date2", "date3"] ]); // INSERT INTO `users` (`login_log`) VALUES ('a:3:{i:0;s:5:"date1";i:1;s:5:"date2";i:2;s:5:"date3";}') $log = new LoginLog(); //Let's assume it exists and is the same as the array but in an object shape $query->insert("users") ->values([ "login_log" => $log ]); // INSERT INTO `users` (`login_log`) VALUES ('O:3:"Obj":3:{s:5:"date1";s:5:"date1";s:5:"date2";s:5:"date2";s:5:"date3";s:5:"date3";}')
或者您可以用JSON编码值
<?php $query->insert("users") ->values([ "(JSON)login_log" => ["date1", "date2", "date3"] ]); // INSERT INTO `users` (`login_log`) VALUES ('["date1","date2","date3"]') $log = new LoginLog(); //Let's assume it exists and is the same as the array but in an object shape $query->insert("users") ->values([ "(JSON)login_log" => $log ]); // INSERT INTO `users` (`login_log`) VALUES ('{"date1":"date1","date2":"date2","date3":"date3"}')
更新查询
方法 \Alexya\Database\QueryBuilder::update
开始一个 UPDATE
查询,并接受一个字符串作为参数,该字符串是要修改的表的名称
<?php /** * Load Alexya's core */ require_once("bootstrap.php"); $query = new \Alexya\Database\QueryBuilder(); $query->update("users"); // UPDATE `users`
现在我们必须设置要修改的值,我们使用方法 \Alexya\Database\QueryBuilder::set
来完成,该方法接受一个数组作为参数,包含要修改的值。您可以在键的末尾追加以下标签
- [+]
- [-]
- [*]
- [/]
您还可以像在INSERT
查询中一样序列化值。
<?php $query->update("users") ->set([ "name" => "test", "money[+]" => 2 ]); // UPDATE `users` SET `name`='test', `money`=(`money`+2)
删除查询
删除查询以方法\Alexya\Database\QueryBuilder::delete
开始,该方法接受一个作为字符串的表名作为参数
<?php $query->delete("users"); // DELETE FROM `users`
WHERE
语法
方法\Alexya\Database\QueryBuilder::where
启动WHERE
子句,并接受一个包含数组的参数
<?php $query->select() ->from("users") ->where([ "name" => "test" ]); // SELECT * FROM `users` WHERE `name`='test'
对于更复杂的情况,您可以使用以下标签
- [>]
- [>=]
- [!]
- [<>]
- [><]
<?php $query->where([ "id[>]" => 100 ]); // WHERE `id`>100 $query->where([ "id[>=]" => 100 ]); // WHERE `id`>=100 $query->where([ "id[!]" => 100 ]); // WHERE `id`!=100 $query->where([ "id[<>]" => [0, 1000] ]); // WHERE `id` BETWEEN 0 AND 1000 $query->where([ "id[><]" => [0, 1000] ]); // WHERE `id` NOT BETWEEN 0 AND 1000 $query->where([ "id" => [0, 1, 2, 3, 4, 5, 6] ]); // WHERE `id` IN(0,1,2,3,4,5,6) $query->where([ "id[!]" => [0, 1, 2, 3, 4, 5, 6] ]); // WHERE `id` NOT IN(0,1,2,3,4,5,6) $query->where([ "name" => NULL ]); // WHERE `name` IS NULL $query->where([ "name[!]" => NULL ]); // WHERE `name` IS NOT NULL
您还可以通过在键的开始处添加(JSON)
来序列化数据
<?php $query->where([ "(JSON)login_log" => ["date1", "date2", "date3"] ]); // WHERE `login_log`='["date1","date2","date3"]'
您还可以添加AND
和OR
语句
<?php $query->where([ "AND" => [ "OR" => [ "username" => "test", "email" => "test@test.test" ], "password" => "test" ] ]); // WHERE `username`='test' OR `email`='test@test.test' AND `password`='test'
其他SQL函数
\Alexya\Database\QueryBuilder
提供了3个其他用于SQL子句的方法
方法\Alexya\Database\QueryBuilder::limit
开始一个LIMIT
子句,可以接受一个整数或一个作为参数的数组
<?php $query->limit(1); //LIMIT 1 $query->limit([1, 10]); //LIMT 1, 10
方法\Alexya\Database\QueryBuilder::offset
开始一个OFFSET
子句,并接受一个整数作为参数
<?php $query->offset(10); //OFFSET 10
方法\Alexya\Database\QueryBuilder::sql
将原始SQL附加到查询中,此方法不会防止SQL注入,因此除非您知道自己在做什么,否则不建议使用
<?php $query->sql("SELECT * FROM users WHERE username='test'"); //SELECT * FROM users WHERE username='test'
ORM
类\Alexya\Database\ORM\Model
在数据库表和PHP代码之间充当调解者。
在开始之前,您应该使用方法initialize
初始化该类。它接受一个类型为\Alexya\Database\Connection
的对象作为参数,这是到数据库的连接,以及一个字符串,表示Model类所在的基命名空间,如果您希望将Model类存储在单独的命名空间中(默认为"")
<?php $connection = new Connection("localhost", 3306, "root", "", "alexya"); Model::initialize($connection, "\Application\ORM");
您应该为每个模型编写一个扩展此类的类,但在遵循命名约定的情况下,您可能会完成一个包含空类的包。为了防止这种情况,您可以使用方法instance
,该方法接受数据库表名称作为参数。此外,所有静态方法都将表名称作为最后一个参数。
扩展此类允许您对其有更多的控制。您可以指定表名、主键名、关系等。
默认情况下,表名是类的snake_case
复数名称,如果您想覆盖它,请将属性_table
更改为表名
<?php class UsersTable extends Model { protected $_table = "users"; // Without this, the table name would be `userstables`, see \Alexya\Database\ORM\Model::getTable }
默认情况下,主键是id
,如果您想覆盖它,请将属性_primaryKey
更改为主键名
<?php class UsersTable extends Model { protected $_primaryKey = "userID"; }
方法onInstance
在类被实例化时执行,请使用它而不是构造函数。
CRUD
CRUD代表创建、读取、更新、删除。
创建记录
要创建新记录,请调用方法\Alexya\Database\ORM\Model::create
<?php $user = UsersTable::create(); $user->id = 1; $user->name = "test"; $user->password = "test"; $user->email = "test@test.test"; $user->save();
读取记录
方法find
从数据库中查找记录并返回Model类的实例。它接受一个整数作为参数,表示主键的值,或一个包含查询WHERE
子句的数组
<?php $user = UsersTable::find(1); // SELECT * FROM `users` WHERE `usersID`=1 $otherUser = UsersTable::find([ "name" => "test" ]); // SELECT * FROM `users` WHERE `name`='test' if($user->name == $otherUser->name) { $user->name = "Test"; $user->save; $otherUser->id = 2; $otherUser->save; }
您可以发送第二个整数参数,表示从数据库中检索的记录数。如果省略,则返回单个记录,否则返回指定数量的记录的数组。
更新记录
一旦您有了ORM实例,您就可以使用方法\Alexya\Database\ORM\Model::get
和\Alexya\Database\ORM\Model::set
来更改列的值,它还提供了魔术方法 __get, __set, __isset 和 __unset以供选择语法。要更新数据库,请使用方法\Alexya\Database\ORM\Model::save
<?php $user = UsersTable::find(1); // SELECT * FROM `users` WHERE `usersID`=1 $user->name = "test"; $user->password = "test"; $user->save(); // UPDATE `users` SET `name`='test', `password`='test' WHERE `usersID`=1
删除记录
要删除记录,必须调用方法\Alexya\Database\ORM\Model::delete
<?php $user = UsersTable::find(1); // SELECT * FROM `users` WHERE `usersID`=1 $user->delete(); // DELETE FROM `users` WHERE `userID`=1
关系
关系是在共享某些共同点的两个表之间建立的。
\Alexya\Database\ORM\Model
类通过静态属性$_relations
提供了一个简单的方法来建立关系,该属性可以是公开的或受保护的,但不能是私有的。
属性 $_relations
是一个数组。该数组的每个索引都将被解释为一条关系规则。
规则可以是:
- 一个字符串,代表表示表或表的名称的 Model 类的类名(如果该类不存在)。
- 包含规则配置的数组。
如果索引是一个数组,则键必须是 Model 类的名称,并且可以有以下索引:
localKey
:用于关系的本地键的名称(默认为外键表名称后跟本地主键)。foreignKey
:用于关系的外键的名称(默认为外键主键)。type
:关系类型(has_one
或has_many
)(默认为has_one
)。name
:为结果关系创建的属性的名称(默认为类名)。amount
:要检索的关系记录的数量(默认为表中的所有记录)。class
:将实例化关系的类的名称(在has_one
关系的情况下默认为外键表的 Model 类,在has_many
关系的情况下默认为外键表的 Model 类的数组)。setRelations
:关系实例是否应处理其关系数组(默认为false
)。
Model 类的名称可以以发送给 initialize
方法的前缀开头,也可以是表名。
示例
<?php class User extends Model { protected static $_relations = [ "Message" => [ "type" => "has_many" ], "Configuration" ] }
此示例将创建两个关系
- 一个用于
messages
表。 - 一个用于
configurations
表。
对于 messages
表的关系,将加载与本地/外键关系匹配的数据库中的所有记录。
默认情况下,本地键是本地表名称后跟外键表的主键名称,外键是外键表的主键。
因此,假设 User
和 Messages
遵循此类的标准,则本地键将是 messages_id
,外键将是 id
,因此生成的查询将是 SELECT * FROM messages WHERE id=users.messages_id;。
要覆盖默认的本地键,请更改索引 localKey
;要覆盖默认的外键,请更改索引 foreignKey
。
由于消息有一个发送者和一个接收者,所以假设 messages
表上的 id
列是用户的 messages_id
是错误的,所以我们将其外键更改为更合适的一个: to_users_id
<?php class User extends Model { protected static $_relations = [ "Message" => [ "type" => "has_many", "foreignKey" => "to_users_id" ], "Configuration" ] }
默认情况下,所有关系都是 一对一
,这意味着只从数据库中获取一个。
由于用户可能有多个消息,我们通过更改值中的 type
索引来更改关系类型。
之后,我们可以通过属性 $user->Message
访问所有发送给用户的消息,这将是一个包含所有代表数据库记录的 Message
类的数组。
然而,将属性命名为 Message
并不是最佳选择,因为它不是一个单独的消息,而是一组消息。我们可以通过将 name
索引设置为不同的值来更改此名称。
<?php class User extends Model { protected static $_relations = [ "Message" => [ "type" => "has_many", "foreignKey" => "to_users_id", "name" => "Messages" ], "Configuration" ] }
现在所有消息都在 $user->Messages
属性中。
我们还想更改从数据库检索的记录数量以及实例化模型是否应处理其关系。我们可以通过分别更改索引 amount
和 setRelations
来完成此操作。
最后,我们可以决定是否只在用户验证了他的电子邮件后检索消息,我们通过索引 condition
来做这件事,它是一个闭包,应该返回一个布尔值,告诉关系是否应该被处理。
闭包必须接受一个数组作为参数,该数组将包含查询的结果。
<?php class User extends Model { protected static $_relations = [ "Message" => [ "type" => "has_many", "foreignKey" => "to_users_id", "name" => "Messages", "condition" => "User::canSetMessage" ], "Configuration" ] public static function canSetMessage(array $columns) : bool { // Check that the user has verified his email if(!$this->email_isVerified) { return false; } // Check that the message isn't deleted by the user if($columns["to_isDeleted"]) { return false; } return true; } }