natcollins / nofus
Nate的单文件实用工具库:用于常见任务的简单单用类
Requires (Dev)
- phpunit/phpunit: ^8
This package is not auto-updated.
Last update: 2024-09-18 00:15:53 UTC
README
关于NOFUS
一组用于常见任务的单一用途类,注重简单直接的使用。每个类都可以单独取用,无需外部代码依赖。
类
- ConfigFile.php - 简易的配置文件解析器
- Logger.php - 简单的日志类,内置文件记录实现
- DBConnect.php - 快速、安全的MySQL/MariaDB查询接口
- UserData.php - 简单的用户数据验证器
安装
推荐的安装方法是使用 Composer。然而,如果您只需要特定的文件,可以独立下载并使用,无需其他文件。
通过Composer安装
composer require natecollins/nofus
ConfigFile.php
用于读取纯文本配置文件的类。
- 简单的“变量 = 值”语法
- 允许引号字符串值
- 允许行注释,包括换行符
- 允许变量作用域
- 允许作用域部分定义
- 允许无值的变量
- 允许每个变量名有多个值
- 可以枚举可用的作用域
- 可以预加载默认值
示例配置文件:
####################################################
# My Config File
# Comment style 1
// Comment style 2
email = me@example.com
name = John Doe # End line comments allowed
debug_mode
nickname = " John \"Slick\" Doe "
date.birth = 1975-02-18
[address]
home.line_1 = 123 Rural Rd
home.state = Ohio
home.city = Delta
work.line_1 = 456 Main St.
work.line_2 = Acme Corporation
work.state = Ohio
work.city = Napoleon
[children]
name = Alice
name = Bobby
name = Chris
示例
use Nofus\ConfigFile; # Create config file object and attempt to load it $cf = new ConfigFile('app.conf'); if (!$cf->load()) { echo "Could not load file!" . PHP_EOL; print_r( $cf->errors() ); // an array of reasons for failure to load exit(1); } #################################################### # Optionally preload default values (will NOT override already loaded values) $cf->preload( array( "email"=>"root@localhost", "debug_mode"=>false, "address.home.city"=>"Kalamazoo", "address.home.state"=>"Michigan" ) ); #################################################### # Simple Variable $email = $cf->get('email'); // if the email variable exists in the file, returns it as a string; if not, returns null #################################################### # Simple Variable with Default Value $phone = $cf->get('phone', '555-1234'); // if the phone variable exists in the file, returns it as a string; if not, returns '555-1234' #################################################### # Simple Valueless Variable $debug = $cf->get('debug_mode', false); // valueless variables return true if they exist, otherwise returns the custom default of false #################################################### # Get Quoted Value $nickname = $cf->get('nickname'); echo "==${$nickname}=="; # will print: == John "Slick" Doe == #################################################### # Get Variable with Scope $birthdate = $cf->get('date.birth'); #################################################### # Get Scope, Example 1 $home = $cf->get('address.home'); $home_line_1 = $home->get("line_1"); #################################################### # Get Scope, Example 2 $addresses = $cf->get('address'); $work_line_1 = $addresses->get("work.line_1"); #################################################### # Get Array of Values $first_child = $cf->get("children.name"); $children = $cf->getArray("children.name"); // value of $first_child would be the string 'Alice' // value of $children would be the array: ('Alice','Bobby','Chris') #################################################### # Enumerate available scopes $scopes1 = $cf->emumerateScope("address.work"); $scopes2 = $cf->emumerateScope(); // Default enumerates top level scope // value of $scopes1 would be the array: ('line_1','line_2','state','city') // value of $scipes2 would be the array: ('email','name','debug_mode','nickname','date','address','children')
只加载一次
调用load()
方法只会在ConfigFile对象首次调用时解析和加载配置文件。后续调用将检查文件是否成功加载,然后不再重复解析。
如果您想强制配置文件重新加载文件,您必须首先对ConfigFile对象调用reset()
,然后再调用load()
。请注意,这将清除任何preload()
变量,因此您在调用reset()
后需要重新调用preload()
。
$cf->reset(); $cf->load();
Logger.php
用于创建日志的类,内置简单的文件记录实现
- 非常简单易用
- 可以注册自定义日志实现以替换内置文件记录器
- 可以指定日志级别
示例
use Nofus\Logger; # Initialize logger with built-in file logger; default level logs all levels Logger::initialize('/path/to/file.log') # Initialize logger with customize logger levels Logger::initialize('/path/to/file.log', Logger::LOG_ERROR | Logger::LOG_CRITICAL); # Disable logger Logger::disable(); # Register custom logger instance which must implement `LoggingInterface`. Logger::register( new CustomLogger() ); # Make log entries Logger::debug("Debug!"); Logger::notice("Notice!"); Logger::warning("Warning!"); Logger::error("Error!"); Logger::critical("Critical!"); # Log entry which includes an exception stack trace try { intdiv(1, 0); } catch (DivisionByZeroError $exc) { Logger::info("Caught something.", $exc); }
DBConnect.php
用于处理MySQL/MariaDB兼容数据库连接的类。特性包括
- 多个服务器之间的自动故障转移
- 通过预编译语句安全转义数据
- 基于PDO扩展
- 查询跟踪
- 查询构造模拟(用于调试)
- 安全转义用户提供的表和列名
- 所有实例共享单个SQL连接
所需库
- PDO
- MySQL
在Ubuntu上,可以通过以下方式安装
apt install php-pdo-mysql
建立连接 建立到SQL服务器的连接需要一个数组对象。该数组必须至少包含一组服务器身份验证参数,包括host
、username
、password
和database
。如果有冗余SQL服务器,可以传递多个参数组,在这种情况下,将按顺序尝试每个服务器,直到建立连接或没有可尝试的连接为止。
将连接参数数组传递给DBConnect
对象以准备连接。
use Nofus\DBConnect; $sql_servers = array( array( 'host'=>'primarymysql.example.com', 'username'=>'my_user', 'password'=>'my_pw', 'database'=>'my_db' ), array( 'host'=>'secondarymysql.example.com', 'username'=>'my_user', 'password'=>'my_pw', 'database'=>'my_db' ) ); $db = new DBConnect($sql_servers);
执行查询
要执行查询,您需要传递一个查询字符串和一个值数组到query()
方法。如果没有值,您可以省略第二个参数。所有查询都作为预处理查询处理。
返回值取决于运行的查询类型。SELECT
查询返回行数组;INSERT
查询返回新行的唯一ID或null
;DELETE
和UPDATE
查询返回受影响的行数。
运行查询的额外方法包括
queryRow
仅返回结果的第一行。如果没有找到行,则返回null
。queryColumn
返回包含特定列值的数组。默认情况下,使用第一列。queryLoop
和queryNext
用于逐行收集结果。请参见下面的示例。queryPrepare
用于手动创建预处理查询,并且可以多次重复使用。
示例查询:
#################################################### # Simple Query $query = "SELECT firstname, lastname FROM users WHERE age > ? AND lastname != ?"; $values = array(21, "Smith"); $names = $db->query($query, $values); foreach ($names as $row) { echo "{$row['lastname']}, {$row['firstname']}"; } #################################################### # Labeled Placeholders $query = "SELECT firstname, lastname FROM users WHERE hair_color = :hair"; $values = array(":hair"=>"brown"); $names = $db->query($query, $values); #################################################### # Single Row Query $query = "SELECT firstname, lastname FROM users WHERE user_id = ?"; $values = array(42); $row = $db->queryRow($query, $values); if ($row !== null) { echo "{$row['lastname']}, {$row['firstname']}"; } #################################################### # Argument Expansion Query (requires anonymous placeholder: ?) $user_id_list = array(2,3,5,7,11) $query = "SELECT firstname, lastname FROM users WHERE user_id IN (?)"; $values = array($user_id_list); $names = $db->query($query, $values); foreach ($names as $row) { echo "{$row['lastname']}, {$row['firstname']}"; } #################################################### # Column Query $query = "SELECT number FROM phone_directory WHERE city = ?"; $values = array('Kalamazoo'); $phone_numbers = $db->queryColumn($query, $values); // will contain an array of the values from the 'number' database column #################################################### # Column Query (selected) $query = "SELECT number, unlisted, area_code FROM phone_directory WHERE city = ?"; $values = array('Kalamazoo'); $area_codes = $db->queryColumn($query, $values, 2); // 2 = 3rd column, 0-based index // will contain an array of the values from the 'area_code' database column #################################################### # Loop Over Large Resultset Query $query = "SELECT firstname, lastname FROM phone_directory WHERE country = ?"; $values = array('US'); $db->queryLoop($query, $values); while ($row = $db->queryNext()) { echo "{$row['lastname']}, {$row['firstname']}"; } #################################################### # Re-using Prepared Statements (argument expansion not permitted) $query = "SELECT * FROM users WHERE lastname = ?"; $prepared = $db->prepare($query); $lastnames = array('Smith','Cooper','Harris'); for ($lastnames as $lname) { $users = $db->query($prepared, array($lname)); echo "Found ".count($users)." users with a lastname of ${lname}"; } #################################################### # Insert Query $query = "INSERT INTO users (firstname, lastname) VALUES(?,?)"; $values = array('John', 'Doe'); $user_id = $db->query($query, $values); // returns the last insert id on success, or null on failure #################################################### # Update Query $query = "UPDATE users SET lastname = ? WHERE user_id = ?"; $values = array('doe', 42); $update_count = $db->query($query, $values); echo "Updated {$update_count} rows."; #################################################### # Delete Query $query = "DELETE FROM users WHERE user_id = ?"; $values = array(33); $delete_count = $db->query($query, $values); echo "Deleted {$delete_count} rows."; #################################################### # Safe Table and Column Name Escaping # (safe from SQL injections; you should still use caution to prevent other shenanigans) $safe_table = $db->escapeIdentifier($table); $safe_column = $db->escapeIdentifier($column); $query = "SELECT firstname, lastname FROM {$safe_table} WHERE {$safe_column} = ?"; $values = array(42); $names = $db->query($query, $values); foreach ($names as $row) { echo "{$row['lastname']}, {$row['firstname']}"; }
显示错误和调试信息
默认情况下,在异常发生时会抛出一个无信息消息。要启用在开发期间自动显示详细错误和输出查询信息,请确保启用调试信息
$db->enableDebugInfo(); // See descriptive errors, and dumps error info into output stream
启用详细异常消息,但禁用自动将查询错误信息输出到输出流
$db->enableDebugInfo(false); // See descriptive errors, but no dumping into output stream
或者,您可以在捕获异常后手动检索错误信息
echo $db->getErrorInfo(); // Manually get error info
抛出异常silentErrors()
方法将设置PDO::ATTR_ERRMODE
。默认情况下,PDO异常将被抛出。
$db->silentErrors(); // disables exceptions on MySQL errors $db->silentErrors(true); // also disables exceptions on MySQL errors $db->silentErrors(false); // an exception will be thrown on MySQL errors from this connection
手动设置PDO属性
您始终可以使用setPDOAttribute()
方法手动设置PDO属性。这需要PDO常量和适当的值,并在创建连接时设置属性。如果属性设置为null
,则之前设置的属性将被移除且不会设置。如果数据库连接之前已经建立,调用此方法将断开连接,强制在下一个查询调用时使用更新后的属性建立新连接。
$db->setPDOAttribute(PDO::MYSQL_ATTR_COMPRESS, 1);
清理标识符
通常您不希望使用变量来表示表和列名,但在极少数情况下可能需要这样做。通过调用escapeIdentifier()
,您可以确保您的标识符已被清理且安全,避免SQL注入攻击。这是通过查询数据库以获取所有有效的表和列名来实现的,并且只有当传入的标识符与从数据库中检索的现有数据库标识符完全匹配时,才被认为是安全的。如果标识符不安全,则返回空字符串。
注意:这仅检查标识符是否在数据库中有效。您仍然永远不应该信任用户提供的数据用于您的查询。
出于性能原因,该方法仅在第一次调用时查询数据库中的标识符。该方法缓存结果,并且后续调用将使用之前加载的数据。
如果您想指定生成的标识符不应该用反引号引用,可以传递一个可选的第二个布尔参数。
$column = $obj->getColumnMatch(); // NOT user supplied data // $safe_column = $db->escapeIdentifier($column); // by default, result is escaped with backticks $safe_column = $db->escapeIdentifier($column, false); // or you can have it not escape with backticks if ($safe_column == '') { echo "Unable to match column!"; exit(1); } $query = "SELECT * FROM books WHERE {$safe_column} = ?"; $values = array( $obj->getColumnValue() ); $results = $db->query($query,$values);
负载均衡多个服务器
目前不是一个真正的负载均衡器,loadBalance()
方法将打乱连接到服务器的顺序。显然,必须在运行任何查询和建立任何连接之前调用此方法。
$db->loadBalance();
强制结束连接
您可以通过调用close()
方法强制数据库连接结束。如果连接已经关闭,此方法不会做任何事情。
$db->close();
获取数据库主机名
// prints the domain string used for the 'host' when the current connection was created $db->getHost(); // if no connection exists or if the connection was closed, empty string will be returned $db->close(); $db->getHost(); // returns ''
获取数据库名称
返回当前连接的数据库的名称。如果没有连接到数据库,将尝试连接到一个并返回它。如果无法连接到任何数据库,将返回空字符串。
$db->getDatabaseName();
调试查询
要转储PDO调试参数并获取最后运行的查询的查询字符串副本,可以调用getLast()
方法。如果你作为事务运行了一系列查询,那么它将包括该事务中的所有查询。
$db->getLast()
在运行之前模拟查询并查看它可能会如何组合,你可以调用queryReturn()
或queryDump()
方法。前者模拟将查询和值组合在一起并返回一个字符串;后者执行相同的操作,但将查询输出到stdout。
$debug = $db->queryReturn($query,$values); $db->queryDump($query,$values);
获取枚举值列表
如果表列是枚举类型,你可以通过调用enumValues()
方法获取包含所有可能的枚举值的数组,并传递所涉及的表和列。返回的数组将按照表中枚举值定义的顺序排序。
$enums = $db->enumValues('mytable', 'mycolumn');
使用事务
可以通过startTransaction()
、commitTransaction()
和rollbackTransaction()
方法使用事务,前提是数据库引擎支持它。
$db->startTransaction(); $update_count = $db->query($query, $values); if ($update_count > 1) { $db->rollbackTransaction(); } else { $db->commitTransaction(); }
默认情况下,MySQL将事务隔离级别设置为REPEATABLE READ
。通过在开始事务时传递布尔值true
,你可以将其更改为READ COMMITTED
。
$db->startTransaction(true);
获取查询调用次数
要获取DBConnect对象特定实例的查询总数列表,可以使用getQueryCount()
方法。
$count = $db->getQueryCount();
UserData.php
一个类,用于从GET、POST、COOKIE和FILES访问和验证用户数据类型。
- 面向对象接口
- 支持检索值数组,包括文件数据
- 函数自动转换为基本数据类型
- 简单的过滤选项,包括范围、长度、正则表达式或预置值
- 所有值检索函数的默认值选项
创建UserData对象
创建新的UserData对象时,必须指定要检索的数据字段名称,并且可以指定查找数据的位置。如果没有指定,UserData将按照以下顺序查找值字段:GET、POST、COOKIE和FILES。
use Nofus\UserData; # Create UserData objects $ud_userid = new UserData("user_id"); $ud_message = new UserData("message", "POST"); # Alternate way of creating UserData objects $ud_attachments = UserData::create("attach", "FILES"); $ud_session = UserData::create("sess_key", "COOKIE");
获取简单值
获取简单值很容易,并返回适当的值类型,如果没有传递变量名称,则返回null。
# Get string values $firstname = $ud_firstname->getStr(); $lastname = $ud_lastname->getString(); # Get integer values (truncates decimals) $age = $ud_age->getInt(); $zip = $ud_zipcode->getInteger(); # Get float values $temperature = $ud_temp->getFloat(); $distance = $ud_dist->getDouble(); # Get boolean values (true values are either: 1, true) $allow_email = $ud_email->getBool(); $allow_text = $ud_text->getBoolean();
设置默认值
如果没有找到值,则使用传递的默认值。如果没有指定默认值,则默认为null。
$msg_type = $ud_type->getStr("public"); $guest = $ud_guest->getBoolean("1");
按允许的值过滤
如果你知道检索的值必须来自一组值,你可以设置一个过滤器以拒绝除了这些值之外的所有值。任何不允许的值将返回默认值。
$ud_acct->filterAllowed( ['guest','normal','admin'] ); $acct_type = $ud_acct->getStr("guest");
按范围过滤
仅适用于获取整数或浮点值。范围之外的价值将返回默认值,除非设置为限制值。
$ud_age->filterRange(0,120); $age = $ud_age->getInteger(); # Only limit the minimum $ud_minimum->filterRange(15.0); # Only limit the maximum range $ud_maximum->filterRange(null, 99.5); # Limit the value to be within the filter range $ud_limit->filterRange(0, 100, true);
按长度过滤
仅适用于字符串值。超出长度限制的值将返回默认值,除非设置为截断。
# Filter minimum and maximum length $ud_username->filterLength(2, 10); # Filter minimum length only $ud_username->filterLength(2); # Filter minimum length and truncate string past maximum length $ud_username->filterLength(2, 10, true);
按正则表达式过滤
仅适用于字符串值。不匹配模式的值将返回默认值。
$ud_date->filterRegExp('/^\d{4}-\d\d-\d\d$/');
检查是否存在具有特定名称的字段
要检查具有给定名称的字段是否存在,即使传递的值是空的。
$ud_check = UserData::create('check'); if ($ud_check->exists()) { echo "Field with name 'check' was submitted."; } else { echo "Field 'check' was not sent to us."; }
获取错误
如果你的值超出了过滤器的范围,它将生成一个错误消息。所有错误消息都存储在一个数组中。使用getErrors()
,你可以检索和查看这些过滤错误。
$errors = $ud_data->getErrors(); foreach ($errors as $err) { echo "$err"; }
获取文件值
获取上传文件的信息。返回包含以下内容的数组:
name
上传文件的原名type
文件的MIME类型(客户端可能伪造)size
上传文件的大小tmp_name
服务器上存在的文件位置和名称error
如果上传过程中出现问题,将显示一个错误代码(0表示没有错误)
$file_info = $ud_attachment->getFile();
获取数组值
当使用PHP表单变量数组时,可以使用UserData函数的数组版本。您应用的所有过滤器将应用于数组中的每个值。
$string_array = $ud_list->getStrArray(); # Can still provide default value to return $string_array = $ud_list->getStringArray( array() ); $int_array = $ud_numbers->getIntArray(); $int_array = $ud_numbers->getIntegerArray(); $float_array = $ud_numbers->getFloatArray(); $float_array = $ud_numbers->getDoubleArray(); $bool_array = $ud_switches->getBoolArray(); $bool_array = $ud_switches->getBooleanArray(); $file_array = $ud_files->getFileArray();