yf_debug class

Debug console (DEBUG_MODE only): the panel at the end of the page, built from the debug() records of the request.

Every `_debug_<name>()` method is a tab; it returns the tab's HTML and may set `$params['count']`, `$params['time']` (seconds)
and `$params['bad']` for the tab label. Listeners of the `debug.render` event may return `[name => html]` to add tabs, and
modules with a `_hook_debug($debug)` method feed the "hooks" tab. The console renders into its own shadow root with its own
stylesheet (assets/yf/debug_console/), so a site theme cannot change it and it cannot change the site.

Fileclasses/yf_debug.class.php
Use_class('debug')

Public properties

A site sets them in its config with $PROJECT_CONF['debug']['NAME'] = ...;

PropertyDefaultDescription
$ADMIN_PATHS
['edit_stpl' => '?object=template_editor&action=edit_stpl&location={LOCATI'..., 'edit_i18n' => '?object=locale_editor&action=var_edit&id={ID}', 'edit_file' => '?object=file_manager&action=edit&id={ID}', 'show_db_table' => '?object=db_manager&action=table_show&id={ID}', 'sql_query' => '?object=db_manager&action=import&id={ID}', 'link' => '{ID}']
array (
  'edit_stpl' => '?object=template_editor&action=edit_stpl&location={LOCATION}&theme={THEME}&name={ID}',
  'edit_i18n' => '?object=locale_editor&action=var_edit&id={ID}',
  'edit_file' => '?object=file_manager&action=edit&id={ID}',
  'show_db_table' => '?object=db_manager&action=table_show&id={ID}',
  'sql_query' => '?object=db_manager&action=import&id={ID}',
  'link' => '{ID}',
)
$DEBUG_CONSOLE_HIDDEN null
$DEBUG_CONSOLE_LIGHT null
$MAX_EXPLAIN
int
100 EXPLAIN runs for at most this many SELECT queries per connection
$MAX_TRACE_FRAMES
int
25 Frames of a stack trace shown, the rest is counted
$MAX_VALUE_LEN
int
20000 A value longer than this (bytes) is cut in the console
$SHOW_APC_INFO true
$SHOW_CORE_CACHE true
$SHOW_CURL_REQUESTS true
$SHOW_DASHBOARD_INFO true
$SHOW_DB_EXPLAIN_QUERY true
$SHOW_DB_QUERY_LOG true
$SHOW_DB_STATS false
$SHOW_DD_TABLE true
$SHOW_ENV_DATA true
$SHOW_FILES_DATA true
$SHOW_FORM2 true
$SHOW_GET_DATA true
$SHOW_GLOBALS true
$SHOW_HTMX
bool
true htmx requests the page makes after load (recorded in the browser)
$SHOW_I18N_VARS true
$SHOW_INCLUDED_FILES true
$SHOW_INPUT_DATA true
$SHOW_LOADED_MODULES true
$SHOW_LOGGER
bool
true logger() records of the request
$SHOW_MAIN_EXECUTE true
$SHOW_MAIN_GET_DATA true
$SHOW_MEMCACHED_INFO true
$SHOW_NOT_TRANSLATED true
$SHOW_OUTPUT_CACHE_INFO true
$SHOW_PHP_ERRORS
bool
true PHP warnings / notices / deprecations of the request (yf_core_errors)
$SHOW_POST_DATA true
$SHOW_REDIS_INFO true
$SHOW_RESIZED_IMAGES_LOG true
$SHOW_REWRITE_INFO true
$SHOW_SERVER_DATA true
$SHOW_SESSION_DATA true
$SHOW_SETTINGS true
$SHOW_SPHINX true
$SHOW_SSH true
$SHOW_STPLS true
$SHOW_TABLE2 true
$SORT_TEMPLATES_BY_NAME true
$TAB_LABELS
array
['DEBUG_YF' => 'yf', '_session' => 'session', 'sphinxsearch' => 'sphinx', 'php_errors' => 'errors', 'curl_requests' => 'curl', 'resize_images' => 'images'] Tab label per panel name, the name itself otherwise
$_NOT_TRANSLATED_FILE null
$_data
array
['b' => [], 'f' => [], 't' => []] @conf_skip lazy data: texts, trace frames, traces (frame ids)
$_data_ids
array
['b' => [], 'f' => [], 't' => []] @conf_skip id of each text / frame / trace stored in the lazy data
$_lazy
bool
false @conf_skip go() is rendering: hidden values and traces go into the lazy data of the console, deduplicated
$_timings
array
[] @conf_skip render time of each panel, seconds
$_used_debug_datas []
$backup_debug_data []

Public methods 77

go

public function go()

The console: host element, its declarative shadow root (style, bar, tabs, one inert <template> per pane) and its script.

    public function go()
    {
        if ($this->DEBUG_CONSOLE_HIDDEN) {
            return false;
        }
        // Do hide console if needed
        if (($_SESSION['hide_debug_console'] ?? null) || ($_GET['hide_debug_console'] ?? null)) {
            return '';
        }
        $ts = microtime(true);
        $exec_time = microtime(true) - main()->_time_start;
        // what _show_execution_time() set, without its translated text (t() calls would land in the i18n tab)
        main()->_time_end = common()->_format_time_value($exec_time);
        $summary = $this->_summary($exec_time);

        $panels = [];
        $meta = [];
        $this->_timings = [];
        $this->_data = ['b' => [], 'f' => [], 't' => []];
        $this->_data_ids = ['b' => [], 'f' => [], 't' => []];
        $this->_lazy = true;
        $class_name = get_class($this);
        foreach ((array) get_class_methods($class_name) as $method) {
            if (substr($method, 0, strlen('_debug_')) != '_debug_' || $method == $class_name || $method == __FUNCTION__) {
                continue;
            }
            $name = substr($method, strlen('_debug_'));
            if ($this->DEBUG_CONSOLE_LIGHT && $name !== 'DEBUG_YF') {
                continue;
            }
            $ts2 = microtime(true);
            $method_params = [];
            $content = $this->$method($method_params);
            $this->_timings[$method] = microtime(true) - $ts2;
            $panels[$name] = $content;
            $meta[$name] = is_array($method_params) ? $method_params : [];
        }
        // listeners return [name => html]: the payload is passed by value, so the return value is the only way back
        $debug_add = [];
        $responses = _class('core_events')->fire('debug.render', $debug_add);
        foreach ((array) $responses as $response) {
            is_array($response) && $debug_add = array_merge($debug_add, $response);
        }
        foreach ((array) $debug_add as $k => $v) {
            $panels[$k] = $v;
        }
        $this->_lazy = false;
        $debug_time = microtime(true) - $ts;
        $summary['console'] = $debug_time;
        if (isset($panels['DEBUG_YF']) && is_string($panels['DEBUG_YF']) && $panels['DEBUG_YF'] !== '') {
            $timings = [];
            foreach ($this->_timings as $method => $time) {
                $timings[$method] = $this->_ms($time);
            }
            $panels['DEBUG_YF'] .= $this->_heading('console render: ' . $this->_ms($debug_time))
                . '<div class="yfd-grid">' . $this->_show_key_val_table($timings, ['no_sort' => 1]) . '</div>';
        }
        return $this->_render_console($panels, $meta, $summary);
    }

Methods starting with _ hooks and helpers; not routable as page actions

__call

public function __call($name, $args)

Catch missing method call.

    public function __call($name, $args)
    {
        return main()->extend_call($this, $name, $args);
    }

_asset_items

public function _asset_items($items)

Rows of the assets_out / assets_add logs: a one-line preview, the length and the content (opened from the preview).

    public function _asset_items($items)
    {
        $items = (array) $items;
        $i = 0;
        foreach ($items as $k => $v) {
            $v = (array) $v;
            $content = is_string($v['content'] ?? null) ? $v['content'] : $this->_var_export($v['content'] ?? '');
            unset($v['content'], $v['is_added']);
            $preview = mb_strimwidth((string) preg_replace('~\s+~u', ' ', $content), 0, 120, '...', 'UTF-8');
            $items[$k] = ['id' => ++$i] + $v + ['preview' => $preview, 'strlen' => strlen($content), 'content' => $content];
        }
        return $items;
    }

_bytes

public function _bytes($bytes)
    public function _bytes($bytes)
    {
        $bytes = (float) $bytes;
        if ($bytes >= 1048576) {
            return number_format($bytes / 1048576, 1, '.', '') . ' MB';
        }
        return $bytes >= 1024 ? number_format($bytes / 1024, 1, '.', '') . ' KB' : (int) $bytes . ' B';
    }

_cache_panel

public function _cache_panel($name, &$params, $hidden_map)
  • $name mixed cache_get, cache_set ...
    public function _cache_panel($name, &$params, $hidden_map)
    {
        if ( ! $this->SHOW_CORE_CACHE) {
            return '';
        }
        $items = (array) $this->_get_debug_data($name);
        $time = 0.0;
        foreach ($items as $k => $v) {
            $items[$k] = $this->_data_item($v);
            $time += (float) ($v['time'] ?? 0);
        }
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        $params['time'] = $time;
        return $this->_show_auto_table($items, ['hidden_map' => $hidden_map, 'caption' => ['driver' => cache()->DRIVER, 'ns' => cache()->CACHE_NS]]);
    }

_cap

public function _cap($text, $max = 0)

$text cut to $max bytes (MAX_VALUE_LEN by default) on a UTF-8 boundary, with a note of the full size.

    public function _cap($text, $max = 0)
    {
        $text = (string) $text;
        $max = (int) ($max ?: $this->MAX_VALUE_LEN);
        if ($max <= 0 || strlen($text) <= $max) {
            return $text;
        }
        return mb_strcut($text, 0, $max, 'UTF-8') . PHP_EOL . '... cut, ' . strlen($text) . ' bytes in all';
    }

_cell

public function _cell($value, $raw = false, $col = '')

A cell value: short text escaped inline, long text in a <pre>, arrays dumped; $raw passes trusted HTML through.

    public function _cell($value, $raw = false, $col = '')
    {
        if ($value === null || $value === false || $value === [] || $value === '') {
            return '';
        }
        if (is_array($value) || is_object($value)) {
            return $this->_pre($this->_var_export($value));
        }
        if ($value === true) {
            return '1';
        }
        if ($raw) {
            return (string) $value;
        }
        if ($col === 'time' && is_numeric($value)) {
            return $this->_ms($value);
        }
        $value = (string) $value;
        if (strlen($value) > 160 || strpos($value, "\n") !== false) {
            return $this->_pre($value);
        }
        return $this->_e($value);
    }

_console_file

public function _console_file($name)

Stylesheet / script of the console, inlined: the framework dir is not served by the web server.

    public function _console_file($name)
    {
        static $files = [];
        if ( ! isset($files[$name])) {
            $path = YF_PATH . 'assets/yf/debug_console/' . basename((string) $name);
            $text = is_file($path) ? (string) file_get_contents($path) : '';
            // the inline <style> / <script> must not be closed early by the file's own text
            $files[$name] = str_ireplace(['</style', '</script'], ['<\/style', '<\/script'], $text);
        }
        return $files[$name];
    }

_data_item

public function _data_item($v)

A get_data / cache row: `data` dumped and cut, its size next to it.

    public function _data_item($v)
    {
        $v = (array) $v;
        if (array_key_exists('data', $v)) {
            $data = $v['data'];
            $dump = is_string($data) ? $data : $this->_var_export($data);
            $v['data_size'] = $data === null ? 'NULL' : strlen($dump);
            $v['data'] = $data === null ? '' : $this->_cap($dump, 2000);
        }
        return $v;
    }
public function _debug_DEBUG_YF(&$params = [])
    public function _debug_DEBUG_YF(&$params = [])
    {
        if ( ! $this->SHOW_SETTINGS) {
            return '';
        }
        $cache_use = ((main()->USE_SYSTEM_CACHE || conf('USE_CACHE')) && ! cache()->NO_CACHE);
        $locale_debug = (array) $this->_get_debug_data('locale');
        $lang_selected = $locale_debug['lang_selected'] ?? '';
        $data['yf'] = [
            'MAIN_TYPE' => MAIN_TYPE,
            'LANG' => conf('language'),
            'IP' => common()->get_ip(),
            'COUNTRY' => conf('country') ?: ($_SERVER['GEOIP_COUNTRY_CODE'] ?? null),
            'TIMEZONE' => date_default_timezone_get() . (conf('timezone') ? ', conf: ' . conf('timezone') : ''),
            'DEBUG_MODE' => (int) DEBUG_MODE,
            'DEV_MODE' => (int) conf('DEV_MODE'),
            'REWRITE_MODE' => (int) tpl()->REWRITE_MODE,
            'DEBUG_CONSOLE_POPUP' => (int) conf('DEBUG_CONSOLE_POPUP'),
            'CACHE_USE' => (int) $cache_use,
            'CACHE_NO_CACHE' => (int) cache()->NO_CACHE,
            'CACHE_NO_WHY' => cache()->_NO_CACHE_WHY,
            'CACHE_DRIVER' => cache()->DRIVER,
            'CACHE_NS' => cache()->CACHE_NS,
            'CACHE_TTL' => (int) cache()->TTL,
            'YF_PATH' => YF_PATH,
            'YF_VERSION' => $this->_get_yf_version(),
            'APP_PATH' => APP_PATH,
            'APP_VERSION' => $this->_get_app_version(),
            'PROJECT_PATH' => PROJECT_PATH,
            'SITE_PATH' => SITE_PATH,
            'ADMIN_SITE_PATH' => ADMIN_SITE_PATH,
            'CONFIG_PATH' => CONFIG_PATH,
            'STORAGE_PATH' => STORAGE_PATH,
            'LOGS_PATH' => LOGS_PATH,
            'UPLOADS_PATH' => UPLOADS_PATH,
            'WEB_PATH' => WEB_PATH,
            'MEDIA_PATH' => MEDIA_PATH,
            'ADMIN_WEB_PATH' => ADMIN_WEB_PATH,
            'CSS_FRAMEWORK' => conf('css_framework') ?: 'bs3',
            'BOOTSTRAP_THEME' => common()->bs_current_theme() ?: ($_COOKIE['yf_theme'] ?? null) ?: (conf('DEF_BOOTSTRAP_THEME_' . strtoupper(MAIN_TYPE)) ?: conf('DEF_BOOTSTRAP_THEME')),
            'TPL_DRIVER' => tpl()->DRIVER_NAME,
            'TPL_COMPILE' => (int) tpl()->COMPILE_TEMPLATES,
            'TPL_THEMES_PATH' => tpl()->_THEMES_PATH,
            'TPL_PATH' => tpl()->TPL_PATH,
            'TPL_SKIN' => conf('theme'),
            'TPL_INHERIT_SKIN' => (string) tpl()->INHERIT_SKIN,
            'TPL_INHERIT_SKIN2' => (string) tpl()->INHERIT_SKIN2,
            'MAIN_HOSTNAME' => main()->HOSTNAME,
            'SITE_ID' => (int) conf('SITE_ID'),
            'SERVER_ID' => (int) conf('SERVER_ID'),
            'SERVER_ROLE' => conf('SERVER_ROLE'),
            'SERVER_ADDR' => ($_SERVER['SERVER_ADDR'] ?? null),
            'SERVER_SELF_IPS' => implode(', ', (array) main()->_server_self_ips),
            'USER_ID' => (int) main()->USER_ID,
            'USER_GROUP' => (int) main()->USER_GROUP,
            'USER_ROLE' => main()->USER_ROLE,
            'IS_POST' => (int) main()->is_post(),
            'IS_AJAX' => (int) main()->is_ajax(),
            'IS_HTTPS' => (int) main()->is_https(),
            'IS_CONSOLE' => (int) main()->is_console(),
            'IS_REDIRECT' => (int) main()->is_redirect(),
            'IS_COMMON_PAGE' => (int) main()->is_common_page(),
            'IS_UNIT_TEST' => (int) main()->is_unit_test(),
            'IS_SPIDER' => (int) conf('IS_SPIDER'),
            'NO_GRAPHICS' => (int) main()->NO_GRAPHICS,
            'HTTP_HOST' => ($_SERVER['HTTP_HOST'] ?? null),
            'SERVER_PORT' => ($_SERVER['SERVER_PORT'] ?? null),
            'REWRITE_DEF_HOST' => _class('rewrite')->DEFAULT_HOST,
            'REWRITE_DEF_PORT' => _class('rewrite')->DEFAULT_PORT,
            'WEB_DOMAIN' => WEB_DOMAIN,
            'REQUEST_URI' => ($_SERVER['REQUEST_URI'] ?? null),
            'REQUEST_METHOD' => ($_SERVER['REQUEST_METHOD'] ?? null),
            'OUTPUT_CACHING' => (int) main()->OUTPUT_CACHING,
            'NO_CACHE_HEADERS' => (int) main()->NO_CACHE_HEADERS,
            'LANG_SELECTED_FROM' => $lang_selected . ' (' . ($locale_debug['lang_variants'][$lang_selected] ?? '') . ')',
            'LANG_PRIORITIES' => implode(', ', (array) ($locale_debug['lang_priorities'] ?? [])),
            'LANG_VARIANTS' => $locale_debug['lang_variants'] ?? null,
            'LOCALE_CURRENT' => $locale_debug['current'] ?? null,
            'LOCALE_DEFAULT' => $locale_debug['default'] ?? null,
            'LOCALE_LC_VARIANTS' => implode(', ', (array) ($locale_debug['lc_variants']['LC_ALL'] ?? [])),
            'LOCALE_SYSTEM' => implode(', ', (array) ($locale_debug['system'] ?? [])),
            'HTTP_IN_HEADERS' => $this->_get_request_headers(),
            'HTTP_OUT_HEADERS' => headers_list(),
        ];
        foreach ((array) $this->_get_debug_data('_DEBUG_META') as $k => $v) {
            $data['yf']['META_' . strtoupper($k)] = $v;
        }

        $ini_all = ini_get_all(null, false);
        foreach (['memory_limit', 'max_execution_time', 'default_socket_timeout', 'max_input_time', 'post_max_size', 'upload_max_filesize',
            'file_uploads', 'allow_url_fopen', 'error_reporting', 'display_errors', 'opcache.enable', 'opcache.jit'] as $name) {
            isset($ini_all[$name]) && $data['php']['ini: ' . $name] = $ini_all[$name];
        }
        if (tpl()->COMPRESS_OUTPUT && ! main()->NO_GRAPHICS) {
            $c_info = (array) $this->_get_debug_data('compress_output');
            $original = (int) ($c_info['size_original'] ?? 0);
            $compressed = (int) ($c_info['size_compressed'] ?? 0);
            $data['php'] += [
                'compress: size original' => $original . ' bytes',
                'compress: size compressed' => $compressed . ' bytes',
                'compress: ratio' => ($compressed ? round($original / $compressed * 100, 0) : 0) . '%',
            ];
        }
        $loaded_exts = array_map('strtolower', get_loaded_extensions());
        sort($loaded_exts);
        $data['php'] += [
            'memory_usage' => $this->_bytes(memory_get_usage()),
            'memory_peak_usage' => $this->_bytes(memory_get_peak_usage()),
            'sys_loadavg' => function_exists('sys_getloadavg') ? implode(' | ', (array) sys_getloadavg()) : '',
            'db_server_version' => db()->get_server_version(),
            'db_host_info' => db()->get_host_info(),
            'php_version' => phpversion(),
            'php_sapi_name' => php_sapi_name(),
            'php_current_user' => get_current_user(),
            'php_uname' => php_uname(),
            'php_include_path' => get_include_path(),
            'php_loaded_extensions' => implode(', ', $loaded_exts),
            'php_ini_scanned_files' => function_exists('php_ini_scanned_files') ? php_ini_scanned_files() : '',
        ];
        // the session tab has the same, unless it is off
        if ( ! $this->SHOW_SESSION_DATA) {
            $data['session']['session_id'] = session_id();
            foreach ((array) ini_get_all('session', false) as $k => $v) {
                $data['session'][$k] = $v;
            }
        }
        // the switches of test::change_debug (session values), as before
        $form = '<form class="yfd-form" method="post" action="' . $this->_e(url('/test/change_debug')) . '">'
            . '<input type="hidden" name="locale_edit" value="0"><label><input type="checkbox" name="locale_edit" value="1"'
            . (($_SESSION['locale_vars_edit'] ?? null) ? ' checked' : '') . '> locale edit</label>'
            . '<input type="hidden" name="debug_console_light" value="0"><label><input type="checkbox" name="debug_console_light" value="1"'
            . (($_SESSION['debug_console_light'] ?? null) ? ' checked' : '') . '> console light (this tab only)</label>'
            . '<button type="submit">save</button></form>';
        $body = '';
        foreach ($data as $name => $_data) {
            $body .= '<div>' . $this->_heading($name) . $this->_show_key_val_table($_data, ['no_sort' => 1, 'raw_keys' => $name == 'yf' ? ['YF_VERSION', 'APP_VERSION'] : []]) . '</div>';
        }
        return $form . '<div class="yfd-grid">' . $body . '</div>';
    }
public function _debug__session(&$params = [])
    public function _debug__session(&$params = [])
    {
        if ( ! $this->SHOW_SESSION_DATA) {
            return '';
        }
        $out = '<div>' . $this->_heading('$_SESSION') . $this->_show_key_val_table((array) ($_SESSION ?? [])) . '</div>';
        $data = ['session_id' => session_id()];
        foreach ((array) ini_get_all('session', false) as $k => $v) {
            $data[$k] = $v;
        }
        $out .= '<div>' . $this->_heading('session ini') . $this->_show_key_val_table($data, ['no_sort' => 1]) . '</div>';
        $items = $this->_get_debug_data('input_session');
        $items && $out .= '<div>' . $this->_show_auto_table($items, ['hidden_map' => ['trace' => 'name']]) . '</div>';
        $params['count'] = count((array) ($_SESSION ?? []));
        return '<div class="yfd-grid">' . $out . '</div>';
    }

_debug_apc

public function _debug_apc(&$params = [])
    public function _debug_apc(&$params = [])
    {
        if ( ! $this->SHOW_APC_INFO || ! function_exists('apcu_cache_info') || ! function_exists('apcu_enabled') || ! apcu_enabled()) {
            return '';
        }
        $data = (array) apcu_cache_info(true);
        foreach ((array) ini_get_all('apcu', false) as $_k => $_v) {
            $data[$_k] = $_v;
        }
        return $this->_show_key_val_table($data);
    }

_debug_assets

public function _debug_assets(&$params = [])
    public function _debug_assets(&$params = [])
    {
        $body = [];
        $items = $this->_asset_items($this->_get_debug_data('assets_out'));
        $params['count'] = count($items);
        $body['out: ' . count($items)] = $this->_show_auto_table($items, ['hidden_map' => ['trace' => 'name', 'content' => 'preview', 'params' => 'name']]);

        $items = $this->_asset_items($this->_get_debug_data('assets_add'));
        $body['add log: ' . count($items)] = $this->_show_auto_table($items, ['hidden_map' => ['trace' => 'name', 'content' => 'preview', 'params' => 'name']]);

        $items = (array) $this->_get_debug_data('assets_names');
        $i = 0;
        foreach ($items as $k => $v) {
            $items[$k] = ['id' => ++$i] + (array) $v;
        }
        $body['names: ' . count($items)] = $this->_show_auto_table($items, ['hidden_map' => ['content' => 'path']]);

        return $this->_tabs($body);
    }
public function _debug_cache_del(&$params = [])
    public function _debug_cache_del(&$params = [])
    {
        return $this->_cache_panel('cache_del', $params, ['trace' => 'name']);
    }
public function _debug_cache_get(&$params = [])
    public function _debug_cache_get(&$params = [])
    {
        return $this->_cache_panel('cache_get', $params, ['trace' => 'params', 'data' => 'name']);
    }
public function _debug_cache_other(&$params = [])

The other cache calls: flush, multi_*, list_keys, del_by_prefix ...

    public function _debug_cache_other(&$params = [])
    {
        if ( ! $this->SHOW_CORE_CACHE) {
            return '';
        }
        $items = [];
        $time = 0.0;
        foreach ((array) debug() as $key => $rows) {
            if (strpos((string) $key, 'cache_') !== 0 || in_array($key, ['cache_get', 'cache_set', 'cache_del'], true) || ! is_array($rows)) {
                continue;
            }
            foreach ((array) $this->_get_debug_data($key) as $v) {
                $items[] = ['call' => substr($key, strlen('cache_'))] + $this->_data_item($v);
                $time += (float) ($v['time'] ?? 0);
            }
        }
        $params['count'] = count($items);
        $params['time'] = $time;
        return $this->_show_auto_table($items, ['hidden_map' => ['trace' => 'call', 'data' => 'call']]);
    }
public function _debug_cache_set(&$params = [])
    public function _debug_cache_set(&$params = [])
    {
        return $this->_cache_panel('cache_set', $params, ['trace' => 'name', 'data' => 'name']);
    }
public function _debug_curl_requests(&$params = [])
    public function _debug_curl_requests(&$params = [])
    {
        if ( ! $this->SHOW_CURL_REQUESTS) {
            return '';
        }
        $items = (array) $this->_get_debug_data('curl_get_remote_page');
        foreach ($items as $k => $v) {
            $info = (array) ($v['info'] ?? []);
            $items[$k] = [
                'id' => $k + 1,
                'url' => $info['url'] ?? '',
                'status' => $info['http_code'] ?? '',
                'time' => (float) ($info['total_time'] ?? 0),
                'info' => $info,
                'trace' => $v['trace'] ?? '',
            ];
        }
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        return $this->_show_auto_table($items, ['hidden_map' => ['trace' => 'url', 'info' => 'url']]);
    }
public function _debug_dashboard()
    public function _debug_dashboard()
    {
        if ( ! $this->SHOW_DB_STATS) {
            return '';
        }
        $items = $this->_get_debug_data('dashboard');
        if ( ! isset($items) || ! isset($items['widgets'])) {
            return false;
        }
        $loaded_modules = (array) ($this->backup_debug_data['main_load_class'] ?? []);
        $_items = [];
        foreach ((array) $items['widgets'] as $key => $value) {
            $_items[$key]['class_name'] = $value['class_name'];
            $_items[$key]['action'] = $value['action'];
            foreach ($loaded_modules as $v) {
                if (($value['class_name'] == $v['class_name'])) {
                    $_items[$key]['loaded_class_name'] = $v['loaded_class_name'];
                    $_items[$key]['storage'] = $v['storage'];
                    $_items[$key]['loaded_path'] = $v['loaded_path'];
                    break;
                }
            }
            $_items[$key]['time'] = $value['time'];
        }
        $_items = $this->_time_count_changes($_items);
        return $this->_show_auto_table($_items, ['caption' => ['dashboard' => $items['name'] ?? '', 'total_time' => $items['total_time'] ?? '']]);
    }

_debug_db

public function _debug_db(&$params = [])
    public function _debug_db(&$params = [])
    {
        if ( ! $this->SHOW_DB_QUERY_LOG) {
            return false;
        }
        $items = [];
        $count = 0;
        $time = 0.0;
        $instances_trace = (array) $this->_get_debug_data('db_instances_trace');
        foreach ((array) $this->_get_debug_data('db_instances') as $k => $db) {
            if ( ! is_object($db)) {
                continue;
            }
            foreach ((array) ($db->_LOG ?? []) as $log) {
                $count++;
                $time += (float) ($log['time'] ?? 0);
            }
            $connect_trace = $instances_trace[$k] ?? [];
            $name = $db->DB_TYPE . ' | ' . $db->DB_USER . ' | ' . $db->DB_HOST . ($db->DB_PORT ? ':' . $db->DB_PORT : '') . ' | ' . $db->DB_NAME;
            $items[$name] = $this->_do_debug_db_connection_queries($db, $connect_trace);
            $items['shutdown queries: ' . $name] = $this->_show_db_shutdown_queries($db);
            $items['stats: ' . $name] = $this->_show_db_stats($db);
        }
        $params['count'] = $count;
        $params['time'] = $time;
        // one connection: its DSN heads the table already
        return $this->_tabs($items, ['no_single_heading' => 1]);
    }
public function _debug_dd_table(&$params = [])
    public function _debug_dd_table(&$params = [])
    {
        if ( ! $this->SHOW_DD_TABLE) {
            return '';
        }
        $i = 0;
        $items = (array) $this->_get_debug_data('dd_table');
        foreach ($items as $k => $v) {
            $items[$k] = ['id' => ++$i] + (array) $v;
        }
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        return $this->_show_auto_table($items, ['hidden_map' => ['trace' => 'fields']]);
    }

_debug_events

public function _debug_events(&$params = [])
    public function _debug_events(&$params = [])
    {
        $main_ts = main()->_time_start;
        $items = [];
        $count = 0;
        foreach (['listen', 'fire', 'queue'] as $name) {
            $data = $this->_get_debug_data('events_' . $name);
            if ( ! $data) {
                continue;
            }
            foreach ((array) $data as $k => $v) {
                $data[$k]['time_offset'] = $this->_ms(($v['time_offset'] ?? $main_ts) - $main_ts);
            }
            $name === 'fire' && $count = count($data);
            $items[$name . ': ' . count($data)] = $this->_show_auto_table($data, ['no_total_time' => 1, 'hidden_map' => ['trace' => 'name']]);
        }
        $params['count'] = $count;
        return $this->_tabs($items);
    }
public function _debug_execute(&$params = [])
    public function _debug_execute(&$params = [])
    {
        if ( ! $this->SHOW_MAIN_EXECUTE) {
            return '';
        }
        $items = (array) $this->_get_debug_data('main_execute_block_time');
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        return $this->_show_auto_table($items, ['hidden_map' => ['trace' => 'params']]);
    }

_debug_form2

public function _debug_form2(&$params = [])
    public function _debug_form2(&$params = [])
    {
        if ( ! $this->SHOW_FORM2) {
            return '';
        }
        $i = 0;
        $items = (array) $this->_get_debug_data('form2');
        foreach ($items as $k => $v) {
            $items[$k] = ['id' => ++$i] + (array) $v;
        }
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        return $this->_show_auto_table($items, ['hidden_map' => ['trace' => 'params', 'fields' => 'params']]);
    }
public function _debug_get_data(&$params = [])
    public function _debug_get_data(&$params = [])
    {
        if ( ! $this->SHOW_MAIN_GET_DATA) {
            return '';
        }
        $items = (array) $this->_get_debug_data('main_get_data');
        foreach ($items as $k => $v) {
            $items[$k] = $this->_data_item($v);
        }
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        return $this->_show_auto_table($items, ['hidden_map' => ['trace' => 'params', 'data' => 'name']]);
    }
public function _debug_globals(&$params = [])
    public function _debug_globals(&$params = [])
    {
        if ( ! $this->SHOW_GLOBALS) {
            return '';
        }
        $classes_builtin = [];
        $classes_custom = [];
        foreach (get_declared_classes() as $name) {
            $r = new ReflectionClass($name);
            $file_name = $r->getFileName();
            if ( ! $file_name) {
                $classes_builtin[$name] = 'built-in';
            } else {
                $classes_custom[$name] = $this->_short_paths($file_name) . ':' . $r->getStartLine();
            }
        }
        ksort($classes_custom);
        ksort($classes_builtin);
        $data['classes'] = $classes_custom + $classes_builtin;

        $data['functions'] = [];
        $funcs = get_defined_functions();
        foreach ((array) $funcs['user'] as $name) {
            $r = new ReflectionFunction($name);
            $data['functions'][$name] = $this->_short_paths($r->getFileName()) . ':' . $r->getStartLine();
        }
        ksort($data['functions']);

        // names only: the values hold credentials (DB_PSWD ...)
        $constants = get_defined_constants(true);
        $data['constants'] = array_keys((array) ($constants['user'] ?? []));
        sort($data['constants']);

        $data['globals'] = array_values(array_filter(array_keys($GLOBALS), function ($v) {
            return $v[0] != '_';
        }));
        sort($data['globals']);

        $body = '';
        foreach ($data as $name => $_data) {
            $table = array_is_list($_data) ? $this->_pre(implode("\n", $_data)) : $this->_show_key_val_table($_data, ['no_sort' => 1]);
            $body .= '<div>' . $this->_heading($name . ' (' . count($_data) . ')') . $table . '</div>';
        }
        return '<div class="yfd-grid">' . $body . '</div>';
    }

_debug_hooks

public function _debug_hooks(&$params = [])
    public function _debug_hooks(&$params = [])
    {
        $items = [];
        $hook_name = '_hook_debug';
        foreach (main()->modules as $module_name => $module_obj) {
            if ( ! is_object($module_obj) || ! method_exists($module_obj, $hook_name)) {
                continue;
            }
            $items[$module_name] = $module_obj->$hook_name($this);
        }
        return $this->_show_key_val_table($items);
    }

_debug_htmx

public function _debug_htmx(&$params = [])

htmx requests of the page after load: the browser fills this tab (console.js), the tab shows once htmx is on the page. Each row has the X-YF-Debug header (time, db queries, memory) when the answer was made with DEBUG_MODE on.

    public function _debug_htmx(&$params = [])
    {
        if ( ! $this->SHOW_HTMX) {
            return '';
        }
        $params['hidden'] = true;
        $head = '';
        foreach (['id', 'verb', 'url', 'status', 'ms', 'bytes', 'target', 'X-YF-Debug', 'HX-Trigger'] as $col) {
            $head .= '<th>' . $this->_e($col) . '</th>';
        }
        return '<div class="yfd-note">htmx requests made by this page since it loaded (recorded in the browser).</div>'
            . '<table class="yfd-t"><thead><tr>' . $head . '</tr></thead><tbody></tbody></table>';
    }

_debug_i18n

public function _debug_i18n(&$params = [])
    public function _debug_i18n(&$params = [])
    {
        if ( ! $this->SHOW_I18N_VARS) {
            return '';
        }
        $i = 0;
        $calls = (array) (_class('i18n')->_calls ?? []);
        $items = (array) $this->_get_debug_data('i18n');
        foreach ($items as $k => $v) {
            $v = (array) $v;
            $v['calls'] = (int) ($calls[$v['name_orig'] ?? ''] ?? 0);
            $v['name'] = $this->_admin_link('edit_i18n', (string) ($v['name'] ?? ''));
            $items[$k] = ['id' => ++$i] + $v;
        }
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        return $this->_show_auto_table($items, ['raw' => ['name'], 'hidden_map' => ['trace' => 'name', 'data' => 'name']]);
    }
public function _debug_included(&$params = [])
    public function _debug_included(&$params = [])
    {
        if ( ! $this->SHOW_INCLUDED_FILES) {
            return '';
        }
        $total_size = 0;
        $i = 0;
        $items = (array) $this->_get_debug_data('included_files');
        foreach ($items as $k => $v) {
            if (empty($v['exists'])) {
                unset($items[$k]);
                continue;
            }
            $v['path'] = $this->_admin_link('edit_file', (string) ($v['path'] ?? ''));
            $items[$k] = ['id' => ++$i] + $v;
            $total_size += (int) ($v['size'] ?? 0);
        }
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        return $this->_show_auto_table($items, ['raw' => ['path'], 'caption' => ['total_size' => $this->_bytes($total_size)], 'hidden_map' => ['trace' => 'path']]);
    }

_debug_input

public function _debug_input(&$params = [])
    public function _debug_input(&$params = [])
    {
        if ( ! $this->SHOW_INPUT_DATA) {
            return '';
        }
        $body = [];
        foreach (['GET', 'POST', 'FILES', 'COOKIE', 'SERVER', 'ENV'] as $name) {
            $enabled_var = 'SHOW_' . $name . '_DATA';
            if ($this->$enabled_var) {
                $out = (string) $this->_show_key_val_table($GLOBALS['_' . $name] ?? null);
                $items = $this->_get_debug_data('input_' . strtolower($name));
                $items && $out .= $this->_show_auto_table($items, ['hidden_map' => ['trace' => 'name']]);
                $out !== '' && $body[] = $this->_heading('$_' . $name) . $out;
            }
        }
        return implode(PHP_EOL, $body);
    }

_debug_logger

public function _debug_logger(&$params = [])

logger() records of the request (yf_logger::_debug_record()), as the handlers got them.

    public function _debug_logger(&$params = [])
    {
        if ( ! $this->SHOW_LOGGER) {
            return '';
        }
        $items = [];
        $i = 0;
        $bad = false;
        foreach ((array) $this->_get_debug_data('logger') as $v) {
            $level = (string) ($v['level'] ?? '');
            $bad = $bad || in_array($level, ['emergency', 'alert', 'critical', 'error'], true);
            $items[] = [
                'id' => ++$i,
                'level' => '<span class="yfd-lvl-' . preg_replace('~[^a-z]~', '', $level) . '">' . $this->_e($level) . '</span>',
                'message' => $v['message'] ?? '',
                'context' => $v['context'] ?? [],
                'handlers' => implode(', ', (array) ($v['handlers'] ?? [])),
                'logged' => ! empty($v['done']) ? 'yes' : 'no',
                'time_offset' => isset($v['time']) ? $this->_ms($v['time'] - main()->_time_start) : '',
                'trace' => $v['trace'] ?? '',
            ];
        }
        $params['count'] = count($items);
        $params['bad'] = $bad;
        return $this->_show_auto_table($items, ['raw' => ['level'], 'hidden_map' => ['trace' => 'message', 'context' => 'message']]);
    }
public function _debug_memcached(&$params = [])
    public function _debug_memcached(&$params = [])
    {
        if ( ! $this->SHOW_MEMCACHED_INFO) {
            return '';
        }
        if (strpos(strtolower((string) cache()->DRIVER), 'memcache') === false) {
            return '';
        }
        $mc_obj = cache_memcached_connect();
        if ( ! is_object($mc_obj)) {
            return '';
        }
        $data = [];
        $ext = '';
        if (method_exists($mc_obj, 'getExtendedStats')) {
            $ext = 'memcache (old)';
            $data = $mc_obj->getExtendedStats();
        } elseif (method_exists($mc_obj, 'getStats')) {
            $ext = 'memcached (new)';
            $data = $mc_obj->getStats();
        }
        if ( ! $data) {
            return '';
        }
        $body = '';
        foreach ((array) $data as $name => $_data) {
            $body .= '<div>' . $this->_heading($name) . $this->_show_key_val_table($_data, ['skip_empty_values' => 1]) . '</div>';
        }
        return '<div class="yfd-note">PHP extension: ' . $this->_e($ext) . '</div><div class="yfd-grid">' . $body . '</div>';
    }
public function _debug_modules(&$params = [])
    public function _debug_modules(&$params = [])
    {
        if ( ! $this->SHOW_LOADED_MODULES) {
            return '';
        }
        $counter = 0;
        $items = [];
        $time = 0.0;
        foreach ((array) $this->_get_debug_data('main_load_class') as $data) {
            $path = (string) ($data['loaded_path'] ?? '');
            $items[] = [
                'id' => ++$counter,
                'module' => $data['class_name'] ?? '',
                'loaded_class' => $data['loaded_class_name'] ?? '',
                'path' => $this->_admin_link('edit_file', $path),
                'size' => $path !== '' && file_exists($path) ? filesize($path) : '',
                'storage' => $data['storage'] ?? '',
                'time' => (float) ($data['time'] ?? 0),
                'trace' => $data['trace'] ?? '',
                'storages' => $data['storages'] ?? '',
            ];
            $time += (float) ($data['time'] ?? 0);
        }
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        $params['time'] = $time;
        return $this->_show_auto_table($items, ['raw' => ['path'], 'hidden_map' => ['trace' => 'path', 'storages' => 'path']]);
    }

_debug_other

public function _debug_other(&$params = [])
    public function _debug_other(&$params = [])
    {
        $items = [];
        foreach ((array) debug() as $k => $v) {
            if (isset($this->_used_debug_datas[$k]) || $v === false) {
                continue;
            }
            $items[$k] = $v;
        }
        $params['count'] = count($items);
        return $this->_show_key_val_table($items);
    }
public function _debug_php_errors(&$params = [])

PHP warnings, notices and deprecations the request raised (yf_core_errors::_debug_record()).

    public function _debug_php_errors(&$params = [])
    {
        if ( ! $this->SHOW_PHP_ERRORS) {
            return '';
        }
        $items = [];
        $i = 0;
        foreach ((array) $this->_get_debug_data('php_errors') as $v) {
            $items[] = [
                'id' => ++$i,
                'type' => $v['type'] ?? '',
                'message' => $v['message'] ?? '',
                'file' => $this->_short_paths(($v['file'] ?? '') . ':' . ($v['line'] ?? '')),
                'time_offset' => isset($v['time']) ? $this->_ms($v['time'] - main()->_time_start) : '',
                'trace' => $v['trace'] ?? '',
            ];
        }
        $params['count'] = count($items);
        $params['bad'] = (bool) $items;
        return $this->_show_auto_table($items, ['hidden_map' => ['trace' => 'message'], 'tr' => function ($row) {
            return preg_match('~ERROR|WARNING~', (string) $row['type']) ? ['class' => 'error'] : [];
        }]);
    }
public function _debug_profiling(&$params = [])
    public function _debug_profiling(&$params = [])
    {
        $all_timings = main()->_timing;
        if ( ! $all_timings) {
            return false;
        }
        $ts = main()->_time_start;
        $_last_item = end($all_timings);
        $time_all = $_last_item[0] - $ts;
        $items = [];
        foreach ((array) $all_timings as $i => $v) {
            $time_offset = $v[0] - $ts;
            $time_change = 0;
            $time_change_p = 0;
            if (isset($all_timings[$i + 1])) {
                $time_change = $all_timings[$i + 1][0] - $v[0];
            }
            if ($time_change > 0.001 && $time_all > 0) {
                $time_change_p = round(100 - (($time_all - $time_change) / $time_all * 100), 1);
            }
            $items[] = [
                'i' => $i,
                'time_offset' => $this->_ms($time_offset),
                'time_change' => $time_change > 0.0001 ? $this->_ms($time_change) : '',
                'timep' => $time_change_p ? '<span class="yfd-pct' . ($time_change_p >= 5 ? ' yfd-warn' : '') . '" style="--p:' . min(100, $time_change_p) . '%">' . $time_change_p . '%</span>' : '',
                'class' => $v[1],
                'method' => $v[2],
                'trace' => $v[3],
                'args' => $v[4] ?: '',
            ];
        }
        $params['count'] = count($items);
        return $this->_show_auto_table($items, ['hidden_map' => ['trace' => 'method', 'args' => 'method']]);
    }

_debug_redis

public function _debug_redis(&$params = [])
    public function _debug_redis(&$params = [])
    {
        if ( ! $this->SHOW_REDIS_INFO) {
            return '';
        }
        $instances = [
            'redis_default' => redis(),
            'redis_cache' => strpos(strtolower((string) cache()->DRIVER), 'redis') !== false ? (cache()->_driver->_connection ?? null) : null,
        ];
        $counter = 0;
        $tabs = [];
        $time_all = 0.0;
        foreach ((array) $instances as $iname => $instance) {
            if ( ! is_object($instance) || empty($instance->log) || ($iname != 'redis_default' && $instance === $instances['redis_default'])) {
                continue;
            }
            $items = [];
            foreach ((array) $instance->log as $v) {
                $func = (string) ($v['func'] ?? '');
                $items[] = [
                    'id' => ++$counter,
                    'func' => '<a href="https://redis.io/commands/' . rawurlencode(strtolower($func)) . '" rel="noopener" target="_blank">' . $this->_e($func) . '</a>',
                    'args' => ! empty($v['args']) ? $this->_cap(implode(PHP_EOL, array_map(function ($a) {
                        return is_scalar($a) ? (string) $a : $this->_var_export($a);
                    }, (array) $v['args'])), 1000) : '',
                    'result' => $v['result'] ?? '',
                    'time' => (float) ($v['exec_time'] ?? 0),
                    'trace' => $v['trace'] ?? '',
                ];
                $time_all += (float) ($v['exec_time'] ?? 0);
            }
            $items = $this->_time_count_changes($items);
            $logs = $this->_show_auto_table($items, [
                'raw' => ['func'],
                'hidden_map' => ['trace' => 'args', 'result' => 'func'],
                'caption' => $iname,
            ]);
            $info = is_callable([$instance, 'info']) ? $instance->info() : [];
            $tabs[$iname] = '<div class="yfd-grid"><div>' . $logs . '</div><div>' . $this->_show_key_val_table($info, ['skip_empty_values' => 1]) . '</div></div>';
        }
        $params['count'] = $counter;
        $params['time'] = $time_all;
        return $this->_tabs($tabs);
    }
public function _debug_resize_images(&$params = [])
    public function _debug_resize_images(&$params = [])
    {
        if ( ! $this->SHOW_RESIZED_IMAGES_LOG || empty($GLOBALS['_RESIZED_IMAGES_LOG'])) {
            return '';
        }
        $params['count'] = count((array) $GLOBALS['_RESIZED_IMAGES_LOG']);
        return $this->_show_auto_table($GLOBALS['_RESIZED_IMAGES_LOG'] ?? null);
    }
public function _debug_rewrite(&$params = [])
    public function _debug_rewrite(&$params = [])
    {
        if ( ! $this->SHOW_REWRITE_INFO) {
            return '';
        }
        $data = $this->_get_debug_data('rewrite');
        if (empty($data)) {
            return '';
        }
        $items = [];
        foreach ((array) $data as $k => $v) {
            $items[] = [
                'id' => $k + 1,
                'source' => (string) ($v['source'] ?? ''),
                'rewrited' => $this->_admin_link('link', (string) ($v['rewrited'] ?? '')),
                'time' => (float) ($v['exec_time'] ?? 0),
                'trace' => $v['trace'] ?? '',
            ];
        }
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        $params['time'] = (float) $this->_get_debug_data('rewrite_exec_time');
        return $this->_show_auto_table($items, [
            'raw' => ['rewrited'],
            'caption' => [
                'rewrite processing time' => $this->_ms($params['time']),
            ],
            'hidden_map' => [
                'trace' => 'source',
            ],
        ]);
    }
public function _debug_sphinxsearch(&$params = [])

SphinxQL statements (Sphinx / Manticore search drivers and the legacy yf_sphinxsearch client). The server status is read only through a connection the request opened already: the console does not connect to a search server itself.

    public function _debug_sphinxsearch(&$params = [])
    {
        if ( ! $this->SHOW_SPHINX) {
            return '';
        }
        $items = $this->_get_debug_data('sphinxsearch');
        if ( ! $items) {
            return '';
        }
        $sphinx_connect_debug = [];
        $time = 0.0;
        $hosts = [];
        foreach ((array) $items as $id => $item) {
            if (($item['query'] ?? '') == 'sphinx connect') {
                $sphinx_connect_debug = $item;
                unset($items[$id]);
                continue;
            }
            $time += (float) ($item['time'] ?? 0);
            isset($item['host']) && $hosts[(string) $item['host']] = true;
            $items[$id] = ['id' => $id + 1] + (array) $item;
        }
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        $params['time'] = $time;
        $sphinx = _class('sphinxsearch');
        $caption = ['host' => implode(', ', array_keys($hosts)) ?: $sphinx->_get_host()];
        $status = [];
        if ( ! empty($sphinx->sphinx_connection)) {
            $caption['version'] = $sphinx->_get_server_version();
            $status = $sphinx->_get_server_status();
        }
        $body = $this->_show_auto_table($items, ['caption' => $caption, 'hidden_map' => ['trace' => 'query', 'meta' => 'count', 'describe' => 'count', 'results' => 'count']]);
        $body .= $sphinx_connect_debug ? $this->_heading('connect') . $this->_pre($this->_var_export($sphinx_connect_debug)) : '';
        $body .= $status ? $this->_heading('SHOW STATUS') . $this->_show_key_val_table($status) : '';
        return $body;
    }

_debug_ssh

public function _debug_ssh(&$params = [])
    public function _debug_ssh(&$params = [])
    {
        if ( ! $this->SHOW_SSH) {
            return '';
        }
        // Need to enable only when ssh was used
        if ( ! isset(main()->modules['ssh'])) {
            return '';
        }
        return $this->_show_key_val_table(_class('ssh')->_debug);
    }

_debug_stpls

public function _debug_stpls(&$params = [])
    public function _debug_stpls(&$params = [])
    {
        if ( ! $this->SHOW_STPLS) {
            return '';
        }
        $data = _class('tpl')->driver->CACHE ?? [];
        $debug = _class('tpl')->driver->debug ?? [];
        if ($this->SORT_TEMPLATES_BY_NAME && ! empty($data)) {
            ksort($data);
        }
        $stpl_vars = (array) $this->_get_debug_data('STPL_REPLACE_VARS');
        $stpl_traces = (array) $this->_get_debug_data('STPL_TRACES');

        $total_size = 0;
        $total_stpls_exec_time = 0;
        $counter = 0;

        $items = [];
        foreach ((array) $data as $k => $v) {
            if (empty($v['calls'])) {
                continue;
            }
            $cur_size = strlen((string) ($v['string'] ?? ''));
            $total_size += $cur_size;
            $total_stpls_exec_time += (float) ($v['exec_time'] ?? 0);

            $items[$counter] = [
                'id' => ++$counter,
                'name' => $this->_admin_link('edit_stpl', $k, false, ['{LOCATION}' => urlencode((string) ($debug[$k]['storage'] ?? ''))]),
                'calls' => (string) ($v['calls']),
                'driver' => (string) ($v['driver'] ?? ''),
                'compiled' => (int) ($v['is_compiled'] ?? 0),
                'storage' => (string) ($debug[$k]['storage'] ?? ''),
                'storages' => $debug[$k]['storages'] ?? '',
                'size' => (string) $cur_size,
                'time' => (float) ($v['exec_time'] ?? 0),
                'trace' => $stpl_traces[$k] ?? '',
            ];
            if (isset($stpl_vars[$counter])) {
                $items[$counter]['vars'] = $stpl_vars[$counter];
            }
        }
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        $params['time'] = $total_stpls_exec_time;
        return $this->_show_auto_table($items, [
            'raw' => ['name'],
            'caption' => [
                'tpl_driver' => tpl()->DRIVER_NAME,
                'compile_mode' => (int) tpl()->COMPILE_TEMPLATES,
                'templates_size' => $this->_bytes($total_size),
            ],
            'hidden_map' => [
                'trace' => 'name',
                'vars' => 'name',
                'storages' => 'name',
            ],
        ]);
    }

_debug_table2

public function _debug_table2(&$params = [])
    public function _debug_table2(&$params = [])
    {
        if ( ! $this->SHOW_TABLE2) {
            return '';
        }
        $i = 0;
        $items = (array) $this->_get_debug_data('table2');
        foreach ($items as $k => $v) {
            $items[$k] = ['id' => ++$i] + (array) $v;
        }
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        return $this->_show_auto_table($items, ['hidden_map' => ['trace' => 'header_links', 'fields' => 'header_links', 'buttons' => 'header_links']]);
    }

_debug_url

public function _debug_url(&$params = [])
    public function _debug_url(&$params = [])
    {
        if ( ! $this->SHOW_REWRITE_INFO) {
            return '';
        }
        $items = (array) $this->_get_debug_data('_url');
        foreach ($items as $k => $v) {
            $items[$k]['time'] = (float) ($v['time'] ?? 0);
            $items[$k]['rewrited_link'] = $this->_admin_link('link', (string) ($v['rewrited_link'] ?? ''));
        }
        $items = $this->_time_count_changes($items);
        $params['count'] = count($items);
        return $this->_show_auto_table($items, ['raw' => ['rewrited_link'], 'hidden_map' => ['trace' => 'params']]);
    }
public function _do_debug_db_connection_queries($db, $connect_trace = [])
    public function _do_debug_db_connection_queries($db, $connect_trace = [])
    {
        if ( ! $this->SHOW_DB_QUERY_LOG) {
            return '';
        }
        if ( ! is_object($db) || ! is_array($db->_LOG) || ! $db->_tried_to_connect) {
            return false;
        }
        $items = [];
        $db_queries_list = $db->_LOG;
        $db_explain_results = [];
        if ($this->SHOW_DB_EXPLAIN_QUERY && ! empty($db_queries_list) && substr((string) $db->DB_TYPE, 0, 5) == 'mysql') {
            $explained = 0;
            foreach ((array) $db_queries_list as $id => $log) {
                if ( ! empty($log['error']) || $explained >= $this->MAX_EXPLAIN) {
                    continue;
                }
                $sql = trim((string) $log['sql']);
                // Cut comment
                if (substr($sql, 0, 2) == '--') {
                    $sql = substr($sql, strpos($sql, "\n"));
                }
                $sql = preg_replace('/[\s]{2,}/ims', ' ', str_replace("\t", ' ', trim($sql)));
                if (preg_match('/^[\(]*select/ims', $sql)) {
                    $db_explain_results[$id] = $db->get_all('EXPLAIN ' . $sql, -1);
                    $explained++;
                }
            }
        }
        $total_queries_exec_time = 0;
        $dsn = ($db->DB_SSL ? 'SSL ' : '') . $db->DB_TYPE . '://' . $db->DB_USER . '@' . $db->DB_HOST . ($db->DB_PORT ? ':' . $db->DB_PORT : '')
            . '/' . $db->DB_NAME . ($db->DB_CHARSET ? '?charset=' . $db->DB_CHARSET : '') . ($db->DB_SOCKET ? '?socket=' . $db->DB_SOCKET : '');
        $body = '<div class="yfd-cap"><strong>' . $this->_e($dsn) . '</strong>';
        if ($connect_trace) {
            $body .= '<span class="yfd-tgs"><button type="button" class="yfd-tg" data-t="connect" aria-pressed="false">connect trace</button></span>'
                . '<div class="yfd-hid" data-h="connect" hidden>' . $this->_format_trace($connect_trace) . '</div>';
        }
        $body .= '</div>';

        $prefix = (string) $db->DB_PREFIX;
        foreach ((array) $db_queries_list as $id => $log) {
            $sql = trim((string) $log['sql']);
            // Cut comment
            if (substr($sql, 0, 2) == '--') {
                $sql = trim(substr($sql, strpos($sql, "\n")));
                if ( ! strlen($sql)) {
                    continue;
                }
            }
            $total_queries_exec_time += $log['time'];
            $admin_link = $this->_admin_link('sql_query', $sql, true);
            $sql_html = $this->_e($this->_cap($sql, 5000));
            if ($prefix !== '' && $this->ADD_ADMIN_LINKS) {
                $sql_html = preg_replace_callback('/(\s`?)(' . preg_quote($this->_e($prefix), '/') . '[a-z0-9_]+)(`?)/ims', function ($m) {
                    return $m[1] . $this->_admin_link('show_db_table', $m[2]) . $m[3];
                }, $sql_html);
            }
            $exec_time = $this->_ms($log['time']);
            if ($admin_link && $this->ADD_ADMIN_LINKS && $admin_link !== $sql) {
                $exec_time = '<a href="' . $this->_e($admin_link) . '" rel="nofollow noindex" target="_blank">' . $exec_time . '</a>';
            }
            $items[] = [
                'id' => $id + 1,
                'sql' => '<span class="yfd-sql">' . $sql_html . '</span>',
                'rows' => (string) ($log['rows'] ?? ''),
                'insert_id' => (string) ($log['insert_id'] ?? ''),
                'error' => $log['error'] ?? '',
                'warning' => $log['warning'] ?? '',
                'info' => $log['info'] ?? '',
                'exec_time' => $exec_time,
                'time' => (float) $log['time'],
                'trace' => $log['trace'] ?? '',
                'explain' => isset($db_explain_results[$id]) ? $this->_format_db_explain_result($db_explain_results[$id]) : '',
            ];
        }
        $items = $this->_time_count_changes($items);
        foreach ((array) $items as $k => $v) {
            unset($items[$k]['time']);
        }
        return $body . $this->_show_auto_table($items, [
            'raw' => ['sql', 'exec_time', 'explain'],
            'tr' => function ($row, $id) {
                return $row['error'] ? ' class="error"' : '';
            },
            'caption' => [
                'total_exec_time' => $this->_ms($total_queries_exec_time),
                'connect_time' => $this->_ms($db->_connection_time),
            ],
            'hidden_map' => [
                'explain' => 'sql',
                'trace' => 'sql',
                'error' => 'sql',
                'warning' => 'sql',
                'info' => 'sql',
            ],
        ]);
    }

_dump

public function _dump($var, $depth, &$budget, array &$seen)
  • $depth int
  • $budget int bytes left
  • $seen array ids of the objects on the current path
    public function _dump($var, $depth, &$budget, array &$seen)
    {
        if ($budget <= 0) {
            return '...';
        }
        if (is_array($var) || is_object($var)) {
            $id = is_object($var) ? spl_object_id($var) : null;
            if ($var instanceof Closure) {
                return 'Closure';
            }
            if ($id !== null && isset($seen[$id])) {
                return get_class($var) . ' {*recursion*}';
            }
            if ($depth >= 6) {
                return $id !== null ? get_class($var) . ' {...}' : '[...]';
            }
            $data = $id !== null ? get_object_vars($var) : $var;
            $open = $id !== null ? get_class($var) . ' {' : '[';
            $close = $id !== null ? '}' : ']';
            if ( ! $data) {
                return $open . $close;
            }
            $id !== null && $seen[$id] = true;
            $pad = str_repeat('  ', $depth + 1);
            $list = $id === null && array_is_list($data);
            $out = $open . "\n";
            $i = 0;
            foreach ($data as $k => $v) {
                if ($budget <= 0 || ++$i > 200) {
                    $out .= $pad . '... ' . (count($data) - $i + 1) . " more\n";
                    break;
                }
                $line = $pad . ($list ? '' : var_export($k, true) . ' => ') . $this->_dump($v, $depth + 1, $budget, $seen) . ",\n";
                $budget -= strlen($line);
                $out .= $line;
            }
            if ($id !== null) {
                unset($seen[$id]);
            }
            return $out . str_repeat('  ', $depth) . $close;
        }
        if (is_string($var)) {
            $var = strlen($var) > $budget ? mb_strcut($var, 0, max(0, $budget), 'UTF-8') . '...' : $var;
            return var_export($var, true);
        }
        if (is_resource($var)) {
            return 'resource(' . get_resource_type($var) . ')';
        }
        return var_export($var, true);
    }

_e

public function _e($value)

HTML-escaped text; arrays and objects are dumped first.

    public function _e($value)
    {
        if (is_array($value) || is_object($value)) {
            $value = $this->_var_export($value);
        }
        // NUL is in the names of anonymous classes
        return htmlspecialchars(str_replace("\0", '\\0', (string) $value), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    }
public function _format_db_explain_result($explain_result = [])
    public function _format_db_explain_result($explain_result = [])
    {
        if (empty($explain_result) || ! is_array($explain_result)) {
            return false;
        }
        $first = (array) reset($explain_result);
        $body = '<table class="yfd-t"><thead><tr>';
        foreach ($first as $k => $v) {
            $body .= '<th>' . $this->_e($k) . '</th>';
        }
        $body .= '</tr></thead><tbody>';
        foreach ($explain_result as $_data) {
            $body .= '<tr>';
            foreach ((array) $_data as $v) {
                $body .= '<td>' . $this->_e($v ?? '') . '</td>';
            }
            $body .= '</tr>';
        }
        return $body . '</tbody></table>';
    }

_format_trace

public function _format_trace($trace)

Escaped stack trace (string or frames), paths shortened, at most MAX_TRACE_FRAMES frames.

    public function _format_trace($trace)
    {
        if (is_array($trace)) {
            $trace = implode(PHP_EOL, array_map(function ($v) {
                return is_scalar($v) ? (string) $v : $this->_var_export($v);
            }, $trace));
        }
        $lines = explode("\n", $this->_short_paths(trim((string) $trace)));
        $max = max(1, (int) $this->MAX_TRACE_FRAMES);
        $more = max(0, count($lines) - $max);
        $lines = array_slice($lines, 0, $max);
        if ( ! $this->_lazy) {
            $more && $lines[] = '... ' . $more . ' more';
            return $this->_pre(implode("\n", $lines), 'yfd-trace');
        }
        // frames are stored once and numbered again by console.js: the traces of a request share most of their frames
        $ids = [];
        foreach ($lines as $line) {
            $line = preg_replace('~^#\d+\s+~', '', $line);
            if ( ! isset($this->_data_ids['f'][$line])) {
                $this->_data_ids['f'][$line] = count($this->_data['f']);
                $this->_data['f'][] = $line;
            }
            $ids[] = $this->_data_ids['f'][$line];
        }
        $more && $ids[] = -$more;
        $key = implode(',', $ids);
        if ( ! isset($this->_data_ids['t'][$key])) {
            $this->_data_ids['t'][$key] = count($this->_data['t']);
            $this->_data['t'][] = $ids;
        }
        return '<pre class="yfd-pre yfd-trace' . (count($ids) > 6 ? ' yfd-clip' : '') . '" data-tr="' . $this->_data_ids['t'][$key] . '"></pre>';
    }
public function _format_trace_in_items($items)
    public function _format_trace_in_items($items)
    {
        foreach ((array) $items as $k => $v) {
            // raw traces only: _show_auto_table() formats them once, as it always did
            if (is_array($v) && isset($v['trace']) && $v['trace'] !== '' && $v['trace'] !== []) {
                $items[$k]['trace'] = $this->_format_trace($v['trace']);
            }
        }
        return $items;
    }
public function _get_app_version()
    public function _get_app_version()
    {
        return $this->_version_links(APP_PATH, '.app_version');
    }
public function _get_debug_data($name)
    public function _get_debug_data($name)
    {
        $this->_used_debug_datas[$name] = ($this->_used_debug_datas[$name] ?? 0) + 1;
        $data = debug($name);
        $this->backup_debug_data[$name] = $data;
        debug($name, false);
        return $data;
    }
public function _get_git_details($FS_PATH, $as_submodule = false)
    public function _get_git_details($FS_PATH, $as_submodule = false)
    {
        $git_base_path = $FS_PATH . '.git';
        if ( ! file_exists($git_base_path)) {
            return [];
        }
        $git_head_path = $git_base_path . '/HEAD';
        $git_branch = '';
        $git_hash = '';
        $git_date = 0;
        if ( ! file_exists($git_head_path) && file_exists($git_base_path) && is_file($git_base_path)) {
            // gitdir: ../.git/modules/yf
            list(, $git_base_path) = explode('gitdir:', file_get_contents($git_base_path)) + [1 => ''];
            $git_base_path = trim((string) $git_base_path);
            // a worktree names its git dir with an absolute path
            $git_base_path = realpath(str_starts_with($git_base_path, '/') ? $git_base_path : $FS_PATH . $git_base_path);
            $git_head_path = $git_base_path && file_exists($git_base_path) ? $git_base_path . '/HEAD' : '';
        }
        if ($git_head_path && file_exists($git_head_path)) {
            // ref: refs/heads/master (a detached HEAD holds the hash itself)
            $head = trim((string) file_get_contents($git_head_path));
            list(, $git_subhead_path) = explode('ref:', $head) + [1 => ''];
            $git_subhead_path = trim((string) $git_subhead_path);
            $git_branch = basename($git_subhead_path);
            $git_hash_file = $git_subhead_path ? $git_base_path . '/' . $git_subhead_path : '';
            $git_hash = $git_hash_file && file_exists($git_hash_file) ? trim(file_get_contents($git_hash_file)) : '';
            if ( ! $git_hash && $git_subhead_path) {
                $git_hash_file = $git_base_path . '/refs/remotes/origin/' . $git_branch;
                $git_hash = file_exists($git_hash_file) ? trim(file_get_contents($git_hash_file)) : '';
            }
            if ( ! $git_hash && ! $git_subhead_path && preg_match('~^[0-9a-f]{40,64}$~', $head)) {
                $git_hash = $head;
            }
        }
        $git_log_path = $git_base_path . '/logs/HEAD';
        if ($git_hash && file_exists($git_log_path)) {
            // ad814593f2844620e37ae220aea8a746d6a53efa 54b490f737cb00c701b3d7439be6d8032d398e96 Some author <[email protected]> 1412937971 +0300    commit: db real mysql tests up
            foreach ((array) file($git_log_path) as $line) {
                if (strpos($line, $git_hash) === false) {
                    continue;
                }
                $line = trim($line);
                $hash = trim(substr($line, 41, 40));
                if ($hash === $git_hash) {
                    list($tmp) = explode("\t", $line);
                    $git_date = (int) substr($tmp, -strlen('1412937971 +0300'), -strlen(' +0300'));
                    break;
                }
            }
        }
        $git_config_path = $git_base_path . '/config';
        $url_part = '';
        if (file_exists($git_config_path)) {
            $config = file_get_contents($git_config_path);
            $pattern1 = '/origin"\].+?url\s+=\s+(git@|https:\/\/)(?P<url_part>.+?).git/ims';
            $pattern2 = '/submodule\s+"yf"\].+?url\s+=\s+(git@|https:\/\/)(?P<url_part>.+?).git/ims';
            if (preg_match($pattern1, $config, $m) || preg_match($pattern2, $config, $m)) {
                $url_part = str_replace(':', '/', $m['url_part']);
            }
        }
        return [
            'hash' => $git_hash,
            'date' => $git_date,
            'url' => $url_part !== '' ? 'https://' . $url_part . '/tree/' : '',
            'branch' => $git_branch,
        ];
    }
public function _get_request_headers()
    public function _get_request_headers()
    {
        $arh = [];
        $rx_http = '/(\AHTTP_)/';
        foreach ((array) $_SERVER as $key => $val) {
            if ( ! preg_match($rx_http, $key, $m)) {
                continue;
            }
            $arh_key = str_replace($m[1], '', $key);
            $rx_matches = explode('_', $arh_key);
            if (count((array) $rx_matches) > 0 && strlen($arh_key) > 2) {
                foreach ($rx_matches as $ak_key => $ak_val) {
                    $rx_matches[$ak_key] = ucwords(strtolower($ak_val));
                }
                $arh_key = implode('-', $rx_matches);
            }
            $arh[$arh_key] = $val;
        }
        return $arh;
    }
public function _get_yf_version()
    public function _get_yf_version()
    {
        return $this->_version_links(YF_PATH, '.yf_version');
    }

_heading

public function _heading($text)
    public function _heading($text)
    {
        return '<div class="yfd-h">' . $this->_e($text) . '</div>';
    }

_hidden_cell

public function _hidden_cell($value, $raw = false)

A value opened by a toggle: text goes into the lazy data while go() renders, HTML stays inline.

    public function _hidden_cell($value, $raw = false)
    {
        if ($raw || ! $this->_lazy || is_bool($value) || $value === null) {
            return $this->_cell($value, $raw);
        }
        return $this->_lazy_pre(is_array($value) || is_object($value) ? $this->_var_export($value) : (string) $value);
    }

_init

public function _init()

Constructor.

    public function _init()
    {
        $this->_NOT_TRANSLATED_FILE = PROJECT_PATH . 'logs/not_translated_' . conf('language') . '.php';
        $name = 'debug_console_light';
        $this->DEBUG_CONSOLE_LIGHT = (int) ((bool) (isset($_SESSION[$name]) ? ($_SESSION[$name] ?? null) : ($_GET[$name] ?? null)));
        $name = 'debug_console_hidden';
        $this->DEBUG_CONSOLE_HIDDEN = (int) ((bool) (isset($_SESSION[$name]) ? ($_SESSION[$name] ?? null) : ($_GET[$name] ?? null)));
        if ($this->DEBUG_CONSOLE_HIDDEN) {
            $this->DEBUG_CONSOLE_LIGHT = true;
        }
    }

_lazy_json

public function _lazy_json()

The lazy data as JSON safe inside <script> (no "<", ">" or "&" left), read by console.js when a value is opened.

    public function _lazy_json()
    {
        $json = json_encode($this->_data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
        return $json === false ? '{}' : $json;
    }

_lazy_pre

public function _lazy_pre($text, $class = '')

An empty <pre> console.js fills (textContent) from the lazy data when it is shown; the same text is stored once.

    public function _lazy_pre($text, $class = '')
    {
        $text = $this->_cap($text);
        $key = strlen($text) > 64 ? md5($text) : $text;
        if ( ! isset($this->_data_ids['b'][$key])) {
            $this->_data_ids['b'][$key] = count($this->_data['b']);
            $this->_data['b'][] = $text;
        }
        $clip = substr_count($text, "\n") > 5 || strlen($text) > 600;
        return '<pre class="yfd-pre' . ($clip ? ' yfd-clip' : '') . ($class ? ' ' . $class : '') . '" data-b="' . $this->_data_ids['b'][$key] . '"></pre>';
    }

_ms

public function _ms($seconds)

Milliseconds of a time in seconds.

    public function _ms($seconds)
    {
        $ms = (float) $seconds * 1000;
        if ($ms >= 100) {
            return round($ms) . ' ms';
        }
        return number_format($ms, $ms >= 10 ? 1 : 2, '.', '') . ' ms';
    }

_pre

public function _pre($text, $class = '')

Escaped <pre> of a long or multi-line text; clipped to a few lines until clicked.

    public function _pre($text, $class = '')
    {
        $text = $this->_cap($text);
        $clip = substr_count($text, "\n") > 5 || strlen($text) > 600;
        return '<pre class="yfd-pre' . ($clip ? ' yfd-clip' : '') . ($class ? ' ' . $class : '') . '">' . $this->_e($text) . '</pre>';
    }
public function _render_console(array $panels, array $meta = [], array $summary = [])
  • $panels array name => html
  • $meta array name => [count, time, bad, hidden]
  • $summary array
    public function _render_console(array $panels, array $meta = [], array $summary = [])
    {
        $tabs = [];
        $panes = [];
        foreach ($panels as $name => $content) {
            if (is_array($content)) {
                $content = $content['content'] ?? '';
            }
            $content = trim((string) $content);
            if ($content === '') {
                continue;
            }
            $m = (array) ($meta[$name] ?? []);
            $id = strtolower(preg_replace('~[^a-z0-9_-]+~i', '', ltrim((string) $name, '_')));
            if ($id === '' || isset($panes[$id])) {
                $id .= '_' . count($panes);
            }
            $label = $this->_e($this->TAB_LABELS[$name] ?? ltrim((string) $name, '_'));
            if (isset($m['count'])) {
                $label .= '<b>' . (int) $m['count'] . '</b>';
            }
            if (isset($m['time']) && (float) $m['time'] > 0) {
                $label .= '<i>' . $this->_ms($m['time']) . '</i>';
            }
            $tabs[$id] = '<button type="button" role="tab" data-tab="' . $id . '" aria-selected="false"'
                . ( ! empty($m['bad']) ? ' class="yfd-bad"' : '') . ( ! empty($m['hidden']) ? ' hidden' : '') . '>' . $label . '</button>';
            $panes[$id] = '<section class="yfd-pane" data-pane="' . $id . '" id="debug_item_' . $id . '" role="tabpanel" hidden><template>'
                . $content . '</template></section>';
        }
        $chips = [];
        if (($summary['route'] ?? '') !== '') {
            $chips[] = '<span class="yfd-chip" title="object::action">' . $this->_e($summary['route']) . '</span>';
        }
        if (isset($summary['time'])) {
            $chips[] = '<span class="yfd-chip" title="Page generation time"><b>' . $this->_ms($summary['time']) . '</b></span>';
        }
        if (isset($summary['db'])) {
            $chips[] = '<span class="yfd-chip" title="Database queries, their time">db <b>' . (int) $summary['db'] . '</b>'
                . ( ! empty($summary['db_time']) ? ' ' . $this->_ms($summary['db_time']) : '') . '</span>';
        }
        if (isset($summary['memory'])) {
            $chips[] = '<span class="yfd-chip" title="Peak memory">' . $this->_bytes($summary['memory']) . '</span>';
        }
        if ( ! empty($summary['errors'])) {
            $chips[] = '<span class="yfd-chip yfd-bad" title="PHP warnings, notices, deprecations">php <b>' . (int) $summary['errors'] . '</b></span>';
        }
        if (isset($summary['console'])) {
            $chips[] = '<span class="yfd-chip" title="Debug console render time">D ' . $this->_ms($summary['console']) . '</span>';
        }
        $bar = '<div class="yfd-bar"><span class="yfd-logo">yf</span>' . implode('', $chips) . '<span class="yfd-sp"></span>'
            . '<input class="yfd-q" type="search" placeholder="filter rows" aria-label="Filter the rows of the open tab">'
            . '<button type="button" data-act="dock" title="Dock to the bottom of the window">dock</button>'
            . '<button type="button" data-act="theme" title="Colours: system, light, dark">theme</button>'
            . '<button type="button" data-act="popup" title="Open in a window">popup</button>'
            . '<button type="button" data-act="min" title="Collapse / expand">–</button></div>';
        return '<yf-debug-console id="debug_console" data-popup="' . (int) (bool) conf('DEBUG_CONSOLE_POPUP') . '">'
            . '<template shadowrootmode="open"><style>' . $this->_console_file('console.css') . '</style>'
            . '<div class="yfd" role="region" aria-label="yf debug console">' . $bar
            . '<nav class="yfd-tabs" role="tablist">' . implode('', $tabs) . '</nav>'
            . '<div class="yfd-panes">' . implode('', $panes) . '</div></div>'
            . '<script type="application/json" class="yfd-data">' . $this->_lazy_json() . '</script></template></yf-debug-console>'
            . '<script>' . $this->_console_file('console.js') . '</script>' . PHP_EOL;
    }

_short_paths

public function _short_paths($text)

File paths of the framework and the app shortened to [yf]/ and [app]/.

    public function _short_paths($text)
    {
        static $replace = null;
        if ($replace === null) {
            $replace = [];
            foreach (['[app]/' => defined('APP_PATH') ? APP_PATH : '', '[yf]/' => defined('YF_PATH') ? YF_PATH : ''] as $label => $path) {
                strlen((string) $path) > 1 && $replace[(string) $path] = $label;
            }
            // the longer (more specific) path first: the app may live inside the framework dir
            uksort($replace, function ($a, $b) {
                return strlen($b) <=> strlen($a);
            });
        }
        return $replace ? str_replace(array_keys($replace), array_values($replace), (string) $text) : (string) $text;
    }
public function _show_auto_table($items = [], $params = [], $name = '')

Table of a list of rows. Cells are escaped (arrays dumped, long text clipped) except the columns in `raw` (trusted HTML), all of them with `no_escape`, and the formatted `trace` / `timep`. hidden_map [column => target column]: the column is not shown, a toggle in the target cell opens it.

  • $params mixed raw, no_escape, hidden_map, tr, caption, header, no_total, no_total_time
    public function _show_auto_table($items = [], $params = [], $name = '')
    {
        if ( ! is_array($items) || ! $items) {
            return false;
        }
        $params = (array) $params;
        $items = $this->_format_trace_in_items($items);
        $no_escape = ! empty($params['no_escape']);
        $raw = array_flip(array_merge(['trace', 'timep'], (array) ($params['raw'] ?? [])));
        $hidden_map = (array) ($params['hidden_map'] ?? []);
        $cols = [];
        foreach ($items as $row) {
            foreach ((array) $row as $k => $v) {
                $cols[$k] = true;
            }
        }
        foreach ($hidden_map as $h => $to) {
            unset($cols[$h]);
        }
        if ( ! $cols) {
            return false;
        }
        $first = array_key_first($cols);
        $total_time = 0.0;
        $rows = '';
        foreach ($items as $id => $row) {
            $row = (array) $row;
            if (isset($row['time']) && is_numeric($row['time'])) {
                $total_time += (float) $row['time'];
            }
            $tr = isset($params['tr']) && is_callable($params['tr']) ? $this->_tr_attrs($params['tr']($row, $id, $id)) : '';
            $cells = '';
            foreach ($cols as $col => $_) {
                $v = $row[$col] ?? '';
                $toggles = '';
                $hidden = '';
                foreach ($hidden_map as $h => $to) {
                    $to = isset($cols[$to]) ? $to : $first;
                    if ($to !== $col || ! isset($row[$h]) || $row[$h] === '' || $row[$h] === [] || $row[$h] === false) {
                        continue;
                    }
                    $toggles .= '<button type="button" class="yfd-tg" data-t="' . $this->_e($h) . '" aria-pressed="false">' . $this->_e($h) . '</button>';
                    $hidden .= '<div class="yfd-hid" data-h="' . $this->_e($h) . '" hidden>' . $this->_hidden_cell($row[$h], $no_escape || isset($raw[$h])) . '</div>';
                }
                $is_num = is_int($v) || is_float($v) || (is_string($v) && $v !== '' && strlen($v) < 16 && is_numeric($v));
                $class = $is_num || $col === 'time' || $col === 'timep' || $col === 'id' ? ' class="yfd-n"' : '';
                if ($class === '' && is_string($v) && $v !== '' && strlen($v) <= 24 && ! isset($raw[$col]) && strpos($v, ' ') === false) {
                    $class = ' class="yfd-nw"';
                }
                $cells .= '<td' . $class . '>' . $this->_cell($v, $no_escape || isset($raw[$col]), $col)
                    . ($toggles !== '' ? '<span class="yfd-tgs">' . $toggles . '</span>' : '') . $hidden . '</td>';
            }
            $rows .= '<tr' . $tr . '>' . $cells . '</tr>';
        }
        $head = '';
        foreach ($cols as $col => $_) {
            $head .= '<th>' . $this->_e($col) . '</th>';
        }
        $caption = isset($params['header']) && $params['header'] !== '' ? $this->_heading($params['header']) : '';
        if (empty($params['no_total'])) {
            $chips = is_array($params['caption'] ?? null) ? $params['caption'] : [];
            $chips['items'] = count($items);
            if ($total_time > 0 && empty($params['no_total_time'])) {
                $chips['total_time'] = $this->_ms($total_time);
            }
            $caption .= '<div class="yfd-cap">';
            if (isset($params['caption']) && is_string($params['caption']) && $params['caption'] !== '') {
                $caption .= '<strong>' . $this->_e($params['caption']) . '</strong>';
            }
            foreach ($chips as $k => $v) {
                $caption .= '<span>' . $this->_e($k) . ': ' . $this->_e($v) . '</span>';
            }
            $caption .= '</div>';
        }
        return $caption . '<table class="yfd-t"><thead><tr>' . $head . '</tr></thead><tbody>' . $rows . '</tbody></table>';
    }
public function _show_db_shutdown_queries($db)
    public function _show_db_shutdown_queries($db)
    {
        if ( ! $this->SHOW_DB_QUERY_LOG) {
            return '';
        }
        return $this->_show_key_val_table($db->_SHUTDOWN_QUERIES ?? []);
    }
public function _show_db_stats($db)
    public function _show_db_stats($db)
    {
        if ( ! $this->SHOW_DB_STATS) {
            return '';
        }
        $data['stats'] = $db->get_2d('SHOW SESSION STATUS');
        $data['vars'] = $db->get_2d('SHOW VARIABLES');
        $body = '';
        foreach ($data as $name => $_data) {
            $body .= '<div>' . $this->_heading($name) . $this->_show_key_val_table($_data, ['no_total' => 1, 'skip_empty_values' => 1]) . '</div>';
        }
        return '<div class="yfd-grid">' . $body . '</div>';
    }
public function _show_key_val_table($a, $params = [], $name = '')

Two-column table of $a. Keys and values are escaped (arrays dumped) unless no_escape; raw_keys: keys whose value is HTML.

  • $params mixed no_sort, no_escape, raw_keys, skip_empty_values, header
    public function _show_key_val_table($a, $params = [], $name = '')
    {
        if ( ! $a) {
            return false;
        }
        $params = (array) $params;
        if (is_array($a) && empty($params['no_sort'])) {
            ksort($a);
        }
        $no_escape = ! empty($params['no_escape']);
        $raw_keys = array_flip((array) ($params['raw_keys'] ?? []));
        $rows = '';
        foreach ((array) $a as $k => $v) {
            if ( ! empty($params['skip_empty_values']) && ! $v) {
                continue;
            }
            $rows .= '<tr><td class="yfd-k">' . ($no_escape ? $k : $this->_e($k)) . '</td><td>'
                . $this->_cell(is_int($v) || is_float($v) ? (string) $v : $v, $no_escape || isset($raw_keys[$k])) . '</td></tr>';
        }
        if ($rows === '') {
            return false;
        }
        return (isset($params['header']) ? $this->_heading($params['header']) : '') . '<table class="yfd-t yfd-kv"><tbody>' . $rows . '</tbody></table>';
    }

_summary

public function _summary($exec_time)

Figures of the bar: page time, db queries, memory, PHP messages.

    public function _summary($exec_time)
    {
        $db_time = 0.0;
        $db_count = 0;
        foreach ((array) debug('db_instances') as $db) {
            if (is_object($db) && is_array($db->_LOG ?? null)) {
                $db_count += count($db->_LOG);
                foreach ($db->_LOG as $log) {
                    $db_time += (float) ($log['time'] ?? 0);
                }
            }
        }
        if ( ! $db_count && is_object(db())) {
            $db_count = (int) db()->NUM_QUERIES;
        }
        return [
            'time' => $exec_time,
            'db' => $db_count,
            'db_time' => $db_time,
            'memory' => memory_get_peak_usage(),
            'errors' => count((array) debug('php_errors')),
            'route' => ($_GET['object'] ?? '') . (($_GET['action'] ?? '') !== '' ? '::' . $_GET['action'] : ''),
        ];
    }

_tabs

public function _tabs($items, $params = [])

Sub tabs inside a pane (connections, asset lists); empty items are skipped.

  • $items array name => html
  • $params array show_all: every item under its heading, no tabs; no_single_heading: a lone item without its heading
    public function _tabs($items, $params = [])
    {
        $items = array_filter((array) $items, function ($v) {
            return trim((string) $v) !== '';
        });
        if ( ! $items) {
            return '';
        }
        if (count($items) === 1 || ! empty($params['show_all'])) {
            $out = '';
            foreach ($items as $name => $html) {
                $out .= (empty($params['no_single_heading']) || count($items) > 1 ? $this->_heading($name) : '') . $html;
            }
            return $out;
        }
        $buttons = '';
        $panes = '';
        $i = 0;
        foreach ($items as $name => $html) {
            $buttons .= '<button type="button" data-sub="' . $i . '" aria-selected="false">' . $this->_e($name) . '</button>';
            $panes .= '<div data-subpane="' . $i . '"' . ($i ? ' hidden' : '') . '>' . $html . '</div>';
            $i++;
        }
        return '<div class="yfd-sub"><div class="yfd-subtabs">' . $buttons . '</div><div class="yfd-subpanes">' . $panes . '</div></div>';
    }
public function _time_count_changes($items = [], $field = 'time')

Adds `timep`: the share of each row in the summed $field, highlighted when it stands out.

    public function _time_count_changes($items = [], $field = 'time')
    {
        $time_all = 0;
        $time_max = 0;
        foreach ((array) $items as $i => $v) {
            $time = (float) ($v[$field] ?? 0);
            $time_all += $time;
            if ($time > $time_max) {
                $time_max = $time;
            }
        }
        if ( ! $time_all) {
            return $items;
        }
        $warn_limit = $time_max / $time_all * 100 / 2;
        if ($warn_limit < 20) {
            $warn_limit = 20;
        }
        foreach ((array) $items as $i => $v) {
            $time = (float) ($v[$field] ?? 0);
            $timep = round($time / $time_all * 100, 1);
            $items[$i]['timep'] = $timep ? '<span class="yfd-pct' . ($timep > $warn_limit ? ' yfd-warn' : '') . '" style="--p:' . min(100, $timep) . '%">' . $timep . '%</span>' : '';
        }
        return $items;
    }

_tr_attrs

public function _tr_attrs($attrs)

Row attributes from a `tr` callback: ' class="error"' (the old Bootstrap names) or ['class' => ...].

    public function _tr_attrs($attrs)
    {
        $class = is_array($attrs) ? (string) ($attrs['class'] ?? '') : (preg_match('~class=["\']?([\w\s-]+)~', (string) $attrs, $m) ? $m[1] : '');
        $map = ['error' => 'yfd-err', 'danger' => 'yfd-err', 'warning' => 'yfd-err', 'active' => 'yfd-tot', 'info' => 'yfd-tot'];
        $out = [];
        foreach (preg_split('~\s+~', trim($class)) as $c) {
            isset($map[$c]) && $out[] = $map[$c];
        }
        return $out ? ' class="' . implode(' ', array_unique($out)) . '"' : '';
    }

_var_export

public function _var_export($var)

PHP-like dump of a value for the console: short arrays, objects by their properties, depth and size limited, no warnings on recursion (var_export() has them) and no changed parentheses (the global _var_export() turns ")" into "]").

    public function _var_export($var)
    {
        $budget = max(1000, (int) $this->MAX_VALUE_LEN);
        $seen = [];
        return $this->_dump($var, 0, $budget, $seen);
    }