phpugph / storm
PHP8 SCM SQL 客户端
Requires
- phpugph/components: 1.0.8
Requires (Dev)
- php-coveralls/php-coveralls: 2.6.0
- phpunit/phpunit: 10.3.3
- squizlabs/php_codesniffer: 3.7.2
README
PHP8 SCM SQL 客户端
安装
composer install phpugph/storm
简介
欢迎来到 Storm,一种不同类型的 ORM。从底层查询到搜索、集合和模型的完整功能。适用于初学者和爱好者。
图 1. 使用
$resource = new PDO('mysql:host=127.0.0.1;dbname=test_db', 'root', ''); $database = new Storm\SqlFactory($resource);
这种模式允许 Storm 更容易与其他 ORM 平台协同工作。 SqlFactory
将确定使用哪种数据库类。目前支持 MySQL、MariaDB、PostGreSQL 和 SQLite(如果您希望看到其他 SQL 类型数据库的支持,请创建一个问题)。
基本查询
在非常低级别上,可以像图 2 所示调用原始查询。
图 2. 原始查询
$database->query('SELECT * FROM user'); // returns results of raw queries
建议您绑定来自最终用户的输入变量。仍然在低级别上,这可以在 图 3
中实现。
图 3. 原始绑定
$query = 'SELECT * FROM user WHERE user_name LIKE :user_name AND user_active = :user_active'; $bind = array(':user_name' => '%'.$userName.'%', ':user_active' => 1); $database->query($query, $bind); // returns results of raw queries
上述图将 $query
设置为一个包含绑定占位符 :user_name
和 :user_active
的字符串。 $bind
包含这些占位符的实际值,应在执行查询时替换。我们鼓励这种方法,因为绑定值可以防止数据库注入。
注意:绑定变量必须以冒号(:)开头。
数据操作
如果您更喜欢包装方式来保存数据,图 4 提供了几个方法示例来说明如何实现。
图 4. 数据操作
$settings = [ 'user_name' => 'Chris' 'user_email' => 'myemail@mail.com' ]; $filter[] = ['user_id=%s', 1]; // inserts row into 'user' table $database->insertRow('user', $settings); // updates rows in 'user' table where user_id is $database->updateRows('user', $settings, $filter); // delete rows in 'user' table where user_id is 1 $database->deleteRows('user', $filter);
插入数据相当简单。我们包含了两种插入数据的方式。就像 getRow() 一样,没有必要担心绑定数据,因为 Storm 会为您处理。图 4 展示了提到的两种插入方式。
图 4. 两种插入方式
$settings = ['user_name' => 'Chris', 'user_email' => 'myemail@mail.com']; // insert row into 'user' table $database->insertRow('user', $settings); $settings = []; $settings[] = ['user_name' => 'Chris', 'user_email' => 'myemail@mail.com']; $settings[] = ['user_name' => 'Dan', 'user_email' => 'myemail2@mail.com']; $settings[] = ['user_name' => 'Clark', 'user_email' => 'myemail3@mail.com']; // insert multiple rows into 'user' table $database->insertRows('user', $settings);
因此,如果您只想插入一行,应使用 insertRow()
。同时插入两行或多行,您应使用 insertRows()
。此方法期望一个数组数组的数组或数组表。
注意:一个常见的错误是使用
insertRows()
而不是insertRow()
。
注意:使用模型和集合时,您实际上不必担心此方法,因为它包含在集合或模型对象的
save()
方法中。我们将在本节稍后介绍模型和集合。
更新与插入几乎一样简单。您只需要了解一种方法。
图 5. 更新
$settings = ['user_name' => 'Chris', 'user_email' => 'myemail@mail.com']; $filter[] = ['user_id=%s', 1]; // update row into 'user' table $database->updateRows('user', $settings, $filter);
一个常见场景是当您需要插入如果列值不存在,如果存在则更新。我们添加了一个名为 setRow()
的额外方法,以简化冗余的代码。
图 6. 插入或更新
$settings = ['user_name' => 'Chris2', 'user_email' => 'myemail@mail.com']; $database->setRow('user', 'user_email', 'myemail@mail.com', $settings);
图 6
基本上表示,在用户表中,如果 myemail@mail.com
存在于 user_email
列中,则更新该行。如果不存在,则插入。
图 7. 删除
$filter[] = ['user_id=%s', 1]; // delete rows in 'user' table where user_id is 1 $database->deleteRows('user', $filter);
搜索
使用搜索对象构建复杂查询的更好方法是。一个概述示例可以在 图 8
中找到。
图 8. MySQL 搜索
$database ->search('user') ->setColumns('*') ->innerJoinOn('group', 'group_owner=user_id') ->leftJoinUsing('friends', 'user_id') ->filterByUserName('Chris') ->addFilter("user_last LIKE '%s%%'", 'Brown') ->sortByUserId('ASC') ->addSort('user_last', 'DESC') ->setRange(25) ->setStart(75) ->getRows();
在上面的图中,有一些方法被魔法激活,但我们将逐一介绍。首先,要实例化搜索对象,只需调用search()
函数,并传入表名作为参数。其次,我们调用setColumns()
。这个调用是可选的,但如果使用,可以接受一个列数组或以空格分隔的列参数,例如:setColumns('user_id', 'user_name')
。接下来,innerJoinOn()
是我们接受连接的新方法。有八种方法专门用于不同类型的连接。
连接方法类型
innerJoinOn() innerJoinUsing() leftJoinOn() leftJoinUsing() rightJoinOn() rightJoinUsing() outerJoinOn() outerJoinUsing()
无论您选择哪种方法,您都需要添加两个参数。第一个参数是您想要连接的表的名称,第二个参数是它们之间的关系。
第一个被魔法激活的方法是filterByUserName()
。库中实际上没有名为filterByUserName()
的方法。相反,当调用此函数时,它将解析方法名称,并识别出UserName是列名,并将其转换为addFilter('user_name=%s', 'Chris')
,如图8
所示。
addFilter()
通常接受两个参数。第一个参数是过滤条件。如果您注意在图8
中的过滤示例,我们使用%s来分隔绑定的值。您可以为每个过滤条件有任意多的绑定值。后面的参数需要按照它们在过滤条件中出现的顺序包含绑定值。
第二个被魔法激活的方法是sortByUserId('ASC')
。库中实际上没有名为sortByUserId('ASC')
的方法。相反,当调用此函数时,它将解析方法名称,并识别出UserId是列名,并将其转换为addSort('user_id', 'ASC')
,如图8
所示。
还有三种分页方法可供使用
分页方法
$database->setRange(75); $database->setStart(25); $database->setPage(1);
如果您打算使用setPage(1)
,则首先调用setRange(75)
是很重要的,因为底层函数只是根据范围计算起始索引。另外两个未在图8
中涉及的方法是分组和设置表为其他内容。
图9. 其他有用方法
$database->->setTable('user'); $database->groupBy('user_active');
获取结果
当您对查询满意时,您可以通过如图0所示的三种方式之一检索结果。
图10. 获取结果
$database->getTotal(); $database->getRows(); $database->getCollection();
图10
显示了三种获取结果的方式,第一种方式getTotal()
将检索总数,不考虑分页元素。getRows()
将简单地返回一个原始数组。getCollection()
将返回一个对象,其中包含结果以供进一步操作。
集合
集合与模型执行完全相同的功能,除了它操作多个模型而不是一个。集合是可迭代的,也可以作为数组访问。集合仅持有模型对象,因此如果您想使用自己的扩展模型,则需要调用setModel('Your_Model')
。
图11. MySQL 集合
//set user name for all rows $collection->setUserName('Chris'); // set or get any abstract key for all rows $collection->setAnyThing() //collections are iterable foreach($collection as $model) { echo $model->getUserName().' '; echo $model['user_email']; } //access as array echo $collection[0]['user_name']; //set as array $collection[0]['user_email'] = 'my@email.com'; //save to 'user' table in database //only relevant columns will be saved //for all rows $collection->save('user', $database);
一些其他未在上例中涵盖的实用方法包括日期格式化和将一个列的值复制到另一个列。图12
展示了如何进行这些操作。
图12. 实用方法
//formats a date column $collection->formatTime('post_created', 'F d, y g:ia'); //for each row, copy the value of post_user to the user_id column $collection->copy('post_user', 'user_id'); //remove the row with the index of 1, reindexes all the rows $collection->cut(1); //returns the number of rows $collection->count(); //adds a new row $collection->add(['post_title' => 'Hi']); //returns a table array (no objects) $collection->get();
模型
我们设法定义了模型,这消除了普通ORM的限制性,并最终实现了可伸缩性。首先,我们定义了一个通用且功能强大的模型类,它可以被扩展,也可以直接使用。我们的模型类已经足够强大,可以解决许多用例,您可能不需要扩展它。我们探讨了“松散定义”的概念,并得出了以下结论。
图13. 数据库模型(扩展数组)
$model->setUserName('Chris'); //set user name $model->getUserEmail(); // returns user email //$model->setAnyThing() // set or get any abstract key echo $model['user_name']; //access as array $model['user_email'] = 'my@email.com'; //set as array echo $model->user_name; //access as object $model->user_name = 'my@email.com'; //set as object //save to 'user' table in database //only relevant columns will be saved $model->save('user', $database);
因此,可以通过方法、对象或数组访问模型属性。具体使用哪种方式由您决定。在我们的模型中,您可以在对象中添加额外的键值,即使它与目标数据库表无关。当您调用save()
时,您需要指定要保存到的表。这个方法非常强大,因为它会首先检查表中存在的列,然后与您的模型进行比较。它只保存对象中具有匹配列名的列。最后,它会自动确定是否插入或更新该行。
一个常见的例子是,当您有一个包含关联数据的数组表时。您可以保持数组不变,然后对每个表调用save()
,如图14
所示。
图14. 两个表
$row = [ 'user_id' => 1, 'user_name' => 'Chris', 'user_email' => 'my@email.com', 'post_user' => 1, 'post_title' => 'My Post', 'post_detail' => 'This is my new article' ]; $database->model($row)->save('user')->save('post');
注意:您也可以保存到不同的数据库,如
save('post', $db2)
整合一切
所以,一个常见的场景是从数据库检索数据,操作结果,并将其发送回数据库。让我们看看通过搜索、集合和模型对象,我们如何实现这一点。
图15. 最酷的事情!
//load database $database //search user table ->search('user') //WHERE user_gender = $_get['gender'] ->filterByUserGender($_GET['gender']) //ORDER BY user_id ->sortByUserId('ASC') //LIMIT 75, 25 ->setStart(75)->setRange(25) //get a collection object ->getCollection() //sets all users to active ->setUserActive(1) //Set a new column post_title ->setPostTitle('A '.$_GET['gender'].'\'s Post') //Set a new column post_detail ->setPostDetail('Content is King') //Copy the contents of user_id to a new column post_user ->copy('user_id', 'post_user') //Set a new column post_created ->setPostCreated(time()) ->formatTime('post_created', 'Y-m-d H:i:s') //save to user table ->save('user') //save to post table ->save('post');