cradlephp / storm
一种SQL客户端的搜索集合模型
Requires
- cradlephp/components: ~2.3.0
Requires (Dev)
- php-coveralls/php-coveralls: ~2.1.0
- phpunit/phpunit: 7.0.2
- squizlabs/php_codesniffer: 3.2.3
README
一种SQL客户端的搜索集合模型
安装
composer install cradlephp/storm
简介
欢迎使用Storm,一种不同的ORM。从底层查询到搜索、集合和模型的完整功能。适用于初学者和爱好者。
图1. 使用方法
$resource = new PDO('mysql:host=127.0.0.1;dbname=test_db', 'root', ''); $database = new Cradle\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中描述的3种方式之一检索结果。
图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()
时,你需要指定要保存到的表。这个方法非常强大,因为它会首先检查表中存在的列,然后与你的模型进行比较。它只会保存对象中具有匹配列名的列。最后,它会自动确定是否应该插入或更新该行。
一个常见的例子是当你有一个由连接数据组成的数组表时。你可以保持那个数组不变,然后像在图14中那样为每个表调用save()
。
图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');