thomas-institut / datatable
简单的数据表界面
Requires
- php: >=8.1
- ext-pdo: *
- psr/log: ^3
- thomas-institut/timestring: >=1.1
Requires (Dev)
- monolog/monolog: ^3.0
- phpunit/phpunit: ^10.0
README
此包定义了一个接口,用于抽象数据访问和由具有唯一整数ID作为键的行组成的类似SQL表的操纵,并提供内存和MySQL实现。目的是将基本数据功能与实际数据库细节解耦,并通过使用内存表来实现更快的测试。
安装
使用以下命令安装最新版本:
$ composer require thomas-institut/datatable
用法
DataTable
主要组件是 DataTable
接口,它捕获了具有唯一整数ID的SQL表的基本功能。实现处理底层存储,这些存储可以在不同的DataTable实例之间共享。提供了基本的ID生成机制,作为顺序ID的替代。
$dt = new DataTableImplementationClass(...)
DataTable
对象还实现了 ArrayAccess
、IteratorAggregate
、LoggerAwareInterface
和 ErrorReporter
接口。
默认的ID生成机制的行为与数据库中的自动递增完全相同,但它不需要数据库干预。可以使用 setIdGenerator
设置其他ID生成器。
此包中提供的 MySqlDataTable
实现还可以将ID生成推迟到MySQL的 AUTO INCREMENT 功能,并且这比DataTable的默认实现更受欢迎。
提供了一个随机ID生成器。此生成器将尝试在两个给定值之间生成随机ID,最多尝试指定次数,之后将默认为当前最大ID加1。
$dt->setIdGenerator(new RandomIdGenerator($min, $max, $maxAttempts));
如果选择了适当的 $min
、$max
和 $maxAttempts
值,随机ID生成器实际上无法回到默认值。
错误处理和报告
无效参数错误由自定义异常处理(请参阅每个方法的文档以获取详细信息),所有其他问题通常会导致抛出 RunTimeException。
可以通过调用 getErrorCode
和 getErrorMessage
方法来检查最新错误。这两个方法定义在 ErrorReporter
接口中。
根据 LoggerAwareInteface
,还可以设置PSR Logger,并且实现应通常在此处报告所有错误。
行创建
$newRow = [ 'field1' => 'exampleStringValue', // or
'field2' => null,
'field3' => true, // or false
'field4' => $someIntegerVariable
// ...
'fieldN' => $nthValue ];
$newId = $dt->createRow($newRow);
// $newId is unique within the table
如果创建时使用的行中存在ID,则将使用该ID作为新ID,如果该ID正在使用中,则将抛出 RowAlreadyExists
异常。
ID字段/列的默认名称为 id
,如果实际存储中的底层表使用不同的名称,则可以通过 setIdColumnName
设置,通常在构造之后立即设置
$dt = new DataTableImplementation();
$dt->setIdColumnName('row_id');
$dt->getIdColumnName(); // 'row_id'
在DataTable中设置列名不会更改底层数据库中的任何内容。它仅告诉DataTable实际数据库ID的名称。
也可以使用数组访问来创建新行
$dt[] = $newRow;
// new row with a given id
$dt[$desiredId] = $newRow;
// but this updates the row if $desiredId already exists
可以在创建行时使用任意数量的字段/列和类型,只要这与实现和实际存储相符合。忽略额外数据或抛出错误取决于实现。
读取/搜索行
检查行是否存在
$result = $dt->rowExists($rowId);
获取特定行
$row = $dt->getRow($rowId);
// $row === [ 'id' => $rowId, 'col1' => value, .... 'colN' => value];
// OR
$row = $dt[$rowId];
// both throw a RowDoesNotExist exception if the row does not exist
获取所有行
$rows = $dt->getAllRows();
可能返回多行的所有方法都返回一个 DataTableIterator
对象。这是一个正常的PHP迭代器,可以在 foreach
语句中使用,并扩展了 count()
方法,该方法返回结果数。
迭代器还提供了一个方便的getFirst()
方法,返回集合中的第一个结果。但是,无法保证在getFirst()
之后对迭代器的foreach
语句将正常工作,或者getFirst()
在foreach
之后将正常工作,因为在特定实现中,回滚可能不可行。
$rows = $dt->getAllRows();
$numRows = $rows->count();
// EITHER
foreach($rows as $row) {
// do something ...
}
// OR
$firstRow = $rows->getFirst(); // null if there are no rows in the result set
search
方法根据以下规则在DataTable上执行基于搜索条件数组和搜索类型的通用搜索:
public function search(array $searchSpecArray, int $searchType = self::SEARCH_AND, int $maxResults = 0) : array;
/**
* Searches the datatable according to the given $searchSpec
*
* $searchSpecArray is an array of conditions.
*
* If $searchType is SEARCH_AND, the row must satisfy:
* $searchSpecArray[0] && $searchSpecArray[1] && ... && $searchSpecArray[n]
*
* if $searchType is SEARCH_OR, the row must satisfy the negation of the spec:
*
* $searchSpecArray[0] || $searchSpecArray[1] || ... || $searchSpecArray[n]
*
*
* A condition is an array of the form:
*
* $condition = [
* 'column' => 'columnName',
* 'condition' => one of (EQUAL_TO, NOT_EQUAL_TO, LESS_THAN, LESS_OR_EQUAL_TO, GREATER_THAN, GREATER_OR_EQUAL_TO)
* 'value' => someValue
* ]
*
* Notice that each condition type has a negation:
* EQUAL_TO <==> NOT_EQUAL_TO
* LESS_THAN <==> GREATER_OR_EQUAL_TO
* LESS_OR_EQUAL_TO <==> GREATER_THAN
*
* if $maxResults > 0, an array of max $maxResults will be returned
* if $maxResults <= 0, all results will be returned
通常,可以使用简单的实用方法findRows
来执行简单的列和值匹配。
public function findRows(array $rowToMatch, int $maxResults = 0) : array;
如果一行与$rowToMatch
中每个键的值匹配,则它将作为结果集的一部分返回。这相当于为$rowToMatch
中的每个键执行AND搜索,并具有EQUAL_TO条件。
更新行
$dt->updateRow($row);
// OR
$dt[$rowId] = $row;
在updateRow
中,给定的行必须有一个id字段,该字段对应于表中的一行,否则将抛出RowDoesNotExist
异常。在数组访问版本中,无论给定的行中是否有id字段,都会使用给定的$rowId
。
只有$row
中的字段会被更新。如果底层数据库模式期望这些列的值,则不完整的行可能会产生错误。
删除行
$result = $dt->deleteRow($rowId);
// OR
unset($dt[$rowId]);
结果是受影响的列数,如果行最初就不存在,则该值为0。
事务
DataTables提供了一种基本接口,用于访问底层数据库事务功能(如果存在)。
要检查是否支持事务:
$supported = $dt->supportsTransactions(); // true if supported
如果支持事务,可以使用startTransaction()
开始事务,并使用commit()
或rollBack()
结束事务。
if($dt->supportsTransactions()){
if ($dt->startTransaction()) {
// create, update,delete rows ...
// decide whether to commit or rollback
if ($goAheadWithCommit) {
if ($dt->commit()){
// all went well, changes are committed
} else {
// error during commit
$errorMessage = $dt->getErrorMessage();
$errorCode = $dt->getErrorCode();
}
} else { // roll back
if ($dt->rollBack() {
// rollBack done, changes to the database not saved
} else {
// error during rollBack
$errorMessage = $dt->getErrorMessage();
$errorCode = $dt->getErrorCode();
}
}
} else {
// error starting transaction
$errorMessage = $dt->getErrorMessage();
$errorCode = $dt->getErrorCode();
}
}
还有一个方便的方法来让DataTable检查底层数据库是否处于事务中。
$result = $dt->isUnderlyingDatabaseInTransaction();
在处理事务时,应格外小心,特别是当数据库连接在多个DataTable之间共享时。如果底层数据库报告正在执行事务(这可能或可能不可靠,取决于数据库),DataTable将不会开始事务。此外,提交只能在启动事务的DataTable上执行。
如果DataTable不支持事务,则startTransaction()
、commit()
和rollBack()
始终返回false
。
InMemoryDataTable
这是一个使用简单PHP数组实现的DataTable
实现,没有存储。这使得可以在不设置数据库的情况下对数据表进行测试。
MySqlDataTable
这是一个使用MySQL表实现的DataTable
实现。
$dt = new MySqlDataTable($pdoDatabaseConnection, $mySqlTableName, $useAutoInc, $idColumnName);
MySqlDataTable
假定存在一个至少有一个具有给定名称的整数id
列的表($idColumnName
,默认为'id')。
如果$useAutoInc
为true,则MySqlDataTable
假定id
列具有AUTO_INCREMENT
属性,并将创建行,以便MySQL负责生成ID。否则,MySqlDataTable
本身将负责生成增量ID。
为了与该库的早期版本兼容,$useAutoInc
默认为false。但是,建议使用MySQL自动增量功能。在并发调用createRow
的情况下,DataTable的内部ID生成器可能无法生成唯一的ID。
MySQL中的表可以有任何数量和类型的额外列。只要createRow
和updateRow
的调用与列名和类型一致,一切都应该正常工作。您还可以在MySQL中定义默认值,并在调用createRow
时省略这些值。
MySqlDataTableWithRandomIds
与MySqlDatable
相同,但使用随机ID生成器。
MySqlUnitemporalDataTable
一个带有时间标记行的 MySQL 表。每一行不仅有一个唯一的 id,还有一个有效的 from 和 until 时间。当使用正常的 DataTable
方法时,MySQLUnitemporalDataTable
的行为与 MySqlDataTable
完全相同,但它不会删除任何行,只是使它们无效。
有一套时间方法用于创建、读取、更新和删除数据的旧版本。例如
$dt = new MySqlUnitemporalDataTable($pdoDatabaseConnection, $mySqlTableName);
$oldRow = $dt->getRowWithTime($rowId, $timeString);
$timeString
是一个格式化为有效 MySQL 日期时间的字符串,例如 '2018-01-01 12:00:00.123123'
使用 TimeString
类的静态方法从 MySQL 日期和日期时间字符串,以及带有或没有微秒的 UNIX 时间戳生成这样的字符串。
底层的 MySQL 表必须有两个日期时间字段:valid_from
和 valid_until
用户负责设置 PDO 连接,以使用所有使用时间参数的查询中将要使用的时区。