n0nag0n/super-model

这是一个简单的基模型,你可以扩展它来减少硬编码的SQL查询,并且可以快速完成90%的数据库请求。

v0.1.0 2020-05-05 14:50 UTC

This package is auto-updated.

Last update: 2024-09-13 06:27:55 UTC


README

Dependencies Build Status codecov contributions welcome HitCount

更新

强烈建议您使用 flightphp/active-record 而不是这个库。将不会对此库进行进一步的开发,但它目前是可以正常工作的。

Super Model

Super model是一个非常简单的ORM类型的PHP类,可以轻松地与数据库中的表交互,而不需要编写大量的SQL代码。

为了证明这一点,以下是代码行...

$ cloc src/
       1 text file.
       1 unique file.                              
       0 files ignored.

github.com/AlDanial/cloc v 1.74  T=0.01 s (71.8 files/s, 48768.5 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
PHP                              1             86            246            347
-------------------------------------------------------------------------------

这是在性能方面考虑而编写的。所以,虽然它可能无法满足每一个项目的要求,但它将在大多数情况下工作,并且会做得非常好!

基本用法

开始使用Super Model非常简单,只需扩展super model类并定义一个表名。就是这样。

<?php
use n0nag0n\Super_Model;
class User extends Super_Model {
	protected $table = 'users';
}

那么,让我们看看一些简单的例子来了解它是如何工作的?

首先,让我们假设以下表

Table: users
---------------------------------------------------------
| id		| email			| company_id	| 
| 1		| hi@example.com	| 50		|
| 2		| another@example.com	| 61		|
| 3		| whatever@example.com	| 61		|
---------------------------------------------------------
<?php
// somefile.php

$pdo = new PDO('sqlite::memory:', '', '', [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]);

$User = new User($pdo);

// WHERE company_id = 50
$users = $User->getAllBycompany_id(50);

// same as above
$users = $User->getAll([ 'company_id' => 50 ]);

简单易懂,对吧?

文档

getBy*(mixed $value): array [结果]

这是一个返回指定值的一行的方法。方法中的 * 部分指的是数据库中的一个字段。字段名称与您的数据库表中的字段名称大小写敏感。

// get by the id field on the users table
$User->getByid(3);

/* 
	[ 
		'id' => 3,
		'email' => 'whatever@example.com',
		'company_id' => 61
	]
*/
$User->getBycompany_id(61);
/* 
	// it only will pull the first row, not all rows
	[ 
		'id' => 2,
		'email' => 'another@example.com',
		'company_id' => 61
	]
*/

getAllBy*(mixed $value): array [ [结果], [结果] ]

这是一个通过给定值返回所有行的快捷过滤器。方法中的 * 部分指的是数据库中的一个字段。字段名称与您的数据库表中的字段名称大小写敏感。

// this is pointless, but will still work
$User->getAllByid(3);

/* 
	[
		[ 
			'id' => 3,
			'email' => 'whatever@example.com',
			'company_id' => 61
		]
	]
*/
$User->getAllBycompany_id(61);
/* 
	[
		[ 
			'id' => 2,
			'email' => 'another@example.com',
			'company_id' => 61
		],
		[ 
			'id' => 3,
			'email' => 'whatever@example.com',
			'company_id' => 61
		]
	]
*/

getAll(array $filters, bool $return_one_row = false): array [ [结果], [结果] ] 或 [结果]

这是一个可以添加大量自定义以过滤表数据的过滤器。请注意一些独特的键和一些帮助您提取特定数据的操作符。

// Full example
$filters = [

	//
	// arguments in the WHERE statement
	//
	'some_field' => 5, // some_field = ?
	'some_field-=' => 5, // some_field = ?
	'another_field' => 'IS NULL', // some_field IS NULL
	'another_field' => 'IS NOT NULL', // some_field IS NOT NULL
	'another_field->' => 'Apple', // another_field > ?
	'another_field->=' => 'Apple', // another_field >= ?
	'another_field-<' => 'Apple', // another_field < ?
	'another_field-<=' => 'Apple', // another_field <= ?
	'another_field-!=' => 'Apple', // another_field != ?
	'another_field-<>' => 'Apple', // another_field <> ?
	'another_field-LIKE' => 'Ap%ple', // another_field LIKE ?
	'another_field-NOT LIKE' => 'Apple%', // another_field NOT LIKE ?
	'another_field-IN' => [ 'Apple', 'Banana', 'Peach' ], // another_field IN(??) double question mark gets parsed as array
	'another_field-NOT IN' => [ 'Apple', 'Banana', 'Peach' ], // another_field NOT IN(??) double question mark gets parsed as array

	// If you need some custom action
	'another_field-RAW-> DATE_SUB(?, INTERVAL 1 DAY)' => '1980-01-01', // another_field > DATE_SUB(?, INTERVAL 1 DAY)

	//
	// Other parts of the query
	//

	// choose what columns you want to select
	'select_fields' => 'id, first_name',

	// Get any joins
	'joins' => [ 'LEFT JOIN companies ON companies.id = users.company_id' ],

	// Group by
	'group_by' => 'company_id',

	// having
	'having' => 'count > 5',

	// order by
	'order_by' => 'id DESC',

	// limit
	'limit' => 15,

	// offset
	'offset' => 10000,
];

$users = $User->getAll($filters);

模型属性还有一些基本的配置选项。

$disallow_wide_open_queries

如果您有一个始终返回小结果集的模型,并希望能够查询整个表,则设置此属性。否则,它是一个保护措施,以防止在没有提供SQL参数的情况下检索整个结果集(这可能会破坏许多东西)。

use n0nag0n\Super_Model;
class User extends Super_Model {
	protected $table = 'users';
	protected $disallow_wide_open_queries = false;
}

create(array $data): int [插入ID]

这将向表中插入一行,但如果您提供多维数组,则可以插入多行。假定主键为 id

$User->create([ 'email' => 'onemore@example.com', 'company_id' => 55 ]);
// returns 4

$User->create([ [ 'email' => 'ok@example.com', 'company_id' => 55 ], [ 'email' => 'thanks@example.com', 'company_id' => 56 ] ]);
// returns 6, only the last id will be returned

update(array $data, string $update_field = 'id'): int (更新的行数)

这将向表中插入一行,但如果您提供多维数组,则可以插入多行。假定主键为 id

$User->update([ 'id' => 1, 'email' => 'whoneedsemail@example.com' ]);
// returns 1 and will only update the email field

$User->update([ 'email' => 'whoneedsemail@example.com', 'company_id' => 61 ], 'email');
// returns 1

$User->update([ 'company_id' => 61, 'email' => 'donotreply@example.com' ], 'company_id');
// returns 3, not really logical, but it would update all the emails

常见问题解答(高级用法)

如果您想自动修改特定标志被触发时的结果,怎么办? 很简单。有一个名为 processResult() 的方法会遍历您检索到的每个结果。您可以在 $filters['processResults'] 键中注入特殊的过滤器。

<?php
	use n0nag0n\Super_Model;
	class User extends Super_Model {
		protected $table = 'users';

		public processResult(array $process_filters, array $result): array {

			// add some trigger here and do whatever checks you need
			if(isset($process_filters['set_full_name']) && $process_filters['set_full_name'] === true && !empty($result['first_name']) && !empty($result['last_name'])) {
				$result['full_name'] = $result['first_name'].' '.$result['last_name'];
			}

			return $result;
		}
	}

	// later on in some other file.
	$User = new User($pdo);

	// setting the processResults filter here is the key to connecting the getAll statement with your processResult method
	$users = $User->getAll([ 'company_id' => 51, 'processResults' => [ 'set_full_name' => true ] ]);

	echo $users[0]['full_name']; // Bob Smith

如果您需要进行一个超出此类或 getAll() 过滤器范围的超复杂SQL查询怎么办?

记住这个类的目的是不是满足每一个曾经存在或将来存在的项目的所有需求,但它会带你达到90%的目标。基于这一点,执行上述问题有一个简单的方法。只需为你的单次使用使用RAW SQL。

<?php
	use n0nag0n\Super_Model;
	class User extends Super_Model {
		protected $table = 'users';

		public function processCrazyKukooQuery(/* add whatever required fields you need */): array {
			$db = $this->getDbConnection();

			// shamelessly ripped from StackOverflow
			$statement = $db->prepare("SELECT 
				DISTINCT
				t.id,
				t.tag, 
				c.title AS Category
				FROM
				tags2Articles t2a 
				INNER JOIN tags t ON t.id = t2a.idTag
				INNER JOIN categories c ON t.tagCategory = c.id
				INNER JOIN (
					SELECT
					a.id 
					FROM 
					articles AS a
					JOIN tags2articles AS ta  ON a.id=ta.idArticle
					JOIN tags AS tsub ON ta.idTag=tsub.id
					WHERE 
					tsub.id IN (12,13,16) 
					GROUP BY a.id
					HAVING COUNT(DISTINCT tsub.id)=3 
				) asub ON t2a.idArticle = asub.id");
			$statement->execute();

			return $statement->fetchAll();
		}
	}

	

测试

只需运行composer test来运行phpunitphpstan。目前覆盖率已达100%,我希望保持在这个水平。

关于100%覆盖率的一个说明:尽管代码可能有100%的覆盖率,但实际的覆盖率是不同的。目标是针对代码测试许多不同场景,以深入思考代码并预见意外结果。我编写代码以达到“真实”覆盖率,而不是“代码是否运行”的覆盖率。