Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.67% covered (success)
96.67%
29 / 30
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
PhpConfigurator
96.67% covered (success)
96.67%
29 / 30
75.00% covered (warning)
75.00%
3 / 4
11
0.00% covered (danger)
0.00%
0 / 1
 fixIncludePath
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 configurePcre
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 registerErrorHandlers
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 configureSession
95.65% covered (success)
95.65%
22 / 23
0.00% covered (danger)
0.00%
0 / 1
7
1<?php
2
3/**
4 * PHP runtime configuration for phpMyFAQ bootstrap
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2012-2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2026-02-08
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Bootstrap;
21
22use phpMyFAQ\Configuration;
23use phpMyFAQ\Session\RedisSessionHandler;
24use RuntimeException;
25
26class PhpConfigurator
27{
28    /** @var null|callable(string): void */
29    private static $redisConfigurator = [RedisSessionHandler::class, 'configure'];
30
31    /**
32     * Ensures '.' is in the PHP include path.
33     */
34    public static function fixIncludePath(): void
35    {
36        $includePaths = explode(PATH_SEPARATOR, (string) ini_get('include_path'));
37        if (!in_array('.', $includePaths, strict: true)) {
38            set_include_path('.' . PATH_SEPARATOR . (string) get_include_path());
39        }
40    }
41
42    /**
43     * Increases PCRE limits to handle large content.
44     */
45    /* @mago-expect lint:no-ini-set - this bootstrap class is the single place that configures PHP runtime settings */
46    public static function configurePcre(): void
47    {
48        ini_set('pcre.backtrack_limit', value: '100000000');
49        ini_set('pcre.recursion_limit', value: '100000000');
50    }
51
52    /**
53     * Registers the phpMyFAQ error and exception handlers.
54     */
55    public static function registerErrorHandlers(): void
56    {
57        set_error_handler(\phpMyFAQ\Core\Error::errorHandler(...));
58        set_exception_handler(\phpMyFAQ\Core\Error::exceptionHandler(...));
59    }
60
61    /**
62     * Configures secure session settings if no session is active yet.
63     */
64    /* @mago-expect lint:no-ini-set - this bootstrap class is the single place that configures PHP runtime settings */
65    public static function configureSession(?Configuration $configuration = null): void
66    {
67        if (session_status() !== PHP_SESSION_ACTIVE) {
68            ini_set('session.use_only_cookies', value: '1');
69            ini_set('session.use_trans_sid', value: '0');
70            ini_set('session.cookie_samesite', value: 'Strict');
71            ini_set('session.cookie_httponly', value: '1');
72            ini_set('session.cookie_secure', value: '1');
73
74            $sessionSavePath = trim((string) ($configuration?->get('session.savePath') ?? ''));
75
76            if ($sessionSavePath !== '') {
77                ini_set('session.save_path', $sessionSavePath);
78            }
79
80            $sessionHandler = strtolower((string) ($configuration?->get('session.handler') ?? 'files'));
81            $redisDsn = trim((string) ($configuration?->get('session.redisDsn') ?? ''));
82
83            switch ($sessionHandler) {
84                case 'files':
85                    ini_set('session.save_handler', value: 'files');
86                    break;
87                case 'redis':
88                    if (self::$redisConfigurator === null) {
89                        throw new RuntimeException('No Redis session configurator is registered.');
90                    }
91
92                    call_user_func(self::$redisConfigurator, $redisDsn);
93                    break;
94                default:
95                    throw new RuntimeException(sprintf(
96                        'Unsupported session handler "%s". Allowed values: files, redis.',
97                        $sessionHandler,
98                    ));
99            }
100        }
101    }
102}