From 23e882dfdad01f37cdc514c3f883804ae7e53880 Mon Sep 17 00:00:00 2001 From: Karel Wintersky Date: Mon, 7 Aug 2023 20:33:30 +0300 Subject: [PATCH] 2.7.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - [+] Хэлперы Arrays, Dataset, Strings, StringsHTML - [*] Удалена или перенесена документация - [+] хелпер-метод `_env` - [*] функция `Arris\groupDatasetByColumn` перенесена в хелпер `Arrays::groupDatasetByColumn` - [*] обновлен `.gitattributes` --- .gitattributes | 4 +- README.md | 167 ++++++-------------------------- docs/AppLogger.md | 131 ------------------------- docs/AppRouter.md | 104 -------------------- docs/CLIConsole.md | 2 - docs/{ => Core}/Curl.md | 0 docs/{ => Helpers}/Dataset.md | 0 functions/functions.php | 25 ----- functions/helpers.php | 74 ++++++++------ sources/Helpers/Arrays.php | 56 +++++++++++ sources/Helpers/Dataset.php | 21 ++++ sources/Helpers/Strings.php | 52 ++++++++++ sources/Helpers/StringsHTML.php | 59 +++++++++++ tests/HelpersArrayTest.php | 34 +++++++ tests/HelpersStringsTest.php | 63 ++++++++++++ tests/standalone/test_.php | 3 + 16 files changed, 366 insertions(+), 429 deletions(-) delete mode 100644 docs/AppLogger.md delete mode 100644 docs/AppRouter.md delete mode 100644 docs/CLIConsole.md rename docs/{ => Core}/Curl.md (100%) rename docs/{ => Helpers}/Dataset.md (100%) create mode 100644 sources/Helpers/Arrays.php create mode 100644 sources/Helpers/Strings.php create mode 100644 sources/Helpers/StringsHTML.php create mode 100644 tests/HelpersArrayTest.php create mode 100644 tests/HelpersStringsTest.php diff --git a/.gitattributes b/.gitattributes index 17672b4..3cd5ee9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,8 +4,10 @@ .git export-ignore .gitattributes export-ignore .gitignore export-ignore +makefile export-ignore _env.example.php export-ignore -tests export-ignore +phpunit.xml export-ignore +tests export-ignore vendor export-ignore docs export-ignore _legacy export-ignore diff --git a/README.md b/README.md index 00f1363..f7660d7 100644 --- a/README.md +++ b/README.md @@ -4,167 +4,64 @@ Class collection for some my projects # Sub-packages -* [Arris.Toolkit.Nginx](https://github.com/KarelWintersky/Arris.Toolkit.Nginx), `composer require karelwintersky/arris.toolkit.nginx` -* [Arris.Toolkit.Sphinx](https://github.com/KarelWintersky/Arris.Toolkit.Sphinx), `composer require karelwintersky/arris.toolkit.sphinx` -* [Arris.Toolkit.CDNNow](https://github.com/KarelWintersky/Arris.Toolkit.CDNNow), `composer require karelwintersky/arris.toolkit.cdnnow` -* [Arris.Toolkit.CLIConsole](https://packagist.org/packages/karelwintersky/arris.toolkit.cli-console), `composer require karelwintersky/arris.toolkit.cli-console` -* [DrCalculus](https://github.com/KarelWintersky/DrCalculus), `composer require karelwintersky/arris.drcalculus` -* []() -* []() -* []() +- Core classes + - [Arris.AppLogger](https://github.com/ArrisFramework/Arris.AppLogger), `composer require karelwintersky/arris.logger` + - [Arris.AppRouter](https://github.com/ArrisFramework/Arris.AppRouter), `composer require karelwintersky/arris.router` +- Helpers + - [Arris.Helpers] +- Utils + - [Arris.PHP_FileDownload](https://github.com/ArrisFramework/Arris.PHP_FileDownload), `composer require karelwintersky/arris.php-file-download` + - +- Toolkits + - [Arris.Toolkit.MimeTypes](https://github.com/ArrisFramework/Arris.Toolkit.MimeTypes), `composer require karelwintersky/arris.toolkit.mimetypes` + - [Arris.Toolkit.Nginx](https://github.com/KarelWintersky/Arris.Toolkit.Nginx), `composer require karelwintersky/arris.toolkit.nginx` + - [Arris.Toolkit.Sphinx](https://github.com/KarelWintersky/Arris.Toolkit.Sphinx), `composer require karelwintersky/arris.toolkit.sphinx` # How to use -* App -* AppConfig -* AppLogger -* AppRouter -* CLIConsole -* DB, DBPool -* Hook -* Utils\Timer - ## App - Реестр -``` -$app = App::factory(); // or ::handle() - -$app->set(DB::class, DB::C()); -$app->set(PHPAuth::class, new PHPAuth(DB::C(), (new PHPAuthConfig())->loadENV('_env')->getConfig() )); -``` - -``` -$app = App::factory(); // or ::handle() - -$dbc = $app->get(DB::class); - -// or +```php +$app = App::factory(); -$dbc = (App::access())->get(DB::class); +$app->set('PDO', new PDO(/* params */)); +$app->set(PHPAuth::class, new PHPAuth($pdo, (new PHPAuthConfig())->loadENV('_env')->getConfig() )); +$app->set(Smarty::class, new Smarty()); -$auth = $app->get(PHPAuth::class); +$app->addService('pdo.main', new PDO()); ``` -## AppConfig - -??? - -## AppLogger - -``` -AppLogger::init($application, $instance, $options = []):void -``` - -Инициализирует класс логгера - -* `$application` - Имя приложения -* `$instance` - код инстанса приложения (рекомендуется генерировать его при старте приложения и с помощью этого кода отличать логи, записанные разными инстансами приложения параллельно) -* `$options` опции приложения: - * `bubbling` - [FALSE] - всплывает ли логгируемое сообщение? - * `default_log_level` - [Monolog::DEBUG] - уровень логгирования по умолчанию - * `default_logfile_path` - [''] - путь к файлам логов по умолчанию - * `default_logfile_prefix` - [''] - префикс файла лога по умолчанию - * `default_log_file` - ['_.log'] - имя файла лога по умолчанию, применяется если для имени файла передан NULL - * `default_handler` - [NULL], или хэндлер, реализующий \Monolog\Handler\HandlerInterface как логгер по умолчанию для этого скоупа - * `add_scope_to_log` - [FALSE] - добавлять ли имя скоупа к имени логгера в файле лога? - * `deferred_scope_creation` - [TRUE] - разрешать ли отложенную инициализацию скоупов - * `deferred_scope_separate_files` - [TRUE] - использовать ли разные файлы для deferred-скоупов (на основе имени скоупа) - -### Add Scope - -``` -AppLogger::addScope($scope = null, $scope_levels = [], $scope_logging_enabled = true, $is_deferred_scope = false):void -``` - -Добавляет скоуп (логгер) с параметрами - -* `$scope` - имя скоупа -* `$scope_levels` - массив кортежей с опциями уровней логгирования. -* `$scope_logging_enabled` - включено ли логгирование для этого скоупа. Это глобальная настройка для скоупа, если логгирование отключено - никакие опции, переданные в `$scope_levels` его не включат. Если этим параметром логгирование отключено (false), но скоуп создается, но для всех уровней логгирования хэндлер ставится `NullLogger`. -* `$is_deferred_scope` - служебный аргумент, его никогда не следует указывать напрямую (он задает создание логгера как deferred) - -Настройки логгеров по уровням (логгирования) передаются в массиве `$scope_levels`. Может быть передан пустой массив - тогда поставятся опции "по умолчанию" (на основе глобальных опций), а скоуп будет создан как 'deferred' (отложенная инициализация). - -Пример: -``` -AppLogger::addScope('mysql', -[ - [ '__mysql.100-debug.log', Logger::DEBUG, 'enable' => true], - [ '__mysql.250-notice.log', Logger::NOTICE, 'enable' => true], - [ '__mysql.300-warning.log', Logger::WARNING, 'enable' => true], - [ '__mysql.400-error.log', Logger::ERROR, 'enable' => true], -], getenv('IS_MYSQL_LOGGER_ENABLED')); -``` +later: -Параметры уровня логгирования (элементы кортежа опций): -* Первый элемент: `filename` - имя файла (в случае отсутствия будет применено имя по умолчанию из глобальных опций) -* Второй элемент:`logging_level` - уровень логгирования, используются числа или (что удобнее), константы `\Monolog\Logger::DEBUG` и другие из того же неймспейса. - -Остальные параметры передаются через ключи ассоциативного массива: -* `enabled` - [TRUE], разрешен ли этот уровень логгирования. Применяется тот же механизм, что и для глобальной опции `$scope_logging_enabled` для скоупа; -* `bubbling` - [FALSE], всплывает ли сообщение логгирования на следующий (более низкий) уровень? -* `handler` - [NULL] либо хэндлер, реализующий интерфейс `Monolog\Handler\HandlerInterface`. Если указан NULL - будет использован хэндлер по умолчанию: StreamHandler, записывающий лог в файл. - -*NB:* Следует отметить, что если используется необъявленный в скоупе логгер, например: -``` -AppLogger::scope('mysql')->emergency('MYSQL EMERGENCY'); -``` -монолог проспамит этим сообщением по всем объявленным уровням. - -### Usage - -Вызов `AppLogger::scope($scope_name)` возвращает инстанс `\Monolog\Logger`, к которому можно применить штатные методы логгирования: -``` -debug, notice, warn, error, emergency и так далее -``` - -Например: -``` -AppLogger::scope('mysql')->debug("mysql::Debug", [ ['x'], ['y']]); -AppLogger::scope('mysql')->notice('mysql::Notice', ['x', 'y']); -``` - -### Deferred Scope - -Есть возможность использовать скоупы с отложенной инициализацией и параметрами по умолчанию. Этот механизм называется Deferred Scope. - -Вызов аналогичен предварительно инициализированному логгеру: -``` -AppLogger::scope('usage')->emergency('EMERGENCY USAGE'); -``` - -В этом случае будет создан скоуп `usage` со всеми уровнями логгирования и параметрами по умолчанию (но реальный вызов логгера произойдет только для уровня `emergency`). +```php +$app = App::factory(); // or ::handle() -*NB:* Если при инициализации обычного скоупа методом `addScope()` передан пустой массив опций логгеров - будет применен механизм инициализации deferred-скоупа. +$dbc = $app->get('PDO'); -### Примечания (usage hints) +// or -#### Один файл для нескольких уровней логгирования +$dbc = (App::access())->get('PDO'); -Указываем наименьший используемый уровень логгирования (`Logger::NOTICE`) -``` -AppLogger::addScope('log.selectel', [ [ '_selectel_upload.log', Logger::NOTICE ] ]); -``` +// or -Теперь вот эти два вызова запишут в файл 2 строчки -``` -AppLogger::scope('log.selectel')->error('Error'); -AppLogger::scope('log.selectel')->notice('Notice'); +$dbc = (App::factory())->getService('pdo.main'); ``` -# AppRouter - # CLIConsole +- todo + # DB -todo +- todo # DBPool -todo +- todo # Hook +- todo + # Utils\Timer diff --git a/docs/AppLogger.md b/docs/AppLogger.md deleted file mode 100644 index 40e71d8..0000000 --- a/docs/AppLogger.md +++ /dev/null @@ -1,131 +0,0 @@ -# Init - -``` -AppLogger::init($application, $instance, $options = []):void -``` - -Инициализирует класс логгера - -* `$application` - Имя приложения -* `$instance` - код инстанса приложения (рекомендуется генерировать его при старте приложения и с помощью этого кода отличать логи, записанные разными инстансами приложения параллельно) -* `$options` опции приложения: - * `bubbling` - [FALSE] - всплывает ли логгируемое сообщение? - * `default_log_level` - [Monolog::DEBUG] - уровень логгирования по умолчанию - * `default_logfile_path` - [''] - путь к файлам логов по умолчанию - * `default_logfile_prefix` - [''] - префикс файла лога по умолчанию - * `default_log_file` - ['_.log'] - имя файла лога по умолчанию, применяется если для имени файла передан NULL - * `default_handler` - [NULL], или хэндлер, реализующий \Monolog\Handler\HandlerInterface как логгер по умолчанию для этого скоупа - * `add_scope_to_log` - [FALSE] - добавлять ли имя скоупа к имени логгера в файле лога? - * `deferred_scope_creation` - [TRUE] - разрешать ли отложенную инициализацию скоупов - * `deferred_scope_separate_files` - [TRUE] - использовать ли разные файлы для deferred-скоупов (на основе имени скоупа) - -# Add Scope - -``` -AppLogger::addScope($scope = null, $scope_levels = [], $scope_logging_enabled = true, $is_deferred_scope = false):void -``` - -Добавляет скоуп (логгер) с параметрами - -* `$scope` - имя скоупа -* `$scope_levels` - массив кортежей с опциями уровней логгирования. -* `$scope_logging_enabled` - включено ли логгирование для этого скоупа. Это глобальная настройка для скоупа, если логгирование отключено - никакие опции, переданные в `$scope_levels` его не включат. Если этим параметром логгирование отключено (false), но скоуп создается, но для всех уровней логгирования хэндлер ставится `NullLogger`. -* `$is_deferred_scope` - служебный аргумент, его никогда не следует указывать напрямую (он задает создание логгера как deferred) - -Настройки логгеров по уровням (логгирования) передаются в массиве `$scope_levels`. Может быть передан пустой массив - тогда поставятся опции "по умолчанию" (на основе глобальных опций), а скоуп будет создан как 'deferred' (отложенная инициализация). - -Пример: -``` -AppLogger::addScope('mysql', -[ - [ '__mysql.100-debug.log', Logger::DEBUG, 'enable' => true], - [ '__mysql.250-notice.log', Logger::NOTICE, 'enable' => true], - [ '__mysql.300-warning.log', Logger::WARNING, 'enable' => true], - [ '__mysql.400-error.log', Logger::ERROR, 'enable' => true], -], getenv('IS_MYSQL_LOGGER_ENABLED')); -``` - -Параметры уровня логгирования (элементы кортежа опций): -* Первый элемент: `filename` - имя файла (в случае отсутствия будет применено имя по умолчанию из глобальных опций) -* Второй элемент:`logging_level` - уровень логгирования, используются числа или (что удобнее), константы `\Monolog\Logger::DEBUG` и другие из того же неймспейса. - -Остальные параметры передаются через ключи ассоциативного массива: -* `enabled` - [TRUE], разрешен ли этот уровень логгирования. Применяется тот же механизм, что и для глобальной опции `$scope_logging_enabled` для скоупа; -* `bubbling` - [FALSE], всплывает ли сообщение логгирования на следующий (более низкий) уровень? -* `handler` - [NULL] либо хэндлер, реализующий интерфейс `Monolog\Handler\HandlerInterface`. Если указан NULL - будет использован хэндлер по умолчанию: StreamHandler, записывающий лог в файл. - -*NB:* Следует отметить, что если используется необъявленный в скоупе логгер, например: -``` -AppLogger::scope('mysql')->emergency('MYSQL EMERGENCY'); -``` -монолог проспамит этим сообщением по всем объявленным уровням. - -# Usage - -Вызов `AppLogger::scope($scope_name)` возвращает инстанс `\Monolog\Logger`, к которому можно применить штатные методы логгирования: -``` -debug, notice, warn, error, emergency и так далее -``` - -Например: -``` -AppLogger::scope('mysql')->debug("mysql::Debug", [ ['x'], ['y']]); -AppLogger::scope('mysql')->notice('mysql::Notice', ['x', 'y']); -``` - -# Deferred Scope - -Есть возможность использовать скоупы с отложенной инициализацией и параметрами по умолчанию. Этот механизм называется Deferred Scope. - -Вызов аналогичен предварительно инициализированному логгеру: -``` -AppLogger::scope('usage')->emergency('EMERGENCY USAGE'); -``` - -В этом случае будет создан скоуп `usage` со всеми уровнями логгирования и параметрами по умолчанию (но реальный вызов логгера произойдет только для уровня `emergency`). - -*NB:* Если при инициализации обычного скоупа методом `addScope()` передан пустой массив опций логгеров - будет применен механизм инициализации deferred-скоупа. - -# Примечания (usage hints) - -## Один файл для нескольких уровней логгирования - -Указываем наименьший используемый уровень логгирования (`Logger::NOTICE`) -``` -AppLogger::addScope('log.selectel', [ [ '_selectel_upload.log', Logger::NOTICE ] ]); -``` - -Теперь вот эти два вызова запишут в файл 2 строчки -``` -AppLogger::scope('log.selectel')->error('Error'); -AppLogger::scope('log.selectel')->notice('Notice'); -``` - -# Custom handler - -```php -AppLogger::addScope('console', [ - [ 'php://stdout', Logger::INFO, [ 'handler' => StreamHandler::class ]] - ], $options['verbose']); -``` -Добавляет стандартный StreamHandler (в stdout). При этом он используется только если `$options['verbose'] === true`. - -```php - AppLogger::addScope('console', [ - [ 'php://stdout', Logger::INFO, [ 'handler' => static function() - { - $formatter = new LineFormatter("[%datetime%]: %message%\n", "Y-m-d H:i:s", false, true); - $handler = new StreamHandler('php://stdout', Logger::INFO); - $handler->setFormatter($formatter); - return $handler; - } - ], $options['verbose']); -``` - -Добавляет собственный хэндлер логгирования - замыкание. - - -https://stackoverflow.com/questions/70875746/laravel-monolog-lineformatter-datetime-pattern - - - diff --git a/docs/AppRouter.md b/docs/AppRouter.md deleted file mode 100644 index a976117..0000000 --- a/docs/AppRouter.md +++ /dev/null @@ -1,104 +0,0 @@ -# About - -Попытка написать свой роутер на базе https://github.com/nikic/FastRoute - -Реализует возможность статического класса AppRouter с методами: - -- init() -- get() -- post() -- etc - -Пример роутинга: - -```php - -AppRouter::init(AppLogger::scope('routing')); - -AppRouter::get('/', function () { - CLIConsole::say('Call from /'); -}, 'root'); - -/** - * ВАЖНО: миддлвары группы не работают, если URL-ы имеют опциональные секции. - * - * Это происходит потому, что метод dispatch() не возвращает информации о реальном сопоставлении переданного URL и сработавшего правила роутинга. - * - * Переделать это можно, но нужно делать свой форк nikic/fast-route - * Возможно, нет смысла, и проще взять роутер из slim или взять весь slim - */ - -AppRouter::group([ - 'prefix' => '/auth', - 'namespace' => 'Auth', - 'before' => static function () { CLIConsole::say('Called BEFORE middleware for /auth/*'); }, - 'after' => null - ], static function() { - - AppRouter::get('/login', function () { - CLIConsole::say('Call /auth/login'); - }); - - AppRouter::group(['prefix' => '/ajax'], static function() { - - AppRouter::get('/getKey', function (){ - CLIConsole::say('Call from /test/ajax/getKey'); - }, 'auth:ajax:getKey'); - - }); - - AppRouter::get('/get', function (){ - CLIConsole::say('Call from /test/get (declared after /ajax prefix group'); - }); - - AppRouter::group(['prefix' => '/2'], static function() { - AppRouter::get('/3', function () { - CLIConsole::say('Call from /test/2/3'); - }); - }); - -}); - -AppRouter::get('/root', function (){ - CLIConsole::say('Call from /root (declared after /ajax prefix group ; after /test prefix group)'); -}); - -AppRouter::group([], function (){ - AppRouter::get('/not_group', function () { - - }); -}); - - - - -``` - - -# todo - -Реализовать хелпер `router()`: - -```php - -// роутер -// 4 аргумента: строка, строка, хэндлер, имя -router('GET', '/', function (){}, 'root'); - -// 3 аргумента: массив, closure, массив от нуля до двух ключей -router(['prefix'=>'/test'], function (){ - - // 3-4 аргумента: строка, строка, коллбэк/хэндлер, [строка] - router('GET', '/ajax', 'Test::ajax'); - - // 2-3 аргумента: строка, коллбэк/хэндлер, [строка] - router('GET /ajaxKeys', 'Test::ajaxKeys'); - -}, [ - 'before' => handler, - 'after' => handler -]); - - - -``` \ No newline at end of file diff --git a/docs/CLIConsole.md b/docs/CLIConsole.md deleted file mode 100644 index dbc41e1..0000000 --- a/docs/CLIConsole.md +++ /dev/null @@ -1,2 +0,0 @@ - -Теперь сообщение печатает метод `say()`, метод `get_message()` генерит подходящее в зависимости от CLI/WEB, `format` форматирует сообщение ESCAPE-кодами. \ No newline at end of file diff --git a/docs/Curl.md b/docs/Core/Curl.md similarity index 100% rename from docs/Curl.md rename to docs/Core/Curl.md diff --git a/docs/Dataset.md b/docs/Helpers/Dataset.md similarity index 100% rename from docs/Dataset.md rename to docs/Helpers/Dataset.md diff --git a/functions/functions.php b/functions/functions.php index c620319..ee5b094 100644 --- a/functions/functions.php +++ b/functions/functions.php @@ -77,32 +77,7 @@ function setOption(array $options = [], $key = null, $default_value = null) } } -if (!function_exists('Arris\groupDatasetByColumn')) { - - /** - * Возвращает новый датасет, индекс для строк которого равен значению колонки строки - * Предназначен для переформатирования PDO-ответов, полученных в режиме FETCH_ASSOC - * - * [ 0 => [ 'id' => 5, 'data' => 10], 1 => [ 'id' => 6, 'data' => 12] .. ] - * При вызове с аргументами ($dataset, 'id') возвращает - * [ 5 => [ 'id' => 5, 'data' => 10], 6 => [ 'id' => 6, 'data' => 12] .. ] - * - * @param array $dataset - * @param $column_id - * @return array - */ - function groupDatasetByColumn(array $dataset, $column_id): array - { - $result = []; - array_map(static function ($row) use (&$result, $column_id){ - $result[ $row[ $column_id ] ] = $row; - }, $dataset); - return $result; - } -} - if (!function_exists('Arris\jsonize')) { - /** * Конвертирует в JSON * diff --git a/functions/helpers.php b/functions/helpers.php index 80d732d..bb05f24 100644 --- a/functions/helpers.php +++ b/functions/helpers.php @@ -1,18 +1,26 @@ '; + if (php_sapi_name() !== "cli") { + echo '
';
+        }
+
         if (func_num_args()) {
             foreach (func_get_args() as $arg) {
                 var_dump($arg);
             }
         }
-        if (php_sapi_name() !== "cli") echo '
'; + + if (php_sapi_name() !== "cli") { + echo ''; + } } } @@ -20,51 +28,55 @@ function d() { /** * Dump and die */ - function dd() { - if (php_sapi_name() !== "cli") echo '
';
+    function dd(...$args) {
+        if (php_sapi_name() !== "cli") {
+            echo '
';
+        }
+
         if (func_num_args()) {
             foreach (func_get_args() as $arg) {
                 var_dump($arg);
             }
         }
-        if (php_sapi_name() !== "cli") echo '
'; + + if (php_sapi_name() !== "cli") { + echo '
'; + } + // d($args); die; } } -if (!function_exists('__env')) { +if (!function_exists('_env')) { /** - * Возвращает переменную окружения либо (если её нет) - значение по умолчанию (null) - * - * @param $key - * @param null $value - * @return array|false|string|null + * @param string $key + * @param $default + * @param string $type (allowed: '', bool, int, float, string, array?, null) + * @return array|mixed|string */ - function __env($key, $value = null) - { - return array_key_exists($key, getenv()) ? getenv($key) : $value; - } -} + function _env(string $key, $default, string $type = '') { + $k = getenv($key); + if ($k === false) { + return $default; + } -if (!function_exists('__envPath')) { - /** - * Возвращает значение пути (path) из окружения (env), возможно, с заключительным слэшэм - * - * @param $key - * @param bool $with_tailing_slash (false) - * @return string - */ - function __envPath($key, $with_tailing_slash = false) - { - return $with_tailing_slash - ? rtrim(getenv($key), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR - : rtrim(getenv($key), DIRECTORY_SEPARATOR); + if ($type !== '') { + if ($type === 'array') { + return explode(' ', trim(str_replace(['[', ']'], '', $k))); + } + + $st = settype($k, $type); + + if ($st === false) { + return $default; + } + } + return $k; } } if (!function_exists('getJSONPayload')) { - /** * Возвращает десериализованный payload * diff --git a/sources/Helpers/Arrays.php b/sources/Helpers/Arrays.php new file mode 100644 index 0000000..7e67412 --- /dev/null +++ b/sources/Helpers/Arrays.php @@ -0,0 +1,56 @@ + [ 'id' => 5, 'data' => 10], 1 => [ 'id' => 6, 'data' => 12] .. ] + * При вызове с аргументами ($dataset, 'id') возвращает + * [ 5 => [ 'id' => 5, 'data' => 10], 6 => [ 'id' => 6, 'data' => 12] .. ] + * + * @param array $dataset + * @param $column_id + * @return array + */ + public static function groupDatasetByColumn(array $dataset, $column_id):array + { + $result = []; + array_map(static function ($row) use (&$result, $column_id){ + $result[ $row[ $column_id ] ] = $row; + }, $dataset); + return $result; + } + } \ No newline at end of file diff --git a/sources/Helpers/Strings.php b/sources/Helpers/Strings.php new file mode 100644 index 0000000..a49370f --- /dev/null +++ b/sources/Helpers/Strings.php @@ -0,0 +1,52 @@ += 2 && $number % 10 <= 4 && ($number % 100 < 10 || $number % 100 >= 20)) + ? $forms[1] + : $forms[2] + ); + } + + + + + +} \ No newline at end of file diff --git a/sources/Helpers/StringsHTML.php b/sources/Helpers/StringsHTML.php new file mode 100644 index 0000000..01e3539 --- /dev/null +++ b/sources/Helpers/StringsHTML.php @@ -0,0 +1,59 @@ +/si', trim($tags), $tags_list); + $tags_list = array_unique($tags_list[1]); + + if(!empty($tags_list) AND count($tags_list) > 0) { + $imploded_tags = implode('|', $tags_list); + return $invert + ? preg_replace('@<('. $imploded_tags .')\b.*?>.*?@si', '', $text) + : preg_replace('@<(?!(?:'. $imploded_tags .')\b)(\w+)\b.*?>.*?@si', '', $text); + + } elseif($invert === false) { + return preg_replace('@<(\w+)\b.*?>.*?@si', '', $text); + } + return $text; + } + + /** + * Вызывается в шаблонах, закрывает открытые теги у обрезанной сторочки + * + * @param $content + * @return string + */ + public static function close_tags($content): string + { + preg_match_all('#<(?!meta|em|strong|img|br|hr|input\b)\b([a-z]+)(?: .*)?(?#iU', $content, $result); $openedtags = $result[1]; + preg_match_all('##iU', $content, $result); $closedtags = $result[1]; + + $len_opened = count($openedtags); + if (count($closedtags) == $len_opened) { + return $content; + } + $openedtags = array_reverse($openedtags); + for ($i = 0; $i < $len_opened; $i++) { + if (!in_array($openedtags[$i], $closedtags)) { + $content .= ''; + } else { + unset($closedtags[ array_search($openedtags[$i], $closedtags) ]); + } + } + return $content; + } + + +} \ No newline at end of file diff --git a/tests/HelpersArrayTest.php b/tests/HelpersArrayTest.php new file mode 100644 index 0000000..71c6ed0 --- /dev/null +++ b/tests/HelpersArrayTest.php @@ -0,0 +1,34 @@ +assertEquals([1, 2], Arrays::explodeToInt('1 2')); + } + + /** + * @return void + * @testdox Разбивает строку по $separator и возвращает массив значений, приведенных к BOOL + */ + public function explodeToBool() + { + $this->assertEquals([false, true, false], Arrays::explodeToType('0 1 0', ' ', 'bool')); + } + + /** + * @return void + * @testdox Разбивает строку по $separator и возвращает массив значений, приведенных к array + */ + public function explodeToArray() + { + $this->assertEquals([['A'], ['B'], ['C']], Arrays::explodeToType('A B C', ' ', 'array')); + } + +} \ No newline at end of file diff --git a/tests/HelpersStringsTest.php b/tests/HelpersStringsTest.php new file mode 100644 index 0000000..5fec99d --- /dev/null +++ b/tests/HelpersStringsTest.php @@ -0,0 +1,63 @@ +assertEquals('штука', Strings::pluralForm(1, ['штука', 'штуки', 'штук'])); + $this->assertEquals('штуки', Strings::pluralForm(2, ['штука', 'штуки', 'штук'])); + $this->assertEquals('штук', Strings::pluralForm(5, ['штука', 'штуки', 'штук'])); + } + + /** + * @return void + * @testdox Тест форм числительного, если форм 2 + */ + public function pluralFormTestFormsCount2() + { + $this->assertEquals('штука', Strings::pluralForm(1, ['штука', 'штуки'])); + $this->assertEquals('штуки', Strings::pluralForm(2, ['штука', 'штуки'])); + $this->assertEquals('штуки', Strings::pluralForm(5, ['штука', 'штуки'])); + } + + /** + * @return void + * @testdox Тест форм числительного, если форм 1 + */ + public function pluralFormTestFormsCount1() + { + $this->assertEquals('штука', Strings::pluralForm(1, ['штука'])); + $this->assertEquals('штука', Strings::pluralForm(2, ['штука'])); + $this->assertEquals('штука', Strings::pluralForm(5, ['штука'])); + } + + /** + * @return void + * @testdox Тест форм числительного, если массив форм пуст + */ + public function pluralFormTestFormsEmpty() + { + $this->assertEquals('1', Strings::pluralForm(1, [])); + $this->assertEquals('2', Strings::pluralForm(2, [])); + $this->assertEquals('5', Strings::pluralForm(5, [])); + } + + /** + * @return void + * @testdox Тест форм числительного, если массив форм не массив + */ + public function pluralFormTestFormsNotValid() + { + $this->assertEquals('1', Strings::pluralForm(1, false)); + $this->assertEquals('2', Strings::pluralForm(2, true)); + $this->assertEquals('5', Strings::pluralForm(5, new stdClass())); + } + +} \ No newline at end of file diff --git a/tests/standalone/test_.php b/tests/standalone/test_.php index 48b34a7..d3dcb67 100644 --- a/tests/standalone/test_.php +++ b/tests/standalone/test_.php @@ -1,3 +1,6 @@