From 9ec08a3e448c866db2780cef53afa8698034bcdb Mon Sep 17 00:00:00 2001 From: me Date: Thu, 23 Jul 2026 15:29:13 +0700 Subject: [PATCH] update --- .gitignore | 6 + composer.json | 31 ++ phpstan.neon | 17 ++ src/Arr.php | 30 ++ src/Cache.php | 207 ++++++++++++++ src/Cache/Driver/File.php | 21 ++ src/Config.php | 90 ++++++ src/Crypto/Aes256Cbc.php | 47 ++++ src/Database/Database.php | 248 ++++++++++++++++ src/Database/Row.php | 27 ++ src/Database/Statement.php | 14 + src/Date.php | 91 ++++++ src/DesignPattern/Singleton.php | 50 ++++ src/Epub/Reader.php | 54 ++++ src/Fs.php | 213 ++++++++++++++ src/Http/Browser.php | 29 ++ src/Http/Client.php | 7 + src/Http/ClientNative.php | 7 + src/Http/Curl.php | 15 + src/Http/Http.php | 40 +++ src/Http/Request.php | 482 ++++++++++++++++++++++++++++++++ src/Http/Response.php | 154 ++++++++++ src/Json.php | 56 ++++ src/Session/Storage/Apcu.php | 57 ++++ src/Str.php | 128 +++++++++ src/Trie.php | 79 ++++++ src/Uuid.php | 62 ++++ src/Yaml.php | 47 ++++ src/Zip.php | 38 +++ src/cache/cache.php | 207 ++++++++++++++ src/cache/driver.php | 0 src/cache2.php | 207 ++++++++++++++ src/function.php | 4 + src/function_global.php | 25 ++ src/http/auth/basic_bultin.php | 0 src/http/auth/form_builtin.php | 0 36 files changed, 2790 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 phpstan.neon create mode 100644 src/Arr.php create mode 100644 src/Cache.php create mode 100644 src/Cache/Driver/File.php create mode 100644 src/Config.php create mode 100644 src/Crypto/Aes256Cbc.php create mode 100644 src/Database/Database.php create mode 100644 src/Database/Row.php create mode 100644 src/Database/Statement.php create mode 100644 src/Date.php create mode 100644 src/DesignPattern/Singleton.php create mode 100644 src/Epub/Reader.php create mode 100644 src/Fs.php create mode 100644 src/Http/Browser.php create mode 100755 src/Http/Client.php create mode 100755 src/Http/ClientNative.php create mode 100644 src/Http/Curl.php create mode 100644 src/Http/Http.php create mode 100644 src/Http/Request.php create mode 100644 src/Http/Response.php create mode 100644 src/Json.php create mode 100755 src/Session/Storage/Apcu.php create mode 100644 src/Str.php create mode 100644 src/Trie.php create mode 100644 src/Uuid.php create mode 100644 src/Yaml.php create mode 100644 src/Zip.php create mode 100644 src/cache/cache.php create mode 100644 src/cache/driver.php create mode 100644 src/cache2.php create mode 100644 src/function.php create mode 100644 src/function_global.php create mode 100644 src/http/auth/basic_bultin.php create mode 100644 src/http/auth/form_builtin.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ed4843 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea/ +vendor/ + +error_log +composer.lock + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..6e0f052 --- /dev/null +++ b/composer.json @@ -0,0 +1,31 @@ +{ + "name": "nightmare/php", + "description": "php helper", + "require": { + "php": ">=7.4.0", + "colinodell/json5": ">=2.3", + "php-curl-class/php-curl-class": ">=11.0", + "symfony/browser-kit": "^5.4 || ^6.4 || ^7.4", + "symfony/cache": "^5.4 || ^6.4 || ^7.4", + "symfony/css-selector": "^5.4 || ^6.4 || ^7.4", + "symfony/http-client": "^5.4 || ^6.4 || ^7.4", + "symfony/var-dumper": "^5.4 || ^6.4 || ^7.4", + "symfony/yaml": "^5.4 || ^6.4 || ^7.4" + }, + "autoload": { + "psr-4": { + "Nightmare\\": "src/" + }, + "files": [ + "src/function_global.php", + "src/function.php" + ] + }, + "config": { + "sort-packages": true, + "platform": { + "php": "7.4" + } + } +} + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..7c1fe0c --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,17 @@ +parameters: + phpVersion: 70400 + level: 6 + paths: + - src/ + reportUnmatchedIgnoredErrors: false + ignoreErrors: + - + identifiers: + - missingType.return + - missingType.parameter + - missingType.iterableValue + - missingType.property + - property.onlyWritten + - missingType.generics + - property.unused + diff --git a/src/Arr.php b/src/Arr.php new file mode 100644 index 0000000..c964329 --- /dev/null +++ b/src/Arr.php @@ -0,0 +1,30 @@ + $adapter]); + } + + /** + * @param string $key + * @return void + */ + public function remove_adapter($key) + { + unset(self::$adapters[$key]); + } + + /** + * @return array + */ + public static function get_adapters() + { + return self::$adapters; + } + + // cache + + /** + * @param string $key + * @return bool + */ + public static function has($key) + { + return self::$adapter->hasItem(self::$prefix . $key); + } + + // truyen 1 tham so - lay binh thuong + // truyen 2 tham so tro len - luu cache cho lan sau + /** + * @param string $key + * @param mixed $default + * @param array $opt + * @return mixed + */ + public static function get($key, $default = null, $opt = []) + { + $opt += [ + 'expire' => self::$expire, + 'debug' => false, + 'save' => true, + 'save_if' => null, // ?callable + ]; + + if (self::$debug || $opt['debug']) { + self::unset(self::$prefix . $key); + } + + // get cache + $item = self::$adapter->getItem(self::$prefix . $key); + + if ($item->isHit()) { + return $item->get(); + } else { + if (is_callable($default)) { + $default = call_user_func($default, $opt); + } + + if ($opt['save']) { + $save = false; + + if (is_callable($opt['save_if'])) { + if (call_user_func($opt['save_if'], $default)) { + $save = true; + } + } else { + $save = true; + } + + if ($save) { + self::set($key, $default, $opt['expire']); + } + } + + return $default; + } + } + + /** + * @param string $key + * @param mixed $value + * @param int|null $expire + * @return bool + */ + public static function set($key, $value, $expire = null) + { + $item = self::$adapter->getItem(self::$prefix . $key); + + if ($expire !== null) { + $item->expiresAfter($expire); + } elseif (self::$expire !== null) { + $item->expiresAfter(self::$expire); + } + + $item->set($value); + return self::$adapter->save($item); + } + + /** + * @param string $key + * @return bool + */ + public static function unset($key) + { + return self::$adapter->deleteItem(self::$prefix . $key); + } + + /** + * @param string $prefix + * @return bool + */ + public static function clear($prefix = '') + { + return self::$adapter->clear($prefix); + } +} diff --git a/src/Cache/Driver/File.php b/src/Cache/Driver/File.php new file mode 100644 index 0000000..74e6e37 --- /dev/null +++ b/src/Cache/Driver/File.php @@ -0,0 +1,21 @@ +config = array_merge([ + 'driver' => 'memory' + ], $config); + + switch ($this->config['driver']) { + case 'memory': + break; + case 'php_file': + break; + } + } + + /** + * @param string $prefix + * @return void + */ + public function set_prefix($prefix) + { + $this->prefix = $prefix; + } + + /** + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (file_exists($this->config['file'])) { + $this->data = require $this->config['file']; + } + + return $this->data[$this->prefix . $key] ?? $default; + } + + /** + * @param string $key + * @param mixed $value + * @return void + */ + public function set($key, $value) + { + $this->data[$this->prefix . $key] = $value; + + file_put_contents($this->config['file'], 'data, true) . ';'); + } + + /** + * @param string $key + * @return void + */ + public function remove($key) + { + unset($this->data[$this->prefix . $key]); + + file_put_contents($this->config['file'], 'data, true) . ';'); + } +} diff --git a/src/Crypto/Aes256Cbc.php b/src/Crypto/Aes256Cbc.php new file mode 100644 index 0000000..2b885e6 --- /dev/null +++ b/src/Crypto/Aes256Cbc.php @@ -0,0 +1,47 @@ +driver = $dsn; + } else { + $this->driver = new PDO( + $dsn, + $username, + $password, + array_merge([ + PDO::MYSQL_ATTR_INIT_COMMAND, 'SET sql_mode="ANSI,TRADITIONAL"' + ], (array) $options) + ); + } + + $this->driver->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->driver->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + $this->driver->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_CLASS); + $this->driver->setAttribute(PDO::ATTR_STATEMENT_CLASS, [Statement::class]); + + $this->driver->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); + } + + public function driver() + { + return $this->driver; + } + + /** + * @param string $sql + * @param array|null $params + * @return PDOStatement|false + */ + public function query($sql, $params = null) + { + $stmt = $this->driver->prepare($sql); + $stmt->execute($params); + + return $stmt; + } + + /** + * giống exec() nhưng dùng prepare + * + * @param string $sql + * @param array|null $params + * @return int + */ + public function query_count($sql, $params = null) + { + $stmt = self::query($sql, $params); + + return $stmt->rowCount(); + } + + /** + * @param string $table + * @param array $params + * @return string + */ + public function insert($table, $params) + { + $sql = 'insert into "' . $table . '"' + . ' (' . implode(',', $this->buildName(array_keys($params))) . ')' + . ' values (' . implode(',', array_fill(0, count($params), '?')) . ')'; + //dd($sql); + $this->query($sql, array_values($params)); + + return $this->driver->lastInsertId(); + } + + /** + * @param string $table + * @param array $con + * @param array $arr + * @return void + */ + public function update_or_insert($table, $con, $arr) + { + $where_conditions = []; + $where_params = []; + + foreach ($con as $column => $value) { + $where_conditions[] = sprintf('"%s" = ?', $column); + $where_params[] = $value; + } + + $where_clause = implode(' AND ', $where_conditions); + $check_sql = sprintf('SELECT COUNT(*) FROM "%s" WHERE %s', $table, $where_clause); + $count = $this->fetch_column($check_sql, $where_params); + + if ($count > 0) { + $update_parts = []; + $update_params = []; + + foreach ($arr as $column => $value) { + $update_parts[] = sprintf('"%s" = ?', $column); + $update_params[] = $value; + } + + $update_clause = implode(', ', $update_parts); + $update_sql = sprintf('UPDATE "%s" SET %s WHERE %s', $table, $update_clause, $where_clause); + + $this->query($update_sql, array_merge($update_params, $where_params)); + } else { + $this->insert($table, array_merge($con, $arr)); + } + } + + /** + * @param string $sql + * @param array|null $params + * @return int + */ + public function update($sql, $params = null) + { + $stmt = $this->query($sql, $params); + return $stmt->rowCount(); + } + + /** + * @param string $sql + * @param array|null $params + * @return mixed + */ + public function fetch($sql, $params = null) + { + return $this->query($sql, $params)->fetch(); + } + + public function exec($sql) + { + return $this->driver->exec($sql); + } + + /** + * @param string $sql + * @param array|null $params + * @return array + */ + public function fetchAll($sql, $params = null) + { + return $this->query($sql, $params)->fetchAll(); + } + + /** + * @param string $sql + * @param array|null $params + * @return array + */ + public function fetch_all($sql, $params = null) + { + return $this->query($sql, $params)->fetchAll(); + } + + /** + * @param string $sql + * @param array|null $params + * @param int $column + * @return mixed + */ + public function fetchColumn($sql, $params = null, $column = 0) + { + $stmt = $this->query($sql, $params); + return $stmt->fetchColumn($column); + } + + /** + * @param string $sql + * @param array|null $params + * @param int $column + * @return mixed + */ + public function fetch_column($sql, $params = null, $column = 0) + { + $stmt = $this->query($sql, $params); + return $stmt->fetchColumn($column); + } + + /** + * @param int $page + * @param int $per_page + * @return int + */ + public function getOffset($page, $per_page) + { + return $page * $per_page - $per_page; + } + + /** + * @param int $page + * @param int $per_page + * @return int + */ + public function get_offset($page, $per_page) + { + return $page * $per_page - $per_page; + } + + /** + * @param string $str + * @return string + */ + public function quote($str) + { + return $this->driver->quote($str); + } + + /** + * @param array $arr + * @return array + */ + public function buildName($arr) + { + return array_map(function ($item) { + return '"' . $item . '"'; + }, $arr); + } + + /** + * @param array $arr + * @return array + */ + public function build_name($arr) + { + return array_map(function ($item) { + return '"' . $item . '"'; + }, $arr); + } +} diff --git a/src/Database/Row.php b/src/Database/Row.php new file mode 100644 index 0000000..48f0fb5 --- /dev/null +++ b/src/Database/Row.php @@ -0,0 +1,27 @@ +setFetchMode(PDO::FETCH_CLASS, Row::class); + } +} diff --git a/src/Date.php b/src/Date.php new file mode 100644 index 0000000..cc46b38 --- /dev/null +++ b/src/Date.php @@ -0,0 +1,91 @@ +file_path = $file_path; + $this->zip = new ZipArchive(); + + $this->check(); + $this->read_metadata(); + } + + private function check() { + // check file + if (!is_file($this->file_path)) { + throw new Exception('epub not exists or not permission'); + } + + if (filesize($this->file_path) < 1) { + throw new Exception('epub not exists or not permission'); + } + + // check epub valid + if ($this->zip->open($this->file_path) !== TRUE) { + throw new Exception('epub read error'); + } + + if ($this->zip->getFromName('mimetype') !== 'application/epub+zip') { + throw new Exception('epub format error'); + } + } + + private function read_metadata() { + $meta = $this->zip->getFromName('META-INF/container.xml'); + $meta = simplexml_load_string($meta); + $this->epub_opf_dir = (string) $meta->rootfiles->rootfile['full-path']; + + $meta = $this->zip->getFromName($this->epub_opf_dir); + $meta = simplexml_load_string($meta); + + var_dump($meta->metadata->children('http://purl.org/dc/elements/1.1/')); + } +} diff --git a/src/Fs.php b/src/Fs.php new file mode 100644 index 0000000..5d60cc1 --- /dev/null +++ b/src/Fs.php @@ -0,0 +1,213 @@ +getExtension(); + } + + /** + * @param string $path + * @return int + */ + public static function size($path) + { + if (!is_dir($path)) { + return filesize($path); + } + + $size = 0; + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + if ($file->isFile()) { + $size += $file->getSize(); + } + } + + return $size; + } + + /** + * @param int $fileSize + * @return string + */ + public static function sizen($fileSize) + { + $size = floatval($fileSize); + + if ($size < 1024) { + $s = $size . ' B'; + } elseif ($size < 1048576) { + $s = round($size / 1024, 2) . ' KB'; + } elseif ($size < 1073741824) { + $s = round($size / 1048576, 2) . ' MB'; + } elseif ($size < 1099511627776) { + $s = round($size / 1073741824, 2) . ' GB'; + } elseif ($size < 1125899906842624) { + $s = round($size / 1099511627776, 2) . ' TB'; + } elseif ($size < 1152921504606846976) { + $s = round($size / 1125899906842624, 2) . ' PB'; + } elseif ($size < 1.1805916207174E+21) { + $s = round($size / 1152921504606846976, 2) . ' EB'; + } elseif ($size < 1.2089258196146E+24) { + $s = round($size / 1.1805916207174E+21, 2) . ' ZB'; + } else { + $s = round($size / 1.2089258196146E+24, 2) . ' YB'; + } + + return $s; + } + + /** + * @param string $path + * @return bool + */ + public static function remove($path) + { + if (is_link($path)) { + return unlink($path); + } + + if (is_file($path)) { + return unlink($path); + } + + if (is_dir($path)) { + $files = array_diff(scandir($path), ['.', '..']); + foreach ($files as $file) { + $filePath = $path . DIRECTORY_SEPARATOR . $file; + if (!self::remove($filePath)) { + return false; + } + } + return rmdir($path); + } + + if (!file_exists($path)) { + return true; + } + + throw new Exception('remove error, not match file type'); + } + + /** + * @param string $path + * @param array $excludes + * @return RecursiveIteratorIterator + */ + public static function read_full_dir($path, $excludes = []) + { + $directory = new RecursiveDirectoryIterator( + $path, + FilesystemIterator::UNIX_PATHS + | FilesystemIterator::SKIP_DOTS + ); + + $filter = new RecursiveCallbackFilterIterator($directory, function ($current, $key, $iterator) use ($path, $excludes) { + $relativePath = Str::replace_first($path, '', $current->getPathname()); + + foreach ($excludes as $exclude) { + if (empty($exclude)) { + continue; + } + //var_dump($relativePath); + //var_dump($exclude); + + $exclude = trim($exclude); + $exclude = trim($exclude, '/'); + $relativePath = trim($relativePath, '/'); + + if (str_ends_with($relativePath, $exclude)) { + return false; + } + } + + return true; + }); + + return new RecursiveIteratorIterator( + $filter, + RecursiveIteratorIterator::SELF_FIRST + ); + } + + /** + * @param string ...$paths + * @return string + */ + public static function join_path(...$paths) + { + return preg_replace('#/+#', '/', implode('/', $paths)); + } + + /** + * Lấy tên chủ sở hữu file theo tên file + * @param string $filename + * @return string + */ + public static function get_owner_name($filename) + { + $owner_id = @fileowner($filename); + if ($owner_id === false) { + return ''; + } + return self::get_owner_name_by_id($owner_id); + } + + /** + * Lấy tên chủ sở hữu theo user ID + * @param int $id + * @return string + */ + public static function get_owner_name_by_id($id) + { + $info = @posix_getpwuid($id); + return $info['name'] ?? ''; + } +} diff --git a/src/Http/Browser.php b/src/Http/Browser.php new file mode 100644 index 0000000..428e1ff --- /dev/null +++ b/src/Http/Browser.php @@ -0,0 +1,29 @@ +setServerParameter('HTTP_USER_AGENT', $userAgent); + } +} diff --git a/src/Http/Client.php b/src/Http/Client.php new file mode 100755 index 0000000..4ba393c --- /dev/null +++ b/src/Http/Client.php @@ -0,0 +1,7 @@ +setDefaultJsonDecoder($assoc = true); + } +} diff --git a/src/Http/Http.php b/src/Http/Http.php new file mode 100644 index 0000000..53f3546 --- /dev/null +++ b/src/Http/Http.php @@ -0,0 +1,40 @@ + $value) { + if (str_starts_with($key, 'HTTP_')) { + $headers[str_replace('_', '-', strtolower(substr($key, 5)))] = $value; + } + } + + return $headers; + } + + return $_SERVER['HTTP_' . str_replace('-', '_', strtoupper($key))] ?? $default; + } + /** + * @param string $key + * @return bool + */ + public static function has_header($key) + { + return isset($_SERVER['HTTP_' . strtoupper($key)]); + } + + // GET + + /** + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function get($key = '', $default = null) + { + if ($key === '') { + return $_GET; + } + + return $_GET[$key] ?? $default; + } + /** + * @param string $key + * @return bool + */ + public static function has_get($key) + { + return isset($_GET[$key]); + } + /** + * @param string $key + * @param mixed $value + * @return void + */ + public static function set_get($key, $value) + { + $_GET[$key] = $value; + } + + // POST + + /** + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function post($key, $default = null) + { + if ($key === '') { + return $_POST; + } + + return $_POST[$key] ?? $default; + } + /** + * @param string $key + * @return bool + */ + public static function has_post($key) + { + return isset($_POST[$key]); + } + /** + * @param string $key + * @param mixed $value + * @return void + */ + public static function set_post($key, $value) + { + $_POST[$key] = $value; + } + + // COOKIE + + /** + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function cookie($key, $default = null) + { + if ($key === '') { + return $_COOKIE; + } + + return $_COOKIE[$key] ?? $default; + } + /** + * @param string $key + * @return bool + */ + public static function has_cookie($key) + { + return isset($_COOKIE[$key]); + } + /** + * @param string $key + * @param string $value + * @return void + */ + public static function set_cookie($key, $value) + { + $_COOKIE[$key] = $value; + } + + // SESSION + + /** + * @param string $prefix + * @param int $ttl + * @return void + */ + public static function session_start($prefix = 'sess_', $ttl = 86400) + { + //session_set_save_handler(new \Nightmare\Session\Storage\Apcu($prefix, $ttl)); + + if (PHP_SESSION_ACTIVE === session_status()) { + throw new RuntimeException('Failed to start the session: already started by PHP.'); + } + + if (!\session_start()) { + throw new RuntimeException('Failed to start the session.'); + } + } + /** + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function session($key, $default = null) + { + if ($key === '') { + return $_SESSION; + } + + return $_SESSION[$key] ?? $default; + } + /** + * @param string $key + * @return bool + */ + public static function has_session($key) + { + return isset($_SESSION[$key]); + } + /** + * @param string $key + * @param mixed $value + * @return void + */ + public static function set_session($key, $value) + { + $_SESSION[$key] = $value; + } + /** + * @param string $key + * @return void + */ + public static function unset_session($key) + { + unset($_SESSION[$key]); + } + + + // SERVER + + /** + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function server($key, $default = null) + { + if ($key === '') { + return $_SERVER; + } + + return (string) (isset($_SERVER[$key]) ? $_SERVER[$key] : (isset($_SERVER[strtoupper($key)]) ? $_SERVER[strtoupper($key)] : $default)); + } + /** + * @param string $key + * @return bool + */ + public static function has_server($key) + { + return isset($_SERVER[$key]) ? true : isset($_SERVER[strtoupper($key)]); + } + + // FILES + /** + * @param string $key + * @return array|null + */ + public static function file($key) + { + if ($key === '') { + return $_FILES; + } + + if (!isset($_FILES[$key])) { + return null; + } + + if (!is_array($_FILES[$key]['name'])) { + return [$_FILES[$key]]; + } + + $tmp = []; + foreach ($_FILES[$key] as $k => $v) { + $fCount = count($_FILES[$key]['name']); + $fKeys = array_keys($_FILES[$key]); + + for ($i = 0; $i < $fCount; $i++) { + foreach ($fKeys as $fKey) { + $tmp[$key][$i][$fKey] = $_FILES[$key][$fKey][$i]; + } + } + } + return $tmp; + } + + // REQUEST + + /** + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function request($key, $default = null) + { + if ($key === '') { + return $_REQUEST; + } + + return $_REQUEST[$key] ?? $default; + } + /** + * @param string $key + * @return bool + */ + public static function has_request($key) + { + return isset($_REQUEST[$key]); + } + + // PAYLOAD + + /** + * @return void + */ + public static function init_payload() + { + self::$payload = Json::decode(file_get_contents('php://input') ?: '[]', true); + } + /** + * @param string $key + * @return bool + */ + public static function has_payload($key) + { + return isset(self::$payload[$key]); + } + /** + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function payload($key = '', $default = null) + { + if ($key === '') { + return self::$payload; + } + + return self::$payload[$key] ?? $default; + } +} diff --git a/src/Http/Response.php b/src/Http/Response.php new file mode 100644 index 0000000..787d062 --- /dev/null +++ b/src/Http/Response.php @@ -0,0 +1,154 @@ +data = $data; + $this->status = $status; + $this->headers = $headers; + } + + /** + * @param mixed $data + * @return self + */ + public function data($data) + { + $this->data = $data; + return $this; + } + /** + * @param int $status + * @return self + */ + public function status($status) + { + $this->status = $status; + return $this; + } + + /** + * @param bool $prettify + * @return self + */ + public function json($prettify = false) + { + $this->headers += ['Content-Type: application/json']; + + if (is_array($this->data) || is_object($this->data)) { + $flags = 0; + + $flags |= JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES; + + if ($prettify) { + $flags |= JSON_PRETTY_PRINT; + } + + $this->data = json_encode($this->data, $flags); + } + + return $this; + } + + /** + * @param array $headers + * @return self + */ + public function headers($headers) + { + $this->headers = $headers; + return $this; + } + + /** + * @return void + */ + public function send() + { + static $is_sended; + + if ($is_sended) { + return; + } else { + $is_sended = true; + } + + if (is_array($this->data)) { + $this->json(); + } + + http_response_code($this->status); + + $this->headers = array_unique($this->headers); + foreach ($this->headers as $header) { + header($header); + } + + echo $this->data; + + if (\function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } elseif (\function_exists('litespeed_finish_request')) { + litespeed_finish_request(); + } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + static::close_output_buffers(0, true); + flush(); + } + } + + /** + * @param int $targetLevel + * @param bool $flush + * @return void + */ + public static function close_output_buffers($targetLevel, $flush) + { + $status = ob_get_status(true); + $level = \count($status); + $flags = \PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? \PHP_OUTPUT_HANDLER_FLUSHABLE : \PHP_OUTPUT_HANDLER_CLEANABLE); + + while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) { + if ($flush) { + ob_end_flush(); + } else { + ob_end_clean(); + } + } + } + + public static function finish() { + if (\function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } elseif (\function_exists('litespeed_finish_request')) { + litespeed_finish_request(); + } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + static::close_output_buffers(0, true); + flush(); + } + } +} diff --git a/src/Json.php b/src/Json.php new file mode 100644 index 0000000..a4e8ec2 --- /dev/null +++ b/src/Json.php @@ -0,0 +1,56 @@ + 0) { + $assoc = $args[0]; + } + + try { + return json5_decode($data, $assoc, ...array_slice($args, 1)); + } catch(\Throwable $e) { + return null; + } + } + + /** + * @param string $file + * @param mixed ...$args + * @return int|false + */ + public static function encode_file($file, ...$args) { + return file_put_contents($file, json_encode(...$args)); + } + + /** + * @param string $file + * @param mixed ...$args + * @return mixed + */ + public static function decode_file($file, ...$args) { + $assoc = true; + if (count($args) > 0) { + $assoc = $args[0]; + } + return self::decode(file_get_contents($file), $assoc, ...array_slice($args, 1)); + } +} diff --git a/src/Session/Storage/Apcu.php b/src/Session/Storage/Apcu.php new file mode 100755 index 0000000..44896ca --- /dev/null +++ b/src/Session/Storage/Apcu.php @@ -0,0 +1,57 @@ +prefix = $prefix; + $this->ttl = $ttl; + } + + #[ReturnTypeWillChange] + public function close() + { + return true; + } + + #[ReturnTypeWillChange] + public function destroy($id) + { + $key = $this->prefix . $id; + return apcu_delete($key); + } + + #[ReturnTypeWillChange] + public function gc($max_lifetime) + { + return 1; + } + + #[ReturnTypeWillChange] + public function open($path, $name) + { + return true; + } + + #[ReturnTypeWillChange] + public function read($id) + { + $key = $this->prefix . $id; + return apcu_exists($key) ? apcu_fetch($key) : ''; + } + + #[ReturnTypeWillChange] + public function write($id, $data) + { + $key = $this->prefix . $id; + return apcu_store($key, $data, $this->ttl); + } +} \ No newline at end of file diff --git a/src/Str.php b/src/Str.php new file mode 100644 index 0000000..37e5465 --- /dev/null +++ b/src/Str.php @@ -0,0 +1,128 @@ +#i', PHP_EOL, $str); + } + + /** + * @param string $str + * @return int + */ + public static function length($str) + { + return mb_strlen($str); + } + + /** + * Chuyển đổi tiếng Việt sang tiếng Anh + * @param string $str + * @return string + */ + public static function vn2en($str) + { + $unicode = [ + 'a' => '/á|à|ả|ã|ạ|ă|ắ|ặ|ằ|ẳ|ẵ|â|ấ|ầ|ẩ|ẫ|ậ/', + 'd' => '/đ/', + 'e' => '/é|è|ẻ|ẽ|ẹ|ê|ế|ề|ể|ễ|ệ/', + 'i' => '/í|ì|ỉ|ĩ|ị/', + 'o' => '/ó|ò|ỏ|õ|ọ|ô|ố|ồ|ổ|ỗ|ộ|ơ|ớ|ờ|ở|ỡ|ợ/', + 'u' => '/ú|ù|ủ|ũ|ụ|ư|ứ|ừ|ử|ữ|ự/', + 'y' => '/ý|ỳ|ỷ|ỹ|ỵ/', + + 'A' => '/Á|À|Ả|Ã|Ạ|Ă|Ắ|Ặ|Ằ|Ằ|Ẵ|Â|Ấ|Ầ|Ẩ|Ẫ|Ậ/', + 'D' => '/Đ/', + 'E' => '/É|È|Ẻ|Ẽ|Ẹ|Ê|Ế|Ề|Ể|Ễ|Ệ/', + 'I' => '/Í|Ì|Ỉ|Ĩ|Ị/', + 'O' => '/Ó|Ò|Ỏ|Õ|Ọ|Ô|Ố|Ồ|Ổ|Ỗ|Ộ|Ơ|Ớ|Ờ|Ở|Ỡ|Ợ/', + 'U' => '/Ú|Ù|Ủ|Ũ|Ụ|Ư|Ứ|Ừ|Ử|Ữ|Ự/', + 'Y' => '/Ý|Ỳ|Ỷ|Ỹ|Ỵ/' + ]; + + return preg_replace(array_values($unicode), array_keys($unicode), $str); + } + + /** + * @param string $needle + * @param string $replace + * @param string $haystack + * @return string + */ + public static function replace_first($needle, $replace, $haystack) + { + $pos = strpos($haystack, $needle); + + if ($pos !== false) { + return substr_replace($haystack, $replace, $pos, strlen($needle)); + } + + return $haystack; + } + + /** + * @param string $text + * @return string + */ + public static function to_unix_newline($text) + { + return str_replace([ + "\r\n", // windows + "\r" // mac old + ], "\n", $text); + } + + /** + * @param string $str + * @return string + */ + public function to_url($str) { + $str = trim($str); + $str = strtolower($str); + $str = self::vn2en($str); + + $str = html_entity_decode($str, ENT_QUOTES, 'UTF-8'); + + $str = str_replace('&', '-and-', $str); + $str = str_replace(' ', '-', $str); + + $str = preg_replace('#[^a-z0-9\-]#', '', $str); + $str = preg_replace('#[-]{2,}#', '-', $str); + $str = trim($str, '-'); + + return $str; + } +} diff --git a/src/Trie.php b/src/Trie.php new file mode 100644 index 0000000..1129267 --- /dev/null +++ b/src/Trie.php @@ -0,0 +1,79 @@ +tree = [ + 'value' => '', + 'child' => [] + ]; + } + + public function add($str, $data = '') { + $length = mb_strlen($str); + + if (!$length) { + return false; + } + + $tree = &$this->tree; + $chars = mb_str_split($str); + $i = 0; + + foreach ($chars as $char) { + $i++; + $is_end = $i === $length; + $char = mb_ord($char); + + if (!isset($tree['child'][$char])) { + $tree['child'][$char] = [ + 'value' => '', + 'child' => [] + ]; + } + + if ($is_end) { + $tree['child'][$char]['value'] = (string) $data; + } + + $tree = &$tree['child'][$char]; + } + } + + // false, array + public function search($str) { + $length = mb_strlen($str); + + if (!$length) { + return false; + } + + $tree = &$this->tree; + $chars = mb_str_split($str); + $i = 0; + + foreach ($chars as $char) { + $i++; + $is_end = $i === $length; + $char = mb_ord($char); + + if (!isset($tree['child'][$char])) { + return false; + } + + if ($is_end) { + return [ + 'value' => $tree['child'][$char]['value'] ?? '', + 'is_end' => count($tree['child'][$char]['child']) === 0 + ]; + } + + $tree = &$tree['child'][$char]; + } + } +} diff --git a/src/Uuid.php b/src/Uuid.php new file mode 100644 index 0000000..1130da3 --- /dev/null +++ b/src/Uuid.php @@ -0,0 +1,62 @@ + 6 byte + // N = 32-bit big-endian, n = 16-bit big-endian + $time_bytes = pack('Nn', $unix_ms >> 16, $unix_ms & 0xFFFF); + + // 3) 10 byte ngẫu nhiên (crypto-safe) + $rand_bytes = random_bytes(10); + + // 4) Ghép thành 16 byte + $bytes = $time_bytes . $rand_bytes; + + // 5) Set version 7 (0111) vào high nibble của byte thứ 7 (index 6) + $bytes[6] = chr((ord($bytes[6]) & 0x0F) | 0x70); + + // 6) Set variant RFC 4122 -> 10xxxxxx vào byte thứ 9 (index 8) + $bytes[8] = chr((ord($bytes[8]) & 0x3F) | 0x80); + + // 7) Chuyển sang dạng string 8-4-4-4-12 + $hex = bin2hex($bytes); + + return sprintf( + '%s-%s-%s-%s-%s', + substr($hex, 0, 8), + substr($hex, 8, 4), + substr($hex, 12, 4), + substr($hex, 16, 4), + substr($hex, 20), + ); + } +} diff --git a/src/Yaml.php b/src/Yaml.php new file mode 100644 index 0000000..15df4f5 --- /dev/null +++ b/src/Yaml.php @@ -0,0 +1,47 @@ +getPathname(); + $pathRelative = $path; + + if ($relative) { + $pathRelative = substr($path, strlen($relative)); + } + + if ($file->isFile()) { + $this->addFile($path, $pathRelative); + } + + if ($file->isDir()) { + $this->addEmptyDir($pathRelative); + } + + return true; + } +} diff --git a/src/cache/cache.php b/src/cache/cache.php new file mode 100644 index 0000000..e971645 --- /dev/null +++ b/src/cache/cache.php @@ -0,0 +1,207 @@ + $adapter]); + } + + /** + * @param string $key + * @return void + */ + public function remove_adapter($key) + { + unset(self::$adapters[$key]); + } + + /** + * @return array + */ + public static function get_adapters() + { + return self::$adapters; + } + + // cache + + /** + * @param string $key + * @return bool + */ + public static function has($key) + { + return self::$adapter->hasItem(self::$prefix . $key); + } + + // truyen 1 tham so - lay binh thuong + // truyen 2 tham so tro len - luu cache cho lan sau + /** + * @param string $key + * @param mixed $default + * @param array $opt + * @return mixed + */ + public static function get($key, $default = null, $opt = []) + { + $opt += [ + 'expire' => self::$expire, + 'debug' => false, + 'save' => true, + 'save_if' => null, // ?callable + ]; + + if (self::$debug || $opt['debug']) { + self::unset(self::$prefix . $key); + } + + // get cache + $item = self::$adapter->getItem(self::$prefix . $key); + + if ($item->isHit()) { + return $item->get(); + } else { + if (is_callable($default)) { + $default = call_user_func($default, $opt); + } + + if ($opt['save']) { + $save = false; + + if (is_callable($opt['save_if'])) { + if (call_user_func($opt['save_if'], $default)) { + $save = true; + } + } else { + $save = true; + } + + if ($save) { + self::set($key, $default, $opt['expire']); + } + } + + return $default; + } + } + + /** + * @param string $key + * @param mixed $value + * @param int|null $expire + * @return bool + */ + public static function set($key, $value, $expire = null) + { + $item = self::$adapter->getItem(self::$prefix . $key); + + if ($expire !== null) { + $item->expiresAfter($expire); + } elseif (self::$expire !== null) { + $item->expiresAfter(self::$expire); + } + + $item->set($value); + return self::$adapter->save($item); + } + + /** + * @param string $key + * @return bool + */ + public static function unset($key) + { + return self::$adapter->deleteItem(self::$prefix . $key); + } + + /** + * @param string $prefix + * @return bool + */ + public static function clear($prefix = '') + { + return self::$adapter->clear($prefix); + } +} diff --git a/src/cache/driver.php b/src/cache/driver.php new file mode 100644 index 0000000..e69de29 diff --git a/src/cache2.php b/src/cache2.php new file mode 100644 index 0000000..3be5e6e --- /dev/null +++ b/src/cache2.php @@ -0,0 +1,207 @@ + $adapter]); + } + + /** + * @param string $key + * @return void + */ + public function remove_adapter($key) + { + unset(self::$adapters[$key]); + } + + /** + * @return array + */ + public function get_adapters() + { + return self::$adapters; + } + + // cache + + /** + * @param string $key + * @return bool + */ + public function has($key) + { + return self::$adapter->hasItem(self::$prefix . $key); + } + + // truyen 1 tham so - lay binh thuong + // truyen 2 tham so tro len - luu cache cho lan sau + /** + * @param string $key + * @param mixed $default + * @param array $opt + * @return mixed + */ + public function get($key, $default = null, $opt = []) + { + $opt += [ + 'expire' => self::$expire, + 'debug' => false, + 'save' => true, + 'save_if' => null, // ?callable + ]; + + if (self::$debug || $opt['debug']) { + self::unset(self::$prefix . $key); + } + + // get cache + $item = self::$adapter->getItem(self::$prefix . $key); + + if ($item->isHit()) { + return $item->get(); + } else { + if (is_callable($default)) { + $default = call_user_func($default, $opt); + } + + if ($opt['save']) { + $save = false; + + if (is_callable($opt['save_if'])) { + if (call_user_func($opt['save_if'], $default)) { + $save = true; + } + } else { + $save = true; + } + + if ($save) { + self::set($key, $default, $opt['expire']); + } + } + + return $default; + } + } + + /** + * @param string $key + * @param mixed $value + * @param int|null $expire + * @return bool + */ + public function set($key, $value, $expire = null) + { + $item = self::$adapter->getItem(self::$prefix . $key); + + if ($expire !== null) { + $item->expiresAfter($expire); + } elseif (self::$expire !== null) { + $item->expiresAfter(self::$expire); + } + + $item->set($value); + return self::$adapter->save($item); + } + + /** + * @param string $key + * @return bool + */ + public function unset($key) + { + return self::$adapter->deleteItem(self::$prefix . $key); + } + + /** + * @param string $prefix + * @return bool + */ + public function clear($prefix = '') + { + return self::$adapter->clear($prefix); + } +} diff --git a/src/function.php b/src/function.php new file mode 100644 index 0000000..2e62692 --- /dev/null +++ b/src/function.php @@ -0,0 +1,4 @@ +'; + echo htmlspecialchars($output, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); + echo ''; + } + } +} + +if (!function_exists('nde')) { + function nde(...$vars) { + ndg(...$vars); + exit(1); + } +} + diff --git a/src/http/auth/basic_bultin.php b/src/http/auth/basic_bultin.php new file mode 100644 index 0000000..e69de29 diff --git a/src/http/auth/form_builtin.php b/src/http/auth/form_builtin.php new file mode 100644 index 0000000..e69de29