Showing
12 changed files
with
833 additions
and
8 deletions
| ... | @@ -89,4 +89,63 @@ sudo systemctl restart php-fpm || sudo systemctl restart httpd | ... | @@ -89,4 +89,63 @@ sudo systemctl restart php-fpm || sudo systemctl restart httpd |
| 89 | extension=gmp | 89 | extension=gmp |
| 90 | extension=bcmath | 90 | extension=bcmath |
| 91 | # 3) Restart web server / PHP-FPM service | 91 | # 3) Restart web server / PHP-FPM service |
| 92 | -``` | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 92 | +``` | ||
| 93 | + | ||
| 94 | +## QueryLog (Laravel shared package) | ||
| 95 | + | ||
| 96 | +### 1) Publish config in child project | ||
| 97 | +```bash | ||
| 98 | +php artisan vendor:publish --tag=query-log-config | ||
| 99 | +``` | ||
| 100 | + | ||
| 101 | +This creates `config/query_log.php` so each project can tune its own settings. | ||
| 102 | + | ||
| 103 | +### 2) Publish migration in child project | ||
| 104 | +```bash | ||
| 105 | +php artisan vendor:publish --tag=query-log-migrations | ||
| 106 | +php artisan migrate | ||
| 107 | +``` | ||
| 108 | + | ||
| 109 | +This publishes a production-oriented migration for `log_queries` with key indexes: | ||
| 110 | + | ||
| 111 | +- `query_at` | ||
| 112 | +- `query_type` | ||
| 113 | +- `connection` | ||
| 114 | +- `user_id` | ||
| 115 | +- composite indexes for common filter windows | ||
| 116 | + | ||
| 117 | +### 3) Minimal table fields | ||
| 118 | + | ||
| 119 | +Use your own migration and ensure these columns exist in the configured table (`query_log.table`): | ||
| 120 | + | ||
| 121 | +- `action` (string) | ||
| 122 | +- `query` (longText/text) | ||
| 123 | +- `query_type` (string, example: `insert`, `update`, `delete`, `replace`) | ||
| 124 | +- `query_time` (float/double) | ||
| 125 | +- `query_at` (datetime) | ||
| 126 | +- `query_order` (int) | ||
| 127 | +- `connection` (string) | ||
| 128 | +- `ip` (nullable string) | ||
| 129 | +- `user_id` (nullable string/int) | ||
| 130 | +- `is_screen` (tinyint/bool) | ||
| 131 | + | ||
| 132 | +### 4) Important config for production | ||
| 133 | + | ||
| 134 | +- `enable`: enable/disable query log | ||
| 135 | +- `min_time`: skip very fast queries (ms) | ||
| 136 | +- `sample_rate`: sampling percent (`0-100`) to reduce high-traffic load | ||
| 137 | +- `chunk`: batch size per queue job | ||
| 138 | +- `max_queries_per_request`: hard limit per request | ||
| 139 | +- `skip_route_patterns`: wildcard route/url patterns to skip | ||
| 140 | +- `skip_command_patterns`: wildcard console command patterns to skip | ||
| 141 | +- `mask_sensitive_bindings`: mask sensitive values in SQL bindings | ||
| 142 | +- `sensitive_keywords`: keyword list used for masking | ||
| 143 | +- `masked_value`: replacement text for sensitive bindings | ||
| 144 | + | ||
| 145 | +### 5) Behavior highlights | ||
| 146 | + | ||
| 147 | +- Query buffer is separated by DB connection. | ||
| 148 | +- Buffer is flushed when transaction commits (outermost level). | ||
| 149 | +- Buffer is cleared on rollback (rolled-back queries are not logged). | ||
| 150 | +- Write-query detection supports CTE (`WITH ... UPDATE/INSERT/...`) and skips read queries. | ||
| 151 | +- `query_type` stores action text directly (`insert`, `update`, `delete`, `replace`, `upsert`). | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -10,10 +10,18 @@ | ... | @@ -10,10 +10,18 @@ |
| 10 | "require": { | 10 | "require": { |
| 11 | "brick/math": "^0.9 || ^0.10 || ^0.11 || ^0.13", | 11 | "brick/math": "^0.9 || ^0.10 || ^0.11 || ^0.13", |
| 12 | "php": "^7.3 || ^8.0", | 12 | "php": "^7.3 || ^8.0", |
| 13 | - "laravel/framework": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0" | 13 | + "laravel/framework": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", |
| 14 | + "milon/barcode": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0" | ||
| 14 | }, | 15 | }, |
| 15 | "suggest": { | 16 | "suggest": { |
| 16 | "ext-gmp": "Faster big-number calculations", | 17 | "ext-gmp": "Faster big-number calculations", |
| 17 | "ext-bcmath": "Faster decimal calculations when GMP is unavailable" | 18 | "ext-bcmath": "Faster decimal calculations when GMP is unavailable" |
| 19 | + }, | ||
| 20 | + "extra": { | ||
| 21 | + "laravel": { | ||
| 22 | + "providers": [ | ||
| 23 | + "Daito\\Lib\\QueryLog\\Providers\\QueryLogProvider" | ||
| 24 | + ] | ||
| 25 | + } | ||
| 18 | } | 26 | } |
| 19 | } | 27 | } | ... | ... |
| ... | @@ -4,7 +4,7 @@ | ... | @@ -4,7 +4,7 @@ |
| 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", | 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", |
| 5 | "This file is @generated automatically" | 5 | "This file is @generated automatically" |
| 6 | ], | 6 | ], |
| 7 | - "content-hash": "0c23b9705a5fe293b678d1bbc5020207", | 7 | + "content-hash": "94cb3df5cd945ab2de60f59263dbd8d6", |
| 8 | "packages": [ | 8 | "packages": [ |
| 9 | { | 9 | { |
| 10 | "name": "brick/math", | 10 | "name": "brick/math", |
| ... | @@ -1144,6 +1144,81 @@ | ... | @@ -1144,6 +1144,81 @@ |
| 1144 | "time": "2024-09-21T08:32:55+00:00" | 1144 | "time": "2024-09-21T08:32:55+00:00" |
| 1145 | }, | 1145 | }, |
| 1146 | { | 1146 | { |
| 1147 | + "name": "milon/barcode", | ||
| 1148 | + "version": "v12.1.0", | ||
| 1149 | + "source": { | ||
| 1150 | + "type": "git", | ||
| 1151 | + "url": "https://github.com/milon/barcode.git", | ||
| 1152 | + "reference": "052e601665cfb99e119a630b6116fab0eb30183e" | ||
| 1153 | + }, | ||
| 1154 | + "dist": { | ||
| 1155 | + "type": "zip", | ||
| 1156 | + "url": "https://api.github.com/repos/milon/barcode/zipball/052e601665cfb99e119a630b6116fab0eb30183e", | ||
| 1157 | + "reference": "052e601665cfb99e119a630b6116fab0eb30183e", | ||
| 1158 | + "shasum": "" | ||
| 1159 | + }, | ||
| 1160 | + "require": { | ||
| 1161 | + "ext-gd": "*", | ||
| 1162 | + "illuminate/support": "^7.0|^8.0|^9.0|^10.0 | ^11.0 | ^12.0", | ||
| 1163 | + "php": "^7.3 | ^8.0" | ||
| 1164 | + }, | ||
| 1165 | + "type": "library", | ||
| 1166 | + "extra": { | ||
| 1167 | + "laravel": { | ||
| 1168 | + "aliases": { | ||
| 1169 | + "DNS1D": "Milon\\Barcode\\Facades\\DNS1DFacade", | ||
| 1170 | + "DNS2D": "Milon\\Barcode\\Facades\\DNS2DFacade" | ||
| 1171 | + }, | ||
| 1172 | + "providers": [ | ||
| 1173 | + "Milon\\Barcode\\BarcodeServiceProvider" | ||
| 1174 | + ] | ||
| 1175 | + } | ||
| 1176 | + }, | ||
| 1177 | + "autoload": { | ||
| 1178 | + "psr-0": { | ||
| 1179 | + "Milon\\Barcode": "src/" | ||
| 1180 | + } | ||
| 1181 | + }, | ||
| 1182 | + "notification-url": "https://packagist.org/downloads/", | ||
| 1183 | + "license": [ | ||
| 1184 | + "LGPL-3.0" | ||
| 1185 | + ], | ||
| 1186 | + "authors": [ | ||
| 1187 | + { | ||
| 1188 | + "name": "Nuruzzaman Milon", | ||
| 1189 | + "email": "contact@milon.im" | ||
| 1190 | + } | ||
| 1191 | + ], | ||
| 1192 | + "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)", | ||
| 1193 | + "keywords": [ | ||
| 1194 | + "CODABAR", | ||
| 1195 | + "CODE 128", | ||
| 1196 | + "CODE 39", | ||
| 1197 | + "barcode", | ||
| 1198 | + "datamatrix", | ||
| 1199 | + "ean", | ||
| 1200 | + "laravel", | ||
| 1201 | + "pdf417", | ||
| 1202 | + "qr code", | ||
| 1203 | + "qrcode" | ||
| 1204 | + ], | ||
| 1205 | + "support": { | ||
| 1206 | + "issues": "https://github.com/milon/barcode/issues", | ||
| 1207 | + "source": "https://github.com/milon/barcode/tree/v12.1.0" | ||
| 1208 | + }, | ||
| 1209 | + "funding": [ | ||
| 1210 | + { | ||
| 1211 | + "url": "https://paypal.me/nuruzzamanmilon", | ||
| 1212 | + "type": "custom" | ||
| 1213 | + }, | ||
| 1214 | + { | ||
| 1215 | + "url": "https://github.com/milon", | ||
| 1216 | + "type": "github" | ||
| 1217 | + } | ||
| 1218 | + ], | ||
| 1219 | + "time": "2026-02-07T00:42:12+00:00" | ||
| 1220 | + }, | ||
| 1221 | + { | ||
| 1147 | "name": "monolog/monolog", | 1222 | "name": "monolog/monolog", |
| 1148 | "version": "2.11.0", | 1223 | "version": "2.11.0", |
| 1149 | "source": { | 1224 | "source": { | ... | ... |
src/DaitoBarcode.php
0 → 100644
| 1 | +<?php | ||
| 2 | +namespace Daito\Lib; | ||
| 3 | + | ||
| 4 | +use Milon\Barcode\DNS1D; | ||
| 5 | +use Milon\Barcode\DNS2D; | ||
| 6 | + | ||
| 7 | +class DaitoBarcode { | ||
| 8 | + public static function getJan13Number($jan12) | ||
| 9 | + { | ||
| 10 | + // Kiểm tra định dạng | ||
| 11 | + if (!preg_match('/^\d{12}$/', $jan12)) { | ||
| 12 | + return $jan12; | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + $sum = 0; | ||
| 16 | + for ($i = 0; $i < 12; $i++) { | ||
| 17 | + $digit = (int) $jan12[$i]; | ||
| 18 | + $sum += $i % 2 === 0 ? $digit : $digit * 3; | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + $checkDigit = (10 - ($sum % 10)) % 10; | ||
| 22 | + | ||
| 23 | + return $jan12 . $checkDigit; | ||
| 24 | + } | ||
| 25 | + public static function isJanEAN13($jan) | ||
| 26 | + { | ||
| 27 | + if (!preg_match('/^\d{13}$/', $jan)) { | ||
| 28 | + return false; | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + $jan12 = substr($jan, 0, 12); | ||
| 32 | + $ean13Jan = self::getJan13Number($jan12); | ||
| 33 | + return $ean13Jan === $jan; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + public static function getJan8Number($jan7) | ||
| 37 | + { | ||
| 38 | + if (!preg_match('/^\d{7}$/', $jan7)) { | ||
| 39 | + return $jan7; | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + $sum = 0; | ||
| 43 | + for ($i = 0; $i < 7; $i++) { | ||
| 44 | + $digit = (int) $jan7[$i]; | ||
| 45 | + $sum += $i % 2 === 0 ? $digit * 3 : $digit; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + $checkDigit = (10 - ($sum % 10)) % 10; | ||
| 49 | + | ||
| 50 | + return $jan7 . $checkDigit; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + public static function isJanEAN8($jan) | ||
| 54 | + { | ||
| 55 | + if (!preg_match('/^\d{8}$/', $jan)) { | ||
| 56 | + return false; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + $jan7 = substr($jan, 0, 7); | ||
| 60 | + $ean8Jan = self::getJan8Number($jan7); | ||
| 61 | + | ||
| 62 | + return $ean8Jan === $jan; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + public static function getBarcodeTypeJan($jan) | ||
| 66 | + { | ||
| 67 | + if (self::isJanEAN8($jan)) { | ||
| 68 | + return 'EAN8'; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + if (self::isJanEAN13($jan)) { | ||
| 72 | + return 'EAN13'; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + return 'C128'; | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + public static function generateBarcodeJan($jan) | ||
| 79 | + { | ||
| 80 | + $typeJan = self::getBarcodeTypeJan($jan); | ||
| 81 | + | ||
| 82 | + if ($typeJan === 'EAN13') { | ||
| 83 | + $jan = substr($jan, 0, 12); | ||
| 84 | + } elseif ($typeJan === 'EAN8') { | ||
| 85 | + $jan = substr($jan, 0, 7); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + $dns1d = new DNS1D(); | ||
| 89 | + | ||
| 90 | + return $dns1d->getBarcodePNG($jan, $typeJan); | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + public static function generateBarcodeC128($text) | ||
| 94 | + { | ||
| 95 | + $dns1d = new DNS1D(); | ||
| 96 | + | ||
| 97 | + return $dns1d->getBarcodePNG($text, 'C128'); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + public static function generateBarcodeC39($text) | ||
| 101 | + { | ||
| 102 | + $dns1d = new DNS1D(); | ||
| 103 | + | ||
| 104 | + return $dns1d->getBarcodePNG($text, 'C39'); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + public static function generateBarcodeEAN13($ean13) | ||
| 108 | + { | ||
| 109 | + $dns1d = new DNS1D(); | ||
| 110 | + | ||
| 111 | + return $dns1d->getBarcodePNG($ean13, 'EAN13'); | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + public static function generateBarcodeEAN8($ean8) | ||
| 115 | + { | ||
| 116 | + $dns1d = new DNS1D(); | ||
| 117 | + | ||
| 118 | + return $dns1d->getBarcodePNG($ean8, 'EAN8'); | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + public static function generateBarcodeQrCode($text) | ||
| 122 | + { | ||
| 123 | + $dns2d = new DNS2D(); | ||
| 124 | + | ||
| 125 | + return $dns2d->getBarcodePNG($text, 'QRCODE'); | ||
| 126 | + } | ||
| 127 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/DaitoNumber.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Daito\Lib; | ||
| 4 | + | ||
| 5 | +use Brick\Math\BigDecimal; | ||
| 6 | +use Brick\Math\RoundingMode; | ||
| 7 | +use RuntimeException; | ||
| 8 | + | ||
| 9 | +class DaitoNumber | ||
| 10 | +{ | ||
| 11 | + const DEFAULT_MAX_DECIMALS = 12; | ||
| 12 | + | ||
| 13 | + /** | ||
| 14 | + * Format number with configurable separators, prefix/suffix, and decimal behavior. | ||
| 15 | + * | ||
| 16 | + * Example: | ||
| 17 | + * DaitoNumber::format('1234567.00') => '1,234,567' | ||
| 18 | + * DaitoNumber::format('1234567.5', array('decimals' => 2)) => '1,234,567.50' | ||
| 19 | + */ | ||
| 20 | + public static function format($number, array $arrOptions = array()) | ||
| 21 | + { | ||
| 22 | + $arrConfig = array_merge( | ||
| 23 | + array( | ||
| 24 | + 'thousands_separator' => ',', | ||
| 25 | + 'decimal_separator' => '.', | ||
| 26 | + 'prefix' => '', | ||
| 27 | + 'suffix' => '', | ||
| 28 | + 'decimals' => null, | ||
| 29 | + 'trim_trailing_zeros' => true, | ||
| 30 | + 'max_decimals' => self::DEFAULT_MAX_DECIMALS, | ||
| 31 | + ), | ||
| 32 | + $arrOptions | ||
| 33 | + ); | ||
| 34 | + | ||
| 35 | + $numberString = self::toCanonicalString($number); | ||
| 36 | + $isNegative = strpos($numberString, '-') === 0; | ||
| 37 | + $unsigned = $isNegative ? substr($numberString, 1) : $numberString; | ||
| 38 | + | ||
| 39 | + if ($arrConfig['decimals'] !== null) { | ||
| 40 | + $decimals = (int) $arrConfig['decimals']; | ||
| 41 | + if ($decimals < 0) { | ||
| 42 | + throw new RuntimeException('decimals must be greater than or equal to 0.'); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + $unsigned = (string) BigDecimal::of($unsigned)->toScale($decimals, RoundingMode::HALF_UP); | ||
| 46 | + } else { | ||
| 47 | + $maxDecimals = (int) $arrConfig['max_decimals']; | ||
| 48 | + if ($maxDecimals < 0) { | ||
| 49 | + throw new RuntimeException('max_decimals must be greater than or equal to 0.'); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + $unsigned = (string) BigDecimal::of($unsigned)->toScale($maxDecimals, RoundingMode::HALF_UP); | ||
| 53 | + if ($arrConfig['trim_trailing_zeros']) { | ||
| 54 | + $unsigned = self::trimTrailingZeros($unsigned); | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + $arrParts = explode('.', $unsigned, 2); | ||
| 59 | + $integerPart = self::addThousandsSeparator($arrParts[0], (string) $arrConfig['thousands_separator']); | ||
| 60 | + $decimalPart = isset($arrParts[1]) ? $arrParts[1] : ''; | ||
| 61 | + | ||
| 62 | + $formattedNumber = $integerPart; | ||
| 63 | + if ($decimalPart !== '') { | ||
| 64 | + $formattedNumber .= (string) $arrConfig['decimal_separator'] . $decimalPart; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + $formatted = (string) $arrConfig['prefix'] . $formattedNumber . (string) $arrConfig['suffix']; | ||
| 68 | + | ||
| 69 | + return $isNegative ? '-' . $formatted : $formatted; | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * Format currency-friendly output (default 2 decimals, keep trailing zeros). | ||
| 74 | + * | ||
| 75 | + * Example: | ||
| 76 | + * DaitoNumber::formatCurrency('1234.5') => '1,234.50' | ||
| 77 | + * DaitoNumber::formatCurrency('1234.5', array('prefix' => '$')) => '$1,234.50' | ||
| 78 | + */ | ||
| 79 | + public static function formatCurrency($number, array $arrOptions = array()) | ||
| 80 | + { | ||
| 81 | + $arrCurrencyOptions = array_merge( | ||
| 82 | + array( | ||
| 83 | + 'decimals' => 2, | ||
| 84 | + 'trim_trailing_zeros' => false, | ||
| 85 | + 'prefix' => '', | ||
| 86 | + 'suffix' => '', | ||
| 87 | + ), | ||
| 88 | + $arrOptions | ||
| 89 | + ); | ||
| 90 | + | ||
| 91 | + return self::format($number, $arrCurrencyOptions); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + /** | ||
| 95 | + * Format percentage value. | ||
| 96 | + * | ||
| 97 | + * Example: | ||
| 98 | + * DaitoNumber::formatPercent('12.3456') => '12.35%' | ||
| 99 | + * DaitoNumber::formatPercent('0.1234', array('input_ratio' => true)) => '12.34%' | ||
| 100 | + */ | ||
| 101 | + public static function formatPercent($number, array $arrOptions = array()) | ||
| 102 | + { | ||
| 103 | + $arrPercentOptions = array_merge( | ||
| 104 | + array( | ||
| 105 | + 'decimals' => 2, | ||
| 106 | + 'trim_trailing_zeros' => true, | ||
| 107 | + 'suffix' => '%', | ||
| 108 | + 'input_ratio' => false, | ||
| 109 | + ), | ||
| 110 | + $arrOptions | ||
| 111 | + ); | ||
| 112 | + | ||
| 113 | + $numberValue = $number; | ||
| 114 | + if ($arrPercentOptions['input_ratio']) { | ||
| 115 | + $numberValue = (string) BigDecimal::of(self::toCanonicalString($number))->multipliedBy('100'); | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + unset($arrPercentOptions['input_ratio']); | ||
| 119 | + | ||
| 120 | + return self::format($numberValue, $arrPercentOptions); | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + private static function toCanonicalString($number) | ||
| 124 | + { | ||
| 125 | + if (is_int($number) || is_string($number)) { | ||
| 126 | + $numberString = trim((string) $number); | ||
| 127 | + } elseif (is_float($number)) { | ||
| 128 | + $numberString = self::trimTrailingZeros(sprintf('%.14F', $number)); | ||
| 129 | + } else { | ||
| 130 | + throw new RuntimeException('DaitoNumber only supports int, float, or numeric string.'); | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + if ($numberString === '' || !preg_match('/^[+-]?\d+(\.\d+)?$/', $numberString)) { | ||
| 134 | + throw new RuntimeException('Invalid number format.'); | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + return (string) BigDecimal::of($numberString); | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + private static function addThousandsSeparator($integerPart, $thousandsSeparator) | ||
| 141 | + { | ||
| 142 | + return preg_replace('/\B(?=(\d{3})+(?!\d))/', $thousandsSeparator, $integerPart); | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + private static function trimTrailingZeros($numberString) | ||
| 146 | + { | ||
| 147 | + if (strpos($numberString, '.') === false) { | ||
| 148 | + return $numberString; | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + $numberString = rtrim($numberString, '0'); | ||
| 152 | + $numberString = rtrim($numberString, '.'); | ||
| 153 | + | ||
| 154 | + return $numberString === '' ? '0' : $numberString; | ||
| 155 | + } | ||
| 156 | +} |
| 1 | <?php | 1 | <?php |
| 2 | + | ||
| 2 | namespace Daito\Lib; | 3 | namespace Daito\Lib; |
| 3 | 4 | ||
| 4 | -class DaitoString { | 5 | +use RuntimeException; |
| 5 | - public static function toUpper($text){ | 6 | + |
| 6 | - return strtoupper($text); | 7 | +class DaitoString |
| 8 | +{ | ||
| 9 | + const UTF8 = 'UTF-8'; | ||
| 10 | + const NKF_SOURCE_ENCODINGS = 'SJIS-win,CP932,Shift_JIS,EUC-JP,UTF-8'; | ||
| 11 | + | ||
| 12 | + /** | ||
| 13 | + * Collapse multiple spaces/tabs/newlines into a single ASCII space. | ||
| 14 | + * | ||
| 15 | + * Example: | ||
| 16 | + * DaitoString::collapseSpaces("a b\t\tc") => "a b c" | ||
| 17 | + */ | ||
| 18 | + public static function collapseSpaces($input) | ||
| 19 | + { | ||
| 20 | + return preg_replace('/\s+/u', ' ', (string) $input); | ||
| 7 | } | 21 | } |
| 8 | - public static function toLower($text){ | 22 | + |
| 9 | - return strtolower($text); | 23 | + /** |
| 24 | + * Convert full-width Japanese spaces to ASCII spaces. | ||
| 25 | + * | ||
| 26 | + * Example: | ||
| 27 | + * DaitoString::normalizeFullWidthSpace("A B") => "A B" | ||
| 28 | + */ | ||
| 29 | + public static function normalizeFullWidthSpace($input) | ||
| 30 | + { | ||
| 31 | + return str_replace(' ', ' ', (string) $input); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * Convert half-width kana to full-width kana. | ||
| 36 | + * | ||
| 37 | + * Example: | ||
| 38 | + * DaitoString::toFullWidthKana("カタカナ") => "カタカナ" | ||
| 39 | + */ | ||
| 40 | + public static function toFullWidthKana($text) | ||
| 41 | + { | ||
| 42 | + return mb_convert_kana((string) $text, 'KV', self::UTF8); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * Convert full-width kana to half-width kana. | ||
| 47 | + * | ||
| 48 | + * Example: | ||
| 49 | + * DaitoString::toHalfWidthKana("カタカナ") => "カタカナ" | ||
| 50 | + */ | ||
| 51 | + public static function toHalfWidthKana($text) | ||
| 52 | + { | ||
| 53 | + return mb_convert_kana((string) $text, 'kV', self::UTF8); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * Split text by normalized spaces (full-width/extra spaces are handled). | ||
| 58 | + * | ||
| 59 | + * Example: | ||
| 60 | + * DaitoString::splitBySpace("A B C") => array("A", "B", "C") | ||
| 61 | + */ | ||
| 62 | + public static function splitBySpace($string) | ||
| 63 | + { | ||
| 64 | + $normalized = self::normalizeFullWidthSpace((string) $string); | ||
| 65 | + $normalized = trim(self::collapseSpaces($normalized)); | ||
| 66 | + | ||
| 67 | + if ($normalized === '') { | ||
| 68 | + return array(); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + return explode(' ', $normalized); | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + /** | ||
| 75 | + * Convert Hiragana to Katakana. | ||
| 76 | + * | ||
| 77 | + * Example: | ||
| 78 | + * DaitoString::toKatakana("ひらがな") => "ヒラガナ" | ||
| 79 | + */ | ||
| 80 | + public static function toKatakana($input) | ||
| 81 | + { | ||
| 82 | + return mb_convert_kana((string) $input, 'C', self::UTF8); | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + /** | ||
| 86 | + * Check whether the input contains Japanese characters. | ||
| 87 | + * | ||
| 88 | + * Example: | ||
| 89 | + * DaitoString::isJapanese("abc日本語") => true | ||
| 90 | + * DaitoString::isJapanese("abcdef") => false | ||
| 91 | + */ | ||
| 92 | + public static function isJapanese($input) | ||
| 93 | + { | ||
| 94 | + return preg_match('/[\x{3040}-\x{30FF}\x{3400}-\x{4DBF}\x{4E00}-\x{9FFF}\x{FF66}-\x{FF9D}]/u', (string) $input) === 1; | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + /** | ||
| 98 | + * Convert Japanese-encoded text file to UTF-8. | ||
| 99 | + * Prefer nkf when available, fallback to mb_convert_encoding. | ||
| 100 | + * | ||
| 101 | + * Example: | ||
| 102 | + * DaitoString::convertToUtf8('/tmp/source.csv'); | ||
| 103 | + * DaitoString::convertToUtf8('/tmp/source.csv', '/tmp/out', 'source_utf8.csv', 1); | ||
| 104 | + */ | ||
| 105 | + public static function convertToUtf8($sourceFile, $destDir = '', $fileName = '', $isBk = 0) | ||
| 106 | + { | ||
| 107 | + $sourceFilePath = (string) $sourceFile; | ||
| 108 | + if (!is_file($sourceFilePath)) { | ||
| 109 | + throw new RuntimeException('Source file does not exist: ' . $sourceFilePath); | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + $targetDirectory = $destDir ? (string) $destDir : dirname($sourceFilePath); | ||
| 113 | + if (!is_dir($targetDirectory)) { | ||
| 114 | + throw new RuntimeException('Destination directory does not exist: ' . $targetDirectory); | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + $targetFileName = $fileName ? (string) $fileName : basename($sourceFilePath); | ||
| 118 | + $destFile = $targetDirectory . DIRECTORY_SEPARATOR . $targetFileName; | ||
| 119 | + | ||
| 120 | + if ((string) realpath($sourceFilePath) === (string) realpath($destFile)) { | ||
| 121 | + $destFile .= '_' . time(); | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + if ($isBk) { | ||
| 125 | + $backupFile = $targetDirectory . DIRECTORY_SEPARATOR . $targetFileName . '.bak'; | ||
| 126 | + if (!copy($sourceFilePath, $backupFile)) { | ||
| 127 | + throw new RuntimeException('Can not create backup file: ' . $backupFile); | ||
| 128 | + } | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + $utf8Content = self::convertJapaneseTextToUtf8($sourceFilePath); | ||
| 132 | + if (file_put_contents($destFile, $utf8Content) === false) { | ||
| 133 | + throw new RuntimeException('Can not write destination file: ' . $destFile); | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + return $destFile; | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + private static function convertJapaneseTextToUtf8($sourceFilePath) | ||
| 140 | + { | ||
| 141 | + if (self::canUseNkf()) { | ||
| 142 | + $nkfContent = self::convertFileByNkf($sourceFilePath); | ||
| 143 | + if ($nkfContent !== null) { | ||
| 144 | + return $nkfContent; | ||
| 145 | + } | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + return self::convertFileToUtf8ByMbstring($sourceFilePath); | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + private static function canUseNkf() | ||
| 152 | + { | ||
| 153 | + if (!function_exists('shell_exec')) { | ||
| 154 | + return false; | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + $disabledFunctions = (string) ini_get('disable_functions'); | ||
| 158 | + if ($disabledFunctions !== '' && strpos($disabledFunctions, 'shell_exec') !== false) { | ||
| 159 | + return false; | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | + $output = @shell_exec('nkf --version'); | ||
| 163 | + | ||
| 164 | + return is_string($output) && $output !== ''; | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + private static function convertFileByNkf($sourceFilePath) | ||
| 168 | + { | ||
| 169 | + $command = 'nkf -w -- ' . escapeshellarg($sourceFilePath); | ||
| 170 | + $output = @shell_exec($command); | ||
| 171 | + | ||
| 172 | + return is_string($output) ? $output : null; | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + /** | ||
| 176 | + * Convert a file content to UTF-8 using mbstring. | ||
| 177 | + * | ||
| 178 | + * Example: | ||
| 179 | + * DaitoString::convertFileToUtf8ByMbstring('/tmp/source_sjis.txt'); | ||
| 180 | + */ | ||
| 181 | + public static function convertFileToUtf8ByMbstring($sourceFilePath) | ||
| 182 | + { | ||
| 183 | + $content = file_get_contents($sourceFilePath); | ||
| 184 | + if ($content === false) { | ||
| 185 | + throw new RuntimeException('Can not read source file: ' . $sourceFilePath); | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + return self::convertTextToUtf8ByMbstring($content); | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + /** | ||
| 192 | + * Convert raw text to UTF-8 using mbstring. | ||
| 193 | + * | ||
| 194 | + * Example: | ||
| 195 | + * DaitoString::convertTextToUtf8ByMbstring($rawText); | ||
| 196 | + */ | ||
| 197 | + public static function convertTextToUtf8ByMbstring($text) | ||
| 198 | + { | ||
| 199 | + $utf8Content = mb_convert_encoding((string) $text, self::UTF8, self::NKF_SOURCE_ENCODINGS); | ||
| 200 | + if ($utf8Content === false) { | ||
| 201 | + throw new RuntimeException('Can not convert text to UTF-8.'); | ||
| 202 | + } | ||
| 203 | + | ||
| 204 | + return $utf8Content; | ||
| 10 | } | 205 | } |
| 11 | } | 206 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
src/QueryLog/Jobs/SaveQueryLogJob.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Daito\Lib\QueryLog\Jobs; | ||
| 4 | + | ||
| 5 | +use Illuminate\Contracts\Queue\ShouldQueue; | ||
| 6 | +use Illuminate\Bus\Queueable; | ||
| 7 | +use Illuminate\Foundation\Bus\Dispatchable; | ||
| 8 | +use Illuminate\Queue\InteractsWithQueue; | ||
| 9 | +use Illuminate\Queue\SerializesModels; | ||
| 10 | +use Illuminate\Support\Facades\DB; | ||
| 11 | + | ||
| 12 | +class SaveQueryLogJob implements ShouldQueue | ||
| 13 | +{ | ||
| 14 | + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||
| 15 | + | ||
| 16 | + protected $arrQueries; | ||
| 17 | + | ||
| 18 | + public function __construct(array $arrQueries) | ||
| 19 | + { | ||
| 20 | + $this->arrQueries = $arrQueries; | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + public function handle(): void | ||
| 24 | + { | ||
| 25 | + if ($this->arrQueries === array()) { | ||
| 26 | + return; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + DB::connection(config('query_log.connection', 'query_log')) | ||
| 30 | + ->table(config('query_log.table', 'log_queries')) | ||
| 31 | + ->insert($this->arrQueries); | ||
| 32 | + } | ||
| 33 | +} |
src/QueryLog/Models/QueryLog.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Daito\Lib\QueryLog\Models; | ||
| 4 | + | ||
| 5 | +use Illuminate\Database\Eloquent\Model; | ||
| 6 | + | ||
| 7 | +class QueryLog extends Model | ||
| 8 | +{ | ||
| 9 | + /** | ||
| 10 | + * The table associated with the model. | ||
| 11 | + * | ||
| 12 | + * @var string | ||
| 13 | + */ | ||
| 14 | + protected $table = 'log_queries'; | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * The primary key for the model. | ||
| 18 | + * | ||
| 19 | + * @var string | ||
| 20 | + */ | ||
| 21 | + protected $primaryKey = 'id'; | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * Indicates if the IDs are auto-incrementing. | ||
| 25 | + * | ||
| 26 | + * @var bool | ||
| 27 | + */ | ||
| 28 | + public $incrementing = true; | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * Indicates if the IDs are auto-incrementing. | ||
| 32 | + * | ||
| 33 | + * @var bool | ||
| 34 | + */ | ||
| 35 | + public $timestamps = false; | ||
| 36 | + | ||
| 37 | + /** | ||
| 38 | + * The attributes that are mass assignable. | ||
| 39 | + * | ||
| 40 | + * @var array<int, string> | ||
| 41 | + */ | ||
| 42 | + protected $fillable = [ | ||
| 43 | + 'action', | ||
| 44 | + 'query', | ||
| 45 | + 'query_type', | ||
| 46 | + 'query_time', | ||
| 47 | + 'query_at', | ||
| 48 | + 'query_order', | ||
| 49 | + 'connection', | ||
| 50 | + 'ip', | ||
| 51 | + 'user_id', | ||
| 52 | + 'is_screen', | ||
| 53 | + ]; | ||
| 54 | + protected $connection = 'query_log'; | ||
| 55 | + | ||
| 56 | + public function __construct(array $arrAttributes = array()) | ||
| 57 | + { | ||
| 58 | + parent::__construct($arrAttributes); | ||
| 59 | + | ||
| 60 | + $this->setConnection(config('query_log.connection', 'query_log')); | ||
| 61 | + $this->setTable(config('query_log.table', 'log_queries')); | ||
| 62 | + } | ||
| 63 | +} |
src/QueryLog/Providers/QueryLogProvider.php
0 → 100644
This diff is collapsed.
Click to expand it.
src/QueryLog/config/query_log.php
0 → 100644
| 1 | +<?php | ||
| 2 | +return [ | ||
| 3 | + 'enable' => env('ENABLE_QUERY_LOG', false), | ||
| 4 | + 'log_on_console' => env('QUERY_LOG_ON_CONSOLE', false), | ||
| 5 | + 'sample_rate' => env('QUERY_LOG_SAMPLE_RATE', 100), // 0-100 (%) | ||
| 6 | + 'chunk' => env('QUERY_LOG_CHUNK', 200), | ||
| 7 | + 'max_queries_per_request' => env('QUERY_LOG_MAX_PER_REQUEST', 1000), | ||
| 8 | + 'min_time' => env('QUERY_LOG_MIN_TIME', 0), | ||
| 9 | + 'connection' => env('QUERY_LOG_CONNECTION', 'query_log'), | ||
| 10 | + 'table' => env('QUERY_LOG_TABLE', 'log_queries'), | ||
| 11 | + 'queue_connection' => env('QUERY_LOG_QUEUE_CONNECTION', null), | ||
| 12 | + 'queue_name' => env('QUERY_LOG_QUEUE_NAME', null), | ||
| 13 | + 'max_sql_length' => env('QUERY_LOG_MAX_SQL_LENGTH', 4000), | ||
| 14 | + 'mask_sensitive_bindings' => env('QUERY_LOG_MASK_SENSITIVE_BINDINGS', true), | ||
| 15 | + 'sensitive_keywords' => array( | ||
| 16 | + 'password', | ||
| 17 | + 'passwd', | ||
| 18 | + 'pwd', | ||
| 19 | + 'token', | ||
| 20 | + 'secret', | ||
| 21 | + 'api_key', | ||
| 22 | + 'apikey', | ||
| 23 | + 'authorization', | ||
| 24 | + 'cookie', | ||
| 25 | + 'credit_card', | ||
| 26 | + 'card_number', | ||
| 27 | + 'cvv', | ||
| 28 | + 'pin', | ||
| 29 | + ), | ||
| 30 | + 'masked_value' => '***', | ||
| 31 | + 'skip_route_patterns' => array( | ||
| 32 | + 'horizon*', | ||
| 33 | + 'telescope*', | ||
| 34 | + '_debugbar*', | ||
| 35 | + ), | ||
| 36 | + 'skip_command_patterns' => array( | ||
| 37 | + 'queue:*', | ||
| 38 | + 'horizon*', | ||
| 39 | + 'schedule:run', | ||
| 40 | + ), | ||
| 41 | + 'ignore_tables' => array( | ||
| 42 | + 'log_queries', | ||
| 43 | + 'jobs', | ||
| 44 | + 'failed_jobs', | ||
| 45 | + 'migrations', | ||
| 46 | + 'mst_batch', | ||
| 47 | + ), | ||
| 48 | +]; |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +use Illuminate\Database\Migrations\Migration; | ||
| 4 | +use Illuminate\Database\Schema\Blueprint; | ||
| 5 | +use Illuminate\Support\Facades\Schema; | ||
| 6 | + | ||
| 7 | +return new class extends Migration { | ||
| 8 | + public function up(): void | ||
| 9 | + { | ||
| 10 | + $connection = config('query_log.connection', 'query_log'); | ||
| 11 | + $table = config('query_log.table', 'log_queries'); | ||
| 12 | + | ||
| 13 | + Schema::connection($connection)->create($table, function (Blueprint $table) { | ||
| 14 | + $table->bigIncrements('id'); | ||
| 15 | + $table->string('action', 512)->nullable(); | ||
| 16 | + $table->longText('query'); | ||
| 17 | + $table->string('query_type', 32); | ||
| 18 | + $table->decimal('query_time', 10, 3)->default(0); | ||
| 19 | + $table->dateTime('query_at'); | ||
| 20 | + $table->unsignedInteger('query_order')->default(0); | ||
| 21 | + $table->string('connection', 64)->nullable(); | ||
| 22 | + $table->string('ip', 64)->nullable(); | ||
| 23 | + $table->string('user_id', 64)->nullable(); | ||
| 24 | + $table->boolean('is_screen')->default(false); | ||
| 25 | + | ||
| 26 | + $table->index('query_at', 'idx_log_queries_query_at'); | ||
| 27 | + $table->index('query_type', 'idx_log_queries_query_type'); | ||
| 28 | + $table->index('connection', 'idx_log_queries_connection'); | ||
| 29 | + $table->index('user_id', 'idx_log_queries_user_id'); | ||
| 30 | + $table->index(array('query_at', 'query_type'), 'idx_log_queries_at_type'); | ||
| 31 | + $table->index(array('connection', 'query_at'), 'idx_log_queries_connection_at'); | ||
| 32 | + }); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public function down(): void | ||
| 36 | + { | ||
| 37 | + $connection = config('query_log.connection', 'query_log'); | ||
| 38 | + $table = config('query_log.table', 'log_queries'); | ||
| 39 | + | ||
| 40 | + Schema::connection($connection)->dropIfExists($table); | ||
| 41 | + } | ||
| 42 | +}; |
test.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +require __DIR__ . '/vendor/autoload.php'; | ||
| 4 | + | ||
| 5 | +$container = new Illuminate\Container\Container(); | ||
| 6 | +Illuminate\Container\Container::setInstance($container); | ||
| 7 | + | ||
| 8 | +$container->instance('config', new Illuminate\Config\Repository(array( | ||
| 9 | + 'barcode' => array( | ||
| 10 | + 'store_path' => sys_get_temp_dir(), | ||
| 11 | + ), | ||
| 12 | +))); | ||
| 13 | + | ||
| 14 | +$base64 = Daito\Lib\DaitoBarcode::generateBarcodeQrCode('HELLO-QR'); | ||
| 15 | + | ||
| 16 | +echo substr($base64, 0, 80) . PHP_EOL; | ||
| 17 | +echo 'length: ' . strlen($base64) . PHP_EOL; | ||
| 18 | + | ||
| 19 | +echo Daito\Lib\DaitoMath::div(1, 2) . PHP_EOL; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or sign in to post a comment