webvimark/pixie

PHP的一个轻量级、表达性、无框架的查询构建器。

2.1.17 2020-03-29 09:30 UTC

README

该项目目前未积极维护,但大多数功能都完全正常工作,没有重大安全问题,只是我没有花太多时间。

Pixie 查询构建器 Build Status

Pixie 是一个轻量级、表达性、无框架的 PHP 查询构建器,也可以称为数据库抽象层。Pixie 支持 MySQL、SQLite 和 PostgreSQL,它使用统一的 API 处理查询净化、表前缀等许多事情。至少需要 PHP 5.3。

它具有一些高级功能,如:

  • 查询事件
  • 嵌套条件
  • 子查询
  • 嵌套查询
  • 多个数据库连接。

语法与 Laravel 的查询构建器非常相似。

示例

// Make sure you have Composer's autoload file included
require 'vendor/autoload.php';

// Create a connection, once only.
$config = array(
            'driver'    => 'mysql', // Db driver
            'host'      => 'localhost',
            'database'  => 'your-database',
            'username'  => 'root',
            'password'  => 'your-password',
            'charset'   => 'utf8', // Optional
            'collation' => 'utf8_unicode_ci', // Optional
            'prefix'    => 'cb_', // Table prefix, optional
            'options'   => array( // PDO constructor options, optional
                PDO::ATTR_TIMEOUT => 5,
                PDO::ATTR_EMULATE_PREPARES => false,
            ),
        );

new \Pixie\Connection('mysql', $config, 'QB');

简单查询

以下查询返回 id = 3 的行,如果没有行则返回 null。

$row = QB::table('my_table')->find(3);

完整查询

$query = QB::table('my_table')->where('name', '=', 'Sana');

// Get result
$query->get();

查询事件

以下代码之后,每次在 users 表上发生 select 查询时,都会添加此 where 条件,因此被禁止的用户无法访问。

QB::registerEvent('before-select', 'users', function($qb)
{
    $qb->where('status', '!=', 'banned');
});

以下是许多高级选项的文档。想安装吗?让我们开始吧。

安装

Pixie 使用 Composer 使事情变得简单。

学习如何使用 Composer 并将其添加到 require 部分(在您的 composer.json 中)

"webvimark/pixie": "2.*@dev"

并运行

composer update

库在 Packagist 上。

完整使用 API

目录

连接

Pixie 支持三种数据库驱动程序,MySQL、SQLite 和 PostgreSQL。您可以在创建新连接时指定驱动程序及其相关配置。您还可以创建多个连接,但一次只能使用一个别名。

// Make sure you have Composer's autoload file included
require 'vendor/autoload.php';

$config = array(
            'driver'    => 'mysql', // Db driver
            'host'      => 'localhost',
            'database'  => 'your-database',
            'username'  => 'root',
            'password'  => 'your-password',
            'charset'   => 'utf8', // Optional
            'collation' => 'utf8_unicode_ci', // Optional
            'prefix'    => 'cb_', // Table prefix, optional
        );

new \Pixie\Connection('mysql', $config, 'QB');

// Run query
$query = QB::table('my_table')->where('name', '=', 'Sana');

别名

创建连接时

new \Pixie\Connection('mysql', $config, 'MyAlias');

MyAlias 是您想要使用的类别名的名称(如 MyAlias::table(...)),您可以使用任何名称(包括命名空间,例如 MyNamespace\\MyClass),也可以跳过它,如果您不需要别名。别名使您能够轻松地在应用程序中访问 QueryBuilder 类。

如果不使用别名,您可以单独实例化 QueryBuilder 处理器,这对于依赖注入和测试很有帮助。

$connection = new \Pixie\Connection('mysql', $config);
$qb = new \Pixie\QueryBuilder\QueryBuilderHandler($connection);

$query = $qb->table('my_table')->where('name', '=', 'Sana');

var_dump($query->get());

$connection在这里是可选的,如果没有提供,它将始终关联到第一个连接,但在有多个数据库连接时可能会很有用。

SQLite 和 PostgreSQL 配置示例

new \Pixie\Connection('sqlite', array(
                'driver'   => 'sqlite',
			    'database' => 'your-file.sqlite',
			    'prefix'   => 'cb_',
		    ), 'QB');
new \Pixie\Connection('pgsql', array(
                    'driver'   => 'pgsql',
                    'host'     => 'localhost',
                    'database' => 'your-database',
                    'username' => 'postgres',
                    'password' => 'your-password',
                    'charset'  => 'utf8',
                    'prefix'   => 'cb_',
                    'schema'   => 'public',
                ), 'QB');

查询

除了原始的query()之外,在每次查询之前都必须使用table()方法。要从多个表中选择,只需传递一个数组。

QB::table(array('mytable1', 'mytable2'));

轻松获取

下面的查询返回id = 3的(第一)行,如果没有行则返回null。

$row = QB::table('my_table')->find(3);

像这样访问行,echo $row['name']。如果你的字段名不是id,则将字段名作为第二个参数传递,例如:QB::table('my_table')->find(3, 'person_id');

下面的查询返回所有name = 'Sana'的行,如果没有行则返回null。

$result = QB::table('my_table')->findAll('name', 'Sana');

选择

$query = QB::table('my_table')->select('*');

多选

->select(array('mytable.myfield1', 'mytable.myfield2', 'another_table.myfield3'));

使用多次select方法select('a')->select('b')也会选择ab。如果你想在PHP if中做条件选择,这可能会很有用。

选择不同

->selectDistinct(array('mytable.myfield1', 'mytable.myfield2'));

获取所有

返回一个数组。

$query = QB::table('my_table')->where('name', '=', 'Sana');
$result = $query->get();

你可以像这样循环遍历它

foreach ($result as $row) {
    echo $row['name'];
}

映射结果

$result = QB::table('my_table')
    ->where('age', '>', '3')
    ->map(function($item) {
        $item['age'] = "{$item['name']} is {$item['age']} years old";
        return $item;
    })
    ->get()

获取第一行

$query = QB::table('my_table')->where('name', '=', 'Sana');
$row = $query->first();

返回第一行,如果没有记录则返回null。使用此方法,你还可以确保记录是否存在。像这样访问:echo $row['name']

获取行数

$query = QB::table('my_table')->where('name', '=', 'Sana');
$query->count();

获取列

$firstColumn = QB::table('my_table')->getColumn(); // ['1','2','3','4'] - first column is usually ids
$specificColumn = QB::table('my_table')->getColumn('name'); // ['John','Sana','Victor','Mark']

获取标量

$firstColumnValue = QB::table('my_table')->getScalar(); // '1'
$specificColumnValue = QB::table('my_table')->getScalar('name'); // 'John'

Where

基本语法是(字段名,操作符,值),如果你提供两个参数,则假定操作符是=。因此,where('name', 'usman')where('name', '=', 'usman')是相同的。

QB::table('my_table')
    ->where('name', '=', 'usman')
    ->whereNot('age', '>', 25)
    ->orWhere('type', '=', 'admin')
    ->orWhereNot('description', 'LIKE', '%query%')
    ;

Where In

QB::table('my_table')
    ->whereIn('name', array('usman', 'sana'))
    ->orWhereIn('name', array('heera', 'dalim'))
    ;

QB::table('my_table')
    ->whereNotIn('name', array('heera', 'dalim'))
    ->orWhereNotIn('name', array('usman', 'sana'))
    ;

Where Between

QB::table('my_table')
    ->whereBetween('id', 10, 100)
    ->orWhereBetween('status', 5, 8);

Where Null

QB::table('my_table')
    ->whereNull('modified')
    ->orWhereNull('field2')
    ->whereNotNull('field3')
    ->orWhereNotNull('field4');

分组 Where

有时查询会变得复杂,需要分组条件,例如WHERE age = 10 and (name like '%usman%' or description LIKE '%usman%')

Pixie允许你这样做,你可以嵌套任意数量的闭包,如下所示。

QB::table('my_table')
            ->where('my_table.age', 10)
            ->where(function($q)
                {
                    $q->where('name', 'LIKE', '%usman%');
                    // You can provide a closure on these wheres too, to nest further.
                    $q->orWhere('description', 'LIKE', '%usman%');
                });

Group By 和 Order By

$query = QB::table('my_table')->groupBy('age')->orderBy('created_at', 'ASC');

多个Group By

->groupBy(array('mytable.myfield1', 'mytable.myfield2', 'another_table.myfield3'));

->orderBy(array('mytable.myfield1', 'mytable.myfield2', 'another_table.myfield3'));

使用多次groupBy()orderBy()方法groupBy('a')->groupBy('b')也会先按a分组,然后按b分组。如果你想在PHP if中做条件分组,这可能会很有用。同样适用于orderBy()

Having

->having('total_count', '>', 2)
->orHaving('type', '=', 'admin');

Limit 和 Offset

->limit(30);

->offset(10);

连接

QB::table('my_table')
    ->join('another_table', 'another_table.person_id', '=', 'my_table.id')

可用方法

  • join()或innerJoin
  • leftJoin()
  • rightJoin()

如果你需要FULL OUTER连接或其他连接,只需将其作为join方法的第五个参数传递。

->join('another_table', 'another_table.person_id', '=', 'my_table.id', 'FULL OUTER')

多个连接条件

如果你需要多个条件来连接表,则将闭包作为第二个参数传递。

->join('another_table', function($table)
    {
        $table->on('another_table.person_id', '=', 'my_table.id');
        $table->on('another_table.person_id2', '=', 'my_table.id2');
        $table->orOn('another_table.age', '>', QB::raw(1));
    })

原始查询

如果你需要,你始终可以使用原始查询

$query = QB::query('select * from cb_my_table where age = 12');

var_dump($query->get());

你也可以传递你的绑定

QB::query('select * from cb_my_table where age = ? and name = ?', array(10, 'usman'));

原始表达式

当你用raw()方法包裹一个表达式时,Pixie不会尝试清理这些。

QB::table('my_table')
            ->select(QB::raw('count(cb_my_table.id) as tot'))
            ->where('value', '=', 'Ifrah')
            ->where(QB::raw('DATE(?)', 'now'))

注意:通过query()方法运行的查询在传递所有值通过绑定之前不会被清理。通过raw()方法运行的查询也不会被清理,你必须自己来做。当然,这些也不会添加表前缀,但你可以使用addTablePrefix()方法。

插入

$data = array(
    'name' => 'Sana',
    'description' => 'Blah'
);
$insertId = QB::table('my_table')->insert($data);

insert()方法返回插入ID。

批量插入

$data = array(
    array(
        'name'        => 'Sana',
        'description' => 'Blah'
    ),
    array(
        'name'        => 'Usman',
        'description' => 'Blah'
    ),
);
$insertIds = QB::table('my_table')->insert($data);

在批量插入的情况下,它将返回一个包含插入ID的数组。

使用 ON DUPLICATE KEY 语句插入

$data = array(
    'name'    => 'Sana',
    'counter' => 1
);
$dataUpdate = array(
    'name'    => 'Sana',
    'counter' => 2
);
$insertId = QB::table('my_table')->onDuplicateKeyUpdate($dataUpdate)->insert($data);

更新

$data = array(
    'name'        => 'Sana',
    'description' => 'Blah'
);

QB::table('my_table')->where('id', 5)->update($data);

将名称字段更新为Sana,将描述字段更新为Blah,其中id = 5。

删除

QB::table('my_table')->where('id', '>', 5)->delete();

将删除id大于5的所有行。

事务

Pixie具有运行数据库“事务”的能力,在事务中,所有数据库更改在提交之前都不会保存。这样,如果出现错误或不同的情况,则不会保存数据库更改,也不会进行更改。

下面是一个基本的事务示例

QB::transaction(function ($qb) {
    $qb->table('my_table')->insert(array(
        'name' => 'Test',
        'url' => 'example.com'
    ));

    $qb->table('my_table')->insert(array(
        'name' => 'Test2',
        'url' => 'example.com'
    ));
});

如果这会导致任何错误(例如,重复的名称或其他此类错误),则数据库中不会显示任何数据集。如果没有错误,则更改将成功保存。

如果你希望手动提交或回滚你的更改,你可以相应地使用commit()rollback()方法。

QB::transaction(function ($qb) {
    $qb->table('my_table')->insert(array(/* data... */));

    $qb->commit(); // to commit the changes (data would be saved)
    $qb->rollback(); // to rollback the changes (data would be rejected)
});

获取构建的查询

有时你可能需要获取查询字符串,这是可能的。

$query = QB::table('my_table')->where('id', '=', 3);
$queryObj = $query->getQuery();

getQuery()将返回一个查询对象,从该对象你可以获取SQL、绑定或原始SQL。

$queryObj->getSql();
// Returns: SELECT * FROM my_table where `id` = ?
$queryObj->getBindings();
// Returns: array(3)
$queryObj->getRawSql();
// Returns: SELECT * FROM my_table where `id` = 3

子查询和嵌套查询

很少,但你可能需要执行子查询或嵌套查询。Pixie足够强大,可以为你做到这一点。你可以创建不同的查询对象,并使用QB::subQuery()方法。

$subQuery = QB::table('person_details')->select('details')->where('person_id', '=', 3);


$query = QB::table('my_table')
            ->select('my_table.*')
            ->select(QB::subQuery($subQuery, 'table_alias1'));

$nestedQuery = QB::table(QB::subQuery($query, 'table_alias2'))->select('*');
$nestedQuery->get();

这将生成如下查询

SELECT * FROM (SELECT `cb_my_table`.*, (SELECT `details` FROM `cb_person_details` WHERE `person_id` = 3) as table_alias1 FROM `cb_my_table`) as table_alias2

注意:Pixie不使用绑定进行子查询或嵌套查询。它使用PDO的quote()方法引用值。

获取 PDO 实例

如果您需要获取PDO实例,可以这样做。

QB::pdo();

以指定类的对象形式获取结果

只需调用查询方法的asObject

QB::table('my_table')->asObject('SomeClass', array('ctor', 'args'))->first();

此外,您还可以通过调用setFetchMode方法来微调获取模式。

QB::table('my_table')->setFetchMode(PDO::FETCH_COLUMN|PDO::FETCH_UNIQUE)->get();

查询事件

Pixie提供了强大的查询事件,以增强您的应用程序。这些事件类似于数据库触发器,您可以在事件发生时执行某些操作,例如,您可以将表的after-delete事件钩子,并从另一个表中删除相关数据。

可用事件

  • before-select
  • after-select
  • before-insert
  • after-insert
  • before-update
  • after-update
  • before-delete
  • after-delete

注册事件

QB::registerEvent('before-select', 'users', function($qb)
{
    $qb->where('status', '!=', 'banned');
});

现在每次在users表上执行选择查询时,都会添加这个where条件,因此被禁止的用户无法访问。

语法是registerEvent('事件类型', '表名',闭包中的操作)

如果想要事件在任何表被查询时执行,请将':any'作为表名提供。

其他示例

my_table中插入数据后,将详情插入另一个表

QB::registerEvent('after-insert', 'my_table', function($queryBuilder, $insertId)
{
    $data = array('person_id' => $insertId, 'details' => 'Meh', 'age' => 5);
    $queryBuilder->table('person_details')->insert($data);
});

每次向person_details表插入数据时,设置时间戳字段created_at,这样我们就不需要在每个地方都指定它

QB::registerEvent('after-insert', 'person_details', function($queryBuilder, $insertId)
{
    $queryBuilder->table('person_details')->where('id', $insertId)->update(array('created_at' => date('Y-m-d H:i:s')));
});

my_table中删除后删除关系

QB::registerEvent('after-delete', 'my_table', function($queryBuilder, $queryObject)
{
    $bindings = $queryObject->getBindings();
    $queryBuilder->table('person_details')->where('person_id', $binding[0])->delete();
});

Pixie将查询构建器的当前实例作为闭包的第一个参数传递给您,这样您就可以使用此对象构建查询,您可以像通常的查询构建器(QB)一样做任何事情。

如果从before-*查询处理器返回的不是null,则值将是执行的结果,并且实际上不会查询数据库(因此,相应的after-*处理器也不会被调用)。

只有在after-*事件中,您才能得到三个参数:第一个是查询构建器,第三个是执行时间作为浮点数,而第二个是变化的。

  • after-select中,您将获得从select获得的results
  • after-insert中,您将获得插入ID(或批量插入的情况下的ID数组)。
  • after-delete中,您将获得查询对象(与从getQuery()获得的相同),您可以从中获取SQL和绑定。
  • after-update中,您将获得与after-delete相同的查询对象

删除事件

QB::removeEvent('event-name', 'table-name');

一些用例

以下是查询事件可以非常有用的场景

  • 限制被禁止的用户。
  • 只获取deleted = 0的记录。
  • 实现所有查询的缓存。
  • 在每次条目后触发用户通知。
  • 在删除查询后删除关系数据。
  • 在插入查询后插入关系数据。
  • 在每次更新查询后记录修改。
  • 在每次条目后添加/编辑created_atupdated_at数据。

注意

  • 查询事件按连接设置,因此多个数据库连接不会造成任何问题,并且创建新的查询构建器实例将保留您的事件。
  • 查询事件是递归的,例如,在向table_a插入数据后,您的事件会插入到table_b,现在您可以在table_b上注册另一个事件,该事件会插入到table_c
  • 当然,查询事件不与原始查询一起使用。

如果您发现任何错误,请编辑并发送pull request。

© 2016 Muhammad Usman。根据MIT许可证授权。