RESAZIP-PC\resaz

update

......@@ -89,4 +89,63 @@ sudo systemctl restart php-fpm || sudo systemctl restart httpd
extension=gmp
extension=bcmath
# 3) Restart web server / PHP-FPM service
```
\ No newline at end of file
```
## QueryLog (Laravel shared package)
### 1) Publish config in child project
```bash
php artisan vendor:publish --tag=query-log-config
```
This creates `config/query_log.php` so each project can tune its own settings.
### 2) Publish migration in child project
```bash
php artisan vendor:publish --tag=query-log-migrations
php artisan migrate
```
This publishes a production-oriented migration for `log_queries` with key indexes:
- `query_at`
- `query_type`
- `connection`
- `user_id`
- composite indexes for common filter windows
### 3) Minimal table fields
Use your own migration and ensure these columns exist in the configured table (`query_log.table`):
- `action` (string)
- `query` (longText/text)
- `query_type` (string, example: `insert`, `update`, `delete`, `replace`)
- `query_time` (float/double)
- `query_at` (datetime)
- `query_order` (int)
- `connection` (string)
- `ip` (nullable string)
- `user_id` (nullable string/int)
- `is_screen` (tinyint/bool)
### 4) Important config for production
- `enable`: enable/disable query log
- `min_time`: skip very fast queries (ms)
- `sample_rate`: sampling percent (`0-100`) to reduce high-traffic load
- `chunk`: batch size per queue job
- `max_queries_per_request`: hard limit per request
- `skip_route_patterns`: wildcard route/url patterns to skip
- `skip_command_patterns`: wildcard console command patterns to skip
- `mask_sensitive_bindings`: mask sensitive values in SQL bindings
- `sensitive_keywords`: keyword list used for masking
- `masked_value`: replacement text for sensitive bindings
### 5) Behavior highlights
- Query buffer is separated by DB connection.
- Buffer is flushed when transaction commits (outermost level).
- Buffer is cleared on rollback (rolled-back queries are not logged).
- Write-query detection supports CTE (`WITH ... UPDATE/INSERT/...`) and skips read queries.
- `query_type` stores action text directly (`insert`, `update`, `delete`, `replace`, `upsert`).
\ No newline at end of file
......
......@@ -10,10 +10,18 @@
"require": {
"brick/math": "^0.9 || ^0.10 || ^0.11 || ^0.13",
"php": "^7.3 || ^8.0",
"laravel/framework": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0"
"laravel/framework": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
"milon/barcode": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0"
},
"suggest": {
"ext-gmp": "Faster big-number calculations",
"ext-bcmath": "Faster decimal calculations when GMP is unavailable"
},
"extra": {
"laravel": {
"providers": [
"Daito\\Lib\\QueryLog\\Providers\\QueryLogProvider"
]
}
}
}
......
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0c23b9705a5fe293b678d1bbc5020207",
"content-hash": "94cb3df5cd945ab2de60f59263dbd8d6",
"packages": [
{
"name": "brick/math",
......@@ -1144,6 +1144,81 @@
"time": "2024-09-21T08:32:55+00:00"
},
{
"name": "milon/barcode",
"version": "v12.1.0",
"source": {
"type": "git",
"url": "https://github.com/milon/barcode.git",
"reference": "052e601665cfb99e119a630b6116fab0eb30183e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/milon/barcode/zipball/052e601665cfb99e119a630b6116fab0eb30183e",
"reference": "052e601665cfb99e119a630b6116fab0eb30183e",
"shasum": ""
},
"require": {
"ext-gd": "*",
"illuminate/support": "^7.0|^8.0|^9.0|^10.0 | ^11.0 | ^12.0",
"php": "^7.3 | ^8.0"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"DNS1D": "Milon\\Barcode\\Facades\\DNS1DFacade",
"DNS2D": "Milon\\Barcode\\Facades\\DNS2DFacade"
},
"providers": [
"Milon\\Barcode\\BarcodeServiceProvider"
]
}
},
"autoload": {
"psr-0": {
"Milon\\Barcode": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
],
"authors": [
{
"name": "Nuruzzaman Milon",
"email": "contact@milon.im"
}
],
"description": "Barcode generator like Qr Code, PDF417, C39, C39+, C39E, C39E+, C93, S25, S25+, I25, I25+, C128, C128A, C128B, C128C, 2-Digits UPC-Based Extention, 5-Digits UPC-Based Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI (Variation of Plessey code)",
"keywords": [
"CODABAR",
"CODE 128",
"CODE 39",
"barcode",
"datamatrix",
"ean",
"laravel",
"pdf417",
"qr code",
"qrcode"
],
"support": {
"issues": "https://github.com/milon/barcode/issues",
"source": "https://github.com/milon/barcode/tree/v12.1.0"
},
"funding": [
{
"url": "https://paypal.me/nuruzzamanmilon",
"type": "custom"
},
{
"url": "https://github.com/milon",
"type": "github"
}
],
"time": "2026-02-07T00:42:12+00:00"
},
{
"name": "monolog/monolog",
"version": "2.11.0",
"source": {
......
<?php
namespace Daito\Lib;
use Milon\Barcode\DNS1D;
use Milon\Barcode\DNS2D;
class DaitoBarcode {
public static function getJan13Number($jan12)
{
// Kiểm tra định dạng
if (!preg_match('/^\d{12}$/', $jan12)) {
return $jan12;
}
$sum = 0;
for ($i = 0; $i < 12; $i++) {
$digit = (int) $jan12[$i];
$sum += $i % 2 === 0 ? $digit : $digit * 3;
}
$checkDigit = (10 - ($sum % 10)) % 10;
return $jan12 . $checkDigit;
}
public static function isJanEAN13($jan)
{
if (!preg_match('/^\d{13}$/', $jan)) {
return false;
}
$jan12 = substr($jan, 0, 12);
$ean13Jan = self::getJan13Number($jan12);
return $ean13Jan === $jan;
}
public static function getJan8Number($jan7)
{
if (!preg_match('/^\d{7}$/', $jan7)) {
return $jan7;
}
$sum = 0;
for ($i = 0; $i < 7; $i++) {
$digit = (int) $jan7[$i];
$sum += $i % 2 === 0 ? $digit * 3 : $digit;
}
$checkDigit = (10 - ($sum % 10)) % 10;
return $jan7 . $checkDigit;
}
public static function isJanEAN8($jan)
{
if (!preg_match('/^\d{8}$/', $jan)) {
return false;
}
$jan7 = substr($jan, 0, 7);
$ean8Jan = self::getJan8Number($jan7);
return $ean8Jan === $jan;
}
public static function getBarcodeTypeJan($jan)
{
if (self::isJanEAN8($jan)) {
return 'EAN8';
}
if (self::isJanEAN13($jan)) {
return 'EAN13';
}
return 'C128';
}
public static function generateBarcodeJan($jan)
{
$typeJan = self::getBarcodeTypeJan($jan);
if ($typeJan === 'EAN13') {
$jan = substr($jan, 0, 12);
} elseif ($typeJan === 'EAN8') {
$jan = substr($jan, 0, 7);
}
$dns1d = new DNS1D();
return $dns1d->getBarcodePNG($jan, $typeJan);
}
public static function generateBarcodeC128($text)
{
$dns1d = new DNS1D();
return $dns1d->getBarcodePNG($text, 'C128');
}
public static function generateBarcodeC39($text)
{
$dns1d = new DNS1D();
return $dns1d->getBarcodePNG($text, 'C39');
}
public static function generateBarcodeEAN13($ean13)
{
$dns1d = new DNS1D();
return $dns1d->getBarcodePNG($ean13, 'EAN13');
}
public static function generateBarcodeEAN8($ean8)
{
$dns1d = new DNS1D();
return $dns1d->getBarcodePNG($ean8, 'EAN8');
}
public static function generateBarcodeQrCode($text)
{
$dns2d = new DNS2D();
return $dns2d->getBarcodePNG($text, 'QRCODE');
}
}
\ No newline at end of file
<?php
namespace Daito\Lib;
use Brick\Math\BigDecimal;
use Brick\Math\RoundingMode;
use RuntimeException;
class DaitoNumber
{
const DEFAULT_MAX_DECIMALS = 12;
/**
* Format number with configurable separators, prefix/suffix, and decimal behavior.
*
* Example:
* DaitoNumber::format('1234567.00') => '1,234,567'
* DaitoNumber::format('1234567.5', array('decimals' => 2)) => '1,234,567.50'
*/
public static function format($number, array $arrOptions = array())
{
$arrConfig = array_merge(
array(
'thousands_separator' => ',',
'decimal_separator' => '.',
'prefix' => '',
'suffix' => '',
'decimals' => null,
'trim_trailing_zeros' => true,
'max_decimals' => self::DEFAULT_MAX_DECIMALS,
),
$arrOptions
);
$numberString = self::toCanonicalString($number);
$isNegative = strpos($numberString, '-') === 0;
$unsigned = $isNegative ? substr($numberString, 1) : $numberString;
if ($arrConfig['decimals'] !== null) {
$decimals = (int) $arrConfig['decimals'];
if ($decimals < 0) {
throw new RuntimeException('decimals must be greater than or equal to 0.');
}
$unsigned = (string) BigDecimal::of($unsigned)->toScale($decimals, RoundingMode::HALF_UP);
} else {
$maxDecimals = (int) $arrConfig['max_decimals'];
if ($maxDecimals < 0) {
throw new RuntimeException('max_decimals must be greater than or equal to 0.');
}
$unsigned = (string) BigDecimal::of($unsigned)->toScale($maxDecimals, RoundingMode::HALF_UP);
if ($arrConfig['trim_trailing_zeros']) {
$unsigned = self::trimTrailingZeros($unsigned);
}
}
$arrParts = explode('.', $unsigned, 2);
$integerPart = self::addThousandsSeparator($arrParts[0], (string) $arrConfig['thousands_separator']);
$decimalPart = isset($arrParts[1]) ? $arrParts[1] : '';
$formattedNumber = $integerPart;
if ($decimalPart !== '') {
$formattedNumber .= (string) $arrConfig['decimal_separator'] . $decimalPart;
}
$formatted = (string) $arrConfig['prefix'] . $formattedNumber . (string) $arrConfig['suffix'];
return $isNegative ? '-' . $formatted : $formatted;
}
/**
* Format currency-friendly output (default 2 decimals, keep trailing zeros).
*
* Example:
* DaitoNumber::formatCurrency('1234.5') => '1,234.50'
* DaitoNumber::formatCurrency('1234.5', array('prefix' => '$')) => '$1,234.50'
*/
public static function formatCurrency($number, array $arrOptions = array())
{
$arrCurrencyOptions = array_merge(
array(
'decimals' => 2,
'trim_trailing_zeros' => false,
'prefix' => '',
'suffix' => '',
),
$arrOptions
);
return self::format($number, $arrCurrencyOptions);
}
/**
* Format percentage value.
*
* Example:
* DaitoNumber::formatPercent('12.3456') => '12.35%'
* DaitoNumber::formatPercent('0.1234', array('input_ratio' => true)) => '12.34%'
*/
public static function formatPercent($number, array $arrOptions = array())
{
$arrPercentOptions = array_merge(
array(
'decimals' => 2,
'trim_trailing_zeros' => true,
'suffix' => '%',
'input_ratio' => false,
),
$arrOptions
);
$numberValue = $number;
if ($arrPercentOptions['input_ratio']) {
$numberValue = (string) BigDecimal::of(self::toCanonicalString($number))->multipliedBy('100');
}
unset($arrPercentOptions['input_ratio']);
return self::format($numberValue, $arrPercentOptions);
}
private static function toCanonicalString($number)
{
if (is_int($number) || is_string($number)) {
$numberString = trim((string) $number);
} elseif (is_float($number)) {
$numberString = self::trimTrailingZeros(sprintf('%.14F', $number));
} else {
throw new RuntimeException('DaitoNumber only supports int, float, or numeric string.');
}
if ($numberString === '' || !preg_match('/^[+-]?\d+(\.\d+)?$/', $numberString)) {
throw new RuntimeException('Invalid number format.');
}
return (string) BigDecimal::of($numberString);
}
private static function addThousandsSeparator($integerPart, $thousandsSeparator)
{
return preg_replace('/\B(?=(\d{3})+(?!\d))/', $thousandsSeparator, $integerPart);
}
private static function trimTrailingZeros($numberString)
{
if (strpos($numberString, '.') === false) {
return $numberString;
}
$numberString = rtrim($numberString, '0');
$numberString = rtrim($numberString, '.');
return $numberString === '' ? '0' : $numberString;
}
}
<?php
namespace Daito\Lib;
class DaitoString {
public static function toUpper($text){
return strtoupper($text);
use RuntimeException;
class DaitoString
{
const UTF8 = 'UTF-8';
const NKF_SOURCE_ENCODINGS = 'SJIS-win,CP932,Shift_JIS,EUC-JP,UTF-8';
/**
* Collapse multiple spaces/tabs/newlines into a single ASCII space.
*
* Example:
* DaitoString::collapseSpaces("a b\t\tc") => "a b c"
*/
public static function collapseSpaces($input)
{
return preg_replace('/\s+/u', ' ', (string) $input);
}
public static function toLower($text){
return strtolower($text);
/**
* Convert full-width Japanese spaces to ASCII spaces.
*
* Example:
* DaitoString::normalizeFullWidthSpace("A B") => "A B"
*/
public static function normalizeFullWidthSpace($input)
{
return str_replace(' ', ' ', (string) $input);
}
/**
* Convert half-width kana to full-width kana.
*
* Example:
* DaitoString::toFullWidthKana("カタカナ") => "カタカナ"
*/
public static function toFullWidthKana($text)
{
return mb_convert_kana((string) $text, 'KV', self::UTF8);
}
/**
* Convert full-width kana to half-width kana.
*
* Example:
* DaitoString::toHalfWidthKana("カタカナ") => "カタカナ"
*/
public static function toHalfWidthKana($text)
{
return mb_convert_kana((string) $text, 'kV', self::UTF8);
}
/**
* Split text by normalized spaces (full-width/extra spaces are handled).
*
* Example:
* DaitoString::splitBySpace("A  B C") => array("A", "B", "C")
*/
public static function splitBySpace($string)
{
$normalized = self::normalizeFullWidthSpace((string) $string);
$normalized = trim(self::collapseSpaces($normalized));
if ($normalized === '') {
return array();
}
return explode(' ', $normalized);
}
/**
* Convert Hiragana to Katakana.
*
* Example:
* DaitoString::toKatakana("ひらがな") => "ヒラガナ"
*/
public static function toKatakana($input)
{
return mb_convert_kana((string) $input, 'C', self::UTF8);
}
/**
* Check whether the input contains Japanese characters.
*
* Example:
* DaitoString::isJapanese("abc日本語") => true
* DaitoString::isJapanese("abcdef") => false
*/
public static function isJapanese($input)
{
return preg_match('/[\x{3040}-\x{30FF}\x{3400}-\x{4DBF}\x{4E00}-\x{9FFF}\x{FF66}-\x{FF9D}]/u', (string) $input) === 1;
}
/**
* Convert Japanese-encoded text file to UTF-8.
* Prefer nkf when available, fallback to mb_convert_encoding.
*
* Example:
* DaitoString::convertToUtf8('/tmp/source.csv');
* DaitoString::convertToUtf8('/tmp/source.csv', '/tmp/out', 'source_utf8.csv', 1);
*/
public static function convertToUtf8($sourceFile, $destDir = '', $fileName = '', $isBk = 0)
{
$sourceFilePath = (string) $sourceFile;
if (!is_file($sourceFilePath)) {
throw new RuntimeException('Source file does not exist: ' . $sourceFilePath);
}
$targetDirectory = $destDir ? (string) $destDir : dirname($sourceFilePath);
if (!is_dir($targetDirectory)) {
throw new RuntimeException('Destination directory does not exist: ' . $targetDirectory);
}
$targetFileName = $fileName ? (string) $fileName : basename($sourceFilePath);
$destFile = $targetDirectory . DIRECTORY_SEPARATOR . $targetFileName;
if ((string) realpath($sourceFilePath) === (string) realpath($destFile)) {
$destFile .= '_' . time();
}
if ($isBk) {
$backupFile = $targetDirectory . DIRECTORY_SEPARATOR . $targetFileName . '.bak';
if (!copy($sourceFilePath, $backupFile)) {
throw new RuntimeException('Can not create backup file: ' . $backupFile);
}
}
$utf8Content = self::convertJapaneseTextToUtf8($sourceFilePath);
if (file_put_contents($destFile, $utf8Content) === false) {
throw new RuntimeException('Can not write destination file: ' . $destFile);
}
return $destFile;
}
private static function convertJapaneseTextToUtf8($sourceFilePath)
{
if (self::canUseNkf()) {
$nkfContent = self::convertFileByNkf($sourceFilePath);
if ($nkfContent !== null) {
return $nkfContent;
}
}
return self::convertFileToUtf8ByMbstring($sourceFilePath);
}
private static function canUseNkf()
{
if (!function_exists('shell_exec')) {
return false;
}
$disabledFunctions = (string) ini_get('disable_functions');
if ($disabledFunctions !== '' && strpos($disabledFunctions, 'shell_exec') !== false) {
return false;
}
$output = @shell_exec('nkf --version');
return is_string($output) && $output !== '';
}
private static function convertFileByNkf($sourceFilePath)
{
$command = 'nkf -w -- ' . escapeshellarg($sourceFilePath);
$output = @shell_exec($command);
return is_string($output) ? $output : null;
}
/**
* Convert a file content to UTF-8 using mbstring.
*
* Example:
* DaitoString::convertFileToUtf8ByMbstring('/tmp/source_sjis.txt');
*/
public static function convertFileToUtf8ByMbstring($sourceFilePath)
{
$content = file_get_contents($sourceFilePath);
if ($content === false) {
throw new RuntimeException('Can not read source file: ' . $sourceFilePath);
}
return self::convertTextToUtf8ByMbstring($content);
}
/**
* Convert raw text to UTF-8 using mbstring.
*
* Example:
* DaitoString::convertTextToUtf8ByMbstring($rawText);
*/
public static function convertTextToUtf8ByMbstring($text)
{
$utf8Content = mb_convert_encoding((string) $text, self::UTF8, self::NKF_SOURCE_ENCODINGS);
if ($utf8Content === false) {
throw new RuntimeException('Can not convert text to UTF-8.');
}
return $utf8Content;
}
}
\ No newline at end of file
......
<?php
namespace Daito\Lib\QueryLog\Jobs;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Bus\Queueable;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
class SaveQueryLogJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $arrQueries;
public function __construct(array $arrQueries)
{
$this->arrQueries = $arrQueries;
}
public function handle(): void
{
if ($this->arrQueries === array()) {
return;
}
DB::connection(config('query_log.connection', 'query_log'))
->table(config('query_log.table', 'log_queries'))
->insert($this->arrQueries);
}
}
<?php
namespace Daito\Lib\QueryLog\Models;
use Illuminate\Database\Eloquent\Model;
class QueryLog extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'log_queries';
/**
* The primary key for the model.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = true;
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $timestamps = false;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'action',
'query',
'query_type',
'query_time',
'query_at',
'query_order',
'connection',
'ip',
'user_id',
'is_screen',
];
protected $connection = 'query_log';
public function __construct(array $arrAttributes = array())
{
parent::__construct($arrAttributes);
$this->setConnection(config('query_log.connection', 'query_log'));
$this->setTable(config('query_log.table', 'log_queries'));
}
}
<?php
namespace Daito\Lib\QueryLog\Providers;
use Carbon\Carbon;
use Daito\Lib\QueryLog\Jobs\SaveQueryLogJob;
use Illuminate\Database\Connection;
use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Database\Events\TransactionCommitted;
use Illuminate\Database\Events\TransactionRolledBack;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
use Throwable;
class QueryLogProvider extends ServiceProvider
{
private $arrQueriesByConnection = array();
private $arrLoggedCountsByConnection = array();
public function register(): void
{
$this->mergeConfigFrom(
__DIR__ . '/../config/query_log.php',
'query_log'
);
}
public function boot(): void
{
$this->registerPublishableResources();
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
if (!$this->isEnabled()) {
return;
}
$this->registerTransactionLifecycleListeners();
DB::listen(function (QueryExecuted $query) {
if (!$this->isEnabled()) {
return;
}
if (!$this->shouldLogInCurrentRuntime()) {
return;
}
if ((float) $query->time < (float) config('query_log.min_time', 0)) {
return;
}
if (!$this->isPassedSampling()) {
return;
}
if ($this->shouldSkipCurrentContext()) {
return;
}
$queryVerb = $this->detectWriteVerb($query->sql);
if ($queryVerb === null) {
return;
}
if ($this->isIgnoredTableSql($query->sql)) {
return;
}
$connectionName = $query->connectionName ?: $query->connection->getName();
if (!$this->canLogMoreQueries($connectionName)) {
return;
}
$arrPayload = $this->buildPayload($query, $queryVerb, $connectionName);
$this->appendQuery($connectionName, $arrPayload);
if (!$this->isInTransaction($query->connection)) {
$this->flushConnectionBuffer($connectionName);
}
});
if (method_exists($this->app, 'terminating')) {
call_user_func(array($this->app, 'terminating'), function () {
$this->flushAllBuffers();
});
}
}
private function registerTransactionLifecycleListeners(): void
{
Event::listen(TransactionCommitted::class, function (TransactionCommitted $event) {
if ($event->connection->transactionLevel() !== 0) {
return;
}
$this->flushConnectionBuffer($event->connection->getName(), true);
});
Event::listen(TransactionRolledBack::class, function (TransactionRolledBack $event) {
if ($event->connection->transactionLevel() !== 0) {
return;
}
$this->clearConnectionBuffer($event->connection->getName());
});
}
private function appendQuery($connectionName, array $arrPayload): void
{
if (!isset($this->arrQueriesByConnection[$connectionName])) {
$this->arrQueriesByConnection[$connectionName] = array();
}
$this->arrQueriesByConnection[$connectionName][] = $arrPayload;
$this->arrLoggedCountsByConnection[$connectionName] = ($this->arrLoggedCountsByConnection[$connectionName] ?? 0) + 1;
}
private function flushConnectionBuffer($connectionName, $force = false): void
{
$arrBuffer = $this->arrQueriesByConnection[$connectionName] ?? array();
if ($arrBuffer === array()) {
return;
}
$chunkSize = max(1, (int) config('query_log.chunk', 200));
if (!$force && count($arrBuffer) < $chunkSize) {
return;
}
foreach (array_chunk($arrBuffer, $chunkSize) as $arrQueries) {
$job = new SaveQueryLogJob($arrQueries);
if (config('query_log.queue_connection') !== null) {
$job->onConnection(config('query_log.queue_connection'));
}
if (config('query_log.queue_name') !== null) {
$job->onQueue(config('query_log.queue_name'));
}
dispatch($job);
}
$this->arrQueriesByConnection[$connectionName] = array();
}
private function flushAllBuffers(): void
{
foreach (array_keys($this->arrQueriesByConnection) as $connectionName) {
$this->flushConnectionBuffer($connectionName, true);
}
}
private function clearConnectionBuffer($connectionName): void
{
$this->arrQueriesByConnection[$connectionName] = array();
}
private function canLogMoreQueries($connectionName): bool
{
$maxQueries = max(1, (int) config('query_log.max_queries_per_request', 1000));
$currentCount = $this->arrLoggedCountsByConnection[$connectionName] ?? 0;
return $currentCount < $maxQueries;
}
private function isIgnoredTableSql($sql): bool
{
$arrIgnoreTables = (array) config('query_log.ignore_tables', array());
if ($arrIgnoreTables === array()) {
return false;
}
foreach ($arrIgnoreTables as $tableName) {
$tablePattern = '/\b`?' . preg_quote((string) $tableName, '/') . '`?\b/i';
if (preg_match($tablePattern, $sql) === 1) {
return true;
}
}
return false;
}
private function detectWriteVerb($sql)
{
$normalizedSql = trim($this->stripLeadingSqlComments((string) $sql));
if ($normalizedSql === '') {
return null;
}
if (preg_match('/^(insert|update|delete|replace|upsert)\b/i', $normalizedSql, $arrMatches) === 1) {
return strtolower($arrMatches[1]);
}
if (preg_match('/^with\b/i', $normalizedSql) === 1
&& preg_match('/\b(insert|update|delete|replace|upsert)\b/i', $normalizedSql, $arrMatches) === 1
) {
return strtolower($arrMatches[1]);
}
return null;
}
private function stripLeadingSqlComments($sql)
{
$cleanSql = preg_replace('/^\s*(\/\*.*?\*\/\s*)+/s', '', $sql);
if ($cleanSql === null) {
return $sql;
}
return preg_replace('/^\s*(--[^\r\n]*[\r\n]\s*)+/s', '', $cleanSql) ?: $cleanSql;
}
private function buildPayload(QueryExecuted $query, $queryVerb, $connectionName): array
{
$rawSql = $this->interpolateSql($query->sql, $query->bindings);
$maxSqlLength = max(256, (int) config('query_log.max_sql_length', 4000));
$sql = mb_substr($rawSql, 0, $maxSqlLength);
return array(
'query' => $sql,
'action' => $this->resolveAction(),
'query_type' => $queryVerb,
'query_time' => $query->time,
'query_at' => Carbon::now(),
'query_order' => ($this->arrLoggedCountsByConnection[$connectionName] ?? 0) + 1,
'connection' => $connectionName,
'is_screen' => app()->runningInConsole() ? 0 : 1,
'user_id' => $this->resolveUserId(),
'ip' => request() ? request()->ip() : null,
);
}
private function interpolateSql($sql, array $arrBindings): string
{
$interpolatedSql = (string) $sql;
foreach ($arrBindings as $index => $binding) {
$isSensitiveBinding = $this->isSensitiveBinding($interpolatedSql, (int) $index);
$value = $this->quoteBinding($binding, $isSensitiveBinding);
$interpolatedSql = preg_replace('/\?/', $value, $interpolatedSql, 1) ?: $interpolatedSql;
}
return str_replace(array("\r\n", "\n"), ' ', trim($interpolatedSql));
}
private function quoteBinding($binding, $isSensitive = false): string
{
if ($isSensitive && (bool) config('query_log.mask_sensitive_bindings', true)) {
return "'" . (string) config('query_log.masked_value', '***') . "'";
}
if ($binding === null) {
return 'null';
}
if (is_bool($binding)) {
return $binding ? '1' : '0';
}
if (is_numeric($binding)) {
return (string) $binding;
}
if ($binding instanceof \DateTimeInterface) {
return "'" . $binding->format('Y-m-d H:i:s') . "'";
}
return "'" . str_replace("'", "''", (string) $binding) . "'";
}
private function isSensitiveBinding($interpolatedSql, int $bindingIndex): bool
{
$arrSensitiveKeywords = (array) config('query_log.sensitive_keywords', array());
if ($arrSensitiveKeywords === array()) {
return false;
}
$parts = explode('?', (string) $interpolatedSql);
if (!isset($parts[$bindingIndex])) {
return false;
}
$context = strtolower(substr($parts[$bindingIndex], -120));
foreach ($arrSensitiveKeywords as $keyword) {
$keywordText = strtolower((string) $keyword);
if ($keywordText !== '' && strpos($context, $keywordText) !== false) {
return true;
}
}
return false;
}
private function resolveAction(): string
{
if (app()->runningInConsole()) {
return $this->resolveConsoleCommand();
}
$request = request();
if ($request === null) {
return 'http';
}
return $request->method() . ' ' . $request->fullUrl();
}
private function resolveUserId()
{
try {
return Auth::check() ? Auth::id() : null;
} catch (Throwable $throwable) {
return null;
}
}
private function shouldSkipCurrentContext(): bool
{
if (app()->runningInConsole()) {
$command = $this->resolveConsoleCommand();
return $this->matchPatterns($command, (array) config('query_log.skip_command_patterns', array()));
}
$request = request();
if ($request === null) {
return false;
}
$route = $request->route();
$routeName = is_object($route) && method_exists($route, 'getName') ? (string) $route->getName() : '';
$routePath = ltrim((string) $request->path(), '/');
$fullUrl = (string) $request->fullUrl();
$arrTargets = array($routeName, $routePath, $fullUrl);
foreach ($arrTargets as $target) {
if ($this->matchPatterns($target, (array) config('query_log.skip_route_patterns', array()))) {
return true;
}
}
return false;
}
private function resolveConsoleCommand(): string
{
$arrArgv = $_SERVER['argv'] ?? array();
return isset($arrArgv[1]) ? trim((string) $arrArgv[1]) : 'console';
}
private function matchPatterns(string $target, array $arrPatterns): bool
{
if ($target === '' || $arrPatterns === array()) {
return false;
}
foreach ($arrPatterns as $pattern) {
$patternText = (string) $pattern;
if ($patternText !== '' && $this->matchPattern($patternText, $target)) {
return true;
}
}
return false;
}
private function matchPattern(string $pattern, string $target): bool
{
if (function_exists('fnmatch')) {
return fnmatch($pattern, $target);
}
$regex = '/^' . str_replace(array('\*', '\?'), array('.*', '.'), preg_quote($pattern, '/')) . '$/i';
return preg_match($regex, $target) === 1;
}
private function isPassedSampling(): bool
{
$sampleRate = (float) config('query_log.sample_rate', 100);
if ($sampleRate >= 100) {
return true;
}
if ($sampleRate <= 0) {
return false;
}
$random = mt_rand(1, 10000) / 100;
return $random <= $sampleRate;
}
private function isInTransaction(Connection $connection): bool
{
return method_exists($connection, 'transactionLevel') && $connection->transactionLevel() > 0;
}
private function shouldLogInCurrentRuntime(): bool
{
if (app()->runningInConsole()) {
return (bool) config('query_log.log_on_console', false);
}
return true;
}
private function isEnabled(): bool
{
return (bool) config('query_log.enable', false);
}
private function registerPublishableResources(): void
{
$this->publishes(
array(
__DIR__ . '/../config/query_log.php' => config_path('query_log.php'),
),
'query-log-config'
);
$this->publishes(
array(
__DIR__ . '/../database/migrations/2026_02_20_000000_create_log_queries_table.php'
=> database_path('migrations/' . date('Y_m_d_His') . '_create_log_queries_table.php'),
),
'query-log-migrations'
);
}
}
<?php
return [
'enable' => env('ENABLE_QUERY_LOG', false),
'log_on_console' => env('QUERY_LOG_ON_CONSOLE', false),
'sample_rate' => env('QUERY_LOG_SAMPLE_RATE', 100), // 0-100 (%)
'chunk' => env('QUERY_LOG_CHUNK', 200),
'max_queries_per_request' => env('QUERY_LOG_MAX_PER_REQUEST', 1000),
'min_time' => env('QUERY_LOG_MIN_TIME', 0),
'connection' => env('QUERY_LOG_CONNECTION', 'query_log'),
'table' => env('QUERY_LOG_TABLE', 'log_queries'),
'queue_connection' => env('QUERY_LOG_QUEUE_CONNECTION', null),
'queue_name' => env('QUERY_LOG_QUEUE_NAME', null),
'max_sql_length' => env('QUERY_LOG_MAX_SQL_LENGTH', 4000),
'mask_sensitive_bindings' => env('QUERY_LOG_MASK_SENSITIVE_BINDINGS', true),
'sensitive_keywords' => array(
'password',
'passwd',
'pwd',
'token',
'secret',
'api_key',
'apikey',
'authorization',
'cookie',
'credit_card',
'card_number',
'cvv',
'pin',
),
'masked_value' => '***',
'skip_route_patterns' => array(
'horizon*',
'telescope*',
'_debugbar*',
),
'skip_command_patterns' => array(
'queue:*',
'horizon*',
'schedule:run',
),
'ignore_tables' => array(
'log_queries',
'jobs',
'failed_jobs',
'migrations',
'mst_batch',
),
];
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
$connection = config('query_log.connection', 'query_log');
$table = config('query_log.table', 'log_queries');
Schema::connection($connection)->create($table, function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('action', 512)->nullable();
$table->longText('query');
$table->string('query_type', 32);
$table->decimal('query_time', 10, 3)->default(0);
$table->dateTime('query_at');
$table->unsignedInteger('query_order')->default(0);
$table->string('connection', 64)->nullable();
$table->string('ip', 64)->nullable();
$table->string('user_id', 64)->nullable();
$table->boolean('is_screen')->default(false);
$table->index('query_at', 'idx_log_queries_query_at');
$table->index('query_type', 'idx_log_queries_query_type');
$table->index('connection', 'idx_log_queries_connection');
$table->index('user_id', 'idx_log_queries_user_id');
$table->index(array('query_at', 'query_type'), 'idx_log_queries_at_type');
$table->index(array('connection', 'query_at'), 'idx_log_queries_connection_at');
});
}
public function down(): void
{
$connection = config('query_log.connection', 'query_log');
$table = config('query_log.table', 'log_queries');
Schema::connection($connection)->dropIfExists($table);
}
};
<?php
require __DIR__ . '/vendor/autoload.php';
$container = new Illuminate\Container\Container();
Illuminate\Container\Container::setInstance($container);
$container->instance('config', new Illuminate\Config\Repository(array(
'barcode' => array(
'store_path' => sys_get_temp_dir(),
),
)));
$base64 = Daito\Lib\DaitoBarcode::generateBarcodeQrCode('HELLO-QR');
echo substr($base64, 0, 80) . PHP_EOL;
echo 'length: ' . strlen($base64) . PHP_EOL;
echo Daito\Lib\DaitoMath::div(1, 2) . PHP_EOL;
\ No newline at end of file